From 25de017c5ab6a4f91f91e0bdf4e0573b2e801588 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Fri, 26 May 2023 16:59:06 -0700 Subject: [PATCH 001/107] fix: fix the hardcoded number of stages printed in logs. (#5210) --- samcli/commands/pipeline/init/interactive_init_flow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/pipeline/init/interactive_init_flow.py b/samcli/commands/pipeline/init/interactive_init_flow.py index e5347be567..b6c93acc4f 100644 --- a/samcli/commands/pipeline/init/interactive_init_flow.py +++ b/samcli/commands/pipeline/init/interactive_init_flow.py @@ -256,7 +256,7 @@ def _generate_from_pipeline_template( continue click.echo( Colored().yellow( - "2 stage(s) were detected, matching the template requirements. " + f"{number_of_stages} stage(s) were detected, matching the template requirements. " "If these are incorrect, delete .aws-sam/pipeline/pipelineconfig.toml and rerun" ) ) From 694ad594202c5f9cedb35e68d1e30c6d090f63d0 Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Fri, 26 May 2023 17:19:17 -0700 Subject: [PATCH 002/107] feat: Linking Authorizers to Lambda functions using the invocation URI (#5196) * Link authorizer to lambda function invoke URI * Updated doc string * Updated exception messages back * Added check for one element in reference list * Updated empty ref list check to not block * Updated log message * Fix long line lint error --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- .../terraform/hooks/prepare/exceptions.py | 14 ++ .../hooks/prepare/resource_linking.py | 89 +++++++++ .../hooks/prepare/resources/apigw.py | 9 + .../hooks/prepare/resources/resource_links.py | 7 + .../prepare/resources/resource_properties.py | 3 + .../hooks/prepare/test_resource_linking.py | 169 +++++++++++++----- 6 files changed, 248 insertions(+), 43 deletions(-) diff --git a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py index e3400a9abf..7168037ae9 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py +++ b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py @@ -193,6 +193,20 @@ class GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitat """ +class OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException(OneResourceLinkingLimitationException): + """ + Exception specific for Gateway Authorizer linking to more than one Lambda Function + """ + + +class GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException( + LocalVariablesLinkingLimitationException +): + """ + Exception specific for Gateway Authorizer linking to Lambda Function using locals. + """ + + class InvalidSamMetadataPropertiesException(UserException): pass diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index e2605353a3..377d7c4321 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -9,6 +9,7 @@ from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( FunctionLayerLocalVariablesLinkingLimitationException, + GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayMethodLocalVariablesLinkingLimitationException, @@ -16,6 +17,7 @@ InvalidResourceLinkingException, LambdaFunctionToApiGatewayIntegrationLocalVariablesLinkingLimitationException, LocalVariablesLinkingLimitationException, + OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, @@ -1086,6 +1088,10 @@ def _link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back( if len(referenced_rest_apis_values) > 1: raise InvalidResourceLinkingException("Could not link multiple Rest APIs to one Gateway resource") + if not referenced_rest_apis_values: + LOG.info("Unable to find any references to Rest APIs, skip linking Gateway resources") + return + logical_id = referenced_rest_apis_values[0] gateway_cfn_resource["Properties"]["RestApiId"] = ( {"Ref": logical_id.value} if isinstance(logical_id, LogicalIdReference) else logical_id.value @@ -1111,6 +1117,10 @@ def _link_gateway_resource_to_gateway_resource_call_back( if len(referenced_gateway_resource_values) > 1: raise InvalidResourceLinkingException("Could not link multiple Gateway Resources to one Gateway resource") + if not referenced_gateway_resource_values: + LOG.info("Unable to find any references to the Gateway Resource, skip linking Gateway resources") + return + logical_id = referenced_gateway_resource_values[0] gateway_resource_cfn_resource["Properties"]["ResourceId"] = ( {"Ref": logical_id.value} if isinstance(logical_id, LogicalIdReference) else logical_id.value @@ -1137,6 +1147,10 @@ def _link_gateway_resource_to_gateway_rest_apis_parent_id_call_back( if len(referenced_rest_apis_values) > 1: raise InvalidResourceLinkingException("Could not link multiple Rest APIs to one Gateway resource") + if not referenced_rest_apis_values: + LOG.info("Unable to find any references to Rest APIs, skip linking Rest API to Gateway resource") + return + logical_id = referenced_rest_apis_values[0] gateway_cfn_resource["Properties"]["ParentId"] = ( {"Fn::GetAtt": [logical_id.value, "RootResourceId"]} @@ -1351,6 +1365,12 @@ def _link_gateway_integration_to_function_call_back( "Could not link multiple Lambda functions to one Gateway integration resource" ) + if not referenced_gateway_resource_values: + LOG.info( + "Unable to find any references to Lambda functions, skip linking Lambda function to Gateway integration" + ) + return + logical_id = referenced_gateway_resource_values[0] gateway_integration_cfn_resource["Properties"]["Uri"] = ( {"Fn::Sub": INVOKE_ARN_FORMAT.format(function_logical_id=logical_id.value)} @@ -1477,3 +1497,72 @@ def _link_gateway_integration_responses_to_gateway_resource( linking_exceptions=exceptions, ) ResourceLinker(resource_linking_pair).link_resources() + + +def _link_gateway_authorizer_to_lambda_function_call_back( + gateway_authorizer_cfn_resource: Dict, lambda_function_resource_values: List[ReferenceType] +) -> None: + """ + Callback function that is used by the linking algorithm to update a CFN Authorizer Resource with + a reference to the Lambda function's invocation URI + + Parameters + ---------- + gateway_authorizer_cfn_resource: Dict + API Gateway Authorizer CFN resource + lambda_function_resource_values: List[ReferenceType] + List of referenced Lambda Functions either as the logical id of Lambda Function reosurces + defined in the customer project, or ARN values for actual Lambda Functions defined + in customer's account. This list should always contain one element only. + """ + if len(lambda_function_resource_values) > 1: + raise InvalidResourceLinkingException("Could not link multiple Lambda functions to one Gateway Authorizer") + + if not lambda_function_resource_values: + LOG.info( + "Unable to find any references to Lambda functions, skip linking Lambda function to Gateway Authorizer" + ) + return + + logical_id = lambda_function_resource_values[0] + gateway_authorizer_cfn_resource["Properties"]["AuthorizerUri"] = ( + {"Fn::Sub": INVOKE_ARN_FORMAT.format(function_logical_id=logical_id.value)} + if isinstance(logical_id, LogicalIdReference) + else logical_id.value + ) + + +def _link_gateway_authorizer_to_lambda_function( + authorizer_config_resources: Dict[str, TFResource], + authorizer_cfn_resources: Dict[str, List], + authorizer_tf_resources: Dict[str, Dict], +) -> None: + """ + Iterate through all the resources and link the corresponding Authorizer to each Lambda Function + + Parameters + ---------- + authorizer_config_resources: Dict[str, TFResource] + Dictionary of configuration Authorizer resources + authorizer_cfn_resources: Dict[str, List] + Dictionary containing resolved configuration address of CFN Authorizer resources + lambda_layers_terraform_resources: Dict[str, Dict] + Dictionary of all actual terraform layers resources (not configuration resources). The dictionary's key is the + calculated logical id for each resource + """ + exceptions = ResourcePairExceptions( + multiple_resource_linking_exception=OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + local_variable_linking_exception=GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + ) + resource_linking_pair = ResourceLinkingPair( + source_resource_cfn_resource=authorizer_cfn_resources, + source_resource_tf_config=authorizer_config_resources, + destination_resource_tf=authorizer_tf_resources, + tf_destination_attribute_name="invoke_arn", + terraform_link_field_name="authorizer_uri", + cfn_link_field_name="AuthorizerUri", + terraform_resource_type_prefix=LAMBDA_FUNCTION_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=_link_gateway_authorizer_to_lambda_function_call_back, + linking_exceptions=exceptions, + ) + ResourceLinker(resource_linking_pair).link_resources() diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py b/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py index 17cc2dc03a..05f1676624 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py @@ -108,6 +108,15 @@ def __init__(self): super(ApiGatewayStageProperties, self).__init__() +class ApiGatewayAuthorizerProperties(ResourceProperties): + """ + Contains the collection logic of the required properties for linking the aws_api_gateway_authorizer resources. + """ + + def __init__(self): + super(ApiGatewayAuthorizerProperties, self).__init__() + + def add_integrations_to_methods( gateway_methods_cfn: Dict[str, List], gateway_integrations_cfn: Dict[str, List] ) -> None: diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py index d9632909c0..45d7a3ed59 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py @@ -1,6 +1,7 @@ from typing import List from samcli.hook_packages.terraform.hooks.prepare.property_builder import ( + TF_AWS_API_GATEWAY_AUTHORIZER, TF_AWS_API_GATEWAY_INTEGRATION, TF_AWS_API_GATEWAY_INTEGRATION_RESPONSE, TF_AWS_API_GATEWAY_METHOD, @@ -11,6 +12,7 @@ TF_AWS_LAMBDA_LAYER_VERSION, ) from samcli.hook_packages.terraform.hooks.prepare.resource_linking import ( + _link_gateway_authorizer_to_lambda_function, _link_gateway_integration_responses_to_gateway_resource, _link_gateway_integration_responses_to_gateway_rest_apis, _link_gateway_integrations_to_function_resource, @@ -71,4 +73,9 @@ dest=TF_AWS_API_GATEWAY_RESOURCE, linking_func=_link_gateway_integration_responses_to_gateway_resource, ), + LinkingPairCaller( + source=TF_AWS_API_GATEWAY_AUTHORIZER, + dest=TF_AWS_LAMBDA_FUNCTION, + linking_func=_link_gateway_authorizer_to_lambda_function, + ), ] diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_properties.py b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_properties.py index af70d58831..30c31aabd0 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_properties.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_properties.py @@ -2,6 +2,7 @@ from typing import Dict from samcli.hook_packages.terraform.hooks.prepare.property_builder import ( + TF_AWS_API_GATEWAY_AUTHORIZER, TF_AWS_API_GATEWAY_INTEGRATION, TF_AWS_API_GATEWAY_INTEGRATION_RESPONSE, TF_AWS_API_GATEWAY_METHOD, @@ -12,6 +13,7 @@ TF_AWS_LAMBDA_LAYER_VERSION, ) from samcli.hook_packages.terraform.hooks.prepare.resources.apigw import ( + ApiGatewayAuthorizerProperties, ApiGatewayMethodProperties, ApiGatewayResourceProperties, ApiGatewayRestApiProperties, @@ -46,4 +48,5 @@ def get_resource_property_mapping() -> Dict[str, ResourceProperties]: TF_AWS_API_GATEWAY_STAGE: ApiGatewayStageProperties(), TF_AWS_API_GATEWAY_INTEGRATION: InternalApiGatewayIntegrationProperties(), TF_AWS_API_GATEWAY_INTEGRATION_RESPONSE: InternalApiGatewayIntegrationResponseProperties(), + TF_AWS_API_GATEWAY_AUTHORIZER: ApiGatewayAuthorizerProperties(), } diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py index 711ff606e2..2e492a2cb1 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py @@ -6,11 +6,13 @@ from parameterized import parameterized from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( + GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, InvalidResourceLinkingException, LocalVariablesLinkingLimitationException, ONE_LAMBDA_LAYER_LINKING_ISSUE_LINK, LOCAL_VARIABLES_SUPPORT_ISSUE_LINK, APPLY_WORK_AROUND_MESSAGE, + OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, OneLambdaLayerLinkingLimitationException, FunctionLayerLocalVariablesLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, @@ -35,6 +37,8 @@ from samcli.hook_packages.terraform.hooks.prepare.resource_linking import ( _clean_references_list, + _link_gateway_authorizer_to_lambda_function, + _link_gateway_authorizer_to_lambda_function_call_back, _resolve_module_output, _resolve_module_variable, _build_module, @@ -1612,7 +1616,7 @@ def test_link_lambda_functions_to_layers( ] ) def test_link_lambda_functions_to_layers_call_back(self, input_function, logical_ids, expected_layers): - lambda_function = input_function.copy() + lambda_function = deepcopy(input_function) _link_lambda_functions_to_layers_call_back(lambda_function, logical_ids) input_function["Properties"]["Layers"] = expected_layers self.assertEqual(lambda_function, input_function) @@ -1938,20 +1942,11 @@ def test_link_gateway_integrations_to_function_resource( def test_link_gateway_methods_to_gateway_rest_apis_call_back( self, input_gateway_method, logical_ids, expected_rest_api ): - gateway_method = input_gateway_method.copy() + gateway_method = deepcopy(input_gateway_method) _link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back(gateway_method, logical_ids) input_gateway_method["Properties"]["RestApiId"] = expected_rest_api self.assertEqual(gateway_method, input_gateway_method) - def test_link_gateway_methods_to_gateway_rest_apis_call_back_multiple_destinations(self): - gateway_method = Mock() - logical_ids = [Mock(), Mock()] - with self.assertRaises( - InvalidResourceLinkingException, - msg="Could not link multiple Rest APIs to one Gateway method resource", - ): - _link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back(gateway_method, logical_ids) - @parameterized.expand( [ ( @@ -1983,20 +1978,11 @@ def test_link_gateway_methods_to_gateway_rest_apis_call_back_multiple_destinatio def test_link_gateway_method_to_gateway_resource_call_back( self, input_gateway_method, logical_ids, expected_resource ): - gateway_method = input_gateway_method.copy() + gateway_method = deepcopy(input_gateway_method) _link_gateway_resource_to_gateway_resource_call_back(gateway_method, logical_ids) input_gateway_method["Properties"]["ResourceId"] = expected_resource self.assertEqual(gateway_method, input_gateway_method) - def test_link_gateway_method_to_gateway_resource_call_back_multiple_destinations(self): - gateway_method = Mock() - logical_ids = [Mock(), Mock()] - with self.assertRaises( - InvalidResourceLinkingException, - msg="Could not link multiple Gateway Resources to one Gateway method resource", - ): - _link_gateway_resource_to_gateway_resource_call_back(gateway_method, logical_ids) - @parameterized.expand( [ ( @@ -2021,27 +2007,18 @@ def test_link_gateway_method_to_gateway_resource_call_back_multiple_destinations "Properties": {}, }, [LogicalIdReference("RestApi")], - {"Fn:GetAtt": ["RestApi", "RootResourceId"]}, + {"Fn::GetAtt": ["RestApi", "RootResourceId"]}, ), ] ) def test_link_gateway_resource_to_gateway_rest_api_parent_id_call_back( self, input_gateway_resource, logical_ids, expected_rest_api ): - gateway_resource = input_gateway_resource.copy() + gateway_resource = deepcopy(input_gateway_resource) _link_gateway_resource_to_gateway_rest_apis_parent_id_call_back(gateway_resource, logical_ids) input_gateway_resource["Properties"]["ParentId"] = expected_rest_api self.assertEqual(gateway_resource, input_gateway_resource) - def test_link_gateway_resource_to_gateway_rest_apis_call_back_multiple_destinations(self): - gateway_resource = Mock() - logical_ids = [Mock(), Mock()] - with self.assertRaises( - InvalidResourceLinkingException, - msg="Could not link multiple Rest APIs to one Gateway resource", - ): - _link_gateway_resource_to_gateway_rest_apis_parent_id_call_back(gateway_resource, logical_ids) - @parameterized.expand( [ ( @@ -2051,7 +2028,7 @@ def test_link_gateway_resource_to_gateway_rest_apis_call_back_multiple_destinati }, [LogicalIdReference("FunctionA")], { - "Fn::Sub": "arn:${{AWS::Partition}}:apigateway:${{AWS::Region}}:lambda:path/2015-03-31/functions/${{FunctionA.Arn}}/invocations" + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${FunctionA.Arn}/invocations" }, ), ( @@ -2069,7 +2046,7 @@ def test_link_gateway_resource_to_gateway_rest_apis_call_back_multiple_destinati }, [LogicalIdReference("RestApi")], { - "Fn::Sub": "arn:${{AWS::Partition}}:apigateway:${{AWS::Region}}:lambda:path/2015-03-31/functions/${{FunctionA.Arn}}/invocations" + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApi.Arn}/invocations" }, ), ] @@ -2077,19 +2054,55 @@ def test_link_gateway_resource_to_gateway_rest_apis_call_back_multiple_destinati def test_link_gateway_integration_to_function_call_back( self, input_gateway_integration, logical_ids, expected_integration ): - gateway_resource = input_gateway_integration.copy() + gateway_resource = deepcopy(input_gateway_integration) _link_gateway_integration_to_function_call_back(gateway_resource, logical_ids) input_gateway_integration["Properties"]["Uri"] = expected_integration self.assertEqual(gateway_resource, input_gateway_integration) - def test_link_gateway_integration_to_function_call_back_multiple_destinations(self): - gateway_integration = Mock() - logical_ids = [Mock(), Mock()] - with self.assertRaises( - InvalidResourceLinkingException, - msg="Could not link multiple Lambda functions to one Gateway integration resource", - ): - _link_gateway_integration_to_function_call_back(gateway_integration, logical_ids) + @parameterized.expand( + [ + ( + _link_gateway_integration_to_function_call_back, + "Could not link multiple Lambda functions to one Gateway integration resource", + ), + ( + _link_gateway_authorizer_to_lambda_function_call_back, + "Could not link multiple Lambda functions to one Gateway Authorizer", + ), + ( + _link_gateway_resource_to_gateway_rest_apis_parent_id_call_back, + "Could not link multiple Rest APIs to one Gateway resource", + ), + ( + _link_gateway_resource_to_gateway_resource_call_back, + "Could not link multiple Gateway Resources to one Gateway resource", + ), + ( + _link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back, + "Could not link multiple Rest APIs to one Gateway resource", + ), + ] + ) + def test_linking_callbacks_raises_multiple_reference_exception(self, linking_call_back_method, expected_message): + with self.assertRaisesRegex(InvalidResourceLinkingException, expected_regex=expected_message): + linking_call_back_method(Mock(), [Mock(), Mock()]) + + @parameterized.expand( + [ + (_link_gateway_integration_to_function_call_back,), + (_link_gateway_authorizer_to_lambda_function_call_back,), + (_link_gateway_resource_to_gateway_rest_apis_parent_id_call_back,), + (_link_gateway_resource_to_gateway_resource_call_back,), + (_link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back,), + ] + ) + def test_linking_callbacks_skips_empty_references(self, linking_call_back_method): + original_props = {"Properties": {}} + passed_props = deepcopy(original_props) + + linking_call_back_method(passed_props, []) + + self.assertEqual(original_props, passed_props) @patch( "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back" @@ -2162,3 +2175,73 @@ def test_link_gateway_integration_response_to_gateway_resource( linking_exceptions=mock_resource_linking_exceptions(), ) mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) + + @parameterized.expand( + [ + ( + { + "Type": "AWS::ApiGateway::Authorizer", + "Properties": {"Uri": "invoke_arn"}, + }, + [LogicalIdReference("Function")], + { + "Fn::Sub": "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations" + }, + ), + ( + { + "Type": "AWS::ApiGateway::Authorizer", + "Properties": {"Uri": "invoke_arn"}, + }, + [ExistingResourceReference("invoke_arn")], + "invoke_arn", + ), + ] + ) + def test_link_gateway_authorizer_to_lambda_function_call_back( + self, input_gateway_authorizer, logical_ids, expected_integration + ): + authorizer = deepcopy(input_gateway_authorizer) + _link_gateway_authorizer_to_lambda_function_call_back(authorizer, logical_ids) + input_gateway_authorizer["Properties"]["AuthorizerUri"] = expected_integration + self.assertEqual(authorizer, input_gateway_authorizer) + + @patch( + "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_authorizer_to_lambda_function_call_back" + ) + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinker") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinkingPair") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourcePairExceptions") + def test_link_gateway_authorizer_to_lambda_function( + self, + mock_resource_linking_exceptions, + mock_resource_linking_pair, + mock_resource_linker, + mock_link_gateway_authorizer_to_lambda_function_call_back, + ): + authorizer_cfn_resources = Mock() + authorizer_config_resources = Mock() + authorizer_tf_resources = Mock() + + _link_gateway_authorizer_to_lambda_function( + authorizer_config_resources, authorizer_cfn_resources, authorizer_tf_resources + ) + + mock_resource_linking_exceptions.assert_called_once_with( + multiple_resource_linking_exception=OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + local_variable_linking_exception=GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + ) + + mock_resource_linking_pair.assert_called_once_with( + source_resource_cfn_resource=authorizer_cfn_resources, + source_resource_tf_config=authorizer_config_resources, + destination_resource_tf=authorizer_tf_resources, + tf_destination_attribute_name="invoke_arn", + terraform_link_field_name="authorizer_uri", + cfn_link_field_name="AuthorizerUri", + terraform_resource_type_prefix=LAMBDA_FUNCTION_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=mock_link_gateway_authorizer_to_lambda_function_call_back, + linking_exceptions=mock_resource_linking_exceptions(), + ) + + mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) From f60e59f237d9870ec39d78f98cd87502c7e41b0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 10:38:18 -0700 Subject: [PATCH 003/107] chore(deps-dev): bump parameterized from 0.8.1 to 0.9.0 in /requirements (#5214) Bumps [parameterized](https://github.com/wolever/parameterized) from 0.8.1 to 0.9.0. - [Changelog](https://github.com/wolever/parameterized/blob/master/CHANGELOG.txt) - [Commits](https://github.com/wolever/parameterized/compare/v0.8.1...v0.9.0) --- updated-dependencies: - dependency-name: parameterized dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 12f2660812..1771b3a6f2 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -17,7 +17,7 @@ types-setuptools==65.4.0.0 # Test requirements pytest==7.2.2 -parameterized==0.8.1 +parameterized==0.9.0 pytest-xdist==3.2.0 pytest-forked==1.6.0 pytest-timeout==2.1.0 From 8065613db08599cdac16fa6f3c22af0ba0ff8360 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 17:38:33 +0000 Subject: [PATCH 004/107] chore(deps-dev): bump filelock from 3.10.7 to 3.12.0 in /requirements (#5213) Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.10.7 to 3.12.0. - [Release notes](https://github.com/tox-dev/py-filelock/releases) - [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/py-filelock/compare/3.10.7...3.12.0) --- updated-dependencies: - dependency-name: filelock dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 1771b3a6f2..b8bccfdecb 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -23,7 +23,7 @@ pytest-forked==1.6.0 pytest-timeout==2.1.0 pytest-rerunfailures==11.1.2 pytest-json-report==1.5.0 -filelock==3.10.7 +filelock==3.12.0 # formatter black==22.6.0 From 4185d83b72d2be7fee142acb85454dac52806712 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 17:38:51 +0000 Subject: [PATCH 005/107] chore(deps): bump attrs from 22.2.0 to 23.1.0 in /requirements (#5212) Bumps [attrs](https://github.com/python-attrs/attrs) from 22.2.0 to 23.1.0. - [Release notes](https://github.com/python-attrs/attrs/releases) - [Changelog](https://github.com/python-attrs/attrs/blob/main/CHANGELOG.md) - [Commits](https://github.com/python-attrs/attrs/compare/22.2.0...23.1.0) --- updated-dependencies: - dependency-name: attrs dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 9a13d3ac2e..71ccb6c0f3 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -8,9 +8,9 @@ arrow==1.2.3 \ --hash=sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1 \ --hash=sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2 # via jinja2-time -attrs==22.2.0 \ - --hash=sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836 \ - --hash=sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99 +attrs==23.1.0 \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 # via # jschema-to-python # jsonschema diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index ad034ca70f..bf6fb04e9e 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -8,9 +8,9 @@ arrow==1.2.3 \ --hash=sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1 \ --hash=sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2 # via jinja2-time -attrs==22.2.0 \ - --hash=sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836 \ - --hash=sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99 +attrs==23.1.0 \ + --hash=sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04 \ + --hash=sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015 # via # jschema-to-python # jsonschema From 6f137dc90c02dad3dc87bb8d49533304df0b33a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 18:29:37 +0000 Subject: [PATCH 006/107] feat: update SAM CLI with latest App Templates commit hash (#5211) * feat: updating app templates repo hash with (a34f563f067e13df3eb350d36461b99397b6cda6) * dummy change to trigger checks * revert dummy commit --------- Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- samcli/runtime_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/runtime_config.json b/samcli/runtime_config.json index fb814034f3..40b2a8b551 100644 --- a/samcli/runtime_config.json +++ b/samcli/runtime_config.json @@ -1,3 +1,3 @@ { - "app_template_repo_commit": "824220f550c2d651dbbf5c64020c453dfcd39c4f" + "app_template_repo_commit": "a34f563f067e13df3eb350d36461b99397b6cda6" } From dc5562a7a324621d478761ddeb7fbcbb4ed82352 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Mon, 29 May 2023 17:06:02 -0700 Subject: [PATCH 007/107] fix: fix failing Terraform integration test cases (#5218) * fix: fix the failing terraform integration test cases * fix: fix the resource address while accessing the module config resources * fix: fix checking the experimental log integration test cases --- .../hook_packages/terraform/hooks/prepare/translate.py | 10 ++++++++-- .../local/invoke/test_invoke_terraform_applications.py | 2 +- .../image_based_lambda_functions_local_backend/main.tf | 1 - .../image_based_lambda_functions_s3_backend/main.tf | 1 - .../terraform/hooks/prepare/test_translate.py | 8 ++++++-- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/samcli/hook_packages/terraform/hooks/prepare/translate.py b/samcli/hook_packages/terraform/hooks/prepare/translate.py index ca02488757..62f8d4da12 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/translate.py +++ b/samcli/hook_packages/terraform/hooks/prepare/translate.py @@ -111,15 +111,21 @@ def _check_unresolvable_values(root_module: dict, root_tf_module: TFModule) -> N # iterate over resources for current module for resource in curr_module.get("resources", []): resource_type = resource.get("type") + resource_name = resource.get("name") + resource_mode = resource.get("mode") resource_mapper = RESOURCE_TRANSLATOR_MAPPING.get(resource_type) if not resource_mapper: continue resource_values = resource.get("values") - resource_full_address = resource.get("address") + resource_address = ( + f"data.{resource_type}.{resource_name}" + if resource_mode == "data" + else f"{resource_type}.{resource_name}" + ) - config_resource_address = get_configuration_address(resource_full_address) + config_resource_address = get_configuration_address(resource_address) config_resource = curr_tf_module.resources[config_resource_address] for prop_builder in resource_mapper.property_builder_mapping.values(): diff --git a/tests/integration/local/invoke/test_invoke_terraform_applications.py b/tests/integration/local/invoke/test_invoke_terraform_applications.py index 3c8053b86e..1421e0fa9b 100644 --- a/tests/integration/local/invoke/test_invoke_terraform_applications.py +++ b/tests/integration/local/invoke/test_invoke_terraform_applications.py @@ -159,7 +159,7 @@ def test_invoke_function_get_experimental_prompted(self): "You can also enable this beta feature with 'sam local invoke --beta-features'." ) self.assertRegex(stdout.decode("utf-8"), terraform_beta_feature_prompted_text) - self.assertTrue(stderr.decode("utf-8").startswith(Colored().yellow(EXPERIMENTAL_WARNING))) + self.assertRegex(stderr.decode("utf-8"), EXPERIMENTAL_WARNING) response = json.loads(stdout.decode("utf-8").split("\n")[2][85:].strip()) expected_response = json.loads('{"statusCode":200,"body":"{\\"message\\": \\"hello world\\"}"}') diff --git a/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_local_backend/main.tf b/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_local_backend/main.tf index 5f7cb18578..821af9b7f6 100644 --- a/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_local_backend/main.tf +++ b/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_local_backend/main.tf @@ -1,6 +1,5 @@ provider "aws" { # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true diff --git a/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_s3_backend/main.tf b/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_s3_backend/main.tf index 99e39ab8e9..df805b56eb 100644 --- a/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_s3_backend/main.tf +++ b/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_s3_backend/main.tf @@ -4,7 +4,6 @@ terraform { provider "aws" { # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py index 164e3c7df6..e8309379db 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py @@ -1116,8 +1116,12 @@ class TestUnresolvableAttributeCheck: @patch("samcli.hook_packages.terraform.hooks.prepare.translate.RESOURCE_TRANSLATOR_MAPPING") @patch("samcli.hook_packages.terraform.hooks.prepare.translate.LOG") def test_module_contains_unresolvables(self, log_mock, mapping_mock): - config_addr = "addr" - module = {"resources": [{"address": config_addr, "values": Mock()}]} + config_addr = "function.func1" + module = { + "resources": [ + {"address": config_addr, "values": Mock(), "type": "function", "mode": "resource", "name": "func1"} + ] + } tf_module = Mock() tf_module_attr = Mock() From 888cb4558817dcd3a6ddd1c1b56a7e4185189256 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 30 May 2023 14:39:03 -0700 Subject: [PATCH 008/107] chore: bump version to 1.85.0 (#5226) --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index c1a3f89daf..87cd027bf6 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.84.0" +__version__ = "1.85.0" From acc0acd10261e038f23bf704cb5779177334f771 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 30 May 2023 16:28:09 -0700 Subject: [PATCH 009/107] chore: use the SAR Application created in testing accounts (#5221) --- .../aws-serverless-application-with-application-id-map.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml b/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml index e50d00fbd3..d1f83aaf40 100644 --- a/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml +++ b/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml @@ -4,7 +4,7 @@ Transform: AWS::Serverless-2016-10-31 Mappings: MappingExample: us-east-2: - ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world + ApplicationId: !Sub arn:aws:serverlessrepo:us-east-1:${AWS::AccountId}:applications/sam-cli-integration-test-sar-app Resources: MyApplication: From f23dc230928f035989c08b3cb7c62c119bca6b2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 23:39:51 +0000 Subject: [PATCH 010/107] chore: update aws_lambda_builders to 1.32.0 (#5215) Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- requirements/reproducible-mac.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index bf6fb04e9e..14dfaf2c06 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -275,6 +275,7 @@ importlib-metadata==6.1.0 \ --hash=sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20 \ --hash=sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09 # via + # attrs # click # flask # jsonpickle From 2e79b5035d42699c1757378a589f36d24b772f6d Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Wed, 31 May 2023 10:08:08 -0700 Subject: [PATCH 011/107] feat: Added linking Gateway Method to Lambda Authorizer (#5228) * Added linking method to authorizer * Fixed docstring spelling mistake --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- .../terraform/hooks/prepare/exceptions.py | 14 ++++ .../hooks/prepare/resource_linking.py | 69 +++++++++++++++++ .../hooks/prepare/resources/resource_links.py | 6 ++ .../hooks/prepare/test_resource_linking.py | 74 +++++++++++++++++++ 4 files changed, 163 insertions(+) diff --git a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py index 7168037ae9..ab42fe70b8 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py +++ b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py @@ -207,6 +207,20 @@ class GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException( """ +class OneGatewayMethodToGatewayAuthorizerLinkingLimitationException(OneResourceLinkingLimitationException): + """ + Exception specific for Gateway Method linking to more than one Gateway Authorizer + """ + + +class GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException( + LocalVariablesLinkingLimitationException +): + """ + Exception specific for Gateway Method linking to Gateway Authorizer using locals. + """ + + class InvalidSamMetadataPropertiesException(UserException): pass diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 377d7c4321..a68d286276 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -10,6 +10,7 @@ from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( FunctionLayerLocalVariablesLinkingLimitationException, GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayMethodLocalVariablesLinkingLimitationException, @@ -18,6 +19,7 @@ LambdaFunctionToApiGatewayIntegrationLocalVariablesLinkingLimitationException, LocalVariablesLinkingLimitationException, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, @@ -50,6 +52,7 @@ LAMBDA_LAYER_RESOURCE_ADDRESS_PREFIX = "aws_lambda_layer_version." API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX = "aws_api_gateway_rest_api." API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX = "aws_api_gateway_resource." +API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX = "aws_api_gateway_authorizer." TERRAFORM_LOCAL_VARIABLES_ADDRESS_PREFIX = "local." DATA_RESOURCE_ADDRESS_PREFIX = "data." @@ -1566,3 +1569,69 @@ def _link_gateway_authorizer_to_lambda_function( linking_exceptions=exceptions, ) ResourceLinker(resource_linking_pair).link_resources() + + +def _link_gateway_method_to_gateway_authorizer_call_back( + gateway_method_cfn_resource: Dict, authorizer_resources: List[ReferenceType] +) -> None: + """ + Callback function that is used by the linking algorithm to update a CFN Method Resource with + a reference to the Lambda Authorizers's Id + + Parameters + ---------- + gateway_method_cfn_resource: Dict + API Gateway Method CFN resource + authorizer_resources: List[ReferenceType] + List of referenced Authorizers either as the logical id of Authorizer resources + defined in the customer project, or ARN values for actual Authorizers defined + in customer's account. This list should always contain one element only. + """ + if len(authorizer_resources) > 1: + raise InvalidResourceLinkingException("Could not link multiple Lambda Authorizers to one Gateway Method") + + if not authorizer_resources: + LOG.info("Unable to find any references to Authorizers, skip linking Gateway Method to Lambda Authorizer") + return + + logical_id = authorizer_resources[0] + gateway_method_cfn_resource["Properties"]["AuthorizerId"] = ( + {"Ref": logical_id.value} if isinstance(logical_id, LogicalIdReference) else logical_id.value + ) + + +def _link_gateway_method_to_gateway_authorizer( + gateway_method_config_resources: Dict[str, TFResource], + gateway_method_config_address_cfn_resources_map: Dict[str, List], + authorizer_resources: Dict[str, Dict], +) -> None: + """ + Iterate through all the resources and link the corresponding + Gateway Method resources to each Gateway Authorizer + + Parameters + ---------- + gateway_method_config_resources: Dict[str, TFResource] + Dictionary of configuration Gateway Methods + gateway_method_config_address_cfn_resources_map: Dict[str, List] + Dictionary containing resolved configuration addresses matched up to the cfn Gateway Stage + authorizer_resources: Dict[str, Dict] + Dictionary of all Terraform Authorizer resources (not configuration resources). + The dictionary's key is the calculated logical id for each resource. + """ + exceptions = ResourcePairExceptions( + multiple_resource_linking_exception=OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, + local_variable_linking_exception=GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, + ) + resource_linking_pair = ResourceLinkingPair( + source_resource_cfn_resource=gateway_method_config_address_cfn_resources_map, + source_resource_tf_config=gateway_method_config_resources, + destination_resource_tf=authorizer_resources, + tf_destination_attribute_name="id", + terraform_link_field_name="authorizer_id", + cfn_link_field_name="AuthorizerId", + terraform_resource_type_prefix=API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=_link_gateway_method_to_gateway_authorizer_call_back, + linking_exceptions=exceptions, + ) + ResourceLinker(resource_linking_pair).link_resources() diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py index 45d7a3ed59..cd882f5815 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py @@ -18,6 +18,7 @@ _link_gateway_integrations_to_function_resource, _link_gateway_integrations_to_gateway_resource, _link_gateway_integrations_to_gateway_rest_apis, + _link_gateway_method_to_gateway_authorizer, _link_gateway_method_to_gateway_resource, _link_gateway_methods_to_gateway_rest_apis, _link_gateway_resources_to_gateway_rest_apis, @@ -78,4 +79,9 @@ dest=TF_AWS_LAMBDA_FUNCTION, linking_func=_link_gateway_authorizer_to_lambda_function, ), + LinkingPairCaller( + source=TF_AWS_API_GATEWAY_METHOD, + dest=TF_AWS_API_GATEWAY_AUTHORIZER, + linking_func=_link_gateway_method_to_gateway_authorizer, + ), ] diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py index 2e492a2cb1..b4afa74564 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py @@ -7,12 +7,14 @@ from parameterized import parameterized from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, InvalidResourceLinkingException, LocalVariablesLinkingLimitationException, ONE_LAMBDA_LAYER_LINKING_ISSUE_LINK, LOCAL_VARIABLES_SUPPORT_ISSUE_LINK, APPLY_WORK_AROUND_MESSAGE, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneLambdaLayerLinkingLimitationException, FunctionLayerLocalVariablesLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, @@ -36,9 +38,12 @@ ) from samcli.hook_packages.terraform.hooks.prepare.resource_linking import ( + API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX, _clean_references_list, _link_gateway_authorizer_to_lambda_function, _link_gateway_authorizer_to_lambda_function_call_back, + _link_gateway_method_to_gateway_authorizer, + _link_gateway_method_to_gateway_authorizer_call_back, _resolve_module_output, _resolve_module_variable, _build_module, @@ -2081,6 +2086,10 @@ def test_link_gateway_integration_to_function_call_back( _link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back, "Could not link multiple Rest APIs to one Gateway resource", ), + ( + _link_gateway_method_to_gateway_authorizer_call_back, + "Could not link multiple Lambda Authorizers to one Gateway Method", + ), ] ) def test_linking_callbacks_raises_multiple_reference_exception(self, linking_call_back_method, expected_message): @@ -2094,6 +2103,7 @@ def test_linking_callbacks_raises_multiple_reference_exception(self, linking_cal (_link_gateway_resource_to_gateway_rest_apis_parent_id_call_back,), (_link_gateway_resource_to_gateway_resource_call_back,), (_link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back,), + (_link_gateway_method_to_gateway_authorizer_call_back,), ] ) def test_linking_callbacks_skips_empty_references(self, linking_call_back_method): @@ -2245,3 +2255,67 @@ def test_link_gateway_authorizer_to_lambda_function( ) mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) + + @parameterized.expand( + [ + ( + [LogicalIdReference("Authorizer")], + {"Ref": "Authorizer"}, + ), + ( + [ExistingResourceReference("Existing123")], + "Existing123", + ), + ] + ) + def test_link_gateway_method_to_gateway_authorizer_call_back(self, logical_ids, expected_reference): + original_method = { + "Type": "AWS::ApiGateway::Method", + "Properties": {"AuthorizerId": "id here"}, + } + new_method = deepcopy(original_method) + + _link_gateway_method_to_gateway_authorizer_call_back(new_method, logical_ids) + + original_method["Properties"]["AuthorizerId"] = expected_reference + self.assertEqual(original_method, new_method) + + @patch( + "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_method_to_gateway_authorizer_call_back" + ) + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinker") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinkingPair") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourcePairExceptions") + def test_link_gateway_method_to_gateway_authorizer( + self, + mock_resource_linking_exceptions, + mock_resource_linking_pair, + mock_resource_linker, + mock_link_gateway_method_to_gateway_authorizer_call_back, + ): + method_cfn_resources = Mock() + method_config_resources = Mock() + authorizer_tf_resources = Mock() + + _link_gateway_method_to_gateway_authorizer( + method_config_resources, method_cfn_resources, authorizer_tf_resources + ) + + mock_resource_linking_exceptions.assert_called_once_with( + multiple_resource_linking_exception=OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, + local_variable_linking_exception=GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, + ) + + mock_resource_linking_pair.assert_called_once_with( + source_resource_cfn_resource=method_cfn_resources, + source_resource_tf_config=method_config_resources, + destination_resource_tf=authorizer_tf_resources, + tf_destination_attribute_name="id", + terraform_link_field_name="authorizer_id", + cfn_link_field_name="AuthorizerId", + terraform_resource_type_prefix=API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=mock_link_gateway_method_to_gateway_authorizer_call_back, + linking_exceptions=mock_resource_linking_exceptions(), + ) + + mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) From ec942e962954d96b44aaf7d3b434acf5c4b9d5e7 Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Wed, 31 May 2023 11:56:42 -0700 Subject: [PATCH 012/107] feat: Return early during linking if no destination resources are found (#5220) * Returns during linking if no destination resources are found * Updated comment to correctly reflect state * Cleaned extra word --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- .../terraform/hooks/prepare/resource_linking.py | 12 ++++++++++-- .../hooks/prepare/test_resource_linking.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index a68d286276..84d67e0808 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -234,9 +234,12 @@ def _link_using_terraform_config(self, source_tf_resource: TFResource, cfn_resou ) if not dest_resources: LOG.debug( - "There are destination resources defined for for the source resource %s", + "There are no destination resources defined for the source resource %s, skipping linking.", source_tf_resource.full_address, ) + + return + for cfn_resource in cfn_resources: self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore @@ -287,7 +290,12 @@ def _link_using_linking_fields(self, cfn_resource: Dict) -> None: else ExistingResourceReference(value) for value in values ] - LOG.debug("The value of the source resource linking field after mapping $s", dest_resources) + + if not dest_resources: + LOG.debug("Skipping linking call back, no destination resources discovered.") + return + + LOG.debug("The value of the source resource linking field after mapping %s", dest_resources) self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore def _process_resolved_resources( diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py index b4afa74564..b363b21fbb 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py @@ -1129,6 +1129,21 @@ def setUp(self) -> None: linking_exceptions=self.linker_exceptions, ) + def test_applied_empty_destination_skip_call_back(self): + resource_linker = ResourceLinker(self.sample_resource_linking_pair) + resource_linker._link_using_linking_fields({"Properties": {"Layers": []}}) + + self.sample_resource_linking_pair.cfn_resource_update_call_back_function.assert_not_called() + + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking._resolve_resource_attribute") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinker._process_resolved_resources") + def test_config_empty_destination_skip_call_back(self, proccess_resolved_res_mock, resolve_resource_attr_mock): + resource_linker = ResourceLinker(self.sample_resource_linking_pair) + proccess_resolved_res_mock.return_value = [] + resource_linker._link_using_terraform_config(Mock(), Mock()) + + self.sample_resource_linking_pair.cfn_resource_update_call_back_function.assert_not_called() + def test_handle_linking_mix_of_applied_and_non_applied_resources(self): cfn_resource_depend_on_applied_resources = { "Type": "AWS::Lambda::Function", From 81b3a71b8bc6c7d405b4d4f8b674271d39ccf4e1 Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Wed, 31 May 2023 14:03:58 -0500 Subject: [PATCH 013/107] chore: Strengthen wording on "no Auth" during deploy (#5231) Co-authored-by: Jacob Fuss Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> --- samcli/commands/deploy/deploy_context.py | 2 +- samcli/commands/deploy/guided_context.py | 2 +- .../commands/deploy/test_guided_context.py | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/samcli/commands/deploy/deploy_context.py b/samcli/commands/deploy/deploy_context.py index 07b883f054..c088518337 100644 --- a/samcli/commands/deploy/deploy_context.py +++ b/samcli/commands/deploy/deploy_context.py @@ -242,7 +242,7 @@ def deploy( for resource, authorization_required in auth_required_per_resource: if not authorization_required: - click.secho(f"{resource} may not have authorization defined.", fg="yellow") + click.secho(f"{resource} has no authentication.", fg="yellow") if use_changeset: try: diff --git a/samcli/commands/deploy/guided_context.py b/samcli/commands/deploy/guided_context.py index 4b65526d84..d5b27b745f 100644 --- a/samcli/commands/deploy/guided_context.py +++ b/samcli/commands/deploy/guided_context.py @@ -224,7 +224,7 @@ def prompt_authorization(self, stacks: List[Stack]): for resource, authorization_required in auth_required_per_resource: if not authorization_required: auth_confirm = confirm( - f"\t{self.start_bold}{resource} may not have authorization defined, Is this okay?{self.end_bold}", + f"\t{self.start_bold}{resource} has no authentication. Is this okay?{self.end_bold}", default=False, ) if not auth_confirm: diff --git a/tests/unit/commands/deploy/test_guided_context.py b/tests/unit/commands/deploy/test_guided_context.py index ce24541a46..a5137a781b 100644 --- a/tests/unit/commands/deploy/test_guided_context.py +++ b/tests/unit/commands/deploy/test_guided_context.py @@ -136,7 +136,7 @@ def test_guided_prompts_check_defaults_public_resources_zips( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -203,7 +203,7 @@ def test_guided_prompts_check_defaults_public_resources_images( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -282,7 +282,7 @@ def test_guided_prompts_check_defaults_public_resources_images_ecr_url( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -406,7 +406,7 @@ def test_guided_prompts_images_missing_repo( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -485,7 +485,7 @@ def test_guided_prompts_images_no_repo( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -702,7 +702,7 @@ def test_guided_prompts_check_configuration_file_prompt_calls( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -766,7 +766,7 @@ def test_guided_prompts_check_parameter_from_template( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -825,7 +825,7 @@ def test_guided_prompts_check_parameter_from_cmd_or_config( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -970,7 +970,7 @@ def test_guided_prompts_check_default_config_region( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), From d517ece302a9e5fdfaa1ab10e231115cad2034fe Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Wed, 31 May 2023 14:16:59 -0700 Subject: [PATCH 014/107] feat: Link Lambda Authorizer to Rest API (#5219) * Link RestApiId property for Lambda Authorizers * Updated docstring * Format files --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- .../terraform/hooks/prepare/exceptions.py | 12 +++++ .../hooks/prepare/resource_linking.py | 46 +++++++++++++++++-- .../hooks/prepare/resources/resource_links.py | 6 +++ .../hooks/prepare/test_resource_linking.py | 41 +++++++++++++++++ 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py index ab42fe70b8..ca8cdf55f6 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py +++ b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py @@ -207,6 +207,18 @@ class GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException( """ +class OneGatewayAuthorizerToRestApiLinkingLimitationException(OneResourceLinkingLimitationException): + """ + Exception specific for Gateway Authorizer linking to more than one Rest API + """ + + +class GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException(LocalVariablesLinkingLimitationException): + """ + Exception specific for Gateway Authorizer linking to Rest APIs using locals. + """ + + class OneGatewayMethodToGatewayAuthorizerLinkingLimitationException(OneResourceLinkingLimitationException): """ Exception specific for Gateway Method linking to more than one Gateway Authorizer diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 84d67e0808..1cfeab8d5f 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -10,6 +10,7 @@ from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( FunctionLayerLocalVariablesLinkingLimitationException, GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException, @@ -19,6 +20,7 @@ LambdaFunctionToApiGatewayIntegrationLocalVariablesLinkingLimitationException, LocalVariablesLinkingLimitationException, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayAuthorizerToRestApiLinkingLimitationException, OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException, @@ -1546,7 +1548,7 @@ def _link_gateway_authorizer_to_lambda_function_call_back( def _link_gateway_authorizer_to_lambda_function( authorizer_config_resources: Dict[str, TFResource], authorizer_cfn_resources: Dict[str, List], - authorizer_tf_resources: Dict[str, Dict], + lamda_function_resources: Dict[str, Dict], ) -> None: """ Iterate through all the resources and link the corresponding Authorizer to each Lambda Function @@ -1557,8 +1559,8 @@ def _link_gateway_authorizer_to_lambda_function( Dictionary of configuration Authorizer resources authorizer_cfn_resources: Dict[str, List] Dictionary containing resolved configuration address of CFN Authorizer resources - lambda_layers_terraform_resources: Dict[str, Dict] - Dictionary of all actual terraform layers resources (not configuration resources). The dictionary's key is the + lamda_function_resources: Dict[str, Dict] + Dictionary of Terraform Lambda Function resources (not configuration resources). The dictionary's key is the calculated logical id for each resource """ exceptions = ResourcePairExceptions( @@ -1568,7 +1570,7 @@ def _link_gateway_authorizer_to_lambda_function( resource_linking_pair = ResourceLinkingPair( source_resource_cfn_resource=authorizer_cfn_resources, source_resource_tf_config=authorizer_config_resources, - destination_resource_tf=authorizer_tf_resources, + destination_resource_tf=lamda_function_resources, tf_destination_attribute_name="invoke_arn", terraform_link_field_name="authorizer_uri", cfn_link_field_name="AuthorizerUri", @@ -1579,6 +1581,42 @@ def _link_gateway_authorizer_to_lambda_function( ResourceLinker(resource_linking_pair).link_resources() +def _link_gateway_authorizer_to_rest_api( + authorizer_config_resources: Dict[str, TFResource], + authorizer_cfn_resources: Dict[str, List], + rest_api_resource: Dict[str, Dict], +) -> None: + """ + Iterate through all the resources and link the corresponding Authorizer to each Rest Api + + Parameters + ---------- + authorizer_config_resources: Dict[str, TFResource] + Dictionary of configuration Authorizer resources + authorizer_cfn_resources: Dict[str, List] + Dictionary containing resolved configuration address of CFN Authorizer resources + rest_api_resource: Dict[str, Dict] + Dictionary of Terraform Rest Api resources (not configuration resources). The dictionary's key is the + calculated logical id for each resource + """ + exceptions = ResourcePairExceptions( + multiple_resource_linking_exception=OneGatewayAuthorizerToRestApiLinkingLimitationException, + local_variable_linking_exception=GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, + ) + resource_linking_pair = ResourceLinkingPair( + source_resource_cfn_resource=authorizer_cfn_resources, + source_resource_tf_config=authorizer_config_resources, + destination_resource_tf=rest_api_resource, + tf_destination_attribute_name="id", + terraform_link_field_name="rest_api_id", + cfn_link_field_name="RestApiId", + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=_link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back, + linking_exceptions=exceptions, + ) + ResourceLinker(resource_linking_pair).link_resources() + + def _link_gateway_method_to_gateway_authorizer_call_back( gateway_method_cfn_resource: Dict, authorizer_resources: List[ReferenceType] ) -> None: diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py index cd882f5815..b91478580a 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py @@ -13,6 +13,7 @@ ) from samcli.hook_packages.terraform.hooks.prepare.resource_linking import ( _link_gateway_authorizer_to_lambda_function, + _link_gateway_authorizer_to_rest_api, _link_gateway_integration_responses_to_gateway_resource, _link_gateway_integration_responses_to_gateway_rest_apis, _link_gateway_integrations_to_function_resource, @@ -79,6 +80,11 @@ dest=TF_AWS_LAMBDA_FUNCTION, linking_func=_link_gateway_authorizer_to_lambda_function, ), + LinkingPairCaller( + source=TF_AWS_API_GATEWAY_AUTHORIZER, + dest=TF_AWS_API_GATEWAY_REST_API, + linking_func=_link_gateway_authorizer_to_rest_api, + ), LinkingPairCaller( source=TF_AWS_API_GATEWAY_METHOD, dest=TF_AWS_API_GATEWAY_AUTHORIZER, diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py index b363b21fbb..cad10e35b3 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py @@ -7,6 +7,7 @@ from parameterized import parameterized from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, InvalidResourceLinkingException, LocalVariablesLinkingLimitationException, @@ -14,6 +15,7 @@ LOCAL_VARIABLES_SUPPORT_ISSUE_LINK, APPLY_WORK_AROUND_MESSAGE, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayAuthorizerToRestApiLinkingLimitationException, OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneLambdaLayerLinkingLimitationException, FunctionLayerLocalVariablesLinkingLimitationException, @@ -42,6 +44,7 @@ _clean_references_list, _link_gateway_authorizer_to_lambda_function, _link_gateway_authorizer_to_lambda_function_call_back, + _link_gateway_authorizer_to_rest_api, _link_gateway_method_to_gateway_authorizer, _link_gateway_method_to_gateway_authorizer_call_back, _resolve_module_output, @@ -2295,6 +2298,44 @@ def test_link_gateway_method_to_gateway_authorizer_call_back(self, logical_ids, original_method["Properties"]["AuthorizerId"] = expected_reference self.assertEqual(original_method, new_method) + @patch( + "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back" + ) + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinker") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinkingPair") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourcePairExceptions") + def test_link_gateway_authorizer_to_rest_api( + self, + mock_resource_linking_exceptions, + mock_resource_linking_pair, + mock_resource_linker, + mock_link_resource_to_rest_api_call_back, + ): + authorizer_cfn_resources = Mock() + authorizer_config_resources = Mock() + rest_api_resources = Mock() + + _link_gateway_authorizer_to_rest_api(authorizer_config_resources, authorizer_cfn_resources, rest_api_resources) + + mock_resource_linking_exceptions.assert_called_once_with( + multiple_resource_linking_exception=OneGatewayAuthorizerToRestApiLinkingLimitationException, + local_variable_linking_exception=GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, + ) + + mock_resource_linking_pair.assert_called_once_with( + source_resource_cfn_resource=authorizer_cfn_resources, + source_resource_tf_config=authorizer_config_resources, + destination_resource_tf=rest_api_resources, + tf_destination_attribute_name="id", + terraform_link_field_name="rest_api_id", + cfn_link_field_name="RestApiId", + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=mock_link_resource_to_rest_api_call_back, + linking_exceptions=mock_resource_linking_exceptions(), + ) + + mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) + @patch( "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_method_to_gateway_authorizer_call_back" ) From 4580dddf7288a5d63a9f7e10238231639790b489 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 22:41:17 +0000 Subject: [PATCH 015/107] feat: updating app templates repo hash with (9ee7db342025a42023882960b23ebfcde1d87422) (#5242) Co-authored-by: GitHub Action --- samcli/runtime_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/runtime_config.json b/samcli/runtime_config.json index 40b2a8b551..d40ef0a840 100644 --- a/samcli/runtime_config.json +++ b/samcli/runtime_config.json @@ -1,3 +1,3 @@ { - "app_template_repo_commit": "a34f563f067e13df3eb350d36461b99397b6cda6" + "app_template_repo_commit": "9ee7db342025a42023882960b23ebfcde1d87422" } From 6ba11c6d6910a2a3b8f8e68475fea5a9f05222a5 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Thu, 1 Jun 2023 16:58:50 -0700 Subject: [PATCH 016/107] fix: handle edge cases with function sync flow in sam sync command (#5222) * fix: handle special cases for function sync flow * update with unit tests * add integration tests * set ADL to false * fix update file methods * address comments * address comments to instantiate FunctionBuildInfo in the beginning --- samcli/commands/build/build_context.py | 78 ++++------- samcli/lib/providers/provider.py | 73 ++++++++++- samcli/lib/providers/sam_function_provider.py | 10 +- .../lib/sync/flows/zip_function_sync_flow.py | 26 ++++ samcli/lib/sync/sync_flow_factory.py | 116 ++++++++++++----- tests/integration/sync/test_sync_code.py | 90 +++++++++++++ .../code/after/pre_zipped_function/app.zip | Bin 0 -> 1084 bytes .../code/before/pre_zipped_function/app.zip | Bin 0 -> 1085 bytes .../sync/code/before/template-pre-zipped.yaml | 14 ++ .../sync/code/before/template-skip-build.yaml | 16 +++ .../commands/buildcmd/test_build_context.py | 106 ++++++++------- tests/unit/commands/buildcmd/test_utils.py | 7 +- .../commands/local/lib/test_local_lambda.py | 7 +- .../unit/commands/local/lib/test_provider.py | 2 + .../local/lib/test_sam_function_provider.py | 40 +++++- .../unit/lib/build_module/test_app_builder.py | 4 +- .../unit/lib/build_module/test_build_graph.py | 4 +- tests/unit/lib/sync/test_sync_flow_factory.py | 121 ++++++++++++------ 18 files changed, 535 insertions(+), 179 deletions(-) create mode 100644 tests/integration/testdata/sync/code/after/pre_zipped_function/app.zip create mode 100644 tests/integration/testdata/sync/code/before/pre_zipped_function/app.zip create mode 100644 tests/integration/testdata/sync/code/before/template-pre-zipped.yaml create mode 100644 tests/integration/testdata/sync/code/before/template-skip-build.yaml diff --git a/samcli/commands/build/build_context.py b/samcli/commands/build/build_context.py index 47d412511e..a6d6b69102 100644 --- a/samcli/commands/build/build_context.py +++ b/samcli/commands/build/build_context.py @@ -5,44 +5,46 @@ import os import pathlib import shutil -from typing import Dict, Optional, List, Tuple, cast +from typing import Dict, Optional, List, Tuple import click -from samcli.commands.build.utils import prompt_user_to_enable_mount_with_write_if_needed, MountMode -from samcli.lib.build.bundler import EsbuildBundlerManager -from samcli.lib.providers.sam_api_provider import SamApiProvider -from samcli.lib.telemetry.event import EventTracker -from samcli.lib.utils.packagetype import IMAGE - -from samcli.commands._utils.template import get_template_data +from samcli.commands._utils.constants import DEFAULT_BUILD_DIR from samcli.commands._utils.experimental import ExperimentalFlag, prompt_experimental +from samcli.commands._utils.template import ( + get_template_data, + move_template, +) from samcli.commands.build.exceptions import InvalidBuildDirException, MissingBuildMethodException +from samcli.commands.build.utils import prompt_user_to_enable_mount_with_write_if_needed, MountMode +from samcli.commands.exceptions import UserException from samcli.lib.bootstrap.nested_stack.nested_stack_manager import NestedStackManager +from samcli.lib.build.app_builder import ( + ApplicationBuilder, + BuildError, + UnsupportedBuilderLibraryVersionError, + ApplicationBuildResult, +) from samcli.lib.build.build_graph import DEFAULT_DEPENDENCIES_DIR +from samcli.lib.build.bundler import EsbuildBundlerManager +from samcli.lib.build.exceptions import ( + BuildInsideContainerError, + InvalidBuildGraphException, +) +from samcli.lib.build.workflow_config import UnsupportedRuntimeException from samcli.lib.intrinsic_resolver.intrinsics_symbol_table import IntrinsicsSymbolTable from samcli.lib.providers.provider import ResourcesToBuildCollector, Stack, Function, LayerVersion +from samcli.lib.providers.sam_api_provider import SamApiProvider from samcli.lib.providers.sam_function_provider import SamFunctionProvider from samcli.lib.providers.sam_layer_provider import SamLayerProvider from samcli.lib.providers.sam_stack_provider import SamLocalStackProvider +from samcli.lib.telemetry.event import EventTracker from samcli.lib.utils.osutils import BUILD_DIR_PERMISSIONS from samcli.local.docker.manager import ContainerManager -from samcli.local.lambdafn.exceptions import ResourceNotFound -from samcli.lib.build.exceptions import BuildInsideContainerError - -from samcli.commands.exceptions import UserException - -from samcli.lib.build.app_builder import ( - ApplicationBuilder, - BuildError, - UnsupportedBuilderLibraryVersionError, - ApplicationBuildResult, +from samcli.local.lambdafn.exceptions import ( + FunctionNotFound, + ResourceNotFound, ) -from samcli.commands._utils.constants import DEFAULT_BUILD_DIR -from samcli.lib.build.workflow_config import UnsupportedRuntimeException -from samcli.local.lambdafn.exceptions import FunctionNotFound -from samcli.commands._utils.template import move_template -from samcli.lib.build.exceptions import InvalidBuildGraphException LOG = logging.getLogger(__name__) @@ -586,7 +588,7 @@ def collect_all_build_resources(self) -> ResourcesToBuildCollector: [ f for f in self.function_provider.get_all() - if (f.name not in excludes) and BuildContext._is_function_buildable(f) + if (f.name not in excludes) and f.function_build_info.is_buildable() ] ) result.add_layers( @@ -650,34 +652,6 @@ def _collect_single_buildable_layer( resource_collector.add_layer(layer) - @staticmethod - def _is_function_buildable(function: Function): - # no need to build inline functions - if function.inlinecode: - LOG.debug("Skip building inline function: %s", function.full_path) - return False - # no need to build functions that are already packaged as a zip file - if isinstance(function.codeuri, str) and function.codeuri.endswith(".zip"): - LOG.debug("Skip building zip function: %s", function.full_path) - return False - # skip build the functions that marked as skip-build - if function.skip_build: - LOG.debug("Skip building pre-built function: %s", function.full_path) - return False - # skip build the functions with Image Package Type with no docker context or docker file metadata - if function.packagetype == IMAGE: - metadata = function.metadata if function.metadata else {} - dockerfile = cast(str, metadata.get("Dockerfile", "")) - docker_context = cast(str, metadata.get("DockerContext", "")) - if not dockerfile or not docker_context: - LOG.debug( - "Skip Building %s function, as it is missing either Dockerfile or DockerContext " - "metadata properties.", - function.full_path, - ) - return False - return True - @staticmethod def is_layer_buildable(layer: LayerVersion): # if build method is not specified, it is not buildable diff --git a/samcli/lib/providers/provider.py b/samcli/lib/providers/provider.py index 3a2a9039ac..fc87e5bc81 100644 --- a/samcli/lib/providers/provider.py +++ b/samcli/lib/providers/provider.py @@ -7,6 +7,7 @@ import os import posixpath from collections import namedtuple +from enum import Enum, auto from typing import TYPE_CHECKING, Any, Dict, Iterator, List, NamedTuple, Optional, Set, Union, cast from samcli.commands.local.cli_common.user_exceptions import ( @@ -21,6 +22,7 @@ ResourceMetadataNormalizer, ) from samcli.lib.utils.architecture import X86_64 +from samcli.lib.utils.packagetype import IMAGE if TYPE_CHECKING: # pragma: no cover # avoid circular import, https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING @@ -35,6 +37,27 @@ CORS_MAX_AGE_HEADER = "Access-Control-Max-Age" +class FunctionBuildInfo(Enum): + """ + Represents information about function's build, see values for details + """ + + # buildable + BuildableZip = auto(), "Regular ZIP function which can be build with SAM CLI" + BuildableImage = auto(), "Regular IMAGE function which can be build with SAM CLI" + # non-buildable + InlineCode = auto(), "A ZIP function which has inline code, non buildable" + PreZipped = auto(), "A ZIP function which points to a .zip file, non buildable" + SkipBuild = auto(), "A Function which is denoted with SkipBuild in metadata, non buildable" + NonBuildableImage = auto(), "An IMAGE function which is missing some information to build, non buildable" + + def is_buildable(self) -> bool: + """ + Returns whether this build info can be buildable nor not + """ + return self in {FunctionBuildInfo.BuildableZip, FunctionBuildInfo.BuildableImage} + + class Function(NamedTuple): """ Named Tuple to representing the properties of a Lambda Function @@ -82,6 +105,8 @@ class Function(NamedTuple): architectures: Optional[List[str]] # The function url configuration function_url_config: Optional[Dict] + # FunctionBuildInfo see implementation doc for its details + function_build_info: FunctionBuildInfo # The path of the stack relative to the root stack, it is empty for functions in root stack stack_path: str = "" # Configuration for runtime management. Includes the fields `UpdateRuntimeOn` and `RuntimeVersionArn` (optional). @@ -105,7 +130,7 @@ def skip_build(self) -> bool: resource. It means that the customer is building the Lambda function code outside SAM, and the provided code path is already built. """ - return self.metadata.get(SAM_METADATA_SKIP_BUILD_KEY, False) if self.metadata else False + return get_skip_build(self.metadata) def get_build_dir(self, build_root_dir: str) -> str: """ @@ -872,6 +897,52 @@ def get_unique_resource_ids( return output_resource_ids +def get_skip_build(metadata: Optional[Dict]) -> bool: + """ + Returns the value of SkipBuild property from Metadata, False if it is not defined + """ + return metadata.get(SAM_METADATA_SKIP_BUILD_KEY, False) if metadata else False + + +def get_function_build_info( + full_path: str, + packagetype: str, + inlinecode: Optional[str], + codeuri: Optional[str], + metadata: Optional[Dict], +) -> FunctionBuildInfo: + """ + Populates FunctionBuildInfo from the given information. + """ + if inlinecode: + LOG.debug("Skip building inline function: %s", full_path) + return FunctionBuildInfo.InlineCode + + if isinstance(codeuri, str) and codeuri.endswith(".zip"): + LOG.debug("Skip building zip function: %s", full_path) + return FunctionBuildInfo.PreZipped + + if get_skip_build(metadata): + LOG.debug("Skip building pre-built function: %s", full_path) + return FunctionBuildInfo.SkipBuild + + if packagetype == IMAGE: + metadata = metadata or {} + dockerfile = cast(str, metadata.get("Dockerfile", "")) + docker_context = cast(str, metadata.get("DockerContext", "")) + + if not dockerfile or not docker_context: + LOG.debug( + "Skip Building %s function, as it is missing either Dockerfile or DockerContext " + "metadata properties.", + full_path, + ) + return FunctionBuildInfo.NonBuildableImage + return FunctionBuildInfo.BuildableImage + + return FunctionBuildInfo.BuildableZip + + def _get_build_dir(resource: Union[Function, LayerVersion], build_root: str) -> str: """ Return the build directory to place build artifact diff --git a/samcli/lib/providers/sam_function_provider.py b/samcli/lib/providers/sam_function_provider.py index 728eb9a164..b7adfa597a 100644 --- a/samcli/lib/providers/sam_function_provider.py +++ b/samcli/lib/providers/sam_function_provider.py @@ -20,7 +20,7 @@ ) from ..build.constants import DEPRECATED_RUNTIMES -from .provider import Function, LayerVersion, Stack +from .provider import Function, LayerVersion, Stack, get_full_path, get_function_build_info from .sam_base_provider import SamBaseProvider from .sam_stack_provider import SamLocalStackProvider @@ -444,12 +444,17 @@ def _build_function_configuration( LOG.debug("--base-dir is not presented, adjusting uri %s relative to %s", codeuri, stack.location) codeuri = SamLocalStackProvider.normalize_resource_path(stack.location, codeuri) + package_type = resource_properties.get("PackageType", ZIP) + function_build_info = get_function_build_info( + get_full_path(stack.stack_path, function_id), package_type, inlinecode, codeuri, metadata + ) + return Function( stack_path=stack.stack_path, function_id=function_id, name=name, functionname=resource_properties.get("FunctionName", name), - packagetype=resource_properties.get("PackageType", ZIP), + packagetype=package_type, runtime=resource_properties.get("Runtime"), memory=resource_properties.get("MemorySize"), timeout=resource_properties.get("Timeout"), @@ -467,6 +472,7 @@ def _build_function_configuration( architectures=resource_properties.get("Architectures", None), function_url_config=resource_properties.get("FunctionUrlConfig"), runtime_management_config=resource_properties.get("RuntimeManagementConfig"), + function_build_info=function_build_info, ) @staticmethod diff --git a/samcli/lib/sync/flows/zip_function_sync_flow.py b/samcli/lib/sync/flows/zip_function_sync_flow.py index e7f738d209..eb16db4eee 100644 --- a/samcli/lib/sync/flows/zip_function_sync_flow.py +++ b/samcli/lib/sync/flows/zip_function_sync_flow.py @@ -3,6 +3,7 @@ import hashlib import logging import os +import shutil import tempfile import uuid from contextlib import ExitStack @@ -226,3 +227,28 @@ def _get_function_api_calls(self) -> List[ResourceAPICall]: @staticmethod def _combine_dependencies() -> bool: return True + + +class ZipFunctionSyncFlowSkipBuildZipFile(ZipFunctionSyncFlow): + """ + Alternative implementation for ZipFunctionSyncFlow, which uses pre-built zip file for running sync flow + """ + + def gather_resources(self) -> None: + self._zip_file = os.path.join(tempfile.gettempdir(), f"data-{uuid.uuid4().hex}") + shutil.copy2(cast(str, self._function.codeuri), self._zip_file) + LOG.debug("%sCreated artifact ZIP file: %s", self.log_prefix, self._zip_file) + self._local_sha = file_checksum(self._zip_file, hashlib.sha256()) + + +class ZipFunctionSyncFlowSkipBuildDirectory(ZipFunctionSyncFlow): + """ + Alternative implementation for ZipFunctionSyncFlow, which doesn't build function but zips folder directly + since function is annotated with SkipBuild inside its Metadata + """ + + def gather_resources(self) -> None: + zip_file_path = os.path.join(tempfile.gettempdir(), f"data-{uuid.uuid4().hex}") + self._zip_file = make_zip_with_lambda_permissions(zip_file_path, self._function.codeuri) + LOG.debug("%sCreated artifact ZIP file: %s", self.log_prefix, self._zip_file) + self._local_sha = file_checksum(cast(str, self._zip_file), hashlib.sha256()) diff --git a/samcli/lib/sync/sync_flow_factory.py b/samcli/lib/sync/sync_flow_factory.py index baa887a803..f9704bf95c 100644 --- a/samcli/lib/sync/sync_flow_factory.py +++ b/samcli/lib/sync/sync_flow_factory.py @@ -1,6 +1,6 @@ """SyncFlow Factory for creating SyncFlows based on resource types""" import logging -from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, cast +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, cast from botocore.exceptions import ClientError @@ -9,7 +9,7 @@ from samcli.lib.bootstrap.nested_stack.nested_stack_manager import NestedStackManager from samcli.lib.build.app_builder import ApplicationBuildResult from samcli.lib.package.utils import is_local_folder, is_zip_file -from samcli.lib.providers.provider import ResourceIdentifier, Stack, get_resource_by_id +from samcli.lib.providers.provider import Function, FunctionBuildInfo, ResourceIdentifier, Stack from samcli.lib.sync.flows.auto_dependency_layer_sync_flow import AutoDependencyLayerParentSyncFlow from samcli.lib.sync.flows.function_sync_flow import FunctionSyncFlow from samcli.lib.sync.flows.http_api_sync_flow import HttpApiSyncFlow @@ -21,7 +21,11 @@ ) from samcli.lib.sync.flows.rest_api_sync_flow import RestApiSyncFlow from samcli.lib.sync.flows.stepfunctions_sync_flow import StepFunctionsSyncFlow -from samcli.lib.sync.flows.zip_function_sync_flow import ZipFunctionSyncFlow +from samcli.lib.sync.flows.zip_function_sync_flow import ( + ZipFunctionSyncFlow, + ZipFunctionSyncFlowSkipBuildDirectory, + ZipFunctionSyncFlowSkipBuildZipFile, +) from samcli.lib.sync.sync_flow import SyncFlow from samcli.lib.utils.boto_utils import ( get_boto_client_provider_with_config, @@ -150,16 +154,35 @@ def load_physical_id_mapping(self) -> None: def _create_lambda_flow( self, resource_identifier: ResourceIdentifier, - resource: Dict[str, Any], application_build_result: Optional[ApplicationBuildResult], ) -> Optional[FunctionSyncFlow]: - resource_properties = resource.get("Properties", dict()) - package_type = resource_properties.get("PackageType", ZIP) - runtime = resource_properties.get("Runtime") - if package_type == ZIP: - # only return auto dependency layer sync if runtime is supported - if self._auto_dependency_layer and NestedStackManager.is_runtime_supported(runtime): - return AutoDependencyLayerParentSyncFlow( + function = self._build_context.function_provider.get(str(resource_identifier)) + if not function: + LOG.warning("Can't find function resource with '%s' logical id", str(resource_identifier)) + return None + + if function.packagetype == ZIP: + return self._create_zip_type_lambda_flow(resource_identifier, application_build_result, function) + if function.packagetype == IMAGE: + return self._create_image_type_lambda_flow(resource_identifier, application_build_result, function) + return None + + def _create_zip_type_lambda_flow( + self, + resource_identifier: ResourceIdentifier, + application_build_result: Optional[ApplicationBuildResult], + function: Function, + ) -> Optional[FunctionSyncFlow]: + if not function.function_build_info.is_buildable(): + if function.function_build_info == FunctionBuildInfo.InlineCode: + LOG.debug( + "No need to create sync flow for a function with InlineCode '%s' resource", str(resource_identifier) + ) + return None + if function.function_build_info == FunctionBuildInfo.PreZipped: + # if codeuri points to zip file, use ZipFunctionSyncFlowSkipBuildZipFile sync flow + LOG.debug("Creating ZipFunctionSyncFlowSkipBuildZipFile for '%s' resource", resource_identifier) + return ZipFunctionSyncFlowSkipBuildZipFile( str(resource_identifier), self._build_context, self._deploy_context, @@ -169,17 +192,22 @@ def _create_lambda_flow( application_build_result, ) - return ZipFunctionSyncFlow( - str(resource_identifier), - self._build_context, - self._deploy_context, - self._sync_context, - self._physical_id_mapping, - self._stacks, - application_build_result, - ) - if package_type == IMAGE: - return ImageFunctionSyncFlow( + if function.function_build_info == FunctionBuildInfo.SkipBuild: + # if function is marked with SkipBuild, use ZipFunctionSyncFlowSkipBuildDirectory sync flow + LOG.debug("Creating ZipFunctionSyncFlowSkipBuildDirectory for '%s' resource", resource_identifier) + return ZipFunctionSyncFlowSkipBuildDirectory( + str(resource_identifier), + self._build_context, + self._deploy_context, + self._sync_context, + self._physical_id_mapping, + self._stacks, + application_build_result, + ) + + # only return auto dependency layer sync if runtime is supported + if self._auto_dependency_layer and NestedStackManager.is_runtime_supported(function.runtime): + return AutoDependencyLayerParentSyncFlow( str(resource_identifier), self._build_context, self._deploy_context, @@ -188,12 +216,40 @@ def _create_lambda_flow( self._stacks, application_build_result, ) - return None + + return ZipFunctionSyncFlow( + str(resource_identifier), + self._build_context, + self._deploy_context, + self._sync_context, + self._physical_id_mapping, + self._stacks, + application_build_result, + ) + + def _create_image_type_lambda_flow( + self, + resource_identifier: ResourceIdentifier, + application_build_result: Optional[ApplicationBuildResult], + function: Function, + ) -> Optional[FunctionSyncFlow]: + if not function.function_build_info.is_buildable(): + LOG.warning("Can't build image type function with '%s' logical id", str(resource_identifier)) + return None + + return ImageFunctionSyncFlow( + str(resource_identifier), + self._build_context, + self._deploy_context, + self._sync_context, + self._physical_id_mapping, + self._stacks, + application_build_result, + ) def _create_layer_flow( self, resource_identifier: ResourceIdentifier, - resource: Dict[str, Any], application_build_result: Optional[ApplicationBuildResult], ) -> Optional[SyncFlow]: layer = self._build_context.layer_provider.get(str(resource_identifier)) @@ -242,7 +298,6 @@ def _create_layer_flow( def _create_rest_api_flow( self, resource_identifier: ResourceIdentifier, - resource: Dict[str, Any], application_build_result: Optional[ApplicationBuildResult], ) -> SyncFlow: return RestApiSyncFlow( @@ -257,7 +312,6 @@ def _create_rest_api_flow( def _create_api_flow( self, resource_identifier: ResourceIdentifier, - resource: Dict[str, Any], application_build_result: Optional[ApplicationBuildResult], ) -> SyncFlow: return HttpApiSyncFlow( @@ -272,7 +326,6 @@ def _create_api_flow( def _create_stepfunctions_flow( self, resource_identifier: ResourceIdentifier, - resource: Dict[str, Any], application_build_result: Optional[ApplicationBuildResult], ) -> Optional[SyncFlow]: return StepFunctionsSyncFlow( @@ -285,7 +338,7 @@ def _create_stepfunctions_flow( ) GeneratorFunction = Callable[ - ["SyncFlowFactory", ResourceIdentifier, Dict[str, Any], Optional[ApplicationBuildResult]], Optional[SyncFlow] + ["SyncFlowFactory", ResourceIdentifier, Optional[ApplicationBuildResult]], Optional[SyncFlow] ] GENERATOR_MAPPING: Dict[str, GeneratorFunction] = { AWS_LAMBDA_FUNCTION: _create_lambda_flow, @@ -308,10 +361,7 @@ def _get_generator_mapping(self) -> Dict[str, GeneratorFunction]: # pylint: dis def create_sync_flow( self, resource_identifier: ResourceIdentifier, application_build_result: Optional[ApplicationBuildResult] = None ) -> Optional[SyncFlow]: - resource = get_resource_by_id(self._stacks, resource_identifier) generator = self._get_generator_function(resource_identifier) - if not generator or not resource: + if not generator: return None - return cast(SyncFlowFactory.GeneratorFunction, generator)( - self, resource_identifier, resource, application_build_result - ) + return cast(SyncFlowFactory.GeneratorFunction, generator)(self, resource_identifier, application_build_result) diff --git a/tests/integration/sync/test_sync_code.py b/tests/integration/sync/test_sync_code.py index 22210a241a..c462339336 100644 --- a/tests/integration/sync/test_sync_code.py +++ b/tests/integration/sync/test_sync_code.py @@ -702,3 +702,93 @@ def test_sync_code_layer(self, layer_path, layer_logical_id, function_logical_id lambda_response = json.loads(self._get_lambda_response(lambda_function)) self.assertIn("extra_message", lambda_response) self.assertEqual(lambda_response.get("message_from_layer"), expected_value) + + +class TestFunctionWithPreZippedCodeUri(TestSyncCodeBase): + template = "template-pre-zipped.yaml" + folder = "code" + dependency_layer = False + + def test_pre_zipped_function(self): + # CFN Api call here to collect all the stack resources + self.stack_resources = self._get_stacks(TestSyncCodeBase.stack_name) + lambda_functions = self.stack_resources.get(AWS_LAMBDA_FUNCTION) + + # first verify current state of the function + for lambda_function in lambda_functions: + if lambda_function == "HelloWorldFunction": + lambda_response = json.loads(self._get_lambda_response(lambda_function)) + self.assertIn("message", lambda_response) + self.assertEqual(lambda_response.get("message"), "hello world") + + # update function code with new values + self.update_file( + self.test_data_path.joinpath(self.folder, "after", "pre_zipped_function", "app.zip"), + self.test_data_path.joinpath(self.folder, "before", "pre_zipped_function", "app.zip"), + ) + + # Run code sync + sync_command_list = self.get_sync_command_list( + template_file=TestSyncCodeBase.template_path, + code=True, + watch=False, + resource_list=["AWS::Serverless::Function"], + stack_name=TestSyncCodeBase.stack_name, + image_repository=self.ecr_repo_name, + s3_prefix=self.s3_prefix, + kms_key_id=self.kms_key, + ) + sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_data_path) + self.assertEqual(sync_process_execute.process.returncode, 0) + + # Verify changed lambda response + for lambda_function in lambda_functions: + if lambda_function == "HelloWorldFunction": + lambda_response = json.loads(self._get_lambda_response(lambda_function)) + self.assertIn("message", lambda_response) + self.assertEqual(lambda_response.get("message"), "hello mars") + + +class TestFunctionWithSkipBuild(TestSyncCodeBase): + template = "template-skip-build.yaml" + folder = "code" + dependency_layer = False + + def test_skip_build(self): + # CFN Api call here to collect all the stack resources + self.stack_resources = self._get_stacks(TestSyncCodeBase.stack_name) + lambda_functions = self.stack_resources.get(AWS_LAMBDA_FUNCTION) + + # first verify current state of the function + for lambda_function in lambda_functions: + if lambda_function == "HelloWorldFunction": + lambda_response = json.loads(self._get_lambda_response(lambda_function)) + self.assertIn("message", lambda_response) + self.assertEqual(lambda_response.get("message"), "hello world") + + # update function code with new values + self.update_file( + self.test_data_path.joinpath(self.folder, "after", "python_function_no_deps", "app_without_numpy.py"), + self.test_data_path.joinpath(self.folder, "before", "python_function_no_deps", "app.py"), + ) + + # Run code sync + sync_command_list = self.get_sync_command_list( + template_file=TestSyncCodeBase.template_path, + code=True, + watch=False, + resource_list=["AWS::Serverless::Function"], + stack_name=TestSyncCodeBase.stack_name, + image_repository=self.ecr_repo_name, + s3_prefix=self.s3_prefix, + kms_key_id=self.kms_key, + ) + sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_data_path) + self.assertEqual(sync_process_execute.process.returncode, 0) + + # Verify changed lambda response + for lambda_function in lambda_functions: + if lambda_function == "HelloWorldFunction": + lambda_response = json.loads(self._get_lambda_response(lambda_function)) + self.assertIn("message", lambda_response) + self.assertEqual(lambda_response.get("message"), "hello mars") diff --git a/tests/integration/testdata/sync/code/after/pre_zipped_function/app.zip b/tests/integration/testdata/sync/code/after/pre_zipped_function/app.zip new file mode 100644 index 0000000000000000000000000000000000000000..756db1b579481d8014fe6ad8f5638a07f6807ebd GIT binary patch literal 1084 zcmWIWW@h1H;9y{2Xe`_t2BhGCn?ZpgK0Y%qvm`!Vub?tCgqML`NwhQthD$5B85mi9 zGBPl*hyXPOc(a2{fr9{`!6HC|IT%Wc5C+$=FfgzI4Nfd5fSLJLtTY9N(ahW%e6Z`b zfxzD1+H6lJIppZuN>yo`-ln5t@+OYkb)(w0ZPS+ax!qlo`hV}FrxuYKZ`U#J(eo5Me4>${sOto_K|x2N@5Uyu7e6V@ZD zEftfF@p9-196Ef3soh}P8sYLoZ&W;+d!8)M-MJ>y`l8u_66uqZnS56)GtaEOardzL z$sbDo>7Qh8KIVMqE-qmgWi+XqwQl+ehnw3P=iU6dY~ABy;h$9F-2QDzz8V$my?nP% z6#vI}JHrBxCCCPeZ#i^JdHTVrmW`K|3VX}ema9zGkN=v}7P>?;ecq+lQx5UiMp%7+ zwZvFdY;JJ_?~T94|1N&X`C`W0c<96}g(E!Gt8I={Hl(QS5? zt7+8#bj_MGht&FK{uQ*mP+`08(fY$bWqugVwYlAW#^>AqwvvBdpMPp6SebrKj=FX5 z_tV|$t)6W4n6+rhw&UMsmu+6OM7L9D_D;!u-Cd{kgKeL+?_Me6&;F8ey?@j};bR9Y zrPVFjd5iLseeS&OP~F6mbKcSJ#<}=X>#lRxc4)54QrYT$AZF2=SL;>N9!3^@*dDep zQ%D<{9cKLn$%7i)b@xRm>9N(u-mvZetoN&)WXut zqSV~fypm$Ql8O>S=~mlISI1XZPm>Ltf&;I$R&xPO0+|-z&B(;Xj5~9{lKPTH5R0U& z!pJ1TjL00wo&#kL7+BI631kwNRX~n~nF$IOWRHM?1qPNhZUzP;seu&W&B_Kcff)$7 L85tO)nL#`Nt0r>H literal 0 HcmV?d00001 diff --git a/tests/integration/testdata/sync/code/before/pre_zipped_function/app.zip b/tests/integration/testdata/sync/code/before/pre_zipped_function/app.zip new file mode 100644 index 0000000000000000000000000000000000000000..20119328edbb2d5cc8c0fb148a8ee0968ceb412c GIT binary patch literal 1085 zcmWIWW@h1H;9y{2Xe`_t2BhGCn?ZpgK0Y%qvm`!Vub?tCgqML`NwhQthD$5B85mi9 zGBPl*hyXPOc(a2{fr9{`!6HC|IT)TI8C=i8z`zDHII*ArX6AjMnJ|oI=HB3gUAGMc z_WsspdpgM>N8eVeO5=21WJJb|y<%NSzBzAiO|z0R^*sHrTJ`kC%>lQfS&v=&arSa` ze$L%!XCD1?JdxX&=5A}bZ);T+IZe~XLH+mMN%x~HSMX)_+7~>~y^@w-^KscJ1s%mB zo8RQNC)g_5^ywCSa6W5Q>g86;3>^8}^!8f4 z>S>L9E|S2S_P?p##8z^zuY-d9hIfKaZEFMh4%s*uO}_VgL9Jo^!T*=*CYeR&dsXwD zakkSuJ$b{$&&Mfp?~WB&$Sz^tW0%Q#UV`np&x|J` zCMWiC8Wyf*o_0cD!%mi;8B*m}b2TlZAFJ(NHSr1`V{m8Cs{PMqv?iatcUP))Q4_zp z`R%7y@2x1-ICg)3z`pHyd%yHH$jzPXnpV((q9?AX5a~Q^=Do9 zHl;WFW2wOH34hYwRo-6mZ~Gf_aMFrCevh4rfdQ0&5J?L;&4N;&05EA4r52WE7NzE< z=9Ludl~j}vO1Royx;nnPdYWwDBpi6HwVDfP63DawZ$>6AX56_0me!Xvf>-mGjO6PSUJn~{M* Ih8e^I0DTW?dH?_b literal 0 HcmV?d00001 diff --git a/tests/integration/testdata/sync/code/before/template-pre-zipped.yaml b/tests/integration/testdata/sync/code/before/template-pre-zipped.yaml new file mode 100644 index 0000000000..75801e5b63 --- /dev/null +++ b/tests/integration/testdata/sync/code/before/template-pre-zipped.yaml @@ -0,0 +1,14 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 + +Globals: + Function: + Timeout: 10 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: pre_zipped_function/app.zip + Handler: app.lambda_handler + Runtime: python3.10 diff --git a/tests/integration/testdata/sync/code/before/template-skip-build.yaml b/tests/integration/testdata/sync/code/before/template-skip-build.yaml new file mode 100644 index 0000000000..ec7b417280 --- /dev/null +++ b/tests/integration/testdata/sync/code/before/template-skip-build.yaml @@ -0,0 +1,16 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 + +Globals: + Function: + Timeout: 10 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: python_function_no_deps/ + Handler: app.lambda_handler + Runtime: python3.10 + Metadata: + SkipBuild: true diff --git a/tests/unit/commands/buildcmd/test_build_context.py b/tests/unit/commands/buildcmd/test_build_context.py index 3cf556a9a1..417ce473e3 100644 --- a/tests/unit/commands/buildcmd/test_build_context.py +++ b/tests/unit/commands/buildcmd/test_build_context.py @@ -4,14 +4,9 @@ from parameterized import parameterized -from samcli.commands.build.utils import MountMode -from samcli.lib.build.build_graph import DEFAULT_DEPENDENCIES_DIR -from samcli.lib.build.bundler import EsbuildBundlerManager -from samcli.lib.utils.osutils import BUILD_DIR_PERMISSIONS -from samcli.lib.utils.packagetype import ZIP, IMAGE -from samcli.local.lambdafn.exceptions import ResourceNotFound from samcli.commands.build.build_context import BuildContext from samcli.commands.build.exceptions import InvalidBuildDirException, MissingBuildMethodException +from samcli.commands.build.utils import MountMode from samcli.commands.exceptions import UserException from samcli.lib.build.app_builder import ( BuildError, @@ -19,8 +14,14 @@ BuildInsideContainerError, ApplicationBuildResult, ) +from samcli.lib.build.build_graph import DEFAULT_DEPENDENCIES_DIR +from samcli.lib.build.bundler import EsbuildBundlerManager from samcli.lib.build.workflow_config import UnsupportedRuntimeException +from samcli.lib.providers.provider import Function, get_function_build_info +from samcli.lib.utils.osutils import BUILD_DIR_PERMISSIONS +from samcli.lib.utils.packagetype import ZIP, IMAGE from samcli.local.lambdafn.exceptions import FunctionNotFound +from samcli.local.lambdafn.exceptions import ResourceNotFound class DeepWrap(Exception): @@ -36,29 +37,46 @@ def __init__(self, name, build_method, codeuri="layer_src", skip_build=False): self.skip_build = skip_build -class DummyFunction: - def __init__( - self, - name, - layers=[], - inlinecode=None, - codeuri="src", - imageuri="image:latest", - packagetype=ZIP, - metadata=None, - skip_build=False, - runtime=None, - ): - self.name = name - self.layers = layers - self.inlinecode = inlinecode - self.codeuri = codeuri - self.imageuri = imageuri - self.full_path = Mock() - self.packagetype = packagetype - self.metadata = metadata if metadata else {} - self.skip_build = skip_build - self.runtime = runtime +def get_function( + name, + layers=None, + inlinecode=None, + codeuri="src", + imageuri="image:latest", + packagetype=ZIP, + metadata=None, + skip_build=False, + runtime=None, +) -> Function: + layers = layers or [] + metadata = metadata or {} + if skip_build: + metadata["SkipBuild"] = "True" + return Function( + function_id=name, + functionname=name, + name=name, + runtime=runtime, + memory=None, + timeout=None, + handler=None, + imageuri=imageuri, + packagetype=packagetype, + imageconfig=None, + codeuri=codeuri, + environment=None, + rolearn=None, + layers=layers, + events=None, + metadata=metadata, + inlinecode=inlinecode, + codesign_config_arn=None, + architectures=None, + function_url_config=None, + stack_path="", + runtime_management_config=None, + function_build_info=get_function_build_info("stack/function", packagetype, inlinecode, codeuri, metadata), + ) class DummyStack: @@ -93,7 +111,7 @@ def test_must_setup_context( layer_provider_mock.get.return_value = layer1 layerprovider = SamLayerProviderMock.return_value = layer_provider_mock - function1 = DummyFunction("func1") + function1 = get_function("func1") func_provider_mock = Mock() func_provider_mock.get.return_value = function1 funcprovider = SamFunctionProviderMock.return_value = func_provider_mock @@ -171,7 +189,7 @@ def test_must_fail_with_illegal_identifier( get_buildable_stacks_mock.return_value = ([stack], []) func_provider_mock = Mock() func_provider_mock.get.return_value = None - func_provider_mock.get_all.return_value = [DummyFunction("func1"), DummyFunction("func2")] + func_provider_mock.get_all.return_value = [get_function("func1"), get_function("func2")] funcprovider = SamFunctionProviderMock.return_value = func_provider_mock layer_provider_mock = Mock() @@ -288,7 +306,7 @@ def test_must_return_buildable_dependent_layer_when_function_is_build( layer_provider_mock.get.return_value = layer1 layerprovider = SamLayerProviderMock.return_value = layer_provider_mock - func1 = DummyFunction("func1", [layer1, layer2]) + func1 = get_function("func1", [layer1, layer2]) func_provider_mock = Mock() func_provider_mock.get.return_value = func1 funcprovider = SamFunctionProviderMock.return_value = func_provider_mock @@ -405,15 +423,15 @@ def test_must_return_many_functions_to_build( stack = Mock() stack.template_dict = template_dict get_buildable_stacks_mock.return_value = ([stack], []) - func1 = DummyFunction("func1") - func2 = DummyFunction("func2") - func3_skipped = DummyFunction("func3", inlinecode="def handler(): pass", codeuri=None) - func4_skipped = DummyFunction("func4", codeuri="packaged_function.zip") - func5_skipped = DummyFunction("func5", codeuri=None, packagetype=IMAGE) - func6 = DummyFunction( + func1 = get_function("func1") + func2 = get_function("func2") + func3_skipped = get_function("func3", inlinecode="def handler(): pass", codeuri=None) + func4_skipped = get_function("func4", codeuri="packaged_function.zip") + func5_skipped = get_function("func5", codeuri=None, packagetype=IMAGE) + func6 = get_function( "func6", packagetype=IMAGE, metadata={"DockerContext": "/path", "Dockerfile": "DockerFile"} ) - func7_skipped = DummyFunction("func7", skip_build=True) + func7_skipped = get_function("func7", skip_build=True) func_provider_mock = Mock() func_provider_mock.get_all.return_value = [ @@ -521,7 +539,7 @@ def test_must_exclude_functions_from_build( stack.template_dict = template_dict get_buildable_stacks_mock.return_value = ([stack], []) - funcs = [DummyFunction(f) for f in resources_to_build] + funcs = [get_function(f) for f in resources_to_build] resource_to_exclude = None for f in funcs: if f.name == resource_identifier: @@ -682,7 +700,7 @@ def test_run_sync_build_context( layer_provider_mock = Mock() layer_provider_mock.get.return_value = layer1 layerprovider = SamLayerProviderMock.return_value = layer_provider_mock - func1 = DummyFunction("func1", [layer1]) + func1 = get_function("func1", [layer1]) func_provider_mock = Mock() func_provider_mock.get.return_value = func1 funcprovider = SamFunctionProviderMock.return_value = func_provider_mock @@ -943,7 +961,7 @@ def test_run_build_context( layer_provider_mock = Mock() layer_provider_mock.get.return_value = layer1 layerprovider = SamLayerProviderMock.return_value = layer_provider_mock - func1 = DummyFunction("func1", [layer1]) + func1 = get_function("func1", [layer1]) func_provider_mock = Mock() func_provider_mock.get.return_value = func1 funcprovider = SamFunctionProviderMock.return_value = func_provider_mock @@ -1115,7 +1133,7 @@ def test_must_catch_known_exceptions( layer_provider_mock = Mock() layer_provider_mock.get.return_value = layer1 layerprovider = SamLayerProviderMock.return_value = layer_provider_mock - func1 = DummyFunction("func1", [layer1]) + func1 = get_function("func1", [layer1]) func_provider_mock = Mock() func_provider_mock.get.return_value = func1 funcprovider = SamFunctionProviderMock.return_value = func_provider_mock @@ -1193,7 +1211,7 @@ def test_must_catch_function_not_found_exception( layer_provider_mock = Mock() layer_provider_mock.get.return_value = layer1 layerprovider = SamLayerProviderMock.return_value = layer_provider_mock - func1 = DummyFunction("func1", [layer1]) + func1 = get_function("func1", [layer1]) func_provider_mock = Mock() func_provider_mock.get.return_value = func1 funcprovider = SamFunctionProviderMock.return_value = func_provider_mock diff --git a/tests/unit/commands/buildcmd/test_utils.py b/tests/unit/commands/buildcmd/test_utils.py index 2a28a65002..20a79b3f0f 100644 --- a/tests/unit/commands/buildcmd/test_utils.py +++ b/tests/unit/commands/buildcmd/test_utils.py @@ -7,7 +7,7 @@ from samcli.commands.build.utils import prompt_user_to_enable_mount_with_write_if_needed from samcli.lib.utils.architecture import X86_64 from samcli.lib.utils.packagetype import ZIP, IMAGE -from samcli.lib.providers.provider import ResourcesToBuildCollector, Function, LayerVersion +from samcli.lib.providers.provider import ResourcesToBuildCollector, Function, LayerVersion, FunctionBuildInfo class TestBuildUtils(TestCase): @@ -52,6 +52,7 @@ def test_must_prompt_for_layer(self, prompt_mock): codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) resources_to_build = ResourcesToBuildCollector() @@ -101,6 +102,7 @@ def test_must_prompt_for_function(self, prompt_mock): codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) resources_to_build = ResourcesToBuildCollector() @@ -152,6 +154,7 @@ def test_must_prompt_for_function_with_specified_workflow(self, prompt_mock): codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) resources_to_build = ResourcesToBuildCollector() @@ -201,6 +204,7 @@ def test_must_not_prompt_for_image_function(self, prompt_mock): codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableImage, ) resources_to_build = ResourcesToBuildCollector() @@ -250,6 +254,7 @@ def test_must_not_prompt(self, prompt_mock): codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) resources_to_build = ResourcesToBuildCollector() diff --git a/tests/unit/commands/local/lib/test_local_lambda.py b/tests/unit/commands/local/lib/test_local_lambda.py index e71d006366..dc5f4384bd 100644 --- a/tests/unit/commands/local/lib/test_local_lambda.py +++ b/tests/unit/commands/local/lib/test_local_lambda.py @@ -10,7 +10,7 @@ from samcli.lib.utils.architecture import X86_64, ARM64 from samcli.commands.local.lib.local_lambda import LocalLambdaRunner -from samcli.lib.providers.provider import Function +from samcli.lib.providers.provider import Function, FunctionBuildInfo from samcli.lib.utils.packagetype import ZIP, IMAGE from samcli.local.docker.container import ContainerResponseException from samcli.local.lambdafn.exceptions import FunctionNotFound @@ -252,6 +252,7 @@ def test_must_work_with_override_values( codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) self.local_lambda.env_vars_values = env_vars_values @@ -305,6 +306,7 @@ def test_must_not_work_with_invalid_override_values(self, env_vars_values, expec codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) self.local_lambda.env_vars_values = env_vars_values @@ -348,6 +350,7 @@ def test_must_work_with_invalid_environment_variable(self, environment_variable, codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) self.local_lambda.env_vars_values = {} @@ -427,6 +430,7 @@ def test_must_work(self, FunctionConfigMock, is_debugging_mock, resolve_code_pat codesign_config_arn=None, function_url_config=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) config = "someconfig" @@ -495,6 +499,7 @@ def test_timeout_set_to_max_during_debugging( function_url_config=None, codesign_config_arn=None, runtime_management_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) config = "someconfig" diff --git a/tests/unit/commands/local/lib/test_provider.py b/tests/unit/commands/local/lib/test_provider.py index d3b5260c51..6270c40d44 100644 --- a/tests/unit/commands/local/lib/test_provider.py +++ b/tests/unit/commands/local/lib/test_provider.py @@ -18,6 +18,7 @@ get_unique_resource_ids, Function, get_resource_full_path_by_id, + FunctionBuildInfo, ) from samcli.commands.local.cli_common.user_exceptions import ( InvalidLayerVersionArn, @@ -293,6 +294,7 @@ def setUp(self) -> None: None, [ARM64], None, + FunctionBuildInfo.BuildableZip, "stackpath", None, ) diff --git a/tests/unit/commands/local/lib/test_sam_function_provider.py b/tests/unit/commands/local/lib/test_sam_function_provider.py index aed832e090..6d2c8f716b 100644 --- a/tests/unit/commands/local/lib/test_sam_function_provider.py +++ b/tests/unit/commands/local/lib/test_sam_function_provider.py @@ -8,7 +8,7 @@ from samcli.lib.utils.architecture import X86_64, ARM64 from samcli.commands.local.cli_common.user_exceptions import InvalidLayerVersionArn -from samcli.lib.providers.provider import Function, LayerVersion, Stack +from samcli.lib.providers.provider import Function, LayerVersion, Stack, FunctionBuildInfo from samcli.lib.providers.sam_function_provider import SamFunctionProvider, RefreshableSamFunctionProvider from samcli.lib.providers.exceptions import InvalidLayerReference from samcli.lib.utils.packagetype import IMAGE, ZIP @@ -302,6 +302,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -328,6 +329,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.InlineCode, ), ), ( @@ -354,6 +356,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ("SamFunc2", None), # codeuri is a s3 location, ignored @@ -387,6 +390,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableImage, ), ), ( @@ -418,6 +422,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableImage, ), ), ("SamFuncWithImage3", None), # imageuri is ecr location, ignored @@ -450,6 +455,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableImage, ), ), ( @@ -476,6 +482,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -506,6 +513,7 @@ def setUp(self): "UpdateRuntimeOn": "Manual", "RuntimeVersionArn": "arn:aws:lambda:us-east-1::runtime:python3.9::0af1966588ced06e3143ae720245c9b7aeaae213c6921c12c742a166679cc505", }, + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ("LambdaFunc1", None), # codeuri is a s3 location, ignored @@ -538,6 +546,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableImage, ), ), ( @@ -569,6 +578,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableImage, ), ), ("LambdaFuncWithImage3", None), # imageuri is a ecr location, ignored @@ -601,6 +611,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableImage, ), ), ( @@ -627,6 +638,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.InlineCode, ), ), ( @@ -653,6 +665,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -679,6 +692,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -705,6 +719,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -731,6 +746,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="ChildStack", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -757,6 +773,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="ChildStack", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -788,6 +805,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="ChildStack", + function_build_info=FunctionBuildInfo.BuildableImage, ), ), ( @@ -814,6 +832,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -840,6 +859,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -872,6 +892,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -904,6 +925,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -936,6 +958,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="ChildStack", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -968,6 +991,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="ChildStack", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -1000,6 +1024,7 @@ def setUp(self): architectures=None, function_url_config=None, stack_path="ChildStack", + function_build_info=FunctionBuildInfo.BuildableZip, ), ), ( @@ -1453,6 +1478,7 @@ def test_must_convert_zip(self): architectures=[X86_64], function_url_config=None, stack_path=STACK_PATH, + function_build_info=FunctionBuildInfo.BuildableZip, ) result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, ["Layer1", "Layer2"]) @@ -1495,6 +1521,7 @@ def test_must_convert_image(self): architectures=None, function_url_config=None, stack_path=STACK_PATH, + function_build_info=FunctionBuildInfo.NonBuildableImage, ) result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, []) @@ -1527,6 +1554,7 @@ def test_must_skip_non_existent_properties(self): architectures=None, function_url_config=None, stack_path=STACK_PATH, + function_build_info=FunctionBuildInfo.BuildableZip, ) result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, []) @@ -1573,6 +1601,7 @@ def test_must_use_inlinecode(self): architectures=[X86_64], function_url_config=None, stack_path=STACK_PATH, + function_build_info=FunctionBuildInfo.InlineCode, ) result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, []) @@ -1613,6 +1642,7 @@ def test_must_prioritize_inlinecode(self): architectures=[ARM64], function_url_config=None, stack_path=STACK_PATH, + function_build_info=FunctionBuildInfo.InlineCode, ) result = SamFunctionProvider._convert_sam_function_resource(STACK, name, properties, []) @@ -1668,6 +1698,7 @@ def test_must_convert(self): architectures=None, function_url_config=None, stack_path=STACK_PATH, + function_build_info=FunctionBuildInfo.BuildableZip, ) result = SamFunctionProvider._convert_lambda_function_resource(STACK, name, properties, ["Layer1", "Layer2"]) @@ -1708,6 +1739,7 @@ def test_must_use_inlinecode(self): architectures=[ARM64], function_url_config=None, stack_path=STACK_PATH, + function_build_info=FunctionBuildInfo.InlineCode, ) result = SamFunctionProvider._convert_lambda_function_resource(STACK, name, properties, []) @@ -1740,6 +1772,7 @@ def test_must_skip_non_existent_properties(self): architectures=None, function_url_config=None, stack_path=STACK_PATH, + function_build_info=FunctionBuildInfo.BuildableZip, ) result = SamFunctionProvider._convert_lambda_function_resource(STACK, name, properties, []) @@ -1882,6 +1915,7 @@ def test_must_return_function_value(self): architectures=None, function_url_config=None, stack_path=STACK_PATH, + function_build_info=Mock(), ) provider.functions = {"func1": function} @@ -1912,6 +1946,7 @@ def test_found_by_different_ids(self): architectures=None, function_url_config=None, stack_path=posixpath.join("this_is", "stack_path_C"), + function_build_info=Mock(), ) function2 = Function( @@ -1936,6 +1971,7 @@ def test_found_by_different_ids(self): architectures=None, function_url_config=None, stack_path=posixpath.join("this_is", "stack_path_B"), + function_build_info=Mock(), ) function3 = Function( @@ -1960,6 +1996,7 @@ def test_found_by_different_ids(self): architectures=None, function_url_config=None, stack_path=posixpath.join("this_is", "stack_path_A"), + function_build_info=Mock(), ) function4 = Function( @@ -1984,6 +2021,7 @@ def test_found_by_different_ids(self): architectures=None, function_url_config=None, stack_path=posixpath.join("this_is", "stack_path_D"), + function_build_info=Mock(), ) provider.functions = {"func1": function1, "func2": function2, "func3": function3, "func4": function4} diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index ba1f47e50b..34f7cc5ae0 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -12,7 +12,7 @@ from parameterized import parameterized from samcli.lib.build.workflow_config import UnsupportedRuntimeException -from samcli.lib.providers.provider import ResourcesToBuildCollector, Function +from samcli.lib.providers.provider import ResourcesToBuildCollector, Function, FunctionBuildInfo from samcli.lib.build.app_builder import ( ApplicationBuilder, UnsupportedBuilderLibraryVersionError, @@ -440,6 +440,7 @@ def test_must_raise_for_functions_with_multi_architecture(self, persist_mock, re architectures=[X86_64, ARM64], stack_path="", function_url_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) resources_to_build_collector = ResourcesToBuildCollector() @@ -498,6 +499,7 @@ def test_must_not_use_dep_layer_for_non_cached(self): architectures=[X86_64], stack_path="", function_url_config=None, + function_build_info=FunctionBuildInfo.BuildableZip, ) resources_to_build_collector = ResourcesToBuildCollector() diff --git a/tests/unit/lib/build_module/test_build_graph.py b/tests/unit/lib/build_module/test_build_graph.py index c1485b0a8d..e0d1f524c9 100644 --- a/tests/unit/lib/build_module/test_build_graph.py +++ b/tests/unit/lib/build_module/test_build_graph.py @@ -34,7 +34,7 @@ BuildHashingInformation, HANDLER_FIELD, ) -from samcli.lib.providers.provider import Function, LayerVersion +from samcli.lib.providers.provider import Function, LayerVersion, FunctionBuildInfo from samcli.lib.utils import osutils from samcli.lib.utils.packagetype import ZIP @@ -60,6 +60,7 @@ def generate_function( inlinecode=None, architectures=[X86_64], stack_path="", + function_build_info=FunctionBuildInfo.BuildableZip, ): if metadata is None: metadata = {} @@ -85,6 +86,7 @@ def generate_function( codesign_config_arn, architectures, stack_path, + function_build_info, ) diff --git a/tests/unit/lib/sync/test_sync_flow_factory.py b/tests/unit/lib/sync/test_sync_flow_factory.py index 908b01d14a..632d25bba5 100644 --- a/tests/unit/lib/sync/test_sync_flow_factory.py +++ b/tests/unit/lib/sync/test_sync_flow_factory.py @@ -4,8 +4,10 @@ from parameterized import parameterized +from samcli.lib.providers.provider import FunctionBuildInfo from samcli.lib.sync.sync_flow_factory import SyncCodeResources, SyncFlowFactory from samcli.lib.utils.cloudformation import CloudFormationResourceSummary +from samcli.lib.utils.packagetype import IMAGE, ZIP from samcli.lib.utils.resources import ( AWS_SERVERLESS_FUNCTION, AWS_LAMBDA_FUNCTION, @@ -21,7 +23,7 @@ class TestSyncFlowFactory(TestCase): - def create_factory(self, auto_dependency_layer: bool = False): + def create_factory(self, auto_dependency_layer: bool = False, build_context=None): stack_resource = MagicMock() stack_resource.resources = { "Resource1": { @@ -37,7 +39,7 @@ def create_factory(self, auto_dependency_layer: bool = False): }, } factory = SyncFlowFactory( - build_context=MagicMock(), + build_context=build_context or MagicMock(), deploy_context=MagicMock(), sync_context=MagicMock(), stacks=[stack_resource, MagicMock()], @@ -68,14 +70,45 @@ def test_load_physical_id_mapping( {"Resource1": "PhysicalResource1", "Resource2": "PhysicalResource2"}, ) - @parameterized.expand([(None,), (Mock(),)]) + @parameterized.expand( + itertools.product( + [None, Mock()], + [ + FunctionBuildInfo.BuildableZip, + FunctionBuildInfo.PreZipped, + FunctionBuildInfo.InlineCode, + FunctionBuildInfo.SkipBuild, + ], + ) + ) @patch("samcli.lib.sync.sync_flow_factory.ImageFunctionSyncFlow") + @patch("samcli.lib.sync.sync_flow_factory.ZipFunctionSyncFlowSkipBuildZipFile") + @patch("samcli.lib.sync.sync_flow_factory.ZipFunctionSyncFlowSkipBuildDirectory") @patch("samcli.lib.sync.sync_flow_factory.ZipFunctionSyncFlow") - def test_create_lambda_flow_zip(self, pre_build_artifacts, zip_function_mock, image_function_mock): - factory = self.create_factory() - resource = {"Properties": {"PackageType": "Zip"}} - result = factory._create_lambda_flow("Function1", resource, pre_build_artifacts) - self.assertEqual(result, zip_function_mock.return_value) + def test_create_lambda_flow_zip( + self, + pre_build_artifacts, + function_build_info, + zip_function_mock, + zip_function_skip_build_directory_mock, + zip_function_skip_build_zip_mock, + _, + ): + build_context = MagicMock() + build_context.function_provider.get.return_value = Mock( + packagetype=ZIP, function_build_info=function_build_info + ) + factory = self.create_factory(build_context=build_context) + result = factory._create_lambda_flow("Function1", pre_build_artifacts) + + if function_build_info == FunctionBuildInfo.BuildableZip: + self.assertEqual(result, zip_function_mock.return_value) + if function_build_info == FunctionBuildInfo.PreZipped: + self.assertEqual(result, zip_function_skip_build_zip_mock.return_value) + if function_build_info == FunctionBuildInfo.SkipBuild: + self.assertEqual(result, zip_function_skip_build_directory_mock.return_value) + if function_build_info == FunctionBuildInfo.InlineCode: + self.assertIsNone(result) @parameterized.expand([(None,), (Mock(),)]) @patch("samcli.lib.sync.sync_flow_factory.ImageFunctionSyncFlow") @@ -84,9 +117,12 @@ def test_create_lambda_flow_zip(self, pre_build_artifacts, zip_function_mock, im def test_create_lambda_flow_zip_with_auto_dependency_layer( self, pre_build_artifacts, auto_dependency_layer_mock, zip_function_mock, image_function_mock ): - factory = self.create_factory(True) - resource = {"Properties": {"PackageType": "Zip", "Runtime": "python3.8"}} - result = factory._create_lambda_flow("Function1", resource, pre_build_artifacts) + build_context = MagicMock() + build_context.function_provider.get.return_value = Mock( + packagetype=ZIP, build_info=FunctionBuildInfo.BuildableZip, runtime="python3.8" + ) + factory = self.create_factory(True, build_context=build_context) + result = factory._create_lambda_flow("Function1", pre_build_artifacts) self.assertEqual(result, auto_dependency_layer_mock.return_value) @parameterized.expand([(None,), (Mock(),)]) @@ -96,19 +132,30 @@ def test_create_lambda_flow_zip_with_auto_dependency_layer( def test_create_lambda_flow_zip_with_unsupported_runtime_auto_dependency_layer( self, pre_build_artifacts, auto_dependency_layer_mock, zip_function_mock, image_function_mock ): - factory = self.create_factory(True) - resource = {"Properties": {"PackageType": "Zip", "Runtime": "ruby2.7"}} - result = factory._create_lambda_flow("Function1", resource, pre_build_artifacts) + build_context = MagicMock() + build_context.function_provider.get.return_value = Mock( + packagetype=ZIP, build_info=FunctionBuildInfo.BuildableZip, runtime="ruby2.7" + ) + factory = self.create_factory(True, build_context=build_context) + result = factory._create_lambda_flow("Function1", pre_build_artifacts) self.assertEqual(result, zip_function_mock.return_value) - @parameterized.expand([(None,), (Mock(),)]) + @parameterized.expand( + itertools.product([None, Mock()], [FunctionBuildInfo.BuildableImage, FunctionBuildInfo.NonBuildableImage]) + ) @patch("samcli.lib.sync.sync_flow_factory.ImageFunctionSyncFlow") @patch("samcli.lib.sync.sync_flow_factory.ZipFunctionSyncFlow") - def test_create_lambda_flow_image(self, pre_build_artifacts, zip_function_mock, image_function_mock): - factory = self.create_factory() - resource = {"Properties": {"PackageType": "Image"}} - result = factory._create_lambda_flow("Function1", resource, pre_build_artifacts) - self.assertEqual(result, image_function_mock.return_value) + def test_create_lambda_flow_image(self, pre_build_artifacts, function_build_info, _, image_function_mock): + build_context = MagicMock() + build_context.function_provider.get.return_value = Mock( + packagetype=IMAGE, function_build_info=function_build_info + ) + factory = self.create_factory(build_context=build_context) + result = factory._create_lambda_flow("Function1", pre_build_artifacts) + if function_build_info == FunctionBuildInfo.BuildableImage: + self.assertEqual(result, image_function_mock.return_value) + else: + self.assertIsNone(result) @parameterized.expand([(None,), (Mock(),)]) @patch("samcli.lib.sync.sync_flow_factory.LayerSyncFlow") @@ -116,7 +163,7 @@ def test_create_layer_flow(self, pre_build_artifacts, layer_sync_mock): factory = self.create_factory() # mock layer for not having SkipBuild:True factory._build_context.layer_provider.get.return_value = Mock(skip_build=False) - result = factory._create_layer_flow("Layer1", {}, pre_build_artifacts) + result = factory._create_layer_flow("Layer1", pre_build_artifacts) self.assertEqual(result, layer_sync_mock.return_value) @parameterized.expand(itertools.product([Mock(build_method=None), Mock(skip_build=True)], [None, Mock()])) @@ -130,7 +177,7 @@ def test_create_layer_flow_with_skip_build_directory( factory._build_context.layer_provider.get.return_value = layer_mock # codeuri should resolve as directory is_local_folder_mock.return_value = True - result = factory._create_layer_flow("Layer1", {}, pre_build_artifacts) + result = factory._create_layer_flow("Layer1", pre_build_artifacts) self.assertEqual(result, layer_sync_mock.return_value) @parameterized.expand(itertools.product([Mock(build_method=None), Mock(skip_build=True)], [None, Mock()])) @@ -145,14 +192,14 @@ def test_create_layer_flow_with_skip_build_zip( # codeuri should resolve as zip file is_local_folder_mock.return_value = False is_zip_file_mock.return_value = True - result = factory._create_layer_flow("Layer1", {}, pre_build_artifacts) + result = factory._create_layer_flow("Layer1", pre_build_artifacts) self.assertEqual(result, layer_sync_mock.return_value) @parameterized.expand([(None,), (Mock(),)]) def test_create_layer_flow_with_no_layer(self, pre_build_artifacts): factory = self.create_factory() factory._build_context.layer_provider.get.return_value = None - result = factory._create_layer_flow("Layer1", {}, pre_build_artifacts) + result = factory._create_layer_flow("Layer1", pre_build_artifacts) self.assertIsNone(result) @parameterized.expand([(None,), (Mock(),)]) @@ -160,37 +207,33 @@ def test_create_layer_flow_with_no_layer(self, pre_build_artifacts): @patch("samcli.lib.sync.sync_flow_factory.ZipFunctionSyncFlow") def test_create_lambda_flow_other(self, pre_build_artifacts, zip_function_mock, image_function_mock): factory = self.create_factory() - resource = {"Properties": {"PackageType": "Other"}} - result = factory._create_lambda_flow("Function1", resource, pre_build_artifacts) + result = factory._create_lambda_flow("Function1", pre_build_artifacts) self.assertEqual(result, None) @patch("samcli.lib.sync.sync_flow_factory.RestApiSyncFlow") def test_create_rest_api_flow(self, rest_api_sync_mock): factory = self.create_factory() - result = factory._create_rest_api_flow("API1", {}, None) + result = factory._create_rest_api_flow("API1", None) self.assertEqual(result, rest_api_sync_mock.return_value) @patch("samcli.lib.sync.sync_flow_factory.HttpApiSyncFlow") def test_create_api_flow(self, http_api_sync_mock): factory = self.create_factory() - result = factory._create_api_flow("API1", {}, None) + result = factory._create_api_flow("API1", None) self.assertEqual(result, http_api_sync_mock.return_value) @patch("samcli.lib.sync.sync_flow_factory.StepFunctionsSyncFlow") def test_create_stepfunctions_flow(self, stepfunctions_sync_mock): factory = self.create_factory() - result = factory._create_stepfunctions_flow("StateMachine1", {}, None) + result = factory._create_stepfunctions_flow("StateMachine1", None) self.assertEqual(result, stepfunctions_sync_mock.return_value) @parameterized.expand([(None,), (Mock(),)]) - @patch("samcli.lib.sync.sync_flow_factory.get_resource_by_id") - def test_create_sync_flow(self, pre_build_artifacts, get_resource_by_id_mock): + def test_create_sync_flow(self, pre_build_artifacts): factory = self.create_factory() sync_flow = MagicMock() resource_identifier = MagicMock() - get_resource_by_id = MagicMock() - get_resource_by_id_mock.return_value = get_resource_by_id generator_mock = MagicMock() generator_mock.return_value = sync_flow @@ -201,21 +244,15 @@ def test_create_sync_flow(self, pre_build_artifacts, get_resource_by_id_mock): result = factory.create_sync_flow(resource_identifier, pre_build_artifacts) self.assertEqual(result, sync_flow) - generator_mock.assert_called_once_with(factory, resource_identifier, get_resource_by_id, pre_build_artifacts) + generator_mock.assert_called_once_with(factory, resource_identifier, pre_build_artifacts) - @patch("samcli.lib.sync.sync_flow_factory.get_resource_by_id") - def test_create_unknown_resource_sync_flow(self, get_resource_by_id_mock): - get_resource_by_id_mock.return_value = None + def test_create_unknown_resource_sync_flow(self): factory = self.create_factory() self.assertIsNone(factory.create_sync_flow(MagicMock())) - @patch("samcli.lib.sync.sync_flow_factory.get_resource_by_id") - def test_create_none_generator_sync_flow(self, get_resource_by_id_mock): + def test_create_none_generator_sync_flow(self): factory = self.create_factory() - resource_identifier = MagicMock() - get_resource_by_id = MagicMock() - get_resource_by_id_mock.return_value = get_resource_by_id get_generator_function_mock = MagicMock() get_generator_function_mock.return_value = None From 5954042d0bced7fea329c06930f021915ed9b746 Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Fri, 2 Jun 2023 11:39:43 -0500 Subject: [PATCH 017/107] chore: Upgrade Mac installer to Py3.11 (#5223) * chore: Upgrade Mac installer to Py3.11 * Remove python in mac installer build process * Update hardcoded python version in build-mac.sh --------- Co-authored-by: Jacob Fuss --- .github/workflows/validate_pyinstaller.yml | 3 - Makefile | 2 +- installer/pyinstaller/build-mac.sh | 20 ++++- requirements/reproducible-mac.txt | 86 +--------------------- 4 files changed, 18 insertions(+), 93 deletions(-) mode change 100644 => 100755 installer/pyinstaller/build-mac.sh diff --git a/.github/workflows/validate_pyinstaller.yml b/.github/workflows/validate_pyinstaller.yml index 8370328b5d..ab0394ff5d 100644 --- a/.github/workflows/validate_pyinstaller.yml +++ b/.github/workflows/validate_pyinstaller.yml @@ -38,9 +38,6 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: "3.7" - name: Build PyInstaller run: | chmod +x ./installer/pyinstaller/build-mac.sh diff --git a/Makefile b/Makefile index c69818162d..ecfa78abcf 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ update-reproducible-linux-reqs: venv-update-reproducible-linux/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-linux.txt update-reproducible-mac-reqs: - python3.7 -m venv venv-update-reproducible-mac + python3.11 -m venv venv-update-reproducible-mac venv-update-reproducible-mac/bin/pip install --upgrade pip-tools pip venv-update-reproducible-mac/bin/pip install -r requirements/base.txt venv-update-reproducible-mac/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-mac.txt diff --git a/installer/pyinstaller/build-mac.sh b/installer/pyinstaller/build-mac.sh old mode 100644 new mode 100755 index eafcb14f31..5697e24ae7 --- a/installer/pyinstaller/build-mac.sh +++ b/installer/pyinstaller/build-mac.sh @@ -30,11 +30,11 @@ if [ "$python_library_zip_filename" = "" ]; then fi if [ "$openssl_version" = "" ]; then - openssl_version="1.1.1o"; + openssl_version="1.1.1t"; fi if [ "$python_version" = "" ]; then - python_version="3.8.13"; + python_version="3.11.3"; fi if ! [ "$build_binary_name" = "" ]; then @@ -71,8 +71,20 @@ echo "Copying Source" cp -r ../[!.]* ./src cp -r ./src/* ./output/aws-sam-cli-src -echo "Removing CI Scripts" +echo "Removing CI Scripts and other files/direcories not needed" rm -vf ./output/aws-sam-cli-src/appveyor*.yml +rm -rf ./output/aws-sam-cli-src/tests +rm -rf ./output/aws-sam-cli-src/designs +rm -rf ./output/aws-sam-cli-src/docs +rm -rf ./output/aws-sam-cli-src/media +rm -rf ./output/aws-sam-cli-src/Make.ps1 +rm -rf ./output/aws-sam-cli-src/CODEOWNERS +rm -rf ./output/aws-sam-cli-src/CODE_OF_CONDUCT.md +rm -rf ./output/aws-sam-cli-src/CONTRIBUTING.md +rm -rf ./output/aws-sam-cli-src/DESIGN.md +rm -rf ./output/aws-sam-cli-src/Makefile +rm -rf ./output/aws-sam-cli-src/mypy.ini +rm -rf ./output/aws-sam-cli-src/pytest.ini echo "Installing Python" curl "https://www.python.org/ftp/python/${python_version}/Python-${python_version}.tgz" --output python.tgz @@ -84,7 +96,7 @@ sudo make install cd .. echo "Installing Python Libraries" -/usr/local/bin/python3.8 -m venv venv +/usr/local/bin/python3.11 -m venv venv ./venv/bin/pip install --upgrade pip ./venv/bin/pip install -r src/requirements/reproducible-mac.txt diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 14dfaf2c06..4971ae1661 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.7 +# This file is autogenerated by pip-compile with Python 3.11 # by the following command: # # pip-compile --allow-unsafe --generate-hashes --output-file=requirements/reproducible-mac.txt @@ -25,24 +25,6 @@ aws-sam-translator==1.68.0 \ # via # aws-sam-cli (setup.py) # cfn-lint -backports-zoneinfo==0.2.1 \ - --hash=sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf \ - --hash=sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328 \ - --hash=sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546 \ - --hash=sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6 \ - --hash=sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570 \ - --hash=sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9 \ - --hash=sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7 \ - --hash=sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987 \ - --hash=sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722 \ - --hash=sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582 \ - --hash=sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc \ - --hash=sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b \ - --hash=sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1 \ - --hash=sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08 \ - --hash=sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac \ - --hash=sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2 - # via tzlocal binaryornot==0.4.4 \ --hash=sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061 \ --hash=sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4 @@ -271,19 +253,6 @@ idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests -importlib-metadata==6.1.0 \ - --hash=sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20 \ - --hash=sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09 - # via - # attrs - # click - # flask - # jsonpickle - # jsonschema -importlib-resources==5.12.0 \ - --hash=sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6 \ - --hash=sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a - # via jsonschema itsdangerous==2.1.2 \ --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a @@ -408,10 +377,6 @@ pbr==5.11.1 \ # via # jschema-to-python # sarif-om -pkgutil-resolve-name==1.3.10 \ - --hash=sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174 \ - --hash=sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e - # via jsonschema pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 @@ -649,44 +614,6 @@ ruamel-yaml==0.17.21 \ --hash=sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7 \ --hash=sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af # via aws-sam-cli (setup.py) -ruamel-yaml-clib==0.2.7 \ - --hash=sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e \ - --hash=sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3 \ - --hash=sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5 \ - --hash=sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497 \ - --hash=sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f \ - --hash=sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac \ - --hash=sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697 \ - --hash=sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763 \ - --hash=sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282 \ - --hash=sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94 \ - --hash=sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1 \ - --hash=sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072 \ - --hash=sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9 \ - --hash=sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5 \ - --hash=sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231 \ - --hash=sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93 \ - --hash=sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b \ - --hash=sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb \ - --hash=sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f \ - --hash=sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307 \ - --hash=sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8 \ - --hash=sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b \ - --hash=sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b \ - --hash=sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640 \ - --hash=sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7 \ - --hash=sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a \ - --hash=sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71 \ - --hash=sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8 \ - --hash=sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122 \ - --hash=sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7 \ - --hash=sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80 \ - --hash=sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e \ - --hash=sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab \ - --hash=sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0 \ - --hash=sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646 \ - --hash=sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38 - # via ruamel-yaml s3transfer==0.6.0 \ --hash=sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd \ --hash=sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947 @@ -723,14 +650,9 @@ typing-extensions==4.4.0 \ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e # via - # arrow # aws-sam-cli (setup.py) # aws-sam-translator - # importlib-metadata - # jsonschema - # markdown-it-py # pydantic - # rich tzlocal==3.0 \ --hash=sha256:c736f2540713deb5938d789ca7c3fc25391e9a20803f05b60ec64987cf086559 \ --hash=sha256:f4e6e36db50499e0d92f79b67361041f048e2609d166e93456b50746dc4aef12 @@ -774,12 +696,6 @@ wheel==0.40.0 \ --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \ --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247 # via aws-lambda-builders -zipp==3.15.0 \ - --hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \ - --hash=sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556 - # via - # importlib-metadata - # importlib-resources # The following packages are considered to be unsafe in a requirements file: setuptools==67.7.2 \ From 243bf330eb5d62211a2aad8062add177f531e7c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 2 Jun 2023 17:16:54 +0000 Subject: [PATCH 018/107] feat: updating app templates repo hash with (66f4a230d1c939a0c3f7b5647710c694c3a486f7) (#5245) Co-authored-by: GitHub Action --- samcli/runtime_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/runtime_config.json b/samcli/runtime_config.json index d40ef0a840..b038567d8c 100644 --- a/samcli/runtime_config.json +++ b/samcli/runtime_config.json @@ -1,3 +1,3 @@ { - "app_template_repo_commit": "9ee7db342025a42023882960b23ebfcde1d87422" + "app_template_repo_commit": "66f4a230d1c939a0c3f7b5647710c694c3a486f7" } From 129302650389bc3226a0f17a9742dd14b4f94860 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Fri, 2 Jun 2023 12:22:01 -0700 Subject: [PATCH 019/107] Revert "chore: Upgrade Mac installer to Py3.11 (#5223)" (#5252) This reverts commit 5954042d0bced7fea329c06930f021915ed9b746. --- .github/workflows/validate_pyinstaller.yml | 3 + Makefile | 2 +- installer/pyinstaller/build-mac.sh | 20 +---- requirements/reproducible-mac.txt | 86 +++++++++++++++++++++- 4 files changed, 93 insertions(+), 18 deletions(-) mode change 100755 => 100644 installer/pyinstaller/build-mac.sh diff --git a/.github/workflows/validate_pyinstaller.yml b/.github/workflows/validate_pyinstaller.yml index ab0394ff5d..8370328b5d 100644 --- a/.github/workflows/validate_pyinstaller.yml +++ b/.github/workflows/validate_pyinstaller.yml @@ -38,6 +38,9 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.7" - name: Build PyInstaller run: | chmod +x ./installer/pyinstaller/build-mac.sh diff --git a/Makefile b/Makefile index ecfa78abcf..c69818162d 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ update-reproducible-linux-reqs: venv-update-reproducible-linux/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-linux.txt update-reproducible-mac-reqs: - python3.11 -m venv venv-update-reproducible-mac + python3.7 -m venv venv-update-reproducible-mac venv-update-reproducible-mac/bin/pip install --upgrade pip-tools pip venv-update-reproducible-mac/bin/pip install -r requirements/base.txt venv-update-reproducible-mac/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-mac.txt diff --git a/installer/pyinstaller/build-mac.sh b/installer/pyinstaller/build-mac.sh old mode 100755 new mode 100644 index 5697e24ae7..eafcb14f31 --- a/installer/pyinstaller/build-mac.sh +++ b/installer/pyinstaller/build-mac.sh @@ -30,11 +30,11 @@ if [ "$python_library_zip_filename" = "" ]; then fi if [ "$openssl_version" = "" ]; then - openssl_version="1.1.1t"; + openssl_version="1.1.1o"; fi if [ "$python_version" = "" ]; then - python_version="3.11.3"; + python_version="3.8.13"; fi if ! [ "$build_binary_name" = "" ]; then @@ -71,20 +71,8 @@ echo "Copying Source" cp -r ../[!.]* ./src cp -r ./src/* ./output/aws-sam-cli-src -echo "Removing CI Scripts and other files/direcories not needed" +echo "Removing CI Scripts" rm -vf ./output/aws-sam-cli-src/appveyor*.yml -rm -rf ./output/aws-sam-cli-src/tests -rm -rf ./output/aws-sam-cli-src/designs -rm -rf ./output/aws-sam-cli-src/docs -rm -rf ./output/aws-sam-cli-src/media -rm -rf ./output/aws-sam-cli-src/Make.ps1 -rm -rf ./output/aws-sam-cli-src/CODEOWNERS -rm -rf ./output/aws-sam-cli-src/CODE_OF_CONDUCT.md -rm -rf ./output/aws-sam-cli-src/CONTRIBUTING.md -rm -rf ./output/aws-sam-cli-src/DESIGN.md -rm -rf ./output/aws-sam-cli-src/Makefile -rm -rf ./output/aws-sam-cli-src/mypy.ini -rm -rf ./output/aws-sam-cli-src/pytest.ini echo "Installing Python" curl "https://www.python.org/ftp/python/${python_version}/Python-${python_version}.tgz" --output python.tgz @@ -96,7 +84,7 @@ sudo make install cd .. echo "Installing Python Libraries" -/usr/local/bin/python3.11 -m venv venv +/usr/local/bin/python3.8 -m venv venv ./venv/bin/pip install --upgrade pip ./venv/bin/pip install -r src/requirements/reproducible-mac.txt diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 4971ae1661..14dfaf2c06 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.11 +# This file is autogenerated by pip-compile with Python 3.7 # by the following command: # # pip-compile --allow-unsafe --generate-hashes --output-file=requirements/reproducible-mac.txt @@ -25,6 +25,24 @@ aws-sam-translator==1.68.0 \ # via # aws-sam-cli (setup.py) # cfn-lint +backports-zoneinfo==0.2.1 \ + --hash=sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf \ + --hash=sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328 \ + --hash=sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546 \ + --hash=sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6 \ + --hash=sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570 \ + --hash=sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9 \ + --hash=sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7 \ + --hash=sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987 \ + --hash=sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722 \ + --hash=sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582 \ + --hash=sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc \ + --hash=sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b \ + --hash=sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1 \ + --hash=sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08 \ + --hash=sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac \ + --hash=sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2 + # via tzlocal binaryornot==0.4.4 \ --hash=sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061 \ --hash=sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4 @@ -253,6 +271,19 @@ idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests +importlib-metadata==6.1.0 \ + --hash=sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20 \ + --hash=sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09 + # via + # attrs + # click + # flask + # jsonpickle + # jsonschema +importlib-resources==5.12.0 \ + --hash=sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6 \ + --hash=sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a + # via jsonschema itsdangerous==2.1.2 \ --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \ --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a @@ -377,6 +408,10 @@ pbr==5.11.1 \ # via # jschema-to-python # sarif-om +pkgutil-resolve-name==1.3.10 \ + --hash=sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174 \ + --hash=sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e + # via jsonschema pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 @@ -614,6 +649,44 @@ ruamel-yaml==0.17.21 \ --hash=sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7 \ --hash=sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af # via aws-sam-cli (setup.py) +ruamel-yaml-clib==0.2.7 \ + --hash=sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e \ + --hash=sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3 \ + --hash=sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5 \ + --hash=sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497 \ + --hash=sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f \ + --hash=sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac \ + --hash=sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697 \ + --hash=sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763 \ + --hash=sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282 \ + --hash=sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94 \ + --hash=sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1 \ + --hash=sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072 \ + --hash=sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9 \ + --hash=sha256:721bc4ba4525f53f6a611ec0967bdcee61b31df5a56801281027a3a6d1c2daf5 \ + --hash=sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231 \ + --hash=sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93 \ + --hash=sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b \ + --hash=sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb \ + --hash=sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f \ + --hash=sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307 \ + --hash=sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8 \ + --hash=sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b \ + --hash=sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b \ + --hash=sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640 \ + --hash=sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7 \ + --hash=sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a \ + --hash=sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71 \ + --hash=sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8 \ + --hash=sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122 \ + --hash=sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7 \ + --hash=sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80 \ + --hash=sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e \ + --hash=sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab \ + --hash=sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0 \ + --hash=sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646 \ + --hash=sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38 + # via ruamel-yaml s3transfer==0.6.0 \ --hash=sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd \ --hash=sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947 @@ -650,9 +723,14 @@ typing-extensions==4.4.0 \ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e # via + # arrow # aws-sam-cli (setup.py) # aws-sam-translator + # importlib-metadata + # jsonschema + # markdown-it-py # pydantic + # rich tzlocal==3.0 \ --hash=sha256:c736f2540713deb5938d789ca7c3fc25391e9a20803f05b60ec64987cf086559 \ --hash=sha256:f4e6e36db50499e0d92f79b67361041f048e2609d166e93456b50746dc4aef12 @@ -696,6 +774,12 @@ wheel==0.40.0 \ --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \ --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247 # via aws-lambda-builders +zipp==3.15.0 \ + --hash=sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b \ + --hash=sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556 + # via + # importlib-metadata + # importlib-resources # The following packages are considered to be unsafe in a requirements file: setuptools==67.7.2 \ From b51d6617340853d891469ff7a4dcc5bb88175389 Mon Sep 17 00:00:00 2001 From: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Date: Fri, 2 Jun 2023 13:28:36 -0700 Subject: [PATCH 020/107] fix: add 3.11 to classifiers and upgrade Docker (#5225) * fix: add 3.11 to classifiers - update dependencies, need to nail down the versions. * Pin dev dependencies and handle excluding folders for mypy * Remove unneeded type: ignores * Fix name-match mypy errors * Fix empty-body error from mypy * Fix mypy errors by ignoring and get pytest to run/pass * Force mypy to not fail hopefully * Remove unneeded assignment * Update pinned requirements file --------- Co-authored-by: Jacob Fuss Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> --- Makefile | 2 +- mypy.ini | 2 +- requirements/base.txt | 10 +++++----- requirements/dev.txt | 14 +++++++++++--- requirements/reproducible-linux.txt | 18 +++++++++++------- requirements/reproducible-mac.txt | 18 +++++++++++------- samcli/cli/global_config.py | 2 +- samcli/cli/hidden_imports.py | 13 ++----------- .../hooks/prepare/resource_linking.py | 4 ++-- samcli/hook_packages/terraform/lib/utils.py | 2 +- samcli/lib/build/workflows.py | 2 +- samcli/lib/deploy/deployer.py | 2 +- samcli/lib/hook/hook_config.py | 2 +- samcli/lib/iac/cdk/cdk_iac.py | 6 +++--- samcli/lib/iac/cfn/cfn_iac.py | 5 ++--- samcli/lib/pipeline/bootstrap/stage.py | 4 ++-- samcli/lib/providers/provider.py | 6 ++++-- samcli/lib/utils/lock_distributor.py | 2 +- samcli/local/docker/container.py | 3 --- setup.py | 1 + tests/integration/logs/test_logs_command.py | 6 +++--- .../integration/traces/test_traces_command.py | 4 ++-- tests/unit/commands/deploy/test_command.py | 2 +- tests/unit/commands/sync/test_command.py | 2 +- tests/unit/commands/validate/test_cli.py | 4 ++-- .../unit/lib/build_module/test_app_builder.py | 3 +-- tests/unit/lib/deploy/test_deployer.py | 4 ++-- .../lib/pipeline/bootstrap/test_environment.py | 2 +- .../unit/local/apigw/test_lambda_authorizer.py | 2 +- tests/unit/local/docker/test_container.py | 1 - 30 files changed, 76 insertions(+), 72 deletions(-) diff --git a/Makefile b/Makefile index c69818162d..8876d482b2 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ lint: # Linter performs static analysis to catch latent bugs ruff samcli # mypy performs type check - mypy --no-incremental setup.py samcli tests + mypy --exclude /testdata/ --exclude /init/templates/ --no-incremental setup.py samcli tests # Command to run everytime you make changes to verify everything works dev: lint test diff --git a/mypy.ini b/mypy.ini index 5a0be0e705..ba4e3fa9d1 100644 --- a/mypy.ini +++ b/mypy.ini @@ -5,7 +5,7 @@ warn_return_any=True warn_unused_configs=True no_implicit_optional=True warn_redundant_casts=True -warn_unused_ignores=True +warn_unused_ignores=False # @jfuss Done as a stop gap since different py versions have different errors warn_unreachable=True # diff --git a/requirements/base.txt b/requirements/base.txt index e240ac9c0e..3a2c53c2e4 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -3,21 +3,21 @@ click~=8.0 Flask<2.3 #Need to add Schemas latest SDK. boto3>=1.19.5,==1.* -jmespath~=0.10.0 -ruamel_yaml==0.17.21 +jmespath~=1.0.1 +ruamel_yaml~=0.17.21 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 aws-sam-translator==1.68.0 #docker minor version updates can include breaking changes. Auto update micro version only. -docker~=4.2.0 +docker~=6.1.0 dateparser~=1.1 -requests==2.31.0 +requests~=2.31.0 serverlessrepo==0.1.10 aws_lambda_builders==1.32.0 tomlkit==0.11.8 watchdog==2.1.2 rich~=13.3.3 -pyopenssl==23.0.0 +pyopenssl~=23.0.0 # Needed for supporting Protocol in Python 3.7, Protocol class became public with python3.8 typing_extensions~=4.4.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index b8bccfdecb..2c9d9fcc7d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,22 +1,30 @@ -r pre-dev.txt -coverage==5.3 +coverage==7.2.7 pytest-cov==4.0.0 # type checking and related stubs # mypy adds new rules in new minor versions, which could cause our PR check to fail # here we fix its version and upgrade it manually in the future -mypy==0.790 +mypy==1.3.0 boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]==1.26.131 types-pywin32==306.0.0.0 types-PyYAML==6.0.12 types-chevron==0.14.2.4 types-psutil==5.9.5.12 types-setuptools==65.4.0.0 +types-Pygments==2.15.0.1 +types-colorama==0.4.15.11 +types-dateparser==1.1.4.9 +types-docutils==0.20.0.1 +types-jsonschema==4.17.0.8 +types-pyOpenSSL==23.2.0.0 +types-requests==2.31.0.1 +types-urllib3==1.26.25.13 # Test requirements -pytest==7.2.2 +pytest~=7.2.2 parameterized==0.9.0 pytest-xdist==3.2.0 pytest-forked==1.6.0 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 71ccb6c0f3..8aa507e1cd 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -241,9 +241,9 @@ dateparser==1.1.8 \ --hash=sha256:070b29b5bbf4b1ec2cd51c96ea040dc68a614de703910a91ad1abba18f9f379f \ --hash=sha256:86b8b7517efcc558f085a142cdb7620f0921543fcabdb538c8a4c4001d8178e3 # via aws-sam-cli (setup.py) -docker==4.2.2 \ - --hash=sha256:03a46400c4080cb6f7aa997f881ddd84fef855499ece219d75fbdb53289c17ab \ - --hash=sha256:26eebadce7e298f55b76a88c4f8802476c5eaddbdbe38dbc6cce8781c47c9b54 +docker==6.1.3 \ + --hash=sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20 \ + --hash=sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9 # via aws-sam-cli (setup.py) flask==2.2.5 \ --hash=sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf \ @@ -268,9 +268,9 @@ jinja2-time==0.2.0 \ --hash=sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40 \ --hash=sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa # via cookiecutter -jmespath==0.10.0 \ - --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ - --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +jmespath==1.0.1 \ + --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ + --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe # via # aws-sam-cli (setup.py) # boto3 @@ -371,6 +371,10 @@ networkx==2.6.3 \ --hash=sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef \ --hash=sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51 # via cfn-lint +packaging==23.1 \ + --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ + --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f + # via docker pbr==5.11.1 \ --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 @@ -630,7 +634,6 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via - # docker # junit-xml # python-dateutil # serverlessrepo @@ -664,6 +667,7 @@ urllib3==1.26.15 \ --hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42 # via # botocore + # docker # requests watchdog==2.1.2 \ --hash=sha256:0237db4d9024859bea27d0efb59fe75eef290833fd988b8ead7a879b0308c2db \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 14dfaf2c06..3f612124e8 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -259,9 +259,9 @@ dateparser==1.1.8 \ --hash=sha256:070b29b5bbf4b1ec2cd51c96ea040dc68a614de703910a91ad1abba18f9f379f \ --hash=sha256:86b8b7517efcc558f085a142cdb7620f0921543fcabdb538c8a4c4001d8178e3 # via aws-sam-cli (setup.py) -docker==4.2.2 \ - --hash=sha256:03a46400c4080cb6f7aa997f881ddd84fef855499ece219d75fbdb53289c17ab \ - --hash=sha256:26eebadce7e298f55b76a88c4f8802476c5eaddbdbe38dbc6cce8781c47c9b54 +docker==6.1.3 \ + --hash=sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20 \ + --hash=sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9 # via aws-sam-cli (setup.py) flask==2.2.5 \ --hash=sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf \ @@ -299,9 +299,9 @@ jinja2-time==0.2.0 \ --hash=sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40 \ --hash=sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa # via cookiecutter -jmespath==0.10.0 \ - --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ - --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +jmespath==1.0.1 \ + --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ + --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe # via # aws-sam-cli (setup.py) # boto3 @@ -402,6 +402,10 @@ networkx==2.6.3 \ --hash=sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef \ --hash=sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51 # via cfn-lint +packaging==23.1 \ + --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ + --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f + # via docker pbr==5.11.1 \ --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 @@ -703,7 +707,6 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via - # docker # junit-xml # python-dateutil # serverlessrepo @@ -742,6 +745,7 @@ urllib3==1.26.15 \ --hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42 # via # botocore + # docker # requests watchdog==2.1.2 \ --hash=sha256:0237db4d9024859bea27d0efb59fe75eef290833fd988b8ead7a879b0308c2db \ diff --git a/samcli/cli/global_config.py b/samcli/cli/global_config.py index e9a105ae76..2542379432 100644 --- a/samcli/cli/global_config.py +++ b/samcli/cli/global_config.py @@ -163,7 +163,7 @@ def get_value( self, config_entry: ConfigEntry, default: Optional[T] = None, - value_type: Type[T] = T, + value_type: Type[T] = T, # type: ignore is_flag: bool = False, reload_config: bool = False, ) -> Optional[T]: diff --git a/samcli/cli/hidden_imports.py b/samcli/cli/hidden_imports.py index cde0e8368a..2d116d9fc8 100644 --- a/samcli/cli/hidden_imports.py +++ b/samcli/cli/hidden_imports.py @@ -2,17 +2,10 @@ Keeps list of hidden/dynamic imports that is being used in SAM CLI, so that pyinstaller can include these packages """ import pkgutil -from typing import cast +from types import ModuleType -from typing_extensions import Protocol - -class HasPathAndName(Protocol): - __path__: str - __name__: str - - -def walk_modules(module: HasPathAndName, visited: set) -> None: +def walk_modules(module: ModuleType, visited: set) -> None: """Recursively find all modules from a parent module""" for pkg in pkgutil.walk_packages(module.__path__, module.__name__ + "."): if pkg.name in visited: @@ -20,13 +13,11 @@ def walk_modules(module: HasPathAndName, visited: set) -> None: visited.add(pkg.name) if pkg.ispkg: submodule = __import__(pkg.name) - submodule = cast(HasPathAndName, submodule) walk_modules(submodule, visited) samcli_modules = set(["samcli"]) samcli = __import__("samcli") -samcli = cast(HasPathAndName, samcli) walk_modules(samcli, samcli_modules) SAM_CLI_HIDDEN_IMPORTS = list(samcli_modules) + [ diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 1cfeab8d5f..80dd302d41 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -243,7 +243,7 @@ def _link_using_terraform_config(self, source_tf_resource: TFResource, cfn_resou return for cfn_resource in cfn_resources: - self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore + self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) def _link_using_linking_fields(self, cfn_resource: Dict) -> None: """ @@ -298,7 +298,7 @@ def _link_using_linking_fields(self, cfn_resource: Dict) -> None: return LOG.debug("The value of the source resource linking field after mapping %s", dest_resources) - self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore + self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) def _process_resolved_resources( self, diff --git a/samcli/hook_packages/terraform/lib/utils.py b/samcli/hook_packages/terraform/lib/utils.py index 1ea7789f8d..888c9f809b 100644 --- a/samcli/hook_packages/terraform/lib/utils.py +++ b/samcli/hook_packages/terraform/lib/utils.py @@ -74,7 +74,7 @@ def _calculate_configuration_attribute_value_hash( else: sorted_references_list = sorted( configuration_attribute_value, - key=lambda x: x.value if isinstance(x, ConstantValue) else f"{x.module_address}.{x.value}", # type: ignore + key=lambda x: x.value if isinstance(x, ConstantValue) else f"{x.module_address}.{x.value}", ) for ref in sorted_references_list: md5.update( diff --git a/samcli/lib/build/workflows.py b/samcli/lib/build/workflows.py index 66d8529e4a..d97f83b99e 100644 --- a/samcli/lib/build/workflows.py +++ b/samcli/lib/build/workflows.py @@ -4,7 +4,7 @@ from typing import List CONFIG = namedtuple( - "Capability", + "CONFIG", [ "language", "dependency_manager", diff --git a/samcli/lib/deploy/deployer.py b/samcli/lib/deploy/deployer.py index 1426360d76..16e860c54c 100644 --- a/samcli/lib/deploy/deployer.py +++ b/samcli/lib/deploy/deployer.py @@ -624,7 +624,7 @@ def sync( msg = "" if exists: - kwargs["DisableRollback"] = disable_rollback + kwargs["DisableRollback"] = disable_rollback # type: ignore result = self.update_stack(**kwargs) self.wait_for_execute(stack_name, "UPDATE", disable_rollback, on_failure=on_failure) diff --git a/samcli/lib/hook/hook_config.py b/samcli/lib/hook/hook_config.py index 8a71a0993b..92908642de 100644 --- a/samcli/lib/hook/hook_config.py +++ b/samcli/lib/hook/hook_config.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import Dict, NamedTuple, Optional, cast -import jsonschema # type: ignore +import jsonschema from .exceptions import InvalidHookPackageConfigException diff --git a/samcli/lib/iac/cdk/cdk_iac.py b/samcli/lib/iac/cdk/cdk_iac.py index 30fcd1c168..700c1ef3c3 100644 --- a/samcli/lib/iac/cdk/cdk_iac.py +++ b/samcli/lib/iac/cdk/cdk_iac.py @@ -19,13 +19,13 @@ class CdkIacImplementation(IaCPluginInterface): the CDK project type """ - def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: + def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: # type: ignore pass - def write_project(self, project: SamCliProject, build_dir: str) -> bool: + def write_project(self, project: SamCliProject, build_dir: str) -> bool: # type: ignore pass - def update_packaged_locations(self, stack: Stack) -> bool: + def update_packaged_locations(self, stack: Stack) -> bool: # type: ignore pass @staticmethod diff --git a/samcli/lib/iac/cfn/cfn_iac.py b/samcli/lib/iac/cfn/cfn_iac.py index a446ebae9c..7617af2f92 100644 --- a/samcli/lib/iac/cfn/cfn_iac.py +++ b/samcli/lib/iac/cfn/cfn_iac.py @@ -1,7 +1,6 @@ """ Provide a CFN implementation of IaCPluginInterface """ - import logging import os from typing import List, Optional @@ -72,11 +71,11 @@ def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: stack = self._build_stack(self._template_file) return SamCliProject([stack]) - def write_project(self, project: SamCliProject, build_dir: str) -> bool: + def write_project(self, project: SamCliProject, build_dir: str) -> bool: # type: ignore # TODO pass - def update_packaged_locations(self, stack: Stack) -> bool: + def update_packaged_locations(self, stack: Stack) -> bool: # type: ignore # TODO pass diff --git a/samcli/lib/pipeline/bootstrap/stage.py b/samcli/lib/pipeline/bootstrap/stage.py index 314b6e4a48..06ab4fa6dc 100644 --- a/samcli/lib/pipeline/bootstrap/stage.py +++ b/samcli/lib/pipeline/bootstrap/stage.py @@ -13,7 +13,7 @@ import click import requests from botocore.exceptions import ClientError -from OpenSSL import SSL, crypto # type: ignore +from OpenSSL import SSL, crypto from samcli.commands.pipeline.bootstrap.guided_context import BITBUCKET, GITHUB_ACTIONS, GITLAB, OPEN_ID_CONNECT from samcli.commands.pipeline.bootstrap.pipeline_oidc_provider import PipelineOidcProvider @@ -222,7 +222,7 @@ def generate_thumbprint(oidc_provider_url: Optional[str]) -> Optional[str]: # If we attempt to get the cert chain without exchanging some traffic it will be empty c.sendall(str.encode("HEAD / HTTP/1.0\n\n")) peerCertChain = c.get_peer_cert_chain() - cert = peerCertChain[-1] + cert = peerCertChain[-1] # type: ignore # Dump the certificate in DER/ASN1 format so that its SHA1 hash can be computed dumped_cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert) diff --git a/samcli/lib/providers/provider.py b/samcli/lib/providers/provider.py index fc87e5bc81..bdf657cfe8 100644 --- a/samcli/lib/providers/provider.py +++ b/samcli/lib/providers/provider.py @@ -463,9 +463,11 @@ def binary_media_types(self) -> List[str]: return list(self.binary_media_types_set) -_CorsTuple = namedtuple("Cors", ["allow_origin", "allow_methods", "allow_headers", "allow_credentials", "max_age"]) +_CorsTuple = namedtuple( + "_CorsTuple", ["allow_origin", "allow_methods", "allow_headers", "allow_credentials", "max_age"] +) -_CorsTuple.__new__.__defaults__ = ( # type: ignore +_CorsTuple.__new__.__defaults__ = ( None, # Allow Origin defaults to None None, # Allow Methods is optional and defaults to empty None, # Allow Headers is optional and defaults to empty diff --git a/samcli/lib/utils/lock_distributor.py b/samcli/lib/utils/lock_distributor.py index 94536b7a2f..2d4ad8dec0 100644 --- a/samcli/lib/utils/lock_distributor.py +++ b/samcli/lib/utils/lock_distributor.py @@ -72,7 +72,7 @@ def __init__( self._manager = manager self._dict_lock = self._create_new_lock() self._locks = ( - self._manager.dict() + self._manager.dict() # type: ignore if self._lock_type == LockDistributorType.PROCESS and self._manager is not None else dict() ) diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py index f3020cc51e..d4574931b1 100644 --- a/samcli/local/docker/container.py +++ b/samcli/local/docker/container.py @@ -206,9 +206,6 @@ def create(self): # Ex: 128m => 128MB kwargs["mem_limit"] = "{}m".format(self._memory_limit_mb) - if self.network_id == "host": - kwargs["network_mode"] = self.network_id - real_container = self.docker_client.containers.create(self._image, **kwargs) self.id = real_container.id diff --git a/setup.py b/setup.py index 25d409f8db..792fed29f5 100644 --- a/setup.py +++ b/setup.py @@ -71,6 +71,7 @@ def read_version(): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.11", "Topic :: Internet", "Topic :: Software Development :: Build Tools", "Topic :: Utilities", diff --git a/tests/integration/logs/test_logs_command.py b/tests/integration/logs/test_logs_command.py index 4bac06d740..22a15b8c72 100644 --- a/tests/integration/logs/test_logs_command.py +++ b/tests/integration/logs/test_logs_command.py @@ -2,7 +2,7 @@ import logging import time from pathlib import Path -from typing import List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import boto3 import pytest @@ -30,7 +30,7 @@ class LogsIntegTestCases(LogsIntegBase): test_template_folder = "" stack_name = "" - stack_resources = {} + stack_resources: Dict[Any, Any] = {} stack_info = None def setUp(self): @@ -76,7 +76,7 @@ def _get_physical_id(self, resource_path: str): return self.stack_resources[resource_path] def _get_output_value(self, key: str): - for output in self.stack_info.outputs: + for output in self.stack_info.outputs: # type: ignore if output.get("OutputKey", "") == key: return output.get("OutputValue", "") diff --git a/tests/integration/traces/test_traces_command.py b/tests/integration/traces/test_traces_command.py index cadca71c42..eb0a1d3fcc 100644 --- a/tests/integration/traces/test_traces_command.py +++ b/tests/integration/traces/test_traces_command.py @@ -1,7 +1,7 @@ import itertools import time from pathlib import Path -from typing import List +from typing import Any, List from unittest import skipIf import boto3 @@ -32,7 +32,7 @@ @skipIf(SKIP_TRACES_TESTS, "Skip traces tests in CI/CD only") @pytest.mark.xdist_group(name="sam_traces") class TestTracesCommand(TracesIntegBase): - stack_resources = [] + stack_resources: List[Any] = [] stack_name = "" def setUp(self): diff --git a/tests/unit/commands/deploy/test_command.py b/tests/unit/commands/deploy/test_command.py index bb96c16079..9f6a59d8e3 100644 --- a/tests/unit/commands/deploy/test_command.py +++ b/tests/unit/commands/deploy/test_command.py @@ -64,7 +64,7 @@ def setUp(self): def tearDown(self): self.companion_stack_manager_patch.stop() - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore @patch("samcli.commands.package.command.click") @patch("samcli.commands.package.package_context.PackageContext") @patch("samcli.commands.deploy.command.click") diff --git a/tests/unit/commands/sync/test_command.py b/tests/unit/commands/sync/test_command.py index 94f0dd118f..b0bcede4d0 100644 --- a/tests/unit/commands/sync/test_command.py +++ b/tests/unit/commands/sync/test_command.py @@ -68,7 +68,7 @@ def setUp(self): (False, False, False, False, True, InfraSyncResult(True)), ] ) - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore @patch("samcli.commands.sync.command.click") @patch("samcli.commands.sync.command.execute_code_sync") @patch("samcli.commands.build.command.click") diff --git a/tests/unit/commands/validate/test_cli.py b/tests/unit/commands/validate/test_cli.py index b952de467f..c19f5d0377 100644 --- a/tests/unit/commands/validate/test_cli.py +++ b/tests/unit/commands/validate/test_cli.py @@ -11,8 +11,8 @@ from samcli.commands.validate.lib.exceptions import InvalidSamDocumentException from samcli.commands.validate.validate import do_cli, _read_sam_file, _lint -ctx_mock = namedtuple("ctx", ["profile", "region"]) -ctx_lint_mock = namedtuple("ctx", ["debug", "region"]) +ctx_mock = namedtuple("ctx_mock", ["profile", "region"]) +ctx_lint_mock = namedtuple("ctx_lint_mock", ["debug", "region"]) class TestValidateCli(TestCase): diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index 34f7cc5ae0..98b8006ad0 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -1418,8 +1418,7 @@ def setUp(self): def test_must_write_absolute_path_for_different_drives(self): def mock_new(cls, *args, **kwargs): cls = WindowsPath - self = cls._from_parts(args, init=False) - self._init() + self = cls._from_parts(args) return self def mock_resolve(self): diff --git a/tests/unit/lib/deploy/test_deployer.py b/tests/unit/lib/deploy/test_deployer.py index bd04722b03..9844366084 100644 --- a/tests/unit/lib/deploy/test_deployer.py +++ b/tests/unit/lib/deploy/test_deployer.py @@ -343,7 +343,7 @@ def test_wait_for_changeset(self): self.deployer._client.get_waiter = MagicMock(return_value=MockChangesetWaiter()) self.deployer.wait_for_changeset("test-id", "test-stack") - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore def test_wait_for_changeset_client_sleep(self): deployer = Deployer(MagicMock().client("cloudformation"), client_sleep=os.getenv("SAM_CLI_POLL_DELAY", 0.5)) deployer._client.get_waiter = MagicMock(return_value=MockChangesetWaiter()) @@ -358,7 +358,7 @@ def test_wait_for_changeset_default_delay(self): ChangeSetName="test-id", StackName="test-stack", WaiterConfig={"Delay": 0.5} ) - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore def test_wait_for_changeset_custom_delay(self): deployer = Deployer(MagicMock().client("cloudformation"), client_sleep=os.getenv("SAM_CLI_POLL_DELAY")) deployer.wait_for_changeset("test-id", "test-stack") diff --git a/tests/unit/lib/pipeline/bootstrap/test_environment.py b/tests/unit/lib/pipeline/bootstrap/test_environment.py index 3728c870d4..85eddc5fc8 100644 --- a/tests/unit/lib/pipeline/bootstrap/test_environment.py +++ b/tests/unit/lib/pipeline/bootstrap/test_environment.py @@ -2,7 +2,7 @@ from unittest import TestCase from unittest.mock import Mock, patch, call, MagicMock -import OpenSSL.SSL # type: ignore +import OpenSSL.SSL import requests from samcli.commands.pipeline.bootstrap.guided_context import GITHUB_ACTIONS diff --git a/tests/unit/local/apigw/test_lambda_authorizer.py b/tests/unit/local/apigw/test_lambda_authorizer.py index 41f81249a4..dc7ca00acb 100644 --- a/tests/unit/local/apigw/test_lambda_authorizer.py +++ b/tests/unit/local/apigw/test_lambda_authorizer.py @@ -25,7 +25,7 @@ def test_valid_header_identity_source(self): [ ({"headers": Headers({})},), # test empty headers ({},), # test no headers - ({"headers": Headers({"not here": 123})},), # test missing headers + ({"headers": Headers({"not here": 123})},), # type: ignore # test missing headers ({"validation_expression": "^123$"},), # test no headers, but provided validation ] ) diff --git a/tests/unit/local/docker/test_container.py b/tests/unit/local/docker/test_container.py index 61ccad4c9f..14f292c0ce 100644 --- a/tests/unit/local/docker/test_container.py +++ b/tests/unit/local/docker/test_container.py @@ -296,7 +296,6 @@ def test_must_connect_to_host_network_on_create(self): tty=False, use_config_proxy=True, volumes=expected_volumes, - network_mode="host", ) self.mock_docker_client.networks.get.assert_not_called() From c5ce5c75f494b4d02232ad559d551ea0141b41f3 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Fri, 2 Jun 2023 13:34:23 -0700 Subject: [PATCH 021/107] fix: fix build and deploy SAR integration test cases (#5244) * fix: fix build SAR integration test cases * add comments to the UpdatableSARTemplate class usage. * fix black check --- .../remote_invoke/remote_invoke_context.py | 1 - tests/integration/buildcmd/test_build_cmd.py | 15 ++++ .../integration/deploy/test_deploy_command.py | 70 ++++++++++--------- ...s-application-with-application-id-map.yaml | 6 +- tests/testing_utils.py | 41 +++++++++++ 5 files changed, 96 insertions(+), 37 deletions(-) diff --git a/samcli/commands/remote_invoke/remote_invoke_context.py b/samcli/commands/remote_invoke/remote_invoke_context.py index 7795476b89..1e176f01a3 100644 --- a/samcli/commands/remote_invoke/remote_invoke_context.py +++ b/samcli/commands/remote_invoke/remote_invoke_context.py @@ -30,7 +30,6 @@ class RemoteInvokeContext: - _boto_client_provider: BotoProviderType _boto_resource_provider: BotoProviderType _stack_name: Optional[str] diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index e9e0f34d3d..f23b4f4f10 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -26,6 +26,7 @@ SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE, run_command_with_input, + UpdatableSARTemplate, ) from .build_integ_base import ( BuildIntegBase, @@ -2959,6 +2960,20 @@ def test_functions_layers_with_s3_codeuri(self): class TestBuildSAR(BuildIntegBase): template = "aws-serverless-application-with-application-id-map.yaml" + @classmethod + def setUpClass(cls): + super(TestBuildSAR, cls).setUpClass() + cls.update_sar_template = None + if cls.template_path: + cls.update_sar_template = UpdatableSARTemplate(cls.template_path) + cls.update_sar_template.setup() + cls.template_path = cls.update_sar_template.updated_template_path + + @classmethod + def tearDownClass(cls): + if cls.update_sar_template: + cls.update_sar_template.clean() + @parameterized.expand( [ ("use_container", "us-east-2"), diff --git a/tests/integration/deploy/test_deploy_command.py b/tests/integration/deploy/test_deploy_command.py index 87dbe5374f..131d3969ab 100644 --- a/tests/integration/deploy/test_deploy_command.py +++ b/tests/integration/deploy/test_deploy_command.py @@ -12,7 +12,7 @@ from samcli.lib.bootstrap.bootstrap import SAM_CLI_STACK_NAME from samcli.lib.config.samconfig import DEFAULT_CONFIG_FILE_NAME from tests.integration.deploy.deploy_integ_base import DeployIntegBase -from tests.testing_utils import RUNNING_ON_CI, RUNNING_TEST_FOR_MASTER_ON_CI, RUN_BY_CANARY +from tests.testing_utils import RUNNING_ON_CI, RUNNING_TEST_FOR_MASTER_ON_CI, RUN_BY_CANARY, UpdatableSARTemplate # Deploy tests require credentials and CI/CD will only add credentials to the env if the PR is from the same repo. # This is to restrict package tests to run outside of CI/CD, when the branch is not master or tests are not run by Canary @@ -888,25 +888,28 @@ def test_deploy_with_code_signing_params(self, should_sign, should_enforce, will ] ) def test_deploy_sar_with_location_from_map(self, template_file, region, will_succeed): - template_path = Path(__file__).resolve().parents[1].joinpath("testdata", "buildcmd", template_file) - stack_name = self._method_to_stack_name(self.id()) - self.stacks.append({"name": stack_name, "region": region}) + with UpdatableSARTemplate( + Path(__file__).resolve().parents[1].joinpath("testdata", "buildcmd", template_file) + ) as sar_app: + template_path = sar_app.updated_template_path + stack_name = self._method_to_stack_name(self.id()) + self.stacks.append({"name": stack_name, "region": region}) - # The default region (us-east-1) has no entry in the map - deploy_command_list = self.get_deploy_command_list( - template_file=template_path, - s3_prefix=self.s3_prefix, - stack_name=stack_name, - capabilities_list=["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"], - region=region, # the !FindInMap has an entry for use-east-2 region only - ) - deploy_process_execute = self.run_command(deploy_command_list) + # The default region (us-east-1) has no entry in the map + deploy_command_list = self.get_deploy_command_list( + template_file=template_path, + s3_prefix=self.s3_prefix, + stack_name=stack_name, + capabilities_list=["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"], + region=region, # the !FindInMap has an entry for use-east-2 region only + ) + deploy_process_execute = self.run_command(deploy_command_list) - if will_succeed: - self.assertEqual(deploy_process_execute.process.returncode, 0) - else: - self.assertEqual(deploy_process_execute.process.returncode, 1) - self.assertIn("Property \\'ApplicationId\\' cannot be resolved.", str(deploy_process_execute.stderr)) + if will_succeed: + self.assertEqual(deploy_process_execute.process.returncode, 0) + else: + self.assertEqual(deploy_process_execute.process.returncode, 1) + self.assertIn("Property \\'ApplicationId\\' cannot be resolved.", str(deploy_process_execute.stderr)) @parameterized.expand( [ @@ -915,23 +918,26 @@ def test_deploy_sar_with_location_from_map(self, template_file, region, will_suc ] ) def test_deploy_guided_sar_with_location_from_map(self, template_file, region, will_succeed): - template_path = Path(__file__).resolve().parents[1].joinpath("testdata", "buildcmd", template_file) - stack_name = self._method_to_stack_name(self.id()) - self.stacks.append({"name": stack_name, "region": region}) + with UpdatableSARTemplate( + Path(__file__).resolve().parents[1].joinpath("testdata", "buildcmd", template_file) + ) as sar_app: + template_path = sar_app.updated_template_path + stack_name = self._method_to_stack_name(self.id()) + self.stacks.append({"name": stack_name, "region": region}) - # Package and Deploy in one go without confirming change set. - deploy_command_list = self.get_deploy_command_list(template_file=template_path, guided=True) + # Package and Deploy in one go without confirming change set. + deploy_command_list = self.get_deploy_command_list(template_file=template_path, guided=True) - deploy_process_execute = self.run_command_with_input( - deploy_command_list, - f"{stack_name}\n{region}\n\nN\nCAPABILITY_IAM CAPABILITY_AUTO_EXPAND\nn\nN\n".encode(), - ) + deploy_process_execute = self.run_command_with_input( + deploy_command_list, + f"{stack_name}\n{region}\n\nN\nCAPABILITY_IAM CAPABILITY_AUTO_EXPAND\nn\nN\n".encode(), + ) - if will_succeed: - self.assertEqual(deploy_process_execute.process.returncode, 0) - else: - self.assertEqual(deploy_process_execute.process.returncode, 1) - self.assertIn("Property \\'ApplicationId\\' cannot be resolved.", str(deploy_process_execute.stderr)) + if will_succeed: + self.assertEqual(deploy_process_execute.process.returncode, 0) + else: + self.assertEqual(deploy_process_execute.process.returncode, 1) + self.assertIn("Property \\'ApplicationId\\' cannot be resolved.", str(deploy_process_execute.stderr)) @parameterized.expand( [os.path.join("deep-nested", "template.yaml"), os.path.join("deep-nested-image", "template.yaml")] diff --git a/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml b/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml index d1f83aaf40..683b40b9b6 100644 --- a/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml +++ b/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml @@ -4,7 +4,7 @@ Transform: AWS::Serverless-2016-10-31 Mappings: MappingExample: us-east-2: - ApplicationId: !Sub arn:aws:serverlessrepo:us-east-1:${AWS::AccountId}:applications/sam-cli-integration-test-sar-app + ApplicationId: arn:aws:serverlessrepo:us-east-1:${AWS::AccountId}:applications/shared-sam-cli-integration-test-sar-app Resources: MyApplication: @@ -12,6 +12,4 @@ Resources: Properties: Location: ApplicationId: !FindInMap [ MappingExample, !Ref AWS::Region, ApplicationId ] - SemanticVersion: 1.0.4 - Parameters: - IdentityNameParameter: AnyValue \ No newline at end of file + SemanticVersion: 1.0.4 \ No newline at end of file diff --git a/tests/testing_utils.py b/tests/testing_utils.py index cd98d7d8e4..a52898f90b 100644 --- a/tests/testing_utils.py +++ b/tests/testing_utils.py @@ -3,6 +3,7 @@ import platform import subprocess import tempfile +from pathlib import Path from threading import Thread from typing import Callable, List, Optional @@ -13,6 +14,7 @@ import shutil from uuid import uuid4 +import boto3 import psutil RUNNING_ON_APPVEYOR = os.environ.get("APPVEYOR", False) @@ -232,3 +234,42 @@ def full_path(self, filename): f.full_path('foo/bar.txt') -> /tmp/asdfasd/foo/bar.txt """ return os.path.join(self.rootdir, filename) + + +def _get_current_account_id(): + sts = boto3.client("sts") + account_id = sts.get_caller_identity()["Account"] + return account_id + + +class UpdatableSARTemplate: + """ + This class is used to replace the `${AWS::AccountId}` in the testing templates with the account id for the testing + is used during the integration testing. This class helps to resolve the problem that SAM CLI does not support Sub + intrinsic function, and to avoid exposing any of our testing accounts ids. + """ + + def __init__(self, source_template_path): + self.source_template_path = source_template_path + self.temp_directory = tempfile.TemporaryDirectory() + self.temp_directory_path = Path(tempfile.TemporaryDirectory().name) + self.updated_template_path = None + + def setup(self): + with open(self.source_template_path, "r") as sar_template: + updated_template_content = sar_template.read() + updated_template_content = updated_template_content.replace("${AWS::AccountId}", _get_current_account_id()) + self.temp_directory_path.mkdir() + self.updated_template_path = os.path.join(self.temp_directory_path, "template.yml") + with open(self.updated_template_path, "w") as updated_template: + updated_template.write(updated_template_content) + + def clean(self): + self.temp_directory.cleanup() + + def __enter__(self): + self.setup() + return self + + def __exit__(self, *args): + self.clean() From 912633df0f9e31eb859a5558e9335368d62f2059 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 13:07:04 -0500 Subject: [PATCH 022/107] chore(deps): bump markupsafe from 2.1.2 to 2.1.3 in /requirements (#5257) Bumps [markupsafe](https://github.com/pallets/markupsafe) from 2.1.2 to 2.1.3. - [Release notes](https://github.com/pallets/markupsafe/releases) - [Changelog](https://github.com/pallets/markupsafe/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/markupsafe/compare/2.1.2...2.1.3) --- updated-dependencies: - dependency-name: markupsafe dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/reproducible-linux.txt | 102 ++++++++++++++-------------- requirements/reproducible-mac.txt | 102 ++++++++++++++-------------- 2 files changed, 102 insertions(+), 102 deletions(-) diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 8aa507e1cd..985e728487 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -305,57 +305,57 @@ markdown-it-py==2.2.0 \ --hash=sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30 \ --hash=sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1 # via rich -markupsafe==2.1.2 \ - --hash=sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed \ - --hash=sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc \ - --hash=sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2 \ - --hash=sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460 \ - --hash=sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7 \ - --hash=sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0 \ - --hash=sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1 \ - --hash=sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa \ - --hash=sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03 \ - --hash=sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 \ - --hash=sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65 \ - --hash=sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013 \ - --hash=sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036 \ - --hash=sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f \ - --hash=sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4 \ - --hash=sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419 \ - --hash=sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2 \ - --hash=sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619 \ - --hash=sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a \ - --hash=sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a \ - --hash=sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd \ - --hash=sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7 \ - --hash=sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666 \ - --hash=sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65 \ - --hash=sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859 \ - --hash=sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625 \ - --hash=sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff \ - --hash=sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156 \ - --hash=sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd \ - --hash=sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba \ - --hash=sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f \ - --hash=sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1 \ - --hash=sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094 \ - --hash=sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a \ - --hash=sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513 \ - --hash=sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed \ - --hash=sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d \ - --hash=sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3 \ - --hash=sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147 \ - --hash=sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c \ - --hash=sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603 \ - --hash=sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601 \ - --hash=sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a \ - --hash=sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1 \ - --hash=sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d \ - --hash=sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3 \ - --hash=sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54 \ - --hash=sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2 \ - --hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \ - --hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58 +markupsafe==2.1.3 \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 # via # jinja2 # werkzeug diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 3f612124e8..137aa39954 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -336,57 +336,57 @@ markdown-it-py==2.2.0 \ --hash=sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30 \ --hash=sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1 # via rich -markupsafe==2.1.2 \ - --hash=sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed \ - --hash=sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc \ - --hash=sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2 \ - --hash=sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460 \ - --hash=sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7 \ - --hash=sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0 \ - --hash=sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1 \ - --hash=sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa \ - --hash=sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03 \ - --hash=sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 \ - --hash=sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65 \ - --hash=sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013 \ - --hash=sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036 \ - --hash=sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f \ - --hash=sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4 \ - --hash=sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419 \ - --hash=sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2 \ - --hash=sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619 \ - --hash=sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a \ - --hash=sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a \ - --hash=sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd \ - --hash=sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7 \ - --hash=sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666 \ - --hash=sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65 \ - --hash=sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859 \ - --hash=sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625 \ - --hash=sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff \ - --hash=sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156 \ - --hash=sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd \ - --hash=sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba \ - --hash=sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f \ - --hash=sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1 \ - --hash=sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094 \ - --hash=sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a \ - --hash=sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513 \ - --hash=sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed \ - --hash=sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d \ - --hash=sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3 \ - --hash=sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147 \ - --hash=sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c \ - --hash=sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603 \ - --hash=sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601 \ - --hash=sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a \ - --hash=sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1 \ - --hash=sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d \ - --hash=sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3 \ - --hash=sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54 \ - --hash=sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2 \ - --hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \ - --hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58 +markupsafe==2.1.3 \ + --hash=sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e \ + --hash=sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e \ + --hash=sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431 \ + --hash=sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686 \ + --hash=sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559 \ + --hash=sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc \ + --hash=sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c \ + --hash=sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0 \ + --hash=sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4 \ + --hash=sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9 \ + --hash=sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575 \ + --hash=sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba \ + --hash=sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d \ + --hash=sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3 \ + --hash=sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00 \ + --hash=sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155 \ + --hash=sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac \ + --hash=sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52 \ + --hash=sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f \ + --hash=sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8 \ + --hash=sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b \ + --hash=sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24 \ + --hash=sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea \ + --hash=sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198 \ + --hash=sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0 \ + --hash=sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee \ + --hash=sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be \ + --hash=sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2 \ + --hash=sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707 \ + --hash=sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6 \ + --hash=sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58 \ + --hash=sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779 \ + --hash=sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636 \ + --hash=sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c \ + --hash=sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad \ + --hash=sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee \ + --hash=sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc \ + --hash=sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2 \ + --hash=sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48 \ + --hash=sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7 \ + --hash=sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e \ + --hash=sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b \ + --hash=sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa \ + --hash=sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5 \ + --hash=sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e \ + --hash=sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb \ + --hash=sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9 \ + --hash=sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57 \ + --hash=sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc \ + --hash=sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2 # via # jinja2 # werkzeug From 0c4ba78893ea5ca39dd8a00be6cb48f5661945bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 18:07:52 +0000 Subject: [PATCH 023/107] chore(deps): bump pydantic from 1.10.7 to 1.10.8 in /requirements (#5258) Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.7 to 1.10.8. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.8/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v1.10.7...v1.10.8) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/reproducible-linux.txt | 74 ++++++++++++++--------------- requirements/reproducible-mac.txt | 74 ++++++++++++++--------------- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 985e728487..89fd165276 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -385,43 +385,43 @@ pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pydantic==1.10.7 \ - --hash=sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e \ - --hash=sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6 \ - --hash=sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd \ - --hash=sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca \ - --hash=sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b \ - --hash=sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a \ - --hash=sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245 \ - --hash=sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d \ - --hash=sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee \ - --hash=sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1 \ - --hash=sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3 \ - --hash=sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d \ - --hash=sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5 \ - --hash=sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914 \ - --hash=sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd \ - --hash=sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1 \ - --hash=sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e \ - --hash=sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e \ - --hash=sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a \ - --hash=sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd \ - --hash=sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f \ - --hash=sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209 \ - --hash=sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d \ - --hash=sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a \ - --hash=sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143 \ - --hash=sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918 \ - --hash=sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52 \ - --hash=sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e \ - --hash=sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f \ - --hash=sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e \ - --hash=sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb \ - --hash=sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe \ - --hash=sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe \ - --hash=sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d \ - --hash=sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209 \ - --hash=sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af +pydantic==1.10.8 \ + --hash=sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375 \ + --hash=sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277 \ + --hash=sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d \ + --hash=sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4 \ + --hash=sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca \ + --hash=sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c \ + --hash=sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01 \ + --hash=sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18 \ + --hash=sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68 \ + --hash=sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887 \ + --hash=sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459 \ + --hash=sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4 \ + --hash=sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5 \ + --hash=sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e \ + --hash=sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1 \ + --hash=sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33 \ + --hash=sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a \ + --hash=sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56 \ + --hash=sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108 \ + --hash=sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2 \ + --hash=sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4 \ + --hash=sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878 \ + --hash=sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0 \ + --hash=sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e \ + --hash=sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6 \ + --hash=sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f \ + --hash=sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800 \ + --hash=sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea \ + --hash=sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f \ + --hash=sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b \ + --hash=sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1 \ + --hash=sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd \ + --hash=sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319 \ + --hash=sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab \ + --hash=sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85 \ + --hash=sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f # via aws-sam-translator pygments==2.15.1 \ --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 137aa39954..e2fc641da4 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -420,43 +420,43 @@ pycparser==2.21 \ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 # via cffi -pydantic==1.10.7 \ - --hash=sha256:01aea3a42c13f2602b7ecbbea484a98169fb568ebd9e247593ea05f01b884b2e \ - --hash=sha256:0cd181f1d0b1d00e2b705f1bf1ac7799a2d938cce3376b8007df62b29be3c2c6 \ - --hash=sha256:10a86d8c8db68086f1e30a530f7d5f83eb0685e632e411dbbcf2d5c0150e8dcd \ - --hash=sha256:193924c563fae6ddcb71d3f06fa153866423ac1b793a47936656e806b64e24ca \ - --hash=sha256:464855a7ff7f2cc2cf537ecc421291b9132aa9c79aef44e917ad711b4a93163b \ - --hash=sha256:516f1ed9bc2406a0467dd777afc636c7091d71f214d5e413d64fef45174cfc7a \ - --hash=sha256:6434b49c0b03a51021ade5c4daa7d70c98f7a79e95b551201fff682fc1661245 \ - --hash=sha256:64d34ab766fa056df49013bb6e79921a0265204c071984e75a09cbceacbbdd5d \ - --hash=sha256:670bb4683ad1e48b0ecb06f0cfe2178dcf74ff27921cdf1606e527d2617a81ee \ - --hash=sha256:68792151e174a4aa9e9fc1b4e653e65a354a2fa0fed169f7b3d09902ad2cb6f1 \ - --hash=sha256:701daea9ffe9d26f97b52f1d157e0d4121644f0fcf80b443248434958fd03dc3 \ - --hash=sha256:7d45fc99d64af9aaf7e308054a0067fdcd87ffe974f2442312372dfa66e1001d \ - --hash=sha256:80b1fab4deb08a8292d15e43a6edccdffa5377a36a4597bb545b93e79c5ff0a5 \ - --hash=sha256:82dffb306dd20bd5268fd6379bc4bfe75242a9c2b79fec58e1041fbbdb1f7914 \ - --hash=sha256:8c7f51861d73e8b9ddcb9916ae7ac39fb52761d9ea0df41128e81e2ba42886cd \ - --hash=sha256:950ce33857841f9a337ce07ddf46bc84e1c4946d2a3bba18f8280297157a3fd1 \ - --hash=sha256:976cae77ba6a49d80f461fd8bba183ff7ba79f44aa5cfa82f1346b5626542f8e \ - --hash=sha256:9f6f0fd68d73257ad6685419478c5aece46432f4bdd8d32c7345f1986496171e \ - --hash=sha256:a7cd2251439988b413cb0a985c4ed82b6c6aac382dbaff53ae03c4b23a70e80a \ - --hash=sha256:abfb7d4a7cd5cc4e1d1887c43503a7c5dd608eadf8bc615413fc498d3e4645cd \ - --hash=sha256:ae150a63564929c675d7f2303008d88426a0add46efd76c3fc797cd71cb1b46f \ - --hash=sha256:b0f85904f73161817b80781cc150f8b906d521fa11e3cdabae19a581c3606209 \ - --hash=sha256:b4a849d10f211389502059c33332e91327bc154acc1845f375a99eca3afa802d \ - --hash=sha256:c15582f9055fbc1bfe50266a19771bbbef33dd28c45e78afbe1996fd70966c2a \ - --hash=sha256:c230c0d8a322276d6e7b88c3f7ce885f9ed16e0910354510e0bae84d54991143 \ - --hash=sha256:cc1dde4e50a5fc1336ee0581c1612215bc64ed6d28d2c7c6f25d2fe3e7c3e918 \ - --hash=sha256:cf135c46099ff3f919d2150a948ce94b9ce545598ef2c6c7bf55dca98a304b52 \ - --hash=sha256:cfc83c0678b6ba51b0532bea66860617c4cd4251ecf76e9846fa5a9f3454e97e \ - --hash=sha256:d2a5ebb48958754d386195fe9e9c5106f11275867051bf017a8059410e9abf1f \ - --hash=sha256:d71e69699498b020ea198468e2480a2f1e7433e32a3a99760058c6520e2bea7e \ - --hash=sha256:d75ae19d2a3dbb146b6f324031c24f8a3f52ff5d6a9f22f0683694b3afcb16fb \ - --hash=sha256:dfe2507b8ef209da71b6fb5f4e597b50c5a34b78d7e857c4f8f3115effaef5fe \ - --hash=sha256:e0cfe895a504c060e5d36b287ee696e2fdad02d89e0d895f83037245218a87fe \ - --hash=sha256:e79e999e539872e903767c417c897e729e015872040e56b96e67968c3b918b2d \ - --hash=sha256:ecbbc51391248116c0a055899e6c3e7ffbb11fb5e2a4cd6f2d0b93272118a209 \ - --hash=sha256:f4a2b50e2b03d5776e7f21af73e2070e1b5c0d0df255a827e7c632962f8315af +pydantic==1.10.8 \ + --hash=sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375 \ + --hash=sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277 \ + --hash=sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d \ + --hash=sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4 \ + --hash=sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca \ + --hash=sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c \ + --hash=sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01 \ + --hash=sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18 \ + --hash=sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68 \ + --hash=sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887 \ + --hash=sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459 \ + --hash=sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4 \ + --hash=sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5 \ + --hash=sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e \ + --hash=sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1 \ + --hash=sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33 \ + --hash=sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a \ + --hash=sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56 \ + --hash=sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108 \ + --hash=sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2 \ + --hash=sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4 \ + --hash=sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878 \ + --hash=sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0 \ + --hash=sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e \ + --hash=sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6 \ + --hash=sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f \ + --hash=sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800 \ + --hash=sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea \ + --hash=sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f \ + --hash=sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b \ + --hash=sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1 \ + --hash=sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd \ + --hash=sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319 \ + --hash=sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab \ + --hash=sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85 \ + --hash=sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f # via aws-sam-translator pygments==2.15.1 \ --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ From dd1adb15ab4036a809f4f4cf976ded09139f5a4e Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Mon, 5 Jun 2023 11:20:50 -0700 Subject: [PATCH 024/107] feat: Add click command for cloud invoke command (#5238) * Add custom click option for cloud invoke called parameter * Added more error handling to executors and updated output-format enum to use auto * Add new CLI command for cloud invoke * Update samcli/commands/remote_invoke/invoke/cli.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Update samcli/commands/remote_invoke/invoke/cli.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Update samcli/commands/remote_invoke/cloud.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Update samcli/cli/types.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Address feedback * Moved all command options to be handled by click configuration * Updated validation function doc-string * Updated debug logs in types.py * Changed remote_invoke dir to cloud and updated log level for validation * Address feedback --------- Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> --- samcli/cli/command.py | 1 + samcli/cli/types.py | 57 +++++++ samcli/commands/_utils/options.py | 31 ++++ .../{remote_invoke => cloud}/__init__.py | 0 samcli/commands/cloud/cloud.py | 19 +++ .../{remote_invoke => cloud}/exceptions.py | 0 .../commands/cloud/invoke}/__init__.py | 0 samcli/commands/cloud/invoke/cli.py | 139 ++++++++++++++++++ .../remote_invoke_context.py | 30 +++- .../remote_invoke_options_validations.py | 94 ++++++++++++ samcli/lib/remote_invoke/exceptions.py | 6 +- .../remote_invoke/lambda_invoke_executors.py | 13 +- .../remote_invoke/remote_invoke_executors.py | 2 +- tests/unit/cli/test_types.py | 75 ++++++++++ tests/unit/commands/_utils/test_options.py | 21 +++ tests/unit/commands/cloud/__init__.py | 0 tests/unit/commands/cloud/invoke/__init__.py | 0 tests/unit/commands/cloud/invoke/test_cli.py | 138 +++++++++++++++++ .../test_remote_invoke_context.py | 24 +-- .../test_remote_invoke_options_validations.py | 111 ++++++++++++++ .../test_lambda_invoke_executors.py | 15 +- 21 files changed, 753 insertions(+), 23 deletions(-) rename samcli/commands/{remote_invoke => cloud}/__init__.py (100%) create mode 100644 samcli/commands/cloud/cloud.py rename samcli/commands/{remote_invoke => cloud}/exceptions.py (100%) rename {tests/unit/commands/remote_invoke => samcli/commands/cloud/invoke}/__init__.py (100%) create mode 100644 samcli/commands/cloud/invoke/cli.py rename samcli/commands/{remote_invoke => cloud}/remote_invoke_context.py (90%) create mode 100644 samcli/lib/cli_validation/remote_invoke_options_validations.py create mode 100644 tests/unit/commands/cloud/__init__.py create mode 100644 tests/unit/commands/cloud/invoke/__init__.py create mode 100644 tests/unit/commands/cloud/invoke/test_cli.py rename tests/unit/commands/{remote_invoke => cloud}/test_remote_invoke_context.py (84%) create mode 100644 tests/unit/lib/cli_validation/test_remote_invoke_options_validations.py diff --git a/samcli/cli/command.py b/samcli/cli/command.py index 7783933fed..b3fab518bf 100644 --- a/samcli/cli/command.py +++ b/samcli/cli/command.py @@ -30,6 +30,7 @@ "samcli.commands.pipeline.pipeline", "samcli.commands.list.list", "samcli.commands.docs", + # "samcli.commands.cloud.cloud", # We intentionally do not expose the `bootstrap` command for now. We might open it up later # "samcli.commands.bootstrap", ] diff --git a/samcli/cli/types.py b/samcli/cli/types.py index bf3f0169c0..5f12f72b31 100644 --- a/samcli/cli/types.py +++ b/samcli/cli/types.py @@ -3,6 +3,7 @@ """ import json +import logging import re from json import JSONDecodeError @@ -12,6 +13,8 @@ PARAM_AND_METADATA_KEY_REGEX = """([A-Za-z0-9\\"\']+)""" +LOG = logging.getLogger(__name__) + def _generate_match_regex(match_pattern, delim): """ @@ -421,3 +424,57 @@ def convert(self, value, param, ctx): if not is_ecr_url(_value): raise click.BadParameter(f"{param.opts[0]} needs to have valid ECR URI as value") return {key: _value} + + +class RemoteInvokeBotoApiParameterType(click.ParamType): + """ + Custom Parameter Type for Multi valued Boto API parameter option of remote invoke command. + """ + + name = "" + MIN_KEY_VALUE_PAIR_LENGTH = 2 + + def convert(self, value, param, ctx): + """Converts the user provided parameter value with the format "parameter=value" to dict + {"parameter": "value"} + + Parameters + ------------ + value: User provided value for the click option + param: click parameter + ctx: Context + """ + # Split by first "=" as some values could have multiple "=" For e.g. base-64 encoded ClientContext for Lambda + key_value_pair = value.split("=", 1) + if len(key_value_pair) < self.MIN_KEY_VALUE_PAIR_LENGTH: + raise click.BadParameter( + f"{param.opts[0]} is not a valid format, it needs to be of the form parameter_key=parameter_value" + ) + key = key_value_pair[0] + _value = key_value_pair[1] + LOG.debug("Converting provided %s option value to dict", param.opts[0]) + return {key: _value} + + +class RemoteInvokeOutputFormatType(click.Choice): + """ + Custom Parameter Type for output-format option of remote invoke command. + """ + + def __init__(self, enum): + self.enum = enum + super().__init__(choices=[item.name.lower() for item in enum]) + + def convert(self, value, param, ctx): + """Converts the user provided parameter value for the option to + the provided Enum + + Parameters + ------------ + value: User provided value for the click option + param: click parameter + ctx: Context + """ + LOG.debug("Converting provided %s option value to Enum", param.opts[0]) + value = super().convert(value, param, ctx) + return self.enum(value) diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py index e3f620cdef..07c46d41fb 100644 --- a/samcli/commands/_utils/options.py +++ b/samcli/commands/_utils/options.py @@ -16,6 +16,7 @@ CfnTags, ImageRepositoriesType, ImageRepositoryType, + RemoteInvokeBotoApiParameterType, SigningProfilesOptionType, ) from samcli.commands._utils.constants import ( @@ -129,6 +130,21 @@ def image_repositories_callback(ctx, param, provided_value): return image_repositories if image_repositories else None +def remote_invoke_boto_parameter_callback(ctx, param, provided_value): + """ + Create an dictionary of boto parameters to their provided values. + :param ctx: Click Context + :param param: Param name + :param provided_value: Value provided by Click, after being processed by RemoteInvokeBotoApiParameterType. + :return: dictionary of boto api parameters to their provided values. E.g. LogType=Tail for Lambda invoke API + """ + boto_api_parameters = {} + for value in provided_value: + boto_api_parameters.update(value) + + return boto_api_parameters + + def artifact_callback(ctx, param, provided_value, artifact): """ Provide an error if there are zip/image artifact based resources, @@ -571,6 +587,21 @@ def image_repositories_option(f): return image_repositories_click_option()(f) +def remote_invoke_parameter_click_option(): + return click.option( + "--parameter", + multiple=True, + type=RemoteInvokeBotoApiParameterType(), + callback=remote_invoke_boto_parameter_callback, + required=False, + help="Additional parameters for the boto API call.\n" "Lambda APIs: invoke and invoke_with_response_stream", + ) + + +def remote_invoke_parameter_option(f): + return remote_invoke_parameter_click_option()(f) + + def s3_prefix_click_option(): return click.option( "--s3-prefix", diff --git a/samcli/commands/remote_invoke/__init__.py b/samcli/commands/cloud/__init__.py similarity index 100% rename from samcli/commands/remote_invoke/__init__.py rename to samcli/commands/cloud/__init__.py diff --git a/samcli/commands/cloud/cloud.py b/samcli/commands/cloud/cloud.py new file mode 100644 index 0000000000..a2cf4c1f70 --- /dev/null +++ b/samcli/commands/cloud/cloud.py @@ -0,0 +1,19 @@ +""" +Command group for "cloud" suite for commands. It provides common CLI arguments for interacting with +cloud resources such as Lambda Function. +""" + +import click + +from samcli.commands.cloud.invoke.cli import cli as invoke_cli + + +@click.group() +def cli(): + """ + Interact with your Serverless application in the cloud for quick development & testing + """ + + +# Add individual commands under this group +cli.add_command(invoke_cli) diff --git a/samcli/commands/remote_invoke/exceptions.py b/samcli/commands/cloud/exceptions.py similarity index 100% rename from samcli/commands/remote_invoke/exceptions.py rename to samcli/commands/cloud/exceptions.py diff --git a/tests/unit/commands/remote_invoke/__init__.py b/samcli/commands/cloud/invoke/__init__.py similarity index 100% rename from tests/unit/commands/remote_invoke/__init__.py rename to samcli/commands/cloud/invoke/__init__.py diff --git a/samcli/commands/cloud/invoke/cli.py b/samcli/commands/cloud/invoke/cli.py new file mode 100644 index 0000000000..532ff26efd --- /dev/null +++ b/samcli/commands/cloud/invoke/cli.py @@ -0,0 +1,139 @@ +"""CLI command for "invoke" command.""" +import logging +from io import TextIOWrapper +from typing import cast + +import click + +from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.context import Context +from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args +from samcli.cli.types import RemoteInvokeOutputFormatType +from samcli.commands._utils.options import remote_invoke_parameter_option +from samcli.lib.cli_validation.remote_invoke_options_validations import ( + event_and_event_file_options_validation, + stack_name_or_resource_id_atleast_one_option_validation, +) +from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeOutputFormat +from samcli.lib.telemetry.metric import track_command +from samcli.lib.utils.version_checker import check_newer_version + +LOG = logging.getLogger(__name__) + +HELP_TEXT = """ +Invoke or send an event to cloud resources in your CFN stack +""" +SHORT_HELP = "Invoke a deployed resource in the cloud" + + +@click.command("invoke", help=HELP_TEXT, short_help=SHORT_HELP) +@configuration_option(provider=TomlProvider(section="parameters")) +@click.option("--stack-name", required=False, help="Name of the stack to get the resource information from") +@click.option("--resource-id", required=False, help="Name of the resource that will be invoked") +@click.option( + "--event", + "-e", + help="The event that will be sent to the resource. The target parameter will depend on the resource type. " + "For instance: 'Payload' for Lambda", +) +@click.option( + "--event-file", + type=click.File("r", encoding="utf-8"), + help="The file that contains the event that will be sent to the resource", +) +@click.option( + "--output-format", + help="Output format for the boto API response", + default=RemoteInvokeOutputFormat.DEFAULT.name.lower(), + type=RemoteInvokeOutputFormatType(RemoteInvokeOutputFormat), +) +@remote_invoke_parameter_option +@stack_name_or_resource_id_atleast_one_option_validation +@event_and_event_file_options_validation +@common_options +@aws_creds_options +@pass_context +@track_command +@check_newer_version +@print_cmdline_args +def cli( + ctx: Context, + stack_name: str, + resource_id: str, + event: str, + event_file: TextIOWrapper, + output_format: RemoteInvokeOutputFormat, + parameter: dict, + config_file: str, + config_env: str, +) -> None: + """ + `sam cloud invoke` command entry point + """ + + do_cli( + stack_name, + resource_id, + event, + event_file, + output_format, + parameter, + ctx.region, + ctx.profile, + config_file, + config_env, + ) + + +def do_cli( + stack_name: str, + resource_id: str, + event: str, + event_file: TextIOWrapper, + output_format: RemoteInvokeOutputFormat, + parameter: dict, + region: str, + profile: str, + config_file: str, + config_env: str, +) -> None: + """ + Implementation of the ``cli`` method + """ + from samcli.commands.cloud.remote_invoke_context import RemoteInvokeContext + from samcli.commands.exceptions import UserException + from samcli.lib.remote_invoke.exceptions import ( + ErrorBotoApiCallException, + InvalideBotoResponseException, + InvalidResourceBotoParameterException, + ) + from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutionInfo + from samcli.lib.utils.boto_utils import get_boto_client_provider_with_config, get_boto_resource_provider_with_config + + boto_client_provider = get_boto_client_provider_with_config(region_name=region) + boto_resource_provider = get_boto_resource_provider_with_config(region_name=region) + try: + with RemoteInvokeContext( + boto_client_provider=boto_client_provider, + boto_resource_provider=boto_resource_provider, + stack_name=stack_name, + resource_id=resource_id, + ) as remote_invoke_context: + + remote_invoke_input = RemoteInvokeExecutionInfo( + payload=event, payload_file=event_file, parameters=parameter, output_format=output_format + ) + + remote_invoke_result = remote_invoke_context.run(remote_invoke_input=remote_invoke_input) + + if remote_invoke_result.is_succeeded(): + LOG.debug("Invoking resource was successfull, writing response to stdout") + if remote_invoke_result.log_output: + LOG.debug("Writing log output to stderr") + remote_invoke_context.stderr.write(remote_invoke_result.log_output.encode()) + output_response = cast(str, remote_invoke_result.response) + remote_invoke_context.stdout.write(output_response.encode()) + else: + raise cast(Exception, remote_invoke_result.exception) + except (ErrorBotoApiCallException, InvalideBotoResponseException, InvalidResourceBotoParameterException) as ex: + raise UserException(str(ex), wrapped_from=ex.__class__.__name__) from ex diff --git a/samcli/commands/remote_invoke/remote_invoke_context.py b/samcli/commands/cloud/remote_invoke_context.py similarity index 90% rename from samcli/commands/remote_invoke/remote_invoke_context.py rename to samcli/commands/cloud/remote_invoke_context.py index 1e176f01a3..7c9dda4fcd 100644 --- a/samcli/commands/remote_invoke/remote_invoke_context.py +++ b/samcli/commands/cloud/remote_invoke_context.py @@ -4,7 +4,7 @@ import logging from typing import Optional, cast -from samcli.commands.remote_invoke.exceptions import ( +from samcli.commands.cloud.exceptions import ( AmbiguousResourceForRemoteInvoke, InvalidRemoteInvokeParameters, NoExecutorFoundForRemoteInvoke, @@ -13,6 +13,7 @@ ) from samcli.lib.remote_invoke.remote_invoke_executor_factory import RemoteInvokeExecutorFactory from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutionInfo +from samcli.lib.utils import osutils from samcli.lib.utils.arn_utils import ARNParts, InvalidArnValue from samcli.lib.utils.boto_utils import BotoProviderType from samcli.lib.utils.cloudformation import ( @@ -22,6 +23,7 @@ get_resource_summary_from_physical_id, ) from samcli.lib.utils.resources import AWS_LAMBDA_FUNCTION +from samcli.lib.utils.stream_writer import StreamWriter LOG = logging.getLogger(__name__) @@ -186,3 +188,29 @@ def _get_from_physical_resource_id(self) -> CloudFormationResourceSummary: f"Please provide full resource ARN or --stack-name to resolve the ambiguity." ) return resource_summary + + @property + def stdout(self) -> StreamWriter: + """ + Returns stream writer for stdout to output Lambda function logs to + + Returns + ------- + samcli.lib.utils.stream_writer.StreamWriter + Stream writer for stdout + """ + stream = osutils.stdout() + return StreamWriter(stream, auto_flush=True) + + @property + def stderr(self) -> StreamWriter: + """ + Returns stream writer for stderr to output Lambda function errors to + + Returns + ------- + samcli.lib.utils.stream_writer.StreamWriter + Stream writer for stderr + """ + stream = osutils.stderr() + return StreamWriter(stream, auto_flush=True) diff --git a/samcli/lib/cli_validation/remote_invoke_options_validations.py b/samcli/lib/cli_validation/remote_invoke_options_validations.py new file mode 100644 index 0000000000..6db65e2c20 --- /dev/null +++ b/samcli/lib/cli_validation/remote_invoke_options_validations.py @@ -0,0 +1,94 @@ +""" +This file contains validations remote invoke options +""" +import logging +import sys +from functools import wraps +from io import TextIOWrapper +from typing import cast + +import click + +from samcli.commands._utils.option_validator import Validator + +LOG = logging.getLogger(__name__) + + +def event_and_event_file_options_validation(func): + """ + This function validates the cases when both --event and --event-file are provided and + neither option is provided + + Parameters + ---------- + func : + Command that would be executed, in this case it is 'sam cloud invoke' + + Returns + ------- + A wrapper function which will first validate options and will execute command if validation succeeds + """ + + @wraps(func) + def wrapped(*args, **kwargs): + ctx = click.get_current_context() + + event = ctx.params.get("event") + event_file = ctx.params.get("event_file") + + validator = Validator( + validation_function=lambda: event and event_file, + exception=click.BadOptionUsage( + option_name="--event-file", + ctx=ctx, + message="Both '--event-file' and '--event' cannot be provided. " + "Please check that you don't have both specified in the command or in a configuration file", + ), + ) + + validator.validate() + + # if no event nor event_file arguments are given, read from stdin + if not event and not event_file: + LOG.debug("Neither --event nor --event-file options have been provided, reading from stdin") + kwargs["event_file"] = cast(TextIOWrapper, sys.stdin) + return func(*args, **kwargs) + + return wrapped + + +def stack_name_or_resource_id_atleast_one_option_validation(func): + """ + This function validates that atleast one of --stack-name or --resource-id should is be provided + + Parameters + ---------- + func : + Command that would be executed, in this case it is 'sam cloud invoke' + + Returns + ------- + A wrapper function which will first validate options and will execute command if validation succeeds + """ + + @wraps(func) + def wrapped(*args, **kwargs): + ctx = click.get_current_context() + + stack_name = ctx.params.get("stack_name") + resource_id = ctx.params.get("resource_id") + + validator = Validator( + validation_function=lambda: not (stack_name or resource_id), + exception=click.BadOptionUsage( + option_name="--resource-id", + ctx=ctx, + message="Atleast 1 of --stack-name or --resource-id parameters should be provided.", + ), + ) + + validator.validate() + + return func(*args, **kwargs) + + return wrapped diff --git a/samcli/lib/remote_invoke/exceptions.py b/samcli/lib/remote_invoke/exceptions.py index 634bb2b250..724d46ca58 100644 --- a/samcli/lib/remote_invoke/exceptions.py +++ b/samcli/lib/remote_invoke/exceptions.py @@ -6,10 +6,10 @@ class InvalidResourceBotoParameterException(Exception): """Exception is raised when parameters passed to boto APIs are invalid""" - pass - class InvalideBotoResponseException(Exception): """Exception is raised when the boto APIs return an invalid response""" - pass + +class ErrorBotoApiCallException(Exception): + """Exception is raised when calling boto APIs returns an error while execution""" diff --git a/samcli/lib/remote_invoke/lambda_invoke_executors.py b/samcli/lib/remote_invoke/lambda_invoke_executors.py index a6ef2e8e01..49245adf99 100644 --- a/samcli/lib/remote_invoke/lambda_invoke_executors.py +++ b/samcli/lib/remote_invoke/lambda_invoke_executors.py @@ -10,7 +10,11 @@ from botocore.exceptions import ClientError, ParamValidationError from botocore.response import StreamingBody -from samcli.lib.remote_invoke.exceptions import InvalideBotoResponseException, InvalidResourceBotoParameterException +from samcli.lib.remote_invoke.exceptions import ( + ErrorBotoApiCallException, + InvalideBotoResponseException, + InvalidResourceBotoParameterException, +) from samcli.lib.remote_invoke.remote_invoke_executors import ( BotoActionExecutor, RemoteInvokeExecutionInfo, @@ -75,7 +79,9 @@ def _execute_action(self, payload: str): raise InvalidResourceBotoParameterException( f"Invalid parameter value provided. {str(client_ex).replace('(ValidationException) ', '')}" ) from client_ex - raise client_ex + elif boto_utils.get_client_error_code(client_ex) == "InvalidRequestContentException": + raise InvalidResourceBotoParameterException(client_ex) from client_ex + raise ErrorBotoApiCallException(client_ex) from client_ex return response @@ -86,6 +92,7 @@ class DefaultConvertToJSON(RemoteInvokeRequestResponseMapper): def map(self, test_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: if not test_input.is_file_provided(): + LOG.debug("Mapping input Payload to JSON string object") try: _ = json.loads(cast(str, test_input.payload)) except JSONDecodeError: @@ -108,6 +115,7 @@ class LambdaResponseConverter(RemoteInvokeRequestResponseMapper): """ def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + LOG.debug("Mapping Lambda response to string object") if not isinstance(remote_invoke_input.response, dict): raise InvalideBotoResponseException("Invalid response type received from Lambda service, expecting dict") @@ -132,6 +140,7 @@ def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExe to stdout. """ if remote_invoke_input.output_format == RemoteInvokeOutputFormat.DEFAULT: + LOG.debug("Formatting Lambda output response") boto_response = cast(Dict, remote_invoke_input.response) log_field = boto_response.get("LogResult") if log_field: diff --git a/samcli/lib/remote_invoke/remote_invoke_executors.py b/samcli/lib/remote_invoke/remote_invoke_executors.py index e1acfa80c0..cb9eb6887b 100644 --- a/samcli/lib/remote_invoke/remote_invoke_executors.py +++ b/samcli/lib/remote_invoke/remote_invoke_executors.py @@ -18,7 +18,7 @@ class RemoteInvokeOutputFormat(Enum): """ DEFAULT = "default" - ORIGINAL_BOTO_RESPONSE = "original-boto-response" + RAW = "raw" class RemoteInvokeExecutionInfo: diff --git a/tests/unit/cli/test_types.py b/tests/unit/cli/test_types.py index 710e513478..eed37f0f58 100644 --- a/tests/unit/cli/test_types.py +++ b/tests/unit/cli/test_types.py @@ -10,8 +10,11 @@ SigningProfilesOptionType, ImageRepositoryType, ImageRepositoriesType, + RemoteInvokeBotoApiParameterType, + RemoteInvokeOutputFormatType, ) from samcli.cli.types import CfnMetadataType +from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeOutputFormat class TestCfnParameterOverridesType(TestCase): @@ -441,3 +444,75 @@ def test_must_fail_on_invalid_format(self, input): def test_successful_parsing(self, input, expected): result = self.param_type.convert(input, self.mock_param, Mock()) self.assertEqual(result, expected, msg="Failed with Input = " + str(input)) + + +class TestRemoteInvokeBotoApiParameterType(TestCase): + def setUp(self): + self.param_type = RemoteInvokeBotoApiParameterType() + self.mock_param = Mock(opts=["--parameter"]) + + @parameterized.expand( + [ + # Just a string + ("some string"), + # no parameter value + ("no-value"), + ] + ) + def test_must_fail_on_invalid_format(self, input): + self.param_type.fail = Mock() + with self.assertRaises(BadParameter): + self.param_type.convert(input, self.mock_param, Mock()) + + @parameterized.expand( + [ + ( + "Parameter1=Value1", + {"Parameter1": "Value1"}, + ), + ( + 'Parameter1=\'{"a":54, "b": 28}\'', + {"Parameter1": '\'{"a":54, "b": 28}\''}, + ), + ( + "Parameter1=base-64-encoded==", + {"Parameter1": "base-64-encoded=="}, + ), + ] + ) + def test_successful_parsing(self, input, expected): + result = self.param_type.convert(input, self.mock_param, Mock()) + self.assertEqual(result, expected) + + +class TestRemoteInvokeOutputFormatParameterType(TestCase): + def setUp(self): + self.param_type = RemoteInvokeOutputFormatType(enum=RemoteInvokeOutputFormat) + self.mock_param = Mock(opts=["--output-format"]) + + @parameterized.expand( + [ + ("string"), + ("some string"), + ("non-default"), + ] + ) + def test_must_fail_on_invalid_values(self, input): + with self.assertRaises(BadParameter): + self.param_type.convert(input, self.mock_param, None) + + @parameterized.expand( + [ + ( + "default", + RemoteInvokeOutputFormat.DEFAULT, + ), + ( + "raw", + RemoteInvokeOutputFormat.RAW, + ), + ] + ) + def test_successful_parsing(self, input, expected): + result = self.param_type.convert(input, self.mock_param, None) + self.assertEqual(result, expected) diff --git a/tests/unit/commands/_utils/test_options.py b/tests/unit/commands/_utils/test_options.py index 345b80e594..48fb0c3c9f 100644 --- a/tests/unit/commands/_utils/test_options.py +++ b/tests/unit/commands/_utils/test_options.py @@ -20,6 +20,7 @@ artifact_callback, resolve_s3_callback, image_repositories_callback, + remote_invoke_boto_parameter_callback, _space_separated_list_func_type, skip_prepare_infra_callback, generate_next_command_recommendation, @@ -136,6 +137,26 @@ def test_image_repositories_callback_None(self): ) +class TestRemoteInvokeBotoParameterCallBack(TestCase): + def test_remote_invoke_boto_parameter_callback(self): + mock_params = MagicMock() + result = remote_invoke_boto_parameter_callback( + ctx=MockContext(info_name="test", parent=None, params=mock_params), + param=MagicMock(), + provided_value=({"a": "b"}, {"c": "d"}), + ) + self.assertEqual(result, {"a": "b", "c": "d"}) + + def test_remote_invoke_boto_parameter_callback_empty(self): + mock_params = MagicMock() + self.assertEqual( + remote_invoke_boto_parameter_callback( + ctx=MockContext(info_name="test", parent=None, params=mock_params), param=MagicMock(), provided_value=() + ), + {}, + ) + + class TestArtifactBasedOptionRequired(TestCase): @patch("samcli.commands._utils.options.get_template_artifacts_format") def test_zip_based_artifact_s3_required(self, template_artifacts_mock): diff --git a/tests/unit/commands/cloud/__init__.py b/tests/unit/commands/cloud/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/commands/cloud/invoke/__init__.py b/tests/unit/commands/cloud/invoke/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/commands/cloud/invoke/test_cli.py b/tests/unit/commands/cloud/invoke/test_cli.py new file mode 100644 index 0000000000..ae7dc7f333 --- /dev/null +++ b/tests/unit/commands/cloud/invoke/test_cli.py @@ -0,0 +1,138 @@ +from unittest import TestCase +from unittest.mock import patch, Mock + +from parameterized import parameterized + +from samcli.commands.cloud.invoke.cli import do_cli +from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeOutputFormat +from samcli.lib.remote_invoke.exceptions import ( + ErrorBotoApiCallException, + InvalideBotoResponseException, + InvalidResourceBotoParameterException, +) +from samcli.commands.exceptions import UserException + + +class TestRemoteInvokeCliCommand(TestCase): + def setUp(self) -> None: + self.stack_name = "stack_name" + self.resource_id = "resource_id" + self.region = "region" + self.profile = "profile" + self.config_file = "config_file" + self.config_env = "config_env" + + @parameterized.expand( + [ + ("event", None, RemoteInvokeOutputFormat.DEFAULT, {}, "log-output"), + ("event", None, RemoteInvokeOutputFormat.DEFAULT, {}, None), + ("event", None, RemoteInvokeOutputFormat.DEFAULT, {"Param1": "ParamValue1"}, "log-output"), + ("event", None, RemoteInvokeOutputFormat.RAW, {}, None), + ("event", None, RemoteInvokeOutputFormat.RAW, {"Param1": "ParamValue1"}, "log-output"), + ("event", None, RemoteInvokeOutputFormat.RAW, {"Param1": "ParamValue1"}, None), + (None, "event_file", RemoteInvokeOutputFormat.DEFAULT, {"Param1": "ParamValue1"}, None), + (None, "event_file", RemoteInvokeOutputFormat.RAW, {"Param1": "ParamValue1"}, "log-output"), + ] + ) + @patch("samcli.lib.remote_invoke.remote_invoke_executors.RemoteInvokeExecutionInfo") + @patch("samcli.lib.utils.boto_utils.get_boto_client_provider_with_config") + @patch("samcli.lib.utils.boto_utils.get_boto_resource_provider_with_config") + @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeContext") + def test_remote_invoke_command( + self, + event, + event_file, + output_format, + parameter, + log_output, + mock_remote_invoke_context, + patched_get_boto_resource_provider_with_config, + patched_get_boto_client_provider_with_config, + patched_remote_invoke_execution_info, + ): + given_client_provider = Mock() + patched_get_boto_client_provider_with_config.return_value = given_client_provider + + given_resource_provider = Mock() + patched_get_boto_resource_provider_with_config.return_value = given_resource_provider + + given_remote_invoke_execution_info = Mock() + patched_remote_invoke_execution_info.return_value = given_remote_invoke_execution_info + + stdout_stream_writer_mock = Mock() + stderr_stream_writer_mock = Mock() + + context_mock = Mock() + mock_remote_invoke_context.return_value.__enter__.return_value = context_mock + context_mock.stdout = stdout_stream_writer_mock + context_mock.stderr = stderr_stream_writer_mock + + given_remote_invoke_result = Mock() + given_remote_invoke_result.is_succeeded.return_value = True + given_remote_invoke_result.log_output = log_output + context_mock.run.return_value = given_remote_invoke_result + + do_cli( + stack_name=self.stack_name, + resource_id=self.resource_id, + event=event, + event_file=event_file, + parameter=parameter, + output_format=output_format, + region=self.region, + profile=self.profile, + config_file=self.config_file, + config_env=self.config_env, + ) + + patched_get_boto_client_provider_with_config.assert_called_with(region_name=self.region) + patched_get_boto_resource_provider_with_config.assert_called_with(region_name=self.region) + + mock_remote_invoke_context.assert_called_with( + boto_client_provider=given_client_provider, + boto_resource_provider=given_resource_provider, + stack_name=self.stack_name, + resource_id=self.resource_id, + ) + + patched_remote_invoke_execution_info.assert_called_with( + payload=event, payload_file=event_file, parameters=parameter, output_format=output_format + ) + + context_mock.run.assert_called_with(remote_invoke_input=given_remote_invoke_execution_info) + + if log_output: + stderr_stream_writer_mock.write.assert_called() + stdout_stream_writer_mock.write.assert_called() + + @parameterized.expand( + [ + (InvalideBotoResponseException,), + (ErrorBotoApiCallException,), + (InvalidResourceBotoParameterException,), + ] + ) + @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeContext") + def test_raise_user_exception_invoke_not_successfull(self, exeception_to_raise, mock_invoke_context): + + context_mock = Mock() + mock_invoke_context.return_value.__enter__.return_value = context_mock + + given_remote_invoke_result = Mock() + given_remote_invoke_result.is_succeeded.return_value = False + context_mock.run.return_value = given_remote_invoke_result + given_remote_invoke_result.exception = exeception_to_raise + + with self.assertRaises(UserException): + do_cli( + stack_name=None, + resource_id="mock-resource-id", + event="event", + event_file=None, + parameter={}, + output_format=RemoteInvokeOutputFormat.DEFAULT, + region=self.region, + profile=self.profile, + config_file=self.config_file, + config_env=self.config_env, + ) diff --git a/tests/unit/commands/remote_invoke/test_remote_invoke_context.py b/tests/unit/commands/cloud/test_remote_invoke_context.py similarity index 84% rename from tests/unit/commands/remote_invoke/test_remote_invoke_context.py rename to tests/unit/commands/cloud/test_remote_invoke_context.py index 70acb381d8..d7e55dce06 100644 --- a/tests/unit/commands/remote_invoke/test_remote_invoke_context.py +++ b/tests/unit/commands/cloud/test_remote_invoke_context.py @@ -2,14 +2,14 @@ from unittest.mock import Mock, patch from uuid import uuid4 -from samcli.commands.remote_invoke.exceptions import ( +from samcli.commands.cloud.exceptions import ( InvalidRemoteInvokeParameters, AmbiguousResourceForRemoteInvoke, NoResourceFoundForRemoteInvoke, UnsupportedServiceForRemoteInvoke, NoExecutorFoundForRemoteInvoke, ) -from samcli.commands.remote_invoke.remote_invoke_context import RemoteInvokeContext, SUPPORTED_SERVICES +from samcli.commands.cloud.remote_invoke_context import RemoteInvokeContext, SUPPORTED_SERVICES from samcli.lib.utils.cloudformation import CloudFormationResourceSummary @@ -32,7 +32,7 @@ def test_no_stack_name_and_no_resource_id_should_fail(self): with self._get_remote_invoke_context(): pass - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_no_resource_should_fail(self, patched_resource_summaries): self.resource_id = None patched_resource_summaries.return_value = {} @@ -40,7 +40,7 @@ def test_only_stack_name_with_no_resource_should_fail(self, patched_resource_sum with self._get_remote_invoke_context(): pass - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_multiple_resource_should_fail(self, patched_resource_summaries): self.resource_id = None patched_resource_summaries.return_value = {"resource1": Mock(), "resource2": Mock()} @@ -48,7 +48,7 @@ def test_only_stack_name_with_multiple_resource_should_fail(self, patched_resour with self._get_remote_invoke_context(): pass - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_single_resource_should_be_valid(self, patched_resource_summaries): self.resource_id = None resource_summary = Mock(logical_resource_id=self.resource_id) @@ -75,7 +75,7 @@ def test_only_resource_id_supported_service_arn_should_be_valid(self): ), ) - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summary_from_physical_id") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary_from_physical_id") def test_only_resource_id_as_invalid_physical_id_should_fail(self, patched_resource_summary_from_physical_id): self.stack_name = None patched_resource_summary_from_physical_id.return_value = None @@ -83,14 +83,14 @@ def test_only_resource_id_as_invalid_physical_id_should_fail(self, patched_resou with self._get_remote_invoke_context(): pass - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") def test_if_no_resource_found_with_given_stack_and_resource_id_should_fail(self, patched_get_resource_summary): patched_get_resource_summary.return_value = None with self.assertRaises(AmbiguousResourceForRemoteInvoke): with self._get_remote_invoke_context() as remote_invoke_context: remote_invoke_context.run(Mock()) - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summary_from_physical_id") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary_from_physical_id") def test_only_resource_id_as_valid_physical_id_should_be_valid(self, patched_resource_summary_from_physical_id): self.stack_name = None resource_summary = Mock() @@ -98,22 +98,22 @@ def test_only_resource_id_as_valid_physical_id_should_be_valid(self, patched_res with self._get_remote_invoke_context() as remote_invoke_context: self.assertEqual(remote_invoke_context._resource_summary, resource_summary) - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") def test_running_without_resource_summary_should_raise_exception(self, patched_get_resource_summary): patched_get_resource_summary.return_value = None with self._get_remote_invoke_context() as remote_invoke_context: with self.assertRaises(AmbiguousResourceForRemoteInvoke): remote_invoke_context.run(Mock()) - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") def test_running_with_unsupported_resource_should_raise_exception(self, patched_get_resource_summary): patched_get_resource_summary.return_value = Mock(resource_type="UnSupportedResource") with self._get_remote_invoke_context() as remote_invoke_context: with self.assertRaises(NoExecutorFoundForRemoteInvoke): remote_invoke_context.run(Mock()) - @patch("samcli.commands.remote_invoke.remote_invoke_context.RemoteInvokeExecutorFactory") - @patch("samcli.commands.remote_invoke.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeExecutorFactory") + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") def test_running_should_execute_remote_invoke_executor_instance( self, patched_get_resource_summary, patched_remote_invoke_executor_factory ): diff --git a/tests/unit/lib/cli_validation/test_remote_invoke_options_validations.py b/tests/unit/lib/cli_validation/test_remote_invoke_options_validations.py new file mode 100644 index 0000000000..17448fc856 --- /dev/null +++ b/tests/unit/lib/cli_validation/test_remote_invoke_options_validations.py @@ -0,0 +1,111 @@ +from unittest import TestCase +from unittest.mock import Mock, patch + +from click import BadOptionUsage + +from samcli.lib.cli_validation.remote_invoke_options_validations import ( + event_and_event_file_options_validation, + stack_name_or_resource_id_atleast_one_option_validation, +) + + +class TestEventFileValidation(TestCase): + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.LOG") + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") + def test_both_not_provided_params(self, patched_click_context, patched_log): + mock_func = Mock() + mocked_context = Mock() + patched_click_context.return_value = mocked_context + + mocked_context.params.get.return_value = {} + + event_and_event_file_options_validation(mock_func)() + patched_log.debug.assert_called_with( + "Neither --event nor --event-file options have been provided, reading from stdin" + ) + + mock_func.assert_called_once() + + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") + def test_only_event_param(self, patched_click_context): + mock_func = Mock() + + mocked_context = Mock() + patched_click_context.return_value = mocked_context + + mocked_context.params.get.side_effect = lambda key: "event" if key == "event" else None + + event_and_event_file_options_validation(mock_func)() + + mock_func.assert_called_once() + + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") + def test_only_event_file_param(self, patched_click_context): + mock_func = Mock() + + mocked_context = Mock() + patched_click_context.return_value = mocked_context + + mocked_context.params.get.side_effect = lambda key: "event_file" if key == "event_file" else None + + event_and_event_file_options_validation(mock_func)() + + mock_func.assert_called_once() + + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") + def test_both_params(self, patched_click_context): + mock_func = Mock() + mocked_context = Mock() + patched_click_context.return_value = mocked_context + + mocked_context.params.get.side_effect = lambda key: "event_content" + + with self.assertRaises(BadOptionUsage) as ex: + event_and_event_file_options_validation(mock_func)() + + self.assertIn("Both '--event-file' and '--event' cannot be provided.", ex.exception.message) + + mock_func.assert_not_called() + + +class TestRemoteInvokeAtleast1OptionProvidedValidation(TestCase): + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") + def test_only_resource_id_param(self, patched_click_context): + mock_func = Mock() + + mocked_context = Mock() + patched_click_context.return_value = mocked_context + + mocked_context.params.get.side_effect = lambda key: "resource_id" if key == "resource_id" else None + + stack_name_or_resource_id_atleast_one_option_validation(mock_func)() + + mock_func.assert_called_once() + + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") + def test_only_stack_name_param(self, patched_click_context): + mock_func = Mock() + + mocked_context = Mock() + patched_click_context.return_value = mocked_context + + mocked_context.params.get.side_effect = lambda key: "stack_name" if key == "stack_name" else None + + stack_name_or_resource_id_atleast_one_option_validation(mock_func)() + + mock_func.assert_called_once() + + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") + def test_no_params_provided(self, patched_click_context): + mock_func = Mock() + mocked_context = Mock() + patched_click_context.return_value = mocked_context + + mocked_context.params.get.return_value = {} + + with self.assertRaises(BadOptionUsage) as ex: + stack_name_or_resource_id_atleast_one_option_validation(mock_func)() + + self.assertIn("Atleast 1 of --stack-name or --resource-id parameters should be provided.", ex.exception.message) + + mock_func.assert_not_called() diff --git a/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py b/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py index 48593abcc2..b719b77da5 100644 --- a/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py +++ b/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py @@ -7,6 +7,7 @@ DefaultConvertToJSON, LambdaResponseConverter, LambdaResponseOutputFormatter, + ErrorBotoApiCallException, InvalidResourceBotoParameterException, InvalideBotoResponseException, RemoteInvokeOutputFormat, @@ -34,9 +35,15 @@ def test_execute_action(self): FunctionName=self.function_name, Payload=given_payload, InvocationType="RequestResponse", LogType="Tail" ) - def test_execute_action_invalid_parameter_value_throws_validation_exception(self): + @parameterized.expand( + [ + ("ValidationException",), + ("InvalidRequestContentException",), + ] + ) + def test_execute_action_invalid_parameter_value_throws_client_error(self, error_code): given_payload = Mock() - error = ClientError(error_response={"Error": {"Code": "ValidationException"}}, operation_name="invoke") + error = ClientError(error_response={"Error": {"Code": error_code}}, operation_name="invoke") self.lambda_client.invoke.side_effect = error with self.assertRaises(InvalidResourceBotoParameterException): self.lambda_invoke_executor._execute_action(given_payload) @@ -52,7 +59,7 @@ def test_execute_action_throws_client_error_exception(self): given_payload = Mock() error = ClientError(error_response={"Error": {"Code": "MockException"}}, operation_name="invoke") self.lambda_client.invoke.side_effect = error - with self.assertRaises(ClientError): + with self.assertRaises(ErrorBotoApiCallException): self.lambda_invoke_executor._execute_action(given_payload) @parameterized.expand( @@ -142,7 +149,7 @@ def setUp(self) -> None: def test_lambda_response_original_boto_output_formatter(self): given_response = {"Payload": {"StatusCode": 200, "message": "hello world"}} - output_format = RemoteInvokeOutputFormat.ORIGINAL_BOTO_RESPONSE + output_format = RemoteInvokeOutputFormat.RAW remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, output_format) remote_invoke_execution_info.response = given_response From 2cf358f83cae062092169d96534057dd29be4699 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 11:23:05 -0700 Subject: [PATCH 025/107] chore(deps-dev): bump boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray] (#5256) Bumps [boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]](https://github.com/youtype/mypy_boto3_builder) from 1.26.131 to 1.26.146. - [Release notes](https://github.com/youtype/mypy_boto3_builder/releases) - [Commits](https://github.com/youtype/mypy_boto3_builder/commits) --- updated-dependencies: - dependency-name: boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray] dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 2c9d9fcc7d..1bed55f987 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -8,7 +8,7 @@ pytest-cov==4.0.0 # mypy adds new rules in new minor versions, which could cause our PR check to fail # here we fix its version and upgrade it manually in the future mypy==1.3.0 -boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]==1.26.131 +boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]==1.26.146 types-pywin32==306.0.0.0 types-PyYAML==6.0.12 types-chevron==0.14.2.4 From 7cc06f4444ec0ddff6fe0fb962d791a8997295c3 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Mon, 5 Jun 2023 12:25:35 -0700 Subject: [PATCH 026/107] pin pytest-metadata to avoid its breaking change (#5261) --- requirements/dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements/dev.txt b/requirements/dev.txt index 1bed55f987..5251b1025e 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -30,6 +30,8 @@ pytest-xdist==3.2.0 pytest-forked==1.6.0 pytest-timeout==2.1.0 pytest-rerunfailures==11.1.2 +# NOTE (hawflau): DO NOT upgrade pytest-metadata and pytest-json-report unless pytest-json-report addresses https://github.com/numirias/pytest-json-report/issues/89 +pytest-metadata==2.0.4 pytest-json-report==1.5.0 filelock==3.12.0 From 4de9c90ccc0d4218c872fb34efb2d57f8b5715be Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 21:07:21 +0000 Subject: [PATCH 027/107] chore: update aws_lambda_builders to 1.33.0 (#5262) Co-authored-by: GitHub Action Co-authored-by: Daniel Mil <84205762+mildaniel@users.noreply.github.com> --- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 3a2c53c2e4..84eb44da14 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -13,7 +13,7 @@ docker~=6.1.0 dateparser~=1.1 requests~=2.31.0 serverlessrepo==0.1.10 -aws_lambda_builders==1.32.0 +aws_lambda_builders==1.33.0 tomlkit==0.11.8 watchdog==2.1.2 rich~=13.3.3 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 89fd165276..e8faac8822 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -15,9 +15,9 @@ attrs==23.1.0 \ # jschema-to-python # jsonschema # sarif-om -aws-lambda-builders==1.32.0 \ - --hash=sha256:97b6965d01596a3a15936faf345b88d17a25339f81f1e5643534345eb44b4dee \ - --hash=sha256:a39e249a44265957e5b551e050490bf1ef3315ab564d44b187b17df6780713e4 +aws-lambda-builders==1.33.0 \ + --hash=sha256:9064bfd17252ec2a1f41a1db8f8c77410bfebb79635abd0610c17df0b6eaf68c \ + --hash=sha256:f901e276f26b8051fba70ac8f10f1ab7ef73df4b01847b587196c4be26d24e1b # via aws-sam-cli (setup.py) aws-sam-translator==1.68.0 \ --hash=sha256:557d8080c9e6c1c609bfe806ea9545f7ea34144e2466c0ddc801806c2c05afdc \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index e2fc641da4..ee5072b94d 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -15,9 +15,9 @@ attrs==23.1.0 \ # jschema-to-python # jsonschema # sarif-om -aws-lambda-builders==1.32.0 \ - --hash=sha256:97b6965d01596a3a15936faf345b88d17a25339f81f1e5643534345eb44b4dee \ - --hash=sha256:a39e249a44265957e5b551e050490bf1ef3315ab564d44b187b17df6780713e4 +aws-lambda-builders==1.33.0 \ + --hash=sha256:9064bfd17252ec2a1f41a1db8f8c77410bfebb79635abd0610c17df0b6eaf68c \ + --hash=sha256:f901e276f26b8051fba70ac8f10f1ab7ef73df4b01847b587196c4be26d24e1b # via aws-sam-cli (setup.py) aws-sam-translator==1.68.0 \ --hash=sha256:557d8080c9e6c1c609bfe806ea9545f7ea34144e2466c0ddc801806c2c05afdc \ From c9f8b812a30ba640c44ec36d1232b831166eb393 Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Mon, 5 Jun 2023 14:25:20 -0700 Subject: [PATCH 028/107] chore: Add python3.11 to canaries (#5263) * chore: Add python3.11 to canaries * Remove python3.9 --- appveyor-ubuntu.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor-ubuntu.yml b/appveyor-ubuntu.yml index 6d794c9676..71e91691d1 100644 --- a/appveyor-ubuntu.yml +++ b/appveyor-ubuntu.yml @@ -38,8 +38,8 @@ environment: - PYTHON_HOME: "$HOME/venv3.8/bin" PYTHON_VERSION: '3.8' - - PYTHON_HOME: "$HOME/venv3.9/bin" - PYTHON_VERSION: '3.9' + - PYTHON_HOME: "$HOME/venv3.11/bin" + PYTHON_VERSION: '3.11' install: # AppVeyor's apt-get cache might be outdated, and the package could potentially be 404. From fe41c4c83557cea8f3ea6909ded45d95392d9d2d Mon Sep 17 00:00:00 2001 From: Slava Senchenko Date: Mon, 5 Jun 2023 16:55:26 -0700 Subject: [PATCH 029/107] Artifact export for GraphQLApi (#5250) * Artifact export for GraphQLApi * format * docstrings * fix unit tests * fix mypy issues * improve search method signature --- samcli/lib/package/packageable_resources.py | 138 +++++++++++++++++- samcli/lib/package/utils.py | 19 +-- samcli/lib/utils/resources.py | 14 +- .../lib/package/test_artifact_exporter.py | 73 ++++++++- 4 files changed, 222 insertions(+), 22 deletions(-) diff --git a/samcli/lib/package/packageable_resources.py b/samcli/lib/package/packageable_resources.py index e9809b7fb0..a52ff32fed 100644 --- a/samcli/lib/package/packageable_resources.py +++ b/samcli/lib/package/packageable_resources.py @@ -4,7 +4,7 @@ import logging import os import shutil -from typing import Dict, Optional, Union +from typing import Any, Dict, List, Optional, Tuple, Union, cast import jmespath from botocore.utils import set_value_from_jmespath @@ -40,6 +40,7 @@ AWS_LAMBDA_LAYERVERSION, AWS_SERVERLESS_API, AWS_SERVERLESS_FUNCTION, + AWS_SERVERLESS_GRAPHQLAPI, AWS_SERVERLESS_HTTPAPI, AWS_SERVERLESS_LAYERVERSION, AWS_SERVERLESS_STATEMACHINE, @@ -89,7 +90,7 @@ class ResourceZip(Resource): Base class representing a CloudFormation resource that can be exported """ - RESOURCE_TYPE: Optional[str] = None + RESOURCE_TYPE: str = "" PROPERTY_NAME: str = "" PACKAGE_NULL_PROPERTY = True # Set this property to True in base class if you want the exporter to zip @@ -133,13 +134,23 @@ def export(self, resource_id: str, resource_dict: Optional[Dict], parent_dir: st if temp_dir: shutil.rmtree(temp_dir) - def do_export(self, resource_id, resource_dict, parent_dir): + def do_export( + self, + resource_id, + resource_dict, + parent_dir, + property_path: Optional[str] = None, + local_path: Optional[str] = None, + ): """ Default export action is to upload artifacts and set the property to S3 URL of the uploaded object If code signing configuration is provided for function/layer, uploaded artifact will be replaced by signed artifact location """ + if property_path is None: + property_path = self.PROPERTY_NAME + uploader = cast(S3Uploader, self.uploader) # code signer only accepts files which has '.zip' extension in it # so package artifact with '.zip' if it is required to be signed should_sign_package = self.code_signer.should_sign_package(resource_id) @@ -148,16 +159,17 @@ def do_export(self, resource_id, resource_dict, parent_dir): self.RESOURCE_TYPE, resource_id, resource_dict, - self.PROPERTY_NAME, + property_path, parent_dir, - self.uploader, + uploader, artifact_extension, + local_path, ) if should_sign_package: uploaded_url = self.code_signer.sign_package( - resource_id, uploaded_url, self.uploader.get_version_of_artifact(uploaded_url) + resource_id, uploaded_url, uploader.get_version_of_artifact(uploaded_url) ) - set_value_from_jmespath(resource_dict, self.PROPERTY_NAME, uploaded_url) + set_value_from_jmespath(resource_dict, property_path, uploaded_url) def delete(self, resource_id, resource_dict): """ @@ -585,6 +597,116 @@ def get_property_value(self, resource_dict): return jmespath.search(self.PROPERTY_NAME, resource_dict) +class GraphQLApiSchemaResource(ResourceZip): + RESOURCE_TYPE = AWS_SERVERLESS_GRAPHQLAPI + PROPERTY_NAME = RESOURCES_WITH_LOCAL_PATHS[RESOURCE_TYPE][0] + # Don't package the directory if SchemaUri is omitted. + # Necessary to support SchemaInline + PACKAGE_NULL_PROPERTY = False + + +class GraphQLApiCodeResource(ResourceZip): + """CodeUri for GraphQLApi resource. + + There can be more than a single instance of CodeUri property in GraphQLApi Resolvers and Functions. + This class handles them all. + + GraphQLApi dict shape looks like the following (yaml representation) + >>> Resolvers: + Mutation: + Resolver1: + CodeUri: ... + Pipeline: + - Func1 + - Func2 + Query: + Resolver2: + CodeUri: ... + Pipeline: + - Func3 + Functions: + Func1: + CodeUri: ... + Func2: + CodeUri: ... + Func3: + CodeUri: ... + ... # other properties, which are not important here + """ + + RESOURCE_TYPE = AWS_SERVERLESS_GRAPHQLAPI + PROPERTY_NAME = RESOURCES_WITH_LOCAL_PATHS[RESOURCE_TYPE][1] + # if CodeUri is omitted the directory is not packaged because it's necessary to support CodeInline + PACKAGE_NULL_PROPERTY = False + + def export(self, resource_id: str, resource_dict: Optional[Dict], parent_dir: str): + if resource_dict is None: + return + + if resource_not_packageable(resource_dict): + return + + # to be able to set different nested properties to S3 uri, paths are necessary + # jmespath doesn't provide that functionality, thus custom implementation + paths_values = self._find_all_with_property_name(resource_dict) + for property_path, property_value in paths_values: + if isinstance(property_value, dict): + LOG.debug("Property %s of %s resource is not a URL", self.PROPERTY_NAME, resource_id) + return + + # If property is a file but not a zip file, place file in temp + # folder and send the temp folder to be zipped + temp_dir = None + if is_local_file(property_value) and not is_zip_file(property_value) and self.FORCE_ZIP: + temp_dir = copy_to_temp_dir(property_value) + set_value_from_jmespath(resource_dict, property_path, temp_dir) + + try: + self.do_export( + resource_id, resource_dict, parent_dir, property_path=property_path, local_path=property_value + ) + + except Exception as ex: + LOG.debug("Unable to export", exc_info=ex) + raise exceptions.ExportFailedError( + resource_id=resource_id, property_name=property_path, property_value=property_value, ex=ex + ) + finally: + if temp_dir: + shutil.rmtree(temp_dir) + + def _find_all_with_property_name(self, graphql_dict: Dict[str, Any]) -> List[Tuple[str, Union[str, Dict]]]: + """Find paths to the all properties with self.PROPERTY_NAME name and their (properties) values. + + It leverages the knowledge of GraphQLApi structure instead of doing generic search in the graph. + + Parameters + ---------- + graphql_dict + GraphQLApi resource dict + + Returns + ------- + list of tuple (path, value) for all found properties which has property_name + """ + # need to look up only in "Resolvers" and "Functions" subtrees + resolvers_and_functions = {k: graphql_dict[k] for k in ("Resolvers", "Functions") if k in graphql_dict} + stack: List[Tuple[Dict[str, Any], str]] = [(resolvers_and_functions, "")] + paths_values: List[Tuple[str, Union[str, Dict]]] = [] + + while stack: + node, path = stack.pop() + if isinstance(node, dict): + for key, value in node.items(): + if key == self.PROPERTY_NAME: + paths_values.append((f"{path}{key}", value)) + elif isinstance(value, dict): + stack.append((value, f"{path}{key}.")) + # there is no need to handle lists because + # paths to "CodeUri" within "Resolvers" and "Functions" doesn't have lists + return paths_values + + RESOURCES_EXPORT_LIST = [ ServerlessFunctionResource, ServerlessFunctionImageResource, @@ -610,6 +732,8 @@ def get_property_value(self, resource_dict): CloudFormationModuleVersionModulePackage, CloudFormationResourceVersionSchemaHandlerPackage, ECRResource, + GraphQLApiSchemaResource, + GraphQLApiCodeResource, ] METADATA_EXPORT_LIST = [ServerlessRepoApplicationReadme, ServerlessRepoApplicationLicense] diff --git a/samcli/lib/package/utils.py b/samcli/lib/package/utils.py index 6434a70d5f..8650d3efa8 100644 --- a/samcli/lib/package/utils.py +++ b/samcli/lib/package/utils.py @@ -57,7 +57,7 @@ def is_path_value_valid(path): return isinstance(path, str) -def make_abs_path(directory, path): +def make_abs_path(directory: str, path: str) -> str: if is_path_value_valid(path) and not os.path.isabs(path): return os.path.normpath(os.path.join(directory, path)) return path @@ -130,10 +130,11 @@ def upload_local_artifacts( resource_type: str, resource_id: str, resource_dict: Dict, - property_name: str, + property_path: str, parent_dir: str, uploader: S3Uploader, extension: Optional[str] = None, + local_path: Optional[str] = None, ) -> str: """ Upload local artifacts referenced by the property at given resource and @@ -150,28 +151,28 @@ def upload_local_artifacts( :param resource_type: Type of the CloudFormation resource :param resource_id: Id of the CloudFormation resource :param resource_dict: Dictionary containing resource definition - :param property_name: Property name of CloudFormation resource where this + :param property_path: Json path to the property of SAM or CloudFormation resource where the local path is present :param parent_dir: Resolve all relative paths with respect to this directory :param uploader: Method to upload files to S3 :param extension: Extension of the uploaded artifact + :param local_path: Local path for the cases when search return more than single result :return: S3 URL of the uploaded object :raise: ValueError if path is not a S3 URL or a local path """ - local_path = jmespath.search(property_name, resource_dict) - if local_path is None: - # Build the root directory and upload to S3 - local_path = parent_dir + # if local_path is not passed and search returns nothing + # build the root directory and upload to S3 + local_path = jmespath.search(property_path, resource_dict) or parent_dir if is_s3_protocol_url(local_path): # A valid CloudFormation template will specify artifacts as S3 URLs. # This check is supporting the case where your resource does not # refer to local artifacts # Nothing to do if property value is an S3 URL - LOG.debug("Property %s of %s is already a S3 URL", property_name, resource_id) + LOG.debug("Property %s of %s is already a S3 URL", property_path, resource_id) return cast(str, local_path) local_path = make_abs_path(parent_dir, local_path) @@ -189,7 +190,7 @@ def upload_local_artifacts( if is_local_file(local_path): return uploader.upload_with_dedup(local_path) - raise InvalidLocalPathError(resource_id=resource_id, property_name=property_name, local_path=local_path) + raise InvalidLocalPathError(resource_id=resource_id, property_name=property_path, local_path=local_path) def resource_not_packageable(resource_dict): diff --git a/samcli/lib/utils/resources.py b/samcli/lib/utils/resources.py index 875f3bd997..5011f581dc 100644 --- a/samcli/lib/utils/resources.py +++ b/samcli/lib/utils/resources.py @@ -41,6 +41,7 @@ AWS_SERVERLESS_APPLICATION = "AWS::Serverless::Application" AWS_SERVERLESSREPO_APPLICATION = "AWS::ServerlessRepo::Application" +AWS_SERVERLESS_GRAPHQLAPI = "AWS::Serverless::GraphQLApi" AWS_APPSYNC_GRAPHQLSCHEMA = "AWS::AppSync::GraphQLSchema" AWS_APPSYNC_RESOLVER = "AWS::AppSync::Resolver" AWS_APPSYNC_FUNCTIONCONFIGURATION = "AWS::AppSync::FunctionConfiguration" @@ -61,12 +62,17 @@ METADATA_WITH_LOCAL_PATHS = {AWS_SERVERLESSREPO_APPLICATION: ["LicenseUrl", "ReadmeUrl"]} RESOURCES_WITH_LOCAL_PATHS = { + AWS_SERVERLESS_GRAPHQLAPI: ["SchemaUri", "CodeUri"], AWS_SERVERLESS_FUNCTION: ["CodeUri"], AWS_SERVERLESS_API: ["DefinitionUri"], AWS_SERVERLESS_HTTPAPI: ["DefinitionUri"], AWS_SERVERLESS_STATEMACHINE: ["DefinitionUri"], AWS_APPSYNC_GRAPHQLSCHEMA: ["DefinitionS3Location"], - AWS_APPSYNC_RESOLVER: ["RequestMappingTemplateS3Location", "ResponseMappingTemplateS3Location", "CodeS3Location"], + AWS_APPSYNC_RESOLVER: [ + "RequestMappingTemplateS3Location", + "ResponseMappingTemplateS3Location", + "CodeS3Location", + ], AWS_APPSYNC_FUNCTIONCONFIGURATION: [ "RequestMappingTemplateS3Location", "ResponseMappingTemplateS3Location", @@ -133,7 +139,11 @@ def get_packageable_resource_paths(): Resource Dictionary containing packageable resource types and their locations as a list. """ _resource_property_dict = defaultdict(list) - for _dict in (METADATA_WITH_LOCAL_PATHS, RESOURCES_WITH_LOCAL_PATHS, RESOURCES_WITH_IMAGE_COMPONENT): + for _dict in ( + METADATA_WITH_LOCAL_PATHS, + RESOURCES_WITH_LOCAL_PATHS, + RESOURCES_WITH_IMAGE_COMPONENT, + ): for key, value in _dict.items(): # Only add values to the list if they are different, same property name could be used with the resource # to package to different locations. diff --git a/tests/unit/lib/package/test_artifact_exporter.py b/tests/unit/lib/package/test_artifact_exporter.py index e622bd5904..1a2e7f2227 100644 --- a/tests/unit/lib/package/test_artifact_exporter.py +++ b/tests/unit/lib/package/test_artifact_exporter.py @@ -10,7 +10,7 @@ from contextlib import contextmanager, closing from unittest import mock -from unittest.mock import patch, Mock, MagicMock +from unittest.mock import call, patch, Mock, MagicMock from samcli.commands.package.exceptions import ExportFailedError from samcli.lib.package.permissions import ( @@ -35,6 +35,8 @@ ServerlessApplicationResource, ) from samcli.lib.package.packageable_resources import ( + GraphQLApiCodeResource, + GraphQLApiSchemaResource, is_s3_protocol_url, is_local_file, upload_local_artifacts, @@ -84,6 +86,20 @@ def get_mock(destination: Destination): self.code_signer_mock = Mock() self.code_signer_mock.should_sign_package.return_value = False + self.graphql_api_local_paths = ["resolvers/createFoo.js", "functions/func1.js", "functions/func2.js"] + self.graphql_api_resource_dict = { + "Resolvers": {"Mutation": {"createFoo": {"CodeUri": self.graphql_api_local_paths[0]}}}, + "Functions": { + "func1": {"CodeUri": self.graphql_api_local_paths[1]}, + "func2": {"CodeUri": self.graphql_api_local_paths[2]}, + }, + } + self.graphql_api_paths_to_property = [ + "Resolvers.Mutation.createFoo.CodeUri", + "Functions.func1.CodeUri", + "Functions.func2.CodeUri", + ] + def test_all_resources_export(self): uploaded_s3_url = "s3://foo/bar?versionId=baz" @@ -114,6 +130,8 @@ def test_all_resources_export(self): {"class": GlueJobCommandScriptLocationResource, "expected_result": {"ScriptLocation": uploaded_s3_url}}, {"class": CloudFormationModuleVersionModulePackage, "expected_result": uploaded_s3_url}, {"class": CloudFormationResourceVersionSchemaHandlerPackage, "expected_result": uploaded_s3_url}, + {"class": GraphQLApiSchemaResource, "expected_result": uploaded_s3_url}, + {"class": GraphQLApiCodeResource, "expected_result": [uploaded_s3_url, uploaded_s3_url, uploaded_s3_url]}, ] with patch("samcli.lib.package.packageable_resources.upload_local_artifacts") as upload_local_artifacts_mock: @@ -149,7 +167,9 @@ def _helper_verify_export_resources( resource_id = "id" - if "." in test_class.PROPERTY_NAME: + if test_class == GraphQLApiCodeResource: + resource_dict = self.graphql_api_resource_dict + elif "." in test_class.PROPERTY_NAME: reversed_property_names = test_class.PROPERTY_NAME.split(".") reversed_property_names.reverse() property_dict = {reversed_property_names[0]: "foo"} @@ -166,7 +186,43 @@ def _helper_verify_export_resources( resource_obj.export(resource_id, resource_dict, parent_dir) - if test_class in ( + if test_class == GraphQLApiCodeResource: + upload_local_artifacts_mock.assert_has_calls( + [ + call( + test_class.RESOURCE_TYPE, + resource_id, + resource_dict, + self.graphql_api_paths_to_property[0], + parent_dir, + s3_uploader_mock, + None, + self.graphql_api_local_paths[0], + ), + call( + test_class.RESOURCE_TYPE, + resource_id, + resource_dict, + self.graphql_api_paths_to_property[1], + parent_dir, + s3_uploader_mock, + None, + self.graphql_api_local_paths[1], + ), + call( + test_class.RESOURCE_TYPE, + resource_id, + resource_dict, + self.graphql_api_paths_to_property[2], + parent_dir, + s3_uploader_mock, + None, + self.graphql_api_local_paths[2], + ), + ], + any_order=True, + ) + elif test_class in ( ApiGatewayRestApiResource, LambdaFunctionResource, ElasticBeanstalkApplicationVersion, @@ -189,9 +245,16 @@ def _helper_verify_export_resources( parent_dir, s3_uploader_mock, None, + None, ) code_signer_mock.sign_package.assert_not_called() - if "." in test_class.PROPERTY_NAME: + if test_class == GraphQLApiCodeResource: + result = [ + self.graphql_api_resource_dict["Resolvers"]["Mutation"]["createFoo"][test_class.PROPERTY_NAME], + self.graphql_api_resource_dict["Functions"]["func1"][test_class.PROPERTY_NAME], + self.graphql_api_resource_dict["Functions"]["func2"][test_class.PROPERTY_NAME], + ] + elif "." in test_class.PROPERTY_NAME: top_level_property_name = test_class.PROPERTY_NAME.split(".")[0] result = resource_dict[top_level_property_name] else: @@ -529,6 +592,7 @@ class MockResource(ResourceZip): parent_dir, self.s3_uploader_mock, None, + None, ) self.assertEqual(resource_dict[resource.PROPERTY_NAME], s3_url) @@ -809,6 +873,7 @@ class MockResource(ResourceZip): parent_dir, self.s3_uploader_mock, None, + None, ) self.code_signer_mock.should_sign_package.assert_called_once_with(resource_id) self.code_signer_mock.sign_package.assert_not_called() From 49c0bc93ac4499d7e194705caeac0bcd07f08b5e Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Mon, 5 Jun 2023 16:58:30 -0700 Subject: [PATCH 030/107] chore: bump version to 1.86.0 (#5266) --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index 87cd027bf6..1882804ed9 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.85.0" +__version__ = "1.86.0" From f4ba4e9d0867c71458bc837641c32a27097ba666 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Mon, 5 Jun 2023 17:15:45 -0700 Subject: [PATCH 031/107] fix: add constant str for enums to support deepcopy operation (#5265) * fix: add constant str for enums to support deepcopy operation * add unit tests * formatting --- samcli/lib/providers/provider.py | 17 ++++++++++------- tests/unit/lib/build_module/test_build_graph.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/samcli/lib/providers/provider.py b/samcli/lib/providers/provider.py index bdf657cfe8..98e05051bf 100644 --- a/samcli/lib/providers/provider.py +++ b/samcli/lib/providers/provider.py @@ -7,7 +7,7 @@ import os import posixpath from collections import namedtuple -from enum import Enum, auto +from enum import Enum from typing import TYPE_CHECKING, Any, Dict, Iterator, List, NamedTuple, Optional, Set, Union, cast from samcli.commands.local.cli_common.user_exceptions import ( @@ -43,13 +43,16 @@ class FunctionBuildInfo(Enum): """ # buildable - BuildableZip = auto(), "Regular ZIP function which can be build with SAM CLI" - BuildableImage = auto(), "Regular IMAGE function which can be build with SAM CLI" + BuildableZip = "BuildableZip", "Regular ZIP function which can be build with SAM CLI" + BuildableImage = "BuildableImage", "Regular IMAGE function which can be build with SAM CLI" # non-buildable - InlineCode = auto(), "A ZIP function which has inline code, non buildable" - PreZipped = auto(), "A ZIP function which points to a .zip file, non buildable" - SkipBuild = auto(), "A Function which is denoted with SkipBuild in metadata, non buildable" - NonBuildableImage = auto(), "An IMAGE function which is missing some information to build, non buildable" + InlineCode = "InlineCode", "A ZIP function which has inline code, non buildable" + PreZipped = "PreZipped", "A ZIP function which points to a .zip file, non buildable" + SkipBuild = "SkipBuild", "A Function which is denoted with SkipBuild in metadata, non buildable" + NonBuildableImage = ( + "NonBuildableImage", + "An IMAGE function which is missing some information to build, non buildable", + ) def is_buildable(self) -> bool: """ diff --git a/tests/unit/lib/build_module/test_build_graph.py b/tests/unit/lib/build_module/test_build_graph.py index e0d1f524c9..4866777e8b 100644 --- a/tests/unit/lib/build_module/test_build_graph.py +++ b/tests/unit/lib/build_module/test_build_graph.py @@ -1,3 +1,4 @@ +import copy import os.path from unittest import TestCase from unittest.mock import patch, Mock @@ -961,3 +962,17 @@ def test_build_folder_with_multiple_functions(self, build_improvements_22_enable build_definition.get_build_dir("build_dir"), build_definition.functions[0].get_build_dir("build_dir") + "-Shared", ) + + def test_deepcopy_build_definition(self): + build_definition = FunctionBuildDefinition( + "runtime", "codeuri", ZIP, ARM64, {}, "handler", "source_hash", "manifest_hash" + ) + function1 = generate_function(runtime="runtime", codeuri="codeuri", handler="handler") + function2 = generate_function(runtime="runtime", codeuri="codeuri", handler="handler") + build_definition.add_function(function1) + build_definition.add_function(function2) + build_definitions = [build_definition] + + copied_build_definitions = copy.deepcopy(build_definitions) + + self.assertEqual(copied_build_definitions, build_definitions) From deb212bc21eda2be0290e9a30f296aa74331e6c3 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Tue, 6 Jun 2023 13:18:41 -0700 Subject: [PATCH 032/107] update automated updates gha to force restart of status checks (#5269) --- .../automated-updates-to-sam-cli.yml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/.github/workflows/automated-updates-to-sam-cli.yml b/.github/workflows/automated-updates-to-sam-cli.yml index 45c49f727a..9fd9c846da 100644 --- a/.github/workflows/automated-updates-to-sam-cli.yml +++ b/.github/workflows/automated-updates-to-sam-cli.yml @@ -48,7 +48,10 @@ jobs: run: | cd aws-sam-cli git push --force origin update_app_templates_hash - gh pr list --repo aws/aws-sam-cli --head update_app_templates_hash --json id --jq length | grep 1 && exit 0 # exit if there is existing pr + gh pr list --repo aws/aws-sam-cli --head update_app_templates_hash --json id --jq length | grep 1 && \ + gh pr close update_app_templates_hash --repo aws/aws-sam-cli && \ + gh pr reopen update_app_templates_hash --repo aws/aws-sam-cli && \ + exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit gh pr create --base develop --head update_app_templates_hash --title "feat: update SAM CLI with latest App Templates commit hash" --body "This PR & commit is automatically created from App Templates repo to update the SAM CLI with latest hash of the App Templates." --label "pr/internal" updateSAMTranslator: @@ -65,13 +68,13 @@ jobs: path: serverless-application-model ref: main fetch-depth: 0 - + - name: Checkout SAM CLI uses: actions/checkout@v3 with: repository: aws/aws-sam-cli path: aws-sam-cli - + - uses: actions/setup-python@v4 # used for make update-reproducible-reqs below with: python-version: | @@ -105,7 +108,10 @@ jobs: run: | cd aws-sam-cli git push --force origin update_sam_transform_version - gh pr list --repo aws/aws-sam-cli --head update_sam_transform_version --json id --jq length | grep 1 && exit 0 # exit if there is existing pr + gh pr list --repo aws/aws-sam-cli --head update_sam_transform_version --json id --jq length | grep 1 && \ + gh pr close update_sam_transform_version --repo aws/aws-sam-cli && \ + gh pr reopen update_sam_transform_version --repo aws/aws-sam-cli && \ + exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit gh pr create --base develop --head update_sam_transform_version --fill --label "pr/internal" updateAWSLambdaBuilders: @@ -161,5 +167,8 @@ jobs: run: | cd aws-sam-cli git push --force origin update_lambda_builders_version - gh pr list --repo aws/aws-sam-cli --head update_lambda_builders_version --json id --jq length | grep 1 && exit 0 # exit if there is existing pr + gh pr list --repo aws/aws-sam-cli --head update_lambda_builders_version --json id --jq length | grep 1 && \ + gh pr close update_lambda_builders_version --repo aws/aws-sam-cli && \ + gh pr reopen update_lambda_builders_version --repo aws/aws-sam-cli && \ + exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit gh pr create --base develop --head update_lambda_builders_version --fill --label "pr/internal" From 789268f7310f8e9072c60601956ddfab0ee57dc2 Mon Sep 17 00:00:00 2001 From: Slava Senchenko Date: Tue, 6 Jun 2023 13:32:09 -0700 Subject: [PATCH 033/107] integration tests for graphql resource package (#5271) --- .../package/test_package_command_zip.py | 8 ++++ .../package/aws-serverless-graphqlapi.yaml | 46 +++++++++++++++++++ .../package/sam_graphql_api/createPostItem.js | 19 ++++++++ .../package/sam_graphql_api/getPost.js | 7 +++ .../sam_graphql_api/getPostFromTable.js | 19 ++++++++ .../package/sam_graphql_api/schema.graphql | 22 +++++++++ 6 files changed, 121 insertions(+) create mode 100644 tests/integration/testdata/package/aws-serverless-graphqlapi.yaml create mode 100644 tests/integration/testdata/package/sam_graphql_api/createPostItem.js create mode 100644 tests/integration/testdata/package/sam_graphql_api/getPost.js create mode 100644 tests/integration/testdata/package/sam_graphql_api/getPostFromTable.js create mode 100644 tests/integration/testdata/package/sam_graphql_api/schema.graphql diff --git a/tests/integration/package/test_package_command_zip.py b/tests/integration/package/test_package_command_zip.py index b18524cfb4..85d2f36ab5 100644 --- a/tests/integration/package/test_package_command_zip.py +++ b/tests/integration/package/test_package_command_zip.py @@ -88,6 +88,7 @@ def test_package_nested_template(self, template_file, uploading_count): "aws-serverless-statemachine.yaml", "aws-stepfunctions-statemachine.yaml", "aws-include-transform.yaml", + "aws-serverless-graphqlapi.yaml", ] ) def test_package_barebones(self, template_file): @@ -140,6 +141,7 @@ def test_package_without_required_args(self): "aws-serverlessrepo-application.yaml", "aws-serverless-statemachine.yaml", "aws-stepfunctions-statemachine.yaml", + "aws-serverless-graphqlapi.yaml", ] ) def test_package_with_prefix(self, template_file): @@ -183,6 +185,7 @@ def test_package_with_prefix(self, template_file): "aws-serverlessrepo-application.yaml", "aws-serverless-statemachine.yaml", "aws-stepfunctions-statemachine.yaml", + "aws-serverless-graphqlapi.yaml", ] ) def test_package_with_output_template_file(self, template_file): @@ -237,6 +240,7 @@ def test_package_with_output_template_file(self, template_file): "aws-serverlessrepo-application.yaml", "aws-serverless-statemachine.yaml", "aws-stepfunctions-statemachine.yaml", + "aws-serverless-graphqlapi.yaml", ] ) def test_package_with_json(self, template_file): @@ -292,6 +296,7 @@ def test_package_with_json(self, template_file): "aws-serverlessrepo-application.yaml", "aws-serverless-statemachine.yaml", "aws-stepfunctions-statemachine.yaml", + "aws-serverless-graphqlapi.yaml", ] ) def test_package_with_force_upload(self, template_file): @@ -349,6 +354,7 @@ def test_package_with_force_upload(self, template_file): "aws-serverlessrepo-application.yaml", "aws-serverless-statemachine.yaml", "aws-stepfunctions-statemachine.yaml", + "aws-serverless-graphqlapi.yaml", ] ) def test_package_with_kms_key(self, template_file): @@ -405,6 +411,7 @@ def test_package_with_kms_key(self, template_file): "aws-serverlessrepo-application.yaml", "aws-serverless-statemachine.yaml", "aws-stepfunctions-statemachine.yaml", + "aws-serverless-graphqlapi.yaml", ] ) def test_package_with_metadata(self, template_file): @@ -460,6 +467,7 @@ def test_package_with_metadata(self, template_file): "aws-serverlessrepo-application.yaml", "aws-serverless-statemachine.yaml", "aws-stepfunctions-statemachine.yaml", + "aws-serverless-graphqlapi.yaml", ] ) def test_package_with_resolve_s3(self, template_file): diff --git a/tests/integration/testdata/package/aws-serverless-graphqlapi.yaml b/tests/integration/testdata/package/aws-serverless-graphqlapi.yaml new file mode 100644 index 0000000000..ba55a34310 --- /dev/null +++ b/tests/integration/testdata/package/aws-serverless-graphqlapi.yaml @@ -0,0 +1,46 @@ +Transform: AWS::Serverless-2016-10-31 + +Resources: + DynamoDBPostsTable: + Type: AWS::Serverless::SimpleTable + + SuperCoolAPI: + Type: AWS::Serverless::GraphQLApi + Properties: + SchemaUri: ./sam_graphql_api/schema.graphql + Auth: + Type: AWS_IAM + DataSources: + DynamoDb: + PostsDataSource: + TableName: !Ref DynamoDBPostsTable + TableArn: !GetAtt DynamoDBPostsTable.Arn + Functions: + createPostItem: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + DataSource: PostsDataSource + CodeUri: ./sam_graphql_api/createPostItem.js + getPostFromTable: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + DataSource: PostsDataSource + CodeUri: ./sam_graphql_api/getPostFromTable.js + Resolvers: + Mutation: + addPost: + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + Pipeline: + - createPostItem + Query: + getPost: + CodeUri: ./sam_graphql_api/getPost.js + Runtime: + Name: APPSYNC_JS + Version: "1.0.0" + Pipeline: + - getPostFromTable diff --git a/tests/integration/testdata/package/sam_graphql_api/createPostItem.js b/tests/integration/testdata/package/sam_graphql_api/createPostItem.js new file mode 100644 index 0000000000..ca55c8bc75 --- /dev/null +++ b/tests/integration/testdata/package/sam_graphql_api/createPostItem.js @@ -0,0 +1,19 @@ +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + return dynamoDBGetItemRequest({ id: ctx.args.id }); +} + +export function response(ctx) { + return ctx.result; +} + +/** + * A helper function to get a DynamoDB item + */ +function dynamoDBGetItemRequest(key) { + return { + operation: "GetItem", + key: util.dynamodb.toMapValues(key), + }; +} diff --git a/tests/integration/testdata/package/sam_graphql_api/getPost.js b/tests/integration/testdata/package/sam_graphql_api/getPost.js new file mode 100644 index 0000000000..86a88bd957 --- /dev/null +++ b/tests/integration/testdata/package/sam_graphql_api/getPost.js @@ -0,0 +1,7 @@ +export function request(ctx) { + return {}; +} + +export function response(ctx) { + return ctx.prev.result; +} diff --git a/tests/integration/testdata/package/sam_graphql_api/getPostFromTable.js b/tests/integration/testdata/package/sam_graphql_api/getPostFromTable.js new file mode 100644 index 0000000000..ca55c8bc75 --- /dev/null +++ b/tests/integration/testdata/package/sam_graphql_api/getPostFromTable.js @@ -0,0 +1,19 @@ +import { util } from "@aws-appsync/utils"; + +export function request(ctx) { + return dynamoDBGetItemRequest({ id: ctx.args.id }); +} + +export function response(ctx) { + return ctx.result; +} + +/** + * A helper function to get a DynamoDB item + */ +function dynamoDBGetItemRequest(key) { + return { + operation: "GetItem", + key: util.dynamodb.toMapValues(key), + }; +} diff --git a/tests/integration/testdata/package/sam_graphql_api/schema.graphql b/tests/integration/testdata/package/sam_graphql_api/schema.graphql new file mode 100644 index 0000000000..e529ee0615 --- /dev/null +++ b/tests/integration/testdata/package/sam_graphql_api/schema.graphql @@ -0,0 +1,22 @@ +schema { + query: Query + mutation: Mutation +} + +type Query { + getPost(id: String!): Post +} + +type Mutation { + addPost(author: String!, title: String!, content: String!): Post! +} + +type Post { + id: String! + author: String + title: String + content: String + ups: Int! + downs: Int! + version: Int! +} From 7242c8b6bce937b5ada0ff0e76a3e0abf632dce5 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Tue, 6 Jun 2023 23:51:17 +0000 Subject: [PATCH 034/107] Revert "fix: add 3.11 to classifiers and upgrade Docker (#5225)" This reverts commit b51d6617340853d891469ff7a4dcc5bb88175389. --- Makefile | 2 +- mypy.ini | 2 +- requirements/base.txt | 10 +++++----- requirements/dev.txt | 16 ++++------------ requirements/reproducible-linux.txt | 18 +++++++----------- requirements/reproducible-mac.txt | 18 +++++++----------- samcli/cli/global_config.py | 2 +- samcli/cli/hidden_imports.py | 13 +++++++++++-- .../hooks/prepare/resource_linking.py | 4 ++-- samcli/hook_packages/terraform/lib/utils.py | 2 +- samcli/lib/build/workflows.py | 2 +- samcli/lib/deploy/deployer.py | 2 +- samcli/lib/hook/hook_config.py | 2 +- samcli/lib/iac/cdk/cdk_iac.py | 6 +++--- samcli/lib/iac/cfn/cfn_iac.py | 5 +++-- samcli/lib/pipeline/bootstrap/stage.py | 4 ++-- samcli/lib/providers/provider.py | 6 ++---- samcli/lib/utils/lock_distributor.py | 2 +- samcli/local/docker/container.py | 3 +++ setup.py | 1 - tests/integration/logs/test_logs_command.py | 6 +++--- .../integration/traces/test_traces_command.py | 4 ++-- tests/unit/commands/deploy/test_command.py | 2 +- tests/unit/commands/sync/test_command.py | 2 +- tests/unit/commands/validate/test_cli.py | 4 ++-- .../unit/lib/build_module/test_app_builder.py | 3 ++- tests/unit/lib/deploy/test_deployer.py | 4 ++-- .../lib/pipeline/bootstrap/test_environment.py | 2 +- .../unit/local/apigw/test_lambda_authorizer.py | 2 +- tests/unit/local/docker/test_container.py | 1 + 30 files changed, 73 insertions(+), 77 deletions(-) diff --git a/Makefile b/Makefile index 8876d482b2..c69818162d 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ lint: # Linter performs static analysis to catch latent bugs ruff samcli # mypy performs type check - mypy --exclude /testdata/ --exclude /init/templates/ --no-incremental setup.py samcli tests + mypy --no-incremental setup.py samcli tests # Command to run everytime you make changes to verify everything works dev: lint test diff --git a/mypy.ini b/mypy.ini index ba4e3fa9d1..5a0be0e705 100644 --- a/mypy.ini +++ b/mypy.ini @@ -5,7 +5,7 @@ warn_return_any=True warn_unused_configs=True no_implicit_optional=True warn_redundant_casts=True -warn_unused_ignores=False # @jfuss Done as a stop gap since different py versions have different errors +warn_unused_ignores=True warn_unreachable=True # diff --git a/requirements/base.txt b/requirements/base.txt index 84eb44da14..f2bb6a0f81 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -3,21 +3,21 @@ click~=8.0 Flask<2.3 #Need to add Schemas latest SDK. boto3>=1.19.5,==1.* -jmespath~=1.0.1 -ruamel_yaml~=0.17.21 +jmespath~=0.10.0 +ruamel_yaml==0.17.21 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 aws-sam-translator==1.68.0 #docker minor version updates can include breaking changes. Auto update micro version only. -docker~=6.1.0 +docker~=4.2.0 dateparser~=1.1 -requests~=2.31.0 +requests==2.31.0 serverlessrepo==0.1.10 aws_lambda_builders==1.33.0 tomlkit==0.11.8 watchdog==2.1.2 rich~=13.3.3 -pyopenssl~=23.0.0 +pyopenssl==23.0.0 # Needed for supporting Protocol in Python 3.7, Protocol class became public with python3.8 typing_extensions~=4.4.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index 5251b1025e..ecb95dbcfa 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,30 +1,22 @@ -r pre-dev.txt -coverage==7.2.7 +coverage==5.3 pytest-cov==4.0.0 # type checking and related stubs # mypy adds new rules in new minor versions, which could cause our PR check to fail # here we fix its version and upgrade it manually in the future -mypy==1.3.0 -boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]==1.26.146 +mypy==0.790 +boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]==1.26.131 types-pywin32==306.0.0.0 types-PyYAML==6.0.12 types-chevron==0.14.2.4 types-psutil==5.9.5.12 types-setuptools==65.4.0.0 -types-Pygments==2.15.0.1 -types-colorama==0.4.15.11 -types-dateparser==1.1.4.9 -types-docutils==0.20.0.1 -types-jsonschema==4.17.0.8 -types-pyOpenSSL==23.2.0.0 -types-requests==2.31.0.1 -types-urllib3==1.26.25.13 # Test requirements -pytest~=7.2.2 +pytest==7.2.2 parameterized==0.9.0 pytest-xdist==3.2.0 pytest-forked==1.6.0 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index e8faac8822..7be7aa0656 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -241,9 +241,9 @@ dateparser==1.1.8 \ --hash=sha256:070b29b5bbf4b1ec2cd51c96ea040dc68a614de703910a91ad1abba18f9f379f \ --hash=sha256:86b8b7517efcc558f085a142cdb7620f0921543fcabdb538c8a4c4001d8178e3 # via aws-sam-cli (setup.py) -docker==6.1.3 \ - --hash=sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20 \ - --hash=sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9 +docker==4.2.2 \ + --hash=sha256:03a46400c4080cb6f7aa997f881ddd84fef855499ece219d75fbdb53289c17ab \ + --hash=sha256:26eebadce7e298f55b76a88c4f8802476c5eaddbdbe38dbc6cce8781c47c9b54 # via aws-sam-cli (setup.py) flask==2.2.5 \ --hash=sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf \ @@ -268,9 +268,9 @@ jinja2-time==0.2.0 \ --hash=sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40 \ --hash=sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa # via cookiecutter -jmespath==1.0.1 \ - --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ - --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f # via # aws-sam-cli (setup.py) # boto3 @@ -371,10 +371,6 @@ networkx==2.6.3 \ --hash=sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef \ --hash=sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51 # via cfn-lint -packaging==23.1 \ - --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ - --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f - # via docker pbr==5.11.1 \ --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 @@ -634,6 +630,7 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via + # docker # junit-xml # python-dateutil # serverlessrepo @@ -667,7 +664,6 @@ urllib3==1.26.15 \ --hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42 # via # botocore - # docker # requests watchdog==2.1.2 \ --hash=sha256:0237db4d9024859bea27d0efb59fe75eef290833fd988b8ead7a879b0308c2db \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index ee5072b94d..0b47b91535 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -259,9 +259,9 @@ dateparser==1.1.8 \ --hash=sha256:070b29b5bbf4b1ec2cd51c96ea040dc68a614de703910a91ad1abba18f9f379f \ --hash=sha256:86b8b7517efcc558f085a142cdb7620f0921543fcabdb538c8a4c4001d8178e3 # via aws-sam-cli (setup.py) -docker==6.1.3 \ - --hash=sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20 \ - --hash=sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9 +docker==4.2.2 \ + --hash=sha256:03a46400c4080cb6f7aa997f881ddd84fef855499ece219d75fbdb53289c17ab \ + --hash=sha256:26eebadce7e298f55b76a88c4f8802476c5eaddbdbe38dbc6cce8781c47c9b54 # via aws-sam-cli (setup.py) flask==2.2.5 \ --hash=sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf \ @@ -299,9 +299,9 @@ jinja2-time==0.2.0 \ --hash=sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40 \ --hash=sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa # via cookiecutter -jmespath==1.0.1 \ - --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ - --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe +jmespath==0.10.0 \ + --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ + --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f # via # aws-sam-cli (setup.py) # boto3 @@ -402,10 +402,6 @@ networkx==2.6.3 \ --hash=sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef \ --hash=sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51 # via cfn-lint -packaging==23.1 \ - --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ - --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f - # via docker pbr==5.11.1 \ --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 @@ -707,6 +703,7 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via + # docker # junit-xml # python-dateutil # serverlessrepo @@ -745,7 +742,6 @@ urllib3==1.26.15 \ --hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42 # via # botocore - # docker # requests watchdog==2.1.2 \ --hash=sha256:0237db4d9024859bea27d0efb59fe75eef290833fd988b8ead7a879b0308c2db \ diff --git a/samcli/cli/global_config.py b/samcli/cli/global_config.py index 2542379432..e9a105ae76 100644 --- a/samcli/cli/global_config.py +++ b/samcli/cli/global_config.py @@ -163,7 +163,7 @@ def get_value( self, config_entry: ConfigEntry, default: Optional[T] = None, - value_type: Type[T] = T, # type: ignore + value_type: Type[T] = T, is_flag: bool = False, reload_config: bool = False, ) -> Optional[T]: diff --git a/samcli/cli/hidden_imports.py b/samcli/cli/hidden_imports.py index 2d116d9fc8..cde0e8368a 100644 --- a/samcli/cli/hidden_imports.py +++ b/samcli/cli/hidden_imports.py @@ -2,10 +2,17 @@ Keeps list of hidden/dynamic imports that is being used in SAM CLI, so that pyinstaller can include these packages """ import pkgutil -from types import ModuleType +from typing import cast +from typing_extensions import Protocol -def walk_modules(module: ModuleType, visited: set) -> None: + +class HasPathAndName(Protocol): + __path__: str + __name__: str + + +def walk_modules(module: HasPathAndName, visited: set) -> None: """Recursively find all modules from a parent module""" for pkg in pkgutil.walk_packages(module.__path__, module.__name__ + "."): if pkg.name in visited: @@ -13,11 +20,13 @@ def walk_modules(module: ModuleType, visited: set) -> None: visited.add(pkg.name) if pkg.ispkg: submodule = __import__(pkg.name) + submodule = cast(HasPathAndName, submodule) walk_modules(submodule, visited) samcli_modules = set(["samcli"]) samcli = __import__("samcli") +samcli = cast(HasPathAndName, samcli) walk_modules(samcli, samcli_modules) SAM_CLI_HIDDEN_IMPORTS = list(samcli_modules) + [ diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 80dd302d41..1cfeab8d5f 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -243,7 +243,7 @@ def _link_using_terraform_config(self, source_tf_resource: TFResource, cfn_resou return for cfn_resource in cfn_resources: - self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) + self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore def _link_using_linking_fields(self, cfn_resource: Dict) -> None: """ @@ -298,7 +298,7 @@ def _link_using_linking_fields(self, cfn_resource: Dict) -> None: return LOG.debug("The value of the source resource linking field after mapping %s", dest_resources) - self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) + self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore def _process_resolved_resources( self, diff --git a/samcli/hook_packages/terraform/lib/utils.py b/samcli/hook_packages/terraform/lib/utils.py index 888c9f809b..1ea7789f8d 100644 --- a/samcli/hook_packages/terraform/lib/utils.py +++ b/samcli/hook_packages/terraform/lib/utils.py @@ -74,7 +74,7 @@ def _calculate_configuration_attribute_value_hash( else: sorted_references_list = sorted( configuration_attribute_value, - key=lambda x: x.value if isinstance(x, ConstantValue) else f"{x.module_address}.{x.value}", + key=lambda x: x.value if isinstance(x, ConstantValue) else f"{x.module_address}.{x.value}", # type: ignore ) for ref in sorted_references_list: md5.update( diff --git a/samcli/lib/build/workflows.py b/samcli/lib/build/workflows.py index d97f83b99e..66d8529e4a 100644 --- a/samcli/lib/build/workflows.py +++ b/samcli/lib/build/workflows.py @@ -4,7 +4,7 @@ from typing import List CONFIG = namedtuple( - "CONFIG", + "Capability", [ "language", "dependency_manager", diff --git a/samcli/lib/deploy/deployer.py b/samcli/lib/deploy/deployer.py index 16e860c54c..1426360d76 100644 --- a/samcli/lib/deploy/deployer.py +++ b/samcli/lib/deploy/deployer.py @@ -624,7 +624,7 @@ def sync( msg = "" if exists: - kwargs["DisableRollback"] = disable_rollback # type: ignore + kwargs["DisableRollback"] = disable_rollback result = self.update_stack(**kwargs) self.wait_for_execute(stack_name, "UPDATE", disable_rollback, on_failure=on_failure) diff --git a/samcli/lib/hook/hook_config.py b/samcli/lib/hook/hook_config.py index 92908642de..8a71a0993b 100644 --- a/samcli/lib/hook/hook_config.py +++ b/samcli/lib/hook/hook_config.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import Dict, NamedTuple, Optional, cast -import jsonschema +import jsonschema # type: ignore from .exceptions import InvalidHookPackageConfigException diff --git a/samcli/lib/iac/cdk/cdk_iac.py b/samcli/lib/iac/cdk/cdk_iac.py index 700c1ef3c3..30fcd1c168 100644 --- a/samcli/lib/iac/cdk/cdk_iac.py +++ b/samcli/lib/iac/cdk/cdk_iac.py @@ -19,13 +19,13 @@ class CdkIacImplementation(IaCPluginInterface): the CDK project type """ - def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: # type: ignore + def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: pass - def write_project(self, project: SamCliProject, build_dir: str) -> bool: # type: ignore + def write_project(self, project: SamCliProject, build_dir: str) -> bool: pass - def update_packaged_locations(self, stack: Stack) -> bool: # type: ignore + def update_packaged_locations(self, stack: Stack) -> bool: pass @staticmethod diff --git a/samcli/lib/iac/cfn/cfn_iac.py b/samcli/lib/iac/cfn/cfn_iac.py index 7617af2f92..a446ebae9c 100644 --- a/samcli/lib/iac/cfn/cfn_iac.py +++ b/samcli/lib/iac/cfn/cfn_iac.py @@ -1,6 +1,7 @@ """ Provide a CFN implementation of IaCPluginInterface """ + import logging import os from typing import List, Optional @@ -71,11 +72,11 @@ def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: stack = self._build_stack(self._template_file) return SamCliProject([stack]) - def write_project(self, project: SamCliProject, build_dir: str) -> bool: # type: ignore + def write_project(self, project: SamCliProject, build_dir: str) -> bool: # TODO pass - def update_packaged_locations(self, stack: Stack) -> bool: # type: ignore + def update_packaged_locations(self, stack: Stack) -> bool: # TODO pass diff --git a/samcli/lib/pipeline/bootstrap/stage.py b/samcli/lib/pipeline/bootstrap/stage.py index 06ab4fa6dc..314b6e4a48 100644 --- a/samcli/lib/pipeline/bootstrap/stage.py +++ b/samcli/lib/pipeline/bootstrap/stage.py @@ -13,7 +13,7 @@ import click import requests from botocore.exceptions import ClientError -from OpenSSL import SSL, crypto +from OpenSSL import SSL, crypto # type: ignore from samcli.commands.pipeline.bootstrap.guided_context import BITBUCKET, GITHUB_ACTIONS, GITLAB, OPEN_ID_CONNECT from samcli.commands.pipeline.bootstrap.pipeline_oidc_provider import PipelineOidcProvider @@ -222,7 +222,7 @@ def generate_thumbprint(oidc_provider_url: Optional[str]) -> Optional[str]: # If we attempt to get the cert chain without exchanging some traffic it will be empty c.sendall(str.encode("HEAD / HTTP/1.0\n\n")) peerCertChain = c.get_peer_cert_chain() - cert = peerCertChain[-1] # type: ignore + cert = peerCertChain[-1] # Dump the certificate in DER/ASN1 format so that its SHA1 hash can be computed dumped_cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert) diff --git a/samcli/lib/providers/provider.py b/samcli/lib/providers/provider.py index 98e05051bf..c630f0ffcc 100644 --- a/samcli/lib/providers/provider.py +++ b/samcli/lib/providers/provider.py @@ -466,11 +466,9 @@ def binary_media_types(self) -> List[str]: return list(self.binary_media_types_set) -_CorsTuple = namedtuple( - "_CorsTuple", ["allow_origin", "allow_methods", "allow_headers", "allow_credentials", "max_age"] -) +_CorsTuple = namedtuple("Cors", ["allow_origin", "allow_methods", "allow_headers", "allow_credentials", "max_age"]) -_CorsTuple.__new__.__defaults__ = ( +_CorsTuple.__new__.__defaults__ = ( # type: ignore None, # Allow Origin defaults to None None, # Allow Methods is optional and defaults to empty None, # Allow Headers is optional and defaults to empty diff --git a/samcli/lib/utils/lock_distributor.py b/samcli/lib/utils/lock_distributor.py index 2d4ad8dec0..94536b7a2f 100644 --- a/samcli/lib/utils/lock_distributor.py +++ b/samcli/lib/utils/lock_distributor.py @@ -72,7 +72,7 @@ def __init__( self._manager = manager self._dict_lock = self._create_new_lock() self._locks = ( - self._manager.dict() # type: ignore + self._manager.dict() if self._lock_type == LockDistributorType.PROCESS and self._manager is not None else dict() ) diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py index d4574931b1..f3020cc51e 100644 --- a/samcli/local/docker/container.py +++ b/samcli/local/docker/container.py @@ -206,6 +206,9 @@ def create(self): # Ex: 128m => 128MB kwargs["mem_limit"] = "{}m".format(self._memory_limit_mb) + if self.network_id == "host": + kwargs["network_mode"] = self.network_id + real_container = self.docker_client.containers.create(self._image, **kwargs) self.id = real_container.id diff --git a/setup.py b/setup.py index 792fed29f5..25d409f8db 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,6 @@ def read_version(): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.11", "Topic :: Internet", "Topic :: Software Development :: Build Tools", "Topic :: Utilities", diff --git a/tests/integration/logs/test_logs_command.py b/tests/integration/logs/test_logs_command.py index 22a15b8c72..4bac06d740 100644 --- a/tests/integration/logs/test_logs_command.py +++ b/tests/integration/logs/test_logs_command.py @@ -2,7 +2,7 @@ import logging import time from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import List, Optional, Tuple import boto3 import pytest @@ -30,7 +30,7 @@ class LogsIntegTestCases(LogsIntegBase): test_template_folder = "" stack_name = "" - stack_resources: Dict[Any, Any] = {} + stack_resources = {} stack_info = None def setUp(self): @@ -76,7 +76,7 @@ def _get_physical_id(self, resource_path: str): return self.stack_resources[resource_path] def _get_output_value(self, key: str): - for output in self.stack_info.outputs: # type: ignore + for output in self.stack_info.outputs: if output.get("OutputKey", "") == key: return output.get("OutputValue", "") diff --git a/tests/integration/traces/test_traces_command.py b/tests/integration/traces/test_traces_command.py index eb0a1d3fcc..cadca71c42 100644 --- a/tests/integration/traces/test_traces_command.py +++ b/tests/integration/traces/test_traces_command.py @@ -1,7 +1,7 @@ import itertools import time from pathlib import Path -from typing import Any, List +from typing import List from unittest import skipIf import boto3 @@ -32,7 +32,7 @@ @skipIf(SKIP_TRACES_TESTS, "Skip traces tests in CI/CD only") @pytest.mark.xdist_group(name="sam_traces") class TestTracesCommand(TracesIntegBase): - stack_resources: List[Any] = [] + stack_resources = [] stack_name = "" def setUp(self): diff --git a/tests/unit/commands/deploy/test_command.py b/tests/unit/commands/deploy/test_command.py index 9f6a59d8e3..bb96c16079 100644 --- a/tests/unit/commands/deploy/test_command.py +++ b/tests/unit/commands/deploy/test_command.py @@ -64,7 +64,7 @@ def setUp(self): def tearDown(self): self.companion_stack_manager_patch.stop() - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) @patch("samcli.commands.package.command.click") @patch("samcli.commands.package.package_context.PackageContext") @patch("samcli.commands.deploy.command.click") diff --git a/tests/unit/commands/sync/test_command.py b/tests/unit/commands/sync/test_command.py index b0bcede4d0..94f0dd118f 100644 --- a/tests/unit/commands/sync/test_command.py +++ b/tests/unit/commands/sync/test_command.py @@ -68,7 +68,7 @@ def setUp(self): (False, False, False, False, True, InfraSyncResult(True)), ] ) - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) @patch("samcli.commands.sync.command.click") @patch("samcli.commands.sync.command.execute_code_sync") @patch("samcli.commands.build.command.click") diff --git a/tests/unit/commands/validate/test_cli.py b/tests/unit/commands/validate/test_cli.py index c19f5d0377..b952de467f 100644 --- a/tests/unit/commands/validate/test_cli.py +++ b/tests/unit/commands/validate/test_cli.py @@ -11,8 +11,8 @@ from samcli.commands.validate.lib.exceptions import InvalidSamDocumentException from samcli.commands.validate.validate import do_cli, _read_sam_file, _lint -ctx_mock = namedtuple("ctx_mock", ["profile", "region"]) -ctx_lint_mock = namedtuple("ctx_lint_mock", ["debug", "region"]) +ctx_mock = namedtuple("ctx", ["profile", "region"]) +ctx_lint_mock = namedtuple("ctx", ["debug", "region"]) class TestValidateCli(TestCase): diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index 98b8006ad0..34f7cc5ae0 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -1418,7 +1418,8 @@ def setUp(self): def test_must_write_absolute_path_for_different_drives(self): def mock_new(cls, *args, **kwargs): cls = WindowsPath - self = cls._from_parts(args) + self = cls._from_parts(args, init=False) + self._init() return self def mock_resolve(self): diff --git a/tests/unit/lib/deploy/test_deployer.py b/tests/unit/lib/deploy/test_deployer.py index 9844366084..bd04722b03 100644 --- a/tests/unit/lib/deploy/test_deployer.py +++ b/tests/unit/lib/deploy/test_deployer.py @@ -343,7 +343,7 @@ def test_wait_for_changeset(self): self.deployer._client.get_waiter = MagicMock(return_value=MockChangesetWaiter()) self.deployer.wait_for_changeset("test-id", "test-stack") - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) def test_wait_for_changeset_client_sleep(self): deployer = Deployer(MagicMock().client("cloudformation"), client_sleep=os.getenv("SAM_CLI_POLL_DELAY", 0.5)) deployer._client.get_waiter = MagicMock(return_value=MockChangesetWaiter()) @@ -358,7 +358,7 @@ def test_wait_for_changeset_default_delay(self): ChangeSetName="test-id", StackName="test-stack", WaiterConfig={"Delay": 0.5} ) - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) def test_wait_for_changeset_custom_delay(self): deployer = Deployer(MagicMock().client("cloudformation"), client_sleep=os.getenv("SAM_CLI_POLL_DELAY")) deployer.wait_for_changeset("test-id", "test-stack") diff --git a/tests/unit/lib/pipeline/bootstrap/test_environment.py b/tests/unit/lib/pipeline/bootstrap/test_environment.py index 85eddc5fc8..3728c870d4 100644 --- a/tests/unit/lib/pipeline/bootstrap/test_environment.py +++ b/tests/unit/lib/pipeline/bootstrap/test_environment.py @@ -2,7 +2,7 @@ from unittest import TestCase from unittest.mock import Mock, patch, call, MagicMock -import OpenSSL.SSL +import OpenSSL.SSL # type: ignore import requests from samcli.commands.pipeline.bootstrap.guided_context import GITHUB_ACTIONS diff --git a/tests/unit/local/apigw/test_lambda_authorizer.py b/tests/unit/local/apigw/test_lambda_authorizer.py index dc7ca00acb..41f81249a4 100644 --- a/tests/unit/local/apigw/test_lambda_authorizer.py +++ b/tests/unit/local/apigw/test_lambda_authorizer.py @@ -25,7 +25,7 @@ def test_valid_header_identity_source(self): [ ({"headers": Headers({})},), # test empty headers ({},), # test no headers - ({"headers": Headers({"not here": 123})},), # type: ignore # test missing headers + ({"headers": Headers({"not here": 123})},), # test missing headers ({"validation_expression": "^123$"},), # test no headers, but provided validation ] ) diff --git a/tests/unit/local/docker/test_container.py b/tests/unit/local/docker/test_container.py index 14f292c0ce..61ccad4c9f 100644 --- a/tests/unit/local/docker/test_container.py +++ b/tests/unit/local/docker/test_container.py @@ -296,6 +296,7 @@ def test_must_connect_to_host_network_on_create(self): tty=False, use_config_proxy=True, volumes=expected_volumes, + network_mode="host", ) self.mock_docker_client.networks.get.assert_not_called() From 90fe7219b7e92c7c9c710a5ba3ab33dd8e7bfb21 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Tue, 6 Jun 2023 23:51:55 +0000 Subject: [PATCH 035/107] chore: bump version to 1.86.1 --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index 1882804ed9..dd9f8681e8 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.86.0" +__version__ = "1.86.1" From 369aa723dfb0ed6d0e5156f879ed47eea9e0abfe Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:38:05 -0500 Subject: [PATCH 036/107] chore: Upgrade Docker-py/ Support Py3.11 for running tests (#5279) * fix: add 3.11 to classifiers and upgrade Docker (#5225) * fix: add 3.11 to classifiers - update dependencies, need to nail down the versions. * Pin dev dependencies and handle excluding folders for mypy * Remove unneeded type: ignores * Fix name-match mypy errors * Fix empty-body error from mypy * Fix mypy errors by ignoring and get pytest to run/pass * Force mypy to not fail hopefully * Remove unneeded assignment * Update pinned requirements file --------- Co-authored-by: Jacob Fuss Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> * chore: Force version on docker and allow unit test to run when docker not running In order for the docker.from_env() not to fail when docker is not installed/running, we force the min version on client creation. This was the default behavior in 4.X of docker-py but not longer in the latest version. --------- Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Jacob Fuss --- Makefile | 2 +- mypy.ini | 2 +- requirements/base.txt | 10 +++--- requirements/dev.txt | 14 ++++++-- requirements/reproducible-linux.txt | 18 ++++++---- requirements/reproducible-mac.txt | 18 ++++++---- samcli/cli/global_config.py | 2 +- samcli/cli/hidden_imports.py | 13 ++----- samcli/commands/package/package_context.py | 3 +- .../hooks/prepare/resource_linking.py | 4 +-- samcli/hook_packages/terraform/lib/utils.py | 2 +- samcli/lib/build/app_builder.py | 3 +- samcli/lib/build/workflows.py | 2 +- samcli/lib/deploy/deployer.py | 2 +- samcli/lib/hook/hook_config.py | 2 +- samcli/lib/iac/cdk/cdk_iac.py | 6 ++-- samcli/lib/iac/cfn/cfn_iac.py | 5 ++- samcli/lib/package/ecr_uploader.py | 3 +- samcli/lib/package/image_utils.py | 3 +- samcli/lib/pipeline/bootstrap/stage.py | 4 +-- samcli/lib/providers/provider.py | 6 ++-- .../sync/flows/image_function_sync_flow.py | 3 +- samcli/lib/utils/file_observer.py | 3 +- samcli/lib/utils/lock_distributor.py | 2 +- samcli/lib/utils/system_info.py | 3 +- samcli/local/docker/container.py | 6 ++-- samcli/local/docker/lambda_image.py | 3 +- samcli/local/docker/manager.py | 3 +- samcli/local/lambdafn/runtime.py | 4 +-- setup.py | 1 + tests/integration/logs/test_logs_command.py | 6 ++-- .../integration/traces/test_traces_command.py | 4 +-- tests/unit/commands/deploy/test_command.py | 2 +- .../local/cli_common/test_invoke_context.py | 36 +++++++++++++++++-- tests/unit/commands/sync/test_command.py | 2 +- tests/unit/commands/validate/test_cli.py | 4 +-- .../unit/lib/build_module/test_app_builder.py | 3 +- tests/unit/lib/deploy/test_deployer.py | 4 +-- .../pipeline/bootstrap/test_environment.py | 2 +- .../local/apigw/test_lambda_authorizer.py | 2 +- tests/unit/local/docker/test_container.py | 1 - tests/unit/local/lambdafn/test_runtime.py | 16 ++++----- 42 files changed, 139 insertions(+), 95 deletions(-) diff --git a/Makefile b/Makefile index c69818162d..8876d482b2 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,7 @@ lint: # Linter performs static analysis to catch latent bugs ruff samcli # mypy performs type check - mypy --no-incremental setup.py samcli tests + mypy --exclude /testdata/ --exclude /init/templates/ --no-incremental setup.py samcli tests # Command to run everytime you make changes to verify everything works dev: lint test diff --git a/mypy.ini b/mypy.ini index 5a0be0e705..ba4e3fa9d1 100644 --- a/mypy.ini +++ b/mypy.ini @@ -5,7 +5,7 @@ warn_return_any=True warn_unused_configs=True no_implicit_optional=True warn_redundant_casts=True -warn_unused_ignores=True +warn_unused_ignores=False # @jfuss Done as a stop gap since different py versions have different errors warn_unreachable=True # diff --git a/requirements/base.txt b/requirements/base.txt index f2bb6a0f81..84eb44da14 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -3,21 +3,21 @@ click~=8.0 Flask<2.3 #Need to add Schemas latest SDK. boto3>=1.19.5,==1.* -jmespath~=0.10.0 -ruamel_yaml==0.17.21 +jmespath~=1.0.1 +ruamel_yaml~=0.17.21 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 aws-sam-translator==1.68.0 #docker minor version updates can include breaking changes. Auto update micro version only. -docker~=4.2.0 +docker~=6.1.0 dateparser~=1.1 -requests==2.31.0 +requests~=2.31.0 serverlessrepo==0.1.10 aws_lambda_builders==1.33.0 tomlkit==0.11.8 watchdog==2.1.2 rich~=13.3.3 -pyopenssl==23.0.0 +pyopenssl~=23.0.0 # Needed for supporting Protocol in Python 3.7, Protocol class became public with python3.8 typing_extensions~=4.4.0 diff --git a/requirements/dev.txt b/requirements/dev.txt index ecb95dbcfa..f5dcf55592 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,22 +1,30 @@ -r pre-dev.txt -coverage==5.3 +coverage==7.2.7 pytest-cov==4.0.0 # type checking and related stubs # mypy adds new rules in new minor versions, which could cause our PR check to fail # here we fix its version and upgrade it manually in the future -mypy==0.790 +mypy==1.3.0 boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]==1.26.131 types-pywin32==306.0.0.0 types-PyYAML==6.0.12 types-chevron==0.14.2.4 types-psutil==5.9.5.12 types-setuptools==65.4.0.0 +types-Pygments==2.15.0.1 +types-colorama==0.4.15.11 +types-dateparser==1.1.4.9 +types-docutils==0.20.0.1 +types-jsonschema==4.17.0.8 +types-pyOpenSSL==23.2.0.0 +types-requests==2.31.0.1 +types-urllib3==1.26.25.13 # Test requirements -pytest==7.2.2 +pytest~=7.2.2 parameterized==0.9.0 pytest-xdist==3.2.0 pytest-forked==1.6.0 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 7be7aa0656..e8faac8822 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -241,9 +241,9 @@ dateparser==1.1.8 \ --hash=sha256:070b29b5bbf4b1ec2cd51c96ea040dc68a614de703910a91ad1abba18f9f379f \ --hash=sha256:86b8b7517efcc558f085a142cdb7620f0921543fcabdb538c8a4c4001d8178e3 # via aws-sam-cli (setup.py) -docker==4.2.2 \ - --hash=sha256:03a46400c4080cb6f7aa997f881ddd84fef855499ece219d75fbdb53289c17ab \ - --hash=sha256:26eebadce7e298f55b76a88c4f8802476c5eaddbdbe38dbc6cce8781c47c9b54 +docker==6.1.3 \ + --hash=sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20 \ + --hash=sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9 # via aws-sam-cli (setup.py) flask==2.2.5 \ --hash=sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf \ @@ -268,9 +268,9 @@ jinja2-time==0.2.0 \ --hash=sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40 \ --hash=sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa # via cookiecutter -jmespath==0.10.0 \ - --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ - --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +jmespath==1.0.1 \ + --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ + --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe # via # aws-sam-cli (setup.py) # boto3 @@ -371,6 +371,10 @@ networkx==2.6.3 \ --hash=sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef \ --hash=sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51 # via cfn-lint +packaging==23.1 \ + --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ + --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f + # via docker pbr==5.11.1 \ --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 @@ -630,7 +634,6 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via - # docker # junit-xml # python-dateutil # serverlessrepo @@ -664,6 +667,7 @@ urllib3==1.26.15 \ --hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42 # via # botocore + # docker # requests watchdog==2.1.2 \ --hash=sha256:0237db4d9024859bea27d0efb59fe75eef290833fd988b8ead7a879b0308c2db \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 0b47b91535..ee5072b94d 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -259,9 +259,9 @@ dateparser==1.1.8 \ --hash=sha256:070b29b5bbf4b1ec2cd51c96ea040dc68a614de703910a91ad1abba18f9f379f \ --hash=sha256:86b8b7517efcc558f085a142cdb7620f0921543fcabdb538c8a4c4001d8178e3 # via aws-sam-cli (setup.py) -docker==4.2.2 \ - --hash=sha256:03a46400c4080cb6f7aa997f881ddd84fef855499ece219d75fbdb53289c17ab \ - --hash=sha256:26eebadce7e298f55b76a88c4f8802476c5eaddbdbe38dbc6cce8781c47c9b54 +docker==6.1.3 \ + --hash=sha256:aa6d17830045ba5ef0168d5eaa34d37beeb113948c413affe1d5991fc11f9a20 \ + --hash=sha256:aecd2277b8bf8e506e484f6ab7aec39abe0038e29fa4a6d3ba86c3fe01844ed9 # via aws-sam-cli (setup.py) flask==2.2.5 \ --hash=sha256:58107ed83443e86067e41eff4631b058178191a355886f8e479e347fa1285fdf \ @@ -299,9 +299,9 @@ jinja2-time==0.2.0 \ --hash=sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40 \ --hash=sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa # via cookiecutter -jmespath==0.10.0 \ - --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \ - --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f +jmespath==1.0.1 \ + --hash=sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980 \ + --hash=sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe # via # aws-sam-cli (setup.py) # boto3 @@ -402,6 +402,10 @@ networkx==2.6.3 \ --hash=sha256:80b6b89c77d1dfb64a4c7854981b60aeea6360ac02c6d4e4913319e0a313abef \ --hash=sha256:c0946ed31d71f1b732b5aaa6da5a0388a345019af232ce2f49c766e2d6795c51 # via cfn-lint +packaging==23.1 \ + --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ + --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f + # via docker pbr==5.11.1 \ --hash=sha256:567f09558bae2b3ab53cb3c1e2e33e726ff3338e7bae3db5dc954b3a44eef12b \ --hash=sha256:aefc51675b0b533d56bb5fd1c8c6c0522fe31896679882e1c4c63d5e4a0fccb3 @@ -703,7 +707,6 @@ six==1.16.0 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 # via - # docker # junit-xml # python-dateutil # serverlessrepo @@ -742,6 +745,7 @@ urllib3==1.26.15 \ --hash=sha256:aa751d169e23c7479ce47a0cb0da579e3ede798f994f5816a74e4f4500dcea42 # via # botocore + # docker # requests watchdog==2.1.2 \ --hash=sha256:0237db4d9024859bea27d0efb59fe75eef290833fd988b8ead7a879b0308c2db \ diff --git a/samcli/cli/global_config.py b/samcli/cli/global_config.py index e9a105ae76..2542379432 100644 --- a/samcli/cli/global_config.py +++ b/samcli/cli/global_config.py @@ -163,7 +163,7 @@ def get_value( self, config_entry: ConfigEntry, default: Optional[T] = None, - value_type: Type[T] = T, + value_type: Type[T] = T, # type: ignore is_flag: bool = False, reload_config: bool = False, ) -> Optional[T]: diff --git a/samcli/cli/hidden_imports.py b/samcli/cli/hidden_imports.py index cde0e8368a..2d116d9fc8 100644 --- a/samcli/cli/hidden_imports.py +++ b/samcli/cli/hidden_imports.py @@ -2,17 +2,10 @@ Keeps list of hidden/dynamic imports that is being used in SAM CLI, so that pyinstaller can include these packages """ import pkgutil -from typing import cast +from types import ModuleType -from typing_extensions import Protocol - -class HasPathAndName(Protocol): - __path__: str - __name__: str - - -def walk_modules(module: HasPathAndName, visited: set) -> None: +def walk_modules(module: ModuleType, visited: set) -> None: """Recursively find all modules from a parent module""" for pkg in pkgutil.walk_packages(module.__path__, module.__name__ + "."): if pkg.name in visited: @@ -20,13 +13,11 @@ def walk_modules(module: HasPathAndName, visited: set) -> None: visited.add(pkg.name) if pkg.ispkg: submodule = __import__(pkg.name) - submodule = cast(HasPathAndName, submodule) walk_modules(submodule, visited) samcli_modules = set(["samcli"]) samcli = __import__("samcli") -samcli = cast(HasPathAndName, samcli) walk_modules(samcli, samcli_modules) SAM_CLI_HIDDEN_IMPORTS = list(samcli_modules) + [ diff --git a/samcli/commands/package/package_context.py b/samcli/commands/package/package_context.py index c030bf23b2..48b4b777df 100644 --- a/samcli/commands/package/package_context.py +++ b/samcli/commands/package/package_context.py @@ -22,6 +22,7 @@ import boto3 import click import docker +from docker.constants import DEFAULT_DOCKER_API_VERSION from samcli.commands.package.exceptions import PackageFailedError from samcli.lib.intrinsic_resolver.intrinsics_symbol_table import IntrinsicsSymbolTable @@ -120,7 +121,7 @@ def run(self): ) ecr_client = boto3.client("ecr", config=get_boto_config_with_user_agent(region_name=region_name)) - docker_client = docker.from_env() + docker_client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) s3_uploader = S3Uploader( s3_client, self.s3_bucket, self.s3_prefix, self.kms_key_id, self.force_upload, self.no_progressbar diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 1cfeab8d5f..80dd302d41 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -243,7 +243,7 @@ def _link_using_terraform_config(self, source_tf_resource: TFResource, cfn_resou return for cfn_resource in cfn_resources: - self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore + self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) def _link_using_linking_fields(self, cfn_resource: Dict) -> None: """ @@ -298,7 +298,7 @@ def _link_using_linking_fields(self, cfn_resource: Dict) -> None: return LOG.debug("The value of the source resource linking field after mapping %s", dest_resources) - self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore + self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) def _process_resolved_resources( self, diff --git a/samcli/hook_packages/terraform/lib/utils.py b/samcli/hook_packages/terraform/lib/utils.py index 1ea7789f8d..888c9f809b 100644 --- a/samcli/hook_packages/terraform/lib/utils.py +++ b/samcli/hook_packages/terraform/lib/utils.py @@ -74,7 +74,7 @@ def _calculate_configuration_attribute_value_hash( else: sorted_references_list = sorted( configuration_attribute_value, - key=lambda x: x.value if isinstance(x, ConstantValue) else f"{x.module_address}.{x.value}", # type: ignore + key=lambda x: x.value if isinstance(x, ConstantValue) else f"{x.module_address}.{x.value}", ) for ref in sorted_references_list: md5.update( diff --git a/samcli/lib/build/app_builder.py b/samcli/lib/build/app_builder.py index c783e7e77e..5dbfcab965 100644 --- a/samcli/lib/build/app_builder.py +++ b/samcli/lib/build/app_builder.py @@ -8,6 +8,7 @@ import pathlib from typing import List, Optional, Dict, cast, NamedTuple import docker +from docker.constants import DEFAULT_DOCKER_API_VERSION import docker.errors from aws_lambda_builders import ( RPC_PROTOCOL_VERSION as lambda_builders_protocol_version, @@ -156,7 +157,7 @@ def __init__( self._parallel = parallel self._mode = mode self._stream_writer = stream_writer if stream_writer else StreamWriter(stream=osutils.stderr(), auto_flush=True) - self._docker_client = docker_client if docker_client else docker.from_env() + self._docker_client = docker_client if docker_client else docker.from_env(version=DEFAULT_DOCKER_API_VERSION) self._deprecated_runtimes = DEPRECATED_RUNTIMES self._colored = Colored() diff --git a/samcli/lib/build/workflows.py b/samcli/lib/build/workflows.py index 66d8529e4a..d97f83b99e 100644 --- a/samcli/lib/build/workflows.py +++ b/samcli/lib/build/workflows.py @@ -4,7 +4,7 @@ from typing import List CONFIG = namedtuple( - "Capability", + "CONFIG", [ "language", "dependency_manager", diff --git a/samcli/lib/deploy/deployer.py b/samcli/lib/deploy/deployer.py index 1426360d76..16e860c54c 100644 --- a/samcli/lib/deploy/deployer.py +++ b/samcli/lib/deploy/deployer.py @@ -624,7 +624,7 @@ def sync( msg = "" if exists: - kwargs["DisableRollback"] = disable_rollback + kwargs["DisableRollback"] = disable_rollback # type: ignore result = self.update_stack(**kwargs) self.wait_for_execute(stack_name, "UPDATE", disable_rollback, on_failure=on_failure) diff --git a/samcli/lib/hook/hook_config.py b/samcli/lib/hook/hook_config.py index 8a71a0993b..92908642de 100644 --- a/samcli/lib/hook/hook_config.py +++ b/samcli/lib/hook/hook_config.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import Dict, NamedTuple, Optional, cast -import jsonschema # type: ignore +import jsonschema from .exceptions import InvalidHookPackageConfigException diff --git a/samcli/lib/iac/cdk/cdk_iac.py b/samcli/lib/iac/cdk/cdk_iac.py index 30fcd1c168..700c1ef3c3 100644 --- a/samcli/lib/iac/cdk/cdk_iac.py +++ b/samcli/lib/iac/cdk/cdk_iac.py @@ -19,13 +19,13 @@ class CdkIacImplementation(IaCPluginInterface): the CDK project type """ - def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: + def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: # type: ignore pass - def write_project(self, project: SamCliProject, build_dir: str) -> bool: + def write_project(self, project: SamCliProject, build_dir: str) -> bool: # type: ignore pass - def update_packaged_locations(self, stack: Stack) -> bool: + def update_packaged_locations(self, stack: Stack) -> bool: # type: ignore pass @staticmethod diff --git a/samcli/lib/iac/cfn/cfn_iac.py b/samcli/lib/iac/cfn/cfn_iac.py index a446ebae9c..7617af2f92 100644 --- a/samcli/lib/iac/cfn/cfn_iac.py +++ b/samcli/lib/iac/cfn/cfn_iac.py @@ -1,7 +1,6 @@ """ Provide a CFN implementation of IaCPluginInterface """ - import logging import os from typing import List, Optional @@ -72,11 +71,11 @@ def read_project(self, lookup_paths: List[LookupPath]) -> SamCliProject: stack = self._build_stack(self._template_file) return SamCliProject([stack]) - def write_project(self, project: SamCliProject, build_dir: str) -> bool: + def write_project(self, project: SamCliProject, build_dir: str) -> bool: # type: ignore # TODO pass - def update_packaged_locations(self, stack: Stack) -> bool: + def update_packaged_locations(self, stack: Stack) -> bool: # type: ignore # TODO pass diff --git a/samcli/lib/package/ecr_uploader.py b/samcli/lib/package/ecr_uploader.py index 334b2a4287..4c6e714b81 100644 --- a/samcli/lib/package/ecr_uploader.py +++ b/samcli/lib/package/ecr_uploader.py @@ -9,6 +9,7 @@ import botocore import click import docker +from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import APIError, BuildError from samcli.commands.package.exceptions import ( @@ -35,7 +36,7 @@ class ECRUploader: def __init__( self, docker_client, ecr_client, ecr_repo, ecr_repo_multi, no_progressbar=False, tag="latest", stream=stderr() ): - self.docker_client = docker_client if docker_client else docker.from_env() + self.docker_client = docker_client if docker_client else docker.from_env(version=DEFAULT_DOCKER_API_VERSION) self.ecr_client = ecr_client self.ecr_repo = ecr_repo self.ecr_repo_multi = ecr_repo_multi diff --git a/samcli/lib/package/image_utils.py b/samcli/lib/package/image_utils.py index ac86694a33..5fefbfcd53 100644 --- a/samcli/lib/package/image_utils.py +++ b/samcli/lib/package/image_utils.py @@ -2,6 +2,7 @@ Image artifacts based utilities """ import docker +from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import APIError, NullResource from samcli.commands.package.exceptions import DockerGetLocalImageFailedError @@ -35,7 +36,7 @@ def tag_translation(image, docker_image_id=None, gen_tag="latest"): if not docker_image_id: try: - docker_client = docker.from_env() + docker_client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) docker_image_id = docker_client.images.get(image).id except APIError as ex: raise DockerGetLocalImageFailedError(str(ex)) from ex diff --git a/samcli/lib/pipeline/bootstrap/stage.py b/samcli/lib/pipeline/bootstrap/stage.py index 314b6e4a48..06ab4fa6dc 100644 --- a/samcli/lib/pipeline/bootstrap/stage.py +++ b/samcli/lib/pipeline/bootstrap/stage.py @@ -13,7 +13,7 @@ import click import requests from botocore.exceptions import ClientError -from OpenSSL import SSL, crypto # type: ignore +from OpenSSL import SSL, crypto from samcli.commands.pipeline.bootstrap.guided_context import BITBUCKET, GITHUB_ACTIONS, GITLAB, OPEN_ID_CONNECT from samcli.commands.pipeline.bootstrap.pipeline_oidc_provider import PipelineOidcProvider @@ -222,7 +222,7 @@ def generate_thumbprint(oidc_provider_url: Optional[str]) -> Optional[str]: # If we attempt to get the cert chain without exchanging some traffic it will be empty c.sendall(str.encode("HEAD / HTTP/1.0\n\n")) peerCertChain = c.get_peer_cert_chain() - cert = peerCertChain[-1] + cert = peerCertChain[-1] # type: ignore # Dump the certificate in DER/ASN1 format so that its SHA1 hash can be computed dumped_cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert) diff --git a/samcli/lib/providers/provider.py b/samcli/lib/providers/provider.py index c630f0ffcc..98e05051bf 100644 --- a/samcli/lib/providers/provider.py +++ b/samcli/lib/providers/provider.py @@ -466,9 +466,11 @@ def binary_media_types(self) -> List[str]: return list(self.binary_media_types_set) -_CorsTuple = namedtuple("Cors", ["allow_origin", "allow_methods", "allow_headers", "allow_credentials", "max_age"]) +_CorsTuple = namedtuple( + "_CorsTuple", ["allow_origin", "allow_methods", "allow_headers", "allow_credentials", "max_age"] +) -_CorsTuple.__new__.__defaults__ = ( # type: ignore +_CorsTuple.__new__.__defaults__ = ( None, # Allow Origin defaults to None None, # Allow Methods is optional and defaults to empty None, # Allow Headers is optional and defaults to empty diff --git a/samcli/lib/sync/flows/image_function_sync_flow.py b/samcli/lib/sync/flows/image_function_sync_flow.py index a284eeb191..cf9ff34871 100644 --- a/samcli/lib/sync/flows/image_function_sync_flow.py +++ b/samcli/lib/sync/flows/image_function_sync_flow.py @@ -5,6 +5,7 @@ import docker from docker.client import DockerClient +from docker.constants import DEFAULT_DOCKER_API_VERSION from samcli.lib.build.app_builder import ApplicationBuilder, ApplicationBuildResult from samcli.lib.package.ecr_uploader import ECRUploader @@ -69,7 +70,7 @@ def __init__( def _get_docker_client(self) -> DockerClient: """Lazy instantiates and returns the docker client""" if not self._docker_client: - self._docker_client = docker.from_env() + self._docker_client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) return self._docker_client def _get_ecr_client(self) -> Any: diff --git a/samcli/lib/utils/file_observer.py b/samcli/lib/utils/file_observer.py index 51f2450942..77928007ef 100644 --- a/samcli/lib/utils/file_observer.py +++ b/samcli/lib/utils/file_observer.py @@ -11,6 +11,7 @@ import docker from docker import DockerClient +from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import ImageNotFound from docker.types import CancellableStream from watchdog.events import FileSystemEvent, FileSystemEventHandler, PatternMatchingEventHandler @@ -257,7 +258,7 @@ def __init__(self, on_change: Callable) -> None: """ self._observed_images: Dict[str, str] = {} self._input_on_change: Callable = on_change - self.docker_client: DockerClient = docker.from_env() + self.docker_client: DockerClient = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) self.events: CancellableStream = self.docker_client.events(filters={"type": "image"}, decode=True) self._images_observer_thread: Optional[Thread] = None self._lock: Lock = threading.Lock() diff --git a/samcli/lib/utils/lock_distributor.py b/samcli/lib/utils/lock_distributor.py index 94536b7a2f..2d4ad8dec0 100644 --- a/samcli/lib/utils/lock_distributor.py +++ b/samcli/lib/utils/lock_distributor.py @@ -72,7 +72,7 @@ def __init__( self._manager = manager self._dict_lock = self._create_new_lock() self._locks = ( - self._manager.dict() + self._manager.dict() # type: ignore if self._lock_type == LockDistributorType.PROCESS and self._manager is not None else dict() ) diff --git a/samcli/lib/utils/system_info.py b/samcli/lib/utils/system_info.py index 39dfe6d92e..03c6173b45 100644 --- a/samcli/lib/utils/system_info.py +++ b/samcli/lib/utils/system_info.py @@ -53,10 +53,11 @@ def _gather_docker_info() -> str: import contextlib import docker + from docker.constants import DEFAULT_DOCKER_API_VERSION from samcli.local.docker.utils import is_docker_reachable - with contextlib.closing(docker.from_env()) as client: + with contextlib.closing(docker.from_env(version=DEFAULT_DOCKER_API_VERSION)) as client: if is_docker_reachable(client): return cast(str, client.version().get("Version", "Not available")) return "Not available" diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py index f3020cc51e..a6205c8e26 100644 --- a/samcli/local/docker/container.py +++ b/samcli/local/docker/container.py @@ -13,6 +13,7 @@ import docker import requests +from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import NotFound as DockerNetworkNotFound from samcli.lib.utils.retry import retry @@ -111,7 +112,7 @@ def __init__( self._logs_thread = None # Use the given Docker client or create new one - self.docker_client = docker_client or docker.from_env() + self.docker_client = docker_client or docker.from_env(version=DEFAULT_DOCKER_API_VERSION) # Runtime properties of the container. They won't have value until container is created or started self.id = None @@ -206,9 +207,6 @@ def create(self): # Ex: 128m => 128MB kwargs["mem_limit"] = "{}m".format(self._memory_limit_mb) - if self.network_id == "host": - kwargs["network_mode"] = self.network_id - real_container = self.docker_client.containers.create(self._image, **kwargs) self.id = real_container.id diff --git a/samcli/local/docker/lambda_image.py b/samcli/local/docker/lambda_image.py index 309ff91346..14ba00d06a 100644 --- a/samcli/local/docker/lambda_image.py +++ b/samcli/local/docker/lambda_image.py @@ -12,6 +12,7 @@ from typing import Optional import docker +from docker.constants import DEFAULT_DOCKER_API_VERSION from samcli.commands.local.cli_common.user_exceptions import ( DockerDistributionAPIError, @@ -123,7 +124,7 @@ def __init__(self, layer_downloader, skip_pull_image, force_image_build, docker_ self.layer_downloader = layer_downloader self.skip_pull_image = skip_pull_image self.force_image_build = force_image_build - self.docker_client = docker_client or docker.from_env() + self.docker_client = docker_client or docker.from_env(version=DEFAULT_DOCKER_API_VERSION) self.invoke_images = invoke_images def build(self, runtime, packagetype, image, layers, architecture, stream=None, function_name=None): diff --git a/samcli/local/docker/manager.py b/samcli/local/docker/manager.py index 50f7178021..45db09bce7 100644 --- a/samcli/local/docker/manager.py +++ b/samcli/local/docker/manager.py @@ -7,6 +7,7 @@ import threading import docker +from docker.constants import DEFAULT_DOCKER_API_VERSION from samcli.lib.utils.stream_writer import StreamWriter from samcli.local.docker import utils @@ -35,7 +36,7 @@ def __init__(self, docker_network_id=None, docker_client=None, skip_pull_image=F self.skip_pull_image = skip_pull_image self.docker_network_id = docker_network_id - self.docker_client = docker_client or docker.from_env() + self.docker_client = docker_client or docker.from_env(version=DEFAULT_DOCKER_API_VERSION) self.do_shutdown_event = do_shutdown_event self._lock = threading.Lock() diff --git a/samcli/local/lambdafn/runtime.py b/samcli/local/lambdafn/runtime.py index 770f3ef9e1..0272604656 100644 --- a/samcli/local/lambdafn/runtime.py +++ b/samcli/local/lambdafn/runtime.py @@ -331,7 +331,7 @@ class WarmLambdaRuntime(LambdaRuntime): warm containers life cycle. """ - def __init__(self, container_manager, image_builder): + def __init__(self, container_manager, image_builder, observer=None): """ Initialize the Local Lambda runtime @@ -347,7 +347,7 @@ def __init__(self, container_manager, image_builder): self._function_configs = {} self._containers = {} - self._observer = LambdaFunctionObserver(self._on_code_change) + self._observer = observer if observer else LambdaFunctionObserver(self._on_code_change) super().__init__(container_manager, image_builder) diff --git a/setup.py b/setup.py index 25d409f8db..792fed29f5 100644 --- a/setup.py +++ b/setup.py @@ -71,6 +71,7 @@ def read_version(): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.11", "Topic :: Internet", "Topic :: Software Development :: Build Tools", "Topic :: Utilities", diff --git a/tests/integration/logs/test_logs_command.py b/tests/integration/logs/test_logs_command.py index 4bac06d740..22a15b8c72 100644 --- a/tests/integration/logs/test_logs_command.py +++ b/tests/integration/logs/test_logs_command.py @@ -2,7 +2,7 @@ import logging import time from pathlib import Path -from typing import List, Optional, Tuple +from typing import Any, Dict, List, Optional, Tuple import boto3 import pytest @@ -30,7 +30,7 @@ class LogsIntegTestCases(LogsIntegBase): test_template_folder = "" stack_name = "" - stack_resources = {} + stack_resources: Dict[Any, Any] = {} stack_info = None def setUp(self): @@ -76,7 +76,7 @@ def _get_physical_id(self, resource_path: str): return self.stack_resources[resource_path] def _get_output_value(self, key: str): - for output in self.stack_info.outputs: + for output in self.stack_info.outputs: # type: ignore if output.get("OutputKey", "") == key: return output.get("OutputValue", "") diff --git a/tests/integration/traces/test_traces_command.py b/tests/integration/traces/test_traces_command.py index cadca71c42..eb0a1d3fcc 100644 --- a/tests/integration/traces/test_traces_command.py +++ b/tests/integration/traces/test_traces_command.py @@ -1,7 +1,7 @@ import itertools import time from pathlib import Path -from typing import List +from typing import Any, List from unittest import skipIf import boto3 @@ -32,7 +32,7 @@ @skipIf(SKIP_TRACES_TESTS, "Skip traces tests in CI/CD only") @pytest.mark.xdist_group(name="sam_traces") class TestTracesCommand(TracesIntegBase): - stack_resources = [] + stack_resources: List[Any] = [] stack_name = "" def setUp(self): diff --git a/tests/unit/commands/deploy/test_command.py b/tests/unit/commands/deploy/test_command.py index bb96c16079..9f6a59d8e3 100644 --- a/tests/unit/commands/deploy/test_command.py +++ b/tests/unit/commands/deploy/test_command.py @@ -64,7 +64,7 @@ def setUp(self): def tearDown(self): self.companion_stack_manager_patch.stop() - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore @patch("samcli.commands.package.command.click") @patch("samcli.commands.package.package_context.PackageContext") @patch("samcli.commands.deploy.command.click") diff --git a/tests/unit/commands/local/cli_common/test_invoke_context.py b/tests/unit/commands/local/cli_common/test_invoke_context.py index 052f265147..3cab08c82a 100644 --- a/tests/unit/commands/local/cli_common/test_invoke_context.py +++ b/tests/unit/commands/local/cli_common/test_invoke_context.py @@ -555,13 +555,20 @@ def test_must_raise_if_more_than_one_function(self): class TestInvokeContext_local_lambda_runner(TestCase): + @patch("samcli.local.lambdafn.runtime.LambdaFunctionObserver") @patch("samcli.commands.local.cli_common.invoke_context.LambdaImage") @patch("samcli.commands.local.cli_common.invoke_context.LayerDownloader") @patch("samcli.commands.local.cli_common.invoke_context.LambdaRuntime") @patch("samcli.commands.local.cli_common.invoke_context.LocalLambdaRunner") @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider") def test_must_create_runner( - self, SamFunctionProviderMock, LocalLambdaMock, LambdaRuntimeMock, download_layers_mock, lambda_image_patch + self, + SamFunctionProviderMock, + LocalLambdaMock, + LambdaRuntimeMock, + download_layers_mock, + lambda_image_patch, + LambdaFunctionObserver_patch, ): runtime_mock = Mock() LambdaRuntimeMock.return_value = runtime_mock @@ -575,6 +582,9 @@ def test_must_create_runner( image_mock = Mock() lambda_image_patch.return_value = image_mock + LambdaFunctionObserver_mock = Mock() + LambdaFunctionObserver_patch.return_value = LambdaFunctionObserver_mock + cwd = "cwd" self.context = InvokeContext( template_file="template_file", @@ -705,13 +715,20 @@ def test_must_create_runner_using_warm_containers( # assert that lambda runner is created only one time, and the cached version used in the second call self.assertEqual(LocalLambdaMock.call_count, 1) + @patch("samcli.local.lambdafn.runtime.LambdaFunctionObserver") @patch("samcli.commands.local.cli_common.invoke_context.LambdaImage") @patch("samcli.commands.local.cli_common.invoke_context.LayerDownloader") @patch("samcli.commands.local.cli_common.invoke_context.LambdaRuntime") @patch("samcli.commands.local.cli_common.invoke_context.LocalLambdaRunner") @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider") def test_must_create_runner_with_container_host_option( - self, SamFunctionProviderMock, LocalLambdaMock, LambdaRuntimeMock, download_layers_mock, lambda_image_patch + self, + SamFunctionProviderMock, + LocalLambdaMock, + LambdaRuntimeMock, + download_layers_mock, + lambda_image_patch, + LambdaFunctionObserver_patch, ): runtime_mock = Mock() LambdaRuntimeMock.return_value = runtime_mock @@ -725,6 +742,9 @@ def test_must_create_runner_with_container_host_option( image_mock = Mock() lambda_image_patch.return_value = image_mock + LambdaFunctionObserver_mock = Mock() + LambdaFunctionObserver_patch.return_value = LambdaFunctionObserver_mock + cwd = "cwd" self.context = InvokeContext( template_file="template_file", @@ -779,13 +799,20 @@ def test_must_create_runner_with_container_host_option( # assert that lambda runner is created only one time, and the cached version used in the second call self.assertEqual(LocalLambdaMock.call_count, 1) + @patch("samcli.local.lambdafn.runtime.LambdaFunctionObserver") @patch("samcli.commands.local.cli_common.invoke_context.LambdaImage") @patch("samcli.commands.local.cli_common.invoke_context.LayerDownloader") @patch("samcli.commands.local.cli_common.invoke_context.LambdaRuntime") @patch("samcli.commands.local.cli_common.invoke_context.LocalLambdaRunner") @patch("samcli.commands.local.cli_common.invoke_context.SamFunctionProvider") def test_must_create_runner_with_invoke_image_option( - self, SamFunctionProviderMock, LocalLambdaMock, LambdaRuntimeMock, download_layers_mock, lambda_image_patch + self, + SamFunctionProviderMock, + LocalLambdaMock, + LambdaRuntimeMock, + download_layers_mock, + lambda_image_patch, + LambdaFunctionObserver_patch, ): runtime_mock = Mock() LambdaRuntimeMock.return_value = runtime_mock @@ -799,6 +826,9 @@ def test_must_create_runner_with_invoke_image_option( image_mock = Mock() lambda_image_patch.return_value = image_mock + LambdaFunctionObserver_mock = Mock() + LambdaFunctionObserver_patch.return_value = LambdaFunctionObserver_mock + cwd = "cwd" self.context = InvokeContext( template_file="template_file", diff --git a/tests/unit/commands/sync/test_command.py b/tests/unit/commands/sync/test_command.py index 94f0dd118f..b0bcede4d0 100644 --- a/tests/unit/commands/sync/test_command.py +++ b/tests/unit/commands/sync/test_command.py @@ -68,7 +68,7 @@ def setUp(self): (False, False, False, False, True, InfraSyncResult(True)), ] ) - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore @patch("samcli.commands.sync.command.click") @patch("samcli.commands.sync.command.execute_code_sync") @patch("samcli.commands.build.command.click") diff --git a/tests/unit/commands/validate/test_cli.py b/tests/unit/commands/validate/test_cli.py index b952de467f..c19f5d0377 100644 --- a/tests/unit/commands/validate/test_cli.py +++ b/tests/unit/commands/validate/test_cli.py @@ -11,8 +11,8 @@ from samcli.commands.validate.lib.exceptions import InvalidSamDocumentException from samcli.commands.validate.validate import do_cli, _read_sam_file, _lint -ctx_mock = namedtuple("ctx", ["profile", "region"]) -ctx_lint_mock = namedtuple("ctx", ["debug", "region"]) +ctx_mock = namedtuple("ctx_mock", ["profile", "region"]) +ctx_lint_mock = namedtuple("ctx_lint_mock", ["debug", "region"]) class TestValidateCli(TestCase): diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index 34f7cc5ae0..98b8006ad0 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -1418,8 +1418,7 @@ def setUp(self): def test_must_write_absolute_path_for_different_drives(self): def mock_new(cls, *args, **kwargs): cls = WindowsPath - self = cls._from_parts(args, init=False) - self._init() + self = cls._from_parts(args) return self def mock_resolve(self): diff --git a/tests/unit/lib/deploy/test_deployer.py b/tests/unit/lib/deploy/test_deployer.py index bd04722b03..9844366084 100644 --- a/tests/unit/lib/deploy/test_deployer.py +++ b/tests/unit/lib/deploy/test_deployer.py @@ -343,7 +343,7 @@ def test_wait_for_changeset(self): self.deployer._client.get_waiter = MagicMock(return_value=MockChangesetWaiter()) self.deployer.wait_for_changeset("test-id", "test-stack") - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore def test_wait_for_changeset_client_sleep(self): deployer = Deployer(MagicMock().client("cloudformation"), client_sleep=os.getenv("SAM_CLI_POLL_DELAY", 0.5)) deployer._client.get_waiter = MagicMock(return_value=MockChangesetWaiter()) @@ -358,7 +358,7 @@ def test_wait_for_changeset_default_delay(self): ChangeSetName="test-id", StackName="test-stack", WaiterConfig={"Delay": 0.5} ) - @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) + @patch("os.environ", {**os.environ, "SAM_CLI_POLL_DELAY": 10}) # type: ignore def test_wait_for_changeset_custom_delay(self): deployer = Deployer(MagicMock().client("cloudformation"), client_sleep=os.getenv("SAM_CLI_POLL_DELAY")) deployer.wait_for_changeset("test-id", "test-stack") diff --git a/tests/unit/lib/pipeline/bootstrap/test_environment.py b/tests/unit/lib/pipeline/bootstrap/test_environment.py index 3728c870d4..85eddc5fc8 100644 --- a/tests/unit/lib/pipeline/bootstrap/test_environment.py +++ b/tests/unit/lib/pipeline/bootstrap/test_environment.py @@ -2,7 +2,7 @@ from unittest import TestCase from unittest.mock import Mock, patch, call, MagicMock -import OpenSSL.SSL # type: ignore +import OpenSSL.SSL import requests from samcli.commands.pipeline.bootstrap.guided_context import GITHUB_ACTIONS diff --git a/tests/unit/local/apigw/test_lambda_authorizer.py b/tests/unit/local/apigw/test_lambda_authorizer.py index 41f81249a4..dc7ca00acb 100644 --- a/tests/unit/local/apigw/test_lambda_authorizer.py +++ b/tests/unit/local/apigw/test_lambda_authorizer.py @@ -25,7 +25,7 @@ def test_valid_header_identity_source(self): [ ({"headers": Headers({})},), # test empty headers ({},), # test no headers - ({"headers": Headers({"not here": 123})},), # test missing headers + ({"headers": Headers({"not here": 123})},), # type: ignore # test missing headers ({"validation_expression": "^123$"},), # test no headers, but provided validation ] ) diff --git a/tests/unit/local/docker/test_container.py b/tests/unit/local/docker/test_container.py index 61ccad4c9f..14f292c0ce 100644 --- a/tests/unit/local/docker/test_container.py +++ b/tests/unit/local/docker/test_container.py @@ -296,7 +296,6 @@ def test_must_connect_to_host_network_on_create(self): tty=False, use_config_proxy=True, volumes=expected_volumes, - network_mode="host", ) self.mock_docker_client.networks.get.assert_not_called() diff --git a/tests/unit/local/lambdafn/test_runtime.py b/tests/unit/local/lambdafn/test_runtime.py index 295b0c8d57..42087ebd00 100644 --- a/tests/unit/local/lambdafn/test_runtime.py +++ b/tests/unit/local/lambdafn/test_runtime.py @@ -939,10 +939,11 @@ def setUp(self): @patch("samcli.local.lambdafn.runtime.os") def test_must_return_same_path_if_path_is_not_compressed_file(self, os_mock): lambda_image_mock = Mock() + observer_mock = Mock() os_mock.path.isfile.return_value = False code_path = "path" - self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock) + self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock, observer_mock) res = self.runtime._get_code_dir(code_path) self.assertEqual(self.runtime._temp_uncompressed_paths_to_be_cleaned, []) self.assertEqual(res, code_path) @@ -951,12 +952,13 @@ def test_must_return_same_path_if_path_is_not_compressed_file(self, os_mock): @patch("samcli.local.lambdafn.runtime.os") def test_must_cache_temp_uncompressed_dirs_to_be_cleared_later(self, os_mock, _unzip_file_mock): lambda_image_mock = Mock() + observer_mock = Mock() os_mock.path.isfile.return_value = True uncompressed_dir_mock = Mock() _unzip_file_mock.return_value = uncompressed_dir_mock code_path = "path.zip" - self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock) + self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock, observer_mock) res = self.runtime._get_code_dir(code_path) self.assertEqual(self.runtime._temp_uncompressed_paths_to_be_cleaned, [uncompressed_dir_mock]) self.assertEqual(res, uncompressed_dir_mock) @@ -966,16 +968,16 @@ class TestWarmLambdaRuntime_clean_warm_containers_related_resources(TestCase): def setUp(self): self.manager_mock = Mock() lambda_image_mock = Mock() - self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock) self.observer_mock = Mock() + self.observer_mock.is_alive.return_value = True + self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock, self.observer_mock) + self.func1_container_mock = Mock() self.func2_container_mock = Mock() self.runtime._containers = { "func_name1": self.func1_container_mock, "func_name2": self.func2_container_mock, } - self.runtime._observer = self.observer_mock - self.runtime._observer.is_alive.return_value = True self.runtime._temp_uncompressed_paths_to_be_cleaned = ["path1", "path2"] @patch("samcli.local.lambdafn.runtime.shutil") @@ -1002,10 +1004,8 @@ class TestWarmLambdaRuntime_on_code_change(TestCase): def setUp(self): self.manager_mock = Mock() lambda_image_mock = Mock() - self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock) - self.observer_mock = Mock() - self.runtime._observer = self.observer_mock + self.runtime = WarmLambdaRuntime(self.manager_mock, lambda_image_mock, self.observer_mock) self.lang = "runtime" self.handler = "handler" From c9088445bacf41f64e9919cd47cb432120f60615 Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Wed, 7 Jun 2023 17:02:36 -0700 Subject: [PATCH 037/107] test: GHA to Execute Test without Docker Running (#5290) * test: Test without Docker running * Add build test * Run install * Remove success condition * Add continue on error * Add continue on error * Separate tests * Fix test name * Require new test * Address comments * Attempt to parameterize for windows * Attempt to parameterize for windows * Attempt to parameterize for windows * Set samdev in environment * Move skip to top of test class --- .github/workflows/build.yml | 44 +++++++++++++- tests/integration/buildcmd/test_build_cmd.py | 62 +++++++++++++++----- 2 files changed, 90 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 280539f094..04c23b8d79 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,12 +27,14 @@ jobs: - make-pr - integration-tests - smoke-and-functional-tests + - docker-disabled steps: - name: report-failure if : | needs.make-pr.result != 'success' || needs.integration-tests.result != 'success' || - needs.smoke-and-functional-tests.result != 'success' + needs.smoke-and-functional-tests.result != 'success' || + needs.docker-disabled.result != 'success' run: exit 1 - name: report-success run: exit 0 @@ -187,3 +189,43 @@ jobs: run: make init - name: Run functional & smoke tests run: pytest -vv -n 4 tests/functional tests/smoke + + docker-disabled: + name: Docker-disabled Tests / ${{ matrix.os }} + if: github.repository_owner == 'aws' + runs-on: ${{ matrix.os }} + env: + SAM_CLI_DEV: "1" + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + # These are the versions of Python that correspond to the supported Lambda runtimes + python-version: | + 3.10 + 3.9 + 3.8 + 3.7 + - name: Init samdev + run: make init + - name: Stop Docker Linux + if: ${{ matrix.os == 'ubuntu-latest' }} + run: sudo systemctl stop docker + - name: Stop Docker Windows + if: ${{ matrix.os == 'windows-latest' }} + shell: pwsh + run: stop-service docker + - name: Check Docker not Running + run: docker info + id: run-docker-info + continue-on-error: true + - name: Report failure + if: steps.run-docker-info.outcome == 'success' + run: exit 1 + - name: Run tests without Docker + run: pytest -vv tests/integration/buildcmd/test_build_cmd.py -k TestBuildCommand_PythonFunctions_WithoutDocker diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index f23b4f4f10..394964c4af 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -398,34 +398,64 @@ def _validate_skipped_built_function( "overrides", "runtime", "codeuri", - "use_container", "check_function_only", "prop", ), [ - ("template.yaml", "Function", True, "python3.7", "Python", False, False, "CodeUri"), - ("template.yaml", "Function", True, "python3.8", "Python", False, False, "CodeUri"), - ("template.yaml", "Function", True, "python3.9", "Python", False, False, "CodeUri"), - ("template.yaml", "Function", True, "python3.10", "Python", False, False, "CodeUri"), - ("template.yaml", "Function", True, "python3.7", "PythonPEP600", False, False, "CodeUri"), - ("template.yaml", "Function", True, "python3.8", "PythonPEP600", False, False, "CodeUri"), - ("template.yaml", "Function", True, "python3.7", "Python", "use_container", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.8", "Python", "use_container", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.9", "Python", "use_container", False, "CodeUri"), - ("template.yaml", "Function", True, "python3.10", "Python", "use_container", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.7", "Python", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.8", "Python", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.9", "Python", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.10", "Python", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.7", "PythonPEP600", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.8", "PythonPEP600", False, "CodeUri"), ], ) -class TestBuildCommand_PythonFunctions(BuildIntegPythonBase): +class TestBuildCommand_PythonFunctions_WithoutDocker(BuildIntegPythonBase): overrides = True runtime = "python3.9" codeuri = "Python" + check_function_only = False use_container = False + + @pytest.mark.flaky(reruns=3) + def test_with_default_requirements(self): + self._test_with_default_requirements( + self.runtime, + self.codeuri, + self.use_container, + self.test_data_path, + do_override=self.overrides, + check_function_only=self.check_function_only, + ) + + +@skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) +@parameterized_class( + ( + "template", + "FUNCTION_LOGICAL_ID", + "overrides", + "runtime", + "codeuri", + "check_function_only", + "prop", + ), + [ + ("template.yaml", "Function", True, "python3.7", "Python", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.8", "Python", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.9", "Python", False, "CodeUri"), + ("template.yaml", "Function", True, "python3.10", "Python", False, "CodeUri"), + ], +) +class TestBuildCommand_PythonFunctions_WithDocker(BuildIntegPythonBase): + overrides = True + runtime = "python3.9" + codeuri = "Python" + use_container = "use_container" check_function_only = False @pytest.mark.flaky(reruns=3) def test_with_default_requirements(self): - if self.use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): - self.skipTest(SKIP_DOCKER_MESSAGE) self._test_with_default_requirements( self.runtime, self.codeuri, @@ -465,7 +495,9 @@ def test_with_default_requirements(self): ), ], ) -class TestBuildCommand_PythonFunctions_CDK(TestBuildCommand_PythonFunctions): +class TestBuildCommand_PythonFunctions_CDK(TestBuildCommand_PythonFunctions_WithoutDocker): + use_container = False + @pytest.mark.flaky(reruns=3) def test_cdk_app_with_default_requirements(self): self._test_with_default_requirements( From 50f6cfae36e1190f96c69805a8aac8cd6c78296f Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Wed, 7 Jun 2023 17:36:10 -0700 Subject: [PATCH 038/107] fix: remove ruby3.2 from preview runtimes (#5296) * fix: remove ruby3.2 from preview runtimes * update {} with set() --- samcli/lib/utils/preview_runtimes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/lib/utils/preview_runtimes.py b/samcli/lib/utils/preview_runtimes.py index 17e15f456e..c17ae95cf8 100644 --- a/samcli/lib/utils/preview_runtimes.py +++ b/samcli/lib/utils/preview_runtimes.py @@ -4,4 +4,4 @@ """ from typing import Set -PREVIEW_RUNTIMES: Set[str] = {"ruby3.2"} +PREVIEW_RUNTIMES: Set[str] = set() From 6cb2a69cc80f3142e1dc7b01f5e4f533de69593c Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Thu, 8 Jun 2023 13:36:32 -0500 Subject: [PATCH 039/107] Fix: Force docker version to match 4.2's default version (#5305) Co-authored-by: Jacob Fuss --- samcli/commands/package/package_context.py | 4 ++-- samcli/lib/build/app_builder.py | 4 ++-- samcli/lib/constants.py | 1 + samcli/lib/package/ecr_uploader.py | 4 ++-- samcli/lib/package/image_utils.py | 4 ++-- samcli/lib/sync/flows/image_function_sync_flow.py | 4 ++-- samcli/lib/utils/file_observer.py | 4 ++-- samcli/lib/utils/system_info.py | 4 ++-- samcli/local/docker/container.py | 4 ++-- samcli/local/docker/lambda_image.py | 4 ++-- samcli/local/docker/manager.py | 4 ++-- 11 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 samcli/lib/constants.py diff --git a/samcli/commands/package/package_context.py b/samcli/commands/package/package_context.py index 48b4b777df..aaeb6635b5 100644 --- a/samcli/commands/package/package_context.py +++ b/samcli/commands/package/package_context.py @@ -22,9 +22,9 @@ import boto3 import click import docker -from docker.constants import DEFAULT_DOCKER_API_VERSION from samcli.commands.package.exceptions import PackageFailedError +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.intrinsic_resolver.intrinsics_symbol_table import IntrinsicsSymbolTable from samcli.lib.package.artifact_exporter import Template from samcli.lib.package.code_signer import CodeSigner @@ -121,7 +121,7 @@ def run(self): ) ecr_client = boto3.client("ecr", config=get_boto_config_with_user_agent(region_name=region_name)) - docker_client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + docker_client = docker.from_env(version=DOCKER_MIN_API_VERSION) s3_uploader = S3Uploader( s3_client, self.s3_bucket, self.s3_prefix, self.kms_key_id, self.force_upload, self.no_progressbar diff --git a/samcli/lib/build/app_builder.py b/samcli/lib/build/app_builder.py index 5dbfcab965..50ddbd74e0 100644 --- a/samcli/lib/build/app_builder.py +++ b/samcli/lib/build/app_builder.py @@ -8,13 +8,13 @@ import pathlib from typing import List, Optional, Dict, cast, NamedTuple import docker -from docker.constants import DEFAULT_DOCKER_API_VERSION import docker.errors from aws_lambda_builders import ( RPC_PROTOCOL_VERSION as lambda_builders_protocol_version, ) from aws_lambda_builders.builder import LambdaBuilder from aws_lambda_builders.exceptions import LambdaBuilderError +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.build.build_graph import FunctionBuildDefinition, LayerBuildDefinition, BuildGraph from samcli.lib.build.build_strategy import ( DefaultBuildStrategy, @@ -157,7 +157,7 @@ def __init__( self._parallel = parallel self._mode = mode self._stream_writer = stream_writer if stream_writer else StreamWriter(stream=osutils.stderr(), auto_flush=True) - self._docker_client = docker_client if docker_client else docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + self._docker_client = docker_client if docker_client else docker.from_env(version=DOCKER_MIN_API_VERSION) self._deprecated_runtimes = DEPRECATED_RUNTIMES self._colored = Colored() diff --git a/samcli/lib/constants.py b/samcli/lib/constants.py new file mode 100644 index 0000000000..ec9dd1d3f5 --- /dev/null +++ b/samcli/lib/constants.py @@ -0,0 +1 @@ +DOCKER_MIN_API_VERSION = "1.35" diff --git a/samcli/lib/package/ecr_uploader.py b/samcli/lib/package/ecr_uploader.py index 4c6e714b81..f2d4371407 100644 --- a/samcli/lib/package/ecr_uploader.py +++ b/samcli/lib/package/ecr_uploader.py @@ -9,7 +9,6 @@ import botocore import click import docker -from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import APIError, BuildError from samcli.commands.package.exceptions import ( @@ -18,6 +17,7 @@ DockerPushFailedError, ECRAuthorizationError, ) +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.docker.log_streamer import LogStreamer, LogStreamError from samcli.lib.package.image_utils import tag_translation from samcli.lib.utils.osutils import stderr @@ -36,7 +36,7 @@ class ECRUploader: def __init__( self, docker_client, ecr_client, ecr_repo, ecr_repo_multi, no_progressbar=False, tag="latest", stream=stderr() ): - self.docker_client = docker_client if docker_client else docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + self.docker_client = docker_client if docker_client else docker.from_env(version=DOCKER_MIN_API_VERSION) self.ecr_client = ecr_client self.ecr_repo = ecr_repo self.ecr_repo_multi = ecr_repo_multi diff --git a/samcli/lib/package/image_utils.py b/samcli/lib/package/image_utils.py index 5fefbfcd53..b5a0a6bf83 100644 --- a/samcli/lib/package/image_utils.py +++ b/samcli/lib/package/image_utils.py @@ -2,10 +2,10 @@ Image artifacts based utilities """ import docker -from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import APIError, NullResource from samcli.commands.package.exceptions import DockerGetLocalImageFailedError +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.package.utils import is_ecr_url SHA_CHECKSUM_TRUNCATION_LENGTH = 12 @@ -36,7 +36,7 @@ def tag_translation(image, docker_image_id=None, gen_tag="latest"): if not docker_image_id: try: - docker_client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + docker_client = docker.from_env(version=DOCKER_MIN_API_VERSION) docker_image_id = docker_client.images.get(image).id except APIError as ex: raise DockerGetLocalImageFailedError(str(ex)) from ex diff --git a/samcli/lib/sync/flows/image_function_sync_flow.py b/samcli/lib/sync/flows/image_function_sync_flow.py index cf9ff34871..f313fa6661 100644 --- a/samcli/lib/sync/flows/image_function_sync_flow.py +++ b/samcli/lib/sync/flows/image_function_sync_flow.py @@ -5,9 +5,9 @@ import docker from docker.client import DockerClient -from docker.constants import DEFAULT_DOCKER_API_VERSION from samcli.lib.build.app_builder import ApplicationBuilder, ApplicationBuildResult +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.package.ecr_uploader import ECRUploader from samcli.lib.providers.provider import Stack from samcli.lib.sync.flows.function_sync_flow import FunctionSyncFlow, wait_for_function_update_complete @@ -70,7 +70,7 @@ def __init__( def _get_docker_client(self) -> DockerClient: """Lazy instantiates and returns the docker client""" if not self._docker_client: - self._docker_client = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + self._docker_client = docker.from_env(version=DOCKER_MIN_API_VERSION) return self._docker_client def _get_ecr_client(self) -> Any: diff --git a/samcli/lib/utils/file_observer.py b/samcli/lib/utils/file_observer.py index 77928007ef..8ccf25cd9a 100644 --- a/samcli/lib/utils/file_observer.py +++ b/samcli/lib/utils/file_observer.py @@ -11,7 +11,6 @@ import docker from docker import DockerClient -from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import ImageNotFound from docker.types import CancellableStream from watchdog.events import FileSystemEvent, FileSystemEventHandler, PatternMatchingEventHandler @@ -19,6 +18,7 @@ from watchdog.observers.api import BaseObserver, ObservedWatch from samcli.cli.global_config import Singleton +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.utils.hash import dir_checksum, file_checksum from samcli.lib.utils.packagetype import IMAGE, ZIP from samcli.local.lambdafn.config import FunctionConfig @@ -258,7 +258,7 @@ def __init__(self, on_change: Callable) -> None: """ self._observed_images: Dict[str, str] = {} self._input_on_change: Callable = on_change - self.docker_client: DockerClient = docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + self.docker_client: DockerClient = docker.from_env(version=DOCKER_MIN_API_VERSION) self.events: CancellableStream = self.docker_client.events(filters={"type": "image"}, decode=True) self._images_observer_thread: Optional[Thread] = None self._lock: Lock = threading.Lock() diff --git a/samcli/lib/utils/system_info.py b/samcli/lib/utils/system_info.py index 03c6173b45..4ecd0f56b7 100644 --- a/samcli/lib/utils/system_info.py +++ b/samcli/lib/utils/system_info.py @@ -53,11 +53,11 @@ def _gather_docker_info() -> str: import contextlib import docker - from docker.constants import DEFAULT_DOCKER_API_VERSION + from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.local.docker.utils import is_docker_reachable - with contextlib.closing(docker.from_env(version=DEFAULT_DOCKER_API_VERSION)) as client: + with contextlib.closing(docker.from_env(version=DOCKER_MIN_API_VERSION)) as client: if is_docker_reachable(client): return cast(str, client.version().get("Version", "Not available")) return "Not available" diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py index a6205c8e26..e70f7c2a1f 100644 --- a/samcli/local/docker/container.py +++ b/samcli/local/docker/container.py @@ -13,9 +13,9 @@ import docker import requests -from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import NotFound as DockerNetworkNotFound +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.utils.retry import retry from samcli.lib.utils.tar import extract_tarfile from samcli.local.docker.effective_user import ROOT_USER_ID, EffectiveUser @@ -112,7 +112,7 @@ def __init__( self._logs_thread = None # Use the given Docker client or create new one - self.docker_client = docker_client or docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + self.docker_client = docker_client or docker.from_env(version=DOCKER_MIN_API_VERSION) # Runtime properties of the container. They won't have value until container is created or started self.id = None diff --git a/samcli/local/docker/lambda_image.py b/samcli/local/docker/lambda_image.py index 14ba00d06a..285e1b81e7 100644 --- a/samcli/local/docker/lambda_image.py +++ b/samcli/local/docker/lambda_image.py @@ -12,13 +12,13 @@ from typing import Optional import docker -from docker.constants import DEFAULT_DOCKER_API_VERSION from samcli.commands.local.cli_common.user_exceptions import ( DockerDistributionAPIError, ImageBuildException, ) from samcli.commands.local.lib.exceptions import InvalidIntermediateImageError +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.utils.architecture import has_runtime_multi_arch_image from samcli.lib.utils.packagetype import IMAGE, ZIP from samcli.lib.utils.stream_writer import StreamWriter @@ -124,7 +124,7 @@ def __init__(self, layer_downloader, skip_pull_image, force_image_build, docker_ self.layer_downloader = layer_downloader self.skip_pull_image = skip_pull_image self.force_image_build = force_image_build - self.docker_client = docker_client or docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + self.docker_client = docker_client or docker.from_env(version=DOCKER_MIN_API_VERSION) self.invoke_images = invoke_images def build(self, runtime, packagetype, image, layers, architecture, stream=None, function_name=None): diff --git a/samcli/local/docker/manager.py b/samcli/local/docker/manager.py index 45db09bce7..a035003bb0 100644 --- a/samcli/local/docker/manager.py +++ b/samcli/local/docker/manager.py @@ -7,8 +7,8 @@ import threading import docker -from docker.constants import DEFAULT_DOCKER_API_VERSION +from samcli.lib.constants import DOCKER_MIN_API_VERSION from samcli.lib.utils.stream_writer import StreamWriter from samcli.local.docker import utils from samcli.local.docker.container import Container @@ -36,7 +36,7 @@ def __init__(self, docker_network_id=None, docker_client=None, skip_pull_image=F self.skip_pull_image = skip_pull_image self.docker_network_id = docker_network_id - self.docker_client = docker_client or docker.from_env(version=DEFAULT_DOCKER_API_VERSION) + self.docker_client = docker_client or docker.from_env(version=DOCKER_MIN_API_VERSION) self.do_shutdown_event = do_shutdown_event self._lock = threading.Lock() From 8111efc726a2e22499513c26ea17a27b1c4b4d54 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Thu, 8 Jun 2023 13:03:31 -0700 Subject: [PATCH 040/107] chore: cleanup appveyor definitions for not running jobs which is already run with GHA & add docker info/version commands (#5306) * chore: remove redundant tests and setup from appveyor definitions * add/update docker info and docker version commands * add 3.11 and macos to GHAs * add some explanations to Windows section --- .github/workflows/build.yml | 5 ++- appveyor-ubuntu.yml | 53 +++--------------------------- appveyor-windows.yml | 65 +++++-------------------------------- 3 files changed, 17 insertions(+), 106 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 04c23b8d79..03e29508fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,13 +47,14 @@ jobs: fail-fast: false matrix: os: - # TODO: Add macos-latest; fails currently, see e.g. https://github.com/aws/aws-sam-cli/actions/runs/3596883449/jobs/6058055981 + - macos-latest - ubuntu-latest - windows-latest python: - "3.7" - "3.8" - "3.9" + - "3.11" steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 @@ -114,6 +115,7 @@ jobs: 3.8 3.9 3.10 + 3.11 ${{ matrix.python }} - uses: actions/setup-go@v4 with: @@ -180,6 +182,7 @@ jobs: - "3.7" - "3.8" - "3.9" + - "3.11" steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 diff --git a/appveyor-ubuntu.yml b/appveyor-ubuntu.yml index 71e91691d1..f5b0c8b384 100644 --- a/appveyor-ubuntu.yml +++ b/appveyor-ubuntu.yml @@ -61,7 +61,8 @@ install: - sh: "source ${HOME}/venv${PYTHON_VERSION}/bin/activate" - sh: "rvm use 2.7.2" - - sh: "docker --version" + - sh: "docker info" + - sh: "docker version" - sh: "nvm install ${NODE_VERSION}" - sh: "npm install npm@7.24.2 -g" - sh: "npm -v" @@ -149,7 +150,8 @@ install: build_script: - - "python -c \"import sys; print(sys.executable)\"" + - "python -c \"import sys; print(sys.executable)\"" + - "make init" # Final clean up no matter success or failure on_finish: @@ -176,10 +178,6 @@ for: - configuration: BuildIntegTesting test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - - "pip install -e \".[dev]\"" - sh: "pytest -vv tests/integration/buildcmd --ignore=tests/integration/buildcmd/test_build_terraform_applications.py --ignore=tests/integration/buildcmd/test_build_terraform_applications_other_cases.py --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # Set JAVA_HOME to java11 @@ -193,10 +191,6 @@ for: - configuration: LocalZipTerraformBuildIntegTesting test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - - "pip install -e \".[dev]\"" - sh: "pytest -vv -n 4 tests/integration/buildcmd/test_build_terraform_applications.py::TestBuildTerraformApplicationsWithZipBasedLambdaFunctionAndLocalBackend_0 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # Local ZIP Terraform Build In Container integ testing @@ -206,10 +200,6 @@ for: - configuration: LocalZipTerraformBuildInContainerIntegTesting test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - - "pip install -e \".[dev]\"" - sh: "pytest -vv -n 4 tests/integration/buildcmd/test_build_terraform_applications.py::TestBuildTerraformApplicationsWithZipBasedLambdaFunctionAndLocalBackend_1 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # S3 ZIP Terraform Build integ testing @@ -219,10 +209,6 @@ for: - configuration: S3ZipTerraformBuildIntegTesting test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - - "pip install -e \".[dev]\"" - sh: "pytest -vv -n 4 tests/integration/buildcmd/test_build_terraform_applications.py::TestBuildTerraformApplicationsWithZipBasedLambdaFunctionAndS3Backend_0 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # S3 ZIP Terraform Build In Container integ testing @@ -232,10 +218,6 @@ for: - configuration: S3ZipTerraformBuildInContainerIntegTesting test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - - "pip install -e \".[dev]\"" - sh: "pytest -vv -n 4 tests/integration/buildcmd/test_build_terraform_applications.py::TestBuildTerraformApplicationsWithZipBasedLambdaFunctionAndS3Backend_1 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # Other Terraform Build In Container integ testing @@ -245,10 +227,6 @@ for: - configuration: OtherTerraformBuildIntegTesting test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - - "pip install -e \".[dev]\"" - sh: "pytest -vv -n 4 tests/integration/buildcmd/test_build_terraform_applications_other_cases.py --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # Integ testing deploy @@ -258,7 +236,6 @@ for: - configuration: DeployIntegTesting test_script: - - "pip install -e \".[dev]\"" - sh: "pytest -vv tests/integration/deploy -n 4 --reruns 4 --dist=loadgroup --json-report --json-report-file=TEST_REPORT-integration-deploy.json" # Integ testing package @@ -268,7 +245,6 @@ for: - configuration: PackageIntegTesting test_script: - - "pip install -e \".[dev]\"" - sh: "pytest -vv tests/integration/package -n 4 --reruns 4 --json-report --json-report-file=TEST_REPORT-integration-package.json" # Integ testing delete @@ -278,7 +254,6 @@ for: - configuration: DeleteIntegTesting test_script: - - "pip install -e \".[dev]\"" - sh: "pytest -vv tests/integration/delete -n 4 --reruns 4 --json-report --json-report-file=TEST_REPORT-integration-delete.json" # Integ testing sync @@ -288,7 +263,6 @@ for: - configuration: SyncIntegTesting test_script: - - "pip install -e \".[dev]\"" - sh: "pytest -vv tests/integration/sync -n 3 --reruns 3 --dist loadscope --json-report --json-report-file=TEST_REPORT-integration-sync.json" # Integ testing local @@ -298,11 +272,6 @@ for: - configuration: LocalIntegTesting test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - "ruff samcli" - - - "pip install -e \".[dev]\"" - sh: "pytest -vv tests/integration/local --json-report --json-report-file=TEST_REPORT-integration-local.json" # End-to-end testing @@ -312,7 +281,6 @@ for: - configuration: EndToEndTesting test_script: - - "pip install -e \".[dev]\"" - sh: "pytest -vv -n 4 --reruns 5 --dist loadscope tests/end_to_end --json-report --json-report-file=TEST_REPORT-end-to-end.json" # Other testing @@ -322,18 +290,5 @@ for: - configuration: OtherTesting test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - "ruff samcli" - - # Dev Tests - - "pip install -e \".[dev]\"" - - "pytest --cov samcli --cov-report term-missing --cov-fail-under 94 tests/unit --json-report --json-report-file=TEST_REPORT-unit.json" - - "ruff samcli" - - "mypy setup.py samcli tests" - - "pytest -n 4 tests/functional --json-report --json-report-file=TEST_REPORT-functional.json" - - sh: "pytest -vv -n 4 --reruns 4 --dist loadgroup tests/integration --ignore=tests/integration/buildcmd --ignore=tests/integration/delete --ignore=tests/integration/deploy --ignore=tests/integration/package --ignore=tests/integration/sync --ignore=tests/integration/local --json-report --json-report-file=TEST_REPORT-integration-others.json" - sh: "pytest -vv tests/regression --json-report --json-report-file=TEST_REPORT-regression.json" - - sh: "black --check setup.py tests samcli" - - sh: "pytest -n 4 -vv tests/smoke --json-report --json-report-file=TEST_REPORT-smoke.json" diff --git a/appveyor-windows.yml b/appveyor-windows.yml index 9d120f2503..886e65e0c4 100644 --- a/appveyor-windows.yml +++ b/appveyor-windows.yml @@ -74,6 +74,7 @@ install: - "echo %PATH%" - "python --version" - "docker info" + - "docker version" # install Terraform CLI - "choco install terraform" @@ -135,6 +136,13 @@ install: - "IF DEFINED BY_CANARY ECHO Logging in Public ECR && aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws" + # claim some disk space before starting the tests + - "docker system prune -a -f" + # activate virtual environment + - "venv\\Scripts\\activate" + + + # Final clean up no matter success or failure on_finish: # Upload test reports as artifacts @@ -181,10 +189,6 @@ for: - cargo lambda -V test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv tests/integration/buildcmd --ignore tests/integration/buildcmd/test_build_terraform_applications.py --ignore=tests/integration/buildcmd/test_build_terraform_applications_other_cases.py --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # Local ZIP Terraform Build integ testing @@ -193,10 +197,6 @@ for: - configuration: LocalZipTerraformBuildIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv -n 4 tests/integration/buildcmd/test_build_terraform_applications.py::TestBuildTerraformApplicationsWithZipBasedLambdaFunctionAndLocalBackend_0 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # Local ZIP Terraform Build In Container integ testing @@ -205,10 +205,6 @@ for: - configuration: LocalZipTerraformBuildInContainerIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv tests/integration/buildcmd/test_build_terraform_applications.py::TestBuildTerraformApplicationsWithZipBasedLambdaFunctionAndLocalBackend_1 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # S3 ZIP Terraform Build integ testing @@ -217,10 +213,6 @@ for: - configuration: S3ZipTerraformBuildIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv -n 4 tests/integration/buildcmd/test_build_terraform_applications.py::TestBuildTerraformApplicationsWithZipBasedLambdaFunctionAndS3Backend_0 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # S3 ZIP Terraform Build In Container integ testing @@ -229,10 +221,6 @@ for: - configuration: S3ZipTerraformBuildInContainerIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv tests/integration/buildcmd/test_build_terraform_applications.py::TestBuildTerraformApplicationsWithZipBasedLambdaFunctionAndS3Backend_1 --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" # Other Terraform Build integ testing @@ -241,10 +229,6 @@ for: - configuration: OtherTerraformBuildIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv -n 4 tests/integration/buildcmd/test_build_terraform_applications_other_cases.py --json-report --json-report-file=TEST_REPORT-integration-buildcmd.json" #Integ testing deploy @@ -253,10 +237,6 @@ for: - configuration: DeployIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv tests/integration/deploy -n 4 --reruns 4 --dist=loadgroup --json-report --json-report-file=TEST_REPORT-integration-deploy.json" # Integ testing package @@ -265,10 +245,6 @@ for: - configuration: PackageIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv tests/integration/package -n 4 --reruns 4 --json-report --json-report-file=TEST_REPORT-integration-package.json" # Integ testing delete @@ -277,10 +253,6 @@ for: - configuration: DeleteIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv tests/integration/delete -n 4 --reruns 4 --json-report --json-report-file=TEST_REPORT-integration-delete.json" # Integ testing sync @@ -289,10 +261,6 @@ for: - configuration: SyncIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv tests/integration/sync -n 3 --reruns 3 --dist loadscope --json-report --json-report-file=TEST_REPORT-integration-sync.json" #Integ testing local @@ -301,10 +269,6 @@ for: - configuration: LocalIntegTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv tests/integration/local --json-report --json-report-file=TEST_REPORT-integration-local.json" # End-to-end testing @@ -313,10 +277,6 @@ for: - configuration: EndToEndTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - ps: "pytest -vv -n 4 --reruns 5 --dist loadscope tests/end_to_end --json-report --json-report-file=TEST_REPORT-end-to-end.json" #Other testing @@ -325,16 +285,9 @@ for: - configuration: OtherTesting test_script: - # Reactivate virtualenv before running tests - - "git --version" - - "venv\\Scripts\\activate" - - "docker system prune -a -f" - - ps: "pytest --cov samcli --cov-report term-missing --cov-fail-under 94 tests/unit --json-report --json-report-file=TEST_REPORT-unit.json" - - "mypy setup.py samcli tests" - - ps: "pytest -n 4 tests/functional --json-report --json-report-file=TEST_REPORT-functional.json" - ps: "pytest -vv -n 4 --reruns 4 --dist loadgroup tests/integration --ignore=tests/integration/buildcmd --ignore=tests/integration/delete --ignore=tests/integration/deploy --ignore=tests/integration/package --ignore=tests/integration/sync --ignore=tests/integration/local --json-report --json-report-file=TEST_REPORT-integration-others.json" - ps: "pytest -vv tests/regression --json-report --json-report-file=TEST_REPORT-regression.json" # Uncomment for RDP # on_finish: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) - \ No newline at end of file + From 60f1882a634a610262108fb6946f64db50a29eab Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Fri, 9 Jun 2023 11:26:03 -0700 Subject: [PATCH 041/107] fix: Fix failing tests on Python3.11 (#5317) --- .../local/invoke/runtimes/test_with_runtime_zips.py | 2 +- tests/integration/sync/test_sync_infra.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/local/invoke/runtimes/test_with_runtime_zips.py b/tests/integration/local/invoke/runtimes/test_with_runtime_zips.py index d455da4ba1..751474b64b 100644 --- a/tests/integration/local/invoke/runtimes/test_with_runtime_zips.py +++ b/tests/integration/local/invoke/runtimes/test_with_runtime_zips.py @@ -33,8 +33,8 @@ def setUp(self): def tearDown(self): os.remove(self.events_file_path) - @pytest.mark.timeout(timeout=300, method="thread") @parameterized.expand([param("Go1xFunction"), param("Java8Function")]) + @pytest.mark.timeout(timeout=300, method="thread") def test_runtime_zip(self, function_name): command_list = InvokeIntegBase.get_command_list( function_name, template_path=self.template_path, event_path=self.events_file_path diff --git a/tests/integration/sync/test_sync_infra.py b/tests/integration/sync/test_sync_infra.py index 4f0b54cb74..53bf5fb763 100644 --- a/tests/integration/sync/test_sync_infra.py +++ b/tests/integration/sync/test_sync_infra.py @@ -69,8 +69,8 @@ def _verify_infra_changes(self, resources): IS_WINDOWS, "Skip sync ruby tests in windows", ) - @pytest.mark.flaky(reruns=3) @parameterized.expand([["ruby", False], ["python", False], ["python", True]]) + @pytest.mark.flaky(reruns=3) def test_sync_infra(self, runtime, use_container): template_before = f"infra/template-{runtime}-before.yaml" template_path = str(self.test_data_path.joinpath(template_before)) @@ -147,8 +147,8 @@ def test_sync_infra(self, runtime, use_container): else: self._verify_infra_changes(self.stack_resources) - @pytest.mark.flaky(reruns=3) @parameterized.expand([["python", False], ["python", True]]) + @pytest.mark.flaky(reruns=3) def test_sync_infra_auto_skip(self, runtime, use_container): template_before = f"infra/template-{runtime}-before.yaml" template_path = str(self.test_data_path.joinpath(template_before)) @@ -208,8 +208,8 @@ def test_sync_infra_auto_skip(self, runtime, use_container): # Lambda Api call here, which tests both the python function and the layer self._verify_infra_changes(self.stack_resources) - @pytest.mark.flaky(reruns=3) @parameterized.expand([["python", False], ["python", True]]) + @pytest.mark.flaky(reruns=3) def test_sync_infra_auto_skip_nested(self, runtime, use_container): template_before = str(Path("infra", "parent-stack.yaml")) template_path = str(self.test_data_path.joinpath(template_before)) From bdc8198fb4fde9f0ee7892cf0d338f59b8d1218f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Jun 2023 21:37:55 +0000 Subject: [PATCH 042/107] chore(deps): bump cryptography from 39.0.2 to 41.0.0 in /requirements (#5251) * chore(deps): bump cryptography from 39.0.2 to 41.0.0 in /requirements Bumps [cryptography](https://github.com/pyca/cryptography) from 39.0.2 to 41.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/39.0.2...41.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump pyopenssl version to support newer cryptography lib --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> --- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 50 +++++++++++++---------------- requirements/reproducible-mac.txt | 50 +++++++++++++---------------- 3 files changed, 47 insertions(+), 55 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 84eb44da14..33572b1d3d 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -17,7 +17,7 @@ aws_lambda_builders==1.33.0 tomlkit==0.11.8 watchdog==2.1.2 rich~=13.3.3 -pyopenssl~=23.0.0 +pyopenssl~=23.2.0 # Needed for supporting Protocol in Python 3.7, Protocol class became public with python3.8 typing_extensions~=4.4.0 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index e8faac8822..2383e49d2f 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -212,30 +212,26 @@ cookiecutter==2.1.1 \ --hash=sha256:9f3ab027cec4f70916e28f03470bdb41e637a3ad354b4d65c765d93aad160022 \ --hash=sha256:f3982be8d9c53dac1261864013fdec7f83afd2e42ede6f6dd069c5e149c540d5 # via aws-sam-cli (setup.py) -cryptography==39.0.2 \ - --hash=sha256:103e8f7155f3ce2ffa0049fe60169878d47a4364b277906386f8de21c9234aa1 \ - --hash=sha256:23df8ca3f24699167daf3e23e51f7ba7334d504af63a94af468f468b975b7dd7 \ - --hash=sha256:2725672bb53bb92dc7b4150d233cd4b8c59615cd8288d495eaa86db00d4e5c06 \ - --hash=sha256:30b1d1bfd00f6fc80d11300a29f1d8ab2b8d9febb6ed4a38a76880ec564fae84 \ - --hash=sha256:35d658536b0a4117c885728d1a7032bdc9a5974722ae298d6c533755a6ee3915 \ - --hash=sha256:50cadb9b2f961757e712a9737ef33d89b8190c3ea34d0fb6675e00edbe35d074 \ - --hash=sha256:5f8c682e736513db7d04349b4f6693690170f95aac449c56f97415c6980edef5 \ - --hash=sha256:6236a9610c912b129610eb1a274bdc1350b5df834d124fa84729ebeaf7da42c3 \ - --hash=sha256:788b3921d763ee35dfdb04248d0e3de11e3ca8eb22e2e48fef880c42e1f3c8f9 \ - --hash=sha256:8bc0008ef798231fac03fe7d26e82d601d15bd16f3afaad1c6113771566570f3 \ - --hash=sha256:8f35c17bd4faed2bc7797d2a66cbb4f986242ce2e30340ab832e5d99ae60e011 \ - --hash=sha256:b49a88ff802e1993b7f749b1eeb31134f03c8d5c956e3c125c75558955cda536 \ - --hash=sha256:bc0521cce2c1d541634b19f3ac661d7a64f9555135e9d8af3980965be717fd4a \ - --hash=sha256:bc5b871e977c8ee5a1bbc42fa8d19bcc08baf0c51cbf1586b0e87a2694dde42f \ - --hash=sha256:c43ac224aabcbf83a947eeb8b17eaf1547bce3767ee2d70093b461f31729a480 \ - --hash=sha256:d15809e0dbdad486f4ad0979753518f47980020b7a34e9fc56e8be4f60702fac \ - --hash=sha256:d7d84a512a59f4412ca8549b01f94be4161c94efc598bf09d027d67826beddc0 \ - --hash=sha256:e029b844c21116564b8b61216befabca4b500e6816fa9f0ba49527653cae2108 \ - --hash=sha256:e8a0772016feeb106efd28d4a328e77dc2edae84dfbac06061319fdb669ff828 \ - --hash=sha256:e944fe07b6f229f4c1a06a7ef906a19652bdd9fd54c761b0ff87e83ae7a30354 \ - --hash=sha256:eb40fe69cfc6f5cdab9a5ebd022131ba21453cf7b8a7fd3631f45bbf52bed612 \ - --hash=sha256:fa507318e427169ade4e9eccef39e9011cdc19534f55ca2f36ec3f388c1f70f3 \ - --hash=sha256:ffd394c7896ed7821a6d13b24657c6a34b6e2650bd84ae063cf11ccffa4f1a97 +cryptography==41.0.0 \ + --hash=sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55 \ + --hash=sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895 \ + --hash=sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be \ + --hash=sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928 \ + --hash=sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d \ + --hash=sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8 \ + --hash=sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237 \ + --hash=sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9 \ + --hash=sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78 \ + --hash=sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d \ + --hash=sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0 \ + --hash=sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46 \ + --hash=sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5 \ + --hash=sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4 \ + --hash=sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d \ + --hash=sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75 \ + --hash=sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb \ + --hash=sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2 \ + --hash=sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be # via pyopenssl dateparser==1.1.8 \ --hash=sha256:070b29b5bbf4b1ec2cd51c96ea040dc68a614de703910a91ad1abba18f9f379f \ @@ -427,9 +423,9 @@ pygments==2.15.1 \ --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ --hash=sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1 # via rich -pyopenssl==23.0.0 \ - --hash=sha256:c1cc5f86bcacefc84dada7d31175cae1b1518d5f60d3d0bb595a67822a868a6f \ - --hash=sha256:df5fc28af899e74e19fccb5510df423581047e10ab6f1f4ba1763ff5fde844c0 +pyopenssl==23.2.0 \ + --hash=sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2 \ + --hash=sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac # via aws-sam-cli (setup.py) pyrsistent==0.19.3 \ --hash=sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8 \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index ee5072b94d..d40e5a8048 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -230,30 +230,26 @@ cookiecutter==2.1.1 \ --hash=sha256:9f3ab027cec4f70916e28f03470bdb41e637a3ad354b4d65c765d93aad160022 \ --hash=sha256:f3982be8d9c53dac1261864013fdec7f83afd2e42ede6f6dd069c5e149c540d5 # via aws-sam-cli (setup.py) -cryptography==39.0.2 \ - --hash=sha256:103e8f7155f3ce2ffa0049fe60169878d47a4364b277906386f8de21c9234aa1 \ - --hash=sha256:23df8ca3f24699167daf3e23e51f7ba7334d504af63a94af468f468b975b7dd7 \ - --hash=sha256:2725672bb53bb92dc7b4150d233cd4b8c59615cd8288d495eaa86db00d4e5c06 \ - --hash=sha256:30b1d1bfd00f6fc80d11300a29f1d8ab2b8d9febb6ed4a38a76880ec564fae84 \ - --hash=sha256:35d658536b0a4117c885728d1a7032bdc9a5974722ae298d6c533755a6ee3915 \ - --hash=sha256:50cadb9b2f961757e712a9737ef33d89b8190c3ea34d0fb6675e00edbe35d074 \ - --hash=sha256:5f8c682e736513db7d04349b4f6693690170f95aac449c56f97415c6980edef5 \ - --hash=sha256:6236a9610c912b129610eb1a274bdc1350b5df834d124fa84729ebeaf7da42c3 \ - --hash=sha256:788b3921d763ee35dfdb04248d0e3de11e3ca8eb22e2e48fef880c42e1f3c8f9 \ - --hash=sha256:8bc0008ef798231fac03fe7d26e82d601d15bd16f3afaad1c6113771566570f3 \ - --hash=sha256:8f35c17bd4faed2bc7797d2a66cbb4f986242ce2e30340ab832e5d99ae60e011 \ - --hash=sha256:b49a88ff802e1993b7f749b1eeb31134f03c8d5c956e3c125c75558955cda536 \ - --hash=sha256:bc0521cce2c1d541634b19f3ac661d7a64f9555135e9d8af3980965be717fd4a \ - --hash=sha256:bc5b871e977c8ee5a1bbc42fa8d19bcc08baf0c51cbf1586b0e87a2694dde42f \ - --hash=sha256:c43ac224aabcbf83a947eeb8b17eaf1547bce3767ee2d70093b461f31729a480 \ - --hash=sha256:d15809e0dbdad486f4ad0979753518f47980020b7a34e9fc56e8be4f60702fac \ - --hash=sha256:d7d84a512a59f4412ca8549b01f94be4161c94efc598bf09d027d67826beddc0 \ - --hash=sha256:e029b844c21116564b8b61216befabca4b500e6816fa9f0ba49527653cae2108 \ - --hash=sha256:e8a0772016feeb106efd28d4a328e77dc2edae84dfbac06061319fdb669ff828 \ - --hash=sha256:e944fe07b6f229f4c1a06a7ef906a19652bdd9fd54c761b0ff87e83ae7a30354 \ - --hash=sha256:eb40fe69cfc6f5cdab9a5ebd022131ba21453cf7b8a7fd3631f45bbf52bed612 \ - --hash=sha256:fa507318e427169ade4e9eccef39e9011cdc19534f55ca2f36ec3f388c1f70f3 \ - --hash=sha256:ffd394c7896ed7821a6d13b24657c6a34b6e2650bd84ae063cf11ccffa4f1a97 +cryptography==41.0.0 \ + --hash=sha256:0ddaee209d1cf1f180f1efa338a68c4621154de0afaef92b89486f5f96047c55 \ + --hash=sha256:14754bcdae909d66ff24b7b5f166d69340ccc6cb15731670435efd5719294895 \ + --hash=sha256:344c6de9f8bda3c425b3a41b319522ba3208551b70c2ae00099c205f0d9fd3be \ + --hash=sha256:34d405ea69a8b34566ba3dfb0521379b210ea5d560fafedf9f800a9a94a41928 \ + --hash=sha256:3680248309d340fda9611498a5319b0193a8dbdb73586a1acf8109d06f25b92d \ + --hash=sha256:3c5ef25d060c80d6d9f7f9892e1d41bb1c79b78ce74805b8cb4aa373cb7d5ec8 \ + --hash=sha256:4ab14d567f7bbe7f1cdff1c53d5324ed4d3fc8bd17c481b395db224fb405c237 \ + --hash=sha256:5c1f7293c31ebc72163a9a0df246f890d65f66b4a40d9ec80081969ba8c78cc9 \ + --hash=sha256:6b71f64beeea341c9b4f963b48ee3b62d62d57ba93eb120e1196b31dc1025e78 \ + --hash=sha256:7d92f0248d38faa411d17f4107fc0bce0c42cae0b0ba5415505df72d751bf62d \ + --hash=sha256:8362565b3835ceacf4dc8f3b56471a2289cf51ac80946f9087e66dc283a810e0 \ + --hash=sha256:84a165379cb9d411d58ed739e4af3396e544eac190805a54ba2e0322feb55c46 \ + --hash=sha256:88ff107f211ea696455ea8d911389f6d2b276aabf3231bf72c8853d22db755c5 \ + --hash=sha256:9f65e842cb02550fac96536edb1d17f24c0a338fd84eaf582be25926e993dde4 \ + --hash=sha256:a4fc68d1c5b951cfb72dfd54702afdbbf0fb7acdc9b7dc4301bbf2225a27714d \ + --hash=sha256:b7f2f5c525a642cecad24ee8670443ba27ac1fab81bba4cc24c7b6b41f2d0c75 \ + --hash=sha256:b846d59a8d5a9ba87e2c3d757ca019fa576793e8758174d3868aecb88d6fc8eb \ + --hash=sha256:bf8fc66012ca857d62f6a347007e166ed59c0bc150cefa49f28376ebe7d992a2 \ + --hash=sha256:f5d0bf9b252f30a31664b6f64432b4730bb7038339bd18b1fafe129cfc2be9be # via pyopenssl dateparser==1.1.8 \ --hash=sha256:070b29b5bbf4b1ec2cd51c96ea040dc68a614de703910a91ad1abba18f9f379f \ @@ -462,9 +458,9 @@ pygments==2.15.1 \ --hash=sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c \ --hash=sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1 # via rich -pyopenssl==23.0.0 \ - --hash=sha256:c1cc5f86bcacefc84dada7d31175cae1b1518d5f60d3d0bb595a67822a868a6f \ - --hash=sha256:df5fc28af899e74e19fccb5510df423581047e10ab6f1f4ba1763ff5fde844c0 +pyopenssl==23.2.0 \ + --hash=sha256:24f0dc5227396b3e831f4c7f602b950a5e9833d292c8e4a2e06b709292806ae2 \ + --hash=sha256:276f931f55a452e7dea69c7173e984eb2a4407ce413c918aa34b55f82f9b8bac # via aws-sam-cli (setup.py) pyrsistent==0.19.3 \ --hash=sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8 \ From 5be690c88d580cfeee7731f549c75ed7543f47c5 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Fri, 9 Jun 2023 15:24:10 -0700 Subject: [PATCH 043/107] add sleep between close and reopen (#5320) --- .github/workflows/automated-updates-to-sam-cli.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/automated-updates-to-sam-cli.yml b/.github/workflows/automated-updates-to-sam-cli.yml index 9fd9c846da..4aadea2967 100644 --- a/.github/workflows/automated-updates-to-sam-cli.yml +++ b/.github/workflows/automated-updates-to-sam-cli.yml @@ -50,6 +50,7 @@ jobs: git push --force origin update_app_templates_hash gh pr list --repo aws/aws-sam-cli --head update_app_templates_hash --json id --jq length | grep 1 && \ gh pr close update_app_templates_hash --repo aws/aws-sam-cli && \ + sleep 2 && \ gh pr reopen update_app_templates_hash --repo aws/aws-sam-cli && \ exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit gh pr create --base develop --head update_app_templates_hash --title "feat: update SAM CLI with latest App Templates commit hash" --body "This PR & commit is automatically created from App Templates repo to update the SAM CLI with latest hash of the App Templates." --label "pr/internal" @@ -110,6 +111,7 @@ jobs: git push --force origin update_sam_transform_version gh pr list --repo aws/aws-sam-cli --head update_sam_transform_version --json id --jq length | grep 1 && \ gh pr close update_sam_transform_version --repo aws/aws-sam-cli && \ + sleep 2 && \ gh pr reopen update_sam_transform_version --repo aws/aws-sam-cli && \ exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit gh pr create --base develop --head update_sam_transform_version --fill --label "pr/internal" @@ -169,6 +171,7 @@ jobs: git push --force origin update_lambda_builders_version gh pr list --repo aws/aws-sam-cli --head update_lambda_builders_version --json id --jq length | grep 1 && \ gh pr close update_lambda_builders_version --repo aws/aws-sam-cli && \ + sleep 2 && \ gh pr reopen update_lambda_builders_version --repo aws/aws-sam-cli && \ exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit gh pr create --base develop --head update_lambda_builders_version --fill --label "pr/internal" From 1d9ad5a88584744e6d5e45f5331f8f9aed692776 Mon Sep 17 00:00:00 2001 From: Slava Senchenko Date: Fri, 9 Jun 2023 16:13:13 -0700 Subject: [PATCH 044/107] GraphQLApi support for `sam deploy` (#5294) * GraphQLApi support for `sam deploy` * unit tests and format fixes --- samcli/commands/_utils/template.py | 14 ++ samcli/lib/package/packageable_resources.py | 40 +---- samcli/lib/utils/graphql_api.py | 41 +++++ samcli/lib/utils/resources.py | 4 +- tests/unit/commands/_utils/test_template.py | 157 +++++++++++++------- tests/unit/lib/utils/test_graphql_api.py | 47 ++++++ 6 files changed, 211 insertions(+), 92 deletions(-) create mode 100644 samcli/lib/utils/graphql_api.py create mode 100644 tests/unit/lib/utils/test_graphql_api.py diff --git a/samcli/commands/_utils/template.py b/samcli/commands/_utils/template.py index 4e085e7e21..8a45c2e0d3 100644 --- a/samcli/commands/_utils/template.py +++ b/samcli/commands/_utils/template.py @@ -11,10 +11,12 @@ from samcli.commands.exceptions import UserException from samcli.lib.samlib.resource_metadata_normalizer import ASSET_PATH_METADATA_KEY, ResourceMetadataNormalizer +from samcli.lib.utils import graphql_api from samcli.lib.utils.packagetype import IMAGE, ZIP from samcli.lib.utils.resources import ( AWS_LAMBDA_FUNCTION, AWS_SERVERLESS_FUNCTION, + AWS_SERVERLESS_GRAPHQLAPI, METADATA_WITH_LOCAL_PATHS, RESOURCES_WITH_LOCAL_PATHS, get_packageable_resource_paths, @@ -161,6 +163,18 @@ def _update_relative_paths(template_dict, original_root, new_root): ): continue + # SAM GraphQLApi has many instances of CODE_ARTIFACT_PROPERTY and all of them must be updated + if resource_type == AWS_SERVERLESS_GRAPHQLAPI and path_prop_name == graphql_api.CODE_ARTIFACT_PROPERTY: + # to be able to set different nested properties to S3 uri, paths are necessary + # jmespath doesn't provide that functionality, thus custom implementation + paths_values = graphql_api.find_all_paths_and_values(path_prop_name, properties) + for property_path, property_value in paths_values: + updated_path = _resolve_relative_to(property_value, original_root, new_root) + if not updated_path: + # This path does not need to get updated + continue + set_value_from_jmespath(properties, property_path, updated_path) + path = jmespath.search(path_prop_name, properties) updated_path = _resolve_relative_to(path, original_root, new_root) diff --git a/samcli/lib/package/packageable_resources.py b/samcli/lib/package/packageable_resources.py index a52ff32fed..79458dc5bc 100644 --- a/samcli/lib/package/packageable_resources.py +++ b/samcli/lib/package/packageable_resources.py @@ -4,7 +4,7 @@ import logging import os import shutil -from typing import Any, Dict, List, Optional, Tuple, Union, cast +from typing import Dict, Optional, Union, cast import jmespath from botocore.utils import set_value_from_jmespath @@ -24,6 +24,7 @@ upload_local_artifacts, upload_local_image_artifacts, ) +from samcli.lib.utils import graphql_api from samcli.lib.utils.packagetype import IMAGE, ZIP from samcli.lib.utils.resources import ( AWS_APIGATEWAY_RESTAPI, @@ -599,7 +600,7 @@ def get_property_value(self, resource_dict): class GraphQLApiSchemaResource(ResourceZip): RESOURCE_TYPE = AWS_SERVERLESS_GRAPHQLAPI - PROPERTY_NAME = RESOURCES_WITH_LOCAL_PATHS[RESOURCE_TYPE][0] + PROPERTY_NAME = graphql_api.SCHEMA_ARTIFACT_PROPERTY # Don't package the directory if SchemaUri is omitted. # Necessary to support SchemaInline PACKAGE_NULL_PROPERTY = False @@ -635,7 +636,7 @@ class GraphQLApiCodeResource(ResourceZip): """ RESOURCE_TYPE = AWS_SERVERLESS_GRAPHQLAPI - PROPERTY_NAME = RESOURCES_WITH_LOCAL_PATHS[RESOURCE_TYPE][1] + PROPERTY_NAME = graphql_api.CODE_ARTIFACT_PROPERTY # if CodeUri is omitted the directory is not packaged because it's necessary to support CodeInline PACKAGE_NULL_PROPERTY = False @@ -648,7 +649,7 @@ def export(self, resource_id: str, resource_dict: Optional[Dict], parent_dir: st # to be able to set different nested properties to S3 uri, paths are necessary # jmespath doesn't provide that functionality, thus custom implementation - paths_values = self._find_all_with_property_name(resource_dict) + paths_values = graphql_api.find_all_paths_and_values(self.PROPERTY_NAME, resource_dict) for property_path, property_value in paths_values: if isinstance(property_value, dict): LOG.debug("Property %s of %s resource is not a URL", self.PROPERTY_NAME, resource_id) @@ -675,37 +676,6 @@ def export(self, resource_id: str, resource_dict: Optional[Dict], parent_dir: st if temp_dir: shutil.rmtree(temp_dir) - def _find_all_with_property_name(self, graphql_dict: Dict[str, Any]) -> List[Tuple[str, Union[str, Dict]]]: - """Find paths to the all properties with self.PROPERTY_NAME name and their (properties) values. - - It leverages the knowledge of GraphQLApi structure instead of doing generic search in the graph. - - Parameters - ---------- - graphql_dict - GraphQLApi resource dict - - Returns - ------- - list of tuple (path, value) for all found properties which has property_name - """ - # need to look up only in "Resolvers" and "Functions" subtrees - resolvers_and_functions = {k: graphql_dict[k] for k in ("Resolvers", "Functions") if k in graphql_dict} - stack: List[Tuple[Dict[str, Any], str]] = [(resolvers_and_functions, "")] - paths_values: List[Tuple[str, Union[str, Dict]]] = [] - - while stack: - node, path = stack.pop() - if isinstance(node, dict): - for key, value in node.items(): - if key == self.PROPERTY_NAME: - paths_values.append((f"{path}{key}", value)) - elif isinstance(value, dict): - stack.append((value, f"{path}{key}.")) - # there is no need to handle lists because - # paths to "CodeUri" within "Resolvers" and "Functions" doesn't have lists - return paths_values - RESOURCES_EXPORT_LIST = [ ServerlessFunctionResource, diff --git a/samcli/lib/utils/graphql_api.py b/samcli/lib/utils/graphql_api.py new file mode 100644 index 0000000000..a10c2a0c2c --- /dev/null +++ b/samcli/lib/utils/graphql_api.py @@ -0,0 +1,41 @@ +"""Helper functions to work with SAM GraphQLApi resource +""" + +from typing import Any, Dict, List, Tuple, Union + +SCHEMA_ARTIFACT_PROPERTY = "SchemaUri" +CODE_ARTIFACT_PROPERTY = "CodeUri" + + +def find_all_paths_and_values(property_name: str, graphql_dict: Dict[str, Any]) -> List[Tuple[str, Union[str, Dict]]]: + """Find paths to the all properties with property_name and their (properties) values. + + It leverages the knowledge of GraphQLApi structure instead of doing generic search in the graph. + + Parameters + ---------- + property_name + Name of the property to look up, for example 'CodeUri' + graphql_dict + GraphQLApi resource dict + + Returns + ------- + list of tuple (path, value) for all found properties which has property_name + """ + # need to look up only in "Resolvers" and "Functions" subtrees + resolvers_and_functions = {k: graphql_dict[k] for k in ("Resolvers", "Functions") if k in graphql_dict} + stack: List[Tuple[Dict[str, Any], str]] = [(resolvers_and_functions, "")] + paths_values: List[Tuple[str, Union[str, Dict]]] = [] + + while stack: + node, path = stack.pop() + if isinstance(node, dict): + for key, value in node.items(): + if key == property_name: + paths_values.append((f"{path}{key}", value)) + elif isinstance(value, dict): + stack.append((value, f"{path}{key}.")) + # there is no need to handle lists because + # paths to "CodeUri" within "Resolvers" and "Functions" doesn't have lists + return paths_values diff --git a/samcli/lib/utils/resources.py b/samcli/lib/utils/resources.py index 5011f581dc..cb99abc8d6 100644 --- a/samcli/lib/utils/resources.py +++ b/samcli/lib/utils/resources.py @@ -4,6 +4,8 @@ from collections import defaultdict +from samcli.lib.utils.graphql_api import CODE_ARTIFACT_PROPERTY, SCHEMA_ARTIFACT_PROPERTY + # Lambda AWS_SERVERLESS_FUNCTION = "AWS::Serverless::Function" AWS_SERVERLESS_LAYERVERSION = "AWS::Serverless::LayerVersion" @@ -62,7 +64,7 @@ METADATA_WITH_LOCAL_PATHS = {AWS_SERVERLESSREPO_APPLICATION: ["LicenseUrl", "ReadmeUrl"]} RESOURCES_WITH_LOCAL_PATHS = { - AWS_SERVERLESS_GRAPHQLAPI: ["SchemaUri", "CodeUri"], + AWS_SERVERLESS_GRAPHQLAPI: [SCHEMA_ARTIFACT_PROPERTY, CODE_ARTIFACT_PROPERTY], AWS_SERVERLESS_FUNCTION: ["CodeUri"], AWS_SERVERLESS_API: ["DefinitionUri"], AWS_SERVERLESS_HTTPAPI: ["DefinitionUri"], diff --git a/tests/unit/commands/_utils/test_template.py b/tests/unit/commands/_utils/test_template.py index d3b6cc08c5..f739befbcc 100644 --- a/tests/unit/commands/_utils/test_template.py +++ b/tests/unit/commands/_utils/test_template.py @@ -8,8 +8,14 @@ import yaml from botocore.utils import set_value_from_jmespath from parameterized import parameterized, param +from samcli.lib.utils.graphql_api import CODE_ARTIFACT_PROPERTY, find_all_paths_and_values -from samcli.lib.utils.resources import AWS_SERVERLESS_FUNCTION, AWS_SERVERLESS_API, RESOURCES_WITH_IMAGE_COMPONENT +from samcli.lib.utils.resources import ( + AWS_SERVERLESS_FUNCTION, + AWS_SERVERLESS_API, + AWS_SERVERLESS_GRAPHQLAPI, + RESOURCES_WITH_IMAGE_COMPONENT, +) from samcli.commands._utils.template import ( get_template_data, METADATA_WITH_LOCAL_PATHS, @@ -215,35 +221,14 @@ def test_must_update_relative_metadata_paths(self, resource_type, properties): @parameterized.expand([(resource_type, props) for resource_type, props in RESOURCES_WITH_LOCAL_PATHS.items()]) def test_must_update_relative_resource_paths(self, resource_type, properties): for propname in properties: - template_dict = { - "Resources": { - "MyResourceWithRelativePath": {"Type": resource_type, "Properties": {}}, - "MyResourceWithS3Path": {"Type": resource_type, "Properties": {propname: self.s3path}}, - "MyResourceWithAbsolutePath": {"Type": resource_type, "Properties": {propname: self.abspath}}, - "MyResourceWithInvalidPath": { - "Type": resource_type, - "Properties": { - # Path is not a string - propname: {"foo": "bar"} - }, - }, - "MyResourceWithoutProperties": {"Type": resource_type}, - "UnsupportedResourceType": {"Type": "AWS::Ec2::Instance", "Properties": {"Code": "bar"}}, - "ResourceWithoutType": {"foo": "bar"}, - }, - "Parameters": {"a": "b"}, - } + template_dict = self._generate_template(resource_type, propname) - set_value_from_jmespath( - template_dict, f"Resources.MyResourceWithRelativePath.Properties.{propname}", self.curpath - ) + self._set_property(self.curpath, propname, template_dict, resource_type, "MyResourceWithRelativePath") expected_template_dict = copy.deepcopy(template_dict) - set_value_from_jmespath( - expected_template_dict, - f"Resources.MyResourceWithRelativePath.Properties.{propname}", - self.expected_result, + self._set_property( + self.expected_result, propname, expected_template_dict, resource_type, "MyResourceWithRelativePath" ) result = _update_relative_paths(template_dict, self.src, self.dest) @@ -251,7 +236,13 @@ def test_must_update_relative_resource_paths(self, resource_type, properties): self.maxDiff = None self.assertEqual(result, expected_template_dict) - @parameterized.expand([(resource_type, props) for resource_type, props in RESOURCES_WITH_LOCAL_PATHS.items()]) + @parameterized.expand( + [ + (resource_type, props) + for resource_type, props in RESOURCES_WITH_LOCAL_PATHS.items() + if resource_type != AWS_SERVERLESS_GRAPHQLAPI # Metadata path to code artifacts is not supported + ] + ) def test_must_update_relative_resource_metadata_paths(self, resource_type, properties): for propname in properties: template_dict = { @@ -336,34 +327,18 @@ def test_must_skip_only_image_components_and_update_relative_resource_paths( ): for non_image_propname in non_image_properties: for image_propname in image_properties: - template_dict = { - "Resources": { - "MyResourceWithRelativePath": {"Type": non_image_resource_type, "Properties": {}}, - "MyResourceWithS3Path": { - "Type": non_image_resource_type, - "Properties": {non_image_propname: self.s3path}, - }, - "MyResourceWithAbsolutePath": { - "Type": non_image_resource_type, - "Properties": {non_image_propname: self.abspath}, - }, - "MyResourceWithInvalidPath": { - "Type": non_image_resource_type, - "Properties": { - # Path is not a string - non_image_propname: {"foo": "bar"} - }, - }, - "MyResourceWithoutProperties": {"Type": non_image_resource_type}, - "UnsupportedResourceType": {"Type": "AWS::Ec2::Instance", "Properties": {"Code": "bar"}}, - "ResourceWithoutType": {"foo": "bar"}, - "ImageResource": {"Type": image_resource_type, "Properties": {"PackageType": "Image"}}, - }, - "Parameters": {"a": "b"}, + template_dict = self._generate_template(non_image_resource_type, non_image_resource_type) + template_dict["Resources"]["ImageResource"] = { + "Type": image_resource_type, + "Properties": {"PackageType": "Image"}, } - set_value_from_jmespath( - template_dict, f"Resources.MyResourceWithRelativePath.Properties.{non_image_propname}", self.curpath + self._set_property( + self.curpath, + non_image_propname, + template_dict, + non_image_resource_type, + "MyResourceWithRelativePath", ) set_value_from_jmespath( @@ -372,10 +347,12 @@ def test_must_skip_only_image_components_and_update_relative_resource_paths( expected_template_dict = copy.deepcopy(template_dict) - set_value_from_jmespath( - expected_template_dict, - f"Resources.MyResourceWithRelativePath.Properties.{non_image_propname}", + self._set_property( self.expected_result, + non_image_propname, + expected_template_dict, + non_image_resource_type, + "MyResourceWithRelativePath", ) result = _update_relative_paths(template_dict, self.src, self.dest) @@ -420,6 +397,74 @@ def test_must_update_aws_include_also(self): self.maxDiff = None self.assertEqual(result, expected_template_dict) + def _generate_template(self, resource_type, property_name): + template = { + "Resources": { + "MyResourceWithRelativePath": {"Type": resource_type, "Properties": {}}, + "MyResourceWithS3Path": {"Type": resource_type, "Properties": {}}, + "MyResourceWithAbsolutePath": {"Type": resource_type, "Properties": {}}, + "MyResourceWithInvalidPath": { + "Type": resource_type, + "Properties": {}, + }, + "MyResourceWithoutProperties": {"Type": resource_type}, + "UnsupportedResourceType": {"Type": "AWS::Ec2::Instance", "Properties": {"Code": "bar"}}, + "ResourceWithoutType": {"foo": "bar"}, + }, + "Parameters": {"a": "b"}, + } + if self._is_graphql_code_uri(resource_type, property_name): + template["Resources"]["MyResourceWithRelativePath"]["Properties"] = self._generate_graphql_props( + property_name + ) + template["Resources"]["MyResourceWithS3Path"]["Properties"] = self._generate_graphql_props( + property_name, self.s3path + ) + template["Resources"]["MyResourceWithAbsolutePath"]["Properties"] = self._generate_graphql_props( + property_name, self.abspath + ) + template["Resources"]["MyResourceWithInvalidPath"]["Properties"] = self._generate_graphql_props( + property_name, {"foo": "bar"} + ) + else: + template["Resources"]["MyResourceWithS3Path"]["Properties"] = {property_name: self.s3path} + template["Resources"]["MyResourceWithAbsolutePath"]["Properties"] = {property_name: self.abspath} + template["Resources"]["MyResourceWithInvalidPath"]["Properties"] = {property_name: {"foo": "bar"}} + return template + + @staticmethod + def _generate_graphql_props(property_name, path=None): + if path is not None: + return { + "Functions": {"Func1": {property_name: path}, "Func2": {property_name: path}}, + "Resolvers": {"Mutation": {"Resolver1": {property_name: path}}}, + } + return { + "Functions": {"Func1": {}, "Func2": {}}, + "Resolvers": {"Mutation": {"Resolver1": {}}}, + } + + def _set_property(self, value, property_name, template, tested_type, resource_name): + if self._is_graphql_code_uri(tested_type, property_name): + resource_dict = template["Resources"][resource_name] + paths_values = find_all_paths_and_values(property_name, resource_dict) + for property_path, _ in paths_values: + set_value_from_jmespath(template, f"Resources.{resource_name}.{property_path}", value) + else: + set_value_from_jmespath(template, f"Resources.{resource_name}.Properties.{property_name}", value) + + @staticmethod + def _is_graphql_code_uri(resource_type, property_name): + return resource_type == AWS_SERVERLESS_GRAPHQLAPI and property_name == CODE_ARTIFACT_PROPERTY + + def _assert_templates_are_equal(self, actual, expected, tested_type, property_name): + if self._is_graphql_code_uri(tested_type, property_name): + actual_paths_values = find_all_paths_and_values(property_name, actual) + expepcted_paths_values = find_all_paths_and_values(property_name, expected) + self.assertListEqual(actual_paths_values, expepcted_paths_values) + else: + self.assertEqual(actual, expected) + class Test_resolve_relative_to(TestCase): def setUp(self): diff --git a/tests/unit/lib/utils/test_graphql_api.py b/tests/unit/lib/utils/test_graphql_api.py new file mode 100644 index 0000000000..e1b101ed3a --- /dev/null +++ b/tests/unit/lib/utils/test_graphql_api.py @@ -0,0 +1,47 @@ +from unittest import TestCase +from samcli.lib.utils.graphql_api import CODE_ARTIFACT_PROPERTY, find_all_paths_and_values + + +class Test_find_all_paths_and_values(TestCase): + def test_finds_all_paths_with_CODE_ARTIFACT_PROPERTY(self): + resource = { + "SchemaUri": "schema.graphql", + "Functions": { + "Func1": {"CodeUri": "foo/bar"}, + "Func2": {"InlineCode": "supercode"}, + }, + "Resolvers": {"Mutation": {"Resolver1": {"CodeUri": "foo/baz"}, "Resolver2": {}}}, + } + paths_values = find_all_paths_and_values(CODE_ARTIFACT_PROPERTY, resource) + self.assertEqual( + paths_values, + [ + ("Functions.Func1.CodeUri", "foo/bar"), + ("Resolvers.Mutation.Resolver1.CodeUri", "foo/baz"), + ], + ) + + def test_finds_nothing_when_no_CODE_ARTIFACT_PROPERTY(self): + resource = { + "SchemaUri": "schema.graphql", + "Functions": { + "Func1": {"InlineCode": "supercode"}, + "Func2": {"InlineCode": "supercode"}, + }, + "Resolvers": {"Mutation": {"Resolver1": {}, "Resolver2": {}}}, + } + paths_values = find_all_paths_and_values(CODE_ARTIFACT_PROPERTY, resource) + self.assertEqual( + paths_values, + [], + ) + + def test_finds_nothing_when_no_resolvers_or_functions(self): + resource = { + "SchemaUri": "schema.graphql", + } + paths_values = find_all_paths_and_values(CODE_ARTIFACT_PROPERTY, resource) + self.assertEqual( + paths_values, + [], + ) From 433de09d58178303bc669d274edd427531f5853a Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Fri, 9 Jun 2023 16:56:41 -0700 Subject: [PATCH 045/107] fix: Update Arn parsing logic and fix some edge cases/bug fixes for remote invoke (#5295) * Fix some edge cases and bug fixes for remote invoke and update Arn parsing logic * Address feedback * Add unit test for s3 with no region/accoint_id provided * Renamed command to sam remote invoke --- samcli/cli/command.py | 2 +- samcli/commands/{cloud => remote}/__init__.py | 0 .../commands/{cloud => remote}/exceptions.py | 4 + .../{cloud => remote}/invoke/__init__.py | 0 .../commands/{cloud => remote}/invoke/cli.py | 4 +- .../{cloud/cloud.py => remote/remote.py} | 4 +- .../remote_invoke_context.py | 43 ++++-- .../remote_invoke_options_validations.py | 4 +- samcli/lib/pipeline/bootstrap/resource.py | 5 +- .../remote_invoke/lambda_invoke_executors.py | 4 +- samcli/lib/utils/arn_utils.py | 43 +++++- .../commands/{cloud => remote}/__init__.py | 0 .../{cloud => remote}/invoke/__init__.py | 0 .../{cloud => remote}/invoke/test_cli.py | 6 +- .../test_remote_invoke_context.py | 37 +++-- tests/unit/lib/utils/test_arn_utils.py | 137 ++++++++++++++++-- 16 files changed, 228 insertions(+), 65 deletions(-) rename samcli/commands/{cloud => remote}/__init__.py (100%) rename samcli/commands/{cloud => remote}/exceptions.py (85%) rename samcli/commands/{cloud => remote}/invoke/__init__.py (100%) rename samcli/commands/{cloud => remote}/invoke/cli.py (97%) rename samcli/commands/{cloud/cloud.py => remote/remote.py} (62%) rename samcli/commands/{cloud => remote}/remote_invoke_context.py (86%) rename tests/unit/commands/{cloud => remote}/__init__.py (100%) rename tests/unit/commands/{cloud => remote}/invoke/__init__.py (100%) rename tests/unit/commands/{cloud => remote}/invoke/test_cli.py (96%) rename tests/unit/commands/{cloud => remote}/test_remote_invoke_context.py (78%) diff --git a/samcli/cli/command.py b/samcli/cli/command.py index b3fab518bf..23934f9eb4 100644 --- a/samcli/cli/command.py +++ b/samcli/cli/command.py @@ -30,7 +30,7 @@ "samcli.commands.pipeline.pipeline", "samcli.commands.list.list", "samcli.commands.docs", - # "samcli.commands.cloud.cloud", + # "samcli.commands.remote.remote", # We intentionally do not expose the `bootstrap` command for now. We might open it up later # "samcli.commands.bootstrap", ] diff --git a/samcli/commands/cloud/__init__.py b/samcli/commands/remote/__init__.py similarity index 100% rename from samcli/commands/cloud/__init__.py rename to samcli/commands/remote/__init__.py diff --git a/samcli/commands/cloud/exceptions.py b/samcli/commands/remote/exceptions.py similarity index 85% rename from samcli/commands/cloud/exceptions.py rename to samcli/commands/remote/exceptions.py index f55b773624..68b0d5983f 100644 --- a/samcli/commands/cloud/exceptions.py +++ b/samcli/commands/remote/exceptions.py @@ -22,3 +22,7 @@ class UnsupportedServiceForRemoteInvoke(UserException): class NoExecutorFoundForRemoteInvoke(UserException): pass + + +class InvalidStackNameProvidedForRemoteInvoke(UserException): + pass diff --git a/samcli/commands/cloud/invoke/__init__.py b/samcli/commands/remote/invoke/__init__.py similarity index 100% rename from samcli/commands/cloud/invoke/__init__.py rename to samcli/commands/remote/invoke/__init__.py diff --git a/samcli/commands/cloud/invoke/cli.py b/samcli/commands/remote/invoke/cli.py similarity index 97% rename from samcli/commands/cloud/invoke/cli.py rename to samcli/commands/remote/invoke/cli.py index 532ff26efd..d3ebd5d36f 100644 --- a/samcli/commands/cloud/invoke/cli.py +++ b/samcli/commands/remote/invoke/cli.py @@ -68,7 +68,7 @@ def cli( config_env: str, ) -> None: """ - `sam cloud invoke` command entry point + `sam remote invoke` command entry point """ do_cli( @@ -100,8 +100,8 @@ def do_cli( """ Implementation of the ``cli`` method """ - from samcli.commands.cloud.remote_invoke_context import RemoteInvokeContext from samcli.commands.exceptions import UserException + from samcli.commands.remote.remote_invoke_context import RemoteInvokeContext from samcli.lib.remote_invoke.exceptions import ( ErrorBotoApiCallException, InvalideBotoResponseException, diff --git a/samcli/commands/cloud/cloud.py b/samcli/commands/remote/remote.py similarity index 62% rename from samcli/commands/cloud/cloud.py rename to samcli/commands/remote/remote.py index a2cf4c1f70..eac2e7028f 100644 --- a/samcli/commands/cloud/cloud.py +++ b/samcli/commands/remote/remote.py @@ -1,11 +1,11 @@ """ -Command group for "cloud" suite for commands. It provides common CLI arguments for interacting with +Command group for "remote" suite for commands. It provides common CLI arguments for interacting with cloud resources such as Lambda Function. """ import click -from samcli.commands.cloud.invoke.cli import cli as invoke_cli +from samcli.commands.remote.invoke.cli import cli as invoke_cli @click.group() diff --git a/samcli/commands/cloud/remote_invoke_context.py b/samcli/commands/remote/remote_invoke_context.py similarity index 86% rename from samcli/commands/cloud/remote_invoke_context.py rename to samcli/commands/remote/remote_invoke_context.py index 7c9dda4fcd..254f504b33 100644 --- a/samcli/commands/cloud/remote_invoke_context.py +++ b/samcli/commands/remote/remote_invoke_context.py @@ -4,9 +4,12 @@ import logging from typing import Optional, cast -from samcli.commands.cloud.exceptions import ( +from botocore.exceptions import ClientError + +from samcli.commands.remote.exceptions import ( AmbiguousResourceForRemoteInvoke, InvalidRemoteInvokeParameters, + InvalidStackNameProvidedForRemoteInvoke, NoExecutorFoundForRemoteInvoke, NoResourceFoundForRemoteInvoke, UnsupportedServiceForRemoteInvoke, @@ -15,7 +18,7 @@ from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutionInfo from samcli.lib.utils import osutils from samcli.lib.utils.arn_utils import ARNParts, InvalidArnValue -from samcli.lib.utils.boto_utils import BotoProviderType +from samcli.lib.utils.boto_utils import BotoProviderType, get_client_error_code from samcli.lib.utils.cloudformation import ( CloudFormationResourceSummary, get_resource_summaries, @@ -105,20 +108,28 @@ def _populate_resource_summary(self) -> None: if not self._stack_name and not self._resource_id: raise InvalidRemoteInvokeParameters("Either --stack-name or --resource-id parameter should be provided") - if not self._resource_id: - # no resource id provided, list all resources from stack and try to find one - self._resource_summary = self._get_single_resource_from_stack() - self._resource_id = self._resource_summary.logical_resource_id - return - - if not self._stack_name: - # no stack name provided, resource id should be physical id so that we can use it - self._resource_summary = self._get_from_physical_resource_id() - return - - self._resource_summary = get_resource_summary( - self._boto_resource_provider, self._boto_client_provider, self._stack_name, self._resource_id - ) + try: + if not self._resource_id: + # no resource id provided, list all resources from stack and try to find one + self._resource_summary = self._get_single_resource_from_stack() + self._resource_id = self._resource_summary.logical_resource_id + return + + if not self._stack_name: + # no stack name provided, resource id should be physical id so that we can use it + self._resource_summary = self._get_from_physical_resource_id() + return + + self._resource_summary = get_resource_summary( + self._boto_resource_provider, self._boto_client_provider, self._stack_name, self._resource_id + ) + except ClientError as ex: + error_code = get_client_error_code(ex) + if error_code == "ValidationError": + raise InvalidStackNameProvidedForRemoteInvoke( + f"Invalid --stack-name parameter. Stack with id '{self._stack_name}' does not exist" + ) + raise ex def _get_single_resource_from_stack(self) -> CloudFormationResourceSummary: """ diff --git a/samcli/lib/cli_validation/remote_invoke_options_validations.py b/samcli/lib/cli_validation/remote_invoke_options_validations.py index 6db65e2c20..dbb0cde6ef 100644 --- a/samcli/lib/cli_validation/remote_invoke_options_validations.py +++ b/samcli/lib/cli_validation/remote_invoke_options_validations.py @@ -22,7 +22,7 @@ def event_and_event_file_options_validation(func): Parameters ---------- func : - Command that would be executed, in this case it is 'sam cloud invoke' + Command that would be executed, in this case it is 'sam remote invoke' Returns ------- @@ -64,7 +64,7 @@ def stack_name_or_resource_id_atleast_one_option_validation(func): Parameters ---------- func : - Command that would be executed, in this case it is 'sam cloud invoke' + Command that would be executed, in this case it is 'sam remote invoke' Returns ------- diff --git a/samcli/lib/pipeline/bootstrap/resource.py b/samcli/lib/pipeline/bootstrap/resource.py index 168da9f9f0..cb5eb000e3 100644 --- a/samcli/lib/pipeline/bootstrap/resource.py +++ b/samcli/lib/pipeline/bootstrap/resource.py @@ -99,10 +99,9 @@ def get_uri(self) -> Optional[str]: # ECR's resource_id contains the resource-type("resource") which is excluded from the URL # from docs: https://docs.aws.amazon.com/AmazonECR/latest/userguide/security_iam_service-with-iam.html # ECR's ARN: arn:${Partition}:ecr:${Region}:${Account}:repository/${Repository-name} - if not arn_parts.resource_id.startswith("repository/"): + if arn_parts.resource_type != "repository": raise ValueError(f"Invalid ECR ARN ({self.arn}), can't extract the URL from it.") - i = len("repository/") - repo_name = arn_parts.resource_id[i:] + repo_name = arn_parts.resource_id return f"{arn_parts.account_id}.dkr.ecr.{arn_parts.region}.amazonaws.com/{repo_name}" diff --git a/samcli/lib/remote_invoke/lambda_invoke_executors.py b/samcli/lib/remote_invoke/lambda_invoke_executors.py index 49245adf99..82e65b8117 100644 --- a/samcli/lib/remote_invoke/lambda_invoke_executors.py +++ b/samcli/lib/remote_invoke/lambda_invoke_executors.py @@ -52,9 +52,7 @@ def validate_action_parameters(self, parameters: dict) -> None: if parameter_key == FUNCTION_NAME: LOG.warning("FunctionName is defined using the value provided for --resource-id option.") elif parameter_key == PAYLOAD: - LOG.warning( - "Payload is defined using the value provided for either --payload or --payload-file options." - ) + LOG.warning("Payload is defined using the value provided for either --event or --event-file options.") else: self.request_parameters[parameter_key] = parameter_value diff --git a/samcli/lib/utils/arn_utils.py b/samcli/lib/utils/arn_utils.py index c610442290..2df2aef9ea 100644 --- a/samcli/lib/utils/arn_utils.py +++ b/samcli/lib/utils/arn_utils.py @@ -1,6 +1,7 @@ """ Module for utilities for ARN (Amazon Resource Names) """ +import re class InvalidArnValue(ValueError): @@ -31,11 +32,43 @@ class ARNParts: service: str region: str account_id: str + resource_type: str resource_id: str def __init__(self, arn: str) -> None: - parts = arn.split(":") - try: - [_, self.partition, self.service, self.region, self.account_id, self.resource_id] = parts - except ValueError as ex: - raise InvalidArnValue(f"Invalid ARN ({arn})") from ex + # Regex pattern formed based on the 3 ARN general formats found here: + # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html + arn_pattern = ( + r"arn:([a-zA-Z0-9_-]+):" # Pattern for partition + r"([a-zA-Z0-9_-]+):" # Pattern for service + r"([a-zA-Z0-9_-]*):" # Pattern for region + r"([a-zA-Z0-9_-]*)" # Pattern for account_id + r"(?::([a-zA-Z0-9_-]+))?" # Pattern for resource_type if it exists + r"(?::(.+))" # Pattern for resource_id if it exists + ) + + matched_arn = re.match(arn_pattern, arn) + if not matched_arn: + raise InvalidArnValue(f"Invalid ARN ({arn}) provided") + + self.partition = matched_arn.group(1) + self.service = matched_arn.group(2) + self.region = matched_arn.group(3) if matched_arn.group(3) else "" + self.account_id = matched_arn.group(4) if matched_arn.group(4) else "" + self.resource_type = matched_arn.group(5) if matched_arn.group(5) else "" + if matched_arn.group(5): + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-type:resource-id + self.resource_type = matched_arn.group(5) + self.resource_id = matched_arn.group(6) if matched_arn.group(6) else "" + elif "/" in matched_arn.group(6): + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-type/resource-id + split_resource_type_and_id = matched_arn.group(6).split("/", 1) + self.resource_type = split_resource_type_and_id[0] + self.resource_id = split_resource_type_and_id[1] + else: + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-id + self.resource_type = "" + self.resource_id = matched_arn.group(6) if matched_arn.group(6) else "" diff --git a/tests/unit/commands/cloud/__init__.py b/tests/unit/commands/remote/__init__.py similarity index 100% rename from tests/unit/commands/cloud/__init__.py rename to tests/unit/commands/remote/__init__.py diff --git a/tests/unit/commands/cloud/invoke/__init__.py b/tests/unit/commands/remote/invoke/__init__.py similarity index 100% rename from tests/unit/commands/cloud/invoke/__init__.py rename to tests/unit/commands/remote/invoke/__init__.py diff --git a/tests/unit/commands/cloud/invoke/test_cli.py b/tests/unit/commands/remote/invoke/test_cli.py similarity index 96% rename from tests/unit/commands/cloud/invoke/test_cli.py rename to tests/unit/commands/remote/invoke/test_cli.py index ae7dc7f333..6e97251b5a 100644 --- a/tests/unit/commands/cloud/invoke/test_cli.py +++ b/tests/unit/commands/remote/invoke/test_cli.py @@ -3,7 +3,7 @@ from parameterized import parameterized -from samcli.commands.cloud.invoke.cli import do_cli +from samcli.commands.remote.invoke.cli import do_cli from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeOutputFormat from samcli.lib.remote_invoke.exceptions import ( ErrorBotoApiCallException, @@ -37,7 +37,7 @@ def setUp(self) -> None: @patch("samcli.lib.remote_invoke.remote_invoke_executors.RemoteInvokeExecutionInfo") @patch("samcli.lib.utils.boto_utils.get_boto_client_provider_with_config") @patch("samcli.lib.utils.boto_utils.get_boto_resource_provider_with_config") - @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeContext") + @patch("samcli.commands.remote.remote_invoke_context.RemoteInvokeContext") def test_remote_invoke_command( self, event, @@ -112,7 +112,7 @@ def test_remote_invoke_command( (InvalidResourceBotoParameterException,), ] ) - @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeContext") + @patch("samcli.commands.remote.remote_invoke_context.RemoteInvokeContext") def test_raise_user_exception_invoke_not_successfull(self, exeception_to_raise, mock_invoke_context): context_mock = Mock() diff --git a/tests/unit/commands/cloud/test_remote_invoke_context.py b/tests/unit/commands/remote/test_remote_invoke_context.py similarity index 78% rename from tests/unit/commands/cloud/test_remote_invoke_context.py rename to tests/unit/commands/remote/test_remote_invoke_context.py index d7e55dce06..0c04a01713 100644 --- a/tests/unit/commands/cloud/test_remote_invoke_context.py +++ b/tests/unit/commands/remote/test_remote_invoke_context.py @@ -2,14 +2,15 @@ from unittest.mock import Mock, patch from uuid import uuid4 -from samcli.commands.cloud.exceptions import ( +from samcli.commands.remote.exceptions import ( InvalidRemoteInvokeParameters, AmbiguousResourceForRemoteInvoke, NoResourceFoundForRemoteInvoke, UnsupportedServiceForRemoteInvoke, NoExecutorFoundForRemoteInvoke, + InvalidStackNameProvidedForRemoteInvoke, ) -from samcli.commands.cloud.remote_invoke_context import RemoteInvokeContext, SUPPORTED_SERVICES +from samcli.commands.remote.remote_invoke_context import RemoteInvokeContext, SUPPORTED_SERVICES from samcli.lib.utils.cloudformation import CloudFormationResourceSummary @@ -32,7 +33,15 @@ def test_no_stack_name_and_no_resource_id_should_fail(self): with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summaries") + def test_invalid_stack_name_with_no_resource_should_fail(self, patched_resource_summaries): + self.resource_id = None + patched_resource_summaries.side_effect = InvalidStackNameProvidedForRemoteInvoke("Invalid stack-name") + with self.assertRaises(InvalidStackNameProvidedForRemoteInvoke): + with self._get_remote_invoke_context(): + pass + + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_no_resource_should_fail(self, patched_resource_summaries): self.resource_id = None patched_resource_summaries.return_value = {} @@ -40,7 +49,7 @@ def test_only_stack_name_with_no_resource_should_fail(self, patched_resource_sum with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_multiple_resource_should_fail(self, patched_resource_summaries): self.resource_id = None patched_resource_summaries.return_value = {"resource1": Mock(), "resource2": Mock()} @@ -48,7 +57,7 @@ def test_only_stack_name_with_multiple_resource_should_fail(self, patched_resour with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_single_resource_should_be_valid(self, patched_resource_summaries): self.resource_id = None resource_summary = Mock(logical_resource_id=self.resource_id) @@ -58,7 +67,7 @@ def test_only_stack_name_with_single_resource_should_be_valid(self, patched_reso def test_only_resource_id_unsupported_service_arn_should_fail(self): self.stack_name = None - self.resource_id = "arn:aws:unsupported-service:region:account:resource_id" + self.resource_id = "arn:aws:unsupported-service:region:account:resource_type:resource_id" with self.assertRaises(UnsupportedServiceForRemoteInvoke): with self._get_remote_invoke_context(): pass @@ -66,7 +75,7 @@ def test_only_resource_id_unsupported_service_arn_should_fail(self): def test_only_resource_id_supported_service_arn_should_be_valid(self): self.stack_name = None service = "lambda" - self.resource_id = f"arn:aws:{service}:region:account:{self.resource_id}" + self.resource_id = f"arn:aws:{service}:region:account:resource_type:{self.resource_id}" with self._get_remote_invoke_context() as remote_invoke_context: self.assertEqual( remote_invoke_context._resource_summary, @@ -75,7 +84,7 @@ def test_only_resource_id_supported_service_arn_should_be_valid(self): ), ) - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary_from_physical_id") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary_from_physical_id") def test_only_resource_id_as_invalid_physical_id_should_fail(self, patched_resource_summary_from_physical_id): self.stack_name = None patched_resource_summary_from_physical_id.return_value = None @@ -83,14 +92,14 @@ def test_only_resource_id_as_invalid_physical_id_should_fail(self, patched_resou with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary") def test_if_no_resource_found_with_given_stack_and_resource_id_should_fail(self, patched_get_resource_summary): patched_get_resource_summary.return_value = None with self.assertRaises(AmbiguousResourceForRemoteInvoke): with self._get_remote_invoke_context() as remote_invoke_context: remote_invoke_context.run(Mock()) - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary_from_physical_id") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary_from_physical_id") def test_only_resource_id_as_valid_physical_id_should_be_valid(self, patched_resource_summary_from_physical_id): self.stack_name = None resource_summary = Mock() @@ -98,22 +107,22 @@ def test_only_resource_id_as_valid_physical_id_should_be_valid(self, patched_res with self._get_remote_invoke_context() as remote_invoke_context: self.assertEqual(remote_invoke_context._resource_summary, resource_summary) - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary") def test_running_without_resource_summary_should_raise_exception(self, patched_get_resource_summary): patched_get_resource_summary.return_value = None with self._get_remote_invoke_context() as remote_invoke_context: with self.assertRaises(AmbiguousResourceForRemoteInvoke): remote_invoke_context.run(Mock()) - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary") def test_running_with_unsupported_resource_should_raise_exception(self, patched_get_resource_summary): patched_get_resource_summary.return_value = Mock(resource_type="UnSupportedResource") with self._get_remote_invoke_context() as remote_invoke_context: with self.assertRaises(NoExecutorFoundForRemoteInvoke): remote_invoke_context.run(Mock()) - @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeExecutorFactory") - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.remote.remote_invoke_context.RemoteInvokeExecutorFactory") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary") def test_running_should_execute_remote_invoke_executor_instance( self, patched_get_resource_summary, patched_remote_invoke_executor_factory ): diff --git a/tests/unit/lib/utils/test_arn_utils.py b/tests/unit/lib/utils/test_arn_utils.py index cf235c1e5d..935c724c31 100644 --- a/tests/unit/lib/utils/test_arn_utils.py +++ b/tests/unit/lib/utils/test_arn_utils.py @@ -1,4 +1,5 @@ from unittest import TestCase +from parameterized import parameterized from samcli.lib.utils.arn_utils import InvalidArnValue, ARNParts @@ -8,18 +9,126 @@ def test_invalid_arn_should_fail(self): with self.assertRaises(InvalidArnValue): _ = ARNParts("invalid_arn") - def test_valid_arn(self): - partition = "aws" - service = "lambda" - region = "us-east-1" - account_id = "0123456789" - resource_id = "resource_id" - arn_value = f"arn:{partition}:{service}:{region}:{account_id}:{resource_id}" + @parameterized.expand( + [ + ( + "arn:aws:service:region:account-id:resource-id", + { + "partition": "aws", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "", + "resource_id": "resource-id", + }, + ), + ( + "arn:aws:service:region:account-id:resource-type/resource-id", + { + "partition": "aws", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "resource-type", + "resource_id": "resource-id", + }, + ), + ( + "arn:aws:service:region:account-id:resource-type:resource-id", + { + "partition": "aws", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "resource-type", + "resource_id": "resource-id", + }, + ), + ( + "arn:partition:service:region:account-id:repository/repository-name", + { + "partition": "partition", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "repository", + "resource_id": "repository-name", + }, + ), + ( + "arn:partition:service:region:account-id:s3-bucket-name", + { + "partition": "partition", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "", + "resource_id": "s3-bucket-name", + }, + ), + ( + "arn:partition:service:::s3-bucket-name", + { + "partition": "partition", + "service": "service", + "region": "", + "account_id": "", + "resource_type": "", + "resource_id": "s3-bucket-name", + }, + ), + ( + "arn:aws:lambda:us-west-2:123456789012:function:my-function", + { + "partition": "aws", + "service": "lambda", + "region": "us-west-2", + "account_id": "123456789012", + "resource_type": "function", + "resource_id": "my-function", + }, + ), + ( + "arn:aws:states:us-east-1:111122223333:stateMachine:HelloWorld-StateMachine", + { + "partition": "aws", + "service": "states", + "region": "us-east-1", + "account_id": "111122223333", + "resource_type": "stateMachine", + "resource_id": "HelloWorld-StateMachine", + }, + ), + ( + "arn:aws:sqs:region:account_id:queue_name", + { + "partition": "aws", + "service": "sqs", + "region": "region", + "account_id": "account_id", + "resource_type": "", + "resource_id": "queue_name", + }, + ), + ( + "arn:aws:kinesis:us-east-2:123456789012:stream/mystream", + { + "partition": "aws", + "service": "kinesis", + "region": "us-east-2", + "account_id": "123456789012", + "resource_type": "stream", + "resource_id": "mystream", + }, + ), + ] + ) + def test_valid_arn(self, given_arn, expected_result): + arn_parts = ARNParts(given_arn) - arn_parts = ARNParts(arn_value) - - self.assertEqual(arn_parts.partition, partition) - self.assertEqual(arn_parts.service, service) - self.assertEqual(arn_parts.region, region) - self.assertEqual(arn_parts.account_id, account_id) - self.assertEqual(arn_parts.resource_id, resource_id) + self.assertEqual(arn_parts.partition, expected_result["partition"]) + self.assertEqual(arn_parts.service, expected_result["service"]) + self.assertEqual(arn_parts.region, expected_result["region"]) + self.assertEqual(arn_parts.account_id, expected_result["account_id"]) + self.assertEqual(arn_parts.resource_type, expected_result["resource_type"]) + self.assertEqual(arn_parts.resource_id, expected_result["resource_id"]) From 14dfa2e9308805b3d21735afbf7a66e645d77b62 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 12:11:39 -0700 Subject: [PATCH 046/107] chore: update aws_lambda_builders to 1.34.0 (#5343) * chore: update aws_lambda_builders to 1.34.0 * Update base.txt --------- Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 33572b1d3d..5632d14797 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -13,7 +13,7 @@ docker~=6.1.0 dateparser~=1.1 requests~=2.31.0 serverlessrepo==0.1.10 -aws_lambda_builders==1.33.0 +aws_lambda_builders==1.34.0 tomlkit==0.11.8 watchdog==2.1.2 rich~=13.3.3 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 2383e49d2f..12691147a1 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -15,9 +15,9 @@ attrs==23.1.0 \ # jschema-to-python # jsonschema # sarif-om -aws-lambda-builders==1.33.0 \ - --hash=sha256:9064bfd17252ec2a1f41a1db8f8c77410bfebb79635abd0610c17df0b6eaf68c \ - --hash=sha256:f901e276f26b8051fba70ac8f10f1ab7ef73df4b01847b587196c4be26d24e1b +aws-lambda-builders==1.34.0 \ + --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ + --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) aws-sam-translator==1.68.0 \ --hash=sha256:557d8080c9e6c1c609bfe806ea9545f7ea34144e2466c0ddc801806c2c05afdc \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index d40e5a8048..eb641e70a8 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -15,9 +15,9 @@ attrs==23.1.0 \ # jschema-to-python # jsonschema # sarif-om -aws-lambda-builders==1.33.0 \ - --hash=sha256:9064bfd17252ec2a1f41a1db8f8c77410bfebb79635abd0610c17df0b6eaf68c \ - --hash=sha256:f901e276f26b8051fba70ac8f10f1ab7ef73df4b01847b587196c4be26d24e1b +aws-lambda-builders==1.34.0 \ + --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ + --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) aws-sam-translator==1.68.0 \ --hash=sha256:557d8080c9e6c1c609bfe806ea9545f7ea34144e2466c0ddc801806c2c05afdc \ From 57640244101074220382713677dde5b23ea5c9a8 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Mon, 12 Jun 2023 14:13:00 -0700 Subject: [PATCH 047/107] test: test building npm and Typescript projects using external manifest file. (#5283) * test: test building npm and Typescript projects using external manifest file. * fix mypy issues * remove node 12.x, and add the new node versions * run make format --- .../integration/buildcmd/build_integ_base.py | 24 +++++- tests/integration/buildcmd/test_build_cmd.py | 82 +++++++++++++++++++ .../Esbuild/Node_without_manifest/main.js | 8 ++ .../TypeScript_without_manifest/app.ts | 24 ++++++ .../nested/function/app.ts | 24 ++++++ .../Esbuild/npm_manifest/package.json | 14 ++++ .../buildcmd/Node_without_manifest/main.js | 13 +++ .../buildcmd/npm_manifest/package.json | 11 +++ 8 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 tests/integration/testdata/buildcmd/Esbuild/Node_without_manifest/main.js create mode 100644 tests/integration/testdata/buildcmd/Esbuild/TypeScript_without_manifest/app.ts create mode 100644 tests/integration/testdata/buildcmd/Esbuild/TypeScript_without_manifest/nested/function/app.ts create mode 100644 tests/integration/testdata/buildcmd/Esbuild/npm_manifest/package.json create mode 100644 tests/integration/testdata/buildcmd/Node_without_manifest/main.js create mode 100644 tests/integration/testdata/buildcmd/npm_manifest/package.json diff --git a/tests/integration/buildcmd/build_integ_base.py b/tests/integration/buildcmd/build_integ_base.py index 3cc1e66f1d..13ba14f310 100644 --- a/tests/integration/buildcmd/build_integ_base.py +++ b/tests/integration/buildcmd/build_integ_base.py @@ -328,13 +328,18 @@ class BuildIntegEsbuildBase(BuildIntegBase): FUNCTION_LOGICAL_ID = "Function" # Everything should be minifed to one line and a second line for the sourcemap mapping MAX_MINIFIED_LINE_COUNT = 2 + MANIFEST_PATH: Optional[str] = None def _test_with_default_package_json( self, runtime, use_container, code_uri, expected_files, handler, architecture=None, build_in_source=None ): overrides = self.get_override(runtime, code_uri, architecture, handler) + manifest_path = str(Path(self.test_data_path, self.MANIFEST_PATH)) if self.MANIFEST_PATH else None cmdlist = self.get_command_list( - use_container=use_container, parameter_overrides=overrides, build_in_source=build_in_source + use_container=use_container, + parameter_overrides=overrides, + build_in_source=build_in_source, + manifest_path=manifest_path, ) LOG.info("Running Command: {}".format(cmdlist)) @@ -416,12 +421,17 @@ def _verify_built_artifact(self, build_dir, function_logical_id, expected_files) class BuildIntegNodeBase(BuildIntegBase): EXPECTED_FILES_PROJECT_MANIFEST = {"node_modules", "main.js"} EXPECTED_NODE_MODULES = {"minimal-request-promise"} - + CODE_URI = "Node" FUNCTION_LOGICAL_ID = "Function" + TEST_INVOKE = False + MANIFEST_PATH: Optional[str] = None def _test_with_default_package_json(self, runtime, use_container, relative_path, architecture=None): - overrides = self.get_override(runtime, "Node", architecture, "ignored") - cmdlist = self.get_command_list(use_container=use_container, parameter_overrides=overrides) + overrides = self.get_override(runtime, self.CODE_URI, architecture, "main.lambdaHandler") + manifest_path = str(Path(self.test_data_path, self.MANIFEST_PATH)) if self.MANIFEST_PATH else None + cmdlist = self.get_command_list( + use_container=use_container, parameter_overrides=overrides, manifest_path=manifest_path + ) LOG.info("Running Command: {}".format(cmdlist)) run_command(cmdlist, cwd=self.working_dir) @@ -433,6 +443,12 @@ def _test_with_default_package_json(self, runtime, use_container, relative_path, self.EXPECTED_NODE_MODULES, ) + expected = {"body": '{"message":"hello world!"}', "statusCode": 200} + if not SKIP_DOCKER_TESTS and self.TEST_INVOKE: + self._verify_invoke_built_function( + self.built_template, self.FUNCTION_LOGICAL_ID, self._make_parameter_override_arg(overrides), expected + ) + self._verify_resource_property( str(self.built_template), "OtherRelativePathResource", diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index 394964c4af..1a22c9cbb7 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -570,6 +570,23 @@ def test_building_default_package_json(self, runtime, use_container): self._test_with_default_package_json(runtime, use_container, self.test_data_path) +class TestBuildCommand_NodeFunctions_With_External_Manifest(BuildIntegNodeBase): + CODE_URI = "Node_without_manifest" + TEST_INVOKE = True + MANIFEST_PATH = "npm_manifest/package.json" + + @parameterized.expand( + [ + ("nodejs14.x",), + ("nodejs16.x",), + ("nodejs18.x",), + ] + ) + @pytest.mark.flaky(reruns=3) + def test_building_default_package_json(self, runtime): + self._test_with_default_package_json(runtime, False, self.test_data_path) + + class TestBuildCommand_EsbuildFunctions(BuildIntegEsbuildBase): template = "template_with_metadata_esbuild.yaml" @@ -608,6 +625,71 @@ def test_building_default_package_json( self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, architecture) +class TestBuildCommand_EsbuildFunctions_With_External_Manifest(BuildIntegEsbuildBase): + template = "template_with_metadata_esbuild.yaml" + MANIFEST_PATH = "Esbuild/npm_manifest/package.json" + + @parameterized.expand( + [ + ( + "nodejs14.x", + "Esbuild/Node_without_manifest", + {"main.js", "main.js.map"}, + "main.lambdaHandler", + False, + "x86_64", + ), + ( + "nodejs16.x", + "Esbuild/Node_without_manifest", + {"main.js", "main.js.map"}, + "main.lambdaHandler", + False, + "arm64", + ), + ( + "nodejs18.x", + "Esbuild/Node_without_manifest", + {"main.js", "main.js.map"}, + "main.lambdaHandler", + False, + "arm64", + ), + ( + "nodejs14.x", + "Esbuild/TypeScript_without_manifest", + {"app.js", "app.js.map"}, + "app.lambdaHandler", + False, + "x86_64", + ), + ( + "nodejs16.x", + "Esbuild/TypeScript_without_manifest", + {"app.js", "app.js.map"}, + "app.lambdaHandler", + False, + "arm64", + ), + ( + "nodejs18.x", + "Esbuild/TypeScript_without_manifest", + {"app.js", "app.js.map"}, + "app.lambdaHandler", + False, + "arm64", + ), + ] + ) + @pytest.mark.flaky(reruns=3) + def test_building_default_package_json( + self, runtime, code_uri, expected_files, handler, use_container, architecture + ): + if use_container and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): + self.skipTest(SKIP_DOCKER_MESSAGE) + self._test_with_default_package_json(runtime, use_container, code_uri, expected_files, handler, architecture) + + @skipIf( ((IS_WINDOWS and RUNNING_ON_CI) and not CI_OVERRIDE), "Skip build tests on windows when running in CI unless overridden", diff --git a/tests/integration/testdata/buildcmd/Esbuild/Node_without_manifest/main.js b/tests/integration/testdata/buildcmd/Esbuild/Node_without_manifest/main.js new file mode 100644 index 0000000000..5ba65da324 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Esbuild/Node_without_manifest/main.js @@ -0,0 +1,8 @@ +exports.lambdaHandler = async (event, context) => { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world!', + }), + }; +}; diff --git a/tests/integration/testdata/buildcmd/Esbuild/TypeScript_without_manifest/app.ts b/tests/integration/testdata/buildcmd/Esbuild/TypeScript_without_manifest/app.ts new file mode 100644 index 0000000000..84ffc9ea30 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Esbuild/TypeScript_without_manifest/app.ts @@ -0,0 +1,24 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; + +export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise => { + let response: APIGatewayProxyResult; + + try { + response = { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world!', + }), + }; + } catch (err) { + console.log(err); + response = { + statusCode: 500, + body: JSON.stringify({ + message: 'some error happened', + }), + }; + } + + return response; +}; diff --git a/tests/integration/testdata/buildcmd/Esbuild/TypeScript_without_manifest/nested/function/app.ts b/tests/integration/testdata/buildcmd/Esbuild/TypeScript_without_manifest/nested/function/app.ts new file mode 100644 index 0000000000..84ffc9ea30 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Esbuild/TypeScript_without_manifest/nested/function/app.ts @@ -0,0 +1,24 @@ +import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; + +export const lambdaHandler = async (event: APIGatewayProxyEvent): Promise => { + let response: APIGatewayProxyResult; + + try { + response = { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world!', + }), + }; + } catch (err) { + console.log(err); + response = { + statusCode: 500, + body: JSON.stringify({ + message: 'some error happened', + }), + }; + } + + return response; +}; diff --git a/tests/integration/testdata/buildcmd/Esbuild/npm_manifest/package.json b/tests/integration/testdata/buildcmd/Esbuild/npm_manifest/package.json new file mode 100644 index 0000000000..f7ad41fae9 --- /dev/null +++ b/tests/integration/testdata/buildcmd/Esbuild/npm_manifest/package.json @@ -0,0 +1,14 @@ +{ + "name": "npmdeps", + "version": "1.0.0", + "description": "", + "keywords": [], + "author": "", + "license": "APACHE2.0", + "main": "main.js", + "dependencies": { + "@types/aws-lambda": "^8.10.92", + "minimal-request-promise": "*", + "esbuild": "^0.14.14" + } +} \ No newline at end of file diff --git a/tests/integration/testdata/buildcmd/Node_without_manifest/main.js b/tests/integration/testdata/buildcmd/Node_without_manifest/main.js new file mode 100644 index 0000000000..1e03a33abc --- /dev/null +++ b/tests/integration/testdata/buildcmd/Node_without_manifest/main.js @@ -0,0 +1,13 @@ + +exports.lambdaHandler = async (event, context) => { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world!', + }), + } +}; + +exports.secondLambdaHandler = async (event, context) => { + return 'Hello Mars' +}; \ No newline at end of file diff --git a/tests/integration/testdata/buildcmd/npm_manifest/package.json b/tests/integration/testdata/buildcmd/npm_manifest/package.json new file mode 100644 index 0000000000..c95b709aec --- /dev/null +++ b/tests/integration/testdata/buildcmd/npm_manifest/package.json @@ -0,0 +1,11 @@ +{ + "name": "npmdeps", + "version": "1.0.0", + "description": "", + "keywords": [], + "author": "", + "license": "APACHE2.0", + "dependencies": { + "minimal-request-promise": "*" + } +} \ No newline at end of file From 927191cf960f2e4a7964d61e067ed41eb878322f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 21:44:01 +0000 Subject: [PATCH 048/107] chore(deps-dev): bump ruff from 0.0.261 to 0.0.272 in /requirements (#5337) Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.261 to 0.0.272. - [Release notes](https://github.com/charliermarsh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.261...v0.0.272) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/pre-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/pre-dev.txt b/requirements/pre-dev.txt index 2bc0c6f01f..9e3312b611 100644 --- a/requirements/pre-dev.txt +++ b/requirements/pre-dev.txt @@ -1 +1 @@ -ruff==0.0.261 +ruff==0.0.272 From e8a46147638ea9800b3cbb01a1d3769bc66613a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 21:44:23 +0000 Subject: [PATCH 049/107] chore(deps-dev): bump pytest-cov from 4.0.0 to 4.1.0 in /requirements (#5335) Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.0.0 to 4.1.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index f5dcf55592..4ef0736e47 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,7 +1,7 @@ -r pre-dev.txt coverage==7.2.7 -pytest-cov==4.0.0 +pytest-cov==4.1.0 # type checking and related stubs From 4061d6e2d101a04377a5796768b16187fb7537e2 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Mon, 12 Jun 2023 18:01:04 -0700 Subject: [PATCH 050/107] feat: add lambda streaming support for remote invoke (#5307) * feat: support response streaming with remote invoke * add invoker and mappers * Update output formatting of stream response * add unit tests * fix formatting * Add docs * address comments * formatting * move is_function_invoke_mode_response_stream into lambda invoke executors and add/update string constants --- requirements/base.txt | 4 +- .../remote_invoke/lambda_invoke_executors.py | 132 ++++++++-- .../remote_invoke_executor_factory.py | 22 +- .../test_lambda_invoke_executors.py | 236 ++++++++++++++---- .../test_remote_invoke_executor_factory.py | 62 +++-- 5 files changed, 371 insertions(+), 85 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 5632d14797..7fa1b843d5 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,8 +1,8 @@ chevron~=0.12 click~=8.0 Flask<2.3 -#Need to add Schemas latest SDK. -boto3>=1.19.5,==1.* +#Need to add latest lambda changes which will return invoke mode details +boto3>=1.26.109,==1.* jmespath~=1.0.1 ruamel_yaml~=0.17.21 PyYAML>=5.4.1,==5.* diff --git a/samcli/lib/remote_invoke/lambda_invoke_executors.py b/samcli/lib/remote_invoke/lambda_invoke_executors.py index 82e65b8117..30f046127c 100644 --- a/samcli/lib/remote_invoke/lambda_invoke_executors.py +++ b/samcli/lib/remote_invoke/lambda_invoke_executors.py @@ -4,9 +4,11 @@ import base64 import json import logging +from abc import ABC, abstractmethod from json import JSONDecodeError -from typing import Any, Dict, cast +from typing import Any, cast +from botocore.eventstream import EventStream from botocore.exceptions import ClientError, ParamValidationError from botocore.response import StreamingBody @@ -26,12 +28,19 @@ LOG = logging.getLogger(__name__) FUNCTION_NAME = "FunctionName" PAYLOAD = "Payload" +EVENT_STREAM = "EventStream" +PAYLOAD_CHUNK = "PayloadChunk" +INVOKE_COMPLETE = "InvokeComplete" +LOG_RESULT = "LogResult" +INVOKE_MODE = "InvokeMode" +RESPONSE_STREAM = "RESPONSE_STREAM" -class LambdaInvokeExecutor(BotoActionExecutor): + +class AbstractLambdaInvokeExecutor(BotoActionExecutor, ABC): """ - Calls "invoke" method of "lambda" service with given input. - If a file location provided, the file handle will be passed as Payload object + Abstract class for different lambda invocation executors, see implementation for details. + For Payload parameter, if a file location provided, the file handle will be passed as Payload object """ _lambda_client: Any @@ -59,14 +68,9 @@ def validate_action_parameters(self, parameters: dict) -> None: def _execute_action(self, payload: str): self.request_parameters[FUNCTION_NAME] = self._function_name self.request_parameters[PAYLOAD] = payload - LOG.debug( - "Calling lambda_client.invoke with FunctionName:%s, Payload:%s, parameters:%s", - self._function_name, - payload, - self.request_parameters, - ) + try: - response = self._lambda_client.invoke(**self.request_parameters) + return self._execute_lambda_invoke(payload) except ParamValidationError as param_val_ex: raise InvalidResourceBotoParameterException( f"Invalid parameter key provided." @@ -80,7 +84,40 @@ def _execute_action(self, payload: str): elif boto_utils.get_client_error_code(client_ex) == "InvalidRequestContentException": raise InvalidResourceBotoParameterException(client_ex) from client_ex raise ErrorBotoApiCallException(client_ex) from client_ex - return response + + @abstractmethod + def _execute_lambda_invoke(self, payload: str): + pass + + +class LambdaInvokeExecutor(AbstractLambdaInvokeExecutor): + """ + Calls "invoke" method of "lambda" service with given input. + """ + + def _execute_lambda_invoke(self, payload: str) -> dict: + LOG.debug( + "Calling lambda_client.invoke with FunctionName:%s, Payload:%s, parameters:%s", + self._function_name, + payload, + self.request_parameters, + ) + return cast(dict, self._lambda_client.invoke(**self.request_parameters)) + + +class LambdaInvokeWithResponseStreamExecutor(AbstractLambdaInvokeExecutor): + """ + Calls "invoke_with_response_stream" method of "lambda" service with given input. + """ + + def _execute_lambda_invoke(self, payload: str) -> dict: + LOG.debug( + "Calling lambda_client.invoke_with_response_stream with FunctionName:%s, Payload:%s, parameters:%s", + self._function_name, + payload, + self.request_parameters, + ) + return cast(dict, self._lambda_client.invoke_with_response_stream(**self.request_parameters)) class DefaultConvertToJSON(RemoteInvokeRequestResponseMapper): @@ -124,6 +161,31 @@ def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExe return remote_invoke_input +class LambdaStreamResponseConverter(RemoteInvokeRequestResponseMapper): + """ + This class helps to convert response from lambda invoke_with_response_stream API call. + That API call returns 'EventStream' which yields 'PayloadChunk's and 'InvokeComplete' as they become available. + This mapper, gets all 'PayloadChunk's and 'InvokeComplete' events and decodes them for next mapper. + """ + + def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + LOG.debug("Mapping Lambda response to string object") + if not isinstance(remote_invoke_input.response, dict): + raise InvalideBotoResponseException("Invalid response type received from Lambda service, expecting dict") + + event_stream: EventStream = remote_invoke_input.response.get(EVENT_STREAM, []) + decoded_event_stream = [] + for event in event_stream: + if PAYLOAD_CHUNK in event: + decoded_payload_chunk = event.get(PAYLOAD_CHUNK).get(PAYLOAD).decode("utf-8") + decoded_event_stream.append({PAYLOAD_CHUNK: {PAYLOAD: decoded_payload_chunk}}) + if INVOKE_COMPLETE in event: + log_output = event.get(INVOKE_COMPLETE).get(LOG_RESULT, b"") + decoded_event_stream.append({INVOKE_COMPLETE: {LOG_RESULT: log_output}}) + remote_invoke_input.response[EVENT_STREAM] = decoded_event_stream + return remote_invoke_input + + class LambdaResponseOutputFormatter(RemoteInvokeRequestResponseMapper): """ This class helps to format output response for lambda service that will be printed on the CLI. @@ -139,8 +201,8 @@ def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExe """ if remote_invoke_input.output_format == RemoteInvokeOutputFormat.DEFAULT: LOG.debug("Formatting Lambda output response") - boto_response = cast(Dict, remote_invoke_input.response) - log_field = boto_response.get("LogResult") + boto_response = cast(dict, remote_invoke_input.response) + log_field = boto_response.get(LOG_RESULT) if log_field: log_result = base64.b64decode(log_field).decode("utf-8") remote_invoke_input.log_output = log_result @@ -152,3 +214,45 @@ def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExe remote_invoke_input.response = boto_response.get(PAYLOAD) return remote_invoke_input + + +class LambdaStreamResponseOutputFormatter(RemoteInvokeRequestResponseMapper): + """ + This class helps to format streaming output response for lambda service that will be printed on the CLI. + It loops through EventStream elements and adds them to response, and once InvokeComplete is reached, it updates + log_output and response objects in remote_invoke_input. + """ + + def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + """ + Maps the lambda response output to the type of output format specified as user input. + If output_format is original-boto-response, write the original boto API response + to stdout. + """ + if remote_invoke_input.output_format == RemoteInvokeOutputFormat.DEFAULT: + LOG.debug("Formatting Lambda output response") + boto_response = cast(dict, remote_invoke_input.response) + combined_response = "" + for event in boto_response.get(EVENT_STREAM, []): + if PAYLOAD_CHUNK in event: + payload_chunk = event.get(PAYLOAD_CHUNK).get(PAYLOAD) + combined_response = f"{combined_response}{payload_chunk}" + if INVOKE_COMPLETE in event: + log_result = base64.b64decode(event.get(INVOKE_COMPLETE).get(LOG_RESULT)).decode("utf-8") + remote_invoke_input.log_output = log_result + remote_invoke_input.response = combined_response + return remote_invoke_input + + +def _is_function_invoke_mode_response_stream(lambda_client: Any, function_name: str): + """ + Returns True if given function has RESPONSE_STREAM as InvokeMode, False otherwise + """ + try: + function_url_config = lambda_client.get_function_url_config(FunctionName=function_name) + function_invoke_mode = function_url_config.get(INVOKE_MODE) + LOG.debug("InvokeMode of function %s: %s", function_name, function_invoke_mode) + return function_invoke_mode == RESPONSE_STREAM + except ClientError as ex: + LOG.debug("Function %s, doesn't have Function URL configured, using regular invoke", function_name, exc_info=ex) + return False diff --git a/samcli/lib/remote_invoke/remote_invoke_executor_factory.py b/samcli/lib/remote_invoke/remote_invoke_executor_factory.py index a30a9532db..33ec958e1f 100644 --- a/samcli/lib/remote_invoke/remote_invoke_executor_factory.py +++ b/samcli/lib/remote_invoke/remote_invoke_executor_factory.py @@ -7,8 +7,12 @@ from samcli.lib.remote_invoke.lambda_invoke_executors import ( DefaultConvertToJSON, LambdaInvokeExecutor, + LambdaInvokeWithResponseStreamExecutor, LambdaResponseConverter, LambdaResponseOutputFormatter, + LambdaStreamResponseConverter, + LambdaStreamResponseOutputFormatter, + _is_function_invoke_mode_response_stream, ) from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutor, ResponseObjectToJsonStringMapper from samcli.lib.utils.cloudformation import CloudFormationResourceSummary @@ -64,6 +68,22 @@ def _create_lambda_boto_executor(self, cfn_resource_summary: CloudFormationResou :return: Returns the created remote invoke Executor """ + lambda_client = self._boto_client_provider("lambda") + if _is_function_invoke_mode_response_stream(lambda_client, cfn_resource_summary.physical_resource_id): + LOG.debug("Creating response stream invocator for function %s", cfn_resource_summary.physical_resource_id) + return RemoteInvokeExecutor( + request_mappers=[DefaultConvertToJSON()], + response_mappers=[ + LambdaStreamResponseConverter(), + LambdaStreamResponseOutputFormatter(), + ResponseObjectToJsonStringMapper(), + ], + boto_action_executor=LambdaInvokeWithResponseStreamExecutor( + lambda_client, + cfn_resource_summary.physical_resource_id, + ), + ) + return RemoteInvokeExecutor( request_mappers=[DefaultConvertToJSON()], response_mappers=[ @@ -72,7 +92,7 @@ def _create_lambda_boto_executor(self, cfn_resource_summary: CloudFormationResou ResponseObjectToJsonStringMapper(), ], boto_action_executor=LambdaInvokeExecutor( - self._boto_client_provider("lambda"), + lambda_client, cfn_resource_summary.physical_resource_id, ), ) diff --git a/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py b/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py index b719b77da5..15ff272bac 100644 --- a/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py +++ b/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py @@ -1,23 +1,96 @@ +import base64 +from abc import ABC, abstractmethod +from typing import Any from unittest import TestCase from unittest.mock import Mock, patch + from parameterized import parameterized from samcli.lib.remote_invoke.lambda_invoke_executors import ( - LambdaInvokeExecutor, + EVENT_STREAM, + INVOKE_COMPLETE, + LOG_RESULT, + PAYLOAD, + PAYLOAD_CHUNK, + AbstractLambdaInvokeExecutor, + ClientError, DefaultConvertToJSON, - LambdaResponseConverter, - LambdaResponseOutputFormatter, ErrorBotoApiCallException, - InvalidResourceBotoParameterException, InvalideBotoResponseException, - RemoteInvokeOutputFormat, - ClientError, + InvalidResourceBotoParameterException, + LambdaInvokeExecutor, + LambdaInvokeWithResponseStreamExecutor, + LambdaResponseConverter, + LambdaResponseOutputFormatter, + LambdaStreamResponseConverter, + LambdaStreamResponseOutputFormatter, ParamValidationError, + RemoteInvokeOutputFormat, + _is_function_invoke_mode_response_stream, ) from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutionInfo -class TestLambdaInvokeExecutor(TestCase): +class CommonTestsLambdaInvokeExecutor: + class AbstractLambdaInvokeExecutorTest(ABC, TestCase): + lambda_client: Any + lambda_invoke_executor: AbstractLambdaInvokeExecutor + + @abstractmethod + def _get_boto3_method(self): + pass + + @parameterized.expand( + [ + ("ValidationException",), + ("InvalidRequestContentException",), + ] + ) + def test_execute_action_invalid_parameter_value_throws_client_error(self, error_code): + given_payload = Mock() + error = ClientError(error_response={"Error": {"Code": error_code}}, operation_name="invoke") + self._get_boto3_method().side_effect = error + with self.assertRaises(InvalidResourceBotoParameterException): + self.lambda_invoke_executor._execute_action(given_payload) + + def test_execute_action_invalid_parameter_key_throws_parameter_validation_exception(self): + given_payload = Mock() + error = ParamValidationError(report="Invalid parameters") + self._get_boto3_method().side_effect = error + with self.assertRaises(InvalidResourceBotoParameterException): + self.lambda_invoke_executor._execute_action(given_payload) + + def test_execute_action_throws_client_error_exception(self): + given_payload = Mock() + error = ClientError(error_response={"Error": {"Code": "MockException"}}, operation_name="invoke") + self._get_boto3_method().side_effect = error + with self.assertRaises(ErrorBotoApiCallException): + self.lambda_invoke_executor._execute_action(given_payload) + + @parameterized.expand( + [ + ({}, {"InvocationType": "RequestResponse", "LogType": "Tail"}), + ({"InvocationType": "Event"}, {"InvocationType": "Event", "LogType": "Tail"}), + ( + {"InvocationType": "DryRun", "Qualifier": "TestQualifier"}, + {"InvocationType": "DryRun", "LogType": "Tail", "Qualifier": "TestQualifier"}, + ), + ( + {"InvocationType": "RequestResponse", "LogType": "None"}, + {"InvocationType": "RequestResponse", "LogType": "None"}, + ), + ( + {"FunctionName": "MyFunction", "Payload": "{hello world}"}, + {"InvocationType": "RequestResponse", "LogType": "Tail"}, + ), + ] + ) + def test_validate_action_parameters(self, parameters, expected_boto_parameters): + self.lambda_invoke_executor.validate_action_parameters(parameters) + self.assertEqual(self.lambda_invoke_executor.request_parameters, expected_boto_parameters) + + +class TestLambdaInvokeExecutor(CommonTestsLambdaInvokeExecutor.AbstractLambdaInvokeExecutorTest): def setUp(self) -> None: self.lambda_client = Mock() self.function_name = Mock() @@ -35,54 +108,30 @@ def test_execute_action(self): FunctionName=self.function_name, Payload=given_payload, InvocationType="RequestResponse", LogType="Tail" ) - @parameterized.expand( - [ - ("ValidationException",), - ("InvalidRequestContentException",), - ] - ) - def test_execute_action_invalid_parameter_value_throws_client_error(self, error_code): - given_payload = Mock() - error = ClientError(error_response={"Error": {"Code": error_code}}, operation_name="invoke") - self.lambda_client.invoke.side_effect = error - with self.assertRaises(InvalidResourceBotoParameterException): - self.lambda_invoke_executor._execute_action(given_payload) + def _get_boto3_method(self): + return self.lambda_client.invoke - def test_execute_action_invalid_parameter_key_throws_parameter_validation_exception(self): - given_payload = Mock() - error = ParamValidationError(report="Invalid parameters") - self.lambda_client.invoke.side_effect = error - with self.assertRaises(InvalidResourceBotoParameterException): - self.lambda_invoke_executor._execute_action(given_payload) - def test_execute_action_throws_client_error_exception(self): +class TestLambdaInvokeWithResponseStreamExecutor(CommonTestsLambdaInvokeExecutor.AbstractLambdaInvokeExecutorTest): + def setUp(self) -> None: + self.lambda_client = Mock() + self.function_name = Mock() + self.lambda_invoke_executor = LambdaInvokeWithResponseStreamExecutor(self.lambda_client, self.function_name) + + def test_execute_action(self): given_payload = Mock() - error = ClientError(error_response={"Error": {"Code": "MockException"}}, operation_name="invoke") - self.lambda_client.invoke.side_effect = error - with self.assertRaises(ErrorBotoApiCallException): - self.lambda_invoke_executor._execute_action(given_payload) + given_result = Mock() + self.lambda_client.invoke_with_response_stream.return_value = given_result - @parameterized.expand( - [ - ({}, {"InvocationType": "RequestResponse", "LogType": "Tail"}), - ({"InvocationType": "Event"}, {"InvocationType": "Event", "LogType": "Tail"}), - ( - {"InvocationType": "DryRun", "Qualifier": "TestQualifier"}, - {"InvocationType": "DryRun", "LogType": "Tail", "Qualifier": "TestQualifier"}, - ), - ( - {"InvocationType": "RequestResponse", "LogType": "None"}, - {"InvocationType": "RequestResponse", "LogType": "None"}, - ), - ( - {"FunctionName": "MyFunction", "Payload": "{hello world}"}, - {"InvocationType": "RequestResponse", "LogType": "Tail"}, - ), - ] - ) - def test_validate_action_parameters(self, parameters, expected_boto_parameters): - self.lambda_invoke_executor.validate_action_parameters(parameters) - self.assertEqual(self.lambda_invoke_executor.request_parameters, expected_boto_parameters) + result = self.lambda_invoke_executor._execute_action(given_payload) + + self.assertEqual(result, given_result) + self.lambda_client.invoke_with_response_stream.assert_called_with( + FunctionName=self.function_name, Payload=given_payload, InvocationType="RequestResponse", LogType="Tail" + ) + + def _get_boto3_method(self): + return self.lambda_client.invoke_with_response_stream class TestDefaultConvertToJSON(TestCase): @@ -143,6 +192,46 @@ def test_lambda_streaming_body_invalid_response_exception(self): self.lambda_response_converter.map(remote_invoke_execution_info) +class TestLambdaStreamResponseConverter(TestCase): + def setUp(self) -> None: + self.lambda_stream_response_converter = LambdaStreamResponseConverter() + + @parameterized.expand([({LOG_RESULT: base64.b64encode(b"log output")}, base64.b64encode(b"log output")), ({}, b"")]) + def test_lambda_streaming_body_response_conversion(self, invoke_complete_response, mapped_log_response): + output_format = RemoteInvokeOutputFormat.DEFAULT + given_test_result = { + EVENT_STREAM: [ + {PAYLOAD_CHUNK: {PAYLOAD: b"stream1"}}, + {PAYLOAD_CHUNK: {PAYLOAD: b"stream2"}}, + {PAYLOAD_CHUNK: {PAYLOAD: b"stream3"}}, + {INVOKE_COMPLETE: invoke_complete_response}, + ] + } + remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, output_format) + remote_invoke_execution_info.response = given_test_result + + expected_result = { + EVENT_STREAM: [ + {PAYLOAD_CHUNK: {PAYLOAD: "stream1"}}, + {PAYLOAD_CHUNK: {PAYLOAD: "stream2"}}, + {PAYLOAD_CHUNK: {PAYLOAD: "stream3"}}, + {INVOKE_COMPLETE: {LOG_RESULT: mapped_log_response}}, + ] + } + + result = self.lambda_stream_response_converter.map(remote_invoke_execution_info) + + self.assertEqual(result.response, expected_result) + + def test_lambda_streaming_body_invalid_response_exception(self): + output_format = RemoteInvokeOutputFormat.DEFAULT + remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, output_format) + remote_invoke_execution_info.response = Mock() + + with self.assertRaises(InvalideBotoResponseException): + self.lambda_stream_response_converter.map(remote_invoke_execution_info) + + class TestLambdaResponseOutputFormatter(TestCase): def setUp(self) -> None: self.lambda_response_converter = LambdaResponseOutputFormatter() @@ -191,3 +280,48 @@ def test_non_default_invocation_type_output_formatter(self, parameters): result = self.lambda_response_converter.map(remote_invoke_execution_info) self.assertEqual(result.response, expected_result) + + +class TestLambdaStreamResponseOutputFormatter(TestCase): + def setUp(self) -> None: + self.lambda_response_converter = LambdaStreamResponseOutputFormatter() + + def test_none_event_stream(self): + remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, RemoteInvokeOutputFormat.DEFAULT) + remote_invoke_execution_info.response = {} + + mapped_response = self.lambda_response_converter.map(remote_invoke_execution_info) + self.assertEqual(mapped_response.response, "") + + def test_event_stream(self): + remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, RemoteInvokeOutputFormat.DEFAULT) + remote_invoke_execution_info.response = { + EVENT_STREAM: [ + {PAYLOAD_CHUNK: {PAYLOAD: "stream1"}}, + {PAYLOAD_CHUNK: {PAYLOAD: "stream2"}}, + {PAYLOAD_CHUNK: {PAYLOAD: "stream3"}}, + {INVOKE_COMPLETE: {LOG_RESULT: base64.b64encode(b"log output")}}, + ] + } + + mapped_response = self.lambda_response_converter.map(remote_invoke_execution_info) + self.assertEqual(mapped_response.response, "stream1stream2stream3") + self.assertEqual(mapped_response.log_output, "log output") + + +class TestLambdaInvokeExecutorUtilities(TestCase): + @parameterized.expand( + [ + ({}, False), + ({"InvokeMode": "BUFFERED"}, False), + ({"InvokeMode": "RESPONSE_STREAM"}, True), + (ClientError({}, "operation"), False), + ] + ) + def test_is_function_invoke_mode_response_stream(self, boto_response, expected_result): + given_boto_client = Mock() + if type(boto_response) is ClientError: + given_boto_client.get_function_url_config.side_effect = boto_response + else: + given_boto_client.get_function_url_config.return_value = boto_response + self.assertEqual(_is_function_invoke_mode_response_stream(given_boto_client, "function_id"), expected_result) diff --git a/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py b/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py index 6ba12c409f..3a1f938e19 100644 --- a/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py +++ b/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py @@ -1,7 +1,11 @@ from unittest import TestCase from unittest.mock import patch, Mock -from samcli.lib.remote_invoke.remote_invoke_executor_factory import RemoteInvokeExecutorFactory +from parameterized import parameterized + +from samcli.lib.remote_invoke.remote_invoke_executor_factory import ( + RemoteInvokeExecutorFactory, +) class TestRemoteInvokeExecutorFactory(TestCase): @@ -33,21 +37,32 @@ def test_failed_create_test_executor(self): executor = self.remote_invoke_executor_factory.create_remote_invoke_executor(given_cfn_resource_summary) self.assertIsNone(executor) + @parameterized.expand([(True,), (False,)]) @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaInvokeExecutor") + @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaInvokeWithResponseStreamExecutor") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.DefaultConvertToJSON") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaResponseConverter") + @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaStreamResponseConverter") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaResponseOutputFormatter") + @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaStreamResponseOutputFormatter") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.ResponseObjectToJsonStringMapper") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.RemoteInvokeExecutor") + @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory._is_function_invoke_mode_response_stream") def test_create_lambda_test_executor( self, + is_function_invoke_mode_response_stream, + patched_is_function_invoke_mode_response_stream, patched_remote_invoke_executor, patched_object_to_json_converter, + patched_stream_response_output_formatter, patched_response_output_formatter, + patched_stream_response_converter, patched_response_converter, patched_convert_to_default_json, + patched_lambda_invoke_with_response_stream_executor, patched_lambda_invoke_executor, ): + patched_is_function_invoke_mode_response_stream.return_value = is_function_invoke_mode_response_stream given_physical_resource_id = "physical_resource_id" given_cfn_resource_summary = Mock(physical_resource_id=given_physical_resource_id) @@ -60,20 +75,33 @@ def test_create_lambda_test_executor( lambda_executor = self.remote_invoke_executor_factory._create_lambda_boto_executor(given_cfn_resource_summary) self.assertEqual(lambda_executor, given_remote_invoke_executor) - - patched_convert_to_default_json.assert_called_once() - patched_response_output_formatter.assert_called_once() - patched_response_converter.assert_called_once() - self.boto_client_provider_mock.assert_called_with("lambda") - patched_lambda_invoke_executor.assert_called_with(given_lambda_client, given_physical_resource_id) - - patched_remote_invoke_executor.assert_called_with( - request_mappers=[patched_convert_to_default_json()], - response_mappers=[ - patched_response_converter(), - patched_response_output_formatter(), - patched_object_to_json_converter(), - ], - boto_action_executor=patched_lambda_invoke_executor(), - ) + patched_convert_to_default_json.assert_called_once() + patched_object_to_json_converter.assert_called_once() + + if is_function_invoke_mode_response_stream: + patched_stream_response_output_formatter.assert_called_once() + patched_stream_response_converter.assert_called_once() + patched_lambda_invoke_with_response_stream_executor.assert_called_once() + patched_remote_invoke_executor.assert_called_with( + request_mappers=[patched_convert_to_default_json()], + response_mappers=[ + patched_stream_response_converter(), + patched_stream_response_output_formatter(), + patched_object_to_json_converter(), + ], + boto_action_executor=patched_lambda_invoke_with_response_stream_executor(), + ) + else: + patched_response_output_formatter.assert_called_once() + patched_response_converter.assert_called_once() + patched_lambda_invoke_executor.assert_called_with(given_lambda_client, given_physical_resource_id) + patched_remote_invoke_executor.assert_called_with( + request_mappers=[patched_convert_to_default_json()], + response_mappers=[ + patched_response_converter(), + patched_response_output_formatter(), + patched_object_to_json_converter(), + ], + boto_action_executor=patched_lambda_invoke_executor(), + ) From b9b9412b8680008b4f1b51a6d1db3323023b745d Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:06:07 -0700 Subject: [PATCH 051/107] chore: bump version to 1.87.0 --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index dd9f8681e8..dba460a2bc 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.86.1" +__version__ = "1.87.0" From 2dc639039e2451060ae02a579fd8b1df80ce1e7a Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Tue, 13 Jun 2023 12:28:41 -0700 Subject: [PATCH 052/107] Revert app templates gha (#5356) * Revert "add sleep between close and reopen (#5320)" This reverts commit 5be690c88d580cfeee7731f549c75ed7543f47c5. * Revert "update automated updates gha to force restart of status checks (#5269)" This reverts commit deb212bc21eda2be0290e9a30f296aa74331e6c3. --- .../automated-updates-to-sam-cli.yml | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/.github/workflows/automated-updates-to-sam-cli.yml b/.github/workflows/automated-updates-to-sam-cli.yml index 4aadea2967..45c49f727a 100644 --- a/.github/workflows/automated-updates-to-sam-cli.yml +++ b/.github/workflows/automated-updates-to-sam-cli.yml @@ -48,11 +48,7 @@ jobs: run: | cd aws-sam-cli git push --force origin update_app_templates_hash - gh pr list --repo aws/aws-sam-cli --head update_app_templates_hash --json id --jq length | grep 1 && \ - gh pr close update_app_templates_hash --repo aws/aws-sam-cli && \ - sleep 2 && \ - gh pr reopen update_app_templates_hash --repo aws/aws-sam-cli && \ - exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit + gh pr list --repo aws/aws-sam-cli --head update_app_templates_hash --json id --jq length | grep 1 && exit 0 # exit if there is existing pr gh pr create --base develop --head update_app_templates_hash --title "feat: update SAM CLI with latest App Templates commit hash" --body "This PR & commit is automatically created from App Templates repo to update the SAM CLI with latest hash of the App Templates." --label "pr/internal" updateSAMTranslator: @@ -69,13 +65,13 @@ jobs: path: serverless-application-model ref: main fetch-depth: 0 - + - name: Checkout SAM CLI uses: actions/checkout@v3 with: repository: aws/aws-sam-cli path: aws-sam-cli - + - uses: actions/setup-python@v4 # used for make update-reproducible-reqs below with: python-version: | @@ -109,11 +105,7 @@ jobs: run: | cd aws-sam-cli git push --force origin update_sam_transform_version - gh pr list --repo aws/aws-sam-cli --head update_sam_transform_version --json id --jq length | grep 1 && \ - gh pr close update_sam_transform_version --repo aws/aws-sam-cli && \ - sleep 2 && \ - gh pr reopen update_sam_transform_version --repo aws/aws-sam-cli && \ - exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit + gh pr list --repo aws/aws-sam-cli --head update_sam_transform_version --json id --jq length | grep 1 && exit 0 # exit if there is existing pr gh pr create --base develop --head update_sam_transform_version --fill --label "pr/internal" updateAWSLambdaBuilders: @@ -169,9 +161,5 @@ jobs: run: | cd aws-sam-cli git push --force origin update_lambda_builders_version - gh pr list --repo aws/aws-sam-cli --head update_lambda_builders_version --json id --jq length | grep 1 && \ - gh pr close update_lambda_builders_version --repo aws/aws-sam-cli && \ - sleep 2 && \ - gh pr reopen update_lambda_builders_version --repo aws/aws-sam-cli && \ - exit 0 # if there is exisitng pr, close/reopen to re-run checks, then exit + gh pr list --repo aws/aws-sam-cli --head update_lambda_builders_version --json id --jq length | grep 1 && exit 0 # exit if there is existing pr gh pr create --base develop --head update_lambda_builders_version --fill --label "pr/internal" From 77d2b95b8806638b9093a7a22fd42dc7a6d45ece Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Wed, 14 Jun 2023 10:01:50 -0700 Subject: [PATCH 053/107] refactor: make remote invoke reactive to display results as soon as they are available (#5359) * refactor: make remote invoke reactive to display results as soon as they are available * addressed the comments --- samcli/commands/remote/invoke/cli.py | 13 +- .../commands/remote/remote_invoke_context.py | 48 +++++-- .../remote_invoke/lambda_invoke_executors.py | 114 ++++++----------- .../remote_invoke_executor_factory.py | 85 +++++++++---- .../remote_invoke/remote_invoke_executors.py | 109 ++++++++++------ tests/unit/commands/remote/invoke/test_cli.py | 11 +- .../remote/test_remote_invoke_context.py | 5 +- .../test_lambda_invoke_executors.py | 118 ++++-------------- .../test_remote_invoke_executor_factory.py | 79 ++++++++---- .../test_remote_invoke_executors.py | 28 ++--- 10 files changed, 305 insertions(+), 305 deletions(-) diff --git a/samcli/commands/remote/invoke/cli.py b/samcli/commands/remote/invoke/cli.py index d3ebd5d36f..4f7ed1b081 100644 --- a/samcli/commands/remote/invoke/cli.py +++ b/samcli/commands/remote/invoke/cli.py @@ -1,7 +1,6 @@ """CLI command for "invoke" command.""" import logging from io import TextIOWrapper -from typing import cast import click @@ -124,16 +123,6 @@ def do_cli( payload=event, payload_file=event_file, parameters=parameter, output_format=output_format ) - remote_invoke_result = remote_invoke_context.run(remote_invoke_input=remote_invoke_input) - - if remote_invoke_result.is_succeeded(): - LOG.debug("Invoking resource was successfull, writing response to stdout") - if remote_invoke_result.log_output: - LOG.debug("Writing log output to stderr") - remote_invoke_context.stderr.write(remote_invoke_result.log_output.encode()) - output_response = cast(str, remote_invoke_result.response) - remote_invoke_context.stdout.write(output_response.encode()) - else: - raise cast(Exception, remote_invoke_result.exception) + remote_invoke_context.run(remote_invoke_input=remote_invoke_input) except (ErrorBotoApiCallException, InvalideBotoResponseException, InvalidResourceBotoParameterException) as ex: raise UserException(str(ex), wrapped_from=ex.__class__.__name__) from ex diff --git a/samcli/commands/remote/remote_invoke_context.py b/samcli/commands/remote/remote_invoke_context.py index 254f504b33..c1ca48193d 100644 --- a/samcli/commands/remote/remote_invoke_context.py +++ b/samcli/commands/remote/remote_invoke_context.py @@ -2,6 +2,7 @@ Context object used by `sam remote invoke` command """ import logging +from dataclasses import dataclass from typing import Optional, cast from botocore.exceptions import ClientError @@ -15,7 +16,12 @@ UnsupportedServiceForRemoteInvoke, ) from samcli.lib.remote_invoke.remote_invoke_executor_factory import RemoteInvokeExecutorFactory -from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutionInfo +from samcli.lib.remote_invoke.remote_invoke_executors import ( + RemoteInvokeConsumer, + RemoteInvokeExecutionInfo, + RemoteInvokeLogOutput, + RemoteInvokeResponse, +) from samcli.lib.utils import osutils from samcli.lib.utils.arn_utils import ARNParts, InvalidArnValue from samcli.lib.utils.boto_utils import BotoProviderType, get_client_error_code @@ -61,7 +67,7 @@ def __enter__(self) -> "RemoteInvokeContext": def __exit__(self, *args) -> None: pass - def run(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + def run(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> None: """ Instantiates remote invoke executor with populated resource summary information, executes it with the provided input & returns its response back to the caller. If no executor can be instantiated it raises @@ -72,11 +78,6 @@ def run(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExe remote_invoke_input: RemoteInvokeExecutionInfo RemoteInvokeExecutionInfo which contains the payload and other information that will be required during the invocation - - Returns - ------- - RemoteInvokeExecutionInfo - Populates result and exception info (if any) and returns back to the caller """ if not self._resource_summary: raise AmbiguousResourceForRemoteInvoke( @@ -85,13 +86,18 @@ def run(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExe ) remote_invoke_executor_factory = RemoteInvokeExecutorFactory(self._boto_client_provider) - remote_invoke_executor = remote_invoke_executor_factory.create_remote_invoke_executor(self._resource_summary) + remote_invoke_executor = remote_invoke_executor_factory.create_remote_invoke_executor( + self._resource_summary, + remote_invoke_input.output_format, + DefaultRemoteInvokeResponseConsumer(self.stdout), + DefaultRemoteInvokeLogConsumer(self.stderr), + ) if not remote_invoke_executor: raise NoExecutorFoundForRemoteInvoke( f"Resource type {self._resource_summary.resource_type} is not supported for remote invoke" ) - return remote_invoke_executor.execute(remote_invoke_input) + remote_invoke_executor.execute(remote_invoke_input) def _populate_resource_summary(self) -> None: """ @@ -225,3 +231,27 @@ def stderr(self) -> StreamWriter: """ stream = osutils.stderr() return StreamWriter(stream, auto_flush=True) + + +@dataclass +class DefaultRemoteInvokeResponseConsumer(RemoteInvokeConsumer[RemoteInvokeResponse]): + """ + Default RemoteInvokeResponse consumer, writes given response event to the configured StreamWriter + """ + + _stream_writer: StreamWriter + + def consume(self, remote_invoke_response: RemoteInvokeResponse) -> None: + self._stream_writer.write(cast(str, remote_invoke_response.response).encode()) + + +@dataclass +class DefaultRemoteInvokeLogConsumer(RemoteInvokeConsumer[RemoteInvokeLogOutput]): + """ + Default RemoteInvokeLogOutput consumer, writes given log event to the configured StreamWriter + """ + + _stream_writer: StreamWriter + + def consume(self, remote_invoke_response: RemoteInvokeLogOutput) -> None: + self._stream_writer.write(remote_invoke_response.log_output.encode()) diff --git a/samcli/lib/remote_invoke/lambda_invoke_executors.py b/samcli/lib/remote_invoke/lambda_invoke_executors.py index 30f046127c..936cd89289 100644 --- a/samcli/lib/remote_invoke/lambda_invoke_executors.py +++ b/samcli/lib/remote_invoke/lambda_invoke_executors.py @@ -20,8 +20,11 @@ from samcli.lib.remote_invoke.remote_invoke_executors import ( BotoActionExecutor, RemoteInvokeExecutionInfo, + RemoteInvokeIterableResponseType, + RemoteInvokeLogOutput, RemoteInvokeOutputFormat, RemoteInvokeRequestResponseMapper, + RemoteInvokeResponse, ) from samcli.lib.utils import boto_utils @@ -45,10 +48,12 @@ class AbstractLambdaInvokeExecutor(BotoActionExecutor, ABC): _lambda_client: Any _function_name: str + _remote_output_format: RemoteInvokeOutputFormat - def __init__(self, lambda_client: Any, function_name: str): + def __init__(self, lambda_client: Any, function_name: str, remote_output_format: RemoteInvokeOutputFormat): self._lambda_client = lambda_client self._function_name = function_name + self._remote_output_format = remote_output_format self.request_parameters = {"InvocationType": "RequestResponse", "LogType": "Tail"} def validate_action_parameters(self, parameters: dict) -> None: @@ -65,12 +70,15 @@ def validate_action_parameters(self, parameters: dict) -> None: else: self.request_parameters[parameter_key] = parameter_value - def _execute_action(self, payload: str): + def _execute_action(self, payload: str) -> RemoteInvokeIterableResponseType: self.request_parameters[FUNCTION_NAME] = self._function_name self.request_parameters[PAYLOAD] = payload + return self._execute_lambda_invoke(payload) + + def _execute_boto_call(self, boto_client_method) -> dict: try: - return self._execute_lambda_invoke(payload) + return cast(dict, boto_client_method(**self.request_parameters)) except ParamValidationError as param_val_ex: raise InvalidResourceBotoParameterException( f"Invalid parameter key provided." @@ -86,8 +94,8 @@ def _execute_action(self, payload: str): raise ErrorBotoApiCallException(client_ex) from client_ex @abstractmethod - def _execute_lambda_invoke(self, payload: str): - pass + def _execute_lambda_invoke(self, payload: str) -> RemoteInvokeIterableResponseType: + raise NotImplementedError() class LambdaInvokeExecutor(AbstractLambdaInvokeExecutor): @@ -95,14 +103,21 @@ class LambdaInvokeExecutor(AbstractLambdaInvokeExecutor): Calls "invoke" method of "lambda" service with given input. """ - def _execute_lambda_invoke(self, payload: str) -> dict: + def _execute_lambda_invoke(self, payload: str) -> RemoteInvokeIterableResponseType: LOG.debug( "Calling lambda_client.invoke with FunctionName:%s, Payload:%s, parameters:%s", self._function_name, payload, self.request_parameters, ) - return cast(dict, self._lambda_client.invoke(**self.request_parameters)) + lambda_response = self._execute_boto_call(self._lambda_client.invoke) + if self._remote_output_format == RemoteInvokeOutputFormat.RAW: + yield RemoteInvokeResponse(lambda_response) + if self._remote_output_format == RemoteInvokeOutputFormat.DEFAULT: + log_result = lambda_response.get(LOG_RESULT) + if log_result: + yield RemoteInvokeLogOutput(base64.b64decode(log_result).decode("utf-8")) + yield RemoteInvokeResponse(cast(StreamingBody, lambda_response.get(PAYLOAD)).read().decode("utf-8")) class LambdaInvokeWithResponseStreamExecutor(AbstractLambdaInvokeExecutor): @@ -110,17 +125,29 @@ class LambdaInvokeWithResponseStreamExecutor(AbstractLambdaInvokeExecutor): Calls "invoke_with_response_stream" method of "lambda" service with given input. """ - def _execute_lambda_invoke(self, payload: str) -> dict: + def _execute_lambda_invoke(self, payload: str) -> RemoteInvokeIterableResponseType: LOG.debug( "Calling lambda_client.invoke_with_response_stream with FunctionName:%s, Payload:%s, parameters:%s", self._function_name, payload, self.request_parameters, ) - return cast(dict, self._lambda_client.invoke_with_response_stream(**self.request_parameters)) + lambda_response = self._execute_boto_call(self._lambda_client.invoke_with_response_stream) + if self._remote_output_format == RemoteInvokeOutputFormat.RAW: + yield RemoteInvokeResponse(lambda_response) + if self._remote_output_format == RemoteInvokeOutputFormat.DEFAULT: + event_stream: EventStream = lambda_response.get(EVENT_STREAM, []) + for event in event_stream: + if PAYLOAD_CHUNK in event: + yield RemoteInvokeResponse(event.get(PAYLOAD_CHUNK).get(PAYLOAD).decode("utf-8")) + if INVOKE_COMPLETE in event: + if LOG_RESULT in event.get(INVOKE_COMPLETE): + yield RemoteInvokeLogOutput( + base64.b64decode(event.get(INVOKE_COMPLETE).get(LOG_RESULT)).decode("utf-8") + ) -class DefaultConvertToJSON(RemoteInvokeRequestResponseMapper): +class DefaultConvertToJSON(RemoteInvokeRequestResponseMapper[RemoteInvokeExecutionInfo]): """ If a regular string is provided as payload, this class will convert it into a JSON object """ @@ -143,13 +170,13 @@ def map(self, test_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInf return test_input -class LambdaResponseConverter(RemoteInvokeRequestResponseMapper): +class LambdaResponseConverter(RemoteInvokeRequestResponseMapper[RemoteInvokeResponse]): """ This class helps to convert response from lambda service. Normally lambda service returns 'Payload' field as stream, this class converts that stream into string object """ - def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + def map(self, remote_invoke_input: RemoteInvokeResponse) -> RemoteInvokeResponse: LOG.debug("Mapping Lambda response to string object") if not isinstance(remote_invoke_input.response, dict): raise InvalideBotoResponseException("Invalid response type received from Lambda service, expecting dict") @@ -168,7 +195,7 @@ class LambdaStreamResponseConverter(RemoteInvokeRequestResponseMapper): This mapper, gets all 'PayloadChunk's and 'InvokeComplete' events and decodes them for next mapper. """ - def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + def map(self, remote_invoke_input: RemoteInvokeResponse) -> RemoteInvokeResponse: LOG.debug("Mapping Lambda response to string object") if not isinstance(remote_invoke_input.response, dict): raise InvalideBotoResponseException("Invalid response type received from Lambda service, expecting dict") @@ -180,70 +207,11 @@ def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExe decoded_payload_chunk = event.get(PAYLOAD_CHUNK).get(PAYLOAD).decode("utf-8") decoded_event_stream.append({PAYLOAD_CHUNK: {PAYLOAD: decoded_payload_chunk}}) if INVOKE_COMPLETE in event: - log_output = event.get(INVOKE_COMPLETE).get(LOG_RESULT, b"") - decoded_event_stream.append({INVOKE_COMPLETE: {LOG_RESULT: log_output}}) + decoded_event_stream.append(event) remote_invoke_input.response[EVENT_STREAM] = decoded_event_stream return remote_invoke_input -class LambdaResponseOutputFormatter(RemoteInvokeRequestResponseMapper): - """ - This class helps to format output response for lambda service that will be printed on the CLI. - If LogResult is found in the response, the decoded LogResult will be written to stderr. The response payload will - be written to stdout. - """ - - def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: - """ - Maps the lambda response output to the type of output format specified as user input. - If output_format is original-boto-response, write the original boto API response - to stdout. - """ - if remote_invoke_input.output_format == RemoteInvokeOutputFormat.DEFAULT: - LOG.debug("Formatting Lambda output response") - boto_response = cast(dict, remote_invoke_input.response) - log_field = boto_response.get(LOG_RESULT) - if log_field: - log_result = base64.b64decode(log_field).decode("utf-8") - remote_invoke_input.log_output = log_result - - invocation_type_parameter = remote_invoke_input.parameters.get("InvocationType") - if invocation_type_parameter and invocation_type_parameter != "RequestResponse": - remote_invoke_input.response = {"StatusCode": boto_response["StatusCode"]} - else: - remote_invoke_input.response = boto_response.get(PAYLOAD) - - return remote_invoke_input - - -class LambdaStreamResponseOutputFormatter(RemoteInvokeRequestResponseMapper): - """ - This class helps to format streaming output response for lambda service that will be printed on the CLI. - It loops through EventStream elements and adds them to response, and once InvokeComplete is reached, it updates - log_output and response objects in remote_invoke_input. - """ - - def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: - """ - Maps the lambda response output to the type of output format specified as user input. - If output_format is original-boto-response, write the original boto API response - to stdout. - """ - if remote_invoke_input.output_format == RemoteInvokeOutputFormat.DEFAULT: - LOG.debug("Formatting Lambda output response") - boto_response = cast(dict, remote_invoke_input.response) - combined_response = "" - for event in boto_response.get(EVENT_STREAM, []): - if PAYLOAD_CHUNK in event: - payload_chunk = event.get(PAYLOAD_CHUNK).get(PAYLOAD) - combined_response = f"{combined_response}{payload_chunk}" - if INVOKE_COMPLETE in event: - log_result = base64.b64decode(event.get(INVOKE_COMPLETE).get(LOG_RESULT)).decode("utf-8") - remote_invoke_input.log_output = log_result - remote_invoke_input.response = combined_response - return remote_invoke_input - - def _is_function_invoke_mode_response_stream(lambda_client: Any, function_name: str): """ Returns True if given function has RESPONSE_STREAM as InvokeMode, False otherwise diff --git a/samcli/lib/remote_invoke/remote_invoke_executor_factory.py b/samcli/lib/remote_invoke/remote_invoke_executor_factory.py index 33ec958e1f..129f9302d9 100644 --- a/samcli/lib/remote_invoke/remote_invoke_executor_factory.py +++ b/samcli/lib/remote_invoke/remote_invoke_executor_factory.py @@ -9,12 +9,17 @@ LambdaInvokeExecutor, LambdaInvokeWithResponseStreamExecutor, LambdaResponseConverter, - LambdaResponseOutputFormatter, LambdaStreamResponseConverter, - LambdaStreamResponseOutputFormatter, _is_function_invoke_mode_response_stream, ) -from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutor, ResponseObjectToJsonStringMapper +from samcli.lib.remote_invoke.remote_invoke_executors import ( + RemoteInvokeConsumer, + RemoteInvokeExecutor, + RemoteInvokeLogOutput, + RemoteInvokeOutputFormat, + RemoteInvokeResponse, + ResponseObjectToJsonStringMapper, +) from samcli.lib.utils.cloudformation import CloudFormationResourceSummary from samcli.lib.utils.resources import ( AWS_LAMBDA_FUNCTION, @@ -29,7 +34,11 @@ def __init__(self, boto_client_provider: Callable[[str], Any]): self._boto_client_provider = boto_client_provider def create_remote_invoke_executor( - self, cfn_resource_summary: CloudFormationResourceSummary + self, + cfn_resource_summary: CloudFormationResourceSummary, + output_format: RemoteInvokeOutputFormat, + response_consumer: RemoteInvokeConsumer[RemoteInvokeResponse], + log_consumer: RemoteInvokeConsumer[RemoteInvokeLogOutput], ) -> Optional[RemoteInvokeExecutor]: """ Creates remote invoker with given CloudFormationResourceSummary @@ -38,8 +47,14 @@ def create_remote_invoke_executor( ---------- cfn_resource_summary : CloudFormationResourceSummary Information about the resource, which RemoteInvokeExecutor will be created for - - Returns: + output_format: RemoteInvokeOutputFormat + Output format of the current remote invoke execution, passed down to executor itself + response_consumer: RemoteInvokeConsumer[RemoteInvokeResponse] + Consumer instance which can process RemoteInvokeResponse events + log_consumer: RemoteInvokeConsumer[RemoteInvokeLogOutput] + Consumer instance which can process RemoteInvokeLogOutput events + + Returns ------- Optional[RemoteInvokeExecutor] RemoteInvoker instance for the given CFN resource, None if the resource is not supported yet @@ -50,7 +65,7 @@ def create_remote_invoke_executor( ) if remote_invoke_executor: - return remote_invoke_executor(self, cfn_resource_summary) + return remote_invoke_executor(self, cfn_resource_summary, output_format, response_consumer, log_consumer) LOG.error( "Can't find remote invoke executor instance for resource %s for type %s", @@ -60,7 +75,13 @@ def create_remote_invoke_executor( return None - def _create_lambda_boto_executor(self, cfn_resource_summary: CloudFormationResourceSummary) -> RemoteInvokeExecutor: + def _create_lambda_boto_executor( + self, + cfn_resource_summary: CloudFormationResourceSummary, + remote_invoke_output_format: RemoteInvokeOutputFormat, + response_consumer: RemoteInvokeConsumer[RemoteInvokeResponse], + log_consumer: RemoteInvokeConsumer[RemoteInvokeLogOutput], + ) -> RemoteInvokeExecutor: """Creates a remote invoke executor for Lambda resource type based on the boto action being called. @@ -69,37 +90,55 @@ def _create_lambda_boto_executor(self, cfn_resource_summary: CloudFormationResou :return: Returns the created remote invoke Executor """ lambda_client = self._boto_client_provider("lambda") + mappers = [] if _is_function_invoke_mode_response_stream(lambda_client, cfn_resource_summary.physical_resource_id): LOG.debug("Creating response stream invocator for function %s", cfn_resource_summary.physical_resource_id) - return RemoteInvokeExecutor( - request_mappers=[DefaultConvertToJSON()], - response_mappers=[ + + if remote_invoke_output_format == RemoteInvokeOutputFormat.RAW: + mappers = [ LambdaStreamResponseConverter(), - LambdaStreamResponseOutputFormatter(), ResponseObjectToJsonStringMapper(), - ], + ] + + return RemoteInvokeExecutor( + request_mappers=[DefaultConvertToJSON()], + response_mappers=mappers, boto_action_executor=LambdaInvokeWithResponseStreamExecutor( - lambda_client, - cfn_resource_summary.physical_resource_id, + lambda_client, cfn_resource_summary.physical_resource_id, remote_invoke_output_format ), + response_consumer=response_consumer, + log_consumer=log_consumer, ) - return RemoteInvokeExecutor( - request_mappers=[DefaultConvertToJSON()], - response_mappers=[ + if remote_invoke_output_format == RemoteInvokeOutputFormat.RAW: + mappers = [ LambdaResponseConverter(), - LambdaResponseOutputFormatter(), ResponseObjectToJsonStringMapper(), - ], + ] + + return RemoteInvokeExecutor( + request_mappers=[DefaultConvertToJSON()], + response_mappers=mappers, boto_action_executor=LambdaInvokeExecutor( - lambda_client, - cfn_resource_summary.physical_resource_id, + lambda_client, cfn_resource_summary.physical_resource_id, remote_invoke_output_format ), + response_consumer=response_consumer, + log_consumer=log_consumer, ) # mapping definition for each supported resource type REMOTE_INVOKE_EXECUTOR_MAPPING: Dict[ - str, Callable[["RemoteInvokeExecutorFactory", CloudFormationResourceSummary], RemoteInvokeExecutor] + str, + Callable[ + [ + "RemoteInvokeExecutorFactory", + CloudFormationResourceSummary, + RemoteInvokeOutputFormat, + RemoteInvokeConsumer[RemoteInvokeResponse], + RemoteInvokeConsumer[RemoteInvokeLogOutput], + ], + RemoteInvokeExecutor, + ], ] = { AWS_LAMBDA_FUNCTION: _create_lambda_boto_executor, } diff --git a/samcli/lib/remote_invoke/remote_invoke_executors.py b/samcli/lib/remote_invoke/remote_invoke_executors.py index cb9eb6887b..0c69a9d5bf 100644 --- a/samcli/lib/remote_invoke/remote_invoke_executors.py +++ b/samcli/lib/remote_invoke/remote_invoke_executors.py @@ -4,14 +4,40 @@ import json import logging from abc import ABC, abstractmethod +from dataclasses import dataclass from enum import Enum from io import TextIOWrapper from pathlib import Path -from typing import Any, Callable, List, Optional, Union, cast +from typing import Any, Callable, Generic, Iterable, List, Optional, TypeVar, Union, cast + +from typing_extensions import TypeAlias LOG = logging.getLogger(__name__) +@dataclass +class RemoteInvokeResponse: + """ + Dataclass that contains response object of the remote invoke execution. + dict for raw events, str for other ones + """ + + response: Union[str, dict] + + +@dataclass +class RemoteInvokeLogOutput: + """ + Dataclass that contains log objects of the remote invoke execution + """ + + log_output: str + + +# type alias to keep consistency between different places for remote invoke return type +RemoteInvokeIterableResponseType: TypeAlias = Iterable[Union[RemoteInvokeResponse, RemoteInvokeLogOutput]] + + class RemoteInvokeOutputFormat(Enum): """ Types of output formats used to by remote invoke @@ -69,7 +95,10 @@ def is_succeeded(self) -> bool: return bool(self.response) -class RemoteInvokeRequestResponseMapper(ABC): +RemoteInvokeResponseType = TypeVar("RemoteInvokeResponseType") + + +class RemoteInvokeRequestResponseMapper(Generic[RemoteInvokeResponseType]): """ Mapper definition which can be used map remote invoke requests or responses. @@ -81,7 +110,13 @@ class RemoteInvokeRequestResponseMapper(ABC): """ @abstractmethod - def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + def map(self, remote_invoke_input: RemoteInvokeResponseType) -> RemoteInvokeResponseType: + raise NotImplementedError() + + +class RemoteInvokeConsumer(Generic[RemoteInvokeResponseType]): + @abstractmethod + def consume(self, remote_invoke_response: RemoteInvokeResponseType) -> None: raise NotImplementedError() @@ -90,7 +125,7 @@ class ResponseObjectToJsonStringMapper(RemoteInvokeRequestResponseMapper): Maps response object inside RemoteInvokeExecutionInfo into formatted JSON string with multiple lines """ - def map(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + def map(self, remote_invoke_input: RemoteInvokeResponse) -> RemoteInvokeResponse: LOG.debug("Converting response object into JSON") remote_invoke_input.response = json.dumps(remote_invoke_input.response, indent=2) return remote_invoke_input @@ -103,7 +138,7 @@ class BotoActionExecutor(ABC): """ @abstractmethod - def _execute_action(self, payload: str) -> dict: + def _execute_action(self, payload: str) -> RemoteInvokeIterableResponseType: """ Specific boto3 API call implementation. @@ -128,7 +163,7 @@ def validate_action_parameters(self, parameters: dict): """ raise NotImplementedError() - def _execute_action_file(self, payload_file: TextIOWrapper) -> dict: + def _execute_action_file(self, payload_file: TextIOWrapper) -> RemoteInvokeIterableResponseType: """ Different implementation which is specific to a file path. Some boto3 APIs may accept a file path rather than a string. This implementation targets these options to support different file types @@ -147,20 +182,21 @@ def _execute_action_file(self, payload_file: TextIOWrapper) -> dict: """ return self._execute_action(payload_file.read()) - def execute(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + def execute(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeIterableResponseType: """ Executes boto3 API and updates response or exception object depending on the result Parameters ---------- remote_invoke_input : RemoteInvokeExecutionInfo - RemoteInvokeExecutionInfo details which contains payload or payload file information + Remote execution details which contains payload or payload file information - Returns : RemoteInvokeExecutionInfo + Returns ------- - Updates response or exception fields of given input and returns it + RemoteInvokeIterableResponseType + Returns iterable response, see response type definition for details """ - action_executor: Callable[[Any], dict] + action_executor: Callable[[Any], Iterable[Union[RemoteInvokeResponse, RemoteInvokeLogOutput]]] payload: Union[str, Path] # if a file pointed is provided for payload, use specific payload and its function here @@ -172,13 +208,7 @@ def execute(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvok payload = cast(str, remote_invoke_input.payload) # execute boto3 API, and update result if it is successful, update exception otherwise - try: - action_response = action_executor(payload) - remote_invoke_input.response = action_response - except Exception as e: - remote_invoke_input.exception = e - - return remote_invoke_input + return action_executor(payload) class RemoteInvokeExecutor: @@ -190,21 +220,28 @@ class RemoteInvokeExecutor: Once the result is returned, if it is successful, response have been mapped with list of response mappers """ - _request_mappers: List[RemoteInvokeRequestResponseMapper] - _response_mappers: List[RemoteInvokeRequestResponseMapper] + _request_mappers: List[RemoteInvokeRequestResponseMapper[RemoteInvokeExecutionInfo]] + _response_mappers: List[RemoteInvokeRequestResponseMapper[RemoteInvokeResponse]] _boto_action_executor: BotoActionExecutor + _response_consumer: RemoteInvokeConsumer[RemoteInvokeResponse] + _log_consumer: RemoteInvokeConsumer[RemoteInvokeLogOutput] + def __init__( self, - request_mappers: List[RemoteInvokeRequestResponseMapper], - response_mappers: List[RemoteInvokeRequestResponseMapper], + request_mappers: List[RemoteInvokeRequestResponseMapper[RemoteInvokeExecutionInfo]], + response_mappers: List[RemoteInvokeRequestResponseMapper[RemoteInvokeResponse]], boto_action_executor: BotoActionExecutor, + response_consumer: RemoteInvokeConsumer[RemoteInvokeResponse], + log_consumer: RemoteInvokeConsumer[RemoteInvokeLogOutput], ): self._request_mappers = request_mappers self._response_mappers = response_mappers self._boto_action_executor = boto_action_executor + self._response_consumer = response_consumer + self._log_consumer = log_consumer - def execute(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + def execute(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> None: """ First runs all mappers for request object to get the final version of it. Then validates all the input boto parameters and invokes the BotoActionExecutor to get the result @@ -212,13 +249,11 @@ def execute(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvok """ remote_invoke_input = self._map_input(remote_invoke_input) self._boto_action_executor.validate_action_parameters(remote_invoke_input.parameters) - remote_invoke_output = self._boto_action_executor.execute(remote_invoke_input) - - # call output mappers if the action is succeeded - if remote_invoke_output.is_succeeded(): - return self._map_output(remote_invoke_output) - - return remote_invoke_output + for remote_invoke_result in self._boto_action_executor.execute(remote_invoke_input): + if isinstance(remote_invoke_result, RemoteInvokeResponse): + self._response_consumer.consume(self._map_output(remote_invoke_result)) + if isinstance(remote_invoke_result, RemoteInvokeLogOutput): + self._log_consumer.consume(remote_invoke_result) def _map_input(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: """ @@ -229,26 +264,28 @@ def _map_input(self, remote_invoke_input: RemoteInvokeExecutionInfo) -> RemoteIn remote_invoke_input : RemoteInvokeExecutionInfo Given remote invoke execution info which contains the request information - Returns : RemoteInvokeExecutionInfo + Returns ------- + RemoteInvokeExecutionInfo RemoteInvokeExecutionInfo which contains updated input payload """ for input_mapper in self._request_mappers: remote_invoke_input = input_mapper.map(remote_invoke_input) return remote_invoke_input - def _map_output(self, remote_invoke_output: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: + def _map_output(self, remote_invoke_output: RemoteInvokeResponse) -> RemoteInvokeResponse: """ Maps the given response through the response mapper list. Parameters ---------- - remote_invoke_output : RemoteInvokeExecutionInfo - Given remote invoke execution info which contains the response information + remote_invoke_output : RemoteInvokeResponse + Given remote invoke response which contains the payload itself - Returns : RemoteInvokeExecutionInfo + Returns ------- - RemoteInvokeExecutionInfo which contains updated response + RemoteInvokeResponse + Returns the mapped instance of RemoteInvokeResponse, after applying all configured mappers """ for output_mapper in self._response_mappers: remote_invoke_output = output_mapper.map(remote_invoke_output) diff --git a/tests/unit/commands/remote/invoke/test_cli.py b/tests/unit/commands/remote/invoke/test_cli.py index 6e97251b5a..97aecfc721 100644 --- a/tests/unit/commands/remote/invoke/test_cli.py +++ b/tests/unit/commands/remote/invoke/test_cli.py @@ -101,10 +101,6 @@ def test_remote_invoke_command( context_mock.run.assert_called_with(remote_invoke_input=given_remote_invoke_execution_info) - if log_output: - stderr_stream_writer_mock.write.assert_called() - stdout_stream_writer_mock.write.assert_called() - @parameterized.expand( [ (InvalideBotoResponseException,), @@ -114,14 +110,9 @@ def test_remote_invoke_command( ) @patch("samcli.commands.remote.remote_invoke_context.RemoteInvokeContext") def test_raise_user_exception_invoke_not_successfull(self, exeception_to_raise, mock_invoke_context): - context_mock = Mock() mock_invoke_context.return_value.__enter__.return_value = context_mock - - given_remote_invoke_result = Mock() - given_remote_invoke_result.is_succeeded.return_value = False - context_mock.run.return_value = given_remote_invoke_result - given_remote_invoke_result.exception = exeception_to_raise + context_mock.run.side_effect = exeception_to_raise with self.assertRaises(UserException): do_cli( diff --git a/tests/unit/commands/remote/test_remote_invoke_context.py b/tests/unit/commands/remote/test_remote_invoke_context.py index 0c04a01713..e01d9de5fb 100644 --- a/tests/unit/commands/remote/test_remote_invoke_context.py +++ b/tests/unit/commands/remote/test_remote_invoke_context.py @@ -129,14 +129,11 @@ def test_running_should_execute_remote_invoke_executor_instance( mocked_remote_invoke_executor_factory = Mock() patched_remote_invoke_executor_factory.return_value = mocked_remote_invoke_executor_factory mocked_remote_invoke_executor = Mock() - mocked_output = Mock() - mocked_remote_invoke_executor.execute.return_value = mocked_output mocked_remote_invoke_executor_factory.create_remote_invoke_executor.return_value = mocked_remote_invoke_executor given_input = Mock() with self._get_remote_invoke_context() as remote_invoke_context: - remote_invoke_result = remote_invoke_context.run(given_input) + remote_invoke_context.run(given_input) mocked_remote_invoke_executor_factory.create_remote_invoke_executor.assert_called_once() mocked_remote_invoke_executor.execute.assert_called_with(given_input) - self.assertEqual(remote_invoke_result, mocked_output) diff --git a/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py b/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py index 15ff272bac..dca00cafae 100644 --- a/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py +++ b/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from typing import Any from unittest import TestCase -from unittest.mock import Mock, patch +from unittest.mock import Mock from parameterized import parameterized @@ -21,14 +21,12 @@ LambdaInvokeExecutor, LambdaInvokeWithResponseStreamExecutor, LambdaResponseConverter, - LambdaResponseOutputFormatter, LambdaStreamResponseConverter, - LambdaStreamResponseOutputFormatter, ParamValidationError, RemoteInvokeOutputFormat, _is_function_invoke_mode_response_stream, ) -from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutionInfo +from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutionInfo, RemoteInvokeResponse class CommonTestsLambdaInvokeExecutor: @@ -51,21 +49,24 @@ def test_execute_action_invalid_parameter_value_throws_client_error(self, error_ error = ClientError(error_response={"Error": {"Code": error_code}}, operation_name="invoke") self._get_boto3_method().side_effect = error with self.assertRaises(InvalidResourceBotoParameterException): - self.lambda_invoke_executor._execute_action(given_payload) + for _ in self.lambda_invoke_executor._execute_action(given_payload): + pass def test_execute_action_invalid_parameter_key_throws_parameter_validation_exception(self): given_payload = Mock() error = ParamValidationError(report="Invalid parameters") self._get_boto3_method().side_effect = error with self.assertRaises(InvalidResourceBotoParameterException): - self.lambda_invoke_executor._execute_action(given_payload) + for _ in self.lambda_invoke_executor._execute_action(given_payload): + pass def test_execute_action_throws_client_error_exception(self): - given_payload = Mock() + given_payload = "payload" error = ClientError(error_response={"Error": {"Code": "MockException"}}, operation_name="invoke") self._get_boto3_method().side_effect = error with self.assertRaises(ErrorBotoApiCallException): - self.lambda_invoke_executor._execute_action(given_payload) + for _ in self.lambda_invoke_executor._execute_action(given_payload): + pass @parameterized.expand( [ @@ -94,7 +95,9 @@ class TestLambdaInvokeExecutor(CommonTestsLambdaInvokeExecutor.AbstractLambdaInv def setUp(self) -> None: self.lambda_client = Mock() self.function_name = Mock() - self.lambda_invoke_executor = LambdaInvokeExecutor(self.lambda_client, self.function_name) + self.lambda_invoke_executor = LambdaInvokeExecutor( + self.lambda_client, self.function_name, RemoteInvokeOutputFormat.RAW + ) def test_execute_action(self): given_payload = Mock() @@ -103,7 +106,7 @@ def test_execute_action(self): result = self.lambda_invoke_executor._execute_action(given_payload) - self.assertEqual(result, given_result) + self.assertEqual(list(result), [RemoteInvokeResponse(given_result)]) self.lambda_client.invoke.assert_called_with( FunctionName=self.function_name, Payload=given_payload, InvocationType="RequestResponse", LogType="Tail" ) @@ -116,7 +119,9 @@ class TestLambdaInvokeWithResponseStreamExecutor(CommonTestsLambdaInvokeExecutor def setUp(self) -> None: self.lambda_client = Mock() self.function_name = Mock() - self.lambda_invoke_executor = LambdaInvokeWithResponseStreamExecutor(self.lambda_client, self.function_name) + self.lambda_invoke_executor = LambdaInvokeWithResponseStreamExecutor( + self.lambda_client, self.function_name, RemoteInvokeOutputFormat.RAW + ) def test_execute_action(self): given_payload = Mock() @@ -125,7 +130,7 @@ def test_execute_action(self): result = self.lambda_invoke_executor._execute_action(given_payload) - self.assertEqual(result, given_result) + self.assertEqual(list(result), [RemoteInvokeResponse(given_result)]) self.lambda_client.invoke_with_response_stream.assert_called_with( FunctionName=self.function_name, Payload=given_payload, InvocationType="RequestResponse", LogType="Tail" ) @@ -196,7 +201,9 @@ class TestLambdaStreamResponseConverter(TestCase): def setUp(self) -> None: self.lambda_stream_response_converter = LambdaStreamResponseConverter() - @parameterized.expand([({LOG_RESULT: base64.b64encode(b"log output")}, base64.b64encode(b"log output")), ({}, b"")]) + @parameterized.expand( + [({LOG_RESULT: base64.b64encode(b"log output")}, {LOG_RESULT: base64.b64encode(b"log output")}), ({}, {})] + ) def test_lambda_streaming_body_response_conversion(self, invoke_complete_response, mapped_log_response): output_format = RemoteInvokeOutputFormat.DEFAULT given_test_result = { @@ -207,20 +214,18 @@ def test_lambda_streaming_body_response_conversion(self, invoke_complete_respons {INVOKE_COMPLETE: invoke_complete_response}, ] } - remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, output_format) - remote_invoke_execution_info.response = given_test_result + remote_invoke_response = RemoteInvokeResponse(given_test_result) expected_result = { EVENT_STREAM: [ {PAYLOAD_CHUNK: {PAYLOAD: "stream1"}}, {PAYLOAD_CHUNK: {PAYLOAD: "stream2"}}, {PAYLOAD_CHUNK: {PAYLOAD: "stream3"}}, - {INVOKE_COMPLETE: {LOG_RESULT: mapped_log_response}}, + {INVOKE_COMPLETE: {**mapped_log_response}}, ] } - result = self.lambda_stream_response_converter.map(remote_invoke_execution_info) - + result = self.lambda_stream_response_converter.map(remote_invoke_response) self.assertEqual(result.response, expected_result) def test_lambda_streaming_body_invalid_response_exception(self): @@ -232,83 +237,6 @@ def test_lambda_streaming_body_invalid_response_exception(self): self.lambda_stream_response_converter.map(remote_invoke_execution_info) -class TestLambdaResponseOutputFormatter(TestCase): - def setUp(self) -> None: - self.lambda_response_converter = LambdaResponseOutputFormatter() - - def test_lambda_response_original_boto_output_formatter(self): - given_response = {"Payload": {"StatusCode": 200, "message": "hello world"}} - output_format = RemoteInvokeOutputFormat.RAW - - remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, output_format) - remote_invoke_execution_info.response = given_response - result = self.lambda_response_converter.map(remote_invoke_execution_info) - - self.assertEqual(result.response, given_response) - - @patch("samcli.lib.remote_invoke.lambda_invoke_executors.base64") - def test_lambda_response_default_output_formatter(self, base64_mock): - decoded_log_str = "decoded log string" - log_str_mock = Mock() - base64_mock.b64decode().decode.return_value = decoded_log_str - given_response = {"Payload": {"StatusCode": 200, "message": "hello world"}, "LogResult": log_str_mock} - output_format = RemoteInvokeOutputFormat.DEFAULT - - remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, output_format) - remote_invoke_execution_info.response = given_response - - expected_result = {"StatusCode": 200, "message": "hello world"} - result = self.lambda_response_converter.map(remote_invoke_execution_info) - - self.assertEqual(result.response, expected_result) - self.assertEqual(result.log_output, decoded_log_str) - - @parameterized.expand( - [ - ({"InvocationType": "DryRun", "Qualifier": "TestQualifier"},), - ({"InvocationType": "Event", "LogType": None},), - ] - ) - def test_non_default_invocation_type_output_formatter(self, parameters): - given_response = {"StatusCode": 200, "Payload": {"StatusCode": 200, "message": "hello world"}} - output_format = RemoteInvokeOutputFormat.DEFAULT - - remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, parameters, output_format) - remote_invoke_execution_info.response = given_response - - expected_result = {"StatusCode": 200} - result = self.lambda_response_converter.map(remote_invoke_execution_info) - - self.assertEqual(result.response, expected_result) - - -class TestLambdaStreamResponseOutputFormatter(TestCase): - def setUp(self) -> None: - self.lambda_response_converter = LambdaStreamResponseOutputFormatter() - - def test_none_event_stream(self): - remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, RemoteInvokeOutputFormat.DEFAULT) - remote_invoke_execution_info.response = {} - - mapped_response = self.lambda_response_converter.map(remote_invoke_execution_info) - self.assertEqual(mapped_response.response, "") - - def test_event_stream(self): - remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, RemoteInvokeOutputFormat.DEFAULT) - remote_invoke_execution_info.response = { - EVENT_STREAM: [ - {PAYLOAD_CHUNK: {PAYLOAD: "stream1"}}, - {PAYLOAD_CHUNK: {PAYLOAD: "stream2"}}, - {PAYLOAD_CHUNK: {PAYLOAD: "stream3"}}, - {INVOKE_COMPLETE: {LOG_RESULT: base64.b64encode(b"log output")}}, - ] - } - - mapped_response = self.lambda_response_converter.map(remote_invoke_execution_info) - self.assertEqual(mapped_response.response, "stream1stream2stream3") - self.assertEqual(mapped_response.log_output, "log output") - - class TestLambdaInvokeExecutorUtilities(TestCase): @parameterized.expand( [ diff --git a/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py b/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py index 3a1f938e19..bbb8c1bac9 100644 --- a/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py +++ b/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py @@ -1,3 +1,4 @@ +import itertools from unittest import TestCase from unittest.mock import patch, Mock @@ -6,6 +7,7 @@ from samcli.lib.remote_invoke.remote_invoke_executor_factory import ( RemoteInvokeExecutorFactory, ) +from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeOutputFormat class TestRemoteInvokeExecutorFactory(TestCase): @@ -24,38 +26,48 @@ def test_create_remote_invoke_executor(self, patched_executor_mapping): given_executor_creator_method.return_value = given_executor given_cfn_resource_summary = Mock() - executor = self.remote_invoke_executor_factory.create_remote_invoke_executor(given_cfn_resource_summary) + given_output_format = Mock() + given_response_consumer = Mock() + given_log_consumer = Mock() + executor = self.remote_invoke_executor_factory.create_remote_invoke_executor( + given_cfn_resource_summary, given_output_format, given_response_consumer, given_log_consumer + ) patched_executor_mapping.get.assert_called_with(given_cfn_resource_summary.resource_type) given_executor_creator_method.assert_called_with( - self.remote_invoke_executor_factory, given_cfn_resource_summary + self.remote_invoke_executor_factory, + given_cfn_resource_summary, + given_output_format, + given_response_consumer, + given_log_consumer, ) self.assertEqual(executor, given_executor) def test_failed_create_test_executor(self): given_cfn_resource_summary = Mock() - executor = self.remote_invoke_executor_factory.create_remote_invoke_executor(given_cfn_resource_summary) + executor = self.remote_invoke_executor_factory.create_remote_invoke_executor( + given_cfn_resource_summary, Mock(), Mock(), Mock() + ) self.assertIsNone(executor) - @parameterized.expand([(True,), (False,)]) + @parameterized.expand( + itertools.product([True, False], [RemoteInvokeOutputFormat.RAW, RemoteInvokeOutputFormat.DEFAULT]) + ) @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaInvokeExecutor") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaInvokeWithResponseStreamExecutor") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.DefaultConvertToJSON") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaResponseConverter") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaStreamResponseConverter") - @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaResponseOutputFormatter") - @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaStreamResponseOutputFormatter") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.ResponseObjectToJsonStringMapper") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.RemoteInvokeExecutor") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory._is_function_invoke_mode_response_stream") def test_create_lambda_test_executor( self, is_function_invoke_mode_response_stream, + remote_invoke_output_format, patched_is_function_invoke_mode_response_stream, patched_remote_invoke_executor, patched_object_to_json_converter, - patched_stream_response_output_formatter, - patched_response_output_formatter, patched_stream_response_converter, patched_response_converter, patched_convert_to_default_json, @@ -72,36 +84,51 @@ def test_create_lambda_test_executor( given_remote_invoke_executor = Mock() patched_remote_invoke_executor.return_value = given_remote_invoke_executor - lambda_executor = self.remote_invoke_executor_factory._create_lambda_boto_executor(given_cfn_resource_summary) + given_response_consumer = Mock() + given_log_consumer = Mock() + lambda_executor = self.remote_invoke_executor_factory._create_lambda_boto_executor( + given_cfn_resource_summary, remote_invoke_output_format, given_response_consumer, given_log_consumer + ) self.assertEqual(lambda_executor, given_remote_invoke_executor) self.boto_client_provider_mock.assert_called_with("lambda") patched_convert_to_default_json.assert_called_once() - patched_object_to_json_converter.assert_called_once() if is_function_invoke_mode_response_stream: - patched_stream_response_output_formatter.assert_called_once() - patched_stream_response_converter.assert_called_once() - patched_lambda_invoke_with_response_stream_executor.assert_called_once() - patched_remote_invoke_executor.assert_called_with( - request_mappers=[patched_convert_to_default_json()], - response_mappers=[ + expected_mappers = [] + if remote_invoke_output_format == RemoteInvokeOutputFormat.RAW: + patched_object_to_json_converter.assert_called_once() + patched_stream_response_converter.assert_called_once() + patched_lambda_invoke_with_response_stream_executor.assert_called_with( + given_lambda_client, given_physical_resource_id, remote_invoke_output_format + ) + expected_mappers = [ patched_stream_response_converter(), - patched_stream_response_output_formatter(), patched_object_to_json_converter(), - ], + ] + patched_remote_invoke_executor.assert_called_with( + request_mappers=[patched_convert_to_default_json()], + response_mappers=expected_mappers, boto_action_executor=patched_lambda_invoke_with_response_stream_executor(), + response_consumer=given_response_consumer, + log_consumer=given_log_consumer, ) else: - patched_response_output_formatter.assert_called_once() - patched_response_converter.assert_called_once() - patched_lambda_invoke_executor.assert_called_with(given_lambda_client, given_physical_resource_id) - patched_remote_invoke_executor.assert_called_with( - request_mappers=[patched_convert_to_default_json()], - response_mappers=[ + expected_mappers = [] + if remote_invoke_output_format == RemoteInvokeOutputFormat.RAW: + patched_object_to_json_converter.assert_called_once() + patched_response_converter.assert_called_once() + patched_lambda_invoke_executor.assert_called_with( + given_lambda_client, given_physical_resource_id, remote_invoke_output_format + ) + expected_mappers = [ patched_response_converter(), - patched_response_output_formatter(), patched_object_to_json_converter(), - ], + ] + patched_remote_invoke_executor.assert_called_with( + request_mappers=[patched_convert_to_default_json()], + response_mappers=expected_mappers, boto_action_executor=patched_lambda_invoke_executor(), + response_consumer=given_response_consumer, + log_consumer=given_log_consumer, ) diff --git a/tests/unit/lib/remote_invoke/test_remote_invoke_executors.py b/tests/unit/lib/remote_invoke/test_remote_invoke_executors.py index bb8cfebb2e..8f3ce96e46 100644 --- a/tests/unit/lib/remote_invoke/test_remote_invoke_executors.py +++ b/tests/unit/lib/remote_invoke/test_remote_invoke_executors.py @@ -11,6 +11,7 @@ ResponseObjectToJsonStringMapper, RemoteInvokeRequestResponseMapper, RemoteInvokeOutputFormat, + RemoteInvokeResponse, ) @@ -89,8 +90,6 @@ def test_execute_with_payload(self): patched_execute_action.assert_called_with(given_payload) patched_execute_action_file.assert_not_called() - self.assertEqual(given_result, result.response) - def test_execute_with_payload_file(self): given_payload_file = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} @@ -108,8 +107,6 @@ def test_execute_with_payload_file(self): patched_execute_action_file.assert_called_with(given_payload_file) patched_execute_action.assert_not_called() - self.assertEqual(given_result, result.response) - def test_execute_error(self): given_payload = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} @@ -120,11 +117,9 @@ def test_execute_error(self): given_exception = ValueError() patched_execute_action.side_effect = given_exception - result = self.boto_action_executor.execute(test_execution_info) - - patched_execute_action.assert_called_with(given_payload) - - self.assertEqual(given_exception, result.exception) + with self.assertRaises(ValueError): + result = self.boto_action_executor.execute(test_execution_info) + patched_execute_action.assert_called_with(given_payload) class TestRemoteInvokeExecutor(TestCase): @@ -142,20 +137,20 @@ def setUp(self) -> None: ] self.test_executor = RemoteInvokeExecutor( - self.mock_request_mappers, self.mock_response_mappers, self.mock_boto_action_executor + self.mock_request_mappers, self.mock_response_mappers, self.mock_boto_action_executor, Mock(), Mock() ) def test_execution(self): given_payload = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} - given_output_format = "original-boto-response" + given_output_format = RemoteInvokeOutputFormat.RAW test_execution_info = RemoteInvokeExecutionInfo(given_payload, None, given_parameters, given_output_format) validate_action_parameters_function = Mock() self.mock_boto_action_executor.validate_action_parameters = validate_action_parameters_function + self.mock_boto_action_executor.execute.return_value = [RemoteInvokeResponse(Mock())] - result = self.test_executor.execute(remote_invoke_input=test_execution_info) + self.test_executor.execute(remote_invoke_input=test_execution_info) - self.assertIsNotNone(result) validate_action_parameters_function.assert_called_once() for request_mapper in self.mock_request_mappers: @@ -167,7 +162,7 @@ def test_execution(self): def test_execution_failure(self): given_payload = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} - given_output_format = "original-boto-response" + given_output_format = RemoteInvokeOutputFormat.RAW test_execution_info = RemoteInvokeExecutionInfo(given_payload, None, given_parameters, given_output_format) validate_action_parameters_function = Mock() self.mock_boto_action_executor.validate_action_parameters = validate_action_parameters_function @@ -176,11 +171,10 @@ def test_execution_failure(self): given_payload, None, given_parameters, given_output_format ) given_result_execution_info.exception = Mock() - self.mock_boto_action_executor.execute.return_value = given_result_execution_info + self.mock_boto_action_executor.execute.return_value = [given_result_execution_info] - result = self.test_executor.execute(test_execution_info) + self.test_executor.execute(test_execution_info) - self.assertIsNotNone(result) validate_action_parameters_function.assert_called_once() for request_mapper in self.mock_request_mappers: From fd09a41358e6e8e45ff4f5d54dbef0a5795fe244 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Wed, 14 Jun 2023 11:27:12 -0700 Subject: [PATCH 054/107] refactor init_clients in sam delete (#5360) * refactor init_clients in sam delete * remove unused line * use client_provider * fix broken tests * Update samcli/commands/delete/delete_context.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * add telemetry * fix format --------- Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> --- samcli/commands/delete/command.py | 2 + samcli/commands/delete/delete_context.py | 51 ++++---- .../commands/delete/test_delete_context.py | 112 ++++++++++++++---- 3 files changed, 109 insertions(+), 56 deletions(-) diff --git a/samcli/commands/delete/command.py b/samcli/commands/delete/command.py index 960b067cbf..ae28122710 100644 --- a/samcli/commands/delete/command.py +++ b/samcli/commands/delete/command.py @@ -9,6 +9,7 @@ from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.commands._utils.command_exception_handler import command_exception_handler +from samcli.lib.telemetry.metric import track_command from samcli.lib.utils.version_checker import check_newer_version SHORT_HELP = "Delete an AWS SAM application and the artifacts created by sam deploy." @@ -82,6 +83,7 @@ @aws_creds_options @common_options @pass_context +@track_command @check_newer_version @print_cmdline_args @command_exception_handler diff --git a/samcli/commands/delete/delete_context.py b/samcli/commands/delete/delete_context.py index 3ebcf29669..08d327eceb 100644 --- a/samcli/commands/delete/delete_context.py +++ b/samcli/commands/delete/delete_context.py @@ -5,13 +5,13 @@ import logging from typing import Optional -import boto3 import click +from botocore.exceptions import NoCredentialsError, NoRegionError from click import confirm, prompt from samcli.cli.cli_config_file import TomlProvider -from samcli.cli.context import Context from samcli.commands.delete.exceptions import CfDeleteFailedStatusError +from samcli.commands.exceptions import AWSServiceClientError, RegionError from samcli.lib.bootstrap.companion_stack.companion_stack_builder import CompanionStack from samcli.lib.delete.cfn_utils import CfnUtils from samcli.lib.package.artifact_exporter import Template @@ -19,7 +19,7 @@ from samcli.lib.package.local_files_utils import get_uploaded_s3_object_name from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.package.uploaders import Uploaders -from samcli.lib.utils.boto_utils import get_boto_config_with_user_agent +from samcli.lib.utils.boto_utils import get_boto_client_provider_with_config CONFIG_COMMAND = "deploy" CONFIG_SECTION = "parameters" @@ -108,38 +108,27 @@ def init_clients(self): """ Initialize all the clients being used by sam delete. """ - if not self.region: - if not self.no_prompts: - session = boto3.Session() - region = session.region_name - self.region = region if region else "us-east-1" - else: - # TODO: as part of the guided and non-guided context separation, we need also to move the options - # validations to a validator similar to samcli/lib/cli_validation/image_repository_validation.py. - raise click.BadOptionUsage( - option_name="--region", - message="Missing option '--region', region is required to run the non guided delete command.", - ) - - if self.profile: - Context.get_current_context().profile = self.profile - if self.region: - Context.get_current_context().region = self.region - - boto_config = get_boto_config_with_user_agent() + client_provider = get_boto_client_provider_with_config(region=self.region, profile=self.profile) - # Define cf_client based on the region as different regions can have same stack-names - cloudformation_client = boto3.client( - "cloudformation", region_name=self.region if self.region else None, config=boto_config - ) - - s3_client = boto3.client("s3", region_name=self.region if self.region else None, config=boto_config) - ecr_client = boto3.client("ecr", region_name=self.region if self.region else None, config=boto_config) + try: + cloudformation_client = client_provider("cloudformation") + s3_client = client_provider("s3") + ecr_client = client_provider("ecr") + except NoCredentialsError as ex: + raise AWSServiceClientError( + "Unable to resolve credentials for the AWS SDK for Python client. " + "Please see their documentation for options to pass in credentials: " + "https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html" + ) from ex + except NoRegionError as ex: + raise RegionError( + "Unable to resolve a region. " + "Please provide a region via the --region, via --profile or by the " + "AWS_DEFAULT_REGION environment variable." + ) from ex self.s3_uploader = S3Uploader(s3_client=s3_client, bucket_name=self.s3_bucket, prefix=self.s3_prefix) - self.ecr_uploader = ECRUploader(docker_client=None, ecr_client=ecr_client, ecr_repo=None, ecr_repo_multi=None) - self.uploaders = Uploaders(self.s3_uploader, self.ecr_uploader) self.cf_utils = CfnUtils(cloudformation_client) diff --git a/tests/unit/commands/delete/test_delete_context.py b/tests/unit/commands/delete/test_delete_context.py index 75559d5416..daa72c187b 100644 --- a/tests/unit/commands/delete/test_delete_context.py +++ b/tests/unit/commands/delete/test_delete_context.py @@ -1,8 +1,9 @@ from samcli.lib.bootstrap.companion_stack.data_types import CompanionStack from unittest import TestCase -from unittest.mock import patch, call, MagicMock +from unittest.mock import patch, call, MagicMock, Mock import click +from botocore.exceptions import NoCredentialsError, NoRegionError from samcli.commands.delete.delete_context import DeleteContext from samcli.lib.package.artifact_exporter import Template @@ -10,6 +11,7 @@ from samcli.lib.delete.cfn_utils import CfnUtils from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.package.ecr_uploader import ECRUploader +from samcli.commands.exceptions import AWSServiceClientError, RegionError from samcli.commands.delete.exceptions import CfDeleteFailedStatusError @@ -18,9 +20,9 @@ class TestDeleteContext(TestCase): @patch("samcli.commands.delete.delete_context.click.echo") @patch("samcli.commands.delete.delete_context.click.get_current_context") @patch.object(CfnUtils, "has_stack", MagicMock(return_value=(False))) - @patch("boto3.client") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") def test_delete_context_stack_does_not_exist( - self, patched_boto3, patched_click_get_current_context, patched_click_echo + self, get_boto_client_provider_mock, patched_click_get_current_context, patched_click_echo ): with DeleteContext( stack_name="test", @@ -40,8 +42,8 @@ def test_delete_context_stack_does_not_exist( @patch.object(DeleteContext, "parse_config_file", MagicMock()) @patch.object(DeleteContext, "init_clients", MagicMock()) - @patch("boto3.client") - def test_delete_context_enter(self, patched_boto3): + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") + def test_delete_context_enter(self, get_boto_client_provider_mock): with DeleteContext( stack_name="test", region="us-east-1", @@ -71,8 +73,8 @@ def test_delete_context_enter(self, patched_boto3): ), ) @patch("samcli.commands.delete.delete_context.click.get_current_context") - @patch("boto3.client") - def test_delete_context_parse_config_file(self, patched_boto3, patched_click_get_current_context): + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") + def test_delete_context_parse_config_file(self, get_boto_client_provider_mock, patched_click_get_current_context): patched_click_get_current_context = MagicMock() with DeleteContext( stack_name=None, @@ -94,9 +96,9 @@ def test_delete_context_parse_config_file(self, patched_boto3, patched_click_get @patch("samcli.commands.delete.delete_context.confirm") @patch("samcli.commands.delete.delete_context.click.get_current_context") @patch.object(CfnUtils, "has_stack", MagicMock(return_value=(False))) - @patch("boto3.client") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") def test_delete_no_user_input( - self, patched_boto3, patched_click_get_current_context, patched_confirm, patched_prompt + self, get_boto_client_provider_mock, patched_click_get_current_context, patched_confirm, patched_prompt ): patched_click_get_current_context = MagicMock() with DeleteContext( @@ -142,8 +144,8 @@ def test_delete_no_user_input( @patch.object(Template, "get_ecr_repos", MagicMock(return_value=({"logical_id": {"Repository": "test_id"}}))) @patch.object(S3Uploader, "delete_prefix_artifacts", MagicMock()) @patch("samcli.commands.delete.delete_context.click.get_current_context") - @patch("boto3.client") - def test_delete_context_valid_execute_run(self, patched_boto3, patched_click_get_current_context): + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") + def test_delete_context_valid_execute_run(self, get_boto_client_provider_mock, patched_click_get_current_context): patched_click_get_current_context = MagicMock() with DeleteContext( stack_name=None, @@ -171,9 +173,9 @@ def test_delete_context_valid_execute_run(self, patched_boto3, patched_click_get @patch.object(CfnUtils, "get_stack_template", MagicMock(return_value=({"TemplateBody": "Hello World"}))) @patch.object(CfnUtils, "delete_stack", MagicMock()) @patch.object(CfnUtils, "wait_for_delete", MagicMock()) - @patch("boto3.client") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") def test_delete_context_no_s3_bucket( - self, patched_boto3, patched_click_get_current_context, patched_click_secho, patched_click_echo + self, get_boto_client_provider_mock, patched_click_get_current_context, patched_click_secho, patched_click_echo ): with DeleteContext( stack_name="test", @@ -211,9 +213,13 @@ def test_delete_context_no_s3_bucket( @patch.object(CfnUtils, "delete_stack", MagicMock()) @patch.object(CfnUtils, "wait_for_delete", MagicMock()) @patch.object(S3Uploader, "delete_artifact", MagicMock()) - @patch("boto3.client") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") def test_guided_prompts_s3_bucket_prefix_present_execute_run( - self, patched_boto3, patched_click_get_current_context, patched_confirm, patched_get_cf_template_name + self, + get_boto_client_provider_mock, + patched_click_get_current_context, + patched_confirm, + patched_get_cf_template_name, ): patched_get_cf_template_name.return_value = "hello.template" with DeleteContext( @@ -270,9 +276,13 @@ def test_guided_prompts_s3_bucket_prefix_present_execute_run( @patch.object(CfnUtils, "wait_for_delete", MagicMock()) @patch.object(S3Uploader, "delete_artifact", MagicMock()) @patch.object(ECRUploader, "delete_ecr_repository", MagicMock()) - @patch("boto3.client") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") def test_guided_prompts_s3_bucket_present_no_prefix_execute_run( - self, patched_boto3, patched_click_get_current_context, patched_confirm, patched_get_cf_template_name + self, + get_boto_client_provider_mock, + patched_click_get_current_context, + patched_confirm, + patched_get_cf_template_name, ): patched_get_cf_template_name.return_value = "hello.template" with DeleteContext( @@ -321,9 +331,13 @@ def test_guided_prompts_s3_bucket_present_no_prefix_execute_run( @patch.object(ECRUploader, "delete_ecr_repository", MagicMock()) @patch.object(Template, "get_ecr_repos", MagicMock(side_effect=({}, {"logical_id": {"Repository": "test_id"}}))) @patch.object(CompanionStack, "stack_name", "Companion-Stack-Name") - @patch("boto3.client") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") def test_guided_prompts_ecr_companion_stack_present_execute_run( - self, patched_boto3, patched_click_get_current_context, patched_confirm, patched_get_cf_template_name + self, + get_boto_client_provider_mock, + patched_click_get_current_context, + patched_confirm, + patched_get_cf_template_name, ): patched_get_cf_template_name.return_value = "hello.template" with DeleteContext( @@ -398,9 +412,13 @@ def test_guided_prompts_ecr_companion_stack_present_execute_run( @patch.object(ECRUploader, "delete_ecr_repository", MagicMock()) @patch.object(Template, "get_ecr_repos", MagicMock(return_value=({"logical_id": {"Repository": "test_id"}}))) @patch.object(CompanionStack, "stack_name", "Companion-Stack-Name") - @patch("boto3.client") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") def test_no_prompts_input_is_ecr_companion_stack_present_execute_run( - self, patched_boto3, patched_click_get_current_context, patched_click_echo, patched_get_cf_template_name + self, + get_boto_client_provider_mock, + patched_click_get_current_context, + patched_click_echo, + patched_get_cf_template_name, ): CfnUtils.get_stack_template.return_value = { "TemplateBody": {"Metadata": {"CompanionStackname": "Companion-Stack-Name"}} @@ -446,9 +464,9 @@ def test_no_prompts_input_is_ecr_companion_stack_present_execute_run( @patch.object(S3Uploader, "delete_prefix_artifacts", MagicMock()) @patch.object(ECRUploader, "delete_ecr_repository", MagicMock()) @patch.object(Template, "get_ecr_repos", MagicMock(side_effect=({}, {"logical_id": {"Repository": "test_id"}}))) - @patch("boto3.client") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") def test_retain_resources_delete_stack( - self, patched_boto3, patched_click_get_current_context, patched_get_cf_template_name + self, get_boto_client_provider_mock, patched_click_get_current_context, patched_get_cf_template_name ): patched_get_cf_template_name.return_value = "hello.template" with DeleteContext( @@ -504,8 +522,8 @@ def test_s3_option_flag(self): ) @patch.object(DeleteContext, "parse_config_file", MagicMock()) @patch.object(DeleteContext, "init_clients", MagicMock()) - @patch("boto3.client") - def test_s3_option_flag_overrides_config(self, patched_boto3): + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") + def test_s3_option_flag_overrides_config(self, get_boto_client_provider_mock): with DeleteContext( stack_name="test", region="us-east-1", @@ -518,3 +536,47 @@ def test_s3_option_flag_overrides_config(self, patched_boto3): ) as delete_context: self.assertEqual(delete_context.s3_bucket, "s3_bucket_override") self.assertEqual(delete_context.s3_prefix, "s3_prefix_override") + + @patch.object(DeleteContext, "parse_config_file", MagicMock()) + @patch("samcli.commands.delete.delete_context.click.get_current_context") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") + def test_must_throw_error_if_boto3_cannot_resolve_credentials( + self, get_boto_client_provider_mock, patched_get_current_context + ): + boto_client_mock = Mock(side_effect=NoCredentialsError) + get_boto_client_provider_mock.return_value = boto_client_mock + with self.assertRaises(AWSServiceClientError) as ex: + with DeleteContext( + stack_name="test", + region=None, + config_file=None, + config_env=None, + profile="profile_without_creds", + no_prompts=True, + s3_bucket=None, + s3_prefix=None, + ): + get_boto_client_provider_mock.assert_called_once_with(region=None, profile="profile_without_creds") + self.assertIn("Unable to resolve credentials for the AWS SDK for Python client", ex) + + @patch.object(DeleteContext, "parse_config_file", MagicMock()) + @patch("samcli.commands.delete.delete_context.click.get_current_context") + @patch("samcli.commands.delete.delete_context.get_boto_client_provider_with_config") + def test_must_throw_error_if_boto3_cannot_resolve_region( + self, get_boto_client_provider_mock, patched_get_current_context + ): + boto_client_mock = Mock(side_effect=NoRegionError) + get_boto_client_provider_mock.return_value = boto_client_mock + with self.assertRaises(RegionError) as ex: + with DeleteContext( + stack_name="test", + region=None, + config_file=None, + config_env=None, + profile="profile_without_region", + no_prompts=True, + s3_bucket=None, + s3_prefix=None, + ): + get_boto_client_provider_mock.assert_called_once_with(region=None, profile="profile_without_region") + self.assertIn("Unable to resolve a region", ex) From 7803782f3acc30a718c7d9fd0b11dfe3d8ada501 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 15 Jun 2023 17:16:01 -0700 Subject: [PATCH 055/107] chore: update aws-sam-translator to 1.69.0 (#5370) Co-authored-by: GitHub Action --- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 7fa1b843d5..480df5b67a 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,7 +7,7 @@ jmespath~=1.0.1 ruamel_yaml~=0.17.21 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 -aws-sam-translator==1.68.0 +aws-sam-translator==1.69.0 #docker minor version updates can include breaking changes. Auto update micro version only. docker~=6.1.0 dateparser~=1.1 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 12691147a1..a523dcc943 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.34.0 \ --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) -aws-sam-translator==1.68.0 \ - --hash=sha256:557d8080c9e6c1c609bfe806ea9545f7ea34144e2466c0ddc801806c2c05afdc \ - --hash=sha256:d12a7bb3909142d32458f76818cb96a5ebc5f50fbd5943301d552679a893afcc +aws-sam-translator==1.69.0 \ + --hash=sha256:bf26c061675f20367e87d48963ada1ec983a8d897e85db02d0f35913a6174bb0 \ + --hash=sha256:c6ed7a25c77d30d3d31156049415d15fde46c0d3b77a4c5cdc0ef8b53ba9bca2 # via # aws-sam-cli (setup.py) # cfn-lint diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index eb641e70a8..b9c4c8abcf 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.34.0 \ --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) -aws-sam-translator==1.68.0 \ - --hash=sha256:557d8080c9e6c1c609bfe806ea9545f7ea34144e2466c0ddc801806c2c05afdc \ - --hash=sha256:d12a7bb3909142d32458f76818cb96a5ebc5f50fbd5943301d552679a893afcc +aws-sam-translator==1.69.0 \ + --hash=sha256:bf26c061675f20367e87d48963ada1ec983a8d897e85db02d0f35913a6174bb0 \ + --hash=sha256:c6ed7a25c77d30d3d31156049415d15fde46c0d3b77a4c5cdc0ef8b53ba9bca2 # via # aws-sam-cli (setup.py) # cfn-lint From 04c498a5fd3ad7b0f80c72d4d556fc9f0f702ef5 Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Fri, 16 Jun 2023 10:43:13 -0700 Subject: [PATCH 056/107] feat: sam remote invoke help text and UX fixes (#5366) * Improve remote invoke help text and fix some UX bugs * Updated help text for parameter option * Updated test class name * Updated test method name * Updated help text for output-format and event-file * Address feedback * Updated help text for parameter option * Changed --output-format name to output and the values to text/json * Handle empty event for lambda and read from stdin when - is passed for event-file --- samcli/commands/_utils/options.py | 8 +- samcli/commands/remote/invoke/cli.py | 42 +++-- .../commands/remote/invoke/core/__init__.py | 0 samcli/commands/remote/invoke/core/command.py | 158 ++++++++++++++++++ .../commands/remote/invoke/core/formatters.py | 21 +++ samcli/commands/remote/invoke/core/options.py | 54 ++++++ .../remote_invoke_options_validations.py | 13 +- .../remote_invoke/lambda_invoke_executors.py | 11 +- .../remote_invoke_executor_factory.py | 5 +- .../remote_invoke/remote_invoke_executors.py | 4 +- tests/unit/cli/test_types.py | 8 +- .../commands/remote/invoke/core/__init__.py | 0 .../remote/invoke/core/test_command.py | 87 ++++++++++ .../remote/invoke/core/test_formatter.py | 12 ++ .../remote/invoke/core/test_options.py | 12 ++ tests/unit/commands/remote/invoke/test_cli.py | 24 +-- .../test_remote_invoke_options_validations.py | 24 ++- .../test_lambda_invoke_executors.py | 15 +- .../test_remote_invoke_executor_factory.py | 6 +- .../test_remote_invoke_executors.py | 14 +- 20 files changed, 449 insertions(+), 69 deletions(-) create mode 100644 samcli/commands/remote/invoke/core/__init__.py create mode 100644 samcli/commands/remote/invoke/core/command.py create mode 100644 samcli/commands/remote/invoke/core/formatters.py create mode 100644 samcli/commands/remote/invoke/core/options.py create mode 100644 tests/unit/commands/remote/invoke/core/__init__.py create mode 100644 tests/unit/commands/remote/invoke/core/test_command.py create mode 100644 tests/unit/commands/remote/invoke/core/test_formatter.py create mode 100644 tests/unit/commands/remote/invoke/core/test_options.py diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py index 07c46d41fb..188b1705b4 100644 --- a/samcli/commands/_utils/options.py +++ b/samcli/commands/_utils/options.py @@ -594,7 +594,13 @@ def remote_invoke_parameter_click_option(): type=RemoteInvokeBotoApiParameterType(), callback=remote_invoke_boto_parameter_callback, required=False, - help="Additional parameters for the boto API call.\n" "Lambda APIs: invoke and invoke_with_response_stream", + help="Additional parameters that can be passed to invoke the resource.\n" + "The following additional parameters can be used to invoke a lambda resource and get a buffered response: " + "InvocationType='Event'|'RequestResponse'|'DryRun', LogType='None'|'Tail', " + "ClientContext='base64-encoded string' Qualifier='string'. " + "The following additional parameters can be used to invoke a lambda resource with response streaming: " + "InvocationType='RequestResponse'|'DryRun', LogType='None'|'Tail', " + "ClientContext='base64-encoded string', Qualifier='string'.", ) diff --git a/samcli/commands/remote/invoke/cli.py b/samcli/commands/remote/invoke/cli.py index 4f7ed1b081..3f3a771ea1 100644 --- a/samcli/commands/remote/invoke/cli.py +++ b/samcli/commands/remote/invoke/cli.py @@ -8,7 +8,9 @@ from samcli.cli.context import Context from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.cli.types import RemoteInvokeOutputFormatType +from samcli.commands._utils.command_exception_handler import command_exception_handler from samcli.commands._utils.options import remote_invoke_parameter_option +from samcli.commands.remote.invoke.core.command import RemoteInvokeCommand from samcli.lib.cli_validation.remote_invoke_options_validations import ( event_and_event_file_options_validation, stack_name_or_resource_id_atleast_one_option_validation, @@ -20,30 +22,45 @@ LOG = logging.getLogger(__name__) HELP_TEXT = """ -Invoke or send an event to cloud resources in your CFN stack +Invoke or send an event to resources in the cloud. """ SHORT_HELP = "Invoke a deployed resource in the cloud" +DESCRIPTION = """ + Invoke or send an event to resources in the cloud. + An event body can be passed using either -e (--event) or --event-file parameter. + Returned response will be written to stdout. Lambda logs will be written to stderr. +""" + -@click.command("invoke", help=HELP_TEXT, short_help=SHORT_HELP) +@click.command( + "invoke", + cls=RemoteInvokeCommand, + help=HELP_TEXT, + description=DESCRIPTION, + short_help=SHORT_HELP, + requires_credentials=True, + context_settings={"max_content_width": 120}, +) @configuration_option(provider=TomlProvider(section="parameters")) @click.option("--stack-name", required=False, help="Name of the stack to get the resource information from") -@click.option("--resource-id", required=False, help="Name of the resource that will be invoked") +@click.argument("resource-id", required=False) @click.option( "--event", "-e", help="The event that will be sent to the resource. The target parameter will depend on the resource type. " - "For instance: 'Payload' for Lambda", + "For instance: 'Payload' for Lambda which can be passed as a JSON string", ) @click.option( "--event-file", type=click.File("r", encoding="utf-8"), - help="The file that contains the event that will be sent to the resource", + help="The file that contains the event that will be sent to the resource.", ) @click.option( - "--output-format", - help="Output format for the boto API response", - default=RemoteInvokeOutputFormat.DEFAULT.name.lower(), + "--output", + help="Output the results from the command in a given output format. " + "The text format prints a readable AWS API response. The json format prints the full AWS API response.", + default=RemoteInvokeOutputFormat.TEXT.name.lower(), type=RemoteInvokeOutputFormatType(RemoteInvokeOutputFormat), ) @remote_invoke_parameter_option @@ -55,13 +72,14 @@ @track_command @check_newer_version @print_cmdline_args +@command_exception_handler def cli( ctx: Context, stack_name: str, resource_id: str, event: str, event_file: TextIOWrapper, - output_format: RemoteInvokeOutputFormat, + output: RemoteInvokeOutputFormat, parameter: dict, config_file: str, config_env: str, @@ -75,7 +93,7 @@ def cli( resource_id, event, event_file, - output_format, + output, parameter, ctx.region, ctx.profile, @@ -89,7 +107,7 @@ def do_cli( resource_id: str, event: str, event_file: TextIOWrapper, - output_format: RemoteInvokeOutputFormat, + output: RemoteInvokeOutputFormat, parameter: dict, region: str, profile: str, @@ -120,7 +138,7 @@ def do_cli( ) as remote_invoke_context: remote_invoke_input = RemoteInvokeExecutionInfo( - payload=event, payload_file=event_file, parameters=parameter, output_format=output_format + payload=event, payload_file=event_file, parameters=parameter, output_format=output ) remote_invoke_context.run(remote_invoke_input=remote_invoke_input) diff --git a/samcli/commands/remote/invoke/core/__init__.py b/samcli/commands/remote/invoke/core/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samcli/commands/remote/invoke/core/command.py b/samcli/commands/remote/invoke/core/command.py new file mode 100644 index 0000000000..37ec181d86 --- /dev/null +++ b/samcli/commands/remote/invoke/core/command.py @@ -0,0 +1,158 @@ +""" +Invoke Command Class. +""" +import json + +from click import Context, style + +from samcli.cli.core.command import CoreCommand +from samcli.cli.row_modifiers import RowDefinition, ShowcaseRowModifier +from samcli.commands.remote.invoke.core.formatters import RemoteInvokeCommandHelpTextFormatter +from samcli.commands.remote.invoke.core.options import OPTIONS_INFO + + +class RemoteInvokeCommand(CoreCommand): + class CustomFormatterContext(Context): + formatter_class = RemoteInvokeCommandHelpTextFormatter + + context_class = CustomFormatterContext + + @staticmethod + def format_examples(ctx: Context, formatter: RemoteInvokeCommandHelpTextFormatter): + with formatter.indented_section(name="Examples", extra_indents=1): + with formatter.indented_section(name="Invoke default lambda function with empty event", extra_indents=1): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style(f"${ctx.command_path} --stack-name hello-world"), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section( + name="Invoke default lambda function with event passed as text input", extra_indents=1 + ): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"${ctx.command_path} --stack-name hello-world -e '{json.dumps({'message':'hello!'})}'" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section(name="Invoke named lambda function with an event file", extra_indents=1): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"${ctx.command_path} --stack-name " + f"hello-world HelloWorldFunction --event-file event.json" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section(name="Invoke lambda function with event as stdin input", extra_indents=1): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"$ echo '{json.dumps({'message':'hello!'})}' | " + f"{ctx.command_path} HelloWorldFunction --event-file -" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section( + name="Invoke lambda function using lambda ARN and get the full AWS API response", extra_indents=1 + ): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"${ctx.command_path} arn:aws:lambda:us-west-2:123456789012:function:my-function -e <>" + f" --output json" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section( + name="Asynchronously invoke lambda function with additional boto parameters", extra_indents=1 + ): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"${ctx.command_path} HelloWorldFunction -e <> " + f"--parameter InvocationType=Event --parameter Qualifier=MyQualifier" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section( + name="Dry invoke a lambda function to validate parameter values and user/role permissions", + extra_indents=1, + ): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"${ctx.command_path} HelloWorldFunction -e <> --output json " + f"--parameter InvocationType=DryRun" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + + @staticmethod + def format_acronyms(formatter: RemoteInvokeCommandHelpTextFormatter): + with formatter.indented_section(name="Acronyms", extra_indents=1): + formatter.write_rd( + [ + RowDefinition( + name="ARN", + text="Amazon Resource Name", + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + + def format_options(self, ctx: Context, formatter: RemoteInvokeCommandHelpTextFormatter) -> None: # type:ignore + # NOTE: `ignore` is put in place here for mypy even though it is the correct behavior, + # as the `formatter_class` can be set in subclass of Command. If ignore is not set, + # mypy raises argument needs to be HelpFormatter as super class defines it. + + self.format_description(formatter) + RemoteInvokeCommand.format_examples(ctx, formatter) + RemoteInvokeCommand.format_acronyms(formatter) + + CoreCommand._format_options( + ctx=ctx, params=self.get_params(ctx), formatter=formatter, formatting_options=OPTIONS_INFO + ) diff --git a/samcli/commands/remote/invoke/core/formatters.py b/samcli/commands/remote/invoke/core/formatters.py new file mode 100644 index 0000000000..ee8cee01aa --- /dev/null +++ b/samcli/commands/remote/invoke/core/formatters.py @@ -0,0 +1,21 @@ +""" +Remote Invoke Command Formatter. +""" +from samcli.cli.formatters import RootCommandHelpTextFormatter +from samcli.cli.row_modifiers import BaseLineRowModifier +from samcli.commands.remote.invoke.core.options import ALL_OPTIONS + + +class RemoteInvokeCommandHelpTextFormatter(RootCommandHelpTextFormatter): + ADDITIVE_JUSTIFICATION = 17 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # NOTE: Add Additional space after determining the longest option. + # However, do not justify with padding for more than half the width of + # the terminal to retain aesthetics. + self.left_justification_length = min( + max([len(option) for option in ALL_OPTIONS]) + self.ADDITIVE_JUSTIFICATION, + self.width // 2 - self.indent_increment, + ) + self.modifiers = [BaseLineRowModifier()] diff --git a/samcli/commands/remote/invoke/core/options.py b/samcli/commands/remote/invoke/core/options.py new file mode 100644 index 0000000000..87f5394fee --- /dev/null +++ b/samcli/commands/remote/invoke/core/options.py @@ -0,0 +1,54 @@ +""" +Remote Invoke Command Options related Datastructures for formatting. +""" +from typing import Dict, List + +from samcli.cli.core.options import ALL_COMMON_OPTIONS, add_common_options_info +from samcli.cli.row_modifiers import RowDefinition + +# NOTE: The ordering of the option lists matter, they are the order +# in which options will be displayed. + +INFRASTRUCTURE_OPTION_NAMES: List[str] = ["stack_name"] + +INPUT_EVENT_OPTIONS: List[str] = ["event", "event_file"] + +ADDITIONAL_OPTIONS: List[str] = ["parameter", "output"] + +AWS_CREDENTIAL_OPTION_NAMES: List[str] = ["region", "profile"] + +CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] + +OTHER_OPTIONS: List[str] = ["debug"] + +ALL_OPTIONS: List[str] = ( + INFRASTRUCTURE_OPTION_NAMES + + INPUT_EVENT_OPTIONS + + ADDITIONAL_OPTIONS + + AWS_CREDENTIAL_OPTION_NAMES + + CONFIGURATION_OPTION_NAMES + + ALL_COMMON_OPTIONS +) + +OPTIONS_INFO: Dict[str, Dict] = { + "Infrastructure Options": { + "option_names": {opt: {"rank": idx} for idx, opt in enumerate(INFRASTRUCTURE_OPTION_NAMES)} + }, + "Input Event Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(INPUT_EVENT_OPTIONS)}}, + "Additional Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(ADDITIONAL_OPTIONS)}}, + "AWS Credential Options": { + "option_names": {opt: {"rank": idx} for idx, opt in enumerate(AWS_CREDENTIAL_OPTION_NAMES)} + }, + "Configuration Options": { + "option_names": {opt: {"rank": idx} for idx, opt in enumerate(CONFIGURATION_OPTION_NAMES)}, + "extras": [ + RowDefinition(name="Learn more about configuration files at:"), + RowDefinition( + name="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli" + "-config.html. " + ), + ], + }, +} + +add_common_options_info(OPTIONS_INFO) diff --git a/samcli/lib/cli_validation/remote_invoke_options_validations.py b/samcli/lib/cli_validation/remote_invoke_options_validations.py index dbb0cde6ef..58345cd807 100644 --- a/samcli/lib/cli_validation/remote_invoke_options_validations.py +++ b/samcli/lib/cli_validation/remote_invoke_options_validations.py @@ -4,8 +4,6 @@ import logging import sys from functools import wraps -from io import TextIOWrapper -from typing import cast import click @@ -17,7 +15,7 @@ def event_and_event_file_options_validation(func): """ This function validates the cases when both --event and --event-file are provided and - neither option is provided + logs if "-" is provided for --event-file and event is read from stdin. Parameters ---------- @@ -48,10 +46,9 @@ def wrapped(*args, **kwargs): validator.validate() - # if no event nor event_file arguments are given, read from stdin - if not event and not event_file: - LOG.debug("Neither --event nor --event-file options have been provided, reading from stdin") - kwargs["event_file"] = cast(TextIOWrapper, sys.stdin) + # If "-" is provided for --event-file, click uses it as a special file to refer to stdin. + if event_file and event_file.fileno() == sys.stdin.fileno(): + LOG.info("Reading event from stdin (you can also pass it from file with --event-file)") return func(*args, **kwargs) return wrapped @@ -83,7 +80,7 @@ def wrapped(*args, **kwargs): exception=click.BadOptionUsage( option_name="--resource-id", ctx=ctx, - message="Atleast 1 of --stack-name or --resource-id parameters should be provided.", + message="At least 1 of --stack-name or --resource-id parameters should be provided.", ), ) diff --git a/samcli/lib/remote_invoke/lambda_invoke_executors.py b/samcli/lib/remote_invoke/lambda_invoke_executors.py index 936cd89289..4c47683008 100644 --- a/samcli/lib/remote_invoke/lambda_invoke_executors.py +++ b/samcli/lib/remote_invoke/lambda_invoke_executors.py @@ -111,9 +111,9 @@ def _execute_lambda_invoke(self, payload: str) -> RemoteInvokeIterableResponseTy self.request_parameters, ) lambda_response = self._execute_boto_call(self._lambda_client.invoke) - if self._remote_output_format == RemoteInvokeOutputFormat.RAW: + if self._remote_output_format == RemoteInvokeOutputFormat.JSON: yield RemoteInvokeResponse(lambda_response) - if self._remote_output_format == RemoteInvokeOutputFormat.DEFAULT: + if self._remote_output_format == RemoteInvokeOutputFormat.TEXT: log_result = lambda_response.get(LOG_RESULT) if log_result: yield RemoteInvokeLogOutput(base64.b64decode(log_result).decode("utf-8")) @@ -133,9 +133,9 @@ def _execute_lambda_invoke(self, payload: str) -> RemoteInvokeIterableResponseTy self.request_parameters, ) lambda_response = self._execute_boto_call(self._lambda_client.invoke_with_response_stream) - if self._remote_output_format == RemoteInvokeOutputFormat.RAW: + if self._remote_output_format == RemoteInvokeOutputFormat.JSON: yield RemoteInvokeResponse(lambda_response) - if self._remote_output_format == RemoteInvokeOutputFormat.DEFAULT: + if self._remote_output_format == RemoteInvokeOutputFormat.TEXT: event_stream: EventStream = lambda_response.get(EVENT_STREAM, []) for event in event_stream: if PAYLOAD_CHUNK in event: @@ -154,6 +154,9 @@ class DefaultConvertToJSON(RemoteInvokeRequestResponseMapper[RemoteInvokeExecuti def map(self, test_input: RemoteInvokeExecutionInfo) -> RemoteInvokeExecutionInfo: if not test_input.is_file_provided(): + if not test_input.payload: + LOG.debug("Input event not found, invoking Lambda Function with an empty event") + test_input.payload = "{}" LOG.debug("Mapping input Payload to JSON string object") try: _ = json.loads(cast(str, test_input.payload)) diff --git a/samcli/lib/remote_invoke/remote_invoke_executor_factory.py b/samcli/lib/remote_invoke/remote_invoke_executor_factory.py index 129f9302d9..19bf7ff106 100644 --- a/samcli/lib/remote_invoke/remote_invoke_executor_factory.py +++ b/samcli/lib/remote_invoke/remote_invoke_executor_factory.py @@ -89,12 +89,13 @@ def _create_lambda_boto_executor( :return: Returns the created remote invoke Executor """ + LOG.info(f"Invoking Lambda Function {cfn_resource_summary.logical_resource_id}") lambda_client = self._boto_client_provider("lambda") mappers = [] if _is_function_invoke_mode_response_stream(lambda_client, cfn_resource_summary.physical_resource_id): LOG.debug("Creating response stream invocator for function %s", cfn_resource_summary.physical_resource_id) - if remote_invoke_output_format == RemoteInvokeOutputFormat.RAW: + if remote_invoke_output_format == RemoteInvokeOutputFormat.JSON: mappers = [ LambdaStreamResponseConverter(), ResponseObjectToJsonStringMapper(), @@ -110,7 +111,7 @@ def _create_lambda_boto_executor( log_consumer=log_consumer, ) - if remote_invoke_output_format == RemoteInvokeOutputFormat.RAW: + if remote_invoke_output_format == RemoteInvokeOutputFormat.JSON: mappers = [ LambdaResponseConverter(), ResponseObjectToJsonStringMapper(), diff --git a/samcli/lib/remote_invoke/remote_invoke_executors.py b/samcli/lib/remote_invoke/remote_invoke_executors.py index 0c69a9d5bf..76713bfe98 100644 --- a/samcli/lib/remote_invoke/remote_invoke_executors.py +++ b/samcli/lib/remote_invoke/remote_invoke_executors.py @@ -43,8 +43,8 @@ class RemoteInvokeOutputFormat(Enum): Types of output formats used to by remote invoke """ - DEFAULT = "default" - RAW = "raw" + TEXT = "text" + JSON = "json" class RemoteInvokeExecutionInfo: diff --git a/tests/unit/cli/test_types.py b/tests/unit/cli/test_types.py index eed37f0f58..68a285b2ac 100644 --- a/tests/unit/cli/test_types.py +++ b/tests/unit/cli/test_types.py @@ -504,12 +504,12 @@ def test_must_fail_on_invalid_values(self, input): @parameterized.expand( [ ( - "default", - RemoteInvokeOutputFormat.DEFAULT, + "text", + RemoteInvokeOutputFormat.TEXT, ), ( - "raw", - RemoteInvokeOutputFormat.RAW, + "json", + RemoteInvokeOutputFormat.JSON, ), ] ) diff --git a/tests/unit/commands/remote/invoke/core/__init__.py b/tests/unit/commands/remote/invoke/core/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/commands/remote/invoke/core/test_command.py b/tests/unit/commands/remote/invoke/core/test_command.py new file mode 100644 index 0000000000..aa5604156d --- /dev/null +++ b/tests/unit/commands/remote/invoke/core/test_command.py @@ -0,0 +1,87 @@ +import unittest +from unittest.mock import Mock, patch +from samcli.commands.remote.invoke.cli import RemoteInvokeCommand +from samcli.commands.remote.invoke.cli import DESCRIPTION +from tests.unit.cli.test_command import MockFormatter + + +class MockParams: + def __init__(self, rv, name): + self.rv = rv + self.name = name + + def get_help_record(self, ctx): + return self.rv + + +class TestRemoteInvokeCommand(unittest.TestCase): + @patch.object(RemoteInvokeCommand, "get_params") + def test_get_options_remote_invoke_command_text(self, mock_get_params): + ctx = Mock() + ctx.command_path = "sam remote invoke" + ctx.parent.command_path = "sam" + formatter = MockFormatter(scrub_text=True) + # NOTE: One option per option section. + mock_get_params.return_value = [ + MockParams(rv=("--region", "Region"), name="region"), + MockParams(rv=("--stack-name", ""), name="stack_name"), + MockParams(rv=("--parameter", ""), name="parameter"), + MockParams(rv=("--event", ""), name="event"), + MockParams(rv=("--config-file", ""), name="config_file"), + MockParams(rv=("--beta-features", ""), name="beta_features"), + MockParams(rv=("--debug", ""), name="debug"), + ] + + cmd = RemoteInvokeCommand(name="remote invoke", requires_credentials=True, description=DESCRIPTION) + expected_output = { + "Description": [(cmd.description + cmd.description_addendum, "")], + "Examples": [], + "Invoke default lambda function with empty event": [ + ("", ""), + ("$sam remote invoke --stack-name hello-world\x1b[0m", ""), + ], + "Invoke default lambda function with event passed as text input": [ + ("", ""), + ('$sam remote invoke --stack-name hello-world -e \'{"message": "hello!"}\'\x1b[0m', ""), + ], + "Invoke named lambda function with an event file": [ + ("", ""), + ("$sam remote invoke --stack-name hello-world HelloWorldFunction --event-file event.json\x1b[0m", ""), + ], + "Invoke lambda function with event as stdin input": [ + ("", ""), + ('$ echo \'{"message": "hello!"}\' | sam remote invoke HelloWorldFunction --event-file -\x1b[0m', ""), + ], + "Invoke lambda function using lambda ARN and get the full AWS API response": [ + ("", ""), + ( + "$sam remote invoke arn:aws:lambda:us-west-2:123456789012:function:my-function -e <> --output json\x1b[0m", + "", + ), + ], + "Asynchronously invoke lambda function with additional boto parameters": [ + ("", ""), + ( + "$sam remote invoke HelloWorldFunction -e <> --parameter InvocationType=Event --parameter Qualifier=MyQualifier\x1b[0m", + "", + ), + ], + "Dry invoke a lambda function to validate parameter values and user/role permissions": [ + ("", ""), + ( + "$sam remote invoke HelloWorldFunction -e <> --output json --parameter InvocationType=DryRun\x1b[0m", + "", + ), + ], + "Acronyms": [("ARN", "")], + "Infrastructure Options": [("", ""), ("--stack-name", ""), ("", "")], + "Input Event Options": [("", ""), ("--event", ""), ("", "")], + "Additional Options": [("", ""), ("--parameter", ""), ("", "")], + "AWS Credential Options": [("", ""), ("--region", ""), ("", "")], + "Configuration Options": [("", ""), ("--config-file", ""), ("", "")], + "Beta Options": [("", ""), ("--beta-features", ""), ("", "")], + "Other Options": [("", ""), ("--debug", ""), ("", "")], + } + + cmd.format_options(ctx, formatter) + self.assertEqual(formatter.data, expected_output) diff --git a/tests/unit/commands/remote/invoke/core/test_formatter.py b/tests/unit/commands/remote/invoke/core/test_formatter.py new file mode 100644 index 0000000000..b21652f4ac --- /dev/null +++ b/tests/unit/commands/remote/invoke/core/test_formatter.py @@ -0,0 +1,12 @@ +from shutil import get_terminal_size +from unittest import TestCase + +from samcli.cli.row_modifiers import BaseLineRowModifier +from samcli.commands.remote.invoke.core.formatters import RemoteInvokeCommandHelpTextFormatter + + +class TestRemoteInvokeCommandHelpTextFormatter(TestCase): + def test_remote_invoke_formatter(self): + self.formatter = RemoteInvokeCommandHelpTextFormatter() + self.assertTrue(self.formatter.left_justification_length <= get_terminal_size().columns // 2) + self.assertIsInstance(self.formatter.modifiers[0], BaseLineRowModifier) diff --git a/tests/unit/commands/remote/invoke/core/test_options.py b/tests/unit/commands/remote/invoke/core/test_options.py new file mode 100644 index 0000000000..bdbe2649bc --- /dev/null +++ b/tests/unit/commands/remote/invoke/core/test_options.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from click import Option + +from samcli.commands.remote.invoke.cli import cli +from samcli.commands.remote.invoke.core.options import ALL_OPTIONS + + +class TestOptions(TestCase): + def test_all_options_formatted(self): + command_options = [param.human_readable_name if isinstance(param, Option) else None for param in cli.params] + self.assertEqual(sorted(ALL_OPTIONS), sorted(filter(lambda item: item is not None, command_options + ["help"]))) diff --git a/tests/unit/commands/remote/invoke/test_cli.py b/tests/unit/commands/remote/invoke/test_cli.py index 97aecfc721..fe9179a891 100644 --- a/tests/unit/commands/remote/invoke/test_cli.py +++ b/tests/unit/commands/remote/invoke/test_cli.py @@ -24,14 +24,14 @@ def setUp(self) -> None: @parameterized.expand( [ - ("event", None, RemoteInvokeOutputFormat.DEFAULT, {}, "log-output"), - ("event", None, RemoteInvokeOutputFormat.DEFAULT, {}, None), - ("event", None, RemoteInvokeOutputFormat.DEFAULT, {"Param1": "ParamValue1"}, "log-output"), - ("event", None, RemoteInvokeOutputFormat.RAW, {}, None), - ("event", None, RemoteInvokeOutputFormat.RAW, {"Param1": "ParamValue1"}, "log-output"), - ("event", None, RemoteInvokeOutputFormat.RAW, {"Param1": "ParamValue1"}, None), - (None, "event_file", RemoteInvokeOutputFormat.DEFAULT, {"Param1": "ParamValue1"}, None), - (None, "event_file", RemoteInvokeOutputFormat.RAW, {"Param1": "ParamValue1"}, "log-output"), + ("event", None, RemoteInvokeOutputFormat.TEXT, {}, "log-output"), + ("event", None, RemoteInvokeOutputFormat.TEXT, {}, None), + ("event", None, RemoteInvokeOutputFormat.TEXT, {"Param1": "ParamValue1"}, "log-output"), + ("event", None, RemoteInvokeOutputFormat.JSON, {}, None), + ("event", None, RemoteInvokeOutputFormat.JSON, {"Param1": "ParamValue1"}, "log-output"), + ("event", None, RemoteInvokeOutputFormat.JSON, {"Param1": "ParamValue1"}, None), + (None, "event_file", RemoteInvokeOutputFormat.TEXT, {"Param1": "ParamValue1"}, None), + (None, "event_file", RemoteInvokeOutputFormat.JSON, {"Param1": "ParamValue1"}, "log-output"), ] ) @patch("samcli.lib.remote_invoke.remote_invoke_executors.RemoteInvokeExecutionInfo") @@ -42,7 +42,7 @@ def test_remote_invoke_command( self, event, event_file, - output_format, + output, parameter, log_output, mock_remote_invoke_context, @@ -78,7 +78,7 @@ def test_remote_invoke_command( event=event, event_file=event_file, parameter=parameter, - output_format=output_format, + output=output, region=self.region, profile=self.profile, config_file=self.config_file, @@ -96,7 +96,7 @@ def test_remote_invoke_command( ) patched_remote_invoke_execution_info.assert_called_with( - payload=event, payload_file=event_file, parameters=parameter, output_format=output_format + payload=event, payload_file=event_file, parameters=parameter, output_format=output ) context_mock.run.assert_called_with(remote_invoke_input=given_remote_invoke_execution_info) @@ -121,7 +121,7 @@ def test_raise_user_exception_invoke_not_successfull(self, exeception_to_raise, event="event", event_file=None, parameter={}, - output_format=RemoteInvokeOutputFormat.DEFAULT, + output=RemoteInvokeOutputFormat.TEXT, region=self.region, profile=self.profile, config_file=self.config_file, diff --git a/tests/unit/lib/cli_validation/test_remote_invoke_options_validations.py b/tests/unit/lib/cli_validation/test_remote_invoke_options_validations.py index 17448fc856..5dca536b54 100644 --- a/tests/unit/lib/cli_validation/test_remote_invoke_options_validations.py +++ b/tests/unit/lib/cli_validation/test_remote_invoke_options_validations.py @@ -10,18 +10,22 @@ class TestEventFileValidation(TestCase): + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.sys") @patch("samcli.lib.cli_validation.remote_invoke_options_validations.LOG") @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") - def test_both_not_provided_params(self, patched_click_context, patched_log): + def test_event_file_provided_as_stdin(self, patched_click_context, patched_log, patched_sys): mock_func = Mock() mocked_context = Mock() patched_click_context.return_value = mocked_context + mock_event_file = Mock() + mock_event_file.fileno.return_value = 0 + patched_sys.stdin.fileno.return_value = 0 - mocked_context.params.get.return_value = {} + mocked_context.params.get.side_effect = lambda key: mock_event_file if key == "event_file" else None event_and_event_file_options_validation(mock_func)() - patched_log.debug.assert_called_with( - "Neither --event nor --event-file options have been provided, reading from stdin" + patched_log.info.assert_called_with( + "Reading event from stdin (you can also pass it from file with --event-file)" ) mock_func.assert_called_once() @@ -39,14 +43,18 @@ def test_only_event_param(self, patched_click_context): mock_func.assert_called_once() + @patch("samcli.lib.cli_validation.remote_invoke_options_validations.sys") @patch("samcli.lib.cli_validation.remote_invoke_options_validations.click.get_current_context") - def test_only_event_file_param(self, patched_click_context): + def test_only_event_file_param(self, patched_click_context, patched_sys): mock_func = Mock() mocked_context = Mock() patched_click_context.return_value = mocked_context + mock_event_file = Mock() + mock_event_file.fileno.return_value = 4 + patched_sys.stdin.fileno.return_value = 0 - mocked_context.params.get.side_effect = lambda key: "event_file" if key == "event_file" else None + mocked_context.params.get.side_effect = lambda key: mock_event_file if key == "event_file" else None event_and_event_file_options_validation(mock_func)() @@ -106,6 +114,8 @@ def test_no_params_provided(self, patched_click_context): with self.assertRaises(BadOptionUsage) as ex: stack_name_or_resource_id_atleast_one_option_validation(mock_func)() - self.assertIn("Atleast 1 of --stack-name or --resource-id parameters should be provided.", ex.exception.message) + self.assertIn( + "At least 1 of --stack-name or --resource-id parameters should be provided.", ex.exception.message + ) mock_func.assert_not_called() diff --git a/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py b/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py index dca00cafae..e1a5093a2f 100644 --- a/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py +++ b/tests/unit/lib/remote_invoke/test_lambda_invoke_executors.py @@ -96,7 +96,7 @@ def setUp(self) -> None: self.lambda_client = Mock() self.function_name = Mock() self.lambda_invoke_executor = LambdaInvokeExecutor( - self.lambda_client, self.function_name, RemoteInvokeOutputFormat.RAW + self.lambda_client, self.function_name, RemoteInvokeOutputFormat.JSON ) def test_execute_action(self): @@ -120,7 +120,7 @@ def setUp(self) -> None: self.lambda_client = Mock() self.function_name = Mock() self.lambda_invoke_executor = LambdaInvokeWithResponseStreamExecutor( - self.lambda_client, self.function_name, RemoteInvokeOutputFormat.RAW + self.lambda_client, self.function_name, RemoteInvokeOutputFormat.JSON ) def test_execute_action(self): @@ -142,10 +142,11 @@ def _get_boto3_method(self): class TestDefaultConvertToJSON(TestCase): def setUp(self) -> None: self.lambda_convert_to_default_json = DefaultConvertToJSON() - self.output_format = RemoteInvokeOutputFormat.DEFAULT + self.output_format = RemoteInvokeOutputFormat.TEXT @parameterized.expand( [ + (None, "{}"), ("Hello World", '"Hello World"'), ('{"message": "hello world"}', '{"message": "hello world"}'), ] @@ -170,7 +171,7 @@ def setUp(self) -> None: self.lambda_response_converter = LambdaResponseConverter() def test_lambda_streaming_body_response_conversion(self): - output_format = RemoteInvokeOutputFormat.DEFAULT + output_format = RemoteInvokeOutputFormat.TEXT given_streaming_body = Mock() given_decoded_string = "decoded string" given_streaming_body.read().decode.return_value = given_decoded_string @@ -185,7 +186,7 @@ def test_lambda_streaming_body_response_conversion(self): self.assertEqual(result.response, expected_result) def test_lambda_streaming_body_invalid_response_exception(self): - output_format = RemoteInvokeOutputFormat.DEFAULT + output_format = RemoteInvokeOutputFormat.TEXT given_streaming_body = Mock() given_decoded_string = "decoded string" given_streaming_body.read().decode.return_value = given_decoded_string @@ -205,7 +206,7 @@ def setUp(self) -> None: [({LOG_RESULT: base64.b64encode(b"log output")}, {LOG_RESULT: base64.b64encode(b"log output")}), ({}, {})] ) def test_lambda_streaming_body_response_conversion(self, invoke_complete_response, mapped_log_response): - output_format = RemoteInvokeOutputFormat.DEFAULT + output_format = RemoteInvokeOutputFormat.TEXT given_test_result = { EVENT_STREAM: [ {PAYLOAD_CHUNK: {PAYLOAD: b"stream1"}}, @@ -229,7 +230,7 @@ def test_lambda_streaming_body_response_conversion(self, invoke_complete_respons self.assertEqual(result.response, expected_result) def test_lambda_streaming_body_invalid_response_exception(self): - output_format = RemoteInvokeOutputFormat.DEFAULT + output_format = RemoteInvokeOutputFormat.TEXT remote_invoke_execution_info = RemoteInvokeExecutionInfo(None, None, {}, output_format) remote_invoke_execution_info.response = Mock() diff --git a/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py b/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py index bbb8c1bac9..57b5e7988c 100644 --- a/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py +++ b/tests/unit/lib/remote_invoke/test_remote_invoke_executor_factory.py @@ -51,7 +51,7 @@ def test_failed_create_test_executor(self): self.assertIsNone(executor) @parameterized.expand( - itertools.product([True, False], [RemoteInvokeOutputFormat.RAW, RemoteInvokeOutputFormat.DEFAULT]) + itertools.product([True, False], [RemoteInvokeOutputFormat.JSON, RemoteInvokeOutputFormat.TEXT]) ) @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaInvokeExecutor") @patch("samcli.lib.remote_invoke.remote_invoke_executor_factory.LambdaInvokeWithResponseStreamExecutor") @@ -96,7 +96,7 @@ def test_create_lambda_test_executor( if is_function_invoke_mode_response_stream: expected_mappers = [] - if remote_invoke_output_format == RemoteInvokeOutputFormat.RAW: + if remote_invoke_output_format == RemoteInvokeOutputFormat.JSON: patched_object_to_json_converter.assert_called_once() patched_stream_response_converter.assert_called_once() patched_lambda_invoke_with_response_stream_executor.assert_called_with( @@ -115,7 +115,7 @@ def test_create_lambda_test_executor( ) else: expected_mappers = [] - if remote_invoke_output_format == RemoteInvokeOutputFormat.RAW: + if remote_invoke_output_format == RemoteInvokeOutputFormat.JSON: patched_object_to_json_converter.assert_called_once() patched_response_converter.assert_called_once() patched_lambda_invoke_executor.assert_called_with( diff --git a/tests/unit/lib/remote_invoke/test_remote_invoke_executors.py b/tests/unit/lib/remote_invoke/test_remote_invoke_executors.py index 8f3ce96e46..19589fc2b9 100644 --- a/tests/unit/lib/remote_invoke/test_remote_invoke_executors.py +++ b/tests/unit/lib/remote_invoke/test_remote_invoke_executors.py @@ -17,7 +17,7 @@ class TestRemoteInvokeExecutionInfo(TestCase): def setUp(self) -> None: - self.output_format = RemoteInvokeOutputFormat.DEFAULT + self.output_format = RemoteInvokeOutputFormat.TEXT def test_execution_info_payload(self): given_payload = Mock() @@ -76,7 +76,7 @@ def setUp(self) -> None: def test_execute_with_payload(self): given_payload = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} - given_output_format = "default" + given_output_format = "text" test_execution_info = RemoteInvokeExecutionInfo(given_payload, None, given_parameters, given_output_format) with patch.object(self.boto_action_executor, "_execute_action") as patched_execute_action, patch.object( @@ -93,7 +93,7 @@ def test_execute_with_payload(self): def test_execute_with_payload_file(self): given_payload_file = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} - given_output_format = "original-boto-response" + given_output_format = "json" test_execution_info = RemoteInvokeExecutionInfo(None, given_payload_file, given_parameters, given_output_format) with patch.object(self.boto_action_executor, "_execute_action") as patched_execute_action, patch.object( @@ -110,7 +110,7 @@ def test_execute_with_payload_file(self): def test_execute_error(self): given_payload = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} - given_output_format = "original-boto-response" + given_output_format = "json" test_execution_info = RemoteInvokeExecutionInfo(given_payload, None, given_parameters, given_output_format) with patch.object(self.boto_action_executor, "_execute_action") as patched_execute_action: @@ -143,7 +143,7 @@ def setUp(self) -> None: def test_execution(self): given_payload = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} - given_output_format = RemoteInvokeOutputFormat.RAW + given_output_format = RemoteInvokeOutputFormat.JSON test_execution_info = RemoteInvokeExecutionInfo(given_payload, None, given_parameters, given_output_format) validate_action_parameters_function = Mock() self.mock_boto_action_executor.validate_action_parameters = validate_action_parameters_function @@ -162,7 +162,7 @@ def test_execution(self): def test_execution_failure(self): given_payload = Mock() given_parameters = {"ExampleParameter": "ExampleValue"} - given_output_format = RemoteInvokeOutputFormat.RAW + given_output_format = RemoteInvokeOutputFormat.JSON test_execution_info = RemoteInvokeExecutionInfo(given_payload, None, given_parameters, given_output_format) validate_action_parameters_function = Mock() self.mock_boto_action_executor.validate_action_parameters = validate_action_parameters_function @@ -186,7 +186,7 @@ def test_execution_failure(self): class TestResponseObjectToJsonStringMapper(TestCase): def test_mapper(self): - output_format = RemoteInvokeOutputFormat.DEFAULT + output_format = RemoteInvokeOutputFormat.TEXT given_object = [{"key": "value", "key2": 123}] test_execution_info = RemoteInvokeExecutionInfo(None, None, {}, output_format) test_execution_info.response = given_object From d70864e9a281d02ac91b1a3804191ea567da6b65 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:43:51 -0700 Subject: [PATCH 057/107] chore: temporary pin python version to 3.7.16 (#5384) * chore: temporary pin python version to 3.7.16 * fix github action syntax error --- .github/workflows/build.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03e29508fe..a48b384170 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,9 +57,17 @@ jobs: - "3.11" steps: - uses: actions/checkout@v3 + # @melasmar + # TODO: Revert back to use 3.7 to all operating systems after the regression issue in Python + # https://github.com/actions/setup-python/issues/682 in github action got resolved - uses: actions/setup-python@v4 + if: matrix.os != 'macos-latest' || ( matrix.os == 'macos-latest' && matrix.python != '3.7' ) with: python-version: ${{ matrix.python }} + - uses: actions/setup-python@v4 + if: matrix.os == 'macos-latest' && matrix.python == '3.7' + with: + python-version: "3.7.16" - run: test -f "./.github/ISSUE_TEMPLATE/Bug_report.md" # prevent Bug_report.md from being renamed or deleted - run: make init - run: make pr From 269b86e26c110aaef848ae97db77d195a47285e4 Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:53:54 -0700 Subject: [PATCH 058/107] Updated cfn-lint to support ruby3.2 in validate (#5375) --- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 480df5b67a..2ba320212e 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -28,4 +28,4 @@ regex!=2021.10.8 tzlocal==3.0 #Adding cfn-lint dependency for SAM validate -cfn-lint~=0.77.5 +cfn-lint~=0.77.9 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index a523dcc943..a6438fc289 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -112,9 +112,9 @@ cffi==1.15.1 \ --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 # via cryptography -cfn-lint==0.77.5 \ - --hash=sha256:4282d13ffe76a5dee6431b1f56e3641d87c28b1ef5be663afe7d8dbf13f28bdb \ - --hash=sha256:b5126dffb834078a71341090d49669046076c09196f0d2bdca68dbace1bf357a +cfn-lint==0.77.9 \ + --hash=sha256:7c1e631b723b521234d92d4081934291b256dba28d723ddb7ff105215fe40020 \ + --hash=sha256:f95b503f7465ee1f2f89ddf32289ea03a517f08c366bb8e6a5d6773a11e5a1aa # via aws-sam-cli (setup.py) chardet==5.1.0 \ --hash=sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5 \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index b9c4c8abcf..0122453b5e 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -130,9 +130,9 @@ cffi==1.15.1 \ --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \ --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0 # via cryptography -cfn-lint==0.77.5 \ - --hash=sha256:4282d13ffe76a5dee6431b1f56e3641d87c28b1ef5be663afe7d8dbf13f28bdb \ - --hash=sha256:b5126dffb834078a71341090d49669046076c09196f0d2bdca68dbace1bf357a +cfn-lint==0.77.9 \ + --hash=sha256:7c1e631b723b521234d92d4081934291b256dba28d723ddb7ff105215fe40020 \ + --hash=sha256:f95b503f7465ee1f2f89ddf32289ea03a517f08c366bb8e6a5d6773a11e5a1aa # via aws-sam-cli (setup.py) chardet==5.1.0 \ --hash=sha256:0d62712b956bc154f85fb0a266e2a3c5913c2967e00348701b32411d6def31e5 \ From ccb3f7f488073b21f7509df943236e1ebc0b147a Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:54:21 -0700 Subject: [PATCH 059/107] Remove unneeded test cases (#5374) * Remove unneeded test cases * Removing the two integ test cases as there is already coverage in unit test for cases that no region is specified --- .../integration/delete/test_delete_command.py | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/tests/integration/delete/test_delete_command.py b/tests/integration/delete/test_delete_command.py index 9c3fe74c0e..ea8cf3010f 100644 --- a/tests/integration/delete/test_delete_command.py +++ b/tests/integration/delete/test_delete_command.py @@ -436,52 +436,6 @@ def test_no_prompts_no_stack_name(self): delete_process_execute = run_command(delete_command_list) self.assertEqual(delete_process_execute.process.returncode, 2) - @pytest.mark.flaky(reruns=3) - def test_no_prompts_no_region(self): - stack_name = self._method_to_stack_name(self.id()) - - delete_command_list = self.get_delete_command_list(stack_name=stack_name, no_prompts=True) - delete_process_execute = run_command(delete_command_list) - self.assertEqual(delete_process_execute.process.returncode, 2) - - @parameterized.expand( - [ - "aws-serverless-function.yaml", - ] - ) - @pytest.mark.flaky(reruns=3) - def test_delete_guided_no_stack_name_no_region(self, template_file): - template_path = self.test_data_path.joinpath(template_file) - - stack_name = self._method_to_stack_name(self.id()) - - deploy_command_list = self.get_deploy_command_list( - template_file=template_path, - stack_name=stack_name, - capabilities="CAPABILITY_IAM", - s3_bucket=self.bucket_name, - s3_prefix=self.s3_prefix, - force_upload=True, - notification_arns=self.sns_arn, - parameter_overrides="Parameter=Clarity", - kms_key_id=self.kms_key, - no_execute_changeset=False, - tags="integ=true clarity=yes foo_bar=baz", - confirm_changeset=False, - region=self._session.region_name, - ) - deploy_process_execute = run_command(deploy_command_list) - - delete_command_list = self.get_delete_command_list() - delete_process_execute = run_command_with_input(delete_command_list, "{}\ny\ny\n".format(stack_name).encode()) - - self.assertEqual(delete_process_execute.process.returncode, 0) - - try: - resp = self.cf_client.describe_stacks(StackName=stack_name) - except ClientError as ex: - self.assertIn(f"Stack with id {stack_name} does not exist", str(ex)) - @parameterized.expand( [ "aws-ecr-repository.yaml", From 0033b78e76bfffe969eb86ff8216bdd7bf847753 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:30:28 +0000 Subject: [PATCH 060/107] feat: updating app templates repo hash with (67f28fd83477e0e15b394f995afb33b2053b4074) (#5362) Co-authored-by: GitHub Action Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> --- samcli/runtime_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/runtime_config.json b/samcli/runtime_config.json index b038567d8c..b648be6cc3 100644 --- a/samcli/runtime_config.json +++ b/samcli/runtime_config.json @@ -1,3 +1,3 @@ { - "app_template_repo_commit": "66f4a230d1c939a0c3f7b5647710c694c3a486f7" + "app_template_repo_commit": "67f28fd83477e0e15b394f995afb33b2053b4074" } From 887411fe4ece31d8fd9f0e36dc4ad85cf07c716f Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 21 Jun 2023 12:32:07 -0400 Subject: [PATCH 061/107] test: Integration tests for remote invoke on regular lambda functions (#5382) * Created base integ glass for remote invoke tests * Add integration tests for invoking lambda functions * make black * Moved tearDownClass to base class * Removed tearDown class from inherited classes and updated lambda fn timeout * Remove the check to skip appveyor tests on master branch --- tests/integration/remote/__init__.py | 0 tests/integration/remote/invoke/__init__.py | 0 .../remote/invoke/remote_invoke_integ_base.py | 104 +++++++ .../remote/invoke/test_remote_invoke.py | 279 ++++++++++++++++++ .../testdata/remote_invoke/__init__.py | 0 .../remote_invoke/events/default_event.json | 5 + .../remote_invoke/lambda-fns/__init__.py | 0 .../testdata/remote_invoke/lambda-fns/main.py | 31 ++ .../childstack/function/__init__.py | 0 .../childstack/function/app.py | 4 + .../childstack/function/requirements.txt | 0 .../nested_templates/childstack/template.yaml | 11 + .../nested_templates/template.yaml | 8 + .../template-multiple-resources.yaml | 55 ++++ .../remote_invoke/template-single-lambda.yaml | 11 + 15 files changed, 508 insertions(+) create mode 100644 tests/integration/remote/__init__.py create mode 100644 tests/integration/remote/invoke/__init__.py create mode 100644 tests/integration/remote/invoke/remote_invoke_integ_base.py create mode 100644 tests/integration/remote/invoke/test_remote_invoke.py create mode 100644 tests/integration/testdata/remote_invoke/__init__.py create mode 100644 tests/integration/testdata/remote_invoke/events/default_event.json create mode 100644 tests/integration/testdata/remote_invoke/lambda-fns/__init__.py create mode 100644 tests/integration/testdata/remote_invoke/lambda-fns/main.py create mode 100644 tests/integration/testdata/remote_invoke/nested_templates/childstack/function/__init__.py create mode 100644 tests/integration/testdata/remote_invoke/nested_templates/childstack/function/app.py create mode 100644 tests/integration/testdata/remote_invoke/nested_templates/childstack/function/requirements.txt create mode 100644 tests/integration/testdata/remote_invoke/nested_templates/childstack/template.yaml create mode 100644 tests/integration/testdata/remote_invoke/nested_templates/template.yaml create mode 100644 tests/integration/testdata/remote_invoke/template-multiple-resources.yaml create mode 100644 tests/integration/testdata/remote_invoke/template-single-lambda.yaml diff --git a/tests/integration/remote/__init__.py b/tests/integration/remote/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/remote/invoke/__init__.py b/tests/integration/remote/invoke/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/remote/invoke/remote_invoke_integ_base.py b/tests/integration/remote/invoke/remote_invoke_integ_base.py new file mode 100644 index 0000000000..b0bd0cdaf8 --- /dev/null +++ b/tests/integration/remote/invoke/remote_invoke_integ_base.py @@ -0,0 +1,104 @@ +from unittest import TestCase, skipIf +from pathlib import Path +from typing import Optional + +from tests.testing_utils import ( + get_sam_command, + run_command, +) +from tests.integration.deploy.deploy_integ_base import DeployIntegBase + +from samcli.lib.utils.boto_utils import get_boto_resource_provider_with_config, get_boto_client_provider_with_config +from samcli.lib.utils.cloudformation import get_resource_summaries + + +class RemoteInvokeIntegBase(TestCase): + template: Optional[Path] = None + + @classmethod + def setUpClass(cls): + cls.cmd = get_sam_command() + cls.test_data_path = cls.get_integ_dir().joinpath("testdata") + if cls.template: + cls.template_path = str(cls.test_data_path.joinpath("remote_invoke", cls.template)) + cls.events_folder_path = cls.test_data_path.joinpath("remote_invoke", "events") + + @classmethod + def tearDownClass(cls): + # Delete the deployed stack + cls.cfn_client.delete_stack(StackName=cls.stack_name) + + @staticmethod + def get_integ_dir(): + return Path(__file__).resolve().parents[2] + + @staticmethod + def remote_invoke_deploy_stack(stack_name, template_path): + + deploy_cmd = DeployIntegBase.get_deploy_command_list( + stack_name=stack_name, + template_file=template_path, + resolve_s3=True, + capabilities_list=["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"], + ) + + run_command(deploy_cmd) + + @classmethod + def create_resources_and_boto_clients(cls): + cls.remote_invoke_deploy_stack(cls.stack_name, cls.template_path) + stack_resource_summaries = get_resource_summaries( + get_boto_resource_provider_with_config(), + get_boto_client_provider_with_config(), + cls.stack_name, + ) + cls.stack_resources = { + resource_full_path: stack_resource_summary.physical_resource_id + for resource_full_path, stack_resource_summary in stack_resource_summaries.items() + } + cls.cfn_client = get_boto_client_provider_with_config()("cloudformation") + cls.lambda_client = get_boto_client_provider_with_config()("lambda") + + @staticmethod + def get_command_list( + stack_name=None, + resource_id=None, + event=None, + event_file=None, + parameter_list=None, + output=None, + region=None, + profile=None, + beta_features=None, + ): + command_list = [get_sam_command(), "remote", "invoke"] + + if stack_name: + command_list = command_list + ["--stack-name", stack_name] + + if event: + command_list = command_list + ["-e", event] + + if event_file: + command_list = command_list + ["--event-file", event_file] + + if profile: + command_list = command_list + ["--parameter", parameter] + + if output: + command_list = command_list + ["--output", output] + + if parameter_list: + for (parameter, value) in parameter_list: + command_list = command_list + ["--parameter", f"{parameter}={value}"] + + if region: + command_list = command_list + ["--region", region] + + if beta_features is not None: + command_list = command_list + ["--beta-features" if beta_features else "--no-beta-features"] + + if resource_id: + command_list = command_list + [resource_id] + + return command_list diff --git a/tests/integration/remote/invoke/test_remote_invoke.py b/tests/integration/remote/invoke/test_remote_invoke.py new file mode 100644 index 0000000000..e3eb6aa2de --- /dev/null +++ b/tests/integration/remote/invoke/test_remote_invoke.py @@ -0,0 +1,279 @@ +import json +import uuid +import base64 + +from parameterized import parameterized + +from tests.integration.remote.invoke.remote_invoke_integ_base import RemoteInvokeIntegBase +from tests.testing_utils import run_command + +from pathlib import Path +import pytest + + +@pytest.mark.xdist_group(name="sam_remote_invoke_single_lambda_resource") +class TestSingleResourceInvoke(RemoteInvokeIntegBase): + template = Path("template-single-lambda.yaml") + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stack_name = f"{TestSingleResourceInvoke.__name__}-{uuid.uuid4().hex}" + cls.create_resources_and_boto_clients() + + def test_invoke_empty_event_provided(self): + command_list = self.get_command_list(stack_name=self.stack_name) + + remote_invoke_result = run_command(command_list) + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout["errorType"], "KeyError") + + def test_invoke_with_only_event_provided(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, {"message": "Hello world"}) + + def test_invoke_with_only_event_file_provided(self): + event_file_path = str(self.events_folder_path.joinpath("default_event.json")) + command_list = self.get_command_list( + stack_name=self.stack_name, resource_id="HelloWorldFunction", event_file=event_file_path + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, {"message": "Hello world"}) + + def test_invoke_with_resource_id_provided_as_arn(self): + resource_id = "HelloWorldFunction" + lambda_name = self.stack_resources[resource_id] + lambda_arn = self.lambda_client.get_function(FunctionName=lambda_name)["Configuration"]["FunctionArn"] + + command_list = self.get_command_list( + resource_id=lambda_arn, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, {"message": "Hello world"}) + + def test_invoke_asynchronous_using_boto_parameter(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + parameter_list=[("InvocationType", "Event"), ("LogType", "None")], + output="json", + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout["Payload"], "") + self.assertEqual(remote_invoke_result_stdout["StatusCode"], 202) + + def test_invoke_dryrun_using_boto_parameter(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + parameter_list=[("InvocationType", "DryRun"), ("Qualifier", "$LATEST")], + output="json", + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout["Payload"], "") + self.assertEqual(remote_invoke_result_stdout["StatusCode"], 204) + + def test_invoke_response_json_output_format(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + output="json", + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + + response_payload = json.loads(remote_invoke_result_stdout["Payload"]) + self.assertEqual(response_payload, {"message": "Hello world"}) + self.assertEqual(remote_invoke_result_stdout["StatusCode"], 200) + + +@pytest.mark.xdist_group(name="sam_remote_invoke_multiple_resources") +class TestMultipleResourcesInvoke(RemoteInvokeIntegBase): + template = Path("template-multiple-resources.yaml") + + @classmethod + def tearDownClass(cls): + # Delete the deployed stack + cls.cfn_client.delete_stack(StackName=cls.stack_name) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stack_name = f"{TestMultipleResourcesInvoke.__name__}-{uuid.uuid4().hex}" + cls.create_resources_and_boto_clients() + + def test_invoke_empty_event_provided(self): + command_list = self.get_command_list(stack_name=self.stack_name, resource_id="EchoEventFunction") + + remote_invoke_result = run_command(command_list) + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, {}) + + @parameterized.expand( + [ + ("HelloWorldServerlessFunction", {"message": "Hello world"}), + ("EchoCustomEnvVarFunction", "MyOtherVar"), + ("EchoEventFunction", {"key1": "Hello", "key2": "serverless", "key3": "world"}), + ] + ) + def test_invoke_with_only_event_provided(self, resource_id, expected_response): + command_list = self.get_command_list( + stack_name=self.stack_name, + resource_id=resource_id, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, expected_response) + + @parameterized.expand( + [ + ("HelloWorldServerlessFunction", {"message": "Hello world"}), + ("EchoCustomEnvVarFunction", "MyOtherVar"), + ("EchoEventFunction", {"key1": "Hello", "key2": "serverless", "key3": "world"}), + ] + ) + def test_invoke_with_resource_id_provided_as_arn(self, resource_id, expected_response): + lambda_name = self.stack_resources[resource_id] + lambda_arn = self.lambda_client.get_function(FunctionName=lambda_name)["Configuration"]["FunctionArn"] + + command_list = self.get_command_list( + resource_id=lambda_arn, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, expected_response) + + def test_lambda_writes_to_stderr_invoke(self): + command_list = RemoteInvokeIntegBase.get_command_list( + stack_name=self.stack_name, + resource_id="WriteToStderrFunction", + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = remote_invoke_result.stdout.strip().decode() + remote_invoke_result_stderr = remote_invoke_result.stderr.strip().decode() + self.assertIn("Lambda Function is writing to stderr", remote_invoke_result_stderr) + self.assertEqual('"wrote to stderr"', remote_invoke_result_stdout) + + def test_lambda_raises_exception_invoke(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + resource_id="RaiseExceptionFunction", + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stderr = remote_invoke_result.stderr.strip().decode() + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + + self.assertIn("Lambda is raising an exception", remote_invoke_result_stderr) + self.assertEqual("Lambda is raising an exception", remote_invoke_result_stdout["errorMessage"]) + + def test_lambda_invoke_client_context_boto_parameter(self): + custom_json_str = {"custom": {"foo": "bar", "baz": "quzz"}} + client_context_base64_str = base64.b64encode(json.dumps(custom_json_str).encode()).decode("utf-8") + + command_list = self.get_command_list( + stack_name=self.stack_name, + resource_id="EchoClientContextData", + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + parameter_list=[("ClientContext", client_context_base64_str)], + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, custom_json_str["custom"]) + + +@pytest.mark.xdist_group(name="sam_remote_invoke_nested_resources") +class TestNestedTemplateResourcesInvoke(RemoteInvokeIntegBase): + template = Path("nested_templates/template.yaml") + + @classmethod + def tearDownClass(cls): + # Delete the deployed stack + cls.cfn_client.delete_stack(StackName=cls.stack_name) + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stack_name = f"{TestNestedTemplateResourcesInvoke.__name__}-{uuid.uuid4().hex}" + cls.create_resources_and_boto_clients() + + def test_invoke_empty_event_provided(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + ) + + remote_invoke_result = run_command(command_list) + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, {"message": "Hello world"}) + + def test_invoke_with_only_event_provided(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + resource_id="ChildStack/HelloWorldFunction", + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, {"message": "Hello world"}) + + def test_invoke_default_lambda_function(self): + event_file_path = str(self.events_folder_path.joinpath("default_event.json")) + command_list = self.get_command_list(stack_name=self.stack_name, event_file=event_file_path) + + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + self.assertEqual(remote_invoke_result_stdout, {"message": "Hello world"}) diff --git a/tests/integration/testdata/remote_invoke/__init__.py b/tests/integration/testdata/remote_invoke/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/testdata/remote_invoke/events/default_event.json b/tests/integration/testdata/remote_invoke/events/default_event.json new file mode 100644 index 0000000000..b842029ae7 --- /dev/null +++ b/tests/integration/testdata/remote_invoke/events/default_event.json @@ -0,0 +1,5 @@ +{ + "key1": "Hello", + "key2": "serverless", + "key3": "world" +} \ No newline at end of file diff --git a/tests/integration/testdata/remote_invoke/lambda-fns/__init__.py b/tests/integration/testdata/remote_invoke/lambda-fns/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/testdata/remote_invoke/lambda-fns/main.py b/tests/integration/testdata/remote_invoke/lambda-fns/main.py new file mode 100644 index 0000000000..b3424d1b70 --- /dev/null +++ b/tests/integration/testdata/remote_invoke/lambda-fns/main.py @@ -0,0 +1,31 @@ +import os +import logging + +LOG = logging.getLogger(__name__) + +def default_handler(event, context): + print("value1 = " + event["key1"]) + print("value2 = " + event["key2"]) + print("value3 = " + event["key3"]) + + return { + "message": f'{event["key1"]} {event["key3"]}' + } + +def custom_env_var_echo_handler(event, context): + return os.environ.get("CustomEnvVar") + +def echo_client_context_data(event, context): + custom_dict = context.client_context.custom + return custom_dict + +def write_to_stderr(event, context): + LOG.error("Lambda Function is writing to stderr") + + return "wrote to stderr" + +def echo_event(event, context): + return event + +def raise_exception(event, context): + raise Exception("Lambda is raising an exception") \ No newline at end of file diff --git a/tests/integration/testdata/remote_invoke/nested_templates/childstack/function/__init__.py b/tests/integration/testdata/remote_invoke/nested_templates/childstack/function/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/testdata/remote_invoke/nested_templates/childstack/function/app.py b/tests/integration/testdata/remote_invoke/nested_templates/childstack/function/app.py new file mode 100644 index 0000000000..cce4a03dca --- /dev/null +++ b/tests/integration/testdata/remote_invoke/nested_templates/childstack/function/app.py @@ -0,0 +1,4 @@ +def handler(event, context): + return { + "message": "Hello world", + } \ No newline at end of file diff --git a/tests/integration/testdata/remote_invoke/nested_templates/childstack/function/requirements.txt b/tests/integration/testdata/remote_invoke/nested_templates/childstack/function/requirements.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/integration/testdata/remote_invoke/nested_templates/childstack/template.yaml b/tests/integration/testdata/remote_invoke/nested_templates/childstack/template.yaml new file mode 100644 index 0000000000..c082eb0fe4 --- /dev/null +++ b/tests/integration/testdata/remote_invoke/nested_templates/childstack/template.yaml @@ -0,0 +1,11 @@ +AWSTemplateFormatVersion : '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + Handler: app.handler + Runtime: python3.9 + CodeUri: function/ + Timeout: 30 \ No newline at end of file diff --git a/tests/integration/testdata/remote_invoke/nested_templates/template.yaml b/tests/integration/testdata/remote_invoke/nested_templates/template.yaml new file mode 100644 index 0000000000..66cd5f3433 --- /dev/null +++ b/tests/integration/testdata/remote_invoke/nested_templates/template.yaml @@ -0,0 +1,8 @@ +AWSTemplateFormatVersion: "2010-09-09" +Transform: AWS::Serverless-2016-10-31 + +Resources: + ChildStack: + Properties: + TemplateURL: childstack/template.yaml + Type: AWS::CloudFormation::Stack \ No newline at end of file diff --git a/tests/integration/testdata/remote_invoke/template-multiple-resources.yaml b/tests/integration/testdata/remote_invoke/template-multiple-resources.yaml new file mode 100644 index 0000000000..ffebe530c1 --- /dev/null +++ b/tests/integration/testdata/remote_invoke/template-multiple-resources.yaml @@ -0,0 +1,55 @@ +AWSTemplateFormatVersion : '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: A hello world application that creates multiple resources used for inteting remote invoke command. + +Resources: + HelloWorldServerlessFunction: + Type: AWS::Serverless::Function + Properties: + Handler: main.default_handler + Runtime: python3.9 + CodeUri: ./lambda-fns + Timeout: 5 + + EchoEventFunction: + Type: AWS::Serverless::Function + Properties: + Handler: main.echo_event + Runtime: python3.9 + CodeUri: ./lambda-fns + Timeout: 5 + + EchoClientContextData: + Type: AWS::Serverless::Function + Properties: + Handler: main.echo_client_context_data + Runtime: python3.9 + CodeUri: ./lambda-fns + Timeout: 5 + + EchoCustomEnvVarFunction: + Type: AWS::Serverless::Function + Properties: + Handler: main.custom_env_var_echo_handler + Runtime: python3.9 + CodeUri: ./lambda-fns + Environment: + Variables: + CustomEnvVar: "MyOtherVar" + Timeout: 5 + + WriteToStderrFunction: + Type: AWS::Serverless::Function + Properties: + Handler: main.write_to_stderr + Runtime: python3.9 + CodeUri: ./lambda-fns + Timeout: 5 + + RaiseExceptionFunction: + Type: AWS::Serverless::Function + Properties: + Handler: main.raise_exception + Runtime: python3.9 + CodeUri: ./lambda-fns + Timeout: 5 \ No newline at end of file diff --git a/tests/integration/testdata/remote_invoke/template-single-lambda.yaml b/tests/integration/testdata/remote_invoke/template-single-lambda.yaml new file mode 100644 index 0000000000..7ca9b259db --- /dev/null +++ b/tests/integration/testdata/remote_invoke/template-single-lambda.yaml @@ -0,0 +1,11 @@ +AWSTemplateFormatVersion : '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Description: A hello world application with single lambda function. + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + Handler: main.default_handler + Runtime: python3.9 + CodeUri: ./lambda-fns \ No newline at end of file From f43c705384a80cc23c5dacc3627c384ac19f49c1 Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 21 Jun 2023 13:37:40 -0400 Subject: [PATCH 062/107] feat: Make remote invoke command available (#5381) * Enabled remote invoke command and updated docs link * Created base integ glass for remote invoke tests * Added end2end integ tests for remote invoke * make black * Moved tearDownClass to base class * Remove the check to skip appveyor tests on master branch --- samcli/cli/command.py | 7 ++++++- samcli/cli/root/command_list.py | 1 + .../commands/remote/remote_invoke_context.py | 4 ++-- .../remote_invoke_options_validations.py | 2 +- samcli/lib/docs/documentation_links.json | 1 + .../remote_invoke/lambda_invoke_executors.py | 2 +- tests/end_to_end/end_to_end_base.py | 6 ++++++ tests/end_to_end/test_runtimes_e2e.py | 19 +++++++++++++------ tests/end_to_end/test_stages.py | 15 --------------- tests/unit/cli/test_command.py | 2 ++ tests/unit/commands/docs/core/test_command.py | 2 +- tests/unit/commands/docs/test_docs_context.py | 1 + 12 files changed, 35 insertions(+), 27 deletions(-) diff --git a/samcli/cli/command.py b/samcli/cli/command.py index 23934f9eb4..df14266948 100644 --- a/samcli/cli/command.py +++ b/samcli/cli/command.py @@ -30,7 +30,7 @@ "samcli.commands.pipeline.pipeline", "samcli.commands.list.list", "samcli.commands.docs", - # "samcli.commands.remote.remote", + "samcli.commands.remote.remote", # We intentionally do not expose the `bootstrap` command for now. We might open it up later # "samcli.commands.bootstrap", ] @@ -173,6 +173,11 @@ def format_commands(self, ctx: click.Context, formatter: RootCommandHelpTextForm text=SAM_CLI_COMMANDS.get("sync", ""), extra_row_modifiers=[HighlightNewRowNameModifier()], ), + RowDefinition( + name="remote", + text=SAM_CLI_COMMANDS.get("remote", ""), + extra_row_modifiers=[HighlightNewRowNameModifier()], + ), ], ) diff --git a/samcli/cli/root/command_list.py b/samcli/cli/root/command_list.py index 4350bb507c..0be843fbe2 100644 --- a/samcli/cli/root/command_list.py +++ b/samcli/cli/root/command_list.py @@ -6,6 +6,7 @@ "validate": "Validate an AWS SAM template.", "build": "Build your AWS serverless function code.", "local": "Run your AWS serverless function locally.", + "remote": "Invoke or send an event to cloud resources in your CFN stack", "package": "Package an AWS SAM application.", "deploy": "Deploy an AWS SAM application.", "delete": "Delete an AWS SAM application and the artifacts created by sam deploy.", diff --git a/samcli/commands/remote/remote_invoke_context.py b/samcli/commands/remote/remote_invoke_context.py index c1ca48193d..90242b5142 100644 --- a/samcli/commands/remote/remote_invoke_context.py +++ b/samcli/commands/remote/remote_invoke_context.py @@ -112,7 +112,7 @@ def _populate_resource_summary(self) -> None: see _get_from_physical_resource_id for details. """ if not self._stack_name and not self._resource_id: - raise InvalidRemoteInvokeParameters("Either --stack-name or --resource-id parameter should be provided") + raise InvalidRemoteInvokeParameters("Either --stack-name option or resource_id argument should be provided") try: if not self._resource_id: @@ -162,7 +162,7 @@ def _get_single_resource_from_stack(self) -> CloudFormationResourceSummary: if len(resource_summaries) > 1: raise AmbiguousResourceForRemoteInvoke( f"{self._stack_name} contains more than one resource that could be used with remote invoke, " - f"please provide --resource-id to resolve ambiguity." + f"please provide resource_id argument to resolve ambiguity." ) # fail if no resource summary found with given types diff --git a/samcli/lib/cli_validation/remote_invoke_options_validations.py b/samcli/lib/cli_validation/remote_invoke_options_validations.py index 58345cd807..da6e3e1f9e 100644 --- a/samcli/lib/cli_validation/remote_invoke_options_validations.py +++ b/samcli/lib/cli_validation/remote_invoke_options_validations.py @@ -56,7 +56,7 @@ def wrapped(*args, **kwargs): def stack_name_or_resource_id_atleast_one_option_validation(func): """ - This function validates that atleast one of --stack-name or --resource-id should is be provided + This function validates that atleast one of --stack-name option or resource_id argument should is be provided Parameters ---------- diff --git a/samcli/lib/docs/documentation_links.json b/samcli/lib/docs/documentation_links.json index 1db35b4b7a..eef861fae9 100644 --- a/samcli/lib/docs/documentation_links.json +++ b/samcli/lib/docs/documentation_links.json @@ -10,6 +10,7 @@ "list endpoints": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-list-endpoints.html", "list resources": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-list-resources.html", "deploy": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-deploy.html", + "remote invoke": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-remote-invoke.html", "package": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-package.html", "delete": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-delete.html", "sync": "https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-sync.html", diff --git a/samcli/lib/remote_invoke/lambda_invoke_executors.py b/samcli/lib/remote_invoke/lambda_invoke_executors.py index 4c47683008..323aeceba3 100644 --- a/samcli/lib/remote_invoke/lambda_invoke_executors.py +++ b/samcli/lib/remote_invoke/lambda_invoke_executors.py @@ -64,7 +64,7 @@ def validate_action_parameters(self, parameters: dict) -> None: """ for parameter_key, parameter_value in parameters.items(): if parameter_key == FUNCTION_NAME: - LOG.warning("FunctionName is defined using the value provided for --resource-id option.") + LOG.warning("FunctionName is defined using the value provided for resource_id argument.") elif parameter_key == PAYLOAD: LOG.warning("Payload is defined using the value provided for either --event or --event-file options.") else: diff --git a/tests/end_to_end/end_to_end_base.py b/tests/end_to_end/end_to_end_base.py index c837e0991a..926a325e86 100644 --- a/tests/end_to_end/end_to_end_base.py +++ b/tests/end_to_end/end_to_end_base.py @@ -8,6 +8,7 @@ from tests.integration.init.test_init_base import InitIntegBase from tests.integration.package.package_integ_base import PackageIntegBase from tests.integration.local.invoke.invoke_integ_base import InvokeIntegBase +from tests.integration.remote.invoke.remote_invoke_integ_base import RemoteInvokeIntegBase from tests.integration.sync.sync_integ_base import SyncIntegBase from tests.integration.list.stack_outputs.stack_outputs_integ_base import StackOutputsIntegBase import logging @@ -73,6 +74,11 @@ def _get_package_command(self, s3_prefix, use_json=False, output_template_file=N def _get_local_command(self, function_name): return InvokeIntegBase.get_command_list(function_to_invoke=function_name) + def _get_remote_invoke_command(self, stack_name, resource_id, event, output): + return RemoteInvokeIntegBase.get_command_list( + stack_name=stack_name, resource_id=resource_id, event=event, output=output + ) + def _get_delete_command(self, stack_name): return self.get_delete_command_list(stack_name=stack_name, region=self.region_name, no_prompts=True) diff --git a/tests/end_to_end/test_runtimes_e2e.py b/tests/end_to_end/test_runtimes_e2e.py index c8d4607d5b..ffe304b705 100644 --- a/tests/end_to_end/test_runtimes_e2e.py +++ b/tests/end_to_end/test_runtimes_e2e.py @@ -10,7 +10,6 @@ from tests.end_to_end.test_stages import ( DefaultInitStage, PackageDownloadZipFunctionStage, - DefaultRemoteInvokeStage, DefaultDeleteStage, EndToEndBaseStage, DefaultSyncStage, @@ -47,8 +46,10 @@ def validate(self, command_result: CommandResult): class RemoteInvokeValidator(BaseValidator): def validate(self, command_result: CommandResult): - self.assertEqual(command_result.process.get("StatusCode"), 200) - self.assertEqual(command_result.process.get("FunctionError", ""), "") + response = json.loads(command_result.stdout.decode("utf-8")) + self.assertEqual(command_result.process.returncode, 0) + self.assertEqual(response["StatusCode"], 200) + self.assertEqual(response.get("FunctionError", ""), "") class StackOutputsValidator(BaseValidator): @@ -75,18 +76,21 @@ class TestHelloWorldDefaultEndToEnd(EndToEndBase): def test_hello_world_default_workflow(self): stack_name = self._method_to_stack_name(self.id()) + function_name = "HelloWorldFunction" + event = '{"hello": "world"}' with EndToEndTestContext(self.app_name) as e2e_context: self.template_path = e2e_context.template_path init_command_list = self._get_init_command(e2e_context.working_directory) build_command_list = self.get_command_list() deploy_command_list = self._get_deploy_command(stack_name) stack_outputs_command_list = self._get_stack_outputs_command(stack_name) + remote_invoke_command_list = self._get_remote_invoke_command(stack_name, function_name, event, "json") delete_command_list = self._get_delete_command(stack_name) stages = [ DefaultInitStage(InitValidator(e2e_context), e2e_context, init_command_list, self.app_name), EndToEndBaseStage(BuildValidator(e2e_context), e2e_context, build_command_list), EndToEndBaseStage(BaseValidator(e2e_context), e2e_context, deploy_command_list), - DefaultRemoteInvokeStage(RemoteInvokeValidator(e2e_context), e2e_context, stack_name), + EndToEndBaseStage(RemoteInvokeValidator(e2e_context), e2e_context, remote_invoke_command_list), EndToEndBaseStage(BaseValidator(e2e_context), e2e_context, stack_outputs_command_list), DefaultDeleteStage(BaseValidator(e2e_context), e2e_context, delete_command_list, stack_name), ] @@ -117,7 +121,7 @@ def test_hello_world_workflow(self): package_command_list = self._get_package_command( s3_prefix="end-to-end-package-test", use_json=True, output_template_file="packaged_template.json" ) - local_command_list = self._get_local_command("HelloWorldFunction") + local_command_list = self._get_local_command(function_name) stages = [ DefaultInitStage(InitValidator(e2e_context), e2e_context, init_command_list, self.app_name), EndToEndBaseStage(BuildValidator(e2e_context), e2e_context, build_command_list), @@ -141,17 +145,20 @@ class TestHelloWorldDefaultSyncEndToEnd(EndToEndBase): app_template = "hello-world" def test_go_hello_world_default_workflow(self): + function_name = "HelloWorldFunction" + event = '{"hello": "world"}' stack_name = self._method_to_stack_name(self.id()) with EndToEndTestContext(self.app_name) as e2e_context: self.template_path = e2e_context.template_path init_command_list = self._get_init_command(e2e_context.working_directory) sync_command_list = self._get_sync_command(stack_name) stack_outputs_command_list = self._get_stack_outputs_command(stack_name) + remote_invoke_command_list = self._get_remote_invoke_command(stack_name, function_name, event, "json") delete_command_list = self._get_delete_command(stack_name) stages = [ DefaultInitStage(InitValidator(e2e_context), e2e_context, init_command_list, self.app_name), DefaultSyncStage(BaseValidator(e2e_context), e2e_context, sync_command_list), - DefaultRemoteInvokeStage(RemoteInvokeValidator(e2e_context), e2e_context, stack_name), + EndToEndBaseStage(RemoteInvokeValidator(e2e_context), e2e_context, remote_invoke_command_list), EndToEndBaseStage(BaseValidator(e2e_context), e2e_context, stack_outputs_command_list), DefaultDeleteStage(BaseValidator(e2e_context), e2e_context, delete_command_list, stack_name), ] diff --git a/tests/end_to_end/test_stages.py b/tests/end_to_end/test_stages.py index a6a5922a0b..8e60211924 100644 --- a/tests/end_to_end/test_stages.py +++ b/tests/end_to_end/test_stages.py @@ -64,21 +64,6 @@ def _delete_default_samconfig(self): pass -class DefaultRemoteInvokeStage(EndToEndBaseStage): - def __init__(self, validator, test_context, stack_name): - super().__init__(validator, test_context) - self.stack_name = stack_name - self.lambda_client = boto3.client("lambda") - self.resource = boto3.resource("cloudformation") - - def run_stage(self) -> CommandResult: - lambda_output = self.lambda_client.invoke(FunctionName=self._get_lambda_physical_id()) - return CommandResult(lambda_output, "", "") - - def _get_lambda_physical_id(self): - return self.resource.StackResource(self.stack_name, "HelloWorldFunction").physical_resource_id - - class DefaultDeleteStage(EndToEndBaseStage): def __init__(self, validator, test_context, command_list, stack_name): super().__init__(validator, test_context, command_list) diff --git a/tests/unit/cli/test_command.py b/tests/unit/cli/test_command.py index d64be4e444..7e2e0a0e50 100644 --- a/tests/unit/cli/test_command.py +++ b/tests/unit/cli/test_command.py @@ -176,6 +176,7 @@ def test_get_command_root_command_text(self): ("local", "local command output"), ("validate", "validate command output"), ("sync", "sync command output"), + ("remote", "remote command output"), ], "Deploy your App": [("package", "package command output"), ("deploy", "deploy command output")], "Monitor your App": [("logs", "logs command output"), ("traces", "traces command output")], @@ -194,6 +195,7 @@ def test_get_command_root_command_text(self): "local": "local command output", "validate": "validate command output", "sync": "sync command output", + "remote": "remote command output", "package": "package command output", "deploy": "deploy command output", "logs": "logs command output", diff --git a/tests/unit/commands/docs/core/test_command.py b/tests/unit/commands/docs/core/test_command.py index 3def7e0dd1..36b80514f7 100644 --- a/tests/unit/commands/docs/core/test_command.py +++ b/tests/unit/commands/docs/core/test_command.py @@ -27,7 +27,7 @@ def test_formatter(self): ), description[0][0], ) - self.assertEqual(len(commands), 19) + self.assertEqual(len(commands), 20) all_commands = set(DocsCommandContext().get_complete_command_paths()) formatter_commands = set([command[0] for command in commands]) self.assertEqual(all_commands, formatter_commands) diff --git a/tests/unit/commands/docs/test_docs_context.py b/tests/unit/commands/docs/test_docs_context.py index 5e4787bd9c..6ea694c1eb 100644 --- a/tests/unit/commands/docs/test_docs_context.py +++ b/tests/unit/commands/docs/test_docs_context.py @@ -24,6 +24,7 @@ "list endpoints", "list resources", "deploy", + "remote invoke", "package", "delete", "sync", From 48f6b714cbe52073f9b17fff9dc702f8dc3f3dc5 Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:20:30 -0400 Subject: [PATCH 063/107] test: Remote invoke integration tests for response stream configured lambda functions (#5383) * Created base integ glass for remote invoke tests * Add integration tests for invoking response streaming lambda fns * make black * Moved tearDownClass to base class * Moved tearDownClass method to base class and removed architectures from template file * Remove the check to skip appveyor tests on master branch --- .../test_lambda_invoke_response_stream.py | 103 ++++++++++++++++++ .../remote_invoke/lambda-fns/src/index.js | 26 +++++ ...emplate-lambda-response-streaming-fns.yaml | 28 +++++ 3 files changed, 157 insertions(+) create mode 100644 tests/integration/remote/invoke/test_lambda_invoke_response_stream.py create mode 100644 tests/integration/testdata/remote_invoke/lambda-fns/src/index.js create mode 100644 tests/integration/testdata/remote_invoke/template-lambda-response-streaming-fns.yaml diff --git a/tests/integration/remote/invoke/test_lambda_invoke_response_stream.py b/tests/integration/remote/invoke/test_lambda_invoke_response_stream.py new file mode 100644 index 0000000000..5adf7bdba5 --- /dev/null +++ b/tests/integration/remote/invoke/test_lambda_invoke_response_stream.py @@ -0,0 +1,103 @@ +import json +import uuid + +from tests.integration.remote.invoke.remote_invoke_integ_base import RemoteInvokeIntegBase +from tests.testing_utils import run_command + +from pathlib import Path +import pytest + + +@pytest.mark.xdist_group(name="sam_remote_invoke_lambda_response_streaming") +class TestInvokeResponseStreamingLambdas(RemoteInvokeIntegBase): + template = Path("template-lambda-response-streaming-fns.yaml") + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.stack_name = f"{TestInvokeResponseStreamingLambdas.__name__}-{uuid.uuid4().hex}" + cls.create_resources_and_boto_clients() + + def test_invoke_empty_event_provided(self): + command_list = self.get_command_list(stack_name=self.stack_name, resource_id="NodeStreamingFunction") + + expected_streamed_responses = "LambdaFunctionStreamingResponsesTestDone!" + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = remote_invoke_result.stdout.strip().decode() + self.assertIn(expected_streamed_responses, remote_invoke_result_stdout) + + def test_invoke_with_only_event_provided(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + resource_id="NodeStreamingFunction", + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + ) + + expected_streamed_responses = "LambdaFunctionStreamingResponsesTestDone!" + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = remote_invoke_result.stdout.strip().decode() + self.assertIn(expected_streamed_responses, remote_invoke_result_stdout) + + def test_invoke_with_only_event_file_provided(self): + event_file_path = str(self.events_folder_path.joinpath("default_event.json")) + command_list = self.get_command_list( + stack_name=self.stack_name, resource_id="NodeStreamingEventValuesFunction", event_file=event_file_path + ) + + expected_streamed_responses = "Helloserverlessworld" + remote_invoke_result = run_command(command_list) + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = remote_invoke_result.stdout.strip().decode() + + self.assertEqual(expected_streamed_responses, remote_invoke_result_stdout) + + def test_invoke_json_output_option(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + resource_id="NodeStreamingEventValuesFunction", + output="json", + parameter_list=[("LogType", "None")], + ) + + remote_invoke_result = run_command(command_list) + expected_output_result = [ + {"PayloadChunk": {"Payload": "Hello"}}, + {"PayloadChunk": {"Payload": "serverless"}}, + {"PayloadChunk": {"Payload": "world"}}, + {"InvokeComplete": {}}, + ] + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + + response_event_stream = remote_invoke_result_stdout["EventStream"] + self.assertEqual(response_event_stream, expected_output_result) + + def test_invoke_different_boto_options(self): + command_list = self.get_command_list( + stack_name=self.stack_name, + event='{"key1": "Hello", "key2": "serverless", "key3": "world"}', + resource_id="NodeStreamingEventValuesFunction", + output="json", + parameter_list=[("LogType", "None"), ("InvocationType", "DryRun"), ("Qualifier", "$LATEST")], + ) + + remote_invoke_result = run_command(command_list) + expected_output_result = [ + {"PayloadChunk": {"Payload": "Hello"}}, + {"PayloadChunk": {"Payload": "serverless"}}, + {"PayloadChunk": {"Payload": "world"}}, + {"InvokeComplete": {}}, + ] + + self.assertEqual(0, remote_invoke_result.process.returncode) + remote_invoke_result_stdout = json.loads(remote_invoke_result.stdout.strip().decode()) + + response_event_stream = remote_invoke_result_stdout["EventStream"] + self.assertEqual(response_event_stream, expected_output_result) diff --git a/tests/integration/testdata/remote_invoke/lambda-fns/src/index.js b/tests/integration/testdata/remote_invoke/lambda-fns/src/index.js new file mode 100644 index 0000000000..d4cdd511a6 --- /dev/null +++ b/tests/integration/testdata/remote_invoke/lambda-fns/src/index.js @@ -0,0 +1,26 @@ +exports.handler = awslambda.streamifyResponse( + async (event, responseStream, context) => { + responseStream.write("Lambda"); + responseStream.write("Function"); + + responseStream.write("Streaming"); + await new Promise(r => setTimeout(r, 1000)); + responseStream.write("Responses"); + await new Promise(r => setTimeout(r, 1000)); + responseStream.write("Test"); + await new Promise(r => setTimeout(r, 1000)); + + responseStream.write("Done!"); + responseStream.end(); + } +); + +exports.stream_event_values = awslambda.streamifyResponse( + async (event, responseStream, context) => { + for (let k in event) { + responseStream.write(event[k]); + await new Promise(r => setTimeout(r, 1000)); + } + responseStream.end(); + } +); \ No newline at end of file diff --git a/tests/integration/testdata/remote_invoke/template-lambda-response-streaming-fns.yaml b/tests/integration/testdata/remote_invoke/template-lambda-response-streaming-fns.yaml new file mode 100644 index 0000000000..1365154a7e --- /dev/null +++ b/tests/integration/testdata/remote_invoke/template-lambda-response-streaming-fns.yaml @@ -0,0 +1,28 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Description: > + Testing application for lambda functions with response streaming + +Resources: + NodeStreamingFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./lambda-fns/src/ + Handler: index.handler + Runtime: nodejs18.x + Timeout: 10 + FunctionUrlConfig: + AuthType: AWS_IAM + InvokeMode: RESPONSE_STREAM + + NodeStreamingEventValuesFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./lambda-fns/src/ + Handler: index.stream_event_values + Runtime: nodejs18.x + Timeout: 10 + FunctionUrlConfig: + AuthType: AWS_IAM + InvokeMode: RESPONSE_STREAM \ No newline at end of file From 32792c084bf126b4b258eba5da9daa4c07765c8f Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:56:22 -0400 Subject: [PATCH 064/107] chore: bump version to 1.88.0 (#5393) --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index dba460a2bc..705542b65e 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.87.0" +__version__ = "1.88.0" From 94adeeb7c55e5ddf2c0904074cc96ab362014c9a Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:08:26 -0700 Subject: [PATCH 065/107] chore: fix issues with appveyor ubuntu setup #5395 --- appveyor-ubuntu.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor-ubuntu.yml b/appveyor-ubuntu.yml index f5b0c8b384..c841bf5b90 100644 --- a/appveyor-ubuntu.yml +++ b/appveyor-ubuntu.yml @@ -43,7 +43,7 @@ environment: install: # AppVeyor's apt-get cache might be outdated, and the package could potentially be 404. - - sh: "sudo apt-get update" + - sh: "sudo apt-get update --allow-releaseinfo-change" - sh: "gvm use go1.19" - sh: "echo $PATH" @@ -87,7 +87,7 @@ install: - sh: "sudo apt install -y jq" # install Terraform - - sh: "sudo apt update" + - sh: "sudo apt update --allow-releaseinfo-change" - sh: "TER_VER=`curl -s https://api.github.com/repos/hashicorp/terraform/releases/latest | grep tag_name | cut -d: -f2 | tr -d \\\"\\,\\v | awk '{$1=$1};1'`" - sh: "wget https://releases.hashicorp.com/terraform/${TER_VER}/terraform_${TER_VER}_linux_amd64.zip -P /tmp" - sh: "sudo unzip -d /opt/terraform /tmp/terraform_${TER_VER}_linux_amd64.zip" From cfacdf592475f30d264e44e57f2ab527376d8b9b Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:10:47 -0700 Subject: [PATCH 066/107] chore: remove deprecated runtime dotnetcore3.1 (#5091) * chore: remove deprecated runtime dotnetcore3.1 * apply pr comments --- .gitignore | 6 --- designs/build_debug_artifacts.md | 2 +- designs/sam_build_cmd.md | 2 +- samcli/commands/build/command.py | 2 +- samcli/commands/init/init_templates.py | 8 ++-- samcli/lib/build/constants.py | 1 + samcli/lib/build/workflow_config.py | 1 - samcli/lib/init/local_manifest.json | 2 +- .../cookiecutter.json | 2 +- .../src/HelloWorld/HelloWorld.csproj | 4 -- .../HelloWorld/aws-lambda-tools-defaults.json | 3 -- .../template.yaml | 4 +- .../HelloWorld.Test/HelloWorld.Tests.csproj | 4 +- samcli/lib/utils/architecture.py | 1 - samcli/local/common/runtime_template.py | 8 ++-- samcli/local/docker/lambda_debug_settings.py | 4 -- samcli/local/docker/lambda_image.py | 3 +- tests/integration/buildcmd/test_build_cmd.py | 35 ++------------- .../buildcmd/Dotnetcore3.1/HelloWorld.csproj | 16 ------- .../buildcmd/Dotnetcore3.1/Program.cs | 44 ------------------- .../aws-lambda-tools-defaults.json | 19 -------- .../inprocess/dotnet/STS/STS.csproj | 2 +- .../credential_tests/inprocess/template.yaml | 2 +- tests/unit/commands/init/test_cli.py | 12 +++-- tests/unit/commands/init/test_manifest.json | 4 +- .../unit/lib/build_module/test_app_builder.py | 4 +- .../lib/build_module/test_build_strategy.py | 2 +- .../lib/build_module/test_workflow_config.py | 2 +- tests/unit/lib/utils/test_architecture.py | 2 +- .../local/docker/test_lambda_container.py | 3 +- .../docker/test_lambda_debug_settings.py | 1 - tests/unit/local/docker/test_lambda_image.py | 1 - 32 files changed, 37 insertions(+), 169 deletions(-) delete mode 100644 tests/integration/testdata/buildcmd/Dotnetcore3.1/HelloWorld.csproj delete mode 100644 tests/integration/testdata/buildcmd/Dotnetcore3.1/Program.cs delete mode 100644 tests/integration/testdata/buildcmd/Dotnetcore3.1/aws-lambda-tools-defaults.json diff --git a/.gitignore b/.gitignore index 05b0da72bd..6b3c26c6e5 100644 --- a/.gitignore +++ b/.gitignore @@ -412,12 +412,6 @@ coverage.xml # Temporary scratch directory used by the tests tests/integration/buildcmd/scratch -tests/integration/testdata/buildcmd/Dotnetcore2.0/bin -tests/integration/testdata/buildcmd/Dotnetcore2.0/obj -tests/integration/testdata/buildcmd/Dotnetcore2.1/bin -tests/integration/testdata/buildcmd/Dotnetcore2.1/obj -tests/integration/testdata/buildcmd/Dotnetcore3.1/bin -tests/integration/testdata/buildcmd/Dotnetcore3.1/obj tests/integration/testdata/buildcmd/Dotnet6/bin tests/integration/testdata/buildcmd/Dotnet6/obj tests/integration/testdata/buildcmd/Dotnet7/bin diff --git a/designs/build_debug_artifacts.md b/designs/build_debug_artifacts.md index 01ba532f9e..b839ba6be2 100644 --- a/designs/build_debug_artifacts.md +++ b/designs/build_debug_artifacts.md @@ -16,7 +16,7 @@ We will introduce a way in `sam build` to produce these debuggable artifacts for Success criteria for the change ------------------------------- -1. Artifacts generated will be debuggable for runtimes DotNetCore 2.0 and above. +1. Artifacts generated will be debuggable for runtimes DotNet 6.0 and above. Out-of-Scope ------------ diff --git a/designs/sam_build_cmd.md b/designs/sam_build_cmd.md index bb6c7cbacb..cb288ddf44 100644 --- a/designs/sam_build_cmd.md +++ b/designs/sam_build_cmd.md @@ -494,7 +494,7 @@ build actions. This section will look like: "build": { "actions": { "java8": "gradle build", - "dotnetcore2.1": "./build.sh" + "dotnet6": "./build.sh" } } } diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index b8ba89052a..92a2c6578c 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -54,7 +54,7 @@ 2. Nodejs 18.x, 16.x, 14.x, 12.x using NPM\n 3. Ruby 2.7, 3.2 using Bundler\n 4. Java 8, Java 11, Java 17 using Gradle and Maven\n - 5. Dotnetcore 3.1, Dotnet6 using Dotnet CLI (without --use-container)\n + 5. Dotnet6 using Dotnet CLI (without --use-container)\n 6. Go 1.x using Go Modules (without --use-container)\n """ diff --git a/samcli/commands/init/init_templates.py b/samcli/commands/init/init_templates.py index 9452faaa4d..c11998c543 100644 --- a/samcli/commands/init/init_templates.py +++ b/samcli/commands/init/init_templates.py @@ -211,17 +211,17 @@ def get_preprocessed_manifest( """ This method get the manifest cloned from the git repo and preprocessed it. Below is the link to manifest: - https://github.com/aws/aws-sam-cli-app-templates/blob/master/manifest.json + https://github.com/aws/aws-sam-cli-app-templates/blob/master/manifest-v2.json The structure of the manifest is shown below: { - "dotnetcore3.1": [ + "dotnet6": [ { - "directory": "dotnetcore3.1/cookiecutter-aws-sam-hello-dotnet", + "directory": "dotnet6/hello", "displayName": "Hello World Example", "dependencyManager": "cli-package", "appTemplate": "hello-world", "packageType": "Zip", - "useCaseName": "Serverless API" + "useCaseName": "Hello World Example" }, ] } diff --git a/samcli/lib/build/constants.py b/samcli/lib/build/constants.py index 77869c27cf..c7c4a3b94d 100644 --- a/samcli/lib/build/constants.py +++ b/samcli/lib/build/constants.py @@ -10,6 +10,7 @@ "nodejs10.x", "dotnetcore2.0", "dotnetcore2.1", + "dotnetcore3.1", "python2.7", "python3.6", "ruby2.5", diff --git a/samcli/lib/build/workflow_config.py b/samcli/lib/build/workflow_config.py index 39ea1acad7..b9ac751aed 100644 --- a/samcli/lib/build/workflow_config.py +++ b/samcli/lib/build/workflow_config.py @@ -161,7 +161,6 @@ def get_workflow_config( "nodejs18.x": BasicWorkflowSelector(NODEJS_NPM_CONFIG), "ruby2.7": BasicWorkflowSelector(RUBY_BUNDLER_CONFIG), "ruby3.2": BasicWorkflowSelector(RUBY_BUNDLER_CONFIG), - "dotnetcore3.1": BasicWorkflowSelector(DOTNET_CLIPACKAGE_CONFIG), "dotnet6": BasicWorkflowSelector(DOTNET_CLIPACKAGE_CONFIG), "go1.x": BasicWorkflowSelector(GO_MOD_CONFIG), # When Maven builder exists, add to this list so we can automatically choose a builder based on the supported diff --git a/samcli/lib/init/local_manifest.json b/samcli/lib/init/local_manifest.json index 38b95dc3be..462d8d4a8f 100644 --- a/samcli/lib/init/local_manifest.json +++ b/samcli/lib/init/local_manifest.json @@ -1,5 +1,5 @@ { - "dotnetcore3.1": [ + "dotnet6": [ { "directory": "template/cookiecutter-aws-sam-hello-dotnet", "displayName": "Hello World Example", diff --git a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/cookiecutter.json b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/cookiecutter.json index 9bf09d0306..19b21add11 100644 --- a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/cookiecutter.json +++ b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/cookiecutter.json @@ -1,6 +1,6 @@ { "project_name": "Name of the project", - "runtime": "dotnetcore3.1", + "runtime": "dotnet6", "architectures": { "value": [ "x86_64" diff --git a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/HelloWorld.csproj b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/HelloWorld.csproj index 0749a6fd85..8926242ef3 100644 --- a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/HelloWorld.csproj +++ b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/HelloWorld.csproj @@ -1,11 +1,7 @@ - {%- if cookiecutter.runtime == 'dotnetcore3.1' %} - netcoreapp3.1 - {%- else %} net6.0 - {%- endif %} true diff --git a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/aws-lambda-tools-defaults.json b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/aws-lambda-tools-defaults.json index 34ae747e11..fd910bc628 100644 --- a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/aws-lambda-tools-defaults.json +++ b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/src/HelloWorld/aws-lambda-tools-defaults.json @@ -11,9 +11,6 @@ "profile":"", "region" : "", "configuration": "Release", - {%- if cookiecutter.runtime == 'dotnetcore3.1' %} - "framework" : "netcoreapp3.1", - {%- endif %} "function-runtime":"{{ cookiecutter.runtime }}", "function-memory-size" : 256, "function-timeout" : 30, diff --git a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/template.yaml b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/template.yaml index 4bcfaa9d43..3450d2620e 100644 --- a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/template.yaml +++ b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/template.yaml @@ -14,8 +14,8 @@ Resources: Properties: CodeUri: ./src/HelloWorld/ Handler: HelloWorld::HelloWorld.Function::FunctionHandler - {%- if cookiecutter.runtime == 'dotnetcore3.1' %} - Runtime: dotnetcore3.1 + {%- if cookiecutter.runtime == 'dotnet6' %} + Runtime: dotnet6 {%- endif %} Architectures: {%- for arch in cookiecutter.architectures.value %} diff --git a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/test/HelloWorld.Test/HelloWorld.Tests.csproj b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/test/HelloWorld.Test/HelloWorld.Tests.csproj index 1c2b1f32e2..998671f9e5 100644 --- a/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/samcli/lib/init/templates/cookiecutter-aws-sam-hello-dotnet/{{cookiecutter.project_name}}/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -1,9 +1,7 @@ - {%- if cookiecutter.runtime == 'dotnetcore3.1' %} - netcoreapp3.1 - {%- else %} + {%- if cookiecutter.runtime == 'dotnet6' %} net6.0 {%- endif %} diff --git a/samcli/lib/utils/architecture.py b/samcli/lib/utils/architecture.py index 470754b2c1..a8025d8b3f 100644 --- a/samcli/lib/utils/architecture.py +++ b/samcli/lib/utils/architecture.py @@ -29,7 +29,6 @@ "java11": [ARM64, X86_64], "java17": [ARM64, X86_64], "go1.x": [X86_64], - "dotnetcore3.1": [ARM64, X86_64], "dotnet6": [ARM64, X86_64], "provided": [X86_64], "provided.al2": [ARM64, X86_64], diff --git a/samcli/local/common/runtime_template.py b/samcli/local/common/runtime_template.py index 85eb282a90..697631adea 100644 --- a/samcli/local/common/runtime_template.py +++ b/samcli/local/common/runtime_template.py @@ -40,7 +40,7 @@ ], "dotnet": [ { - "runtimes": ["dotnet6", "dotnetcore3.1"], + "runtimes": ["dotnet6"], "dependency_manager": "cli-package", "init_location": os.path.join(_templates, "cookiecutter-aws-sam-hello-dotnet"), "build": True, @@ -96,9 +96,8 @@ def get_local_lambda_images_location(mapping, runtime): # When adding new Lambda runtimes, please update SAM_RUNTIME_TO_SCHEMAS_CODE_LANG_MAPPING # Runtimes are ordered in alphabetical fashion with reverse version order (latest versions first) INIT_RUNTIMES = [ - # dotnetcore runtimes in descending order + # dotnet runtimes in descending order "dotnet6", - "dotnetcore3.1", "go1.x", # java runtimes in descending order "java17", @@ -126,7 +125,6 @@ def get_local_lambda_images_location(mapping, runtime): LAMBDA_IMAGES_RUNTIMES_MAP = { "dotnet6": "amazon/dotnet6-base", - "dotnetcore3.1": "amazon/dotnetcore3.1-base", "go1.x": "amazon/go1.x-base", "go (provided.al2)": "amazon/go-provided.al2-base", "java17": "amazon/java17-base", @@ -158,7 +156,7 @@ def get_local_lambda_images_location(mapping, runtime): "python3.8": "Python36", "python3.9": "Python36", "python3.10": "Python36", - "dotnet6": "dotnetcore3.1", + "dotnet6": "dotnet6", "go1.x": "Go1", } diff --git a/samcli/local/docker/lambda_debug_settings.py b/samcli/local/docker/lambda_debug_settings.py index 856717be99..8bffc6a3e2 100644 --- a/samcli/local/docker/lambda_debug_settings.py +++ b/samcli/local/docker/lambda_debug_settings.py @@ -93,10 +93,6 @@ def get_debug_settings(debug_port, debug_args_list, _container_env_vars, runtime **_container_env_vars, }, ), - Runtime.dotnetcore31.value: lambda: DebugSettings( - entry + ["/var/runtime/bootstrap"] + debug_args_list, - container_env_vars={"_AWS_LAMBDA_DOTNET_DEBUGGING": "1", **_container_env_vars}, - ), Runtime.dotnet6.value: lambda: DebugSettings( entry + ["/var/runtime/bootstrap"] + debug_args_list, container_env_vars={"_AWS_LAMBDA_DOTNET_DEBUGGING": "1", **_container_env_vars}, diff --git a/samcli/local/docker/lambda_image.py b/samcli/local/docker/lambda_image.py index 285e1b81e7..23f0a770d9 100644 --- a/samcli/local/docker/lambda_image.py +++ b/samcli/local/docker/lambda_image.py @@ -46,7 +46,6 @@ class Runtime(Enum): java11 = "java11" java17 = "java17" go1x = "go1.x" - dotnetcore31 = "dotnetcore3.1" dotnet6 = "dotnet6" provided = "provided" providedal2 = "provided.al2" @@ -86,7 +85,7 @@ def get_image_name_tag(cls, runtime: str, architecture: str) -> str: # `provided.al2` becomes `provided:al2`` runtime_image_tag = runtime.replace(".", ":") elif runtime.startswith("dotnet"): - # dotnetcore3.1 becomes dotnet:core3.1 and dotnet6 becomes dotnet:6 + # dotnet6 becomes dotnet:6 runtime_image_tag = runtime.replace("dotnet", "dotnet:") else: # This fits most runtimes format: `nameN.M` becomes `name:N.M` (python3.9 -> python:3.9) diff --git a/tests/integration/buildcmd/test_build_cmd.py b/tests/integration/buildcmd/test_build_cmd.py index 1a22c9cbb7..2b35d7c6e9 100644 --- a/tests/integration/buildcmd/test_build_cmd.py +++ b/tests/integration/buildcmd/test_build_cmd.py @@ -1223,15 +1223,13 @@ class TestBuildCommand_Dotnet_cli_package(BuildIntegBase): @parameterized.expand( [ - ("dotnetcore3.1", "Dotnetcore3.1", None), ("dotnet6", "Dotnet6", None), - ("dotnetcore3.1", "Dotnetcore3.1", "debug"), ("dotnet6", "Dotnet6", "debug"), ("provided.al2", "Dotnet7", None), ] ) @pytest.mark.flaky(reruns=3) - def test_dotnetcore_in_process(self, runtime, code_uri, mode, architecture="x86_64"): + def test_dotnet_in_process(self, runtime, code_uri, mode, architecture="x86_64"): # dotnet7 requires docker to build the function if code_uri == "Dotnet7" and (SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD): self.skipTest(SKIP_DOCKER_MESSAGE) @@ -1294,9 +1292,7 @@ def test_dotnetcore_in_process(self, runtime, code_uri, mode, architecture="x86_ @parameterized.expand( [ - ("dotnetcore3.1", "Dotnetcore3.1", None), ("dotnet6", "Dotnet6", None), - ("dotnetcore3.1", "Dotnetcore3.1", "debug"), ("dotnet6", "Dotnet6", "debug"), # force to run tests on arm64 machines may cause dotnet7 test failing # because Native AOT Lambda functions require the host and lambda architectures to match @@ -1305,7 +1301,7 @@ def test_dotnetcore_in_process(self, runtime, code_uri, mode, architecture="x86_ ) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) @pytest.mark.flaky(reruns=3) - def test_dotnetcore_in_container_mount_with_write_explicit(self, runtime, code_uri, mode, architecture="x86_64"): + def test_dotnet_in_container_mount_with_write_explicit(self, runtime, code_uri, mode, architecture="x86_64"): overrides = { "Runtime": runtime, "CodeUri": code_uri, @@ -1368,9 +1364,7 @@ def test_dotnetcore_in_container_mount_with_write_explicit(self, runtime, code_u @parameterized.expand( [ - ("dotnetcore3.1", "Dotnetcore3.1", None), ("dotnet6", "Dotnet6", None), - ("dotnetcore3.1", "Dotnetcore3.1", "debug"), ("dotnet6", "Dotnet6", "debug"), # force to run tests on arm64 machines may cause dotnet7 test failing # because Native AOT Lambda functions require the host and lambda architectures to match @@ -1379,7 +1373,7 @@ def test_dotnetcore_in_container_mount_with_write_explicit(self, runtime, code_u ) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) @pytest.mark.flaky(reruns=3) - def test_dotnetcore_in_container_mount_with_write_interactive( + def test_dotnet_in_container_mount_with_write_interactive( self, runtime, code_uri, @@ -1444,7 +1438,7 @@ def test_dotnetcore_in_container_mount_with_write_interactive( ) self.verify_docker_container_cleanedup(runtime) - @parameterized.expand([("dotnetcore3.1", "Dotnetcore3.1"), ("dotnet6", "Dotnet6")]) + @parameterized.expand([("dotnet6", "Dotnet6")]) @skipIf(SKIP_DOCKER_TESTS or SKIP_DOCKER_BUILD, SKIP_DOCKER_MESSAGE) @pytest.mark.flaky(reruns=3) def test_must_fail_on_container_mount_without_write_interactive(self, runtime, code_uri): @@ -2045,13 +2039,6 @@ class TestBuildWithDedupBuilds(DedupBuildIntegBase): @parameterized.expand( [ # in process - ( - False, - "Dotnetcore3.1", - "HelloWorld::HelloWorld.FirstFunction::FunctionHandler", - "HelloWorld::HelloWorld.SecondFunction::FunctionHandler", - "dotnetcore3.1", - ), ( False, "Dotnet6", @@ -2177,13 +2164,6 @@ class TestBuildWithCacheBuilds(CachedBuildIntegBase): @parameterized.expand( [ # in process - ( - False, - "Dotnetcore3.1", - "HelloWorld::HelloWorld.FirstFunction::FunctionHandler", - "HelloWorld::HelloWorld.SecondFunction::FunctionHandler", - "dotnetcore3.1", - ), ( False, "Dotnet6", @@ -2366,13 +2346,6 @@ class TestParallelBuilds(DedupBuildIntegBase): @parameterized.expand( [ # in process - ( - False, - "Dotnetcore3.1", - "HelloWorld::HelloWorld.FirstFunction::FunctionHandler", - "HelloWorld::HelloWorld.SecondFunction::FunctionHandler", - "dotnetcore3.1", - ), ( False, "Dotnet6", diff --git a/tests/integration/testdata/buildcmd/Dotnetcore3.1/HelloWorld.csproj b/tests/integration/testdata/buildcmd/Dotnetcore3.1/HelloWorld.csproj deleted file mode 100644 index b795d281f0..0000000000 --- a/tests/integration/testdata/buildcmd/Dotnetcore3.1/HelloWorld.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - netcoreapp3.1 - true - - - - - - - - - - - diff --git a/tests/integration/testdata/buildcmd/Dotnetcore3.1/Program.cs b/tests/integration/testdata/buildcmd/Dotnetcore3.1/Program.cs deleted file mode 100644 index b7dc35f8f6..0000000000 --- a/tests/integration/testdata/buildcmd/Dotnetcore3.1/Program.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Net.Http; -using System.Net.Http.Headers; -using Newtonsoft.Json; - -using Amazon.Lambda.Core; -using Amazon.Lambda.APIGatewayEvents; - -// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. -[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] - -namespace HelloWorld -{ - - public class Function - { - - public string FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) - { - return "{'message': 'Hello World'}"; - } - } - - public class FirstFunction - { - - public string FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) - { - return "Hello World"; - } - } - - public class SecondFunction - { - - public string FunctionHandler(APIGatewayProxyRequest apigProxyEvent, ILambdaContext context) - { - return "Hello Mars"; - } - } -} diff --git a/tests/integration/testdata/buildcmd/Dotnetcore3.1/aws-lambda-tools-defaults.json b/tests/integration/testdata/buildcmd/Dotnetcore3.1/aws-lambda-tools-defaults.json deleted file mode 100644 index 89bbd11cf2..0000000000 --- a/tests/integration/testdata/buildcmd/Dotnetcore3.1/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "Information" : [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - - "dotnet lambda help", - - "All the command line options for the Lambda command can be specified in this file." - ], - - "profile":"", - "region" : "", - "configuration": "Release", - "framework": "netcoreapp3.1", - "function-runtime":"dotnetcore3.1", - "function-memory-size" : 256, - "function-timeout" : 30, - "function-handler" : "HelloWorld::HelloWorld.Function::FunctionHandler" -} diff --git a/tests/integration/testdata/invoke/credential_tests/inprocess/dotnet/STS/STS.csproj b/tests/integration/testdata/invoke/credential_tests/inprocess/dotnet/STS/STS.csproj index 95f1ecee69..148daf168b 100644 --- a/tests/integration/testdata/invoke/credential_tests/inprocess/dotnet/STS/STS.csproj +++ b/tests/integration/testdata/invoke/credential_tests/inprocess/dotnet/STS/STS.csproj @@ -1,7 +1,7 @@ - netcoreapp2.1 + net6.0 true diff --git a/tests/integration/testdata/invoke/credential_tests/inprocess/template.yaml b/tests/integration/testdata/invoke/credential_tests/inprocess/template.yaml index d5b0c32de6..41147cf58c 100644 --- a/tests/integration/testdata/invoke/credential_tests/inprocess/template.yaml +++ b/tests/integration/testdata/invoke/credential_tests/inprocess/template.yaml @@ -13,7 +13,7 @@ Resources: Properties: CodeUri: dotnet/STS Handler: STS::STS.Function::FunctionHandler - Runtime: dotnetcore3.1 + Runtime: dotnet6 GoStsExample: Type: AWS::Serverless::Function Properties: diff --git a/tests/unit/commands/init/test_cli.py b/tests/unit/commands/init/test_cli.py index 65ff97a31b..15c892a114 100644 --- a/tests/unit/commands/init/test_cli.py +++ b/tests/unit/commands/init/test_cli.py @@ -2119,7 +2119,6 @@ def test_init_cli_must_not_generate_default_hello_world_app( def test_must_return_runtime_from_base_image_name(self): base_images = [ "amazon/dotnet6-base", - "amazon/dotnetcore3.1-base", "amazon/go1.x-base", "amazon/java11-base", "amazon/nodejs14.x-base", @@ -2131,7 +2130,6 @@ def test_must_return_runtime_from_base_image_name(self): expected_runtime = [ "dotnet6", - "dotnetcore3.1", "go1.x", "java11", "nodejs14.x", @@ -2153,10 +2151,10 @@ def test_must_process_manifest(self, _get_manifest_mock): preprocess_manifest = template.get_preprocessed_manifest() expected_result = { "Hello World Example": { - "dotnetcore3.1": { + "dotnet6": { "Zip": [ { - "directory": "dotnetcore3.1/cookiecutter-aws-sam-hello-dotnet", + "directory": "dotnet6/cookiecutter-aws-sam-hello-dotnet", "displayName": "Hello World Example", "dependencyManager": "cli-package", "appTemplate": "hello-world", @@ -2788,9 +2786,9 @@ def test_init_cli_generate_app_template_from_local_cli_templates( @patch("samcli.local.common.runtime_template.INIT_RUNTIMES") def test_must_remove_unsupported_runtime(self, init_runtime_mock): - runtime_option_list = ["python3.7", "ruby3.2", "ruby2.7", "java11", "unsupported_runtime", "dotnetcore3.1"] - init_runtime_mock.return_value = ["dotnetcore3.1", "go1.x", "java11", "python3.7", "ruby3.2", "ruby2.7"] - expect_result = ["dotnetcore3.1", "java11", "python3.7", "ruby3.2", "ruby2.7"] + runtime_option_list = ["python3.7", "ruby3.2", "ruby2.7", "java11", "unsupported_runtime"] + init_runtime_mock.return_value = ["go1.x", "java11", "python3.7", "ruby3.2", "ruby2.7"] + expect_result = ["java11", "python3.7", "ruby3.2", "ruby2.7"] actual_result = get_sorted_runtimes(runtime_option_list) self.assertEqual(actual_result, expect_result) diff --git a/tests/unit/commands/init/test_manifest.json b/tests/unit/commands/init/test_manifest.json index 0ae5edf30f..7ae51a146b 100644 --- a/tests/unit/commands/init/test_manifest.json +++ b/tests/unit/commands/init/test_manifest.json @@ -1,7 +1,7 @@ { - "dotnetcore3.1": [ + "dotnet6": [ { - "directory": "dotnetcore3.1/cookiecutter-aws-sam-hello-dotnet", + "directory": "dotnet6/cookiecutter-aws-sam-hello-dotnet", "displayName": "Hello World Example", "dependencyManager": "cli-package", "appTemplate": "hello-world", diff --git a/tests/unit/lib/build_module/test_app_builder.py b/tests/unit/lib/build_module/test_app_builder.py index 98b8006ad0..408ba8bb35 100644 --- a/tests/unit/lib/build_module/test_app_builder.py +++ b/tests/unit/lib/build_module/test_app_builder.py @@ -460,7 +460,9 @@ def test_must_raise_for_functions_with_multi_architecture(self, persist_mock, re msg = "Function name property Architectures should be a list of length 1" self.assertEqual(str(ex.exception), msg) - @parameterized.expand([("python2.7",), ("python3.6",), ("ruby2.5",), ("nodejs10.x",), ("dotnetcore2.1",)]) + @parameterized.expand( + [("python2.7",), ("python3.6",), ("ruby2.5",), ("nodejs10.x",), ("dotnetcore2.1",), ("dotnetcore3.1",)] + ) def test_deprecated_runtimes(self, runtime): with self.assertRaises(UnsupportedRuntimeException): self.builder._build_function( diff --git a/tests/unit/lib/build_module/test_build_strategy.py b/tests/unit/lib/build_module/test_build_strategy.py index 3e3bc35754..5b2f3f7d77 100644 --- a/tests/unit/lib/build_module/test_build_strategy.py +++ b/tests/unit/lib/build_module/test_build_strategy.py @@ -749,7 +749,7 @@ def test_will_call_incremental_build_strategy(self, mocked_read, mocked_write, r @parameterized.expand( [ - "dotnetcore3.1", + "dotnet6", "go1.x", "java11", ] diff --git a/tests/unit/lib/build_module/test_workflow_config.py b/tests/unit/lib/build_module/test_workflow_config.py index c8a1fad900..9a1beaccfd 100644 --- a/tests/unit/lib/build_module/test_workflow_config.py +++ b/tests/unit/lib/build_module/test_workflow_config.py @@ -64,7 +64,7 @@ def test_must_work_for_provided_with_build_method_dotnet7(self, runtime): self.assertIn(Event("BuildWorkflowUsed", "dotnet-cli-package"), EventTracker.get_tracked_events()) self.assertTrue(result.must_mount_with_write_in_container) - @parameterized.expand([("dotnetcore3.1",), ("dotnet6",), ("provided.al2", "dotnet7")]) + @parameterized.expand([("dotnet6",), ("provided.al2", "dotnet7")]) def test_must_mount_with_write_for_dotnet_in_container(self, runtime, specified_workflow=None): result = get_workflow_config(runtime, self.code_dir, self.project_dir, specified_workflow) self.assertTrue(result.must_mount_with_write_in_container) diff --git a/tests/unit/lib/utils/test_architecture.py b/tests/unit/lib/utils/test_architecture.py index 1c95a96aa5..860c513a07 100644 --- a/tests/unit/lib/utils/test_architecture.py +++ b/tests/unit/lib/utils/test_architecture.py @@ -44,7 +44,7 @@ def test_validate_architecture_errors(self, value): [ ("nodejs14.x", X86_64, ZIP), ("java8.al2", ARM64, ZIP), - ("dotnetcore3.1", ARM64, ZIP), + ("dotnet6", ARM64, ZIP), (None, X86_64, IMAGE), (None, ARM64, IMAGE), (None, X86_64, IMAGE), diff --git a/tests/unit/local/docker/test_lambda_container.py b/tests/unit/local/docker/test_lambda_container.py index 1ccff3c2f0..90893e4e49 100644 --- a/tests/unit/local/docker/test_lambda_container.py +++ b/tests/unit/local/docker/test_lambda_container.py @@ -12,7 +12,7 @@ from samcli.local.docker.lambda_debug_settings import DebuggingNotSupported from samcli.local.docker.lambda_image import RAPID_IMAGE_TAG_PREFIX -RUNTIMES_WITH_ENTRYPOINT = [Runtime.dotnetcore31.value, Runtime.dotnet6.value, Runtime.go1x.value] +RUNTIMES_WITH_ENTRYPOINT = [Runtime.dotnet6.value, Runtime.go1x.value] RUNTIMES_WITH_BOOTSTRAP_ENTRYPOINT = [ Runtime.nodejs12x.value, @@ -31,7 +31,6 @@ Runtime.java11.value, Runtime.java8.value, Runtime.java8al2.value, - Runtime.dotnetcore31.value, Runtime.dotnet6.value, Runtime.go1x.value, ] diff --git a/tests/unit/local/docker/test_lambda_debug_settings.py b/tests/unit/local/docker/test_lambda_debug_settings.py index 407cd05bba..1dd024ebc2 100644 --- a/tests/unit/local/docker/test_lambda_debug_settings.py +++ b/tests/unit/local/docker/test_lambda_debug_settings.py @@ -10,7 +10,6 @@ Runtime.java8al2, Runtime.java11, Runtime.java17, - Runtime.dotnetcore31, Runtime.dotnet6, Runtime.go1x, Runtime.nodejs12x, diff --git a/tests/unit/local/docker/test_lambda_image.py b/tests/unit/local/docker/test_lambda_image.py index 3ad8a10c54..1e8f936d98 100644 --- a/tests/unit/local/docker/test_lambda_image.py +++ b/tests/unit/local/docker/test_lambda_image.py @@ -34,7 +34,6 @@ class TestRuntime(TestCase): ("java17", "java:17-x86_64"), ("go1.x", "go:1"), ("dotnet6", "dotnet:6-x86_64"), - ("dotnetcore3.1", "dotnet:core3.1-x86_64"), ("provided", "provided:alami"), ("provided.al2", "provided:al2-x86_64"), ] From 97104eac05c47aec1c7db62cb98cd050c7656d3d Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:16:14 -0500 Subject: [PATCH 067/107] fix(invoke): Write in UTF-8 string instead of bytes. (#5232) * fix(invoke): Write in UTF-8 string instead of bytes. It appears that we were using sys.stdout.buffer to support python2 and python3 at the same time. Switching to just write to sys.stdout allows us to write a utf-8 encoding string. When using sys.stdout.buffer, we can only write bytes and I couldn't get the correct UTF8 encoded string to print correctly. * Fix ruff errors * Update log_streamer.py to remove encoding * More updates to make everything work better in general * Fix with ruff again * Explictingly write to stream for building images * More patching writes * More patching * Fix long line * Use mock over io.string * More fixing of tests * Assert mock instead of data directly * More small edits in test * Verify through calls instead of value * run make black * Fix when we flush to match pervious behavior and output * add integration tests * run make black --------- Co-authored-by: Jacob Fuss Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> --- samcli/lib/utils/osutils.py | 4 ++-- samcli/lib/utils/stream_writer.py | 7 ++++-- samcli/local/docker/container.py | 5 +++- samcli/local/docker/lambda_image.py | 10 ++++---- samcli/local/docker/manager.py | 8 ++++--- .../local/invoke/test_integrations_cli.py | 21 ++++++++++++++++ tests/unit/lib/utils/test_osutils.py | 8 ------- tests/unit/lib/utils/test_stream_writer.py | 2 +- tests/unit/local/docker/test_lambda_image.py | 9 ++++--- tests/unit/local/docker/test_manager.py | 24 +++++++++++++------ 10 files changed, 64 insertions(+), 34 deletions(-) diff --git a/samcli/lib/utils/osutils.py b/samcli/lib/utils/osutils.py index d53dc9ffb5..dfdd1ed256 100644 --- a/samcli/lib/utils/osutils.py +++ b/samcli/lib/utils/osutils.py @@ -87,7 +87,7 @@ def stdout(): io.BytesIO Byte stream of Stdout """ - return sys.stdout.buffer + return sys.stdout def stderr(): @@ -99,7 +99,7 @@ def stderr(): io.BytesIO Byte stream of stderr """ - return sys.stderr.buffer + return sys.stderr def remove(path): diff --git a/samcli/lib/utils/stream_writer.py b/samcli/lib/utils/stream_writer.py index 1fc62fa690..606efb606c 100644 --- a/samcli/lib/utils/stream_writer.py +++ b/samcli/lib/utils/stream_writer.py @@ -22,7 +22,7 @@ def __init__(self, stream, auto_flush=False): def stream(self): return self._stream - def write(self, output, encode=False): + def write(self, output, encode=False, write_to_buffer=True): """ Writes specified text to the underlying stream @@ -31,7 +31,10 @@ def write(self, output, encode=False): output bytes-like object Bytes to write """ - self._stream.write(output.encode() if encode else output) + if write_to_buffer: + self._stream.buffer.write(output.encode() if encode else output) + else: + self._stream.write(output) if self._auto_flush: self._stream.flush() diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py index e70f7c2a1f..a5aebc1bd3 100644 --- a/samcli/local/docker/container.py +++ b/samcli/local/docker/container.py @@ -1,6 +1,7 @@ """ Representation of a generic Docker container """ +import json import logging import os import pathlib @@ -324,7 +325,8 @@ def wait_for_http_response(self, name, event, stdout): data=event.encode("utf-8"), timeout=(self.RAPID_CONNECTION_TIMEOUT, None), ) - stdout.write(resp.content) + stdout.write(json.dumps(json.loads(resp.content), ensure_ascii=False), write_to_buffer=False) + stdout.flush() def wait_for_result(self, full_path, event, stdout, stderr, start_timer=None): # NOTE(sriram-mv): Let logging happen in its own thread, so that a http request can be sent. @@ -434,6 +436,7 @@ def _write_container_output(output_itr, stdout=None, stderr=None): if stderr_data and stderr: stderr.write(stderr_data) + except Exception as ex: LOG.debug("Failed to get the logs from the container", exc_info=ex) diff --git a/samcli/local/docker/lambda_image.py b/samcli/local/docker/lambda_image.py index 23f0a770d9..ab9a20a8a8 100644 --- a/samcli/local/docker/lambda_image.py +++ b/samcli/local/docker/lambda_image.py @@ -226,7 +226,7 @@ def build(self, runtime, packagetype, image, layers, architecture, stream=None, or not runtime ): stream_writer = stream or StreamWriter(sys.stderr) - stream_writer.write("Building image...") + stream_writer.write("Building image...", write_to_buffer=False) stream_writer.flush() self._build_image( image if image else base_image, rapid_image, downloaded_layers, architecture, stream=stream_writer @@ -337,15 +337,15 @@ def set_item_permission(tar_info): platform=get_docker_platform(architecture), ) for log in resp_stream: - stream_writer.write(".") + stream_writer.write(".", write_to_buffer=False) stream_writer.flush() if "error" in log: - stream_writer.write("\n") + stream_writer.write("\n", write_to_buffer=False) LOG.exception("Failed to build Docker Image") raise ImageBuildException("Error building docker image: {}".format(log["error"])) - stream_writer.write("\n") + stream_writer.write("\n", write_to_buffer=False) except (docker.errors.BuildError, docker.errors.APIError) as ex: - stream_writer.write("\n") + stream_writer.write("\n", write_to_buffer=False) LOG.exception("Failed to build Docker Image") raise ImageBuildException("Building Image failed.") from ex finally: diff --git a/samcli/local/docker/manager.py b/samcli/local/docker/manager.py index a035003bb0..0cd8dc8dc8 100644 --- a/samcli/local/docker/manager.py +++ b/samcli/local/docker/manager.py @@ -168,16 +168,18 @@ def pull_image(self, image_name, tag=None, stream=None): raise DockerImagePullFailedException(str(ex)) from ex # io streams, especially StringIO, work only with unicode strings - stream_writer.write("\nFetching {}:{} Docker container image...".format(image_name, tag)) + stream_writer.write( + "\nFetching {}:{} Docker container image...".format(image_name, tag), write_to_buffer=False + ) # Each line contains information on progress of the pull. Each line is a JSON string for _ in result_itr: # For every line, print a dot to show progress - stream_writer.write(".") + stream_writer.write(".", write_to_buffer=False) stream_writer.flush() # We are done. Go to the next line - stream_writer.write("\n") + stream_writer.write("\n", write_to_buffer=False) def has_image(self, image_name): """ diff --git a/tests/integration/local/invoke/test_integrations_cli.py b/tests/integration/local/invoke/test_integrations_cli.py index 3604fc4010..bf3f1d1e2e 100644 --- a/tests/integration/local/invoke/test_integrations_cli.py +++ b/tests/integration/local/invoke/test_integrations_cli.py @@ -291,6 +291,27 @@ def test_invoke_returns_expected_result_when_no_event_given(self): self.assertEqual(process.returncode, 0) self.assertEqual("{}", process_stdout.decode("utf-8")) + # @pytest.mark.flaky(reruns=3) + def test_invoke_returns_utf8(self): + command_list = InvokeIntegBase.get_command_list( + "EchoEventFunction", template_path=self.template_path, event_path=self.event_utf8_path + ) + + process = Popen(command_list, stdout=PIPE) + try: + stdout, _ = process.communicate(timeout=TIMEOUT) + except TimeoutExpired: + process.kill() + raise + + process_stdout = stdout.strip() + + with open(self.event_utf8_path) as f: + expected_output = json.dumps(json.load(f), ensure_ascii=False) + + self.assertEqual(process.returncode, 0) + self.assertEqual(expected_output, process_stdout.decode("utf-8")) + @pytest.mark.flaky(reruns=3) def test_invoke_with_env_using_parameters(self): command_list = InvokeIntegBase.get_command_list( diff --git a/tests/unit/lib/utils/test_osutils.py b/tests/unit/lib/utils/test_osutils.py index bf4794f2c4..6f7a6cf4df 100644 --- a/tests/unit/lib/utils/test_osutils.py +++ b/tests/unit/lib/utils/test_osutils.py @@ -34,9 +34,7 @@ def test_raises_on_cleanup_failure(self, rmdir_mock): @patch("os.rmdir") def test_handles_ignore_error_case(self, rmdir_mock): rmdir_mock.side_effect = OSError("fail") - dir_name = None with osutils.mkdir_temp(ignore_errors=True) as tempdir: - dir_name = tempdir self.assertTrue(os.path.exists(tempdir)) @@ -44,9 +42,6 @@ class Test_stderr(TestCase): def test_must_return_sys_stderr(self): expected_stderr = sys.stderr - if sys.version_info.major > 2: - expected_stderr = sys.stderr.buffer - self.assertEqual(expected_stderr, osutils.stderr()) @@ -54,9 +49,6 @@ class Test_stdout(TestCase): def test_must_return_sys_stdout(self): expected_stdout = sys.stdout - if sys.version_info.major > 2: - expected_stdout = sys.stdout.buffer - self.assertEqual(expected_stdout, osutils.stdout()) diff --git a/tests/unit/lib/utils/test_stream_writer.py b/tests/unit/lib/utils/test_stream_writer.py index cb48955850..3ef9425a3e 100644 --- a/tests/unit/lib/utils/test_stream_writer.py +++ b/tests/unit/lib/utils/test_stream_writer.py @@ -17,7 +17,7 @@ def test_must_write_to_stream(self): writer = StreamWriter(stream_mock) writer.write(buffer) - stream_mock.write.assert_called_once_with(buffer) + stream_mock.buffer.write.assert_called_once_with(buffer) def test_must_flush_underlying_stream(self): stream_mock = Mock() diff --git a/tests/unit/local/docker/test_lambda_image.py b/tests/unit/local/docker/test_lambda_image.py index 1e8f936d98..03b57be804 100644 --- a/tests/unit/local/docker/test_lambda_image.py +++ b/tests/unit/local/docker/test_lambda_image.py @@ -1,4 +1,3 @@ -import io import tempfile from unittest import TestCase @@ -271,7 +270,7 @@ def test_force_building_image_that_doesnt_already_exists( docker_client_mock.images.get.side_effect = ImageNotFound("image not found") docker_client_mock.images.list.return_value = [] - stream = io.StringIO() + stream = Mock() lambda_image = LambdaImage(layer_downloader_mock, False, True, docker_client=docker_client_mock) actual_image_id = lambda_image.build( @@ -311,7 +310,7 @@ def test_force_building_image_on_daemon_404( docker_client_mock.images.get.side_effect = NotFound("image not found") docker_client_mock.images.list.return_value = [] - stream = io.StringIO() + stream = Mock() lambda_image = LambdaImage(layer_downloader_mock, False, True, docker_client=docker_client_mock) actual_image_id = lambda_image.build( @@ -351,7 +350,7 @@ def test_docker_distribution_api_error_on_daemon_api_error( docker_client_mock.images.get.side_effect = APIError("error from docker daemon") docker_client_mock.images.list.return_value = [] - stream = io.StringIO() + stream = Mock() lambda_image = LambdaImage(layer_downloader_mock, False, True, docker_client=docker_client_mock) with self.assertRaises(DockerDistributionAPIError): @@ -377,7 +376,7 @@ def test_not_force_building_image_that_doesnt_already_exists( docker_client_mock.images.get.side_effect = ImageNotFound("image not found") docker_client_mock.images.list.return_value = [] - stream = io.StringIO() + stream = Mock() lambda_image = LambdaImage(layer_downloader_mock, False, False, docker_client=docker_client_mock) actual_image_id = lambda_image.build( diff --git a/tests/unit/local/docker/test_manager.py b/tests/unit/local/docker/test_manager.py index ada69903ea..b5dbe16a9b 100644 --- a/tests/unit/local/docker/test_manager.py +++ b/tests/unit/local/docker/test_manager.py @@ -1,8 +1,6 @@ """ Tests container manager """ - -import io import importlib from unittest import TestCase from unittest.mock import Mock, patch, MagicMock, ANY, call @@ -218,17 +216,29 @@ def setUp(self): self.manager = ContainerManager(docker_client=self.mock_docker_client) def test_must_pull_and_print_progress_dots(self): - stream = io.StringIO() + stream = Mock() pull_result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] self.mock_docker_client.api.pull.return_value = pull_result - expected_stream_output = "\nFetching {}:latest Docker container image...{}\n".format( - self.image_name, "." * len(pull_result) # Progress bar will print one dot per response from pull API - ) + expected_stream_calls = [ + call(f"\nFetching {self.image_name}:latest Docker container image...", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call(".", write_to_buffer=False), + call("\n", write_to_buffer=False), + ] self.manager.pull_image(self.image_name, stream=stream) self.mock_docker_client.api.pull.assert_called_with(self.image_name, stream=True, decode=True, tag="latest") - self.assertEqual(stream.getvalue(), expected_stream_output) + + stream.write.assert_has_calls(expected_stream_calls) def test_must_raise_if_image_not_found(self): msg = "some error" From 7b7c54c59ad15ad90fd558d640daefa5142115d7 Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:43:21 -0500 Subject: [PATCH 068/107] Revert "fix(invoke): Write in UTF-8 string instead of bytes. (#5232)" (#5401) This reverts commit 97104eac05c47aec1c7db62cb98cd050c7656d3d. --- samcli/lib/utils/osutils.py | 4 ++-- samcli/lib/utils/stream_writer.py | 7 ++---- samcli/local/docker/container.py | 5 +--- samcli/local/docker/lambda_image.py | 10 ++++---- samcli/local/docker/manager.py | 8 +++---- .../local/invoke/test_integrations_cli.py | 21 ---------------- tests/unit/lib/utils/test_osutils.py | 8 +++++++ tests/unit/lib/utils/test_stream_writer.py | 2 +- tests/unit/local/docker/test_lambda_image.py | 9 +++---- tests/unit/local/docker/test_manager.py | 24 ++++++------------- 10 files changed, 34 insertions(+), 64 deletions(-) diff --git a/samcli/lib/utils/osutils.py b/samcli/lib/utils/osutils.py index dfdd1ed256..d53dc9ffb5 100644 --- a/samcli/lib/utils/osutils.py +++ b/samcli/lib/utils/osutils.py @@ -87,7 +87,7 @@ def stdout(): io.BytesIO Byte stream of Stdout """ - return sys.stdout + return sys.stdout.buffer def stderr(): @@ -99,7 +99,7 @@ def stderr(): io.BytesIO Byte stream of stderr """ - return sys.stderr + return sys.stderr.buffer def remove(path): diff --git a/samcli/lib/utils/stream_writer.py b/samcli/lib/utils/stream_writer.py index 606efb606c..1fc62fa690 100644 --- a/samcli/lib/utils/stream_writer.py +++ b/samcli/lib/utils/stream_writer.py @@ -22,7 +22,7 @@ def __init__(self, stream, auto_flush=False): def stream(self): return self._stream - def write(self, output, encode=False, write_to_buffer=True): + def write(self, output, encode=False): """ Writes specified text to the underlying stream @@ -31,10 +31,7 @@ def write(self, output, encode=False, write_to_buffer=True): output bytes-like object Bytes to write """ - if write_to_buffer: - self._stream.buffer.write(output.encode() if encode else output) - else: - self._stream.write(output) + self._stream.write(output.encode() if encode else output) if self._auto_flush: self._stream.flush() diff --git a/samcli/local/docker/container.py b/samcli/local/docker/container.py index a5aebc1bd3..e70f7c2a1f 100644 --- a/samcli/local/docker/container.py +++ b/samcli/local/docker/container.py @@ -1,7 +1,6 @@ """ Representation of a generic Docker container """ -import json import logging import os import pathlib @@ -325,8 +324,7 @@ def wait_for_http_response(self, name, event, stdout): data=event.encode("utf-8"), timeout=(self.RAPID_CONNECTION_TIMEOUT, None), ) - stdout.write(json.dumps(json.loads(resp.content), ensure_ascii=False), write_to_buffer=False) - stdout.flush() + stdout.write(resp.content) def wait_for_result(self, full_path, event, stdout, stderr, start_timer=None): # NOTE(sriram-mv): Let logging happen in its own thread, so that a http request can be sent. @@ -436,7 +434,6 @@ def _write_container_output(output_itr, stdout=None, stderr=None): if stderr_data and stderr: stderr.write(stderr_data) - except Exception as ex: LOG.debug("Failed to get the logs from the container", exc_info=ex) diff --git a/samcli/local/docker/lambda_image.py b/samcli/local/docker/lambda_image.py index ab9a20a8a8..23f0a770d9 100644 --- a/samcli/local/docker/lambda_image.py +++ b/samcli/local/docker/lambda_image.py @@ -226,7 +226,7 @@ def build(self, runtime, packagetype, image, layers, architecture, stream=None, or not runtime ): stream_writer = stream or StreamWriter(sys.stderr) - stream_writer.write("Building image...", write_to_buffer=False) + stream_writer.write("Building image...") stream_writer.flush() self._build_image( image if image else base_image, rapid_image, downloaded_layers, architecture, stream=stream_writer @@ -337,15 +337,15 @@ def set_item_permission(tar_info): platform=get_docker_platform(architecture), ) for log in resp_stream: - stream_writer.write(".", write_to_buffer=False) + stream_writer.write(".") stream_writer.flush() if "error" in log: - stream_writer.write("\n", write_to_buffer=False) + stream_writer.write("\n") LOG.exception("Failed to build Docker Image") raise ImageBuildException("Error building docker image: {}".format(log["error"])) - stream_writer.write("\n", write_to_buffer=False) + stream_writer.write("\n") except (docker.errors.BuildError, docker.errors.APIError) as ex: - stream_writer.write("\n", write_to_buffer=False) + stream_writer.write("\n") LOG.exception("Failed to build Docker Image") raise ImageBuildException("Building Image failed.") from ex finally: diff --git a/samcli/local/docker/manager.py b/samcli/local/docker/manager.py index 0cd8dc8dc8..a035003bb0 100644 --- a/samcli/local/docker/manager.py +++ b/samcli/local/docker/manager.py @@ -168,18 +168,16 @@ def pull_image(self, image_name, tag=None, stream=None): raise DockerImagePullFailedException(str(ex)) from ex # io streams, especially StringIO, work only with unicode strings - stream_writer.write( - "\nFetching {}:{} Docker container image...".format(image_name, tag), write_to_buffer=False - ) + stream_writer.write("\nFetching {}:{} Docker container image...".format(image_name, tag)) # Each line contains information on progress of the pull. Each line is a JSON string for _ in result_itr: # For every line, print a dot to show progress - stream_writer.write(".", write_to_buffer=False) + stream_writer.write(".") stream_writer.flush() # We are done. Go to the next line - stream_writer.write("\n", write_to_buffer=False) + stream_writer.write("\n") def has_image(self, image_name): """ diff --git a/tests/integration/local/invoke/test_integrations_cli.py b/tests/integration/local/invoke/test_integrations_cli.py index bf3f1d1e2e..3604fc4010 100644 --- a/tests/integration/local/invoke/test_integrations_cli.py +++ b/tests/integration/local/invoke/test_integrations_cli.py @@ -291,27 +291,6 @@ def test_invoke_returns_expected_result_when_no_event_given(self): self.assertEqual(process.returncode, 0) self.assertEqual("{}", process_stdout.decode("utf-8")) - # @pytest.mark.flaky(reruns=3) - def test_invoke_returns_utf8(self): - command_list = InvokeIntegBase.get_command_list( - "EchoEventFunction", template_path=self.template_path, event_path=self.event_utf8_path - ) - - process = Popen(command_list, stdout=PIPE) - try: - stdout, _ = process.communicate(timeout=TIMEOUT) - except TimeoutExpired: - process.kill() - raise - - process_stdout = stdout.strip() - - with open(self.event_utf8_path) as f: - expected_output = json.dumps(json.load(f), ensure_ascii=False) - - self.assertEqual(process.returncode, 0) - self.assertEqual(expected_output, process_stdout.decode("utf-8")) - @pytest.mark.flaky(reruns=3) def test_invoke_with_env_using_parameters(self): command_list = InvokeIntegBase.get_command_list( diff --git a/tests/unit/lib/utils/test_osutils.py b/tests/unit/lib/utils/test_osutils.py index 6f7a6cf4df..bf4794f2c4 100644 --- a/tests/unit/lib/utils/test_osutils.py +++ b/tests/unit/lib/utils/test_osutils.py @@ -34,7 +34,9 @@ def test_raises_on_cleanup_failure(self, rmdir_mock): @patch("os.rmdir") def test_handles_ignore_error_case(self, rmdir_mock): rmdir_mock.side_effect = OSError("fail") + dir_name = None with osutils.mkdir_temp(ignore_errors=True) as tempdir: + dir_name = tempdir self.assertTrue(os.path.exists(tempdir)) @@ -42,6 +44,9 @@ class Test_stderr(TestCase): def test_must_return_sys_stderr(self): expected_stderr = sys.stderr + if sys.version_info.major > 2: + expected_stderr = sys.stderr.buffer + self.assertEqual(expected_stderr, osutils.stderr()) @@ -49,6 +54,9 @@ class Test_stdout(TestCase): def test_must_return_sys_stdout(self): expected_stdout = sys.stdout + if sys.version_info.major > 2: + expected_stdout = sys.stdout.buffer + self.assertEqual(expected_stdout, osutils.stdout()) diff --git a/tests/unit/lib/utils/test_stream_writer.py b/tests/unit/lib/utils/test_stream_writer.py index 3ef9425a3e..cb48955850 100644 --- a/tests/unit/lib/utils/test_stream_writer.py +++ b/tests/unit/lib/utils/test_stream_writer.py @@ -17,7 +17,7 @@ def test_must_write_to_stream(self): writer = StreamWriter(stream_mock) writer.write(buffer) - stream_mock.buffer.write.assert_called_once_with(buffer) + stream_mock.write.assert_called_once_with(buffer) def test_must_flush_underlying_stream(self): stream_mock = Mock() diff --git a/tests/unit/local/docker/test_lambda_image.py b/tests/unit/local/docker/test_lambda_image.py index 03b57be804..1e8f936d98 100644 --- a/tests/unit/local/docker/test_lambda_image.py +++ b/tests/unit/local/docker/test_lambda_image.py @@ -1,3 +1,4 @@ +import io import tempfile from unittest import TestCase @@ -270,7 +271,7 @@ def test_force_building_image_that_doesnt_already_exists( docker_client_mock.images.get.side_effect = ImageNotFound("image not found") docker_client_mock.images.list.return_value = [] - stream = Mock() + stream = io.StringIO() lambda_image = LambdaImage(layer_downloader_mock, False, True, docker_client=docker_client_mock) actual_image_id = lambda_image.build( @@ -310,7 +311,7 @@ def test_force_building_image_on_daemon_404( docker_client_mock.images.get.side_effect = NotFound("image not found") docker_client_mock.images.list.return_value = [] - stream = Mock() + stream = io.StringIO() lambda_image = LambdaImage(layer_downloader_mock, False, True, docker_client=docker_client_mock) actual_image_id = lambda_image.build( @@ -350,7 +351,7 @@ def test_docker_distribution_api_error_on_daemon_api_error( docker_client_mock.images.get.side_effect = APIError("error from docker daemon") docker_client_mock.images.list.return_value = [] - stream = Mock() + stream = io.StringIO() lambda_image = LambdaImage(layer_downloader_mock, False, True, docker_client=docker_client_mock) with self.assertRaises(DockerDistributionAPIError): @@ -376,7 +377,7 @@ def test_not_force_building_image_that_doesnt_already_exists( docker_client_mock.images.get.side_effect = ImageNotFound("image not found") docker_client_mock.images.list.return_value = [] - stream = Mock() + stream = io.StringIO() lambda_image = LambdaImage(layer_downloader_mock, False, False, docker_client=docker_client_mock) actual_image_id = lambda_image.build( diff --git a/tests/unit/local/docker/test_manager.py b/tests/unit/local/docker/test_manager.py index b5dbe16a9b..ada69903ea 100644 --- a/tests/unit/local/docker/test_manager.py +++ b/tests/unit/local/docker/test_manager.py @@ -1,6 +1,8 @@ """ Tests container manager """ + +import io import importlib from unittest import TestCase from unittest.mock import Mock, patch, MagicMock, ANY, call @@ -216,29 +218,17 @@ def setUp(self): self.manager = ContainerManager(docker_client=self.mock_docker_client) def test_must_pull_and_print_progress_dots(self): - stream = Mock() + stream = io.StringIO() pull_result = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] self.mock_docker_client.api.pull.return_value = pull_result - expected_stream_calls = [ - call(f"\nFetching {self.image_name}:latest Docker container image...", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call(".", write_to_buffer=False), - call("\n", write_to_buffer=False), - ] + expected_stream_output = "\nFetching {}:latest Docker container image...{}\n".format( + self.image_name, "." * len(pull_result) # Progress bar will print one dot per response from pull API + ) self.manager.pull_image(self.image_name, stream=stream) self.mock_docker_client.api.pull.assert_called_with(self.image_name, stream=True, decode=True, tag="latest") - - stream.write.assert_has_calls(expected_stream_calls) + self.assertEqual(stream.getvalue(), expected_stream_output) def test_must_raise_if_image_not_found(self): msg = "some error" From 49fe5925a325934d2464f9435319114613b765a0 Mon Sep 17 00:00:00 2001 From: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Date: Thu, 22 Jun 2023 12:43:47 -0700 Subject: [PATCH 069/107] Add sanity check script and use it in pyinstaller GHA (#5400) * Add sanity check script and use it in pyinstaller GHA * set pipefail in sanity-check.sh * Make CI_OVERRIDE a global env var in the GHA workflow * setup go in GHA * disable telemetry * Update script to check binary existence and to fix an issue in go build --- .github/workflows/validate_pyinstaller.yml | 17 +++++++- tests/sanity-check.sh | 45 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 2 deletions(-) create mode 100755 tests/sanity-check.sh diff --git a/.github/workflows/validate_pyinstaller.yml b/.github/workflows/validate_pyinstaller.yml index 8370328b5d..04b5c828ca 100644 --- a/.github/workflows/validate_pyinstaller.yml +++ b/.github/workflows/validate_pyinstaller.yml @@ -5,6 +5,9 @@ on: branches: - develop +env: + CI_OVERRIDE: "1" + jobs: build-for-linux: name: build-pyinstaller-linux @@ -15,15 +18,20 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.20" - name: Build PyInstaller run: | chmod +x ./installer/pyinstaller/build-linux.sh - CI_OVERRIDE=1 ./installer/pyinstaller/build-linux.sh aws-sam-cli-linux-x86_64.zip + ./installer/pyinstaller/build-linux.sh aws-sam-cli-linux-x86_64.zip - name: Basic tests for PyInstaller run: | unzip .build/output/aws-sam-cli-linux-x86_64.zip -d sam-installation ./sam-installation/install sam-beta --version + ./tests/sanity-check.sh - uses: actions/upload-artifact@v3 with: name: pyinstaller-linux-zip @@ -41,15 +49,20 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.7" + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: "1.20" - name: Build PyInstaller run: | chmod +x ./installer/pyinstaller/build-mac.sh - CI_OVERRIDE=1 ./installer/pyinstaller/build-mac.sh aws-sam-cli-macos-x86_64.zip + ./installer/pyinstaller/build-mac.sh aws-sam-cli-macos-x86_64.zip - name: Basic tests for PyInstaller run: | unzip .build/output/aws-sam-cli-macos-x86_64.zip -d sam-installation sudo ./sam-installation/install sam-beta --version + ./tests/sanity-check.sh - uses: actions/upload-artifact@v3 with: name: pyinstaller-macos-zip diff --git a/tests/sanity-check.sh b/tests/sanity-check.sh new file mode 100755 index 0000000000..8f971dbd00 --- /dev/null +++ b/tests/sanity-check.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -xeo pipefail + +export SAM_CLI_TELEMETRY="${SAM_CLI_TELEMETRY:-0}" + +if [ "$CI_OVERRIDE" = "1" ]; then + sam_binary="sam-beta" +elif [ "$IS_NIGHTLY" = "1" ]; then + sam_binary="sam-nightly" +elif [ "$SAM_CLI_DEV" = "1" ]; then + sam_binary="samdev" +else + sam_binary="sam" +fi + +if ! command -v "$sam_binary" &> /dev/null; then + echo "$sam_binary not found. Please check if it is in PATH" + exit 1 +fi + +echo "Using ${sam_binary} as SAM CLI binary name" + +if [ "$sam_binary" = "sam" ]; then + SAMCLI_INSTALLED_VERSION=$($sam_binary --version | cut -d " " -f 4) + + # Get latest SAM CLI version from GH main branch + SAMCLI_LATEST_VERSION=$(curl -L https://raw.githubusercontent.com/aws/aws-sam-cli/master/samcli/__init__.py | tail -n 1 | cut -d '"' -f 2) + + # Check version + if [[ "$SAMCLI_INSTALLED_VERSION" != "$SAMCLI_LATEST_VERSION" ]]; then + echo "expected: $SAMCLI_LATEST_VERSION; got: $SAMCLI_INSTALLED_VERSION" + exit 1 + fi + + echo "Version check succeeded" +fi + +echo "Starting testing sam binary" +rm -rf sam-app-testing +"$sam_binary" init --no-interactive -n sam-app-testing --dependency-manager mod --runtime go1.x --app-template hello-world --package-type Zip --architecture x86_64 +cd sam-app-testing +GOFLAGS="-buildvcs=false" "$sam_binary" build +"$sam_binary" validate + +echo "sam init, sam build, and sam validate commands Succeeded" From 4e2509badf7800dd58583d0098eb6d494acf37e4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 23 Jun 2023 02:43:32 +0000 Subject: [PATCH 070/107] chore: update aws-sam-translator to 1.70.0 (#5402) Co-authored-by: GitHub Action --- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 2ba320212e..34edac3fd0 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,7 +7,7 @@ jmespath~=1.0.1 ruamel_yaml~=0.17.21 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 -aws-sam-translator==1.69.0 +aws-sam-translator==1.70.0 #docker minor version updates can include breaking changes. Auto update micro version only. docker~=6.1.0 dateparser~=1.1 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index a6438fc289..6ad1ef3c90 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.34.0 \ --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) -aws-sam-translator==1.69.0 \ - --hash=sha256:bf26c061675f20367e87d48963ada1ec983a8d897e85db02d0f35913a6174bb0 \ - --hash=sha256:c6ed7a25c77d30d3d31156049415d15fde46c0d3b77a4c5cdc0ef8b53ba9bca2 +aws-sam-translator==1.70.0 \ + --hash=sha256:753288eda07b057e5350773b7617076962b59404d49cd05e2259ac96a7694436 \ + --hash=sha256:a2df321607d29791893707ef2ded9e79be00dbb71ac430696f6e6d7d0b0301a5 # via # aws-sam-cli (setup.py) # cfn-lint diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 0122453b5e..c69964a4c7 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.34.0 \ --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) -aws-sam-translator==1.69.0 \ - --hash=sha256:bf26c061675f20367e87d48963ada1ec983a8d897e85db02d0f35913a6174bb0 \ - --hash=sha256:c6ed7a25c77d30d3d31156049415d15fde46c0d3b77a4c5cdc0ef8b53ba9bca2 +aws-sam-translator==1.70.0 \ + --hash=sha256:753288eda07b057e5350773b7617076962b59404d49cd05e2259ac96a7694436 \ + --hash=sha256:a2df321607d29791893707ef2ded9e79be00dbb71ac430696f6e6d7d0b0301a5 # via # aws-sam-cli (setup.py) # cfn-lint From af26ea2f00d94b8057a48b63ae3fb1e4c8763175 Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Mon, 26 Jun 2023 10:33:37 -0700 Subject: [PATCH 071/107] Version bump to 1.89.0 (#5420) --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index 705542b65e..48bdee91f7 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.88.0" +__version__ = "1.89.0" From 8aa10081f0c6d3cd6a90b8dae334902eb5321432 Mon Sep 17 00:00:00 2001 From: Stephen Liedig Date: Tue, 27 Jun 2023 01:00:33 +0800 Subject: [PATCH 072/107] chore(docs): updated readme with additional resources (#5349) * chore: updated gitignore to ignore tmp scratch directory used by dotnet tests * chore: update readme to include additional workshop resources and missing Powertools links. Fixed formatting --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- .gitignore | 3 +++ README.md | 49 +++++++++++++++++++++++-------------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/.gitignore b/.gitignore index 6b3c26c6e5..784bc4af7c 100644 --- a/.gitignore +++ b/.gitignore @@ -417,8 +417,11 @@ tests/integration/testdata/buildcmd/Dotnet6/obj tests/integration/testdata/buildcmd/Dotnet7/bin tests/integration/testdata/buildcmd/Dotnet7/obj tests/integration/testdata/invoke/credential_tests/inprocess/dotnet/STS/obj +tests/integration/testdata/sync/code/after/dotnet_function/src/HelloWorld/obj/ +tests/integration/testdata/sync/code/before/dotnet_function/src/HelloWorld/obj/ # End of https://www.gitignore.io/api/osx,node,macos,linux,python,windows,pycharm,intellij,sublimetext,visualstudiocode # Installer build folder .build + diff --git a/README.md b/README.md index 452f5c6318..359f085e74 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,14 @@ [Installation](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) | [Blogs](https://serverlessland.com/blog?tag=AWS%20SAM) | [Videos](https://serverlessland.com/video?tag=AWS%20SAM) | [AWS Docs](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) | [Roadmap](https://github.com/aws/aws-sam-cli/wiki/SAM-CLI-Roadmap) | [Try It Out](https://s12d.com/jKo46elk) | [Slack Us](https://join.slack.com/t/awsdevelopers/shared_invite/zt-yryddays-C9fkWrmguDv0h2EEDzCqvw) The AWS Serverless Application Model (SAM) CLI is an open-source CLI tool that helps you develop serverless applications containing [Lambda functions](https://aws.amazon.com/lambda/), [Step Functions](https://aws.amazon.com/step-functions/), [API Gateway](https://aws.amazon.com/api-gateway/), [EventBridge](https://aws.amazon.com/eventbridge/), [SQS](https://aws.amazon.com/sqs/), [SNS](https://aws.amazon.com/sns/) and more. Some of the features it provides are: -- **Initialize serverless applications** in minutes with AWS-provided infrastructure templates with `sam init` -- **Compile, build, and package** Lambda functions with provided runtimes and with custom Makefile workflows, for zip and image types of Lambda functions with `sam build` -- **Locally test** a Lambda function and API Gateway easily in a Docker container with `sam local` commands on SAM and CDK applications -- **Sync and test your changes in the cloud** with `sam sync` in your developer environments -- **Deploy** your SAM and CloudFormation templates using `sam deploy` -- Quickly **create pipelines** with prebuilt templates with popular CI/CD systems using `sam pipeline init` -- **Tail CloudWatch logs and X-Ray traces** with `sam logs` and `sam traces` +* **Initialize serverless applications** in minutes with AWS-provided infrastructure templates with `sam init` +* **Compile, build, and package** Lambda functions with provided runtimes and with custom Makefile workflows, for zip and image types of Lambda functions with `sam build` +* **Locally test** a Lambda function and API Gateway easily in a Docker container with `sam local` commands on SAM and CDK applications +* **Sync and test your changes in the cloud** with `sam sync` in your developer environments +* **Deploy** your SAM and CloudFormation templates using `sam deploy` +* Quickly **create pipelines** with prebuilt templates with popular CI/CD systems using `sam pipeline init` +* **Tail CloudWatch logs and X-Ray traces** with `sam logs` and `sam traces` ## Recent blogposts and workshops @@ -28,53 +28,51 @@ The AWS Serverless Application Model (SAM) CLI is an open-source CLI tool that h * **Speed up development with SAM Accelerate** - quickly test your changes in the cloud. [Read docs here](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/accelerate.html). +* **AWS Serverless Developer Experience Workshop: A day in a life of a developer** - [This advanced workshop](https://s12d.com/aws-sde-workshop) provides you with an immersive experience as a serverless developer, with hands-on experience building a serverless solution using AWS SAM and SAM CLI. + * **The Complete SAM Workshop**- [This workshop](https://s12d.com/jKo46elk) is a great way to experience the power of SAM and SAM CLI. * **Getting started with CI/CD? SAM pipelines can help you get started** - [This workshop](https://s12d.com/_JQ48d5T) walks you through the basics. * **Get started with Serverless Application development using SAM CLI** - [This workshop](https://s12d.com/Tq9ZE-Br) walks you through the basics. - ## Get Started -To get started with building SAM-based applications, use the SAM CLI. SAM CLI provides a Lambda-like execution +To get started with building SAM-based applications, use the SAM CLI. SAM CLI provides a Lambda-like execution environment that lets you locally build, test, debug, and deploy [AWS serverless](https://aws.amazon.com/serverless/) applications. * [Install SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) * [Build & Deploy a "Hello World" Web App](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-quick-start.html) * [Install AWS Toolkit](https://aws.amazon.com/getting-started/tools-sdks/#IDE_and_IDE_Toolkits) to use SAM with your favorite IDEs * [Tutorials and Workshops](https://serverlessland.com/learn) -* [Powertools for AWS Lambda](https://aws.amazon.com/blogs/opensource/simplifying-serverless-best-practices-with-lambda-powertools/) Utilities for building Lambda functions in [Python](https://awslabs.github.io/aws-lambda-powertools-python/latest/), [Java](https://github.com/awslabs/aws-lambda-powertools-java), and [TypeScript](https://github.com/awslabs/aws-lambda-powertools-typescript) - +* **Powertools for AWS Lambda** is a developer toolkit to implement Serverless best practices and increase developer velocity. Available for [Python](https://awslabs.github.io/aws-lambda-powertools-python), [Java](https://github.com/awslabs/aws-lambda-powertools-java), [TypeScript](https://github.com/awslabs/aws-lambda-powertools-typescript) and [.NET](https://github.com/awslabs/aws-lambda-powertools-dotnet). **Next Steps:** Learn to build a more complex serverless application. + * [Extract text from images and store it in a database](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-example-s3.html) using Amazon S3 and Amazon Rekognition services. * [Detect when records are added to a database](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-example-ddb.html) using Amazon DynamoDB database and asynchronous stream processing. * [Explore popular patterns](https://serverlessland.com/patterns) - ## What is this Github repository? 💻 This Github repository contains source code for SAM CLI. Here is the development team talking about this code: -> SAM CLI code is written in Python. Source code is well documented, very modular, with 95% unit test coverage. +> SAM CLI code is written in Python. Source code is well documented, very modular, with 95% unit test coverage. It uses this awesome Python library called Click to manage the command line interaction and uses Docker to run Lambda functions locally. We think you'll like the code base. Clone it and run `make pr` or `./Make -pr` on Windows! - ## Related Repositories and Resources -+ **SAM Transform** [Open source template specification](https://github.com/aws/serverless-application-model/) that provides shorthand syntax for CloudFormation -+ **SAM CLI application templates** Get started quickly with [predefined application templates](https://github.com/aws/aws-sam-cli-app-templates/blob/master/README.md) for all supported runtimes and languages, used by `sam init` -+ **Lambda Builders** [Lambda builder tools](https://github.com/aws/aws-lambda-builders) for supported runtimes and custom build workflows, used by `sam build` -+ **Build and local emulation images for CI/CD tools** [Build container images](https://gallery.ecr.aws/sam/) to use with CI/CD tasks - +* **SAM Transform** [Open source template specification](https://github.com/aws/serverless-application-model/) that provides shorthand syntax for CloudFormation +* **SAM CLI application templates** Get started quickly with [predefined application templates](https://github.com/aws/aws-sam-cli-app-templates/blob/master/README.md) for all supported runtimes and languages, used by `sam init` +* **Lambda Builders** [Lambda builder tools](https://github.com/aws/aws-lambda-builders) for supported runtimes and custom build workflows, used by `sam build` +* **Build and local emulation images for CI/CD tools** [Build container images](https://gallery.ecr.aws/sam/) to use with CI/CD tasks ## Contribute to SAM -We love our contributors ❤️ We have over 100 contributors who have built various parts of the product. +We love our contributors ❤️ We have over 100 contributors who have built various parts of the product. Read this [testimonial from @ndobryanskyy](https://www.lohika.com/aws-sam-my-exciting-first-open-source-experience/) to learn -more about what it was like contributing to SAM. +more about what it was like contributing to SAM. Depending on your interest and skill, you can help build the different parts of the SAM project; @@ -84,21 +82,20 @@ Make pull requests, report bugs, and share ideas to improve the full SAM templat Source code is located on Github at [aws/serverless-application-model](https://github.com/aws/serverless-application-model). Read the [SAM Specification Contributing Guide](https://github.com/aws/serverless-application-model/blob/master/CONTRIBUTING.md) to get started. - + **Strengthen SAM CLI** Add new commands, enhance existing ones, report bugs, or request new features for the SAM CLI. Source code is located on Github at [aws/aws-sam-cli](https://github.com/aws/aws-sam-cli). Read the [SAM CLI Contributing Guide](https://github.com/aws/aws-sam-cli/blob/develop/CONTRIBUTING.md) to -get started. +get started. **Update SAM Developer Guide** [SAM Developer Guide](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/index.html) provides a comprehensive getting started guide and reference documentation. Source code is located on Github at [awsdocs/aws-sam-developer-guide](https://github.com/awsdocs/aws-sam-developer-guide). Read the [SAM Documentation Contribution Guide](https://github.com/awsdocs/aws-sam-developer-guide/blob/master/CONTRIBUTING.md) to get -started. - +started. ### Join the SAM Community on Slack -[Join the SAM developers channel (#samdev)](https://join.slack.com/t/awsdevelopers/shared_invite/zt-yryddays-C9fkWrmguDv0h2EEDzCqvw) on Slack to collaborate with fellow community members and the AWS SAM team. +[Join the SAM developers channel (#samdev)](https://join.slack.com/t/awsdevelopers/shared_invite/zt-yryddays-C9fkWrmguDv0h2EEDzCqvw) on Slack to collaborate with fellow community members and the AWS SAM team. \ No newline at end of file From 18ba6ede49fce8660d02ecf271350636632c5958 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:19:21 -0700 Subject: [PATCH 073/107] chore(deps): bump actions/setup-go from 3 to 4 (#5418) Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 4. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-go dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> --- .github/workflows/validate_pyinstaller.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate_pyinstaller.yml b/.github/workflows/validate_pyinstaller.yml index 04b5c828ca..b611420310 100644 --- a/.github/workflows/validate_pyinstaller.yml +++ b/.github/workflows/validate_pyinstaller.yml @@ -19,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: "1.20" - name: Build PyInstaller @@ -50,7 +50,7 @@ jobs: with: python-version: "3.7" - name: Set up Go - uses: actions/setup-go@v3 + uses: actions/setup-go@v4 with: go-version: "1.20" - name: Build PyInstaller From 00b262d1bea1b475854ffa79d78fc28a73801c77 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 14:21:23 -0700 Subject: [PATCH 074/107] chore(deps-dev): bump filelock from 3.12.0 to 3.12.2 in /requirements (#5378) Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.12.0 to 3.12.2. - [Release notes](https://github.com/tox-dev/py-filelock/releases) - [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/py-filelock/compare/3.12.0...3.12.2) --- updated-dependencies: - dependency-name: filelock dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 4ef0736e47..4370b187ca 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -33,7 +33,7 @@ pytest-rerunfailures==11.1.2 # NOTE (hawflau): DO NOT upgrade pytest-metadata and pytest-json-report unless pytest-json-report addresses https://github.com/numirias/pytest-json-report/issues/89 pytest-metadata==2.0.4 pytest-json-report==1.5.0 -filelock==3.12.0 +filelock==3.12.2 # formatter black==22.6.0 From 663c88d992508a38f76123281b76c4615f8eed56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 27 Jun 2023 21:52:34 +0000 Subject: [PATCH 075/107] feat: updating app templates repo hash with (bb905c379830c3d8edbc196bda731076549028e3) (#5398) Co-authored-by: GitHub Action --- samcli/runtime_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/runtime_config.json b/samcli/runtime_config.json index b648be6cc3..3609a8eea6 100644 --- a/samcli/runtime_config.json +++ b/samcli/runtime_config.json @@ -1,3 +1,3 @@ { - "app_template_repo_commit": "67f28fd83477e0e15b394f995afb33b2053b4074" + "app_template_repo_commit": "bb905c379830c3d8edbc196bda731076549028e3" } From 9a715915928b3a5ef96291228afc51f0356b1ffa Mon Sep 17 00:00:00 2001 From: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Date: Tue, 27 Jun 2023 20:09:17 -0700 Subject: [PATCH 076/107] fix: add a table for package help text. (#5298) * fix: add a table for package help text. * Update samcli/commands/package/core/command.py Co-authored-by: Daniel Mil <84205762+mildaniel@users.noreply.github.com> * tests: fix strings in package help text * fix: PR comments * fix: PR comments. --------- Co-authored-by: Daniel Mil <84205762+mildaniel@users.noreply.github.com> --- samcli/cli/context.py | 6 + samcli/commands/_utils/options.py | 8 +- samcli/commands/package/command.py | 35 +++-- samcli/commands/package/core/__init__.py | 0 samcli/commands/package/core/command.py | 138 ++++++++++++++++++ samcli/commands/package/core/formatters.py | 19 +++ samcli/commands/package/core/options.py | 68 +++++++++ tests/unit/cli/test_context.py | 6 + tests/unit/commands/package/core/__init__.py | 0 .../commands/package/core/test_command.py | 85 +++++++++++ .../commands/package/core/test_formatter.py | 12 ++ .../commands/package/core/test_options.py | 12 ++ 12 files changed, 373 insertions(+), 16 deletions(-) create mode 100644 samcli/commands/package/core/__init__.py create mode 100644 samcli/commands/package/core/command.py create mode 100644 samcli/commands/package/core/formatters.py create mode 100644 samcli/commands/package/core/options.py create mode 100644 tests/unit/commands/package/core/__init__.py create mode 100644 tests/unit/commands/package/core/test_command.py create mode 100644 tests/unit/commands/package/core/test_formatter.py create mode 100644 tests/unit/commands/package/core/test_options.py diff --git a/samcli/cli/context.py b/samcli/cli/context.py index 404fd36661..49c5e44c78 100644 --- a/samcli/cli/context.py +++ b/samcli/cli/context.py @@ -7,6 +7,7 @@ from typing import List, Optional, cast import click +from rich.console import Console from samcli.cli.formatters import RootCommandHelpTextFormatter from samcli.commands.exceptions import AWSServiceClientError @@ -44,6 +45,11 @@ def __init__(self): self._session_id = str(uuid.uuid4()) self._experimental = False self._exception = None + self._console = Console() + + @property + def console(self): + return self._console @property def exception(self): diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py index 188b1705b4..5b1b55cc32 100644 --- a/samcli/commands/_utils/options.py +++ b/samcli/commands/_utils/options.py @@ -326,7 +326,7 @@ def no_progressbar_click_option(): default=False, required=False, is_flag=True, - help="Does not showcase a progress bar when uploading artifacts to s3 and pushing docker images to ECR", + help="Does not showcase a progress bar when uploading artifacts to S3 and pushing docker images to ECR", ) @@ -679,9 +679,9 @@ def resolve_s3_click_option(guided): required=False, is_flag=True, callback=callback, - help="Automatically resolve s3 bucket for non-guided deployments. " - "Enabling this option will also create a managed default s3 bucket for you. " - "If you do not provide a --s3-bucket value, the managed bucket will be used. " + help="Automatically resolve AWS S3 bucket for non-guided deployments. " + "Enabling this option will also create a managed default AWS S3 bucket for you. " + "If one does not provide a --s3-bucket value, the managed bucket will be used. " "Do not use --guided with this option.", ) diff --git a/samcli/commands/package/command.py b/samcli/commands/package/command.py index ee74b67c37..41fb10b133 100644 --- a/samcli/commands/package/command.py +++ b/samcli/commands/package/command.py @@ -21,6 +21,7 @@ template_click_option, use_json_option, ) +from samcli.commands.package.core.command import PackageCommand from samcli.lib.bootstrap.bootstrap import manage_stack from samcli.lib.cli_validation.image_repository_validation import image_repository_validation from samcli.lib.telemetry.metric import track_command, track_template_warnings @@ -42,20 +43,30 @@ def resources_and_properties_help_string(): ) -HELP_TEXT = ( - """The SAM package command creates and uploads artifacts based on the package type of a given resource. -It uploads local images to ECR for `Image` package types. -It creates zip of your code and dependencies and uploads it to S3 for other package types. -The command returns a copy of your template, replacing references to local artifacts -with the AWS location where the command uploaded the artifacts. - -The following resources and their property locations are supported. -""" - + resources_and_properties_help_string() -) +DESCRIPTION = """ + Creates and uploads artifacts based on the package type of a given resource. + It uploads local images to ECR for `Image` package types. + It creates a zip of code and dependencies and uploads it to S3 for `Zip` package types. + + A new template is returned which replaces references to local artifacts + with the AWS location where the command uploaded the artifacts. + """ -@click.command("package", short_help=SHORT_HELP, help=HELP_TEXT, context_settings=dict(max_content_width=120)) +@click.command( + "package", + short_help=SHORT_HELP, + context_settings={ + "ignore_unknown_options": False, + "allow_interspersed_args": True, + "allow_extra_args": True, + "max_content_width": 120, + }, + cls=PackageCommand, + help=SHORT_HELP, + description=DESCRIPTION, + requires_credentials=True, +) @configuration_option(provider=TomlProvider(section="parameters")) @template_click_option(include_build=True) @click.option( diff --git a/samcli/commands/package/core/__init__.py b/samcli/commands/package/core/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samcli/commands/package/core/command.py b/samcli/commands/package/core/command.py new file mode 100644 index 0000000000..16a9ee3d2a --- /dev/null +++ b/samcli/commands/package/core/command.py @@ -0,0 +1,138 @@ +""" +`sam package` command class for help text visual layer. +""" +import click +from click import Context, style +from rich.table import Table + +from samcli.cli.core.command import CoreCommand +from samcli.cli.row_modifiers import RowDefinition, ShowcaseRowModifier +from samcli.commands.package.core.formatters import PackageCommandHelpTextFormatter +from samcli.commands.package.core.options import OPTIONS_INFO +from samcli.lib.utils.resources import resources_generator + +COL_SIZE_MODIFIER = 38 + + +class PackageCommand(CoreCommand): + """ + `sam` package specific command class that specializes in the visual appearance + of `sam package` help text. + It hosts a custom formatter, examples, table for supported resources, acronyms + and how options are to be used in the CLI for `sam package`. + """ + + class CustomFormatterContext(Context): + formatter_class = PackageCommandHelpTextFormatter + + context_class = CustomFormatterContext + + @staticmethod + def format_examples(ctx: Context, formatter: PackageCommandHelpTextFormatter): + with formatter.indented_section(name="Examples", extra_indents=1): + with formatter.indented_section(name="Automatic resolution of S3 buckets", extra_indents=1): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style(f"$ {ctx.command_path} --resolve-s3"), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ], + col_max=COL_SIZE_MODIFIER, + ) + with formatter.indented_section(name="Get packaged template", extra_indents=1): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style(f"$ {ctx.command_path} --resolve-s3 --output-template-file packaged.yaml"), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ], + col_max=COL_SIZE_MODIFIER, + ) + with formatter.indented_section(name="Customized location for uploading artifacts", extra_indents=1): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"$ {ctx.command_path} --s3-bucket S3_BUCKET --output-template-file packaged.yaml" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ], + col_max=COL_SIZE_MODIFIER, + ) + + @staticmethod + def format_table(formatter: PackageCommandHelpTextFormatter): + with formatter.section(name="Supported Resources"): + pass + ctx = click.get_current_context() + table = Table(width=ctx.max_content_width) + table.add_column("Resource") + table.add_column("Location") + for resource, location in resources_generator(): + table.add_row(resource, location) + with ctx.obj.console.capture() as capture: + ctx.obj.console.print(table) + formatter.write_rd( + [ + RowDefinition(name="\n"), + RowDefinition(name=capture.get()), + ], + col_max=COL_SIZE_MODIFIER, + ) + + @staticmethod + def format_acronyms(formatter: PackageCommandHelpTextFormatter): + with formatter.indented_section(name="Acronyms", extra_indents=1): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name="S3", + text="Simple Storage Service", + extra_row_modifiers=[ShowcaseRowModifier()], + ), + RowDefinition( + name="ECR", + text="Elastic Container Registry", + extra_row_modifiers=[ShowcaseRowModifier()], + ), + RowDefinition( + name="KMS", + text="Key Management Service", + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ], + col_max=COL_SIZE_MODIFIER, + ) + + def format_options(self, ctx: Context, formatter: PackageCommandHelpTextFormatter) -> None: # type:ignore + # `ignore` is put in place here for mypy even though it is the correct behavior, + # as the `formatter_class` can be set in subclass of Command. If ignore is not set, + # mypy raises argument needs to be HelpFormatter as super class defines it. + + self.format_description(formatter) + PackageCommand.format_examples(ctx, formatter) + PackageCommand.format_table(formatter) + PackageCommand.format_acronyms(formatter) + + CoreCommand._format_options( + ctx=ctx, + params=self.get_params(ctx), + formatter=formatter, + formatting_options=OPTIONS_INFO, + write_rd_overrides={"col_max": COL_SIZE_MODIFIER}, + ) diff --git a/samcli/commands/package/core/formatters.py b/samcli/commands/package/core/formatters.py new file mode 100644 index 0000000000..3faca8d644 --- /dev/null +++ b/samcli/commands/package/core/formatters.py @@ -0,0 +1,19 @@ +from samcli.cli.formatters import RootCommandHelpTextFormatter +from samcli.cli.row_modifiers import BaseLineRowModifier +from samcli.commands.deploy.core.options import ALL_OPTIONS + + +class PackageCommandHelpTextFormatter(RootCommandHelpTextFormatter): + # Picked an additive constant that gives an aesthetically pleasing look. + ADDITIVE_JUSTIFICATION = 15 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Add Additional space after determining the longest option. + # However, do not justify with padding for more than half the width of + # the terminal to retain aesthetics. + self.left_justification_length = min( + max([len(option) for option in ALL_OPTIONS]) + self.ADDITIVE_JUSTIFICATION, + self.width // 2 - self.indent_increment, + ) + self.modifiers = [BaseLineRowModifier()] diff --git a/samcli/commands/package/core/options.py b/samcli/commands/package/core/options.py new file mode 100644 index 0000000000..5a10f943f9 --- /dev/null +++ b/samcli/commands/package/core/options.py @@ -0,0 +1,68 @@ +""" +Package Command Options related Datastructures for formatting. +""" +from typing import Dict, List + +from samcli.cli.core.options import ALL_COMMON_OPTIONS, add_common_options_info +from samcli.cli.row_modifiers import RowDefinition + +# The ordering of the option lists matter, they are the order in which options will be displayed. + +REQUIRED_OPTIONS: List[str] = ["s3_bucket", "resolve_s3"] + +AWS_CREDENTIAL_OPTION_NAMES: List[str] = ["region", "profile"] + +INFRASTRUCTURE_OPTION_NAMES: List[str] = [ + "s3_prefix", + "image_repository", + "image_repositories", + "kms_key_id", + "metadata", +] + +DEPLOYMENT_OPTIONS: List[str] = [ + "force_upload", +] + +CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] + +ADDITIONAL_OPTIONS: List[str] = [ + "no_progressbar", + "signing_profiles", + "template_file", + "output_template_file", + "use_json", +] + +ALL_OPTIONS: List[str] = ( + REQUIRED_OPTIONS + + AWS_CREDENTIAL_OPTION_NAMES + + INFRASTRUCTURE_OPTION_NAMES + + DEPLOYMENT_OPTIONS + + CONFIGURATION_OPTION_NAMES + + ADDITIONAL_OPTIONS + + ALL_COMMON_OPTIONS +) + +OPTIONS_INFO: Dict[str, Dict] = { + "Required Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(REQUIRED_OPTIONS)}}, + "AWS Credential Options": { + "option_names": {opt: {"rank": idx} for idx, opt in enumerate(AWS_CREDENTIAL_OPTION_NAMES)} + }, + "Infrastructure Options": { + "option_names": {opt: {"rank": idx} for idx, opt in enumerate(INFRASTRUCTURE_OPTION_NAMES)} + }, + "Package Management Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(DEPLOYMENT_OPTIONS)}}, + "Configuration Options": { + "option_names": {opt: {"rank": idx} for idx, opt in enumerate(CONFIGURATION_OPTION_NAMES)}, + "extras": [ + RowDefinition(name="Learn more about configuration files at:"), + RowDefinition( + name="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli" + "-config.html. " + ), + ], + }, + "Additional Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(ADDITIONAL_OPTIONS)}}, +} +add_common_options_info(OPTIONS_INFO) diff --git a/tests/unit/cli/test_context.py b/tests/unit/cli/test_context.py index 709182e0f5..06dce014a5 100644 --- a/tests/unit/cli/test_context.py +++ b/tests/unit/cli/test_context.py @@ -5,6 +5,8 @@ from unittest import TestCase from unittest.mock import patch, ANY +from rich.console import Console + from samcli.cli.context import Context from samcli.lib.utils.sam_logging import ( SamCliLogger, @@ -20,6 +22,10 @@ def test_must_initialize_with_defaults(self): self.assertEqual(ctx.debug, False, "debug must default to False") + def test_must_have_console(self): + ctx = Context() + self.assertTrue(isinstance(ctx.console, Console)) + def test_must_set_get_debug_flag(self): ctx = Context() diff --git a/tests/unit/commands/package/core/__init__.py b/tests/unit/commands/package/core/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/commands/package/core/test_command.py b/tests/unit/commands/package/core/test_command.py new file mode 100644 index 0000000000..fdfd571461 --- /dev/null +++ b/tests/unit/commands/package/core/test_command.py @@ -0,0 +1,85 @@ +import unittest +from unittest.mock import Mock, patch, MagicMock +from samcli.commands.package.core.command import PackageCommand +from samcli.commands.package.command import DESCRIPTION +from tests.unit.cli.test_command import MockFormatter + + +class MockParams: + def __init__(self, rv, name): + self.rv = rv + self.name = name + + def get_help_record(self, ctx): + return self.rv + + +class TestPackageCommand(unittest.TestCase): + @patch.object(PackageCommand, "get_params") + def test_get_options_package_command_text(self, mock_get_params): + with patch("click.get_current_context", return_value=MagicMock()) as mock_get_current_context: + # Set up the chain of calls to return 'mock' on .get() + mock_get_current_context.return_value.obj.console.capture().__enter__().get.return_value = "mock" + ctx = Mock() + ctx.command_path = "sam package" + ctx.parent.command_path = "sam" + formatter = MockFormatter(scrub_text=True) + # NOTE(sriram-mv): One option per option section. + mock_get_params.return_value = [ + MockParams(rv=("--region", "Region"), name="region"), + MockParams(rv=("--debug", ""), name="debug"), + MockParams(rv=("--config-file", ""), name="config_file"), + MockParams(rv=("--s3-prefix", ""), name="s3_prefix"), + MockParams(rv=("--s3-bucket", ""), name="s3_bucket"), + MockParams(rv=("--signing-profiles", ""), name="signing_profiles"), + MockParams(rv=("--stack-name", ""), name="stack_name"), + MockParams(rv=("--force-upload", ""), name="force_upload"), + MockParams(rv=("--beta-features", ""), name="beta_features"), + ] + + cmd = PackageCommand(name="package", requires_credentials=False, description=DESCRIPTION) + expected_output = { + "AWS Credential Options": [("", ""), ("--region", ""), ("", "")], + "Acronyms": [("", ""), ("S3", ""), ("ECR", ""), ("KMS", "")], + "Additional Options": [("", ""), ("--signing-profiles", ""), ("", "")], + "Automatic resolution of S3 buckets": [("", ""), ("$ sam package --resolve-s3\x1b[0m", "")], + "Beta Options": [("", ""), ("--beta-features", ""), ("", "")], + "Configuration Options": [("", ""), ("--config-file", ""), ("", "")], + "Customized location for uploading artifacts": [ + ("", ""), + ("$ sam package --s3-bucket " "S3_BUCKET " "--output-template-file " "packaged.yaml\x1b[0m", ""), + ], + "Description": [ + ( + "\n" + " Creates and uploads artifacts based on the package type " + "of a given resource.\n" + " It uploads local images to ECR for `Image` package " + "types.\n" + " It creates a zip of code and dependencies and uploads it " + "to S3 for `Zip` package types. \n" + " \n" + " A new template is returned which replaces references to " + "local artifacts\n" + " with the AWS location where the command uploaded the " + "artifacts.\n" + " \x1b[1m\n" + " This command may not require access to AWS " + "credentials.\x1b[0m", + "", + ) + ], + "Examples": [], + "Get packaged template": [ + ("", ""), + ("$ sam package --resolve-s3 --output-template-file " "packaged.yaml\x1b[0m", ""), + ], + "Infrastructure Options": [("", ""), ("--s3-prefix", ""), ("", "")], + "Other Options": [("", ""), ("--debug", ""), ("", "")], + "Package Management Options": [("", ""), ("--force-upload", ""), ("", "")], + "Required Options": [("", ""), ("--s3-bucket", ""), ("", "")], + "Supported Resources": [("\n", ""), ("mock", "")], + } + + cmd.format_options(ctx, formatter) + self.assertEqual(formatter.data, expected_output) diff --git a/tests/unit/commands/package/core/test_formatter.py b/tests/unit/commands/package/core/test_formatter.py new file mode 100644 index 0000000000..559b247fd6 --- /dev/null +++ b/tests/unit/commands/package/core/test_formatter.py @@ -0,0 +1,12 @@ +from shutil import get_terminal_size +from unittest import TestCase + +from samcli.cli.row_modifiers import BaseLineRowModifier +from samcli.commands.package.core.formatters import PackageCommandHelpTextFormatter + + +class TestPackageCommandHelpTextFormatter(TestCase): + def test_deploy_formatter(self): + self.formatter = PackageCommandHelpTextFormatter() + self.assertTrue(self.formatter.left_justification_length <= get_terminal_size().columns // 2) + self.assertIsInstance(self.formatter.modifiers[0], BaseLineRowModifier) diff --git a/tests/unit/commands/package/core/test_options.py b/tests/unit/commands/package/core/test_options.py new file mode 100644 index 0000000000..534aadb2f3 --- /dev/null +++ b/tests/unit/commands/package/core/test_options.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from click import Option + +from samcli.commands.package.command import cli +from samcli.commands.package.core.options import ALL_OPTIONS + + +class TestOptions(TestCase): + def test_all_options_formatted(self): + command_options = [param.human_readable_name if isinstance(param, Option) else None for param in cli.params] + self.assertEqual(sorted(ALL_OPTIONS), sorted(filter(lambda item: item is not None, command_options + ["help"]))) From 743d389f7bf10570cd68b1292c5e16be6e734615 Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Wed, 28 Jun 2023 14:50:03 -0700 Subject: [PATCH 077/107] fix: Handle BROKEN_PIPE_ERROR (#5386) * Handle pywintypes pipe exception * Moved exception checking to check for winerror * Use decorator and added unit tests * Added failure test case * make format * Added more context/comments --- samcli/lib/utils/file_observer.py | 42 ++++++++++++++++++++++ tests/unit/lib/utils/test_file_observer.py | 28 +++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/samcli/lib/utils/file_observer.py b/samcli/lib/utils/file_observer.py index 8ccf25cd9a..282aedb97b 100644 --- a/samcli/lib/utils/file_observer.py +++ b/samcli/lib/utils/file_observer.py @@ -2,6 +2,7 @@ Wraps watchdog to observe file system for any change. """ import logging +import platform import threading import uuid from abc import ABC, abstractmethod @@ -24,6 +25,8 @@ from samcli.local.lambdafn.config import FunctionConfig LOG = logging.getLogger(__name__) +# Windows API error returned when attempting to perform I/O on closed pipe +BROKEN_PIPE_ERROR = 109 class ResourceObserver(ABC): @@ -243,6 +246,44 @@ class ImageObserverException(ObserverException): """ +def broken_pipe_handler(func: Callable) -> Callable: + """ + Decorator to handle the Windows API BROKEN_PIPE_ERROR error. + + Parameters + ---------- + func: Callable + The method to wrap around + """ + + # NOTE: As of right now, this checks for the Windows API error 109 + # specifically. This could be abstracted to potentially utilize a + # callback method to further customize this. + + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as exception: + # handle a pywintypes exception that gets thrown when trying to exit + # from a command that utilizes ImageObserver(s) in + # EAGER container mode (start-api, start-lambda) + + # all containers would have been stopped, and deleted, however + # the pipes to those containers are still loaded somewhere + + if not platform.system() == "Windows": + raise + + win_error = getattr(exception, "winerror", None) + + if not win_error == BROKEN_PIPE_ERROR: + raise + + LOG.debug("Handling BROKEN_PIPE_ERROR pywintypes, exception ignored gracefully") + + return wrapper + + class ImageObserver(ResourceObserver): """ A class that will observe some docker images for any change. @@ -263,6 +304,7 @@ def __init__(self, on_change: Callable) -> None: self._images_observer_thread: Optional[Thread] = None self._lock: Lock = threading.Lock() + @broken_pipe_handler def _watch_images_events(self): for event in self.events: if event.get("Action", None) != "tag": diff --git a/tests/unit/lib/utils/test_file_observer.py b/tests/unit/lib/utils/test_file_observer.py index 739648ad4c..9400aac775 100644 --- a/tests/unit/lib/utils/test_file_observer.py +++ b/tests/unit/lib/utils/test_file_observer.py @@ -11,6 +11,7 @@ from samcli.lib.utils.file_observer import ( FileObserver, FileObserverException, + broken_pipe_handler, calculate_checksum, ImageObserver, ImageObserverException, @@ -1070,3 +1071,30 @@ def test_calculate_check_sum_for_dir(self, dir_checksum_mock, PathMock): path_mock.is_file.return_value = False dir_checksum_mock.return_value = "1234" self.assertEqual(calculate_checksum(path), "1234") + + +class TestBrokenPipeDecorator(TestCase): + def setUp(self): + self.mock_exception = Exception() + setattr(self.mock_exception, "winerror", 109) + + @patch("samcli.lib.utils.file_observer.platform.system") + def test_decorator_handle_gracefully(self, system_mock): + system_mock.return_value = "Windows" + + @broken_pipe_handler + def test_method(): + raise self.mock_exception + + test_method() + + @patch("samcli.lib.utils.file_observer.platform.system") + def test_decorator_raises_exception(self, system_mock): + system_mock.return_value = "not windows" + + @broken_pipe_handler + def test_method(): + raise self.mock_exception + + with self.assertRaises(Exception): + test_method() From 3952ff61527cee07265aab7c4b109db66f301f61 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:38:46 -0700 Subject: [PATCH 078/107] fix: remove circular dependency by moving parse_s3 method to its own util file (#5430) * fix: remove circular dependency by moving parse_s3 method to its own util file * add missing unit tests file --- .../companion_stack_manager.py | 3 +- samcli/lib/deploy/deployer.py | 5 +- samcli/lib/package/artifact_exporter.py | 6 +- samcli/lib/package/code_signer.py | 4 +- samcli/lib/package/packageable_resources.py | 5 +- samcli/lib/package/s3_uploader.py | 77 +------------------ samcli/lib/package/utils.py | 3 +- samcli/lib/utils/s3.py | 74 ++++++++++++++++++ tests/end_to_end/test_stages.py | 3 +- .../test_companion_stack_manager.py | 4 + .../lib/package/test_artifact_exporter.py | 46 ----------- tests/unit/lib/utils/test_s3.py | 49 ++++++++++++ 12 files changed, 146 insertions(+), 133 deletions(-) create mode 100644 samcli/lib/utils/s3.py create mode 100644 tests/unit/lib/utils/test_s3.py diff --git a/samcli/lib/bootstrap/companion_stack/companion_stack_manager.py b/samcli/lib/bootstrap/companion_stack/companion_stack_manager.py index 612341988d..f15d3e52bf 100644 --- a/samcli/lib/bootstrap/companion_stack/companion_stack_manager.py +++ b/samcli/lib/bootstrap/companion_stack/companion_stack_manager.py @@ -17,6 +17,7 @@ from samcli.lib.providers.sam_function_provider import SamFunctionProvider from samcli.lib.providers.sam_stack_provider import SamLocalStackProvider from samcli.lib.utils.packagetype import IMAGE +from samcli.lib.utils.s3 import parse_s3_url # pylint: disable=E0401 if typing.TYPE_CHECKING: # pragma: no cover @@ -112,7 +113,7 @@ def update_companion_stack(self) -> None: self._s3_client, bucket_name=self._s3_bucket, prefix=self._s3_prefix, no_progressbar=True ) # TemplateUrl property requires S3 URL to be in path-style format - parts = S3Uploader.parse_s3_url( + parts = parse_s3_url( s3_uploader.upload_with_dedup(temporary_file.name, "template"), version_property="Version" ) diff --git a/samcli/lib/deploy/deployer.py b/samcli/lib/deploy/deployer.py index 16e860c54c..58a2582403 100644 --- a/samcli/lib/deploy/deployer.py +++ b/samcli/lib/deploy/deployer.py @@ -38,6 +38,7 @@ from samcli.lib.package.local_files_utils import get_uploaded_s3_object_name, mktempfile from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.utils.colors import Colored, Colors +from samcli.lib.utils.s3 import parse_s3_url from samcli.lib.utils.time import utc_to_timestamp LOG = logging.getLogger(__name__) @@ -203,9 +204,7 @@ def _process_kwargs( temporary_file.flush() remote_path = get_uploaded_s3_object_name(file_path=temporary_file.name, extension="template") # TemplateUrl property requires S3 URL to be in path-style format - parts = S3Uploader.parse_s3_url( - s3_uploader.upload(temporary_file.name, remote_path), version_property="Version" - ) + parts = parse_s3_url(s3_uploader.upload(temporary_file.name, remote_path), version_property="Version") kwargs["TemplateURL"] = s3_uploader.to_path_style_s3_url(parts["Key"], parts.get("Version", None)) # don't set these arguments if not specified to use existing values diff --git a/samcli/lib/package/artifact_exporter.py b/samcli/lib/package/artifact_exporter.py index 9e60dc5c1c..b2bbce7328 100644 --- a/samcli/lib/package/artifact_exporter.py +++ b/samcli/lib/package/artifact_exporter.py @@ -29,7 +29,6 @@ ECRResource, ResourceZip, ) -from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.package.uploaders import Destination, Uploaders from samcli.lib.package.utils import ( is_local_file, @@ -47,6 +46,7 @@ AWS_SERVERLESS_FUNCTION, RESOURCES_WITH_LOCAL_PATHS, ) +from samcli.lib.utils.s3 import parse_s3_url from samcli.yamlhelper import yaml_dump, yaml_parse # NOTE: sriram-mv, A cyclic dependency on `Template` needs to be broken. @@ -99,7 +99,7 @@ def do_export(self, resource_id, resource_dict, parent_dir): url = self.uploader.upload(temporary_file.name, remote_path) # TemplateUrl property requires S3 URL to be in path-style format - parts = S3Uploader.parse_s3_url(url, version_property="Version") + parts = parse_s3_url(url, version_property="Version") s3_path_url = self.uploader.to_path_style_s3_url(parts["Key"], parts.get("Version", None)) set_value_from_jmespath(resource_dict, self.PROPERTY_NAME, s3_path_url) @@ -146,7 +146,7 @@ def do_export(self, resource_id, resource_dict, parent_dir): url = self.uploader.upload(abs_template_path, remote_path) # TemplateUrl property requires S3 URL to be in path-style format - parts = S3Uploader.parse_s3_url(url, version_property="Version") + parts = parse_s3_url(url, version_property="Version") s3_path_url = self.uploader.to_path_style_s3_url(parts["Key"], parts.get("Version", None)) set_value_from_jmespath(resource_dict, self.PROPERTY_NAME, s3_path_url) diff --git a/samcli/lib/package/code_signer.py b/samcli/lib/package/code_signer.py index 92f0a78273..02434d8fb5 100644 --- a/samcli/lib/package/code_signer.py +++ b/samcli/lib/package/code_signer.py @@ -5,7 +5,7 @@ import logging from samcli.commands.exceptions import UserException -from samcli.lib.package.s3_uploader import S3Uploader +from samcli.lib.utils.s3 import parse_s3_url LOG = logging.getLogger(__name__) @@ -60,7 +60,7 @@ def sign_package(self, resource_id, s3_url, s3_version): profile_owner = signing_profile_for_resource["profile_owner"] # parse given s3 url, and extract bucket and object key - parsed_s3_url = S3Uploader.parse_s3_url(s3_url) + parsed_s3_url = parse_s3_url(s3_url) s3_bucket = parsed_s3_url["Bucket"] s3_key = parsed_s3_url["Key"] s3_target_prefix = s3_key.rsplit("/", 1)[0] + "/signed_" diff --git a/samcli/lib/package/packageable_resources.py b/samcli/lib/package/packageable_resources.py index 79458dc5bc..ca245715b5 100644 --- a/samcli/lib/package/packageable_resources.py +++ b/samcli/lib/package/packageable_resources.py @@ -51,6 +51,7 @@ RESOURCES_WITH_IMAGE_COMPONENT, RESOURCES_WITH_LOCAL_PATHS, ) +from samcli.lib.utils.s3 import parse_s3_url LOG = logging.getLogger(__name__) @@ -196,7 +197,7 @@ def get_property_value(self, resource_dict): # artifact, as deletion of intrinsic ref function artifacts is not supported yet. # TODO: Allow deletion of S3 artifacts with intrinsic ref functions. if resource_path and isinstance(resource_path, str): - return self.uploader.parse_s3_url(resource_path) + return parse_s3_url(resource_path) return {"Bucket": None, "Key": None} @@ -340,7 +341,7 @@ def do_export(self, resource_id, resource_dict, parent_dir): self.RESOURCE_TYPE, resource_id, resource_dict, self.PROPERTY_NAME, parent_dir, self.uploader ) - parsed_url = S3Uploader.parse_s3_url( + parsed_url = parse_s3_url( artifact_s3_url, bucket_name_property=self.BUCKET_NAME_PROPERTY, object_key_property=self.OBJECT_KEY_PROPERTY, diff --git a/samcli/lib/package/s3_uploader.py b/samcli/lib/package/s3_uploader.py index 5dab8c0d9a..fe141ada51 100644 --- a/samcli/lib/package/s3_uploader.py +++ b/samcli/lib/package/s3_uploader.py @@ -20,8 +20,7 @@ import sys import threading from collections import abc -from typing import Any, Dict, Optional, cast -from urllib.parse import parse_qs, urlparse +from typing import Any, Optional, cast import botocore import botocore.exceptions @@ -30,6 +29,7 @@ from samcli.commands.package.exceptions import BucketNotSpecifiedError, NoSuchBucketError from samcli.lib.package.local_files_utils import get_uploaded_s3_object_name +from samcli.lib.utils.s3 import parse_s3_url LOG = logging.getLogger(__name__) @@ -234,7 +234,7 @@ def get_version_of_artifact(self, s3_url: str) -> str: """ Returns version information of the S3 object that is given as S3 URL """ - parsed_s3_url = self.parse_s3_url(s3_url) + parsed_s3_url = parse_s3_url(s3_url) s3_bucket = parsed_s3_url["Bucket"] s3_key = parsed_s3_url["Key"] s3_object_tagging = self.s3.get_object_tagging(Bucket=s3_bucket, Key=s3_key) @@ -242,77 +242,6 @@ def get_version_of_artifact(self, s3_url: str) -> str: s3_object_version_id = s3_object_tagging["VersionId"] return cast(str, s3_object_version_id) - @staticmethod - def parse_s3_url( - url: Any, - bucket_name_property: str = "Bucket", - object_key_property: str = "Key", - version_property: Optional[str] = None, - ) -> Dict: - if isinstance(url, str) and url.startswith("s3://"): - return S3Uploader._parse_s3_format_url( - url=url, - bucket_name_property=bucket_name_property, - object_key_property=object_key_property, - version_property=version_property, - ) - - if isinstance(url, str) and url.startswith("https://s3"): - return S3Uploader._parse_path_style_s3_url( - url=url, bucket_name_property=bucket_name_property, object_key_property=object_key_property - ) - - raise ValueError("URL given to the parse method is not a valid S3 url {0}".format(url)) - - @staticmethod - def _parse_s3_format_url( - url: Any, - bucket_name_property: str = "Bucket", - object_key_property: str = "Key", - version_property: Optional[str] = None, - ) -> Dict: - """ - Method for parsing s3 urls that begin with s3:// - e.g. s3://bucket/key - """ - parsed = urlparse(url) - query = parse_qs(parsed.query) - if parsed.netloc and parsed.path: - result = dict() - result[bucket_name_property] = parsed.netloc - result[object_key_property] = parsed.path.lstrip("/") - - # If there is a query string that has a single versionId field, - # set the object version and return - if version_property is not None and "versionId" in query and len(query["versionId"]) == 1: - result[version_property] = query["versionId"][0] - - return result - - raise ValueError("URL given to the parse method is not a valid S3 url {0}".format(url)) - - @staticmethod - def _parse_path_style_s3_url( - url: Any, - bucket_name_property: str = "Bucket", - object_key_property: str = "Key", - ) -> Dict: - """ - Static method for parsing path style s3 urls. - e.g. https://s3.us-east-1.amazonaws.com/bucket/key - """ - parsed = urlparse(url) - result = dict() - # parsed.path would point to /bucket/key - if parsed.path: - s3_bucket_key = parsed.path.split("/", 2)[1:] - - result[bucket_name_property] = s3_bucket_key[0] - result[object_key_property] = s3_bucket_key[1] - - return result - raise ValueError("URL given to the parse method is not a valid S3 url {0}".format(url)) - class ProgressPercentage: # This class was copied directly from S3Transfer docs diff --git a/samcli/lib/package/utils.py b/samcli/lib/package/utils.py index 8650d3efa8..d0a1ae9787 100644 --- a/samcli/lib/package/utils.py +++ b/samcli/lib/package/utils.py @@ -26,6 +26,7 @@ from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.utils.hash import dir_checksum from samcli.lib.utils.resources import LAMBDA_LOCAL_RESOURCES +from samcli.lib.utils.s3 import parse_s3_url LOG = logging.getLogger(__name__) @@ -68,7 +69,7 @@ def is_s3_protocol_url(url): Check whether url is a valid path in the form of "s3://..." """ try: - S3Uploader.parse_s3_url(url) + parse_s3_url(url) return True except ValueError: return False diff --git a/samcli/lib/utils/s3.py b/samcli/lib/utils/s3.py new file mode 100644 index 0000000000..e841fb236c --- /dev/null +++ b/samcli/lib/utils/s3.py @@ -0,0 +1,74 @@ +"""Contains utility functions related to AWS S3 service""" +from typing import Any, Dict, Optional +from urllib.parse import parse_qs, urlparse + + +def parse_s3_url( + url: Any, + bucket_name_property: str = "Bucket", + object_key_property: str = "Key", + version_property: Optional[str] = None, +) -> Dict: + if isinstance(url, str) and url.startswith("s3://"): + return _parse_s3_format_url( + url=url, + bucket_name_property=bucket_name_property, + object_key_property=object_key_property, + version_property=version_property, + ) + + if isinstance(url, str) and url.startswith("https://s3"): + return _parse_path_style_s3_url( + url=url, bucket_name_property=bucket_name_property, object_key_property=object_key_property + ) + + raise ValueError("URL given to the parse method is not a valid S3 url {0}".format(url)) + + +def _parse_s3_format_url( + url: Any, + bucket_name_property: str = "Bucket", + object_key_property: str = "Key", + version_property: Optional[str] = None, +) -> Dict: + """ + Method for parsing s3 urls that begin with s3:// + e.g. s3://bucket/key + """ + parsed = urlparse(url) + query = parse_qs(parsed.query) + if parsed.netloc and parsed.path: + result = dict() + result[bucket_name_property] = parsed.netloc + result[object_key_property] = parsed.path.lstrip("/") + + # If there is a query string that has a single versionId field, + # set the object version and return + if version_property is not None and "versionId" in query and len(query["versionId"]) == 1: + result[version_property] = query["versionId"][0] + + return result + + raise ValueError("URL given to the parse method is not a valid S3 url {0}".format(url)) + + +def _parse_path_style_s3_url( + url: Any, + bucket_name_property: str = "Bucket", + object_key_property: str = "Key", +) -> Dict: + """ + Static method for parsing path style s3 urls. + e.g. https://s3.us-east-1.amazonaws.com/bucket/key + """ + parsed = urlparse(url) + result = dict() + # parsed.path would point to /bucket/key + if parsed.path: + s3_bucket_key = parsed.path.split("/", 2)[1:] + + result[bucket_name_property] = s3_bucket_key[0] + result[object_key_property] = s3_bucket_key[1] + + return result + raise ValueError("URL given to the parse method is not a valid S3 url {0}".format(url)) diff --git a/tests/end_to_end/test_stages.py b/tests/end_to_end/test_stages.py index 8e60211924..f405606331 100644 --- a/tests/end_to_end/test_stages.py +++ b/tests/end_to_end/test_stages.py @@ -12,6 +12,7 @@ from samcli.cli.global_config import GlobalConfig from filelock import FileLock +from samcli.lib.utils.s3 import parse_s3_url from tests.end_to_end.end_to_end_context import EndToEndTestContext from tests.testing_utils import CommandResult, run_command, run_command_with_input @@ -102,7 +103,7 @@ def _download_packaged_file(self): ) if zipped_fn_s3_loc: - s3_info = S3Uploader.parse_s3_url(zipped_fn_s3_loc) + s3_info = parse_s3_url(zipped_fn_s3_loc) self.s3_client.download_file(s3_info["Bucket"], s3_info["Key"], str(zip_file_path)) with zipfile.ZipFile(zip_file_path, "r") as zip_refzip: diff --git a/tests/unit/lib/bootstrap/companion_stack/test_companion_stack_manager.py b/tests/unit/lib/bootstrap/companion_stack/test_companion_stack_manager.py index f40fb36bd8..69f7d76ff6 100644 --- a/tests/unit/lib/bootstrap/companion_stack/test_companion_stack_manager.py +++ b/tests/unit/lib/bootstrap/companion_stack/test_companion_stack_manager.py @@ -48,8 +48,10 @@ def test_set_functions(self): @patch("samcli.lib.bootstrap.companion_stack.companion_stack_manager.mktempfile") @patch("samcli.lib.bootstrap.companion_stack.companion_stack_manager.S3Uploader") + @patch("samcli.lib.bootstrap.companion_stack.companion_stack_manager.parse_s3_url") def test_create_companion_stack( self, + parse_s3_url_mock, s3_uploader_mock, mktempfile_mock, ): @@ -70,8 +72,10 @@ def test_create_companion_stack( @patch("samcli.lib.bootstrap.companion_stack.companion_stack_manager.mktempfile") @patch("samcli.lib.bootstrap.companion_stack.companion_stack_manager.S3Uploader") + @patch("samcli.lib.bootstrap.companion_stack.companion_stack_manager.parse_s3_url") def test_update_companion_stack( self, + parse_s3_url_mock, s3_uploader_mock, mktempfile_mock, ): diff --git a/tests/unit/lib/package/test_artifact_exporter.py b/tests/unit/lib/package/test_artifact_exporter.py index 1a2e7f2227..41ac388714 100644 --- a/tests/unit/lib/package/test_artifact_exporter.py +++ b/tests/unit/lib/package/test_artifact_exporter.py @@ -19,7 +19,6 @@ AdditiveFilePermissionPermissionMapper, AdditiveDirPermissionPermissionMapper, ) -from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.package.uploaders import Destination from samcli.lib.package.utils import zip_folder, make_zip, make_zip_with_lambda_permissions, make_zip_with_permissions from samcli.lib.utils.packagetype import ZIP, IMAGE @@ -293,51 +292,6 @@ def _assert_is_valid_s3_url(self, url): def _assert_is_invalid_s3_url(self, url): self.assertFalse(is_s3_protocol_url(url), "{0} should be valid".format(url)) - def test_parse_s3_url(self): - valid = [ - {"url": "s3://foo/bar", "result": {"Bucket": "foo", "Key": "bar"}}, - {"url": "s3://foo/bar/cat/dog", "result": {"Bucket": "foo", "Key": "bar/cat/dog"}}, - { - "url": "s3://foo/bar/baz?versionId=abc¶m1=val1¶m2=val2", - "result": {"Bucket": "foo", "Key": "bar/baz", "VersionId": "abc"}, - }, - { - # VersionId is not returned if there are more than one versionId - # keys in query parameter - "url": "s3://foo/bar/baz?versionId=abc&versionId=123", - "result": {"Bucket": "foo", "Key": "bar/baz"}, - }, - { - # Path style url - "url": "https://s3-eu-west-1.amazonaws.com/bucket/key", - "result": {"Bucket": "bucket", "Key": "key"}, - }, - { - # Path style url - "url": "https://s3.us-east-1.amazonaws.com/bucket/key", - "result": {"Bucket": "bucket", "Key": "key"}, - }, - ] - - invalid = [ - # For purposes of exporter, we need S3 URLs to point to an object - # and not a bucket - "s3://foo", - "https://www.amazon.com", - "https://s3.us-east-1.amazonaws.com", - ] - - for config in valid: - result = S3Uploader.parse_s3_url( - config["url"], bucket_name_property="Bucket", object_key_property="Key", version_property="VersionId" - ) - - self.assertEqual(result, config["result"]) - - for url in invalid: - with self.assertRaises(ValueError): - S3Uploader.parse_s3_url(url) - def test_is_local_file(self): with tempfile.NamedTemporaryFile() as handle: self.assertTrue(is_local_file(handle.name)) diff --git a/tests/unit/lib/utils/test_s3.py b/tests/unit/lib/utils/test_s3.py new file mode 100644 index 0000000000..c4900b908b --- /dev/null +++ b/tests/unit/lib/utils/test_s3.py @@ -0,0 +1,49 @@ +from unittest import TestCase +from samcli.lib.utils.s3 import parse_s3_url + + +class TestS3Utils(TestCase): + def test_parse_s3_url(self): + valid = [ + {"url": "s3://foo/bar", "result": {"Bucket": "foo", "Key": "bar"}}, + {"url": "s3://foo/bar/cat/dog", "result": {"Bucket": "foo", "Key": "bar/cat/dog"}}, + { + "url": "s3://foo/bar/baz?versionId=abc¶m1=val1¶m2=val2", + "result": {"Bucket": "foo", "Key": "bar/baz", "VersionId": "abc"}, + }, + { + # VersionId is not returned if there are more than one versionId + # keys in query parameter + "url": "s3://foo/bar/baz?versionId=abc&versionId=123", + "result": {"Bucket": "foo", "Key": "bar/baz"}, + }, + { + # Path style url + "url": "https://s3-eu-west-1.amazonaws.com/bucket/key", + "result": {"Bucket": "bucket", "Key": "key"}, + }, + { + # Path style url + "url": "https://s3.us-east-1.amazonaws.com/bucket/key", + "result": {"Bucket": "bucket", "Key": "key"}, + }, + ] + + invalid = [ + # For purposes of exporter, we need S3 URLs to point to an object + # and not a bucket + "s3://foo", + "https://www.amazon.com", + "https://s3.us-east-1.amazonaws.com", + ] + + for config in valid: + result = parse_s3_url( + config["url"], bucket_name_property="Bucket", object_key_property="Key", version_property="VersionId" + ) + + self.assertEqual(result, config["result"]) + + for url in invalid: + with self.assertRaises(ValueError): + parse_s3_url(url) From 46f7e1fa1d81c4f5781ead085917a80de5a4e44f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 14:50:42 -0700 Subject: [PATCH 079/107] chore(deps): bump sympy from 1.10.1 to 1.12 in /requirements (#5338) Bumps [sympy](https://github.com/sympy/sympy) from 1.10.1 to 1.12. - [Release notes](https://github.com/sympy/sympy/releases) - [Commits](https://github.com/sympy/sympy/compare/sympy-1.10.1...sympy-1.12) --- updated-dependencies: - dependency-name: sympy dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: hnnasit <84355507+hnnasit@users.noreply.github.com> --- requirements/reproducible-mac.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index c69964a4c7..24eb610b40 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -706,9 +706,9 @@ six==1.16.0 \ # junit-xml # python-dateutil # serverlessrepo -sympy==1.10.1 \ - --hash=sha256:5939eeffdf9e152172601463626c022a2c27e75cf6278de8d401d50c9d58787b \ - --hash=sha256:df75d738930f6fe9ebe7034e59d56698f29e85f443f743e51e47df0caccc2130 +sympy==1.12 \ + --hash=sha256:c3588cd4295d0c0f603d0f2ae780587e64e2efeedb3521e46b9bb1d08d184fa5 \ + --hash=sha256:ebf595c8dac3e0fdc4152c51878b498396ec7f30e7a914d6071e674d49420fb8 # via cfn-lint text-unidecode==1.3 \ --hash=sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8 \ From 30336bcd7f420d3daf7ad70fbf7ff12e2fd19c8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 22:18:20 +0000 Subject: [PATCH 080/107] chore(deps): bump websocket-client from 1.5.1 to 1.6.1 in /requirements (#5417) Bumps [websocket-client](https://github.com/websocket-client/websocket-client) from 1.5.1 to 1.6.1. - [Release notes](https://github.com/websocket-client/websocket-client/releases) - [Changelog](https://github.com/websocket-client/websocket-client/blob/master/ChangeLog) - [Commits](https://github.com/websocket-client/websocket-client/compare/v1.5.1...v1.6.1) --- updated-dependencies: - dependency-name: websocket-client dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 6ad1ef3c90..65952aeeac 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -684,9 +684,9 @@ watchdog==2.1.2 \ --hash=sha256:d34ce2261f118ecd57eedeef95fc2a495fc4a40b3ed7b3bf0bd7a8ccc1ab4f8f \ --hash=sha256:edcd9ef3fd460bb8a98eb1fcf99941e9fd9f275f45f1a82cb1359ec92975d647 # via aws-sam-cli (setup.py) -websocket-client==1.5.1 \ - --hash=sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40 \ - --hash=sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e +websocket-client==1.6.1 \ + --hash=sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd \ + --hash=sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d # via docker werkzeug==2.2.3 \ --hash=sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe \ diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 24eb610b40..ccd42b465a 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -762,9 +762,9 @@ watchdog==2.1.2 \ --hash=sha256:d34ce2261f118ecd57eedeef95fc2a495fc4a40b3ed7b3bf0bd7a8ccc1ab4f8f \ --hash=sha256:edcd9ef3fd460bb8a98eb1fcf99941e9fd9f275f45f1a82cb1359ec92975d647 # via aws-sam-cli (setup.py) -websocket-client==1.5.1 \ - --hash=sha256:3f09e6d8230892547132177f575a4e3e73cfdf06526e20cc02aa1c3b47184d40 \ - --hash=sha256:cdf5877568b7e83aa7cf2244ab56a3213de587bbe0ce9d8b9600fc77b455d89e +websocket-client==1.6.1 \ + --hash=sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd \ + --hash=sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d # via docker werkzeug==2.2.3 \ --hash=sha256:2e1ccc9417d4da358b9de6f174e3ac094391ea1d4fbef2d667865d819dfd0afe \ From 4336c7703c5dc611f8d8728fb94766a7d78f6716 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Jun 2023 22:34:04 +0000 Subject: [PATCH 081/107] chore(deps): bump ruamel-yaml from 0.17.21 to 0.17.32 in /requirements (#5376) * chore(deps): bump ruamel-yaml from 0.17.21 to 0.17.32 in /requirements Bumps [ruamel-yaml](https://sourceforge.net/p/ruamel-yaml/code/ci/default/tree) from 0.17.21 to 0.17.32. --- updated-dependencies: - dependency-name: ruamel-yaml dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Pin ruamel-yaml-clib version --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: hnnasit <84355507+hnnasit@users.noreply.github.com> Co-authored-by: Haresh Nasit --- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 45 +++++++++++++++++++++++++++-- requirements/reproducible-mac.txt | 6 ++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/requirements/base.txt b/requirements/base.txt index 34edac3fd0..f0626e7145 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -4,7 +4,7 @@ Flask<2.3 #Need to add latest lambda changes which will return invoke mode details boto3>=1.26.109,==1.* jmespath~=1.0.1 -ruamel_yaml~=0.17.21 +ruamel_yaml~=0.17.32 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 aws-sam-translator==1.70.0 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 65952aeeac..f946536226 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -610,10 +610,49 @@ rich==13.3.3 \ --hash=sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333 \ --hash=sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15 # via aws-sam-cli (setup.py) -ruamel-yaml==0.17.21 \ - --hash=sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7 \ - --hash=sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af +ruamel-yaml==0.17.32 \ + --hash=sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447 \ + --hash=sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2 # via aws-sam-cli (setup.py) +ruamel-yaml-clib==0.2.7 \ + --hash=sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e \ + --hash=sha256:15910ef4f3e537eea7fe45f8a5d19997479940d9196f357152a09031c5be59f3 \ + --hash=sha256:184faeaec61dbaa3cace407cffc5819f7b977e75360e8d5ca19461cd851a5fc5 \ + --hash=sha256:1a6391a7cabb7641c32517539ca42cf84b87b667bad38b78d4d42dd23e957c81 \ + --hash=sha256:1f08fd5a2bea9c4180db71678e850b995d2a5f4537be0e94557668cf0f5f9497 \ + --hash=sha256:2aa261c29a5545adfef9296b7e33941f46aa5bbd21164228e833412af4c9c75f \ + --hash=sha256:3110a99e0f94a4a3470ff67fc20d3f96c25b13d24c6980ff841e82bafe827cac \ + --hash=sha256:3243f48ecd450eddadc2d11b5feb08aca941b5cd98c9b1db14b2fd128be8c697 \ + --hash=sha256:370445fd795706fd291ab00c9df38a0caed0f17a6fb46b0f607668ecb16ce763 \ + --hash=sha256:40d030e2329ce5286d6b231b8726959ebbe0404c92f0a578c0e2482182e38282 \ + --hash=sha256:41d0f1fa4c6830176eef5b276af04c89320ea616655d01327d5ce65e50575c94 \ + --hash=sha256:4a4d8d417868d68b979076a9be6a38c676eca060785abaa6709c7b31593c35d1 \ + --hash=sha256:4b3a93bb9bc662fc1f99c5c3ea8e623d8b23ad22f861eb6fce9377ac07ad6072 \ + --hash=sha256:5bc0667c1eb8f83a3752b71b9c4ba55ef7c7058ae57022dd9b29065186a113d9 \ + --hash=sha256:763d65baa3b952479c4e972669f679fe490eee058d5aa85da483ebae2009d231 \ + --hash=sha256:7bdb4c06b063f6fd55e472e201317a3bb6cdeeee5d5a38512ea5c01e1acbdd93 \ + --hash=sha256:8831a2cedcd0f0927f788c5bdf6567d9dc9cc235646a434986a852af1cb54b4b \ + --hash=sha256:91a789b4aa0097b78c93e3dc4b40040ba55bef518f84a40d4442f713b4094acb \ + --hash=sha256:92460ce908546ab69770b2e576e4f99fbb4ce6ab4b245345a3869a0a0410488f \ + --hash=sha256:99e77daab5d13a48a4054803d052ff40780278240a902b880dd37a51ba01a307 \ + --hash=sha256:9c7617df90c1365638916b98cdd9be833d31d337dbcd722485597b43c4a215bf \ + --hash=sha256:a234a20ae07e8469da311e182e70ef6b199d0fbeb6c6cc2901204dd87fb867e8 \ + --hash=sha256:a7b301ff08055d73223058b5c46c55638917f04d21577c95e00e0c4d79201a6b \ + --hash=sha256:be2a7ad8fd8f7442b24323d24ba0b56c51219513cfa45b9ada3b87b76c374d4b \ + --hash=sha256:bf9a6bc4a0221538b1a7de3ed7bca4c93c02346853f44e1cd764be0023cd3640 \ + --hash=sha256:c3ca1fbba4ae962521e5eb66d72998b51f0f4d0f608d3c0347a48e1af262efa7 \ + --hash=sha256:d000f258cf42fec2b1bbf2863c61d7b8918d31ffee905da62dede869254d3b8a \ + --hash=sha256:d5859983f26d8cd7bb5c287ef452e8aacc86501487634573d260968f753e1d71 \ + --hash=sha256:d5e51e2901ec2366b79f16c2299a03e74ba4531ddcfacc1416639c557aef0ad8 \ + --hash=sha256:da538167284de58a52109a9b89b8f6a53ff8437dd6dc26d33b57bf6699153122 \ + --hash=sha256:debc87a9516b237d0466a711b18b6ebeb17ba9f391eb7f91c649c5c4ec5006c7 \ + --hash=sha256:df5828871e6648db72d1c19b4bd24819b80a755c4541d3409f0f7acd0f335c80 \ + --hash=sha256:ecdf1a604009bd35c674b9225a8fa609e0282d9b896c03dd441a91e5f53b534e \ + --hash=sha256:efa08d63ef03d079dcae1dfe334f6c8847ba8b645d08df286358b1f5293d24ab \ + --hash=sha256:f01da5790e95815eb5a8a138508c01c758e5f5bc0ce4286c4f7028b8dd7ac3d0 \ + --hash=sha256:f34019dced51047d6f70cb9383b2ae2853b7fc4dce65129a5acd49f4f9256646 \ + --hash=sha256:f6d3d39611ac2e4f62c3128a9eed45f19a6608670c5a2f4f07f24e8de3441d38 + # via ruamel-yaml s3transfer==0.6.0 \ --hash=sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd \ --hash=sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947 diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index ccd42b465a..8ec7ddf316 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -645,9 +645,9 @@ rich==13.3.3 \ --hash=sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333 \ --hash=sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15 # via aws-sam-cli (setup.py) -ruamel-yaml==0.17.21 \ - --hash=sha256:742b35d3d665023981bd6d16b3d24248ce5df75fdb4e2924e93a05c1f8b61ca7 \ - --hash=sha256:8b7ce697a2f212752a35c1ac414471dc16c424c9573be4926b56ff3f5d23b7af +ruamel-yaml==0.17.32 \ + --hash=sha256:23cd2ed620231677564646b0c6a89d138b6822a0d78656df7abda5879ec4f447 \ + --hash=sha256:ec939063761914e14542972a5cba6d33c23b0859ab6342f61cf070cfc600efc2 # via aws-sam-cli (setup.py) ruamel-yaml-clib==0.2.7 \ --hash=sha256:045e0626baf1c52e5527bd5db361bc83180faaba2ff586e763d3d5982a876a9e \ From 359e43bb361c16edb4951a7f3d6bac5667c20a06 Mon Sep 17 00:00:00 2001 From: hnnasit <84355507+hnnasit@users.noreply.github.com> Date: Thu, 29 Jun 2023 19:17:48 -0700 Subject: [PATCH 082/107] Updated package formatter to import package options instead of deploy (#5433) Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> --- samcli/commands/package/core/formatters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/commands/package/core/formatters.py b/samcli/commands/package/core/formatters.py index 3faca8d644..5c05ddcbfd 100644 --- a/samcli/commands/package/core/formatters.py +++ b/samcli/commands/package/core/formatters.py @@ -1,6 +1,6 @@ from samcli.cli.formatters import RootCommandHelpTextFormatter from samcli.cli.row_modifiers import BaseLineRowModifier -from samcli.commands.deploy.core.options import ALL_OPTIONS +from samcli.commands.package.core.options import ALL_OPTIONS class PackageCommandHelpTextFormatter(RootCommandHelpTextFormatter): From 5e8df69d2724b17de33704c633b76c5f89cc17d7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:07:13 -0700 Subject: [PATCH 083/107] chore(deps): bump importlib-metadata in /requirements (#5437) Bumps [importlib-metadata](https://github.com/python/importlib_metadata) from 6.1.0 to 6.7.0. - [Release notes](https://github.com/python/importlib_metadata/releases) - [Changelog](https://github.com/python/importlib_metadata/blob/main/NEWS.rst) - [Commits](https://github.com/python/importlib_metadata/compare/v6.1.0...v6.7.0) --- updated-dependencies: - dependency-name: importlib-metadata dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/reproducible-mac.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index 8ec7ddf316..a1db7a41cf 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -267,9 +267,9 @@ idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests -importlib-metadata==6.1.0 \ - --hash=sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20 \ - --hash=sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09 +importlib-metadata==6.7.0 \ + --hash=sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4 \ + --hash=sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5 # via # attrs # click From 9877db23f19968d320943810c8e43602bd13612f Mon Sep 17 00:00:00 2001 From: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Date: Mon, 3 Jul 2023 11:39:52 -0700 Subject: [PATCH 084/107] feat: `sam logs` help text (#5397) * feat: `sam logs` help text * fix: make ruff happy * fix: address comments --- samcli/cli/root/command_list.py | 4 +- samcli/commands/logs/command.py | 52 ++++---- samcli/commands/logs/core/__init__.py | 0 samcli/commands/logs/core/command.py | 119 ++++++++++++++++++ samcli/commands/logs/core/formatters.py | 19 +++ samcli/commands/logs/core/options.py | 45 +++++++ tests/unit/commands/logs/core/__init__.py | 0 tests/unit/commands/logs/core/test_command.py | 73 +++++++++++ .../unit/commands/logs/core/test_formatter.py | 12 ++ tests/unit/commands/logs/core/test_options.py | 12 ++ 10 files changed, 307 insertions(+), 29 deletions(-) create mode 100644 samcli/commands/logs/core/__init__.py create mode 100644 samcli/commands/logs/core/command.py create mode 100644 samcli/commands/logs/core/formatters.py create mode 100644 samcli/commands/logs/core/options.py create mode 100644 tests/unit/commands/logs/core/__init__.py create mode 100644 tests/unit/commands/logs/core/test_command.py create mode 100644 tests/unit/commands/logs/core/test_formatter.py create mode 100644 tests/unit/commands/logs/core/test_options.py diff --git a/samcli/cli/root/command_list.py b/samcli/cli/root/command_list.py index 0be843fbe2..cfa7000739 100644 --- a/samcli/cli/root/command_list.py +++ b/samcli/cli/root/command_list.py @@ -6,11 +6,11 @@ "validate": "Validate an AWS SAM template.", "build": "Build your AWS serverless function code.", "local": "Run your AWS serverless function locally.", - "remote": "Invoke or send an event to cloud resources in your CFN stack", + "remote": "Invoke or send an event to cloud resources in your AWS Cloudformation stack.", "package": "Package an AWS SAM application.", "deploy": "Deploy an AWS SAM application.", "delete": "Delete an AWS SAM application and the artifacts created by sam deploy.", - "logs": "Fetch AWS Cloudwatch logs for a function.", + "logs": "Fetch AWS Cloudwatch logs for AWS Lambda Functions or Cloudwatch Log groups.", "publish": "Publish a packaged AWS SAM template to AWS Serverless Application Repository for easy sharing.", "traces": "Fetch AWS X-Ray traces.", "sync": "Sync an AWS SAM project to AWS.", diff --git a/samcli/commands/logs/command.py b/samcli/commands/logs/command.py index 7a3b1d8c6a..1146767a60 100644 --- a/samcli/commands/logs/command.py +++ b/samcli/commands/logs/command.py @@ -11,6 +11,7 @@ from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.command_exception_handler import command_exception_handler from samcli.commands._utils.options import common_observability_options, generate_next_command_recommendation +from samcli.commands.logs.core.command import LogsCommand from samcli.commands.logs.validation_and_exception_handlers import ( SAM_LOGS_ADDITIONAL_EXCEPTION_HANDLERS, stack_name_cw_log_group_validation, @@ -20,37 +21,34 @@ LOG = logging.getLogger(__name__) +SHORT_HELP = ( + "Fetch logs for your AWS SAM Application or AWS Cloudformation stack - Lambda Functions/CloudWatch Log groups" +) + HELP_TEXT = """ -Use this command to fetch logs generated by your Lambda function.\n -\b -When your functions are a part of a CloudFormation stack, you can fetch logs using the function's -LogicalID when you specify the stack name. -$ sam logs -n HelloWorldFunction --stack-name mystack \n -\b -Or, you can fetch logs using the function's name. -$ sam logs -n mystack-HelloWorldFunction-1FJ8PD36GML2Q \n -\b -You can view logs for a specific time range using the -s (--start-time) and -e (--end-time) options -$ sam logs -n HelloWorldFunction --stack-name mystack -s '10min ago' -e '2min ago' \n -\b -You can also add the --tail option to wait for new logs and see them as they arrive. -$ sam logs -n HelloWorldFunction --stack-name mystack --tail \n -\b -Use the --filter option to quickly find logs that match terms, phrases or values in your log events. -$ sam logs -n HelloWorldFunction --stack-name mystack --filter 'error' \n -\b -Fetch logs for all supported resources in your application, and additionally from the specified log groups. -$ sam logs --cw-log-group /aws/lambda/myfunction-123 --cw-log-group /aws/lambda/myfunction-456 -\b -You can now fetch logs from supported resources, by only providing --stack-name parameter -$ sam logs --stack-name mystack \n -\b -You can also fetch logs from a resource which is defined in a nested stack. -$ sam logs --stack-name mystack -n MyNestedStack/HelloWorldFunction +The sam logs commands fetches logs of Lambda Functions/CloudWatch log groups +with additional filtering by options. """ +DESCRIPTION = """ + Fetch logs generated by Lambda functions or other Cloudwatch log groups with additional filtering. +""" -@click.command("logs", help=HELP_TEXT, short_help="Fetch logs for a function") + +@click.command( + "logs", + short_help=SHORT_HELP, + context_settings={ + "ignore_unknown_options": False, + "allow_interspersed_args": True, + "allow_extra_args": True, + "max_content_width": 120, + }, + cls=LogsCommand, + help=HELP_TEXT, + description=DESCRIPTION, + requires_credentials=True, +) @configuration_option(provider=TomlProvider(section="parameters")) @click.option( "--name", diff --git a/samcli/commands/logs/core/__init__.py b/samcli/commands/logs/core/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samcli/commands/logs/core/command.py b/samcli/commands/logs/core/command.py new file mode 100644 index 0000000000..60b1734e50 --- /dev/null +++ b/samcli/commands/logs/core/command.py @@ -0,0 +1,119 @@ +from click import Context, style + +from samcli.cli.core.command import CoreCommand +from samcli.cli.row_modifiers import RowDefinition, ShowcaseRowModifier +from samcli.commands.logs.core.formatters import LogsCommandHelpTextFormatter +from samcli.commands.logs.core.options import OPTIONS_INFO + +COL_SIZE_MODIFIER = 38 + + +class LogsCommand(CoreCommand): + class CustomFormatterContext(Context): + formatter_class = LogsCommandHelpTextFormatter + + context_class = CustomFormatterContext + + @staticmethod + def format_examples(ctx: Context, formatter: LogsCommandHelpTextFormatter): + with formatter.indented_section(name="Examples", extra_indents=1): + with formatter.indented_section( + name="Fetch logs with Lambda Function Logical ID and Cloudformation Stack Name" + ): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style(f"$ {ctx.command_path} -n HelloWorldFunction --stack-name mystack"), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section(name="View logs for specific time range"): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"$ {ctx.command_path} -n HelloWorldFunction --stack-name mystack -s " + f"'10min ago' -e '2min ago'" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section(name="Tail new logs"): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style(f"$ {ctx.command_path} -n HelloWorldFunction --stack-name " f"mystack --tail"), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + with formatter.indented_section(name="Fetch from Cloudwatch log groups"): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"$ {ctx.command_path} --cw-log-group /aws/lambda/myfunction-123 " + f"--cw-log-group /aws/lambda/myfunction-456" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + + with formatter.indented_section(name="Fetch logs from supported resources in Cloudformation stack"): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style(f"$ {ctx.command_path} ---stack-name mystack"), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + + with formatter.indented_section(name="Fetch logs from resource defined in nested Cloudformation stack"): + formatter.write_rd( + [ + RowDefinition( + text="\n", + ), + RowDefinition( + name=style( + f"$ {ctx.command_path} ---stack-name mystack -n MyNestedStack/HelloWorldFunction" + ), + extra_row_modifiers=[ShowcaseRowModifier()], + ), + ] + ) + + def format_options(self, ctx: Context, formatter: LogsCommandHelpTextFormatter) -> None: # type:ignore + # `ignore` is put in place here for mypy even though it is the correct behavior, + # as the `formatter_class` can be set in subclass of Command. If ignore is not set, + # mypy raises argument needs to be HelpFormatter as super class defines it. + + self.format_description(formatter) + LogsCommand.format_examples(ctx, formatter) + + CoreCommand._format_options( + ctx=ctx, + params=self.get_params(ctx), + formatter=formatter, + formatting_options=OPTIONS_INFO, + write_rd_overrides={"col_max": COL_SIZE_MODIFIER}, + ) diff --git a/samcli/commands/logs/core/formatters.py b/samcli/commands/logs/core/formatters.py new file mode 100644 index 0000000000..6a35facce0 --- /dev/null +++ b/samcli/commands/logs/core/formatters.py @@ -0,0 +1,19 @@ +from samcli.cli.formatters import RootCommandHelpTextFormatter +from samcli.cli.row_modifiers import BaseLineRowModifier +from samcli.commands.logs.core.options import ALL_OPTIONS + + +class LogsCommandHelpTextFormatter(RootCommandHelpTextFormatter): + # Picked an additive constant that gives an aesthetically pleasing look. + ADDITIVE_JUSTIFICATION = 22 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Add Additional space after determining the longest option. + # However, do not justify with padding for more than half the width of + # the terminal to retain aesthetics. + self.left_justification_length = min( + max([len(option) for option in ALL_OPTIONS]) + self.ADDITIVE_JUSTIFICATION, + self.width // 2 - self.indent_increment, + ) + self.modifiers = [BaseLineRowModifier()] diff --git a/samcli/commands/logs/core/options.py b/samcli/commands/logs/core/options.py new file mode 100644 index 0000000000..c537c857e6 --- /dev/null +++ b/samcli/commands/logs/core/options.py @@ -0,0 +1,45 @@ +""" +Logs Command Options related Datastructures for formatting. +""" +from typing import Dict, List + +from samcli.cli.core.options import ALL_COMMON_OPTIONS, add_common_options_info +from samcli.cli.row_modifiers import RowDefinition + +# The ordering of the option lists matter, they are the order in which options will be displayed. + +LOG_IDENTIFIER_OPTIONS: List[str] = ["stack_name", "cw_log_group", "name"] + +# Can be used instead of the options in the first list +ADDITIONAL_OPTIONS: List[str] = ["include_traces", "filter", "output", "tail", "start_time", "end_time"] + +AWS_CREDENTIAL_OPTION_NAMES: List[str] = ["region", "profile"] + +CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] + +ALL_OPTIONS: List[str] = ( + LOG_IDENTIFIER_OPTIONS + + AWS_CREDENTIAL_OPTION_NAMES + + ADDITIONAL_OPTIONS + + CONFIGURATION_OPTION_NAMES + + ALL_COMMON_OPTIONS +) + +OPTIONS_INFO: Dict[str, Dict] = { + "Log Identifier Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(LOG_IDENTIFIER_OPTIONS)}}, + "AWS Credential Options": { + "option_names": {opt: {"rank": idx} for idx, opt in enumerate(AWS_CREDENTIAL_OPTION_NAMES)} + }, + "Additional Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(ADDITIONAL_OPTIONS)}}, + "Configuration Options": { + "option_names": {opt: {"rank": idx} for idx, opt in enumerate(CONFIGURATION_OPTION_NAMES)}, + "extras": [ + RowDefinition(name="Learn more about configuration files at:"), + RowDefinition( + name="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli" + "-config.html. " + ), + ], + }, +} +add_common_options_info(OPTIONS_INFO) diff --git a/tests/unit/commands/logs/core/__init__.py b/tests/unit/commands/logs/core/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/unit/commands/logs/core/test_command.py b/tests/unit/commands/logs/core/test_command.py new file mode 100644 index 0000000000..5b7b4e334e --- /dev/null +++ b/tests/unit/commands/logs/core/test_command.py @@ -0,0 +1,73 @@ +import unittest +from unittest.mock import Mock, patch +from samcli.commands.logs.core.command import LogsCommand +from samcli.commands.logs.command import DESCRIPTION +from tests.unit.cli.test_command import MockFormatter + + +class MockParams: + def __init__(self, rv, name): + self.rv = rv + self.name = name + + def get_help_record(self, ctx): + return self.rv + + +class TestLogsCommand(unittest.TestCase): + @patch.object(LogsCommand, "get_params") + def test_get_options_logs_command_text(self, mock_get_params): + ctx = Mock() + ctx.command_path = "sam logs" + ctx.parent.command_path = "sam" + formatter = MockFormatter(scrub_text=True) + # NOTE(sriram-mv): One option per option section. + mock_get_params.return_value = [ + MockParams(rv=("--region", "Region"), name="region"), + MockParams(rv=("--debug", ""), name="debug"), + MockParams(rv=("--config-file", ""), name="config_file"), + MockParams(rv=("--stack-name", ""), name="stack_name"), + MockParams(rv=("--tail", ""), name="tail"), + MockParams(rv=("--beta-features", ""), name="beta_features"), + ] + + cmd = LogsCommand(name="logs", requires_credentials=True, description=DESCRIPTION) + expected_output = { + "AWS Credential Options": [("", ""), ("--region", ""), ("", "")], + "Additional Options": [("", ""), ("--tail", ""), ("", "")], + "Beta Options": [("", ""), ("--beta-features", ""), ("", "")], + "Configuration Options": [("", ""), ("--config-file", ""), ("", "")], + "Description": [(cmd.description + cmd.description_addendum, "")], + "Examples": [], + "Fetch from Cloudwatch log groups": [ + ("", ""), + ( + "$ sam logs --cw-log-group " + "/aws/lambda/myfunction-123 " + "--cw-log-group " + "/aws/lambda/myfunction-456\x1b[0m", + "", + ), + ], + "Fetch logs from resource defined in nested Cloudformation stack": [ + ("", ""), + ("$ sam " "logs " "---stack-name " "mystack " "-n " "MyNestedStack/HelloWorldFunction\x1b[0m", ""), + ], + "Fetch logs from supported resources in Cloudformation stack": [ + ("", ""), + ("$ sam logs " "---stack-name " "mystack\x1b[0m", ""), + ], + "Fetch logs with Lambda Function Logical ID and Cloudformation Stack Name": [ + ("", ""), + ("$ " "sam " "logs " "-n " "HelloWorldFunction " "--stack-name " "mystack\x1b[0m", ""), + ], + "Log Identifier Options": [("", ""), ("--stack-name", ""), ("", "")], + "Other Options": [("", ""), ("--debug", ""), ("", "")], + "Tail new logs": [("", ""), ("$ sam logs -n HelloWorldFunction --stack-name mystack " "--tail\x1b[0m", "")], + "View logs for specific time range": [ + ("", ""), + ("$ sam logs -n HelloWorldFunction " "--stack-name mystack -s '10min ago' " "-e '2min ago'\x1b[0m", ""), + ], + } + cmd.format_options(ctx, formatter) + self.assertEqual(formatter.data, expected_output) diff --git a/tests/unit/commands/logs/core/test_formatter.py b/tests/unit/commands/logs/core/test_formatter.py new file mode 100644 index 0000000000..e59e90207b --- /dev/null +++ b/tests/unit/commands/logs/core/test_formatter.py @@ -0,0 +1,12 @@ +from shutil import get_terminal_size +from unittest import TestCase + +from samcli.cli.row_modifiers import BaseLineRowModifier +from samcli.commands.logs.core.formatters import LogsCommandHelpTextFormatter + + +class TestLogsCommandHelpTextFormatter(TestCase): + def test_logs_formatter(self): + self.formatter = LogsCommandHelpTextFormatter() + self.assertTrue(self.formatter.left_justification_length <= get_terminal_size().columns // 2) + self.assertIsInstance(self.formatter.modifiers[0], BaseLineRowModifier) diff --git a/tests/unit/commands/logs/core/test_options.py b/tests/unit/commands/logs/core/test_options.py new file mode 100644 index 0000000000..4b2acd844e --- /dev/null +++ b/tests/unit/commands/logs/core/test_options.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from click import Option + +from samcli.commands.logs.command import cli +from samcli.commands.logs.core.options import ALL_OPTIONS + + +class TestOptions(TestCase): + def test_all_options_formatted(self): + command_options = [param.human_readable_name if isinstance(param, Option) else None for param in cli.params] + self.assertEqual(sorted(ALL_OPTIONS), sorted(filter(lambda item: item is not None, command_options + ["help"]))) From c53db02e84e6565a92fcff66c9f5669b53838c85 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Mon, 3 Jul 2023 12:02:34 -0700 Subject: [PATCH 085/107] feat: enable terraform support for local start-api command (#5389) * feat: Enable hook-name and skip-prepare-infra flagf for sam local start-api (#5217) * Enable hook-name flag for sam local start-api * Format files * test: Terraform local start-api integration tests base (#5240) * feat: update SAM CLI with latest App Templates commit hash (#5211) * feat: updating app templates repo hash with (a34f563f067e13df3eb350d36461b99397b6cda6) * dummy change to trigger checks * revert dummy commit --------- Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * Enable hook-name flag for sam local start-api * Format files * fix: fix failing Terraform integration test cases (#5218) * fix: fix the failing terraform integration test cases * fix: fix the resource address while accessing the module config resources * fix: fix checking the experimental log integration test cases * chore: bump version to 1.85.0 (#5226) * chore: use the SAR Application created in testing accounts (#5221) * chore: update aws_lambda_builders to 1.32.0 (#5215) Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * feat: Added linking Gateway Method to Lambda Authorizer (#5228) * Added linking method to authorizer * Fixed docstring spelling mistake --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * feat: Return early during linking if no destination resources are found (#5220) * Returns during linking if no destination resources are found * Updated comment to correctly reflect state * Cleaned extra word --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * chore: Strengthen wording on "no Auth" during deploy (#5231) Co-authored-by: Jacob Fuss Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> * feat: Link Lambda Authorizer to Rest API (#5219) * Link RestApiId property for Lambda Authorizers * Updated docstring * Format files --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * Terraform start-api integration tests * Add test files * Uncomment skip --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Jacob Fuss Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> * feat: Added OpenApi body integration testing and updated property builder (#5291) * Added OpenApi body integration testing and updated property builder * Added more test cases * Changed tearDown to tearDownClass * Updated JSON body parser to handle parsing errors and added unit tests * Removed V1 references * feat: Terraform Authorizer resource testing (#5270) * Added authorizer project * Added project files * Removed extra print * Add request based authorizer testing * test: Test the unsupported limitations for local start api (#5309) * test: Test the unsupported limitations for local start api * fix lint issues * apply pr comments * fix: Bug Bash UX Issues (#5387) * Fix bug bash UX issues * Fix beta warning printing extra characters * Fix authorizer logging --------- Co-authored-by: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Jacob Fuss Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> --- samcli/commands/_utils/experimental.py | 4 +- samcli/commands/local/start_api/cli.py | 40 ++- .../commands/local/start_api/core/options.py | 4 + .../hooks/prepare/property_builder.py | 36 ++- .../hooks/prepare/resources/apigw.py | 2 +- .../terraform/hooks/prepare/translate.py | 11 +- samcli/lib/providers/api_collector.py | 4 +- .../local/start_api/start_api_integ_base.py | 12 +- ...st_start_api_with_terraform_application.py | 268 ++++++++++++++++++ .../lambda-auth-openapi/lambda-functions.zip | Bin 0 -> 551 bytes .../terraform/lambda-auth-openapi/main.tf | 112 ++++++++ .../HelloWorldFunction.zip | Bin 0 -> 1079 bytes .../main.tf | 136 +++++++++ .../HelloWorldFunction.zip | Bin 0 -> 1079 bytes .../main.tf | 138 +++++++++ .../HelloWorldFunction.zip | Bin 0 -> 1079 bytes .../terraform/terraform-v1-api-simple/main.tf | 112 ++++++++ .../v1-lambda-authorizer/lambda-functions.zip | Bin 0 -> 551 bytes .../terraform/v1-lambda-authorizer/main.tf | 139 +++++++++ .../local/start_api/core/test_command.py | 2 + .../unit/commands/local/start_api/test_cli.py | 3 + .../unit/commands/samconfig/test_samconfig.py | 1 + .../hooks/prepare/resources/test_apigw.py | 1 + .../hooks/prepare/test_property_builder.py | 20 ++ .../terraform/hooks/prepare/test_translate.py | 11 +- 25 files changed, 1032 insertions(+), 24 deletions(-) create mode 100644 tests/integration/local/start_api/test_start_api_with_terraform_application.py create mode 100644 tests/integration/testdata/start_api/terraform/lambda-auth-openapi/lambda-functions.zip create mode 100644 tests/integration/testdata/start_api/terraform/lambda-auth-openapi/main.tf create mode 100644 tests/integration/testdata/start_api/terraform/terraform-api-simple-local-variables-limitation/HelloWorldFunction.zip create mode 100644 tests/integration/testdata/start_api/terraform/terraform-api-simple-local-variables-limitation/main.tf create mode 100644 tests/integration/testdata/start_api/terraform/terraform-api-simple-multiple-resources-limitation/HelloWorldFunction.zip create mode 100644 tests/integration/testdata/start_api/terraform/terraform-api-simple-multiple-resources-limitation/main.tf create mode 100644 tests/integration/testdata/start_api/terraform/terraform-v1-api-simple/HelloWorldFunction.zip create mode 100644 tests/integration/testdata/start_api/terraform/terraform-v1-api-simple/main.tf create mode 100644 tests/integration/testdata/start_api/terraform/v1-lambda-authorizer/lambda-functions.zip create mode 100644 tests/integration/testdata/start_api/terraform/v1-lambda-authorizer/main.tf diff --git a/samcli/commands/_utils/experimental.py b/samcli/commands/_utils/experimental.py index 240c5b80da..b8b75570c1 100644 --- a/samcli/commands/_utils/experimental.py +++ b/samcli/commands/_utils/experimental.py @@ -10,7 +10,7 @@ from samcli.cli.context import Context from samcli.cli.global_config import ConfigEntry, GlobalConfig from samcli.commands._utils.parameterized_option import parameterized_option -from samcli.lib.utils.colors import Colored +from samcli.lib.utils.colors import Colored, Colors LOG = logging.getLogger(__name__) @@ -162,7 +162,7 @@ def update_experimental_context(show_warning=True): if not Context.get_current_context().experimental: Context.get_current_context().experimental = True if show_warning: - LOG.warning(Colored().yellow(EXPERIMENTAL_WARNING)) + LOG.warning(Colored().color_log(EXPERIMENTAL_WARNING, color=Colors.WARNING), extra=dict(markup=True)) def _experimental_option_callback(ctx, param, enabled: Optional[bool]): diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index 145b0a58a2..9de4d7982c 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -9,8 +9,13 @@ from samcli.cli.cli_config_file import TomlProvider, configuration_option from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options +from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled from samcli.commands._utils.option_value_processor import process_image_options -from samcli.commands._utils.options import generate_next_command_recommendation +from samcli.commands._utils.options import ( + generate_next_command_recommendation, + hook_name_click_option, + skip_prepare_infra_option, +) from samcli.commands.local.cli_common.options import ( invoke_common_options, local_common_options, @@ -54,6 +59,10 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=TomlProvider(section="parameters")) +@hook_name_click_option( + force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] +) +@skip_prepare_infra_option @service_common_options(3000) @click.option( "--static-dir", @@ -98,6 +107,8 @@ def cli( container_host, container_host_interface, invoke_image, + hook_name, + skip_prepare_infra, ): """ `sam local start-api` command entry point @@ -128,6 +139,7 @@ def cli( container_host, container_host_interface, invoke_image, + hook_name, ) # pragma: no cover @@ -155,6 +167,7 @@ def do_cli( # pylint: disable=R0914 container_host, container_host_interface, invoke_image, + hook_name, ): """ Implementation of the ``cli`` method, just separated out for unit testing purposes @@ -170,6 +183,14 @@ def do_cli( # pylint: disable=R0914 LOG.debug("local start-api command is called") + if ( + hook_name + and ExperimentalFlag.IaCsSupport.get(hook_name) is not None + and not is_experimental_enabled(ExperimentalFlag.IaCsSupport.get(hook_name)) + ): + LOG.info("Terraform Support beta feature is not enabled.") + return + processed_invoke_images = process_image_options(invoke_image) # Pass all inputs to setup necessary context to invoke function locally. @@ -202,14 +223,15 @@ def do_cli( # pylint: disable=R0914 ) as invoke_context: service = LocalApiService(lambda_invoke_context=invoke_context, port=port, host=host, static_dir=static_dir) service.start() - command_suggestions = generate_next_command_recommendation( - [ - ("Validate SAM template", "sam validate"), - ("Test Function in the Cloud", "sam sync --stack-name {{stack-name}} --watch"), - ("Deploy", "sam deploy --guided"), - ] - ) - click.secho(command_suggestions, fg="yellow") + if not hook_name: + command_suggestions = generate_next_command_recommendation( + [ + ("Validate SAM template", "sam validate"), + ("Test Function in the Cloud", "sam sync --stack-name {{stack-name}} --watch"), + ("Deploy", "sam deploy --guided"), + ] + ) + click.secho(command_suggestions, fg="yellow") except NoApisDefined as ex: raise UserException( diff --git a/samcli/commands/local/start_api/core/options.py b/samcli/commands/local/start_api/core/options.py index d9b89145e0..21bb1bf822 100644 --- a/samcli/commands/local/start_api/core/options.py +++ b/samcli/commands/local/start_api/core/options.py @@ -17,6 +17,8 @@ "parameter_overrides", ] +EXTENSION_OPTIONS: List[str] = ["hook_name", "skip_prepare_infra"] + CONTAINER_OPTION_NAMES: List[str] = [ "host", "port", @@ -53,6 +55,7 @@ + ARTIFACT_LOCATION_OPTIONS + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS + + EXTENSION_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -65,6 +68,7 @@ "Artifact Location Options": { "option_names": {opt: {"rank": idx} for idx, opt in enumerate(ARTIFACT_LOCATION_OPTIONS)} }, + "Extension Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(EXTENSION_OPTIONS)}}, "Configuration Options": { "option_names": {opt: {"rank": idx} for idx, opt in enumerate(CONFIGURATION_OPTION_NAMES)}, "extras": [ diff --git a/samcli/hook_packages/terraform/hooks/prepare/property_builder.py b/samcli/hook_packages/terraform/hooks/prepare/property_builder.py index add29c68b2..a8910f112a 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/property_builder.py +++ b/samcli/hook_packages/terraform/hooks/prepare/property_builder.py @@ -1,6 +1,9 @@ """ Terraform prepare property builder """ +import logging +from json import loads +from json.decoder import JSONDecodeError from typing import Any, Dict, Optional from samcli.hook_packages.terraform.hooks.prepare.resource_linking import _resolve_resource_attribute @@ -24,6 +27,8 @@ from samcli.lib.utils.resources import AWS_LAMBDA_FUNCTION as CFN_AWS_LAMBDA_FUNCTION from samcli.lib.utils.resources import AWS_LAMBDA_LAYERVERSION as CFN_AWS_LAMBDA_LAYER_VERSION +LOG = logging.getLogger(__name__) + REMOTE_DUMMY_VALUE = "<>" TF_AWS_LAMBDA_FUNCTION = "aws_lambda_function" TF_AWS_LAMBDA_LAYER_VERSION = "aws_lambda_layer_version" @@ -211,6 +216,35 @@ def _check_image_config_value(image_config: Any) -> bool: return True +def _get_json_body(tf_properties: dict, resource: TFResource) -> Any: + """ + Gets the JSON formatted body value from the API Gateway if there is one + + Parameters + ---------- + tf_properties: dict + Properties of the terraform AWS Lambda function resource + resource: TFResource + Configuration terraform resource + + Returns + ------- + Any + Returns a dictonary if there is a valid body to parse, otherwise return original value + """ + body = tf_properties.get("body") + + if isinstance(body, str): + try: + return loads(body) + except JSONDecodeError: + pass + + LOG.debug(f"Failed to load JSON body for API Gateway body, returning original value: '{body}'") + + return body + + AWS_LAMBDA_FUNCTION_PROPERTY_BUILDER_MAPPING: PropertyBuilderMapping = { "FunctionName": _get_property_extractor("function_name"), "Architectures": _get_property_extractor("architectures"), @@ -234,7 +268,7 @@ def _check_image_config_value(image_config: Any) -> bool: AWS_API_GATEWAY_REST_API_PROPERTY_BUILDER_MAPPING: PropertyBuilderMapping = { "Name": _get_property_extractor("name"), - "Body": _get_property_extractor("body"), + "Body": _get_json_body, "Parameters": _get_property_extractor("parameters"), "BinaryMediaTypes": _get_property_extractor("binary_media_types"), } diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py b/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py index 05f1676624..356c744e12 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/apigw.py @@ -66,7 +66,7 @@ def _unsupported_reference_field(field: str, resource: Dict, config_resource: TF False otherwise """ return bool( - not resource.get(field) + not (resource.get(field) or resource.get("values", {}).get(field)) and config_resource.attributes.get(field) and isinstance(config_resource.attributes.get(field), References) ) diff --git a/samcli/hook_packages/terraform/hooks/prepare/translate.py b/samcli/hook_packages/terraform/hooks/prepare/translate.py index 62f8d4da12..14f9e73733 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/translate.py +++ b/samcli/hook_packages/terraform/hooks/prepare/translate.py @@ -52,7 +52,7 @@ get_sam_metadata_planned_resource_value_attribute, ) from samcli.lib.hook.exceptions import PrepareHookException -from samcli.lib.utils.colors import Colored +from samcli.lib.utils.colors import Colored, Colors from samcli.lib.utils.resources import AWS_LAMBDA_FUNCTION as CFN_AWS_LAMBDA_FUNCTION SAM_METADATA_RESOURCE_TYPE = "null_resource" @@ -134,9 +134,12 @@ def _check_unresolvable_values(root_module: dict, root_tf_module: TFModule) -> N if config_values and not planned_values: LOG.warning( - Colored().yellow( - "\nUnresolvable attributes discovered in project, run terraform apply to resolve them.\n" - ) + Colored().color_log( + msg="\nUnresolvable attributes discovered in project, " + "run terraform apply to resolve them.\n", + color=Colors.WARNING, + ), + extra=dict(markup=True), ) return diff --git a/samcli/lib/providers/api_collector.py b/samcli/lib/providers/api_collector.py index d0c0f5b2a8..7cb5d0c1d1 100644 --- a/samcli/lib/providers/api_collector.py +++ b/samcli/lib/providers/api_collector.py @@ -9,7 +9,7 @@ from typing import Dict, Iterator, List, Optional, Set, Tuple, Union from samcli.lib.providers.provider import Api, Cors -from samcli.lib.utils.colors import Colored +from samcli.lib.utils.colors import Colored, Colors from samcli.local.apigw.authorizers.authorizer import Authorizer from samcli.local.apigw.route import Route @@ -197,7 +197,7 @@ def get_api(self) -> Api: be validated thoroughly before deploying to production. Testing application behaviour against authorizers deployed on AWS can be done using the sam sync command.{os.linesep}""" - LOG.warning(Colored().yellow(message)) + LOG.warning(Colored().color_log(message, color=Colors.WARNING), extra=dict(markup=True)) break diff --git a/tests/integration/local/start_api/start_api_integ_base.py b/tests/integration/local/start_api/start_api_integ_base.py index b1f9a6a785..77f755aec8 100644 --- a/tests/integration/local/start_api/start_api_integ_base.py +++ b/tests/integration/local/start_api/start_api_integ_base.py @@ -33,6 +33,9 @@ class StartApiIntegBaseClass(TestCase): do_collect_cmd_init_output: bool = False + command_list = None + project_directory = None + @classmethod def setUpClass(cls): # This is the directory for tests/integration which will be used to file the testdata @@ -84,7 +87,8 @@ def start_api_with_retry(cls, retries=3): def start_api(cls): command = get_sam_command() - command_list = [command, "local", "start-api", "-t", cls.template, "-p", cls.port] + command_list = cls.command_list or [command, "local", "start-api", "-t", cls.template] + command_list.extend(["-p", cls.port]) if cls.container_mode: command_list += ["--warm-containers", cls.container_mode] @@ -99,7 +103,11 @@ def start_api(cls): for image in cls.invoke_image: command_list += ["--invoke-image", image] - cls.start_api_process = Popen(command_list, stderr=PIPE, stdout=PIPE) + cls.start_api_process = ( + Popen(command_list, stderr=PIPE, stdout=PIPE) + if not cls.project_directory + else Popen(command_list, stderr=PIPE, stdout=PIPE, cwd=cls.project_directory) + ) cls.start_api_process_output = wait_for_local_process( cls.start_api_process, cls.port, collect_output=cls.do_collect_cmd_init_output ) diff --git a/tests/integration/local/start_api/test_start_api_with_terraform_application.py b/tests/integration/local/start_api/test_start_api_with_terraform_application.py new file mode 100644 index 0000000000..e8ab21d01d --- /dev/null +++ b/tests/integration/local/start_api/test_start_api_with_terraform_application.py @@ -0,0 +1,268 @@ +import logging +import shutil +import os +from pathlib import Path +from subprocess import CalledProcessError, CompletedProcess, run +from typing import Optional +from unittest import skipIf +from parameterized import parameterized, parameterized_class + +import pytest +import requests + +from tests.integration.local.common_utils import random_port +from tests.integration.local.start_api.start_api_integ_base import StartApiIntegBaseClass +from tests.testing_utils import get_sam_command, CI_OVERRIDE + +LOG = logging.getLogger(__name__) + + +class TerraformStartApiIntegrationBase(StartApiIntegBaseClass): + run_command_timeout = 300 + terraform_application: Optional[str] = None + + @classmethod + def setUpClass(cls): + command = get_sam_command() + cls.template_path = "" + cls.build_before_invoke = False + cls.command_list = [command, "local", "start-api", "--hook-name", "terraform", "--beta-features"] + cls.test_data_path = Path(cls.get_integ_dir()) / "testdata" / "start_api" + cls.project_directory = cls.test_data_path / "terraform" / cls.terraform_application + super(TerraformStartApiIntegrationBase, cls).setUpClass() + + @staticmethod + def get_integ_dir(): + return Path(__file__).resolve().parents[2] + + @classmethod + def tearDownClass(cls) -> None: + super(TerraformStartApiIntegrationBase, cls).tearDownClass() + cls._remove_generated_directories() + + @classmethod + def _remove_generated_directories(cls): + shutil.rmtree(str(Path(cls.project_directory / ".aws-sam-iacs")), ignore_errors=True) + shutil.rmtree(str(Path(cls.project_directory / ".terraform")), ignore_errors=True) + try: + os.remove(str(Path(cls.project_directory / ".terraform.lock.hcl"))) + except (FileNotFoundError, PermissionError): + pass + + @classmethod + def _run_command(cls, command, check) -> CompletedProcess: + test_data_folder = ( + Path(cls.get_integ_dir()) / "testdata" / "start_api" / "terraform" / cls.terraform_application # type: ignore + ) + return run(command, cwd=test_data_folder, check=check, capture_output=True, timeout=cls.run_command_timeout) + + +class TerraformStartApiIntegrationApplyBase(TerraformStartApiIntegrationBase): + terraform_application: str + + @classmethod + def setUpClass(cls): + # init terraform project to populate deploy-only values + cls._run_command(["terraform", "init", "-input=false"], check=True) + cls._run_command(["terraform", "apply", "-auto-approve", "-input=false"], check=True) + + super(TerraformStartApiIntegrationApplyBase, cls).setUpClass() + + @staticmethod + def get_integ_dir(): + return Path(__file__).resolve().parents[2] + + @classmethod + def tearDownClass(cls) -> None: + try: + cls._run_command(["terraform", "apply", "-destroy", "-auto-approve", "-input=false"], check=True) + except CalledProcessError: + # skip, command can fail here if there isn't an applied project to destroy + # (eg. failed to apply in setup) + pass + + try: + os.remove(str(Path(cls.project_directory / "terraform.tfstate"))) # type: ignore + os.remove(str(Path(cls.project_directory / "terraform.tfstate.backup"))) # type: ignore + except (FileNotFoundError, PermissionError): + pass + + super(TerraformStartApiIntegrationApplyBase, cls).tearDownClass() + + +@skipIf( + not CI_OVERRIDE, + "Skip Terraform test cases unless running in CI", +) +@pytest.mark.flaky(reruns=3) +class TestStartApiTerraformApplication(TerraformStartApiIntegrationBase): + terraform_application = "terraform-v1-api-simple" + + def setUp(self): + self.url = "http://127.0.0.1:{}".format(self.port) + + def test_successful_request(self): + response = requests.get(self.url + "/hello", timeout=300) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"message": "hello world"}) + + +@skipIf( + not CI_OVERRIDE, + "Skip Terraform test cases unless running in CI", +) +@pytest.mark.flaky(reruns=3) +@parameterized_class( + [ + { + "terraform_application": "lambda-auth-openapi", + "expected_error_message": "Error: AWS SAM CLI is unable to process a Terraform project that uses an OpenAPI" + " specification to define the API Gateway resource.", + }, + { + "terraform_application": "terraform-api-simple-multiple-resources-limitation", + "expected_error_message": "Error: AWS SAM CLI could not process a Terraform project that contains a source " + "resource that is linked to more than one destination resource.", + }, + { + "terraform_application": "terraform-api-simple-local-variables-limitation", + "expected_error_message": "Error: AWS SAM CLI could not process a Terraform project that uses local " + "variables to define linked resources.", + }, + ] +) +class TestStartApiTerraformApplicationLimitations(TerraformStartApiIntegrationBase): + @classmethod + def setUpClass(cls): + command = get_sam_command() + cls.command_list = [ + command, + "local", + "start-api", + "--hook-name", + "terraform", + "--beta-features", + "-p", + str(random_port()), + ] + cls.test_data_path = Path(cls.get_integ_dir()) / "testdata" / "start_api" + cls.project_directory = cls.test_data_path / "terraform" / cls.terraform_application + + @classmethod + def tearDownClass(cls) -> None: + cls._remove_generated_directories() + + def test_unsupported_limitations(self): + apply_disclaimer_message = "Unresolvable attributes discovered in project, run terraform apply to resolve them." + + process = self._run_command(self.command_list, check=False) + + LOG.info(process.stderr) + output = process.stderr.decode("utf-8") + self.assertEqual(process.returncode, 1) + self.assertRegex(output, self.expected_error_message) + self.assertRegex(output, apply_disclaimer_message) + + +@skipIf( + not CI_OVERRIDE, + "Skip Terraform test cases unless running in CI", +) +@pytest.mark.flaky(reruns=3) +@parameterized_class( + [ + { + "terraform_application": "terraform-api-simple-multiple-resources-limitation", + }, + { + "terraform_application": "terraform-api-simple-local-variables-limitation", + }, + ] +) +class TestStartApiTerraformApplicationLimitationsAfterApply(TerraformStartApiIntegrationApplyBase): + def setUp(self): + self.url = "http://127.0.0.1:{}".format(self.port) + + def test_successful_request(self): + response = requests.get(self.url + "/hello", timeout=300) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"message": "hello world"}) + + +@skipIf( + not CI_OVERRIDE, + "Skip Terraform test cases unless running in CI", +) +@pytest.mark.flaky(reruns=3) +class TestStartApiTerraformApplicationV1LambdaAuthorizers(TerraformStartApiIntegrationBase): + terraform_application = "v1-lambda-authorizer" + + def setUp(self): + self.url = "http://127.0.0.1:{}".format(self.port) + + @parameterized.expand( + [ + ("/hello", {"headers": {"myheader": "123"}}), + ("/hello-request", {"headers": {"myheader": "123"}, "params": {"mystring": "456"}}), + ("/hello-request-empty", {}), + ("/hello-request-empty", {"headers": {"foo": "bar"}}), + ] + ) + def test_invoke_authorizer(self, endpoint, parameters): + response = requests.get(self.url + endpoint, timeout=300, **parameters) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"message": "from authorizer"}) + + @parameterized.expand( + [ + ("/hello", {"headers": {"blank": "invalid"}}), + ("/hello-request", {"headers": {"blank": "invalid"}, "params": {"blank": "invalid"}}), + ] + ) + def test_missing_authorizer_identity_source(self, endpoint, parameters): + response = requests.get(self.url + endpoint, timeout=300, **parameters) + + self.assertEqual(response.status_code, 401) + + def test_fails_token_header_validation_authorizer(self): + response = requests.get(self.url + "/hello", timeout=300, headers={"myheader": "not valid"}) + + self.assertEqual(response.status_code, 401) + + +@skipIf( + not CI_OVERRIDE, + "Skip Terraform test cases unless running in CI", +) +@pytest.mark.flaky(reruns=3) +class TestStartApiTerraformApplicationOpenApiAuthorizer(TerraformStartApiIntegrationApplyBase): + terraform_application = "lambda-auth-openapi" + + def setUp(self): + self.url = "http://127.0.0.1:{}".format(self.port) + + @parameterized.expand( + [ + ("/hello", {"headers": {"myheader": "123"}}), + ("/hello-request", {"headers": {"myheader": "123"}, "params": {"mystring": "456"}}), + ] + ) + def test_successful_request(self, endpoint, params): + response = requests.get(self.url + endpoint, timeout=300, **params) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"message": "from authorizer"}) + + @parameterized.expand( + [ + ("/hello", {"headers": {"missin": "123"}}), + ("/hello-request", {"headers": {"notcorrect": "123"}, "params": {"abcde": "456"}}), + ] + ) + def test_missing_identity_sources(self, endpoint, params): + response = requests.get(self.url + endpoint, timeout=300, **params) + + self.assertEqual(response.status_code, 401) diff --git a/tests/integration/testdata/start_api/terraform/lambda-auth-openapi/lambda-functions.zip b/tests/integration/testdata/start_api/terraform/lambda-auth-openapi/lambda-functions.zip new file mode 100644 index 0000000000000000000000000000000000000000..36c26446344af50d1215621f24af2bff75856930 GIT binary patch literal 551 zcmWIWW@Zs#-~d9t+CyOsP@u%jz`)I*z>txcmy(lORIFD}85+XNz((kZ=$e!ogI`e0(%A72s5%DNMly7eVLnpV>iPE@R4oU3# zu^KoQ?CfWD~ErxLp0T@O^Yry3(yF zCr{n`dm?4$t2v=}=Y5O1>-L5}z?+?eUHjgqcwl&gVkf|xkx7IZ5p&3LpqPV!C5<2! X3Go=<&B_MS!U%+sKzawzLIwr^eU{QQ literal 0 HcmV?d00001 diff --git a/tests/integration/testdata/start_api/terraform/lambda-auth-openapi/main.tf b/tests/integration/testdata/start_api/terraform/lambda-auth-openapi/main.tf new file mode 100644 index 0000000000..8005fb4e95 --- /dev/null +++ b/tests/integration/testdata/start_api/terraform/lambda-auth-openapi/main.tf @@ -0,0 +1,112 @@ +provider "aws" {} + +data "aws_region" "current" {} + +resource "aws_api_gateway_authorizer" "header_authorizer" { + name = "header-authorizer-open-api" + rest_api_id = aws_api_gateway_rest_api.api.id + authorizer_uri = aws_lambda_function.authorizer.invoke_arn + authorizer_credentials = aws_iam_role.invocation_role.arn + identity_source = "method.request.header.myheader" + identity_validation_expression = "^123$" +} + +resource "aws_lambda_function" "authorizer" { + filename = "lambda-functions.zip" + function_name = "authorizer-open-api" + role = aws_iam_role.invocation_role.arn + handler = "handlers.auth_handler" + runtime = "python3.8" + source_code_hash = filebase64sha256("lambda-functions.zip") +} + +resource "aws_lambda_function" "hello_endpoint" { + filename = "lambda-functions.zip" + function_name = "hello-lambda-open-api" + role = aws_iam_role.invocation_role.arn + handler = "handlers.hello_handler" + runtime = "python3.8" + source_code_hash = filebase64sha256("lambda-functions.zip") +} + +resource "aws_api_gateway_rest_api" "api" { + name = "api-open-api" + body = jsonencode({ + swagger = "2.0" + info = { + title = "api-body" + version = "1.0" + } + securityDefinitions = { + TokenAuthorizer = { + type = "apiKey" + in = "header" + name = "myheader" + x-amazon-apigateway-authtype = "custom" + x-amazon-apigateway-authorizer = { + type = "TOKEN" + authorizerUri = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/${aws_lambda_function.authorizer.arn}/invocations" + } + } + RequestAuthorizer = { + type = "apiKey" + in = "unused" + name = "unused" + x-amazon-apigateway-authtype = "custom" + x-amazon-apigateway-authorizer = { + type = "REQUEST" + identitySource = "method.request.header.myheader, method.request.querystring.mystring" + authorizerUri = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/${aws_lambda_function.authorizer.arn}/invocations" + } + } + } + paths = { + "/hello" = { + get = { + security = [ + {TokenAuthorizer = []} + ] + x-amazon-apigateway-integration = { + httpMethod = "GET" + payloadFormatVersion = "1.0" + type = "AWS_PROXY" + uri = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/${aws_lambda_function.hello_endpoint.arn}/invocations" + } + } + } + "/hello-request" = { + get = { + security = [ + {RequestAuthorizer = []} + ] + x-amazon-apigateway-integration = { + httpMethod = "GET" + payloadFormatVersion = "1.0" + type = "AWS_PROXY" + uri = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/${aws_lambda_function.hello_endpoint.arn}/invocations" + } + } + } + } + }) +} + +resource "aws_iam_role" "invocation_role" { + name = "iam-lambda-open-api" + path = "/" + assume_role_policy = <d% zj=rr_mB#73$cT&^d&Rnvd~@F3nr0UsKKwd(1On*(k|vmU$lC-Lv;C$Aq$o-7pq&E{M-q~^XxoMvJEv_l+m@7jTa9;hPw$6Vy`wtz-{!cZE_a|D; zeVDjm-otwV`j)dFmQUoFvQj%^=GxV#jB;WW`yak~>TfKv^`&=a>12=7dUs@}Yn^z} zFu?8)zcdJTqJ=t z?SE6fiLK;bUk3&I4etb<+SUg09kOvSntbo~f?C7+ga0qrO)`tl_p0VQ<7}sSdh&*g z$w~E2-%Miq^?$!E*jEv^Y;xM}6Fw>bvpuG-H<`PiX<4zU?Xji0HFNg=UgQ|py+YGt zRrH$pt#d_Oa|5T`@Htzt>Z0C$pN~`I-W@BlkX^#O$1aogyad~GpBYa?Oit|OG%Q@r zJne+QhMg=wGo;F|=4x6*KUUklYT^|>#^BDNRr{aKXiYwO@2*tqq9%TG^V?6a-djyoq`xwJ&A=1r>d(6HZAx$U$5MgY z6aJ*VtGvDB-}X1=;G`9O{2n_nZGjRH_M`<$cLKnqRg_v-npu>Zo0?ZrtXEP|LQc|U zWD;RU%@?p_4ayfVu%r>hA|aOqc%vDNd% zj=rr_mB#73$cT&^d&Rnvd~@F3nr0UsKKwd(1On*(k|vmU$lC-Lv;C$Aq$o-7pq&E{M-q~^XxoMvJEv_l+m@7jTa9;hPw$6Vy`wtz-{!cZE_a|D; zeVDjm-otwV`j)dFmQUoFvQj%^=GxV#jB;WW`yak~>TfKv^`&=a>12=7dUs@}Yn^z} zFu?8)zcdJTqJ=t z?SE6fiLK;bUk3&I4etb<+SUg09kOvSntbo~f?C7+ga0qrO)`tl_p0VQ<7}sSdh&*g z$w~E2-%Miq^?$!E*jEv^Y;xM}6Fw>bvpuG-H<`PiX<4zU?Xji0HFNg=UgQ|py+YGt zRrH$pt#d_Oa|5T`@Htzt>Z0C$pN~`I-W@BlkX^#O$1aogyad~GpBYa?Oit|OG%Q@r zJne+QhMg=wGo;F|=4x6*KUUklYT^|>#^BDNRr{aKXiYwO@2*tqq9%TG^V?6a-djyoq`xwJ&A=1r>d(6HZAx$U$5MgY z6aJ*VtGvDB-}X1=;G`9O{2n_nZGjRH_M`<$cLKnqRg_v-npu>Zo0?ZrtXEP|LQc|U zWD;RU%@?p_4ayfVu%r>hA|aOqc%vDNd% zj=rr_mB#73$cT&^d&Rnvd~@F3nr0UsKKwd(1On*(k|vmU$lC-Lv;C$Aq$o-7pq&E{M-q~^XxoMvJEv_l+m@7jTa9;hPw$6Vy`wtz-{!cZE_a|D; zeVDjm-otwV`j)dFmQUoFvQj%^=GxV#jB;WW`yak~>TfKv^`&=a>12=7dUs@}Yn^z} zFu?8)zcdJTqJ=t z?SE6fiLK;bUk3&I4etb<+SUg09kOvSntbo~f?C7+ga0qrO)`tl_p0VQ<7}sSdh&*g z$w~E2-%Miq^?$!E*jEv^Y;xM}6Fw>bvpuG-H<`PiX<4zU?Xji0HFNg=UgQ|py+YGt zRrH$pt#d_Oa|5T`@Htzt>Z0C$pN~`I-W@BlkX^#O$1aogyad~GpBYa?Oit|OG%Q@r zJne+QhMg=wGo;F|=4x6*KUUklYT^|>#^BDNRr{aKXiYwO@2*tqq9%TG^V?6a-djyoq`xwJ&A=1r>d(6HZAx$U$5MgY z6aJ*VtGvDB-}X1=;G`9O{2n_nZGjRH_M`<$cLKnqRg_v-npu>Zo0?ZrtXEP|LQc|U zWD;RU%@?p_4ayfVu%r>hA|aOqc%vDNtxcmy(lORIFD}85+XNz((kZ=$e!ogI`e0(%A72s5%DNMly7eVLnpV>iPE@R4oU3# zu^KoQ?CfWD~ErxLp0T@O^Yry3(yF zCr{n`dm?4$t2v=}=Y5O1>-L5}z?+?eUHjgqcwl&gVkf|xkx7IZ5p&3LpqPV!C5<2! X3Go=<&B_MS!U%+sKzawzLIwr^eU{QQ literal 0 HcmV?d00001 diff --git a/tests/integration/testdata/start_api/terraform/v1-lambda-authorizer/main.tf b/tests/integration/testdata/start_api/terraform/v1-lambda-authorizer/main.tf new file mode 100644 index 0000000000..b3dcc7b51c --- /dev/null +++ b/tests/integration/testdata/start_api/terraform/v1-lambda-authorizer/main.tf @@ -0,0 +1,139 @@ +provider "aws" {} + +resource "aws_api_gateway_authorizer" "header_authorizer" { + name = "header_authorizer" + rest_api_id = aws_api_gateway_rest_api.api.id + authorizer_uri = aws_lambda_function.authorizer.invoke_arn + authorizer_credentials = aws_iam_role.invocation_role.arn + identity_source = "method.request.header.myheader" + identity_validation_expression = "^123$" +} + +resource "aws_api_gateway_authorizer" "request_authorizer" { + name = "request_authorizer" + rest_api_id = aws_api_gateway_rest_api.api.id + authorizer_uri = aws_lambda_function.authorizer.invoke_arn + authorizer_credentials = aws_iam_role.invocation_role.arn + identity_source = "method.request.header.myheader, method.request.querystring.mystring" + type = "REQUEST" +} + +resource "aws_api_gateway_authorizer" "request_authorizer_empty" { + name = "request_authorizer" + rest_api_id = aws_api_gateway_rest_api.api.id + authorizer_uri = aws_lambda_function.authorizer.invoke_arn + authorizer_credentials = aws_iam_role.invocation_role.arn + identity_source = "" + type = "REQUEST" +} + +resource "aws_lambda_function" "authorizer" { + filename = "lambda-functions.zip" + function_name = "authorizer" + role = aws_iam_role.invocation_role.arn + handler = "handlers.auth_handler" + runtime = "python3.8" + source_code_hash = filebase64sha256("lambda-functions.zip") +} + +resource "aws_lambda_function" "hello_endpoint" { + filename = "lambda-functions.zip" + function_name = "hello_lambda" + role = aws_iam_role.invocation_role.arn + handler = "handlers.hello_handler" + runtime = "python3.8" + source_code_hash = filebase64sha256("lambda-functions.zip") +} + +resource "aws_api_gateway_method" "get_hello" { + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.hello_resource.id + http_method = "GET" + authorizer_id = aws_api_gateway_authorizer.header_authorizer.id + authorization = "CUSTOM" +} + +resource "aws_api_gateway_method" "get_hello_request" { + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.hello_resource_request.id + http_method = "GET" + authorizer_id = aws_api_gateway_authorizer.request_authorizer.id + authorization = "CUSTOM" +} + +resource "aws_api_gateway_method" "get_hello_request_empty" { + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.hello_resource_request_empty.id + http_method = "GET" + authorizer_id = aws_api_gateway_authorizer.request_authorizer_empty.id + authorization = "CUSTOM" +} + +resource "aws_api_gateway_resource" "hello_resource" { + rest_api_id = aws_api_gateway_rest_api.api.id + parent_id = aws_api_gateway_rest_api.api.root_resource_id + path_part = "hello" +} + +resource "aws_api_gateway_resource" "hello_resource_request" { + rest_api_id = aws_api_gateway_rest_api.api.id + parent_id = aws_api_gateway_rest_api.api.root_resource_id + path_part = "hello-request" +} + +resource "aws_api_gateway_resource" "hello_resource_request_empty" { + rest_api_id = aws_api_gateway_rest_api.api.id + parent_id = aws_api_gateway_rest_api.api.root_resource_id + path_part = "hello-request-empty" +} + +resource "aws_api_gateway_integration" "MyDemoIntegration" { + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.hello_resource.id + http_method = aws_api_gateway_method.get_hello.http_method + type = "AWS_PROXY" + content_handling = "CONVERT_TO_TEXT" + uri = aws_lambda_function.hello_endpoint.invoke_arn +} + +resource "aws_api_gateway_integration" "MyDemoIntegrationRequest" { + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.hello_resource_request.id + http_method = aws_api_gateway_method.get_hello_request.http_method + type = "AWS_PROXY" + content_handling = "CONVERT_TO_TEXT" + uri = aws_lambda_function.hello_endpoint.invoke_arn +} + +resource "aws_api_gateway_integration" "MyDemoIntegrationRequestEmpty" { + rest_api_id = aws_api_gateway_rest_api.api.id + resource_id = aws_api_gateway_resource.hello_resource_request_empty.id + http_method = aws_api_gateway_method.get_hello_request_empty.http_method + type = "AWS_PROXY" + content_handling = "CONVERT_TO_TEXT" + uri = aws_lambda_function.hello_endpoint.invoke_arn +} + +resource "aws_api_gateway_rest_api" "api" { + name = "api" +} + +resource "aws_iam_role" "invocation_role" { + name = "iam_lambda" + path = "/" + assume_role_policy = < Date: Mon, 3 Jul 2023 12:11:12 -0700 Subject: [PATCH 086/107] Updated warning message about missing function in template (#5347) Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> --- samcli/local/apigw/local_apigw_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/local/apigw/local_apigw_service.py b/samcli/local/apigw/local_apigw_service.py index e63a8775e7..f979b2e9a3 100644 --- a/samcli/local/apigw/local_apigw_service.py +++ b/samcli/local/apigw/local_apigw_service.py @@ -686,7 +686,7 @@ def _request_handler(self, **kwargs): LOG.warning( "Failed to find a Function to invoke a Lambda authorizer, verify that " - "this Function exists locally if it is not a remote resource." + "this Function is defined and exists locally in the template." ) except Exception as ex: # re-raise the catch all exception after we track it in our telemetry From ed93c2aa6720825c2077d0c8c2f24bfeb37bb973 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 3 Jul 2023 21:16:22 +0000 Subject: [PATCH 087/107] chore(deps-dev): bump types-pywin32 in /requirements (#5436) Bumps [types-pywin32](https://github.com/python/typeshed) from 306.0.0.0 to 306.0.0.2. - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-pywin32 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 4370b187ca..2421d05095 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -9,7 +9,7 @@ pytest-cov==4.1.0 # here we fix its version and upgrade it manually in the future mypy==1.3.0 boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]==1.26.131 -types-pywin32==306.0.0.0 +types-pywin32==306.0.0.2 types-PyYAML==6.0.12 types-chevron==0.14.2.4 types-psutil==5.9.5.12 From acb4627d505dc1a5963078875cba56a282e6b191 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 4 Jul 2023 10:06:42 -0700 Subject: [PATCH 088/107] chore: use latest python version (#5439) --- .github/workflows/build.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a48b384170..03e29508fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,17 +57,9 @@ jobs: - "3.11" steps: - uses: actions/checkout@v3 - # @melasmar - # TODO: Revert back to use 3.7 to all operating systems after the regression issue in Python - # https://github.com/actions/setup-python/issues/682 in github action got resolved - uses: actions/setup-python@v4 - if: matrix.os != 'macos-latest' || ( matrix.os == 'macos-latest' && matrix.python != '3.7' ) with: python-version: ${{ matrix.python }} - - uses: actions/setup-python@v4 - if: matrix.os == 'macos-latest' && matrix.python == '3.7' - with: - python-version: "3.7.16" - run: test -f "./.github/ISSUE_TEMPLATE/Bug_report.md" # prevent Bug_report.md from being renamed or deleted - run: make init - run: make pr From 2c9a939692fd99c05bf3f7dc39c6f00804c22fce Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 4 Jul 2023 13:13:47 -0700 Subject: [PATCH 089/107] chore: remove the CDK SAM CLI integration testing and depend on the same test cases defined in CDK repo (#5410) * chore: fix CDK Appveyor job and deprecate testing CDK V1 * move CDK testing to GH Actions * fix spelling mistake * run only on aws-sam-cli repo * delete Appveyor Iac integration job * change cron schedule * update lambda functions to only use py3.9 * test on my github repo * run the GH action only on aws-sam-cli repo * update lambda functions to only use py3.9 * test on my github repo * run the GH action only on aws-sam-cli repo * chore: remove the CDK SAM CLI integration testing and depend on the same test cases defined in CDK repo. --- appveyor-iac-integration-ubuntu.yml | 105 --- tests/iac_integration/__init__.py | 0 tests/iac_integration/cdk/__init__.py | 0 .../cdk/test_sam_cdk_integration.py | 132 ---- .../iac_integration/cdk/testdata/__init__.py | 0 .../cdk/testdata/cdk_v1/java/cdk.json | 6 - .../cdk/testdata/cdk_v1/java/pom.xml | 90 --- .../java/src/main/java/com/myorg/JavaApp.java | 17 - .../src/main/java/com/myorg/JavaStack.java | 269 -------- .../src/main/java/com/myorg/NestedStack1.java | 64 -- .../cdk/testdata/cdk_v1/python/app.py | 10 - .../cdk/testdata/cdk_v1/python/cdk.json | 6 - .../testdata/cdk_v1/python/python/__init__.py | 0 .../cdk_v1/python/python/nested_stack.py | 61 -- .../cdk_v1/python/python/python_stack.py | 323 --------- .../testdata/cdk_v1/python/requirements.txt | 8 - .../cdk_v1/typescript/bin/test-app.ts | 7 - .../cdk/testdata/cdk_v1/typescript/cdk.json | 6 - .../cdk_v1/typescript/lib/nested-stack.ts | 58 -- .../cdk_v1/typescript/lib/test-stack.ts | 279 -------- .../testdata/cdk_v1/typescript/package.json | 28 - .../testdata/cdk_v1/typescript/tsconfig.json | 30 - .../cdk/testdata/cdk_v2/java/cdk.json | 6 - .../cdk/testdata/cdk_v2/java/pom.xml | 80 --- .../java/src/main/java/com/myorg/JavaApp.java | 17 - .../src/main/java/com/myorg/JavaStack.java | 270 -------- .../src/main/java/com/myorg/NestedStack1.java | 64 -- .../cdk/testdata/cdk_v2/python/app.py | 10 - .../cdk/testdata/cdk_v2/python/cdk.json | 6 - .../testdata/cdk_v2/python/python/__init__.py | 0 .../cdk_v2/python/python/nested_stack.py | 59 -- .../cdk_v2/python/python/python_stack.py | 323 --------- .../testdata/cdk_v2/python/requirements.txt | 6 - .../cdk_v2/typescript/bin/test-app.ts | 7 - .../cdk/testdata/cdk_v2/typescript/cdk.json | 6 - .../cdk_v2/typescript/lib/nested-stack.ts | 59 -- .../cdk_v2/typescript/lib/test-stack.ts | 278 -------- .../testdata/cdk_v2/typescript/package.json | 26 - .../testdata/cdk_v2/typescript/tsconfig.json | 30 - .../DockerImageFunctionConstruct/Dockerfile | 9 - .../DockerImageFunctionConstruct/app.js | 22 - .../DockerImageFunctionConstruct/package.json | 16 - .../Dockerfile | 9 - .../app.js | 22 - .../package.json | 16 - .../Dockerfile | 16 - .../DockerImageFunctionWithSharedCode/app.js | 24 - .../Dockerfile | 16 - .../FunctionImageAssetWithSharedCode/app.js | 24 - .../ImagesWithSharedCode/SharedCode/shared.js | 4 - .../docker/ImagesWithSharedCode/package.json | 16 - .../testdata/src/go/FunctionConstruct/go.mod | 5 - .../testdata/src/go/FunctionConstruct/go.sum | 17 - .../testdata/src/go/FunctionConstruct/main.go | 17 - .../src/go/GoFunctionConstruct/go.mod | 5 - .../src/go/GoFunctionConstruct/go.sum | 17 - .../src/go/GoFunctionConstruct/main.go | 17 - .../src/nodejs/BuiltFunctionConstruct/app.js | 17 - .../src/nodejs/FunctionConstruct/app.js | 23 - .../src/nodejs/FunctionConstruct/package.json | 8 - .../src/nodejs/NodeJsFunctionConstruct/app.ts | 22 - .../node_modules/.package-lock.json | 16 - .../unique-names-generator/LICENSE | 9 - .../unique-names-generator/README.md | 643 ------------------ .../dist/dictionaries/adjectives.d.ts | 2 - .../dist/dictionaries/animals.d.ts | 2 - .../dist/dictionaries/colors.d.ts | 2 - .../dist/dictionaries/countries.d.ts | 2 - .../dist/dictionaries/index.d.ts | 9 - .../dist/dictionaries/languages.d.ts | 2 - .../dist/dictionaries/names.d.ts | 2 - .../dist/dictionaries/numbers.d.ts | 9 - .../dist/dictionaries/star-wars.d.ts | 2 - .../unique-names-generator/dist/index.d.ts | 3 - .../unique-names-generator/dist/index.js | 2 - .../unique-names-generator/dist/index.m.js | 2 - .../dist/index.modern.js | 2 - .../unique-names-generator/dist/index.umd.js | 2 - .../unique-names-generator/dist/seed.d.ts | 1 - .../unique-names-generator.constructor.d.ts | 18 - .../dist/unique-names-generator.d.ts | 2 - .../unique-names-generator/package.json | 60 -- .../NodeJsFunctionConstruct/package-lock.json | 30 - .../NodeJsFunctionConstruct/package.json | 8 - .../LayerVersion/layer_version_dependency.js | 9 - .../nodejs/layers/LayerVersion/package.json | 8 - .../src/python/BuiltFunctionConstruct/app.py | 18 - .../python/BundledFunctionConstruct/app.py | 19 - .../BundledFunctionConstruct/requirements.txt | 1 - .../src/python/FunctionConstruct/app.py | 19 - .../python/FunctionConstruct/requirements.txt | 1 - .../NestedPythonFunctionConstruct/app.py | 19 - .../requirements.txt | 1 - .../src/python/PythonFunctionConstruct/app.py | 19 - .../PythonFunctionConstruct/requirements.txt | 1 - .../layer_version_dependency.py | 5 - .../BundledLayerVersion/requirements.txt | 1 - .../LayerVersion/layer_version_dependency.py | 5 - .../layers/LayerVersion/requirements.txt | 1 - .../python_layer_version_dependency.py | 7 - .../PythonLayerVersion/requirements.txt | 1 - .../cdk/testdata/src/rest-api-definition.yaml | 12 - 102 files changed, 4065 deletions(-) delete mode 100644 appveyor-iac-integration-ubuntu.yml delete mode 100644 tests/iac_integration/__init__.py delete mode 100644 tests/iac_integration/cdk/__init__.py delete mode 100644 tests/iac_integration/cdk/test_sam_cdk_integration.py delete mode 100644 tests/iac_integration/cdk/testdata/__init__.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/java/cdk.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/java/pom.xml delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/JavaApp.java delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/JavaStack.java delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/NestedStack1.java delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/python/app.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/python/cdk.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/python/python/__init__.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/python/python/nested_stack.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/python/python/python_stack.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/python/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/typescript/bin/test-app.ts delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/typescript/cdk.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/typescript/lib/nested-stack.ts delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/typescript/lib/test-stack.ts delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/typescript/package.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v1/typescript/tsconfig.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/java/cdk.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/java/pom.xml delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/JavaApp.java delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/JavaStack.java delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/NestedStack1.java delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/python/app.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/python/cdk.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/python/python/__init__.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/python/python/nested_stack.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/python/python/python_stack.py delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/python/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/typescript/bin/test-app.ts delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/typescript/cdk.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/typescript/lib/nested-stack.ts delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/typescript/lib/test-stack.ts delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/typescript/package.json delete mode 100644 tests/iac_integration/cdk/testdata/cdk_v2/typescript/tsconfig.json delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/Dockerfile delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/app.js delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/package.json delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/Dockerfile delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/app.js delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/package.json delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/DockerImageFunctionWithSharedCode/Dockerfile delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/DockerImageFunctionWithSharedCode/app.js delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/FunctionImageAssetWithSharedCode/Dockerfile delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/FunctionImageAssetWithSharedCode/app.js delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/SharedCode/shared.js delete mode 100644 tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/package.json delete mode 100644 tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/go.mod delete mode 100644 tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/go.sum delete mode 100644 tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/main.go delete mode 100644 tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/go.mod delete mode 100644 tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/go.sum delete mode 100644 tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/main.go delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/BuiltFunctionConstruct/app.js delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/FunctionConstruct/app.js delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/FunctionConstruct/package.json delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/app.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/.package-lock.json delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/LICENSE delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/README.md delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/adjectives.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/animals.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/colors.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/countries.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/index.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/languages.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/names.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/numbers.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/star-wars.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.js delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.m.js delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.modern.js delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.umd.js delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/seed.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/unique-names-generator.constructor.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/unique-names-generator.d.ts delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/package.json delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/package-lock.json delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/package.json delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/layers/LayerVersion/layer_version_dependency.js delete mode 100644 tests/iac_integration/cdk/testdata/src/nodejs/layers/LayerVersion/package.json delete mode 100644 tests/iac_integration/cdk/testdata/src/python/BuiltFunctionConstruct/app.py delete mode 100644 tests/iac_integration/cdk/testdata/src/python/BundledFunctionConstruct/app.py delete mode 100644 tests/iac_integration/cdk/testdata/src/python/BundledFunctionConstruct/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/src/python/FunctionConstruct/app.py delete mode 100644 tests/iac_integration/cdk/testdata/src/python/FunctionConstruct/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/src/python/NestedPythonFunctionConstruct/app.py delete mode 100644 tests/iac_integration/cdk/testdata/src/python/NestedPythonFunctionConstruct/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/src/python/PythonFunctionConstruct/app.py delete mode 100644 tests/iac_integration/cdk/testdata/src/python/PythonFunctionConstruct/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/src/python/layers/BundledLayerVersion/layer_version_dependency.py delete mode 100644 tests/iac_integration/cdk/testdata/src/python/layers/BundledLayerVersion/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/src/python/layers/LayerVersion/layer_version_dependency.py delete mode 100644 tests/iac_integration/cdk/testdata/src/python/layers/LayerVersion/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/src/python/layers/PythonLayerVersion/python_layer_version_dependency.py delete mode 100644 tests/iac_integration/cdk/testdata/src/python/layers/PythonLayerVersion/requirements.txt delete mode 100644 tests/iac_integration/cdk/testdata/src/rest-api-definition.yaml diff --git a/appveyor-iac-integration-ubuntu.yml b/appveyor-iac-integration-ubuntu.yml deleted file mode 100644 index ea2e4f54fc..0000000000 --- a/appveyor-iac-integration-ubuntu.yml +++ /dev/null @@ -1,105 +0,0 @@ -version: 1.0.{build} -image: - - Ubuntu2004 - -environment: - AWS_DEFAULT_REGION: us-east-1 - SAM_CLI_DEV: 1 - NOSE_PARAMETERIZED_NO_WARN: 1 - APPVEYOR_CONSOLE_DISABLE_PTY: false - APPVEYOR_DETAILED_SHELL_LOGGING: true - - matrix: - - PYTHON_HOME: "$HOME/venv3.7/bin" - PYTHON_VERSION: '3.7' - - - PYTHON_HOME: "$HOME/venv3.8/bin" - PYTHON_VERSION: '3.8' - - - PYTHON_HOME: "$HOME/venv3.9/bin" - PYTHON_VERSION: '3.9' - -install: - # AppVeyor's apt-get cache might be outdated, and the package could potentially be 404. - - sh: "sudo apt-get update" - - - sh: "gvm use go1.19" - - sh: "echo $PATH" - - sh: "ls /usr/" - - sh: "JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64" - - sh: "PATH=$JAVA_HOME/bin:$PATH" - - sh: "source ${HOME}/venv${PYTHON_VERSION}/bin/activate" - - sh: "rvm use 2.7.2" - - sh: "docker --version" - # install nodejs - - sh: "nvm install --lts=Fermium" - - sh: "node --version" - - sh: "npx --version" - - # Install AWS CLI - - sh: "virtualenv aws_cli" - - sh: "./aws_cli/bin/python -m pip install awscli" - - sh: "PATH=$(echo $PWD'/aws_cli/bin'):$PATH" - - # Install latest maven - - sh: "sudo apt update" - - sh: "sudo apt install maven" - - sh: "mvn --version" - - - sh: "PATH=$PATH:$HOME/venv3.7/bin:$HOME/venv3.8/bin:$HOME/venv3.9/bin:$HOME/venv3.10/bin" - - # get testing env vars - - sh: "sudo apt install -y jq" - - sh: "python3.9 -m venv .venv_env_vars" - - sh: ".venv_env_vars/bin/pip install boto3" - - sh: "test_env_var=$(.venv_env_vars/bin/python tests/get_testing_resources.py)" - - sh: ' - if [ $? -ne 0 ]; then - echo "get_testing_resources failed. Failed to acquire credentials or test resources."; - false; - fi - ' - - - sh: 'export CI_ACCESS_ROLE_AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID' - - sh: 'export CI_ACCESS_ROLE_AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY' - - sh: 'export CI_ACCESS_ROLE_AWS_SESSION_TOKEN=$AWS_SESSION_TOKEN' - - - sh: 'export AWS_ACCESS_KEY_ID=$(echo "$test_env_var" | jq -j ".accessKeyID")' - - sh: 'export AWS_SECRET_ACCESS_KEY=$(echo "$test_env_var" | jq -j ".secretAccessKey")' - - sh: 'export AWS_SESSION_TOKEN=$(echo "$test_env_var" | jq -j ".sessionToken")' - - sh: 'export TASK_TOKEN=$(echo "$test_env_var" | jq -j ".taskToken")' - - sh: 'export AWS_S3_TESTING=$(echo "$test_env_var" | jq -j ".TestBucketName")' - - sh: 'export AWS_ECR_TESTING=$(echo "$test_env_var" | jq -j ".TestECRURI")' - - sh: 'export AWS_KMS_KEY=$(echo "$test_env_var" | jq -j ".TestKMSKeyArn")' - - sh: 'export AWS_SIGNING_PROFILE_NAME=$(echo "$test_env_var" | jq -j ".TestSigningProfileName")' - - sh: 'export AWS_SIGNING_PROFILE_VERSION_ARN=$(echo "$test_env_var" | jq -j ".TestSigningProfileARN")' - - # required for RIE with arm64 in linux - - sh: "docker run --rm --privileged multiarch/qemu-user-static --reset -p yes" - - # update ca-certificates which causes failures with newest golang library - - sh: "sudo apt-get install --reinstall ca-certificates" - -build_script: - - "python -c \"import sys; print(sys.executable)\"" - -test_script: - # Pre-dev Tests - - "pip install -e \".[pre-dev]\"" - - # Runs only in Linux, logging Public ECR when running canary and cred is available - - sh: " - if [[ -n $BY_CANARY ]]; - then echo Logging in Public ECR; aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws; - fi" - - - "pip install -e \".[dev]\"" - - sh: "pytest -vv tests/iac_integration" - -# Final clean up no matter success or failure -on_finish: - - sh: 'export AWS_ACCESS_KEY_ID=$CI_ACCESS_ROLE_AWS_ACCESS_KEY_ID' - - sh: 'export AWS_SECRET_ACCESS_KEY=$CI_ACCESS_ROLE_AWS_SECRET_ACCESS_KEY' - - sh: 'export AWS_SESSION_TOKEN=$CI_ACCESS_ROLE_AWS_SESSION_TOKEN' - - - sh: 'aws stepfunctions send-task-success --task-token "$TASK_TOKEN" --task-output "{}" --region us-west-2' diff --git a/tests/iac_integration/__init__.py b/tests/iac_integration/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/iac_integration/cdk/__init__.py b/tests/iac_integration/cdk/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/iac_integration/cdk/test_sam_cdk_integration.py b/tests/iac_integration/cdk/test_sam_cdk_integration.py deleted file mode 100644 index f6257f0da5..0000000000 --- a/tests/iac_integration/cdk/test_sam_cdk_integration.py +++ /dev/null @@ -1,132 +0,0 @@ -from unittest import TestCase -from subprocess import Popen, PIPE -import os -import random -from pathlib import Path -import threading - -import pytest -import requests -from parameterized import parameterized_class, parameterized - -from tests.testing_utils import run_command, start_persistent_process, read_until_string, kill_process - - -@parameterized_class( - ("cdk_project_path", "cdk_version", "cdk_stack_template"), - [ - ("/testdata/cdk_v1/typescript", "1.x", "TestStack.template.json"), - ("/testdata/cdk_v2/typescript", "2.x", "TestStack.template.json"), - ("/testdata/cdk_v1/python", "1.x", "TestStack.template.json"), - ("/testdata/cdk_v2/python", "2.x", "TestStack.template.json"), - ("/testdata/cdk_v1/java", "1.x", "TestStack.template.json"), - ("/testdata/cdk_v2/java", "2.x", "TestStack.template.json"), - ], -) -class TestSamCdkIntegration(TestCase): - integration_dir = str(Path(__file__).resolve().parents[0]) - cdk_project_path = "" - cdk_version = "" - cdk_stack_template = "" - - @classmethod - def setUpClass(cls): - cls.cdk_project = cls.integration_dir + cls.cdk_project_path - cls.api_port = str(TestSamCdkIntegration.random_port()) - cls.build_cdk_project() - cls.build() - - cls.start_api() - cls.url = "http://127.0.0.1:{}".format(cls.api_port) - - @classmethod - def build_cdk_project(cls): - command_list = ["npx", f"aws-cdk@{cls.cdk_version}", "synth", "--no-staging"] - working_dir = cls.cdk_project - result = run_command(command_list, cwd=working_dir) - if result.process.returncode != 0: - raise Exception("cdk synth command failed") - - @classmethod - def build(cls): - command = "sam" - if os.getenv("SAM_CLI_DEV"): - command = "samdev" - command_list = [command, "build", "-t", cls.cdk_stack_template] - working_dir = cls.cdk_project + "/cdk.out" - result = run_command(command_list, cwd=working_dir) - if result.process.returncode != 0: - raise Exception("sam build command failed") - - @classmethod - def start_api(cls): - command = "sam" - if os.getenv("SAM_CLI_DEV"): - command = "samdev" - - command_list = [command, "local", "start-api", "-p", cls.api_port] - - working_dir = cls.cdk_project + "/cdk.out" - cls.start_api_process = Popen(command_list, cwd=working_dir, stderr=PIPE) - - while True: - line = cls.start_api_process.stderr.readline() - if "Press CTRL+C to quit" in str(line): - break - - cls.stop_api_reading_thread = False - - def read_sub_process_stderr(): - while not cls.stop_api_reading_thread: - cls.start_api_process.stderr.readline() - - cls.api_read_threading = threading.Thread(target=read_sub_process_stderr, daemon=True) - cls.api_read_threading.start() - - @classmethod - def tearDownClass(cls): - # After all the tests run, we need to kill the start_lambda process. - cls.stop_api_reading_thread = True - kill_process(cls.start_api_process) - - @staticmethod - def random_port(): - return random.randint(30000, 40000) - - @parameterized.expand( - [ - ("/httpapis/nestedPythonFunction", "Hello World from Nested Python Function Construct 7"), - ("/restapis/spec/pythonFunction", "Hello World from python function construct 7"), - ("/restapis/normal/pythonFunction", "Hello World from python function construct 7"), - ("/restapis/normal/functionPythonRuntime", "Hello World from function construct with python runtime 7"), - ("/restapis/normal/preBuiltFunctionPythonRuntime", "Hello World from python pre built function 7"), - ( - "/restapis/normal/bundledFunctionPythonRuntime", - "Hello World from bundled function construct with python runtime 7", - ), - ("/restapis/normal/nodejsFunction", "Hello World from nodejs function construct 7"), - ("/restapis/normal/functionNodeJsRuntime", "Hello World from function construct with nodejs runtime 7"), - ("/restapis/normal/preBuiltFunctionNodeJsRuntime", "Hello World from nodejs pre built function 7"), - ("/restapis/normal/goFunction", "Hello World from go function construct"), - ("/restapis/normal/functionGoRuntime", "Hello World from function construct with go runtime"), - ("/restapis/normal/dockerImageFunction", "Hello World from docker image function construct"), - ("/restapis/normal/functionImageAsset", "Hello World from function construct with image asset"), - ( - "/restapis/normal/dockerImageFunctionWithSharedCode", - "Hello World from docker image function construct " - "with a Dockerfile that shares code with another Dockerfile", - ), - ( - "/restapis/normal/functionImageAssetWithSharedCode", - "Hello World from function construct with image asset " - "with a Dockerfile that shares code with another Dockerfile", - ), - ] - ) - @pytest.mark.flaky(reruns=3) - @pytest.mark.timeout(timeout=1000, method="thread") - def test_invoke_api(self, url_suffix, expected_message): - response = requests.get(self.url + url_suffix, timeout=800) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json().get("message"), expected_message) diff --git a/tests/iac_integration/cdk/testdata/__init__.py b/tests/iac_integration/cdk/testdata/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/java/cdk.json b/tests/iac_integration/cdk/testdata/cdk_v1/java/cdk.json deleted file mode 100644 index d981a1fd3c..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/java/cdk.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "app": "mvn versions:use-latest-versions -DallowMajorUpdates=false && mvn -e -q compile exec:java", - "context": { - - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/java/pom.xml b/tests/iac_integration/cdk/testdata/cdk_v1/java/pom.xml deleted file mode 100644 index 4a6d29e2d1..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/java/pom.xml +++ /dev/null @@ -1,90 +0,0 @@ - - - 4.0.0 - - com.myorg - java - 0.1 - - - UTF-8 - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.7 - - - software.amazon.awscdk:* - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - - org.codehaus.mojo - exec-maven-plugin - 3.0.0 - - com.myorg.JavaApp - - - - - - - - - software.amazon.awscdk - core - 1.120.0 - - - software.amazon.awscdk - apigateway - 1.120.0 - - - software.amazon.awscdk - apigatewayv2 - 1.120.0 - - - software.amazon.awscdk - apigatewayv2-integrations - 1.120.0 - - - software.amazon.awscdk - lambda - 1.120.0 - - - software.amazon.awscdk - lambda-go - 1.120.0 - - - software.amazon.awscdk - lambda-nodejs - 1.120.0 - - - software.amazon.awscdk - lambda-python - 1.120.0 - - - diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/JavaApp.java b/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/JavaApp.java deleted file mode 100644 index ff8cf94126..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/JavaApp.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.myorg; - -import software.amazon.awscdk.core.App; -import software.amazon.awscdk.core.StackProps; - -import java.util.Arrays; - -public class JavaApp { - public static void main(final String[] args) { - App app = new App(); - - new JavaStack(app, "TestStack", StackProps.builder() - .build()); - - app.synth(); - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/JavaStack.java b/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/JavaStack.java deleted file mode 100644 index 90a0f516a4..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/JavaStack.java +++ /dev/null @@ -1,269 +0,0 @@ -package com.myorg; - -import software.amazon.awscdk.core.*; -import software.amazon.awscdk.services.apigateway.Resource; -import software.amazon.awscdk.services.apigateway.*; -import software.amazon.awscdk.services.iam.PolicyStatement; -import software.amazon.awscdk.services.iam.Role; -import software.amazon.awscdk.services.iam.ServicePrincipal; -import software.amazon.awscdk.services.lambda.Runtime; -import software.amazon.awscdk.services.lambda.*; -import software.amazon.awscdk.services.lambda.go.GoFunction; -import software.amazon.awscdk.services.lambda.nodejs.NodejsFunction; -import software.amazon.awscdk.services.lambda.python.PythonFunction; -import software.amazon.awscdk.services.lambda.python.PythonLayerVersion; -import software.amazon.awscdk.services.logs.RetentionDays; -import software.amazon.awscdk.services.s3.assets.AssetOptions; -import com.myorg.NestedStack1; - -import java.util.Arrays; - -public class JavaStack extends Stack { - public JavaStack(final Construct scope, final String id) { - this(scope, id, null); - } - - public JavaStack(final Construct scope, final String id, final StackProps props) { - super(scope, id, props); - - // Python Runtime - // Layers - PythonLayerVersion pythonLayerVersion = PythonLayerVersion.Builder - .create(this, "PythonLayerVersion") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .entry("../../src/python/layers/PythonLayerVersion") - .build(); - - LayerVersion layerVersion = LayerVersion.Builder - .create(this, "LayerVersion") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .code(Code.fromAsset("../../src/python/layers/LayerVersion")) - .build(); - // add SAM metadata to build layer - CfnLayerVersion cfnLayerVersion = (CfnLayerVersion) layerVersion.getNode().getDefaultChild(); - cfnLayerVersion.addMetadata("BuildMethod", "python3.7"); - - // Lambda LayerVersion with bundled Asset that will be built by CDK - LayerVersion bundledLayerVersionPythonRuntime = LayerVersion.Builder - .create(this, "BundledLayerVersionPythonRuntime") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .code(Code.fromAsset("../../src/python/layers/BundledLayerVersion", - AssetOptions.builder().bundling( - BundlingOptions.builder() - .image(Runtime.PYTHON_3_7.getBundlingImage()) - .command(Arrays.asList( - "/bin/sh", - "-c", - "rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input" + - " && pip install -r requirements.txt -t . && mkdir /asset-output/python && " + - "cp -R /tmp/asset-input/* /asset-output/python" - )).build() - ).build() - )).build(); - - // ZIP package type Functions - // Functions Built by CDK - Runtime Functions Constructs - PythonFunction pythonFunction = PythonFunction.Builder - .create(this, "PythonFunction") - .entry("../../src/python/PythonFunctionConstruct") - .index("app.py") - .handler("lambda_handler") - .runtime(Runtime.PYTHON_3_9) - .functionName("pythonFunc") // we need the name to use it in the API definition file - .logRetention(RetentionDays.THREE_MONTHS) - .layers(Arrays.asList(pythonLayerVersion, layerVersion)) - .tracing(Tracing.ACTIVE) - .build(); - - // Normal Lambda Function Construct - Python Runtime - Function functionPythonRuntime = Function.Builder.create(this, "FunctionPythonRuntime") - .runtime(Runtime.PYTHON_3_7) - .code(Code.fromAsset("../../src/python/FunctionConstruct")) - .handler("app.lambda_handler") - .layers(Arrays.asList(pythonLayerVersion, layerVersion)) - .tracing(Tracing.ACTIVE) - .build(); - - // Normal Lambda Function Construct - Python Runtime - with skip build metadata - Function preBuiltFunctionPythonRuntime = Function.Builder.create(this, "PreBuiltFunctionPythonRuntime") - .runtime(Runtime.PYTHON_3_7) - .code(Code.fromAsset("../../src/python/BuiltFunctionConstruct")) - .handler("app.lambda_handler") - .layers(Arrays.asList(pythonLayerVersion, layerVersion)) - .tracing(Tracing.ACTIVE) - .build(); - // add SkipBuild Metadata, so SAM will skip building this function - CfnFunction cfnPreBuiltFunctionPythonRuntime = (CfnFunction) preBuiltFunctionPythonRuntime.getNode() - .getDefaultChild(); - cfnPreBuiltFunctionPythonRuntime.addMetadata("SkipBuild", true); - - // Normal Lambda Function with bundled Asset will be built by CDK - Function bundledFunctionPythonRuntime = Function.Builder.create(this, "BundledFunctionPythonRuntime") - .runtime(Runtime.PYTHON_3_7) - .code(Code.fromAsset("../../src/python/BundledFunctionConstruct/", - AssetOptions.builder().bundling( - BundlingOptions.builder() - .command(Arrays.asList("/bin/sh", "-c", "rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && cp -R /tmp/asset-input/* /asset-output")) - .image(Runtime.PYTHON_3_7.getBundlingImage()) - .build() - ).build() - )) - .handler("app.lambda_handler") - .layers(Arrays.asList(bundledLayerVersionPythonRuntime, pythonLayerVersion)) - .timeout(Duration.seconds(120)) - .tracing(Tracing.ACTIVE) - .build(); - - // NodeJs Runtime - //Layers - LayerVersion layerVersionNodeJsRuntime = LayerVersion.Builder.create(this, "LayerVersionNodeJsRuntime") - .compatibleRuntimes(Arrays.asList(Runtime.NODEJS_14_X)) - .code(Code.fromAsset("../../src/nodejs/layers/LayerVersion")) - .build(); - // add SAM metadata to build layer - CfnLayerVersion cfnLayerVersionNodeJsRuntime = (CfnLayerVersion) layerVersionNodeJsRuntime.getNode().getDefaultChild(); - cfnLayerVersionNodeJsRuntime.addMetadata("BuildMethod", "nodejs14.x"); - - NodejsFunction nodejsFunction = NodejsFunction.Builder.create(this, "NodejsFunction") - .runtime(Runtime.NODEJS_14_X) - .entry("../../src/nodejs/NodeJsFunctionConstruct/app.ts") - .depsLockFilePath("../../src/nodejs/NodeJsFunctionConstruct/package-lock.json") - .handler("lambdaHandler") - .layers(Arrays.asList(layerVersionNodeJsRuntime)) - .tracing(Tracing.ACTIVE) - .bundling(software.amazon.awscdk.services.lambda.nodejs.BundlingOptions.builder() - .externalModules( - Arrays.asList("/opt/nodejs/layer_version_dependency") - ).build() - ).build(); - - // Normal Lambda Function Construct - NodeJs Runtime - Function functionNodeJsRuntime = Function.Builder.create(this, "FunctionNodeJsRuntime") - .runtime(Runtime.NODEJS_14_X) - .code(Code.fromAsset("../../src/nodejs/FunctionConstruct")) - .handler("app.lambdaHandler") - .layers(Arrays.asList(layerVersionNodeJsRuntime)) - .tracing(Tracing.ACTIVE) - .build(); - - // Normal Lambda Function Construct - NodeJs Runtime - with skip build metadata - Function preBuiltFunctionNodeJsRuntime = Function.Builder.create(this, "PreBuiltFunctionNodeJsRuntime") - .runtime(Runtime.NODEJS_14_X) - .code(Code.fromAsset("../../src/nodejs/BuiltFunctionConstruct")) - .handler("app.lambdaHandler") - .layers(Arrays.asList(layerVersionNodeJsRuntime)) - .tracing(Tracing.ACTIVE) - .build(); - // add SkipBuild Metadata, so SAM will skip building this function - CfnFunction cfnPreBuiltFunctionNodeJsRuntime = (CfnFunction) preBuiltFunctionNodeJsRuntime.getNode().getDefaultChild(); - cfnPreBuiltFunctionNodeJsRuntime.addMetadata("SkipBuild", true); - - // Go Runtime - GoFunction goFunction = GoFunction.Builder.create(this, "GoFunction") - .entry("../../src/go/GoFunctionConstruct") - .bundling(software.amazon.awscdk.services.lambda.go.BundlingOptions.builder() - .forcedDockerBundling(true).build()) - .build(); - - // Normal Lambda Function Construct - Go Runtime - Function functionGoRuntime = Function.Builder.create(this, "FunctionGoRuntime") - .runtime(Runtime.GO_1_X) - .code(Code.fromAsset("../../src/go/FunctionConstruct")) - .handler("FunctionConstruct") - .build(); - - // Image Package Type Functions - // One way to define an Image Package Type Function - DockerImageFunction dockerImageFunction = DockerImageFunction.Builder.create(this, "DockerImageFunction") - .code(DockerImageCode.fromImageAsset("../../src/docker/DockerImageFunctionConstruct", - AssetImageCodeProps.builder().file("Dockerfile").build() - ) - ).tracing(Tracing.ACTIVE) - .build(); - - // another way - Function functionImageAsset = Function.Builder.create(this, "FunctionImageAsset") - .code(Code.fromAssetImage("../../src/docker/FunctionConstructWithImageAssetCode", - AssetImageCodeProps.builder().file("Dockerfile").build())) - .handler(Handler.FROM_IMAGE) - .runtime(Runtime.FROM_IMAGE) - .tracing(Tracing.ACTIVE) - .build(); - - // both ways work when 'file' is a path via subfolders to the Dockerfile - // this is useful when multiple docker images share some common code - DockerImageFunction dockerImageFunctionWithSharedCode = DockerImageFunction.Builder.create(this, "DockerImageFunctionWithSharedCode") - .code(DockerImageCode.fromImageAsset("../../src/docker/ImagesWithSharedCode", - AssetImageCodeProps.builder().file("DockerImageFunctionWithSharedCode/Dockerfile").build() - ) - ).tracing(Tracing.ACTIVE) - .build(); - - Function functionImageAssetWithSharedCode = Function.Builder.create(this, "FunctionImageAssetWithSharedCode") - .code(Code.fromAssetImage("../../src/docker/ImagesWithSharedCode", - AssetImageCodeProps.builder().file("FunctionImageAssetWithSharedCode/Dockerfile").build())) - .handler(Handler.FROM_IMAGE) - .runtime(Runtime.FROM_IMAGE) - .tracing(Tracing.ACTIVE) - .build(); - - //Rest APIs - - // Spec Rest Api - SpecRestApi.Builder.create(this, "SpecRestAPI") - .apiDefinition(ApiDefinition.fromAsset("../../src/rest-api-definition.yaml")) - .build(); - - // Role to be used as credentials for the Spec rest APi - // it is used inside the spec rest api definition file - Role.Builder.create(this, "SpecRestApiRole") - .assumedBy(new ServicePrincipal("apigateway.amazonaws.com")) - .roleName("SpecRestApiRole") - .build() - .addToPolicy( - PolicyStatement.Builder.create() - .actions(Arrays.asList("lambda:InvokeFunction")) - .resources(Arrays.asList("*")) - .build() - ); - - // Rest Api - RestApi restApi = new RestApi(this, "RestAPI"); - Resource normalRootResource = restApi.getRoot().addResource("restapis") - .addResource("normal"); - - normalRootResource.addResource("pythonFunction") - .addMethod("GET", new LambdaIntegration(pythonFunction)); - normalRootResource.addResource("functionPythonRuntime") - .addMethod("GET", new LambdaIntegration(functionPythonRuntime)); - normalRootResource.addResource("preBuiltFunctionPythonRuntime") - .addMethod("GET", new LambdaIntegration(preBuiltFunctionPythonRuntime)); - normalRootResource.addResource("bundledFunctionPythonRuntime") - .addMethod("GET", new LambdaIntegration(bundledFunctionPythonRuntime)); - normalRootResource.addResource("nodejsFunction") - .addMethod("GET", new LambdaIntegration(nodejsFunction)); - normalRootResource.addResource("functionNodeJsRuntime") - .addMethod("GET", new LambdaIntegration(functionNodeJsRuntime)); - normalRootResource.addResource("preBuiltFunctionNodeJsRuntime") - .addMethod("GET", new LambdaIntegration(preBuiltFunctionNodeJsRuntime)); - normalRootResource.addResource("goFunction") - .addMethod("GET", new LambdaIntegration(goFunction)); - normalRootResource.addResource("functionGoRuntime") - .addMethod("GET", new LambdaIntegration(functionGoRuntime)); - normalRootResource.addResource("dockerImageFunction") - .addMethod("GET", new LambdaIntegration(dockerImageFunction)); - normalRootResource.addResource("functionImageAsset") - .addMethod("GET", new LambdaIntegration(functionImageAsset)); - normalRootResource.addResource("dockerImageFunctionWithSharedCode") - .addMethod("GET", new LambdaIntegration(dockerImageFunctionWithSharedCode)); - normalRootResource.addResource("functionImageAssetWithSharedCode") - .addMethod("GET", new LambdaIntegration(functionImageAssetWithSharedCode)); - - // Nested Stack - new NestedStack1(this, "NestedStack"); - - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/NestedStack1.java b/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/NestedStack1.java deleted file mode 100644 index cea2e65260..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/java/src/main/java/com/myorg/NestedStack1.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.myorg; - -import org.jetbrains.annotations.NotNull; -import software.amazon.awscdk.core.Construct; -import software.amazon.awscdk.core.NestedStack; -import software.amazon.awscdk.services.apigatewayv2.AddRoutesOptions; -import software.amazon.awscdk.services.apigatewayv2.HttpApi; -import software.amazon.awscdk.services.apigatewayv2.HttpMethod; -import software.amazon.awscdk.services.apigatewayv2.integrations.HttpLambdaIntegration; -import software.amazon.awscdk.services.lambda.Runtime; -import software.amazon.awscdk.services.lambda.*; -import software.amazon.awscdk.services.lambda.python.PythonFunction; -import software.amazon.awscdk.services.lambda.python.PythonLayerVersion; -import software.amazon.awscdk.services.logs.RetentionDays; - -import java.util.Arrays; - -public class NestedStack1 extends NestedStack { - - public NestedStack1(@NotNull Construct scope, @NotNull String id) { - super(scope, id); - - // Python Runtime - // Layers - PythonLayerVersion pythonLayerVersion = PythonLayerVersion.Builder - .create(this, "PythonLayerVersion") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .entry("../../src/python/layers/PythonLayerVersion") - .build(); - - LayerVersion layerVersion = LayerVersion.Builder - .create(this, "LayerVersion") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .code(Code.fromAsset("../../src/python/layers/LayerVersion")) - .build(); - // add SAM metadata to build layer - CfnLayerVersion cfnLayerVersion = (CfnLayerVersion) layerVersion.getNode().getDefaultChild(); - cfnLayerVersion.addMetadata("BuildMethod", "python3.7"); - - // ZIP package type Functions - // Functions Built by CDK - Runtime Functions Constructs - PythonFunction nestedPythonFunction = PythonFunction.Builder - .create(this, "NestedPythonFunction") - .entry("../../src/python/NestedPythonFunctionConstruct") - .index("app.py") - .handler("lambda_handler") - .runtime(Runtime.PYTHON_3_9) - .logRetention(RetentionDays.THREE_MONTHS) - .layers(Arrays.asList(pythonLayerVersion, layerVersion)) - .tracing(Tracing.ACTIVE) - .build(); - - HttpApi httpApi = new HttpApi(this, "httpAPi"); - - httpApi.addRoutes(AddRoutesOptions.builder() - .path("/httpapis/nestedPythonFunction") - .methods(Arrays.asList(HttpMethod.GET)) - .integration(new HttpLambdaIntegration("httpApiRandomNameIntegration", nestedPythonFunction)) - .build() - ); - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/python/app.py b/tests/iac_integration/cdk/testdata/cdk_v1/python/app.py deleted file mode 100644 index be87c07f11..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/python/app.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 -from aws_cdk import core - -from python.python_stack import PythonStack - - -app = core.App() -PythonStack(app, "TestStack") - -app.synth() diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/python/cdk.json b/tests/iac_integration/cdk/testdata/cdk_v1/python/cdk.json deleted file mode 100644 index 05b65aa6c3..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/python/cdk.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "app": "python3 -m venv .env && ./.env/bin/python -m pip install -r requirements.txt && ./.env/bin/python app.py", - "context": { - - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/python/python/__init__.py b/tests/iac_integration/cdk/testdata/cdk_v1/python/python/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/python/python/nested_stack.py b/tests/iac_integration/cdk/testdata/cdk_v1/python/python/nested_stack.py deleted file mode 100644 index eb697579c1..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/python/python/nested_stack.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import cast - -from aws_cdk import ( - core as cdk, - aws_lambda as lambda1, -) -from aws_cdk.aws_apigatewayv2 import HttpApi, HttpMethod -from aws_cdk.aws_apigatewayv2_integrations import HttpLambdaIntegration -from aws_cdk.aws_lambda import CfnLayerVersion -from aws_cdk.aws_lambda_python import PythonLayerVersion, PythonFunction - - -class NestedStack1(cdk.NestedStack): - def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None: - super().__init__(scope, construct_id, **kwargs) - - # Python Runtime - # Layers - python_layer_version = PythonLayerVersion( - self, - "PythonLayerVersion", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - entry="../../src/python/layers/PythonLayerVersion", - ) - layer_version = lambda1.LayerVersion( - self, - "LayerVersion", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - code=lambda1.Code.from_asset("../../src/python/layers/LayerVersion"), - ) - # add SAM metadata to build layer - cfn_layer_version = cast(CfnLayerVersion, layer_version.node.default_child) - cfn_layer_version.add_metadata("BuildMethod", "python3.7") - - # ZIP package type Functions - # Functions Built by CDK - Runtime Functions Constructs - nested_python_function = PythonFunction( - self, - "NestedPythonFunction", - entry="../../src/python/NestedPythonFunctionConstruct", - index="app.py", - handler="lambda_handler", - runtime=lambda1.Runtime.PYTHON_3_9, - layers=[python_layer_version, layer_version], - tracing=lambda1.Tracing.ACTIVE, - ) - http_api = HttpApi(self, "httpAPi") - - http_api.add_routes( - path="/httpapis/nestedPythonFunction", - methods=[HttpMethod.GET], - integration=HttpLambdaIntegration("httpApiRandomNameIntegration", nested_python_function), - ) diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/python/python/python_stack.py b/tests/iac_integration/cdk/testdata/cdk_v1/python/python/python_stack.py deleted file mode 100644 index 78b884883f..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/python/python/python_stack.py +++ /dev/null @@ -1,323 +0,0 @@ -import os -from pathlib import Path -from typing import cast - -from aws_cdk import core as cdk, aws_lambda as lambda1, aws_apigateway as apigw, aws_logs as logs -from aws_cdk.aws_apigateway import LambdaIntegration -from aws_cdk.aws_lambda_nodejs import NodejsFunction, BundlingOptions as NodeJsBundlingOptions -from aws_cdk.aws_lambda_go import GoFunction, BundlingOptions as GoBundlingOptions -from aws_cdk.aws_lambda_python import PythonFunction, PythonLayerVersion -from aws_cdk.aws_iam import Role, ServicePrincipal, PolicyStatement -from aws_cdk.aws_lambda import CfnFunction, CfnLayerVersion -from aws_cdk.core import BundlingOptions - -from .nested_stack import NestedStack1 - - -class PythonStack(cdk.Stack): - def __init__(self, scope: cdk.Construct, construct_id: str, **kwargs) -> None: - super().__init__(scope, construct_id, **kwargs) - - # Python Runtime - # Layers - python_layer_version = PythonLayerVersion( - self, - "PythonLayerVersion", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - entry="../../src/python/layers/PythonLayerVersion", - ) - layer_version = lambda1.LayerVersion( - self, - "LayerVersion", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - code=lambda1.Code.from_asset("../../src/python/layers/LayerVersion"), - ) - # add SAM metadata to build layer - cfn_layer_version = cast(CfnLayerVersion, layer_version.node.default_child) - cfn_layer_version.add_metadata("BuildMethod", "python3.7") - - # Lambda LayerVersion with bundled Asset that will be built by CDK - bundled_layer_version_python_runtime = lambda1.LayerVersion( - self, - "BundledLayerVersionPythonRuntime", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - code=lambda1.Code.from_asset( - "../../src/python/layers/BundledLayerVersion", - bundling=BundlingOptions( - command=[ - "/bin/sh", - "-c", - "rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && mkdir /asset-output/python && cp -R /tmp/asset-input/* /asset-output/python", - ], - image=lambda1.Runtime.PYTHON_3_7.bundling_image, - user="root", - ), - ), - ) - - # ZIP package type Functions - # Functions Built by CDK - Runtime Functions Constructs - python_function = PythonFunction( - self, - "PythonFunction", - entry="../../src/python/PythonFunctionConstruct", - index="app.py", - handler="lambda_handler", - runtime=lambda1.Runtime.PYTHON_3_9, - function_name="pythonFunc", # we need the name to use it in the API definition file - log_retention=logs.RetentionDays.THREE_MONTHS, - layers=[python_layer_version, layer_version], - tracing=lambda1.Tracing.ACTIVE, - ) - - # Normal Lambda Function Construct - Python Runtime - function_python_runtime = lambda1.Function( - self, - "FunctionPythonRuntime", - runtime=lambda1.Runtime.PYTHON_3_7, - code=lambda1.Code.from_asset("../../src/python/FunctionConstruct"), - handler="app.lambda_handler", - layers=[python_layer_version, layer_version], - tracing=lambda1.Tracing.ACTIVE, - ) - - # Normal Lambda Function Construct - Python Runtime - with skip build metadata - pre_built_function_python_runtime = lambda1.Function( - self, - "PreBuiltFunctionPythonRuntime", - runtime=lambda1.Runtime.PYTHON_3_7, - code=lambda1.Code.from_asset("../../src/python/BuiltFunctionConstruct"), - handler="app.lambda_handler", - layers=[python_layer_version, layer_version], - tracing=lambda1.Tracing.ACTIVE, - ) - # add SkipBuild Metadata, so SAM will skip building self function - cfn_pre_built_function_python_runtime = cast(CfnFunction, pre_built_function_python_runtime.node.default_child) - cfn_pre_built_function_python_runtime.add_metadata("SkipBuild", True) - - # Normal Lambda Function with bundled Asset will be built by CDK - bundled_function_python_runtime = lambda1.Function( - self, - "BundledFunctionPythonRuntime", - runtime=lambda1.Runtime.PYTHON_3_7, - code=lambda1.Code.from_asset( - "../../src/python/BundledFunctionConstruct/", - bundling=BundlingOptions( - command=[ - "/bin/sh", - "-c", - "rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && cp -R /tmp/asset-input/* /asset-output", - ], - image=lambda1.Runtime.PYTHON_3_7.bundling_image, - user="root", - ), - ), - handler="app.lambda_handler", - layers=[ - bundled_layer_version_python_runtime, - python_layer_version, - ], - timeout=cdk.Duration.seconds(120), - tracing=lambda1.Tracing.ACTIVE, - ) - - # NodeJs Runtime - # Layers - layer_version_node_js_runtime = lambda1.LayerVersion( - self, - "LayerVersionNodeJsRuntime", - compatible_runtimes=[ - lambda1.Runtime.NODEJS_14_X, - ], - code=lambda1.Code.from_asset("../../src/nodejs/layers/LayerVersion"), - ) - # add SAM metadata to build layer - cfn_layer_version_node_js_runtime = cast(CfnLayerVersion, layer_version_node_js_runtime.node.default_child) - cfn_layer_version_node_js_runtime.add_metadata("BuildMethod", "nodejs14.x") - - nodejs_function = NodejsFunction( - self, - "NodejsFunction", - runtime=lambda1.Runtime.NODEJS_14_X, - entry=os.path.join( - Path(__file__).resolve().parents[0], "../../../src/nodejs/NodeJsFunctionConstruct/app.ts" - ), - deps_lock_file_path=os.path.join( - Path(__file__).resolve().parents[0], "../../../src/nodejs/NodeJsFunctionConstruct/package-lock.json" - ), - bundling=NodeJsBundlingOptions( - external_modules=["/opt/nodejs/layer_version_dependency"], - ), - handler="lambdaHandler", - layers=[layer_version_node_js_runtime], - tracing=lambda1.Tracing.ACTIVE, - ) - - # Normal Lambda Function Construct - NodeJs Runtime - function_node_js_runtime = lambda1.Function( - self, - "FunctionNodeJsRuntime", - runtime=lambda1.Runtime.NODEJS_14_X, - code=lambda1.Code.from_asset("../../src/nodejs/FunctionConstruct"), - handler="app.lambdaHandler", - layers=[layer_version_node_js_runtime], - tracing=lambda1.Tracing.ACTIVE, - ) - - # Normal Lambda Function Construct - NodeJs Runtime - with skip build metadata - pre_built_function_node_js_runtime = lambda1.Function( - self, - "PreBuiltFunctionNodeJsRuntime", - runtime=lambda1.Runtime.NODEJS_14_X, - code=lambda1.Code.from_asset("../../src/nodejs/BuiltFunctionConstruct"), - handler="app.lambdaHandler", - layers=[layer_version_node_js_runtime], - tracing=lambda1.Tracing.ACTIVE, - ) - # add SkipBuild Metadata, so SAM will skip building self function - cfn_pre_built_function_node_js_runtime = cast( - CfnFunction, pre_built_function_node_js_runtime.node.default_child - ) - cfn_pre_built_function_node_js_runtime.add_metadata("SkipBuild", True) - - # Go Runtime - go_function = GoFunction( - self, - "GoFunction", - entry="../../src/go/GoFunctionConstruct", - bundling=GoBundlingOptions( - forced_docker_bundling=True, - ), - ) - - # Normal Lambda Function Construct - Go Runtime - function_go_runtime = lambda1.Function( - self, - "FunctionGoRuntime", - runtime=lambda1.Runtime.GO_1_X, - code=lambda1.Code.from_asset("../../src/go/FunctionConstruct"), - handler="FunctionConstruct", - ) - - # Image Package Type Functions - - # One way to define an Image Package Type Function - docker_image_function = lambda1.DockerImageFunction( - self, - "DockerImageFunction", - code=lambda1.DockerImageCode.from_image_asset( - directory="../../src/docker/DockerImageFunctionConstruct", - file="Dockerfile", - ), - tracing=lambda1.Tracing.ACTIVE, - ) - - # another way - function_image_asset = lambda1.Function( - self, - "FunctionImageAsset", - code=lambda1.Code.from_asset_image( - directory="../../src/docker/FunctionConstructWithImageAssetCode", - file="Dockerfile", - ), - handler=lambda1.Handler.FROM_IMAGE, - runtime=lambda1.Runtime.FROM_IMAGE, - tracing=lambda1.Tracing.ACTIVE, - ) - - # both ways work when 'file' is a path via subfolders to the Dockerfile - # this is useful when multiple docker images share some common code - docker_image_function_with_shared_code = lambda1.DockerImageFunction( - self, - "DockerImageFunctionWithSharedCode", - code=lambda1.DockerImageCode.from_image_asset( - directory="../../src/docker/ImagesWithSharedCode", - file="DockerImageFunctionWithSharedCode/Dockerfile", - ), - tracing=lambda1.Tracing.ACTIVE, - ) - - function_image_asset_with_shared_code = lambda1.Function( - self, - "FunctionImageAssetWithSharedCode", - code=lambda1.Code.from_asset_image( - directory="../../src/docker/ImagesWithSharedCode", - file="FunctionImageAssetWithSharedCode/Dockerfile", - ), - handler=lambda1.Handler.FROM_IMAGE, - runtime=lambda1.Runtime.FROM_IMAGE, - tracing=lambda1.Tracing.ACTIVE, - ) - - # Rest APIs - - # Spec Rest Api - apigw.SpecRestApi( - self, - "SpecRestAPI", - api_definition=apigw.ApiDefinition.from_asset("../../src/rest-api-definition.yaml"), - ) - - # Role to be used as credentials for the Spec rest APi - # it is used inside the spec rest api definition file - Role( - self, - "SpecRestApiRole", - assumed_by=ServicePrincipal("apigateway.amazonaws.com"), - role_name="SpecRestApiRole", - ).add_to_policy( - PolicyStatement( - actions=["lambda:InvokeFunction"], - resources=["*"], - ) - ) - - # Rest Api - rest_api = apigw.RestApi(self, "RestAPI") - normal_root_resource = rest_api.root.add_resource("restapis").add_resource("normal") - normal_root_resource.add_resource("pythonFunction").add_method("GET", LambdaIntegration(python_function)) - normal_root_resource.add_resource("functionPythonRuntime").add_method( - "GET", LambdaIntegration(function_python_runtime) - ) - normal_root_resource.add_resource("preBuiltFunctionPythonRuntime").add_method( - "GET", LambdaIntegration(pre_built_function_python_runtime) - ) - normal_root_resource.add_resource("bundledFunctionPythonRuntime").add_method( - "GET", LambdaIntegration(bundled_function_python_runtime) - ) - normal_root_resource.add_resource("nodejsFunction").add_method("GET", LambdaIntegration(nodejs_function)) - normal_root_resource.add_resource("functionNodeJsRuntime").add_method( - "GET", LambdaIntegration(function_node_js_runtime) - ) - normal_root_resource.add_resource("preBuiltFunctionNodeJsRuntime").add_method( - "GET", LambdaIntegration(pre_built_function_node_js_runtime) - ) - normal_root_resource.add_resource("goFunction").add_method("GET", LambdaIntegration(go_function)) - normal_root_resource.add_resource("functionGoRuntime").add_method("GET", LambdaIntegration(function_go_runtime)) - normal_root_resource.add_resource("dockerImageFunction").add_method( - "GET", LambdaIntegration(docker_image_function) - ) - normal_root_resource.add_resource("functionImageAsset").add_method( - "GET", LambdaIntegration(function_image_asset) - ) - normal_root_resource.add_resource("dockerImageFunctionWithSharedCode").add_method( - "GET", LambdaIntegration(docker_image_function_with_shared_code) - ) - normal_root_resource.add_resource("functionImageAssetWithSharedCode").add_method( - "GET", LambdaIntegration(function_image_asset_with_shared_code) - ) - - # Nested Stack - NestedStack1(self, "NestedStack") diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/python/requirements.txt b/tests/iac_integration/cdk/testdata/cdk_v1/python/requirements.txt deleted file mode 100644 index dcde485867..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/python/requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -aws-cdk.aws-apigateway<2.0.0 -aws-cdk.aws-apigatewayv2<2.0.0 -aws-cdk.aws-apigatewayv2-integrations<2.0.0 -aws-cdk.aws-lambda<2.0.0 -aws-cdk.aws-lambda-go<2.0.0 -aws-cdk.aws-lambda-nodejs<2.0.0 -aws-cdk.aws-lambda-python<2.0.0 -aws-cdk.core<2.0.0 \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/bin/test-app.ts b/tests/iac_integration/cdk/testdata/cdk_v1/typescript/bin/test-app.ts deleted file mode 100644 index 277298f8bb..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/bin/test-app.ts +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node -import * as cdk from '@aws-cdk/core'; -import { CDKSupportDemoRootStack } from '../lib/test-stack'; - -const app = new cdk.App(); -new CDKSupportDemoRootStack(app, 'TestStack'); -app.synth(); \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/cdk.json b/tests/iac_integration/cdk/testdata/cdk_v1/typescript/cdk.json deleted file mode 100644 index 608fbbc197..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/cdk.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "app": "npm install && npx ts-node --prefer-ts-exts bin/test-app.ts", - "context": { - - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/lib/nested-stack.ts b/tests/iac_integration/cdk/testdata/cdk_v1/typescript/lib/nested-stack.ts deleted file mode 100644 index fc3416e2c8..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/lib/nested-stack.ts +++ /dev/null @@ -1,58 +0,0 @@ -import * as cdk from '@aws-cdk/core'; -import * as lambda from '@aws-cdk/aws-lambda'; -import { HttpApi, HttpMethod } from '@aws-cdk/aws-apigatewayv2'; -import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations'; -import { PythonFunction, PythonLayerVersion } from '@aws-cdk/aws-lambda-python'; -import {CfnLayerVersion} from "@aws-cdk/aws-lambda"; - -export class NestedStack1 extends cdk.NestedStack { - - constructor(scope: cdk.Construct, id: string, props?: cdk.NestedStackProps) { - super(scope, id, props); - - // Python Runtime - // Layers - const pythonLayerVersion = new PythonLayerVersion(this, 'PythonLayerVersion', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - entry: '../../src/python/layers/PythonLayerVersion', - }); - const layerVersion = new lambda.LayerVersion(this, 'LayerVersion', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - code: lambda.Code.fromAsset('../../src/python/layers/LayerVersion'), - }); - // add SAM metadata to build layer - const cfnLayerVersion = layerVersion.node.defaultChild as CfnLayerVersion; - cfnLayerVersion.addMetadata('BuildMethod', 'python3.7'); - - // ZIP package type Functions - // Functions Built by CDK - Runtime Functions Constructs - const nestedPythonFunction = new PythonFunction(this, 'NestedPythonFunction', { - entry: '../../src/python/NestedPythonFunctionConstruct', - index: 'app.py', - handler: 'lambda_handler', - runtime: lambda.Runtime.PYTHON_3_9, - layers: [pythonLayerVersion, layerVersion], - tracing: lambda.Tracing.ACTIVE, - }); - - const httpApi = new HttpApi(this, 'httpAPi', { - }); - - httpApi.addRoutes({ - path: '/httpapis/nestedPythonFunction', - methods: [HttpMethod.GET], - integration: new HttpLambdaIntegration('httpApiRandomNameIntegration', - nestedPythonFunction, {} - ), - }); - - } -} \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/lib/test-stack.ts b/tests/iac_integration/cdk/testdata/cdk_v1/typescript/lib/test-stack.ts deleted file mode 100644 index 9396cdb663..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/lib/test-stack.ts +++ /dev/null @@ -1,279 +0,0 @@ -import * as path from 'path'; -import * as cdk from '@aws-cdk/core'; -import * as lambda from '@aws-cdk/aws-lambda'; -import * as apigw from '@aws-cdk/aws-apigateway'; -import { LambdaIntegration} from '@aws-cdk/aws-apigateway'; -import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs'; -import { GoFunction } from '@aws-cdk/aws-lambda-go'; -import { PythonFunction, PythonLayerVersion } from '@aws-cdk/aws-lambda-python'; -import {Role, ServicePrincipal, PolicyStatement} from '@aws-cdk/aws-iam'; -import { CfnFunction, CfnLayerVersion } from '@aws-cdk/aws-lambda'; -import {NestedStack1} from './nested-stack'; -import * as logs from '@aws-cdk/aws-logs'; - -export class CDKSupportDemoRootStack extends cdk.Stack { - constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - // Python Runtime - // Layers - const pythonLayerVersion = new PythonLayerVersion(this, 'PythonLayerVersion', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - entry: '../../src/python/layers/PythonLayerVersion', - }); - const layerVersion = new lambda.LayerVersion(this, 'LayerVersion', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - code: lambda.Code.fromAsset('../../src/python/layers/LayerVersion'), - }); - // add SAM metadata to build layer - const cfnLayerVersion = layerVersion.node.defaultChild as CfnLayerVersion; - cfnLayerVersion.addMetadata('BuildMethod', 'python3.7'); - - // Lambda LayerVersion with bundled Asset that will be built by CDK - const bundledLayerVersionPythonRuntime = new lambda.LayerVersion(this, 'BundledLayerVersionPythonRuntime', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - code: lambda.Code.fromAsset('../../src/python/layers/BundledLayerVersion', { - bundling: { - command: [ - '/bin/sh', - '-c', - 'rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && mkdir /asset-output/python && cp -R /tmp/asset-input/* /asset-output/python', - ], - image: lambda.Runtime.PYTHON_3_7.bundlingImage, - user: 'root', - } - }), - }); - - // ZIP package type Functions - // Functions Built by CDK - Runtime Functions Constructs - const pythonFunction = new PythonFunction(this, 'PythonFunction', { - entry: '../../src/python/PythonFunctionConstruct', - index: 'app.py', - handler: 'lambda_handler', - runtime: lambda.Runtime.PYTHON_3_9, - functionName: 'pythonFunc', // we need the name to use it in the API definition file - logRetention: logs.RetentionDays.THREE_MONTHS, - layers: [pythonLayerVersion, layerVersion], - tracing: lambda.Tracing.ACTIVE, - }); - - // Normal Lambda Function Construct - Python Runtime - const functionPythonRuntime = new lambda.Function(this, 'FunctionPythonRuntime', { - runtime: lambda.Runtime.PYTHON_3_7, - code: lambda.Code.fromAsset('../../src/python/FunctionConstruct'), - handler: 'app.lambda_handler', - layers: [pythonLayerVersion, layerVersion], - tracing: lambda.Tracing.ACTIVE, - }); - - // Normal Lambda Function Construct - Python Runtime - with skip build metadata - const preBuiltFunctionPythonRuntime = new lambda.Function(this, 'PreBuiltFunctionPythonRuntime', { - runtime: lambda.Runtime.PYTHON_3_7, - code: lambda.Code.fromAsset('../../src/python/BuiltFunctionConstruct'), - handler: 'app.lambda_handler', - layers: [pythonLayerVersion, layerVersion], - tracing: lambda.Tracing.ACTIVE, - }); - // add SkipBuild Metadata, so SAM will skip building this function - const cfnPreBuiltFunctionPythonRuntime = preBuiltFunctionPythonRuntime.node.defaultChild as CfnFunction; - cfnPreBuiltFunctionPythonRuntime.addMetadata('SkipBuild', true); - - // Normal Lambda Function with bundled Asset will be built by CDK - const bundledFunctionPythonRuntime = new lambda.Function(this, 'BundledFunctionPythonRuntime', { - runtime: lambda.Runtime.PYTHON_3_7, - code: lambda.Code.fromAsset('../../src/python/BundledFunctionConstruct/', { - bundling: { - command: [ - '/bin/sh', - '-c', - 'rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && cp -R /tmp/asset-input/* /asset-output', - ], - image: lambda.Runtime.PYTHON_3_7.bundlingImage, - user: 'root', - } - }), - handler: "app.lambda_handler", - layers: [bundledLayerVersionPythonRuntime, pythonLayerVersion], - timeout: cdk.Duration.seconds(120), - tracing: lambda.Tracing.ACTIVE, - }); - - // NodeJs Runtime - //Layers - const layerVersionNodeJsRuntime = new lambda.LayerVersion(this, 'LayerVersionNodeJsRuntime', { - compatibleRuntimes: [ - lambda.Runtime.NODEJS_14_X, - ], - code: lambda.Code.fromAsset('../../src/nodejs/layers/LayerVersion'), - }); - // add SAM metadata to build layer - const cfnLayerVersionNodeJsRuntime = layerVersionNodeJsRuntime.node.defaultChild as CfnLayerVersion; - cfnLayerVersionNodeJsRuntime.addMetadata('BuildMethod', 'nodejs14.x'); - - const nodejsFunction = new NodejsFunction(this, 'NodejsFunction', { - runtime: lambda.Runtime.NODEJS_14_X, - entry: path.join(__dirname, '../../../src/nodejs/NodeJsFunctionConstruct/app.ts'), - depsLockFilePath: path.join(__dirname, '../../../src/nodejs/NodeJsFunctionConstruct/package-lock.json'), - bundling: { - externalModules: ['/opt/nodejs/layer_version_dependency'], - }, - handler: 'lambdaHandler', - layers: [layerVersionNodeJsRuntime], - tracing: lambda.Tracing.ACTIVE, - }); - - // Normal Lambda Function Construct - NodeJs Runtime - const functionNodeJsRuntime = new lambda.Function(this, 'FunctionNodeJsRuntime', { - runtime: lambda.Runtime.NODEJS_14_X, - code: lambda.Code.fromAsset('../../src/nodejs/FunctionConstruct'), - handler: 'app.lambdaHandler', - layers: [layerVersionNodeJsRuntime], - tracing: lambda.Tracing.ACTIVE, - }); - - // Normal Lambda Function Construct - NodeJs Runtime - with skip build metadata - const preBuiltFunctionNodeJsRuntime = new lambda.Function(this, 'PreBuiltFunctionNodeJsRuntime', { - runtime: lambda.Runtime.NODEJS_14_X, - code: lambda.Code.fromAsset('../../src/nodejs/BuiltFunctionConstruct'), - handler: 'app.lambdaHandler', - layers: [layerVersionNodeJsRuntime], - tracing: lambda.Tracing.ACTIVE, - }); - // add SkipBuild Metadata, so SAM will skip building this function - const cfnPreBuiltFunctionNodeJsRuntime = preBuiltFunctionNodeJsRuntime.node.defaultChild as CfnFunction; - cfnPreBuiltFunctionNodeJsRuntime.addMetadata('SkipBuild', true); - - // Go Runtime - - const goFunction = new GoFunction(this, 'GoFunction', { - entry: '../../src/go/GoFunctionConstruct', - bundling: { - forcedDockerBundling: true, - }, - }); - - // Normal Lambda Function Construct - Go Runtime - const functionGoRuntime = new lambda.Function(this, 'FunctionGoRuntime', { - runtime: lambda.Runtime.GO_1_X, - code: lambda.Code.fromAsset('../../src/go/FunctionConstruct'), - handler: 'FunctionConstruct', - }); - - // Image Package Type Functions - - // One way to define an Image Package Type Function - const dockerImageFunction = new lambda.DockerImageFunction(this, "DockerImageFunction", { - code: lambda.DockerImageCode.fromImageAsset('../../src/docker/DockerImageFunctionConstruct', { - file: 'Dockerfile', - }), - tracing: lambda.Tracing.ACTIVE, - }); - - // another way - const functionImageAsset = new lambda.Function(this, "FunctionImageAsset", { - code: lambda.Code.fromAssetImage('../../src/docker/FunctionConstructWithImageAssetCode', { - file: 'Dockerfile', - }), - handler: lambda.Handler.FROM_IMAGE, - runtime: lambda.Runtime.FROM_IMAGE, - tracing: lambda.Tracing.ACTIVE, - }); - - // both ways work when 'file' is a path via subfolders to the Dockerfile - // this is useful when multiple docker images share some common code - const dockerImageFunctionWithSharedCode = new lambda.DockerImageFunction(this, "DockerImageFunctionWithSharedCode", { - code: lambda.DockerImageCode.fromImageAsset("../../src/docker/ImagesWithSharedCode", { - file: "DockerImageFunctionWithSharedCode/Dockerfile", - }), - tracing: lambda.Tracing.ACTIVE, - }); - - // another way - const functionImageAssetWithSharedCode = new lambda.Function(this, "FunctionImageAssetWithSharedCode", { - code: lambda.Code.fromAssetImage("../../src/docker/ImagesWithSharedCode", { - file: "FunctionImageAssetWithSharedCode/Dockerfile", - }), - handler: lambda.Handler.FROM_IMAGE, - runtime: lambda.Runtime.FROM_IMAGE, - tracing: lambda.Tracing.ACTIVE, - }); - - - //Rest APIs - - // Spec Rest Api - new apigw.SpecRestApi(this, 'SpecRestAPI', { - apiDefinition: apigw.ApiDefinition.fromAsset('../../src/rest-api-definition.yaml'), - }); - - // Role to be used as credentials for the Spec rest APi - // it is used inside the spec rest api definition file - new Role(this, 'SpecRestApiRole', { - assumedBy: new ServicePrincipal('apigateway.amazonaws.com'), - roleName: 'SpecRestApiRole', - }).addToPolicy(new PolicyStatement({ - actions: ['lambda:InvokeFunction'], - resources: ['*'], - })); - - // Rest Api - const restApi = new apigw.RestApi(this, 'RestAPI', {}); - const normalRootResource = restApi.root.addResource('restapis') - .addResource('normal'); - - normalRootResource.addResource('pythonFunction') - .addMethod('GET', new LambdaIntegration(pythonFunction)); - - normalRootResource.addResource('functionPythonRuntime') - .addMethod('GET', new LambdaIntegration(functionPythonRuntime)); - - normalRootResource.addResource('preBuiltFunctionPythonRuntime') - .addMethod('GET', new LambdaIntegration(preBuiltFunctionPythonRuntime)); - - normalRootResource.addResource('bundledFunctionPythonRuntime') - .addMethod('GET', new LambdaIntegration(bundledFunctionPythonRuntime)); - - normalRootResource.addResource('nodejsFunction') - .addMethod('GET', new LambdaIntegration(nodejsFunction)); - - normalRootResource.addResource('functionNodeJsRuntime') - .addMethod('GET', new LambdaIntegration(functionNodeJsRuntime)); - - normalRootResource.addResource('preBuiltFunctionNodeJsRuntime') - .addMethod('GET', new LambdaIntegration(preBuiltFunctionNodeJsRuntime)); - - normalRootResource.addResource('goFunction') - .addMethod('GET', new LambdaIntegration(goFunction)); - - normalRootResource.addResource('functionGoRuntime') - .addMethod('GET', new LambdaIntegration(functionGoRuntime)); - - normalRootResource.addResource('dockerImageFunction') - .addMethod('GET', new LambdaIntegration(dockerImageFunction)); - - normalRootResource.addResource('functionImageAsset') - .addMethod('GET', new LambdaIntegration(functionImageAsset)); - - normalRootResource.addResource('dockerImageFunctionWithSharedCode') - .addMethod('GET', new LambdaIntegration(dockerImageFunctionWithSharedCode)); - - normalRootResource.addResource('functionImageAssetWithSharedCode') - .addMethod('GET', new LambdaIntegration(functionImageAssetWithSharedCode)); - - // Nested Stack - new NestedStack1(this, 'NestedStack' ,{}); - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/package.json b/tests/iac_integration/cdk/testdata/cdk_v1/typescript/package.json deleted file mode 100644 index 5f405323e1..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "api-cors-issue", - "version": "0.1.0", - "bin": { - "api-cors-issue": "bin/api-cors-issue.js" - }, - "scripts": { - "build": "tsc", - "cdk": "cdk" - }, - "devDependencies": { - "@aws-cdk/assert": "<2.0.0", - "@types/node": "10.17.27", - "aws-cdk": "<2.0.0", - "ts-node": "^9.0.0", - "typescript": "~3.9.7" - }, - "dependencies": { - "@aws-cdk/aws-apigateway": "<2.0.0", - "@aws-cdk/aws-apigatewayv2": "<2.0.0", - "@aws-cdk/aws-apigatewayv2-integrations": "<2.0.0", - "@aws-cdk/aws-lambda": "<2.0.0", - "@aws-cdk/aws-lambda-go": "<2.0.0", - "@aws-cdk/aws-lambda-nodejs": "<2.0.0", - "@aws-cdk/aws-lambda-python": "<2.0.0", - "@aws-cdk/core": "<2.0.0" - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/tsconfig.json b/tests/iac_integration/cdk/testdata/cdk_v1/typescript/tsconfig.json deleted file mode 100644 index 9f8e8beabd..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v1/typescript/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2018", - "module": "commonjs", - "lib": [ - "es2018" - ], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, - "strictPropertyInitialization": false, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "cdk.out" - ] -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/java/cdk.json b/tests/iac_integration/cdk/testdata/cdk_v2/java/cdk.json deleted file mode 100644 index d981a1fd3c..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/java/cdk.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "app": "mvn versions:use-latest-versions -DallowMajorUpdates=false && mvn -e -q compile exec:java", - "context": { - - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/java/pom.xml b/tests/iac_integration/cdk/testdata/cdk_v2/java/pom.xml deleted file mode 100644 index 8c55e74136..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/java/pom.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - 4.0.0 - - com.myorg - java - 0.1 - - - UTF-8 - - - - - - org.codehaus.mojo - versions-maven-plugin - 2.7 - - - software.amazon.awscdk:* - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - - org.codehaus.mojo - exec-maven-plugin - 3.0.0 - - com.myorg.JavaApp - - - - - - - - - software.amazon.awscdk - aws-cdk-lib - 2.0.0 - - - software.amazon.awscdk - apigatewayv2-alpha - 2.0.0-alpha.0 - - - software.amazon.awscdk - apigatewayv2-integrations-alpha - 2.0.0-alpha.0 - - - software.amazon.awscdk - lambda-go-alpha - 2.0.0-alpha.0 - - - software.amazon.awscdk - lambda-python-alpha - 2.0.0-alpha.0 - - - software.constructs - constructs - 10.0.0 - - - diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/JavaApp.java b/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/JavaApp.java deleted file mode 100644 index dee8a0e85a..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/JavaApp.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.myorg; - -import software.amazon.awscdk.App; -import software.amazon.awscdk.StackProps; - -import java.util.Arrays; - -public class JavaApp { - public static void main(final String[] args) { - App app = new App(); - - new JavaStack(app, "TestStack", StackProps.builder() - .build()); - - app.synth(); - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/JavaStack.java b/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/JavaStack.java deleted file mode 100644 index dbf9232001..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/JavaStack.java +++ /dev/null @@ -1,270 +0,0 @@ -package com.myorg; - -import software.amazon.awscdk.*; -import software.amazon.awscdk.services.apigateway.Resource; -import software.amazon.awscdk.services.apigateway.*; -import software.amazon.awscdk.services.iam.PolicyStatement; -import software.amazon.awscdk.services.iam.Role; -import software.amazon.awscdk.services.iam.ServicePrincipal; -import software.amazon.awscdk.services.lambda.Runtime; -import software.amazon.awscdk.services.lambda.*; -import software.amazon.awscdk.services.lambda.go.alpha.GoFunction; -import software.amazon.awscdk.services.lambda.nodejs.NodejsFunction; -import software.amazon.awscdk.services.lambda.python.alpha.PythonFunction; -import software.amazon.awscdk.services.lambda.python.alpha.PythonLayerVersion; -import software.amazon.awscdk.services.logs.RetentionDays; -import software.amazon.awscdk.services.s3.assets.AssetOptions; -import software.constructs.Construct; -import com.myorg.NestedStack1; - -import java.util.Arrays; - -public class JavaStack extends Stack { - public JavaStack(final Construct scope, final String id) { - this(scope, id, null); - } - - public JavaStack(final Construct scope, final String id, final StackProps props) { - super(scope, id, props); - - // Python Runtime - // Layers - PythonLayerVersion pythonLayerVersion = PythonLayerVersion.Builder - .create(this, "PythonLayerVersion") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .entry("../../src/python/layers/PythonLayerVersion") - .build(); - - LayerVersion layerVersion = LayerVersion.Builder - .create(this, "LayerVersion") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .code(Code.fromAsset("../../src/python/layers/LayerVersion")) - .build(); - // add SAM metadata to build layer - CfnLayerVersion cfnLayerVersion = (CfnLayerVersion) layerVersion.getNode().getDefaultChild(); - cfnLayerVersion.addMetadata("BuildMethod", "python3.7"); - - // Lambda LayerVersion with bundled Asset that will be built by CDK - LayerVersion bundledLayerVersionPythonRuntime = LayerVersion.Builder - .create(this, "BundledLayerVersionPythonRuntime") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .code(Code.fromAsset("../../src/python/layers/BundledLayerVersion", - AssetOptions.builder().bundling( - BundlingOptions.builder() - .image(Runtime.PYTHON_3_7.getBundlingImage()) - .command(Arrays.asList( - "/bin/sh", - "-c", - "rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input" + - " && pip install -r requirements.txt -t . && mkdir /asset-output/python && " + - "cp -R /tmp/asset-input/* /asset-output/python" - )).build() - ).build() - )).build(); - - // ZIP package type Functions - // Functions Built by CDK - Runtime Functions Constructs - PythonFunction pythonFunction = PythonFunction.Builder - .create(this, "PythonFunction") - .entry("../../src/python/PythonFunctionConstruct") - .index("app.py") - .handler("lambda_handler") - .runtime(Runtime.PYTHON_3_9) - .functionName("pythonFunc") // we need the name to use it in the API definition file - .logRetention(RetentionDays.THREE_MONTHS) - .layers(Arrays.asList(pythonLayerVersion, layerVersion)) - .tracing(Tracing.ACTIVE) - .build(); - - // Normal Lambda Function Construct - Python Runtime - Function functionPythonRuntime = Function.Builder.create(this, "FunctionPythonRuntime") - .runtime(Runtime.PYTHON_3_7) - .code(Code.fromAsset("../../src/python/FunctionConstruct")) - .handler("app.lambda_handler") - .layers(Arrays.asList(pythonLayerVersion, layerVersion)) - .tracing(Tracing.ACTIVE) - .build(); - - // Normal Lambda Function Construct - Python Runtime - with skip build metadata - Function preBuiltFunctionPythonRuntime = Function.Builder.create(this, "PreBuiltFunctionPythonRuntime") - .runtime(Runtime.PYTHON_3_7) - .code(Code.fromAsset("../../src/python/BuiltFunctionConstruct")) - .handler("app.lambda_handler") - .layers(Arrays.asList(pythonLayerVersion, layerVersion)) - .tracing(Tracing.ACTIVE) - .build(); - // add SkipBuild Metadata, so SAM will skip building this function - CfnFunction cfnPreBuiltFunctionPythonRuntime = (CfnFunction) preBuiltFunctionPythonRuntime.getNode() - .getDefaultChild(); - cfnPreBuiltFunctionPythonRuntime.addMetadata("SkipBuild", true); - - // Normal Lambda Function with bundled Asset will be built by CDK - Function bundledFunctionPythonRuntime = Function.Builder.create(this, "BundledFunctionPythonRuntime") - .runtime(Runtime.PYTHON_3_7) - .code(Code.fromAsset("../../src/python/BundledFunctionConstruct/", - AssetOptions.builder().bundling( - BundlingOptions.builder() - .command(Arrays.asList("/bin/sh", "-c", "rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && cp -R /tmp/asset-input/* /asset-output")) - .image(Runtime.PYTHON_3_7.getBundlingImage()) - .build() - ).build() - )) - .handler("app.lambda_handler") - .layers(Arrays.asList(bundledLayerVersionPythonRuntime, pythonLayerVersion)) - .timeout(Duration.seconds(120)) - .tracing(Tracing.ACTIVE) - .build(); - - // NodeJs Runtime - //Layers - LayerVersion layerVersionNodeJsRuntime = LayerVersion.Builder.create(this, "LayerVersionNodeJsRuntime") - .compatibleRuntimes(Arrays.asList(Runtime.NODEJS_14_X)) - .code(Code.fromAsset("../../src/nodejs/layers/LayerVersion")) - .build(); - // add SAM metadata to build layer - CfnLayerVersion cfnLayerVersionNodeJsRuntime = (CfnLayerVersion) layerVersionNodeJsRuntime.getNode().getDefaultChild(); - cfnLayerVersionNodeJsRuntime.addMetadata("BuildMethod", "nodejs14.x"); - - NodejsFunction nodejsFunction = NodejsFunction.Builder.create(this, "NodejsFunction") - .runtime(Runtime.NODEJS_14_X) - .entry("../../src/nodejs/NodeJsFunctionConstruct/app.ts") - .depsLockFilePath("../../src/nodejs/NodeJsFunctionConstruct/package-lock.json") - .handler("lambdaHandler") - .layers(Arrays.asList(layerVersionNodeJsRuntime)) - .tracing(Tracing.ACTIVE) - .bundling(software.amazon.awscdk.services.lambda.nodejs.BundlingOptions.builder() - .externalModules( - Arrays.asList("/opt/nodejs/layer_version_dependency") - ).build() - ).build(); - - // Normal Lambda Function Construct - NodeJs Runtime - Function functionNodeJsRuntime = Function.Builder.create(this, "FunctionNodeJsRuntime") - .runtime(Runtime.NODEJS_14_X) - .code(Code.fromAsset("../../src/nodejs/FunctionConstruct")) - .handler("app.lambdaHandler") - .layers(Arrays.asList(layerVersionNodeJsRuntime)) - .tracing(Tracing.ACTIVE) - .build(); - - // Normal Lambda Function Construct - NodeJs Runtime - with skip build metadata - Function preBuiltFunctionNodeJsRuntime = Function.Builder.create(this, "PreBuiltFunctionNodeJsRuntime") - .runtime(Runtime.NODEJS_14_X) - .code(Code.fromAsset("../../src/nodejs/BuiltFunctionConstruct")) - .handler("app.lambdaHandler") - .layers(Arrays.asList(layerVersionNodeJsRuntime)) - .tracing(Tracing.ACTIVE) - .build(); - // add SkipBuild Metadata, so SAM will skip building this function - CfnFunction cfnPreBuiltFunctionNodeJsRuntime = (CfnFunction) preBuiltFunctionNodeJsRuntime.getNode().getDefaultChild(); - cfnPreBuiltFunctionNodeJsRuntime.addMetadata("SkipBuild", true); - - // Go Runtime - GoFunction goFunction = GoFunction.Builder.create(this, "GoFunction") - .entry("../../src/go/GoFunctionConstruct") - .bundling(software.amazon.awscdk.services.lambda.go.alpha.BundlingOptions.builder() - .forcedDockerBundling(true).build()) - .build(); - - // Normal Lambda Function Construct - Go Runtime - Function functionGoRuntime = Function.Builder.create(this, "FunctionGoRuntime") - .runtime(Runtime.GO_1_X) - .code(Code.fromAsset("../../src/go/FunctionConstruct")) - .handler("FunctionConstruct") - .build(); - - // Image Package Type Functions - // One way to define an Image Package Type Function - DockerImageFunction dockerImageFunction = DockerImageFunction.Builder.create(this, "DockerImageFunction") - .code(DockerImageCode.fromImageAsset("../../src/docker/DockerImageFunctionConstruct", - AssetImageCodeProps.builder().file("Dockerfile").build() - ) - ).tracing(Tracing.ACTIVE) - .build(); - - // another way - Function functionImageAsset = Function.Builder.create(this, "FunctionImageAsset") - .code(Code.fromAssetImage("../../src/docker/FunctionConstructWithImageAssetCode", - AssetImageCodeProps.builder().file("Dockerfile").build())) - .handler(Handler.FROM_IMAGE) - .runtime(Runtime.FROM_IMAGE) - .tracing(Tracing.ACTIVE) - .build(); - - // both ways work when 'file' is a path via subfolders to the Dockerfile - // this is useful when multiple docker images share some common code - DockerImageFunction dockerImageFunctionWithSharedCode = DockerImageFunction.Builder.create(this, "DockerImageFunctionWithSharedCode") - .code(DockerImageCode.fromImageAsset("../../src/docker/ImagesWithSharedCode", - AssetImageCodeProps.builder().file("DockerImageFunctionWithSharedCode/Dockerfile").build() - ) - ).tracing(Tracing.ACTIVE) - .build(); - - Function functionImageAssetWithSharedCode = Function.Builder.create(this, "FunctionImageAssetWithSharedCode") - .code(Code.fromAssetImage("../../src/docker/ImagesWithSharedCode", - AssetImageCodeProps.builder().file("FunctionImageAssetWithSharedCode/Dockerfile").build())) - .handler(Handler.FROM_IMAGE) - .runtime(Runtime.FROM_IMAGE) - .tracing(Tracing.ACTIVE) - .build(); - - //Rest APIs - - // Spec Rest Api - SpecRestApi.Builder.create(this, "SpecRestAPI") - .apiDefinition(ApiDefinition.fromAsset("../../src/rest-api-definition.yaml")) - .build(); - - // Role to be used as credentials for the Spec rest APi - // it is used inside the spec rest api definition file - Role.Builder.create(this, "SpecRestApiRole") - .assumedBy(new ServicePrincipal("apigateway.amazonaws.com")) - .roleName("SpecRestApiRole") - .build() - .addToPolicy( - PolicyStatement.Builder.create() - .actions(Arrays.asList("lambda:InvokeFunction")) - .resources(Arrays.asList("*")) - .build() - ); - - // Rest Api - RestApi restApi = new RestApi(this, "RestAPI"); - Resource normalRootResource = restApi.getRoot().addResource("restapis") - .addResource("normal"); - - normalRootResource.addResource("pythonFunction") - .addMethod("GET", new LambdaIntegration(pythonFunction)); - normalRootResource.addResource("functionPythonRuntime") - .addMethod("GET", new LambdaIntegration(functionPythonRuntime)); - normalRootResource.addResource("preBuiltFunctionPythonRuntime") - .addMethod("GET", new LambdaIntegration(preBuiltFunctionPythonRuntime)); - normalRootResource.addResource("bundledFunctionPythonRuntime") - .addMethod("GET", new LambdaIntegration(bundledFunctionPythonRuntime)); - normalRootResource.addResource("nodejsFunction") - .addMethod("GET", new LambdaIntegration(nodejsFunction)); - normalRootResource.addResource("functionNodeJsRuntime") - .addMethod("GET", new LambdaIntegration(functionNodeJsRuntime)); - normalRootResource.addResource("preBuiltFunctionNodeJsRuntime") - .addMethod("GET", new LambdaIntegration(preBuiltFunctionNodeJsRuntime)); - normalRootResource.addResource("goFunction") - .addMethod("GET", new LambdaIntegration(goFunction)); - normalRootResource.addResource("functionGoRuntime") - .addMethod("GET", new LambdaIntegration(functionGoRuntime)); - normalRootResource.addResource("dockerImageFunction") - .addMethod("GET", new LambdaIntegration(dockerImageFunction)); - normalRootResource.addResource("functionImageAsset") - .addMethod("GET", new LambdaIntegration(functionImageAsset)); - normalRootResource.addResource("dockerImageFunctionWithSharedCode") - .addMethod("GET", new LambdaIntegration(dockerImageFunctionWithSharedCode)); - normalRootResource.addResource("functionImageAssetWithSharedCode") - .addMethod("GET", new LambdaIntegration(functionImageAssetWithSharedCode)); - - // Nested Stack - new NestedStack1(this, "NestedStack"); - - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/NestedStack1.java b/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/NestedStack1.java deleted file mode 100644 index 505179e22f..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/java/src/main/java/com/myorg/NestedStack1.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.myorg; - -import org.jetbrains.annotations.NotNull; -import software.amazon.awscdk.NestedStack; -import software.amazon.awscdk.services.apigatewayv2.alpha.AddRoutesOptions; -import software.amazon.awscdk.services.apigatewayv2.alpha.HttpApi; -import software.amazon.awscdk.services.apigatewayv2.alpha.HttpMethod; -import software.amazon.awscdk.services.apigatewayv2.integrations.alpha.HttpLambdaIntegration; -import software.amazon.awscdk.services.lambda.Runtime; -import software.amazon.awscdk.services.lambda.*; -import software.amazon.awscdk.services.lambda.python.alpha.PythonFunction; -import software.amazon.awscdk.services.lambda.python.alpha.PythonLayerVersion; -import software.amazon.awscdk.services.logs.RetentionDays; -import software.constructs.Construct; - -import java.util.Arrays; - -public class NestedStack1 extends NestedStack { - - public NestedStack1(@NotNull Construct scope, @NotNull String id) { - super(scope, id); - - // Python Runtime - // Layers - PythonLayerVersion pythonLayerVersion = PythonLayerVersion.Builder - .create(this, "PythonLayerVersion") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .entry("../../src/python/layers/PythonLayerVersion") - .build(); - - LayerVersion layerVersion = LayerVersion.Builder - .create(this, "LayerVersion") - .compatibleRuntimes(Arrays.asList(Runtime.PYTHON_3_7, Runtime.PYTHON_3_8, - Runtime.PYTHON_3_9)) - .code(Code.fromAsset("../../src/python/layers/LayerVersion")) - .build(); - // add SAM metadata to build layer - CfnLayerVersion cfnLayerVersion = (CfnLayerVersion) layerVersion.getNode().getDefaultChild(); - cfnLayerVersion.addMetadata("BuildMethod", "python3.7"); - - // ZIP package type Functions - // Functions Built by CDK - Runtime Functions Constructs - PythonFunction nestedPythonFunction = PythonFunction.Builder - .create(this, "NestedPythonFunction") - .entry("../../src/python/NestedPythonFunctionConstruct") - .index("app.py") - .handler("lambda_handler") - .runtime(Runtime.PYTHON_3_9) - .logRetention(RetentionDays.THREE_MONTHS) - .layers(Arrays.asList(pythonLayerVersion, layerVersion)) - .tracing(Tracing.ACTIVE) - .build(); - - HttpApi httpApi = new HttpApi(this, "httpAPi"); - - httpApi.addRoutes(AddRoutesOptions.builder() - .path("/httpapis/nestedPythonFunction") - .methods(Arrays.asList(HttpMethod.GET)) - .integration(new HttpLambdaIntegration("httpApiRandomNameIntegration", nestedPythonFunction)) - .build() - ); - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/python/app.py b/tests/iac_integration/cdk/testdata/cdk_v2/python/app.py deleted file mode 100644 index c16cdfe67a..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/python/app.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 -from aws_cdk import App - -from python.python_stack import PythonStack - - -app = App() -PythonStack(app, "TestStack") - -app.synth() diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/python/cdk.json b/tests/iac_integration/cdk/testdata/cdk_v2/python/cdk.json deleted file mode 100644 index 05b65aa6c3..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/python/cdk.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "app": "python3 -m venv .env && ./.env/bin/python -m pip install -r requirements.txt && ./.env/bin/python app.py", - "context": { - - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/python/python/__init__.py b/tests/iac_integration/cdk/testdata/cdk_v2/python/python/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/python/python/nested_stack.py b/tests/iac_integration/cdk/testdata/cdk_v2/python/python/nested_stack.py deleted file mode 100644 index 576aa882ef..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/python/python/nested_stack.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import cast - -from aws_cdk import NestedStack, aws_lambda as lambda1 -from aws_cdk.aws_apigatewayv2_alpha import HttpApi, HttpMethod -from aws_cdk.aws_apigatewayv2_integrations_alpha import HttpLambdaIntegration -from aws_cdk.aws_lambda import CfnLayerVersion -from aws_cdk.aws_lambda_python_alpha import PythonLayerVersion, PythonFunction -from constructs import Construct - - -class NestedStack1(NestedStack): - def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: - super().__init__(scope, construct_id, **kwargs) - - # Python Runtime - # Layers - python_layer_version = PythonLayerVersion( - self, - "PythonLayerVersion", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - entry="../../src/python/layers/PythonLayerVersion", - ) - layer_version = lambda1.LayerVersion( - self, - "LayerVersion", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - code=lambda1.Code.from_asset("../../src/python/layers/LayerVersion"), - ) - # add SAM metadata to build layer - cfn_layer_version = cast(CfnLayerVersion, layer_version.node.default_child) - cfn_layer_version.add_metadata("BuildMethod", "python3.7") - - # ZIP package type Functions - # Functions Built by CDK - Runtime Functions Constructs - nested_python_function = PythonFunction( - self, - "NestedPythonFunction", - entry="../../src/python/NestedPythonFunctionConstruct", - index="app.py", - handler="lambda_handler", - runtime=lambda1.Runtime.PYTHON_3_9, - layers=[python_layer_version, layer_version], - tracing=lambda1.Tracing.ACTIVE, - ) - http_api = HttpApi(self, "httpAPi") - - http_api.add_routes( - path="/httpapis/nestedPythonFunction", - methods=[HttpMethod.GET], - integration=HttpLambdaIntegration("httpApiRandomNameIntegration", nested_python_function), - ) diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/python/python/python_stack.py b/tests/iac_integration/cdk/testdata/cdk_v2/python/python/python_stack.py deleted file mode 100644 index b38a23e4af..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/python/python/python_stack.py +++ /dev/null @@ -1,323 +0,0 @@ -import os -from pathlib import Path -from typing import cast - -from aws_cdk import Stack, Duration, BundlingOptions, aws_lambda as lambda1, aws_apigateway as apigw, aws_logs as logs -from aws_cdk.aws_apigateway import LambdaIntegration -from aws_cdk.aws_lambda_nodejs import NodejsFunction, BundlingOptions as NodeJsBundlingOptions -from aws_cdk.aws_lambda_go_alpha import GoFunction, BundlingOptions as GoBundlingOptions -from aws_cdk.aws_lambda_python_alpha import PythonFunction, PythonLayerVersion -from aws_cdk.aws_iam import Role, ServicePrincipal, PolicyStatement -from aws_cdk.aws_lambda import CfnFunction, CfnLayerVersion -from constructs import Construct - -from .nested_stack import NestedStack1 - - -class PythonStack(Stack): - def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: - super().__init__(scope, construct_id, **kwargs) - - # Python Runtime - # Layers - python_layer_version = PythonLayerVersion( - self, - "PythonLayerVersion", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - entry="../../src/python/layers/PythonLayerVersion", - ) - layer_version = lambda1.LayerVersion( - self, - "LayerVersion", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - code=lambda1.Code.from_asset("../../src/python/layers/LayerVersion"), - ) - # add SAM metadata to build layer - cfn_layer_version = cast(CfnLayerVersion, layer_version.node.default_child) - cfn_layer_version.add_metadata("BuildMethod", "python3.7") - - # Lambda LayerVersion with bundled Asset that will be built by CDK - bundled_layer_version_python_runtime = lambda1.LayerVersion( - self, - "BundledLayerVersionPythonRuntime", - compatible_runtimes=[ - lambda1.Runtime.PYTHON_3_7, - lambda1.Runtime.PYTHON_3_8, - lambda1.Runtime.PYTHON_3_9, - ], - code=lambda1.Code.from_asset( - "../../src/python/layers/BundledLayerVersion", - bundling=BundlingOptions( - command=[ - "/bin/sh", - "-c", - "rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && mkdir /asset-output/python && cp -R /tmp/asset-input/* /asset-output/python", - ], - image=lambda1.Runtime.PYTHON_3_7.bundling_image, - user="root", - ), - ), - ) - - # ZIP package type Functions - # Functions Built by CDK - Runtime Functions Constructs - python_function = PythonFunction( - self, - "PythonFunction", - entry="../../src/python/PythonFunctionConstruct", - index="app.py", - handler="lambda_handler", - runtime=lambda1.Runtime.PYTHON_3_9, - function_name="pythonFunc", # we need the name to use it in the API definition file - log_retention=logs.RetentionDays.THREE_MONTHS, - layers=[python_layer_version, layer_version], - tracing=lambda1.Tracing.ACTIVE, - ) - - # Normal Lambda Function Construct - Python Runtime - function_python_runtime = lambda1.Function( - self, - "FunctionPythonRuntime", - runtime=lambda1.Runtime.PYTHON_3_7, - code=lambda1.Code.from_asset("../../src/python/FunctionConstruct"), - handler="app.lambda_handler", - layers=[python_layer_version, layer_version], - tracing=lambda1.Tracing.ACTIVE, - ) - - # Normal Lambda Function Construct - Python Runtime - with skip build metadata - pre_built_function_python_runtime = lambda1.Function( - self, - "PreBuiltFunctionPythonRuntime", - runtime=lambda1.Runtime.PYTHON_3_7, - code=lambda1.Code.from_asset("../../src/python/BuiltFunctionConstruct"), - handler="app.lambda_handler", - layers=[python_layer_version, layer_version], - tracing=lambda1.Tracing.ACTIVE, - ) - # add SkipBuild Metadata, so SAM will skip building self function - cfn_pre_built_function_python_runtime = cast(CfnFunction, pre_built_function_python_runtime.node.default_child) - cfn_pre_built_function_python_runtime.add_metadata("SkipBuild", True) - - # Normal Lambda Function with bundled Asset will be built by CDK - bundled_function_python_runtime = lambda1.Function( - self, - "BundledFunctionPythonRuntime", - runtime=lambda1.Runtime.PYTHON_3_7, - code=lambda1.Code.from_asset( - "../../src/python/BundledFunctionConstruct/", - bundling=BundlingOptions( - command=[ - "/bin/sh", - "-c", - "rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && cp -R /tmp/asset-input/* /asset-output", - ], - image=lambda1.Runtime.PYTHON_3_7.bundling_image, - user="root", - ), - ), - handler="app.lambda_handler", - layers=[ - bundled_layer_version_python_runtime, - python_layer_version, - ], - timeout=Duration.seconds(120), - tracing=lambda1.Tracing.ACTIVE, - ) - - # NodeJs Runtime - # Layers - layer_version_node_js_runtime = lambda1.LayerVersion( - self, - "LayerVersionNodeJsRuntime", - compatible_runtimes=[ - lambda1.Runtime.NODEJS_14_X, - ], - code=lambda1.Code.from_asset("../../src/nodejs/layers/LayerVersion"), - ) - # add SAM metadata to build layer - cfn_layer_version_node_js_runtime = cast(CfnLayerVersion, layer_version_node_js_runtime.node.default_child) - cfn_layer_version_node_js_runtime.add_metadata("BuildMethod", "nodejs14.x") - - nodejs_function = NodejsFunction( - self, - "NodejsFunction", - runtime=lambda1.Runtime.NODEJS_14_X, - entry=os.path.join( - Path(__file__).resolve().parents[0], "../../../src/nodejs/NodeJsFunctionConstruct/app.ts" - ), - deps_lock_file_path=os.path.join( - Path(__file__).resolve().parents[0], "../../../src/nodejs/NodeJsFunctionConstruct/package-lock.json" - ), - bundling=NodeJsBundlingOptions( - external_modules=["/opt/nodejs/layer_version_dependency"], - ), - handler="lambdaHandler", - layers=[layer_version_node_js_runtime], - tracing=lambda1.Tracing.ACTIVE, - ) - - # Normal Lambda Function Construct - NodeJs Runtime - function_node_js_runtime = lambda1.Function( - self, - "FunctionNodeJsRuntime", - runtime=lambda1.Runtime.NODEJS_14_X, - code=lambda1.Code.from_asset("../../src/nodejs/FunctionConstruct"), - handler="app.lambdaHandler", - layers=[layer_version_node_js_runtime], - tracing=lambda1.Tracing.ACTIVE, - ) - - # Normal Lambda Function Construct - NodeJs Runtime - with skip build metadata - pre_built_function_node_js_runtime = lambda1.Function( - self, - "PreBuiltFunctionNodeJsRuntime", - runtime=lambda1.Runtime.NODEJS_14_X, - code=lambda1.Code.from_asset("../../src/nodejs/BuiltFunctionConstruct"), - handler="app.lambdaHandler", - layers=[layer_version_node_js_runtime], - tracing=lambda1.Tracing.ACTIVE, - ) - # add SkipBuild Metadata, so SAM will skip building self function - cfn_pre_built_function_node_js_runtime = cast( - CfnFunction, pre_built_function_node_js_runtime.node.default_child - ) - cfn_pre_built_function_node_js_runtime.add_metadata("SkipBuild", True) - - # Go Runtime - go_function = GoFunction( - self, - "GoFunction", - entry="../../src/go/GoFunctionConstruct", - bundling=GoBundlingOptions( - forced_docker_bundling=True, - ), - ) - - # Normal Lambda Function Construct - Go Runtime - function_go_runtime = lambda1.Function( - self, - "FunctionGoRuntime", - runtime=lambda1.Runtime.GO_1_X, - code=lambda1.Code.from_asset("../../src/go/FunctionConstruct"), - handler="FunctionConstruct", - ) - - # Image Package Type Functions - - # One way to define an Image Package Type Function - docker_image_function = lambda1.DockerImageFunction( - self, - "DockerImageFunction", - code=lambda1.DockerImageCode.from_image_asset( - directory="../../src/docker/DockerImageFunctionConstruct", - file="Dockerfile", - ), - tracing=lambda1.Tracing.ACTIVE, - ) - - # another way - function_image_asset = lambda1.Function( - self, - "FunctionImageAsset", - code=lambda1.Code.from_asset_image( - directory="../../src/docker/FunctionConstructWithImageAssetCode", - file="Dockerfile", - ), - handler=lambda1.Handler.FROM_IMAGE, - runtime=lambda1.Runtime.FROM_IMAGE, - tracing=lambda1.Tracing.ACTIVE, - ) - - # both ways work when 'file' is a path via subfolders to the Dockerfile - # this is useful when multiple docker images share some common code - docker_image_function_with_shared_code = lambda1.DockerImageFunction( - self, - "DockerImageFunctionWithSharedCode", - code=lambda1.DockerImageCode.from_image_asset( - directory="../../src/docker/ImagesWithSharedCode", - file="DockerImageFunctionWithSharedCode/Dockerfile", - ), - tracing=lambda1.Tracing.ACTIVE, - ) - - function_image_asset_with_shared_code = lambda1.Function( - self, - "FunctionImageAssetWithSharedCode", - code=lambda1.Code.from_asset_image( - directory="../../src/docker/ImagesWithSharedCode", - file="FunctionImageAssetWithSharedCode/Dockerfile", - ), - handler=lambda1.Handler.FROM_IMAGE, - runtime=lambda1.Runtime.FROM_IMAGE, - tracing=lambda1.Tracing.ACTIVE, - ) - - # Rest APIs - - # Spec Rest Api - apigw.SpecRestApi( - self, - "SpecRestAPI", - api_definition=apigw.ApiDefinition.from_asset("../../src/rest-api-definition.yaml"), - ) - - # Role to be used as credentials for the Spec rest APi - # it is used inside the spec rest api definition file - Role( - self, - "SpecRestApiRole", - assumed_by=ServicePrincipal("apigateway.amazonaws.com"), - role_name="SpecRestApiRole", - ).add_to_policy( - PolicyStatement( - actions=["lambda:InvokeFunction"], - resources=["*"], - ) - ) - - # Rest Api - rest_api = apigw.RestApi(self, "RestAPI") - normal_root_resource = rest_api.root.add_resource("restapis").add_resource("normal") - normal_root_resource.add_resource("pythonFunction").add_method("GET", LambdaIntegration(python_function)) - normal_root_resource.add_resource("functionPythonRuntime").add_method( - "GET", LambdaIntegration(function_python_runtime) - ) - normal_root_resource.add_resource("preBuiltFunctionPythonRuntime").add_method( - "GET", LambdaIntegration(pre_built_function_python_runtime) - ) - normal_root_resource.add_resource("bundledFunctionPythonRuntime").add_method( - "GET", LambdaIntegration(bundled_function_python_runtime) - ) - normal_root_resource.add_resource("nodejsFunction").add_method("GET", LambdaIntegration(nodejs_function)) - normal_root_resource.add_resource("functionNodeJsRuntime").add_method( - "GET", LambdaIntegration(function_node_js_runtime) - ) - normal_root_resource.add_resource("preBuiltFunctionNodeJsRuntime").add_method( - "GET", LambdaIntegration(pre_built_function_node_js_runtime) - ) - normal_root_resource.add_resource("goFunction").add_method("GET", LambdaIntegration(go_function)) - normal_root_resource.add_resource("functionGoRuntime").add_method("GET", LambdaIntegration(function_go_runtime)) - normal_root_resource.add_resource("dockerImageFunction").add_method( - "GET", LambdaIntegration(docker_image_function) - ) - normal_root_resource.add_resource("functionImageAsset").add_method( - "GET", LambdaIntegration(function_image_asset) - ) - normal_root_resource.add_resource("dockerImageFunctionWithSharedCode").add_method( - "GET", LambdaIntegration(docker_image_function_with_shared_code) - ) - normal_root_resource.add_resource("functionImageAssetWithSharedCode").add_method( - "GET", LambdaIntegration(function_image_asset_with_shared_code) - ) - - # Nested Stack - NestedStack1(self, "NestedStack") diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/python/requirements.txt b/tests/iac_integration/cdk/testdata/cdk_v2/python/requirements.txt deleted file mode 100644 index 5c81fd6f2d..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/python/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -aws-cdk-lib -constructs>=10.0.0 -aws-cdk.aws-apigatewayv2-alpha -aws-cdk.aws-apigatewayv2-integrations-alpha -aws-cdk.aws-lambda-go-alpha -aws-cdk.aws-lambda-python-alpha \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/bin/test-app.ts b/tests/iac_integration/cdk/testdata/cdk_v2/typescript/bin/test-app.ts deleted file mode 100644 index d16b2299c9..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/bin/test-app.ts +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node -import * as cdk from 'aws-cdk-lib'; -import { CDKSupportDemoRootStack } from '../lib/test-stack'; - -const app = new cdk.App(); -new CDKSupportDemoRootStack(app, 'TestStack'); -app.synth(); \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/cdk.json b/tests/iac_integration/cdk/testdata/cdk_v2/typescript/cdk.json deleted file mode 100644 index 608fbbc197..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/cdk.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "app": "npm install && npx ts-node --prefer-ts-exts bin/test-app.ts", - "context": { - - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/lib/nested-stack.ts b/tests/iac_integration/cdk/testdata/cdk_v2/typescript/lib/nested-stack.ts deleted file mode 100644 index f877e8a3db..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/lib/nested-stack.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import * as lambda from 'aws-cdk-lib/aws-lambda'; -import { HttpApi, HttpMethod } from '@aws-cdk/aws-apigatewayv2-alpha'; -import { HttpLambdaIntegration } from '@aws-cdk/aws-apigatewayv2-integrations-alpha'; -import { PythonFunction, PythonLayerVersion } from '@aws-cdk/aws-lambda-python-alpha'; -import { Construct } from 'constructs'; -import {CfnLayerVersion} from 'aws-cdk-lib/aws-lambda'; - -export class NestedStack1 extends cdk.NestedStack { - - constructor(scope: Construct, id: string, props?: cdk.NestedStackProps) { - super(scope, id, props); - - // Python Runtime - // Layers - const pythonLayerVersion = new PythonLayerVersion(this, 'PythonLayerVersion', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - entry: '../../src/python/layers/PythonLayerVersion', - }); - const layerVersion = new lambda.LayerVersion(this, 'LayerVersion', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - code: lambda.Code.fromAsset('../../src/python/layers/LayerVersion'), - }); - // add SAM metadata to build layer - const cfnLayerVersion = layerVersion.node.defaultChild as CfnLayerVersion; - cfnLayerVersion.addMetadata('BuildMethod', 'python3.7'); - - // ZIP package type Functions - // Functions Built by CDK - Runtime Functions Constructs - const nestedPythonFunction = new PythonFunction(this, 'NestedPythonFunction', { - entry: '../../src/python/NestedPythonFunctionConstruct', - index: 'app.py', - handler: 'lambda_handler', - runtime: lambda.Runtime.PYTHON_3_9, - layers: [pythonLayerVersion, layerVersion], - tracing: lambda.Tracing.ACTIVE, - }); - - const httpApi = new HttpApi(this, 'httpAPi', { - }); - - httpApi.addRoutes({ - path: '/httpapis/nestedPythonFunction', - methods: [HttpMethod.GET], - integration: new HttpLambdaIntegration('httpApiRandomNameIntegration', - nestedPythonFunction, {} - ), - }); - - } -} \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/lib/test-stack.ts b/tests/iac_integration/cdk/testdata/cdk_v2/typescript/lib/test-stack.ts deleted file mode 100644 index 7c4ff3e67d..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/lib/test-stack.ts +++ /dev/null @@ -1,278 +0,0 @@ -import * as path from 'path'; -import * as cdk from 'aws-cdk-lib'; -import * as lambda from 'aws-cdk-lib/aws-lambda'; -import * as apigw from 'aws-cdk-lib/aws-apigateway'; -import { LambdaIntegration} from 'aws-cdk-lib/aws-apigateway'; -import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; -import { GoFunction } from '@aws-cdk/aws-lambda-go-alpha'; -import { PythonFunction, PythonLayerVersion } from '@aws-cdk/aws-lambda-python-alpha'; -import {Role, ServicePrincipal, PolicyStatement} from 'aws-cdk-lib/aws-iam'; -import { CfnFunction, CfnLayerVersion } from 'aws-cdk-lib/aws-lambda'; -import {NestedStack1} from './nested-stack'; -import { Construct } from 'constructs'; -import * as logs from 'aws-cdk-lib/aws-logs'; - -export class CDKSupportDemoRootStack extends cdk.Stack { - constructor(scope: Construct, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - // Python Runtime - // Layers - const pythonLayerVersion = new PythonLayerVersion(this, 'PythonLayerVersion', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - entry: '../../src/python/layers/PythonLayerVersion', - }); - const layerVersion = new lambda.LayerVersion(this, 'LayerVersion', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - code: lambda.Code.fromAsset('../../src/python/layers/LayerVersion'), - }); - // add SAM metadata to build layer - const cfnLayerVersion = layerVersion.node.defaultChild as CfnLayerVersion; - cfnLayerVersion.addMetadata('BuildMethod', 'python3.7'); - - const bundledLayerVersionPythonRuntime = new lambda.LayerVersion(this, 'BundledLayerVersionPythonRuntime', { - compatibleRuntimes: [ - lambda.Runtime.PYTHON_3_7, - lambda.Runtime.PYTHON_3_8, - lambda.Runtime.PYTHON_3_9, - ], - code: lambda.Code.fromAsset('../../src/python/layers/BundledLayerVersion', { - bundling: { - command: [ - '/bin/sh', - '-c', - 'rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && mkdir /asset-output/python && cp -R /tmp/asset-input/* /asset-output/python', - ], - image: lambda.Runtime.PYTHON_3_7.bundlingImage, - user: 'root', - } - }), - }); - - // ZIP package type Functions - // Functions Built by CDK - Runtime Functions Constructs - const pythonFunction = new PythonFunction(this, 'PythonFunction', { - entry: '../../src/python/PythonFunctionConstruct', - index: 'app.py', - handler: 'lambda_handler', - runtime: lambda.Runtime.PYTHON_3_9, - functionName: 'pythonFunc', // we need the name to use it in the API definition file - logRetention: logs.RetentionDays.THREE_MONTHS, - layers: [pythonLayerVersion, layerVersion], - tracing: lambda.Tracing.ACTIVE, - }); - - // Normal Lambda Function Construct - Python Runtime - const functionPythonRuntime = new lambda.Function(this, 'FunctionPythonRuntime', { - runtime: lambda.Runtime.PYTHON_3_7, - code: lambda.Code.fromAsset('../../src/python/FunctionConstruct'), - handler: 'app.lambda_handler', - layers: [pythonLayerVersion, layerVersion], - tracing: lambda.Tracing.ACTIVE, - }); - - // Normal Lambda Function Construct - Python Runtime - with skip build metadata - const preBuiltFunctionPythonRuntime = new lambda.Function(this, 'PreBuiltFunctionPythonRuntime', { - runtime: lambda.Runtime.PYTHON_3_7, - code: lambda.Code.fromAsset('../../src/python/BuiltFunctionConstruct'), - handler: 'app.lambda_handler', - layers: [pythonLayerVersion, layerVersion], - tracing: lambda.Tracing.ACTIVE, - }); - // add SkipBuild Metadata, so SAM will skip building this function - const cfnPreBuiltFunctionPythonRuntime = preBuiltFunctionPythonRuntime.node.defaultChild as CfnFunction; - cfnPreBuiltFunctionPythonRuntime.addMetadata('SkipBuild', true); - - const bundledFunctionPythonRuntime = new lambda.Function(this, 'BundledFunctionPythonRuntime', { - runtime: lambda.Runtime.PYTHON_3_7, - code: lambda.Code.fromAsset('../../src/python/BundledFunctionConstruct/', { - bundling: { - command: [ - '/bin/sh', - '-c', - 'rm -rf /tmp/asset-input && mkdir /tmp/asset-input && cp * /tmp/asset-input && cd /tmp/asset-input && pip install -r requirements.txt -t . && cp -R /tmp/asset-input/* /asset-output', - ], - image: lambda.Runtime.PYTHON_3_7.bundlingImage, - user: 'root', - } - }), - handler: "app.lambda_handler", - layers: [bundledLayerVersionPythonRuntime, pythonLayerVersion], - timeout: cdk.Duration.seconds(120), - tracing: lambda.Tracing.ACTIVE, - }); - - // NodeJs Runtime - //Layers - const layerVersionNodeJsRuntime = new lambda.LayerVersion(this, 'LayerVersionNodeJsRuntime', { - compatibleRuntimes: [ - lambda.Runtime.NODEJS_14_X, - ], - code: lambda.Code.fromAsset('../../src/nodejs/layers/LayerVersion'), - }); - // add SAM metadata to build layer - const cfnLayerVersionNodeJsRuntime = layerVersionNodeJsRuntime.node.defaultChild as CfnLayerVersion; - cfnLayerVersionNodeJsRuntime.addMetadata('BuildMethod', 'nodejs14.x'); - - const nodejsFunction = new NodejsFunction(this, 'NodejsFunction', { - runtime: lambda.Runtime.NODEJS_14_X, - entry: path.join(__dirname, '../../../src/nodejs/NodeJsFunctionConstruct/app.ts'), - depsLockFilePath: path.join(__dirname, '../../../src/nodejs/NodeJsFunctionConstruct/package-lock.json'), - bundling: { - externalModules: ['/opt/nodejs/layer_version_dependency'], - }, - handler: 'lambdaHandler', - layers: [layerVersionNodeJsRuntime], - tracing: lambda.Tracing.ACTIVE, - }); - - // Normal Lambda Function Construct - NodeJs Runtime - const functionNodeJsRuntime = new lambda.Function(this, 'FunctionNodeJsRuntime', { - runtime: lambda.Runtime.NODEJS_14_X, - code: lambda.Code.fromAsset('../../src/nodejs/FunctionConstruct'), - handler: 'app.lambdaHandler', - layers: [layerVersionNodeJsRuntime], - tracing: lambda.Tracing.ACTIVE, - }); - - // Normal Lambda Function Construct - NodeJs Runtime - with skip build metadata - const preBuiltFunctionNodeJsRuntime = new lambda.Function(this, 'PreBuiltFunctionNodeJsRuntime', { - runtime: lambda.Runtime.NODEJS_14_X, - code: lambda.Code.fromAsset('../../src/nodejs/BuiltFunctionConstruct'), - handler: 'app.lambdaHandler', - layers: [layerVersionNodeJsRuntime], - tracing: lambda.Tracing.ACTIVE, - }); - // add SkipBuild Metadata, so SAM will skip building this function - const cfnPreBuiltFunctionNodeJsRuntime = preBuiltFunctionNodeJsRuntime.node.defaultChild as CfnFunction; - cfnPreBuiltFunctionNodeJsRuntime.addMetadata('SkipBuild', true); - - // Go Runtime - - const goFunction = new GoFunction(this, 'GoFunction', { - entry: '../../src/go/GoFunctionConstruct', - bundling: { - forcedDockerBundling: true, - }, - }); - - // Normal Lambda Function Construct - Go Runtime - const functionGoRuntime = new lambda.Function(this, 'FunctionGoRuntime', { - runtime: lambda.Runtime.GO_1_X, - code: lambda.Code.fromAsset('../../src/go/FunctionConstruct'), - handler: 'FunctionConstruct', - }); - - // Image Package Type Functions - - // One way to define an Image Package Type Function - const dockerImageFunction = new lambda.DockerImageFunction(this, "DockerImageFunction", { - code: lambda.DockerImageCode.fromImageAsset('../../src/docker/DockerImageFunctionConstruct', { - file: 'Dockerfile', - }), - tracing: lambda.Tracing.ACTIVE, - }); - - // another way - const functionImageAsset = new lambda.Function(this, "FunctionImageAsset", { - code: lambda.Code.fromAssetImage('../../src/docker/FunctionConstructWithImageAssetCode', { - file: 'Dockerfile', - }), - handler: lambda.Handler.FROM_IMAGE, - runtime: lambda.Runtime.FROM_IMAGE, - tracing: lambda.Tracing.ACTIVE, - }); - - // both ways work when 'file' is a path via subfolders to the Dockerfile - // this is useful when multiple docker images share some common code - const dockerImageFunctionWithSharedCode = new lambda.DockerImageFunction(this, "DockerImageFunctionWithSharedCode", { - code: lambda.DockerImageCode.fromImageAsset("../../src/docker/ImagesWithSharedCode", { - file: "DockerImageFunctionWithSharedCode/Dockerfile", - }), - tracing: lambda.Tracing.ACTIVE, - }); - - // another way - const functionImageAssetWithSharedCode = new lambda.Function(this, "FunctionImageAssetWithSharedCode", { - code: lambda.Code.fromAssetImage("../../src/docker/ImagesWithSharedCode", { - file: "FunctionImageAssetWithSharedCode/Dockerfile", - }), - handler: lambda.Handler.FROM_IMAGE, - runtime: lambda.Runtime.FROM_IMAGE, - tracing: lambda.Tracing.ACTIVE, - }); - - - //Rest APIs - - // Spec Rest Api - new apigw.SpecRestApi(this, 'SpecRestAPI', { - apiDefinition: apigw.ApiDefinition.fromAsset('../../src/rest-api-definition.yaml'), - }); - - // Role to be used as credentials for the Spec rest APi - // it is used inside the spec rest api definition file - new Role(this, 'SpecRestApiRole', { - assumedBy: new ServicePrincipal('apigateway.amazonaws.com'), - roleName: 'SpecRestApiRole', - }).addToPolicy(new PolicyStatement({ - actions: ['lambda:InvokeFunction'], - resources: ['*'], - })); - - // Rest Api - const restApi = new apigw.RestApi(this, 'RestAPI', {}); - const normalRootResource = restApi.root.addResource('restapis') - .addResource('normal'); - - normalRootResource.addResource('pythonFunction') - .addMethod('GET', new LambdaIntegration(pythonFunction)); - - normalRootResource.addResource('functionPythonRuntime') - .addMethod('GET', new LambdaIntegration(functionPythonRuntime)); - - normalRootResource.addResource('preBuiltFunctionPythonRuntime') - .addMethod('GET', new LambdaIntegration(preBuiltFunctionPythonRuntime)); - - normalRootResource.addResource('bundledFunctionPythonRuntime') - .addMethod('GET', new LambdaIntegration(bundledFunctionPythonRuntime)); - - normalRootResource.addResource('nodejsFunction') - .addMethod('GET', new LambdaIntegration(nodejsFunction)); - - normalRootResource.addResource('functionNodeJsRuntime') - .addMethod('GET', new LambdaIntegration(functionNodeJsRuntime)); - - normalRootResource.addResource('preBuiltFunctionNodeJsRuntime') - .addMethod('GET', new LambdaIntegration(preBuiltFunctionNodeJsRuntime)); - - normalRootResource.addResource('goFunction') - .addMethod('GET', new LambdaIntegration(goFunction)); - - normalRootResource.addResource('functionGoRuntime') - .addMethod('GET', new LambdaIntegration(functionGoRuntime)); - - normalRootResource.addResource('dockerImageFunction') - .addMethod('GET', new LambdaIntegration(dockerImageFunction)); - - normalRootResource.addResource('functionImageAsset') - .addMethod('GET', new LambdaIntegration(functionImageAsset)); - - normalRootResource.addResource('dockerImageFunctionWithSharedCode') - .addMethod('GET', new LambdaIntegration(dockerImageFunctionWithSharedCode)); - - normalRootResource.addResource('functionImageAssetWithSharedCode') - .addMethod('GET', new LambdaIntegration(functionImageAssetWithSharedCode)); - - // Nested Stack - new NestedStack1(this, 'NestedStack' ,{}); - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/package.json b/tests/iac_integration/cdk/testdata/cdk_v2/typescript/package.json deleted file mode 100644 index baa0873358..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "api-cors-issue", - "version": "0.1.0", - "bin": { - "api-cors-issue": "bin/api-cors-issue.js" - }, - "scripts": { - "build": "tsc", - "cdk": "cdk" - }, - "devDependencies": { - "@aws-cdk/assert": "", - "@types/node": "10.17.27", - "aws-cdk": "", - "ts-node": "^9.0.0", - "typescript": "~3.9.7" - }, - "dependencies": { - "@aws-cdk/aws-apigatewayv2-alpha": "", - "@aws-cdk/aws-apigatewayv2-integrations-alpha": "", - "@aws-cdk/aws-lambda-go-alpha": "", - "@aws-cdk/aws-lambda-python-alpha": "", - "aws-cdk-lib": "", - "constructs": "^10.0.0" - } -} diff --git a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/tsconfig.json b/tests/iac_integration/cdk/testdata/cdk_v2/typescript/tsconfig.json deleted file mode 100644 index 9f8e8beabd..0000000000 --- a/tests/iac_integration/cdk/testdata/cdk_v2/typescript/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2018", - "module": "commonjs", - "lib": [ - "es2018" - ], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, - "strictPropertyInitialization": false, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "cdk.out" - ] -} diff --git a/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/Dockerfile b/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/Dockerfile deleted file mode 100644 index d26e4d5ea0..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM public.ecr.aws/lambda/nodejs:14 - -# Assumes your function is named "app.js", and there is a package.json file in the app directory -COPY app.js package.json ./ - -RUN npm install - -# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) -CMD [ "app.lambdaHandler" ] \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/app.js b/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/app.js deleted file mode 100644 index 2489020d66..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/app.js +++ /dev/null @@ -1,22 +0,0 @@ -var gen = require('unique-names-generator'); - -const colorName = gen.uniqueNamesGenerator({ - dictionaries: [gen.colors] -}); - -exports.lambdaHandler = async(event, context) => { - let response; - - try { - response = { - 'statusCode': 200, - 'body': JSON.stringify({ - message: "Hello World from docker image function construct", - }) - }; - } catch (err) { - console.log(err); - return err; - } - return response; -}; diff --git a/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/package.json b/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/package.json deleted file mode 100644 index 7b15f7c978..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/DockerImageFunctionConstruct/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "randomColors", - "version": "0.1.0", - "bin": { - "randomColors": "bin/app.js" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk" - }, - "dependencies": { - "unique-names-generator": "^4.6.0" - } -} diff --git a/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/Dockerfile b/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/Dockerfile deleted file mode 100644 index d26e4d5ea0..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -FROM public.ecr.aws/lambda/nodejs:14 - -# Assumes your function is named "app.js", and there is a package.json file in the app directory -COPY app.js package.json ./ - -RUN npm install - -# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) -CMD [ "app.lambdaHandler" ] \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/app.js b/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/app.js deleted file mode 100644 index ad398aff72..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/app.js +++ /dev/null @@ -1,22 +0,0 @@ -var gen = require('unique-names-generator'); - -const colorName = gen.uniqueNamesGenerator({ - dictionaries: [gen.colors] -}); - -exports.lambdaHandler = async(event, context) => { - let response; - - try { - response = { - 'statusCode': 200, - 'body': JSON.stringify({ - message: "Hello World from function construct with image asset", - }), - }; - } catch (err) { - console.log(err); - return err; - } - return response; -}; diff --git a/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/package.json b/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/package.json deleted file mode 100644 index 7b15f7c978..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/FunctionConstructWithImageAssetCode/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "randomColors", - "version": "0.1.0", - "bin": { - "randomColors": "bin/app.js" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk" - }, - "dependencies": { - "unique-names-generator": "^4.6.0" - } -} diff --git a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/DockerImageFunctionWithSharedCode/Dockerfile b/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/DockerImageFunctionWithSharedCode/Dockerfile deleted file mode 100644 index a2c99c17fd..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/DockerImageFunctionWithSharedCode/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM public.ecr.aws/lambda/nodejs:14 - -# Add the lambda handler for this feature -COPY DockerImageFunctionWithSharedCode/app.js ./ - -# Add the shared code -COPY SharedCode/ ./SharedCode - -# Add the shared dependencies -COPY package.json ./ - -# Install the dependencies -RUN npm install - -# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) -CMD [ "app.lambdaHandler" ] \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/DockerImageFunctionWithSharedCode/app.js b/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/DockerImageFunctionWithSharedCode/app.js deleted file mode 100644 index 239c7bafad..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/DockerImageFunctionWithSharedCode/app.js +++ /dev/null @@ -1,24 +0,0 @@ -var gen = require('unique-names-generator'); -const {sayHelloWorld} = require("./SharedCode/shared"); - -const colorName = gen.uniqueNamesGenerator({ - dictionaries: [gen.colors] -}); - - -exports.lambdaHandler = async(event, context) => { - let response; - - try { - response = { - 'statusCode': 200, - 'body': JSON.stringify({ - message: sayHelloWorld("docker image function construct"), - }), - }; - } catch (err) { - console.log(err); - return err; - } - return response; -}; diff --git a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/FunctionImageAssetWithSharedCode/Dockerfile b/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/FunctionImageAssetWithSharedCode/Dockerfile deleted file mode 100644 index a68a4f5978..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/FunctionImageAssetWithSharedCode/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM public.ecr.aws/lambda/nodejs:14 - -# Add the lambda handler for this feature -COPY FunctionImageAssetWithSharedCode/app.js ./ - -# Add the shared code -COPY SharedCode/ ./SharedCode - -# Add the shared dependencies -COPY package.json ./ - -# Install the dependencies -RUN npm install - -# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile) -CMD [ "app.lambdaHandler" ] \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/FunctionImageAssetWithSharedCode/app.js b/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/FunctionImageAssetWithSharedCode/app.js deleted file mode 100644 index f1dca39268..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/FunctionImageAssetWithSharedCode/app.js +++ /dev/null @@ -1,24 +0,0 @@ -var gen = require('unique-names-generator'); -const {sayHelloWorld} = require("./SharedCode/shared"); - -const colorName = gen.uniqueNamesGenerator({ - dictionaries: [gen.colors] -}); - - -exports.lambdaHandler = async(event, context) => { - let response; - - try { - response = { - 'statusCode': 200, - 'body': JSON.stringify({ - message: sayHelloWorld("function construct with image asset"), - }), - }; - } catch (err) { - console.log(err); - return err; - } - return response; -}; diff --git a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/SharedCode/shared.js b/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/SharedCode/shared.js deleted file mode 100644 index 334a8051fe..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/SharedCode/shared.js +++ /dev/null @@ -1,4 +0,0 @@ - -exports.sayHelloWorld = (from) => { - return `Hello World from ${from} with a Dockerfile that shares code with another Dockerfile`; -} diff --git a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/package.json b/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/package.json deleted file mode 100644 index 7b15f7c978..0000000000 --- a/tests/iac_integration/cdk/testdata/src/docker/ImagesWithSharedCode/package.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "randomColors", - "version": "0.1.0", - "bin": { - "randomColors": "bin/app.js" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk" - }, - "dependencies": { - "unique-names-generator": "^4.6.0" - } -} diff --git a/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/go.mod b/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/go.mod deleted file mode 100644 index ac54bd0c32..0000000000 --- a/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -require github.com/aws/aws-lambda-go v1.28.0 - -module hello-world - -go 1.15 diff --git a/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/go.sum b/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/go.sum deleted file mode 100644 index 4c65c61e81..0000000000 --- a/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/go.sum +++ /dev/null @@ -1,17 +0,0 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-lambda-go v1.28.0 h1:fZiik1PZqW2IyAN4rj+Y0UBaO1IDFlsNo9Zz/XnArK4= -github.com/aws/aws-lambda-go v1.28.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/main.go b/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/main.go deleted file mode 100644 index cc08daa959..0000000000 --- a/tests/iac_integration/cdk/testdata/src/go/FunctionConstruct/main.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "github.com/aws/aws-lambda-go/events" - "github.com/aws/aws-lambda-go/lambda" -) - -func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { - return events.APIGatewayProxyResponse{ - Body: "{\"message\": \"Hello World from function construct with go runtime\"}", - StatusCode: 200, - }, nil -} - -func main() { - lambda.Start(handler) -} diff --git a/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/go.mod b/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/go.mod deleted file mode 100644 index ac54bd0c32..0000000000 --- a/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -require github.com/aws/aws-lambda-go v1.28.0 - -module hello-world - -go 1.15 diff --git a/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/go.sum b/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/go.sum deleted file mode 100644 index 4c65c61e81..0000000000 --- a/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/go.sum +++ /dev/null @@ -1,17 +0,0 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-lambda-go v1.28.0 h1:fZiik1PZqW2IyAN4rj+Y0UBaO1IDFlsNo9Zz/XnArK4= -github.com/aws/aws-lambda-go v1.28.0/go.mod h1:jJmlefzPfGnckuHdXX7/80O3BvUUi12XOkbv4w9SGLU= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/main.go b/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/main.go deleted file mode 100644 index d1d3ac1b21..0000000000 --- a/tests/iac_integration/cdk/testdata/src/go/GoFunctionConstruct/main.go +++ /dev/null @@ -1,17 +0,0 @@ -package main - -import ( - "github.com/aws/aws-lambda-go/events" - "github.com/aws/aws-lambda-go/lambda" -) - -func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { - return events.APIGatewayProxyResponse{ - Body: "{\"message\": \"Hello World from go function construct\"}", - StatusCode: 200, - }, nil -} - -func main() { - lambda.Start(handler) -} diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/BuiltFunctionConstruct/app.js b/tests/iac_integration/cdk/testdata/src/nodejs/BuiltFunctionConstruct/app.js deleted file mode 100644 index 99f7ed6837..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/BuiltFunctionConstruct/app.js +++ /dev/null @@ -1,17 +0,0 @@ -const layer_version_dependency = require('/opt/nodejs/layer_version_dependency'); -let response; - -exports.lambdaHandler = async (event, context) => { - try { - response = { - 'statusCode': 200, - 'body': JSON.stringify({ - message: `Hello World from nodejs pre built function ${layer_version_dependency.get_dependency()}`, - }) - }; - } catch (err) { - console.log(err); - return err; - } - return response; -}; \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/FunctionConstruct/app.js b/tests/iac_integration/cdk/testdata/src/nodejs/FunctionConstruct/app.js deleted file mode 100644 index 27991e939d..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/FunctionConstruct/app.js +++ /dev/null @@ -1,23 +0,0 @@ -const unique_names_generator = require('unique-names-generator'); -const layer_version_dependency = require('/opt/nodejs/layer_version_dependency'); - -const characterName = unique_names_generator.uniqueNamesGenerator({ - dictionaries: [unique_names_generator.animals] -}); - -let response; - -exports.lambdaHandler = async (event, context) => { - try { - response = { - 'statusCode': 200, - 'body': JSON.stringify({ - message: `Hello World from function construct with nodejs runtime ${layer_version_dependency.get_dependency()}`, - }) - }; - } catch (err) { - console.log(err); - return err; - } - return response; -}; \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/FunctionConstruct/package.json b/tests/iac_integration/cdk/testdata/src/nodejs/FunctionConstruct/package.json deleted file mode 100644 index 1dc652ae81..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/FunctionConstruct/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "hello_world", - "version": "1.0.0", - "main": "app.js", - "dependencies": { - "unique-names-generator": "^4.6.0" - } -} diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/app.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/app.ts deleted file mode 100644 index 5f629a9dff..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/app.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { uniqueNamesGenerator, animals} from 'unique-names-generator'; -import {get_dependency} from '/opt/nodejs/layer_version_dependency'; - -const characterName = uniqueNamesGenerator({ - dictionaries: [animals] -}); -let response; - -exports.lambdaHandler = async(event, context) => { - try { - response = { - 'statusCode': 200, - 'body': JSON.stringify({ - message: `Hello World from nodejs function construct ${get_dependency()}`, - }) - }; - } catch (err) { - console.log(err); - return err; - } - return response; -}; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/.package-lock.json b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/.package-lock.json deleted file mode 100644 index ffbc1ed91a..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/.package-lock.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "hello_world", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "node_modules/unique-names-generator": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.6.0.tgz", - "integrity": "sha512-m0fke1emBeT96UYn2psPQYwljooDWRTKt9oUZ5vlt88ZFMBGxqwPyLHXwCfkbgdm8jzioCp7oIpo6KdM+fnUlQ==", - "engines": { - "node": ">=8" - } - } - } -} diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/LICENSE b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/LICENSE deleted file mode 100644 index a413d1ffc3..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/LICENSE +++ /dev/null @@ -1,9 +0,0 @@ -MIT License - -Copyright (c) 2018-2021 AndreaSonny (https://github.com/andreasonny83) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/README.md b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/README.md deleted file mode 100644 index 88fc351741..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/README.md +++ /dev/null @@ -1,643 +0,0 @@ -# Unique Names Generator - -[![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors-) - - -[![Build Status](https://travis-ci.com/andreasonny83/unique-names-generator.svg?branch=main)](https://travis-ci.com/andreasonny83/unique-names-generator) -[![](https://img.shields.io/npm/v/unique-names-generator.svg)](https://npmjs.org/package/unique-names-generator) -[![](https://img.shields.io/npm/l/unique-names-generator.svg)](https://github.com/andreasonny83/unique-names-generator/blob/main/LICENSE) -[![Known Vulnerabilities](https://snyk.io/test/github/andreasonny83/unique-names-generator/badge.svg?targetFile=package.json)](https://snyk.io/test/github/andreasonny83/unique-names-generator?targetFile=package.json) -[![](https://img.shields.io/npm/dt/unique-names-generator.svg)](https://npmjs.org/package/unique-names-generator) -[![devDependencies Status](https://david-dm.org/andreasonny83/unique-names-generator/dev-status.svg)](https://david-dm.org/andreasonny83/unique-names-generator?type=dev) - -[![NPM](https://nodei.co/npm/unique-names-generator.png)](https://npmjs.org/package/unique-names-generator) - -> More than 50,000,000 name combinations out of the box - -## What is Unique name generator? - -Unique name generator is a tree-shakeable Node package for generating random and unique names. - -It comes with a list of dictionaries out of the box, but you can also provide your custom ones. - -## Docs - -This documentation is for the `unique-names-generator` v4. - -If you are using a version 3.x of the library, please refer to the -[v3 Docs](https://github.com/andreasonny83/unique-names-generator/blob/v3.1.1/README.md) - -For the version 1 & 2, please refer to the -[v2 Docs](https://github.com/andreasonny83/unique-names-generator/blob/v2.0.2/README.md) - -### Migrating to v4 - -If you want to migrate, from an older version of the library to v4, please read the [Migration guide](#migration-guide) - -## Table of contents - -- [Unique Names Generator](#unique-names-generator) - - [What is Unique name generator?](#what-is-unique-name-generator) - - [Docs](#docs) - - [Migrating to v4](#migrating-to-v4) - - [Table of contents](#table-of-contents) - - [Prerequisites](#prerequisites) - - [Installation](#installation) - - [Usage](#usage) - - [Typescript support](#typescript-support) - - [API](#api) - - [uniqueNamesGenerator (options)](#uniquenamesgenerator-options) - - [options](#options) - - [dictionaries](#dictionaries) - - [separator](#separator) - - [length](#length) - - [style](#style) - - [seed](#seed) - - [Dictionaries available](#dictionaries-available) - - [Numbers](#numbers) - - [Adjectives](#adjectives) - - [Animals](#animals) - - [Colors](#colors) - - [Countries](#countries) - - [Names](#names) - - [Languages](#languages) - - [Star Wars](#star-wars) - - [Default dictionaries](#default-dictionaries) - - [Custom dictionaries](#custom-dictionaries) - - [Numbers Dictionary](#numbers-dictionary) - - [Numbers Dictionary API](#numbers-dictionary-api) - - [generate (options)](#generate-options) - - [options](#options-1) - - [min](#min) - - [max](#max) - - [length](#length-1) - - [Combining custom and provided dictionaries](#combining-custom-and-provided-dictionaries) - - [Migration guide](#migration-guide) - - [Migration guide from version 3 to version 4](#migration-guide-from-version-3-to-version-4) - - [Mandatory `dictionaries` config](#mandatory-dictionaries-config) - - [Migration guide from version 1 or 2](#migration-guide-from-version-1-or-2) - - [uniqueNamesGenerator](#uniquenamesgenerator) - - [Separator](#separator-1) - - [Short](#short) - - [Contributing](#contributing) - - [License](#license) - - [Contributors ✨](#contributors-) - -## Prerequisites - -This project requires NodeJS (at least version 6) and NPM. -[Node](http://nodejs.org/) and [NPM](https://npmjs.org/) are really easy to install. -To make sure you have them available on your machine, -try running the following command. - -```sh -$ node --version -v7.10.1 - -$ npm --version -4.2.0 -``` - -## Installation - -**BEFORE YOU INSTALL:** please read the [prerequisites](#prerequisites) - -Install the package using npm or Yarn - -```sh -$ npm i -S unique-names-generator -``` - -Or using Yarn - -```sh -$ yarn add unique-names-generator -``` - -## Usage - -```js -const { uniqueNamesGenerator, adjectives, colors, animals } = require('unique-names-generator'); - -const randomName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals] }); // big_red_donkey - -const shortName = uniqueNamesGenerator({ - dictionaries: [adjectives, animals, colors], // colors can be omitted here as not used - length: 2 -}); // big-donkey -``` - -### Typescript support - -This package export a type definition file so you can use it, out of the box, -inside your Typescript project. - -```typescript -import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator'; - -const customConfig: Config = { - dictionaries: [adjectives, colors], - separator: '-', - length: 2, -}; - -const randomName: string = uniqueNamesGenerator({ - dictionaries: [adjectives, colors, animals] -}); // big_red_donkey - -const shortName: string = uniqueNamesGenerator(customConfig); // big-donkey -``` - -## API - -### uniqueNamesGenerator (options) - -Returns a `string` with a random generated name - -### options - -Type: `Config` - -#### dictionaries - -Type: `array` - -required: `true` - -This is an array of dictionaries. Each dictionary is an array of strings containing the words to use for generating the string. - -The [provided dictionaries](#dictionaries-available) can be imported from the library as a separate modules and provided in the desired order. - -```typescript -import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator'; - -const shortName: string = uniqueNamesGenerator({ - dictionaries: [colors, adjectives, animals] -}); // red_big_donkey -``` - -Read more about the dictionaries and how to use them, in the [Dictionaries](#dictionaries-available) section. - -#### separator - -Type: `string` - -required: `false` - -Default: `_` - -A string separator to be used for separate the words generated. -The default separator is set to `_`. - -#### length - -Type: `number` - -required: `false` - -Default: `3` - -The default value is set to `3` and it will return a name composed of 3 words. -This values must be equal or minor to the number of [dictionaries](#dictionaries-available) defined (3 by default). -Setting the `length` to a value of `4` will throw an error when only 3 dictionaries are provided. - -#### style - -Type: `lowerCase | upperCase | capital` - -required: `false` - -Default: `lowerCase` - -The default value is set to `lowerCase` and it will return a lower case name. -By setting the value to `upperCase`, the words, will be returned with all the letters in upper case format. -The `capital` option will capitalize each word of the unique name generated - -```typescript -import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator'; - -const capitalizedName: string = uniqueNamesGenerator({ - dictionaries: [colors, adjectives, animals], - style: 'capital' -}); // Red_Big_Donkey - -const upperCaseName: string = uniqueNamesGenerator({ - dictionaries: [colors, adjectives, animals], - style: 'upperCase' -}); // RED_BIG_DONKEY - -const lowerCaseName: string = uniqueNamesGenerator({ - dictionaries: [colors, adjectives, animals], - style: 'lowerCase' -}); // red_big_donkey -``` - -#### seed - -Type: `number` - -required: `false` - -A seed is used when wanting to deterministically generate a name. As long as the provided seed is the same the generated name will also always be the same. - -## Dictionaries available - -#### Numbers - -This is a dynamic dictionary. Read more in the [Numbers Dictionary](#numbers-dictionary) section - -#### Adjectives - -A list of more than 1,400 adjectives ready for you to use - -```typescript -import { uniqueNamesGenerator, Config, adjectives } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [adjectives] -} - -const characterName: string = uniqueNamesGenerator(config); // big -``` - -#### Animals - -A list of more than 350 animals ready to use - -```typescript -import { uniqueNamesGenerator, Config, animals } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [animals] -} - -const characterName: string = uniqueNamesGenerator(config); // donkey -``` - -#### Colors - -A list of more than 50 different colors - -```typescript -import { uniqueNamesGenerator, Config, colors } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [colors] -} - -const characterName: string = uniqueNamesGenerator(config); // red -``` - -#### Countries - -A list of more than 250 different countries - -```typescript -import { uniqueNamesGenerator, Config, countries } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [countries] -} - -const characterName: string = uniqueNamesGenerator(config); // United Arab Emirates -``` - -#### Names - -A list of more than 4,900 unique names - -```typescript -import { uniqueNamesGenerator, Config, names } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [names] -} - -const characterName: string = uniqueNamesGenerator(config); // Winona -``` - -#### Languages - -A list of languages - -```typescript -import { uniqueNamesGenerator, Config, languages } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [languages] -} - -const characterName: string = uniqueNamesGenerator(config); // polish -``` - -#### Star Wars - -A list of more than 80 unique character names from Star Wars - -```typescript -import { uniqueNamesGenerator, Config, starWars } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [starWars] -} - -const characterName: string = uniqueNamesGenerator(config); // Han Solo -``` - -### Default dictionaries - -By default, the Unique name generator library comes with 3 dictionaries out of the box, so that you can use them -straight away. -Starting from the version 4 of the library, however, you must explicitly provide the dictionaries within the -configuration object. -This is for reducing the bundle size and allowing tree shaking to remove the extra dictionaries from your bundle when -using custom ones. - -The new syntax for using the default dictionaries is the following: - -```typescript -import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [adjectives, colors, animals] -} - -const characterName: string = uniqueNamesGenerator(config); // red_big_donkey -``` - -### Custom dictionaries - -You might want to provide your custom dictionaries to use for generating your unique names, -in order to meet your business requirements. - -You can easily do that using the [dictionaries](#dictionaries-available) option. - -```typescript -import { uniqueNamesGenerator } from 'unique-names-generator'; - -const starWarsCharacters = [ - 'Han Solo', - 'Jabba The Hutt', - 'R2-D2', - 'Luke Skywalker', - 'Princess Leia Organa' -]; -const colors = [ - 'Green', 'Red', 'Yellow', 'Black' -] - -const characterName: string = uniqueNamesGenerator({ - dictionaries: [colors, starWarsCharacters], - length: 2, - separator: ' ' -}); // Green Luke Skywalker -``` - -### Numbers Dictionary - -You can easily generate random numbers inside your unique name using the Numbers dictionary helper. - -```typescript -import { uniqueNamesGenerator, NumberDictionary } from 'unique-names-generator'; - -const numberDictionary = NumberDictionary.generate({ min: 100, max: 999 }); -const characterName: string = uniqueNamesGenerator({ -dictionaries: [['Dangerous'], ['Snake'], numberDictionary], - length: 3, - separator: '', - style: 'capital' -}); // DangerousSnake123 -``` - -## Numbers Dictionary API - -### generate (options) - -Returns a `string` with a random generated number between 1 and 999 - -### options - -Type: `Config` - -#### min - -Type: `number` - -required: `false` - -default: `1` - -The minimum value to be returned as a random number - -#### max - -Type: `number` - -required: `false` - -default: `999` - -The maximum value to be returned as a random number - -#### length - -Type: `number` - -required: `false` - -The length of the random generated number to be returned. - -Setting a length of 3 will always return a random number between `100` and `999`. This is the same as setting `100` and `999` as `min` and `max` option. - -**Note** If set, this will ignore any `min` and `max` options provided. - - -### Combining custom and provided dictionaries - -You can reuse the dictionaries provided by the library. -Just import the ones that you need and use them directly in your app. - -```typescript -import { uniqueNamesGenerator, adjectives, colors } from 'unique-names-generator'; - -const improvedAdjectives = [ - ...adjectives, - 'abrasive', - 'brash', - 'callous', - 'daft', - 'eccentric', -]; -const xMen = [ -'professorX', -'beast', -'colossus', -'cyclops', -'iceman', -'wolverine', -]; - -const characterName: string = uniqueNamesGenerator({ - dictionaries: [improvedAdjectives, color, xMen], - length: 2, - separator: '-' -}); // eccentric-blue-iceman -``` - -## Migration guide - -### Migration guide from version 3 to version 4 - -Unique names generator v4 implement a new breaking change. - -#### Mandatory `dictionaries` config - -You must now explicitly provide the library with the dictionaries to use. -This is for improving flexibility and allowing tree-shaking to remove the unused dictionaries from -your bundle size. - -Read more about the dictionaries in the [Dictionaries](dictionaries-available) section. - -**v3** - -```typescript -import { uniqueNamesGenerator } from 'unique-names-generator'; - -const randomName = uniqueNamesGenerator(); // big_red_donkey -``` - -**v4** - -```typescript -import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [adjectives, colors, animals] -} - -const randomName = uniqueNamesGenerator(config); // big_red_donkey -``` - -### Migration guide from version 1 or 2 - -Unique names generator v3 implements a couple of breaking changes. -If are upgrading your library from a version 1 or 2, you might be interested in knowing the following: - -#### uniqueNamesGenerator - -This will now work only when a `dictionaries` array is provided according to the -[v4 breaking change](#mandatory-dictionaries-config). - -**v2** - -```typescript -import { uniqueNamesGenerator } from 'unique-names-generator'; - -const randomName = uniqueNamesGenerator(); -``` - -**v4** - -```typescript -import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [adjectives, colors, animals] -} - -const randomName = uniqueNamesGenerator(config); // big_red_donkey -``` - -#### Separator - -**v2** - -```typescript -import { uniqueNamesGenerator } from 'unique-names-generator'; - -const shortName = uniqueNamesGenerator('-'); // big-red-donkey -``` - -**v4** - -```typescript -import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [adjectives, colors, animals], - separator: '-' -} - -const shortName = uniqueNamesGenerator(config); // big-red-donkey -``` - -#### Short - -The `short` property has been replaced by `length` so you can specify as many word as you want - -**v2** - -```typescript -import { uniqueNamesGenerator } from 'unique-names-generator'; - -const shortName = uniqueNamesGenerator(true); // big-donkey -``` - -**v4** - -```typescript -import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator'; - -const config: Config = { - dictionaries: [adjectives, colors, animals], - length: 2 -} - -const shortName = uniqueNamesGenerator(config); // big-donkey -``` - -## Contributing - -Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. - -1. Fork it! -2. Create your feature branch: `git checkout -b my-new-feature` -3. Add your changes: `git add .` -4. Commit your changes: `git commit -am 'Add some feature'` -5. Push to the branch: `git push origin my-new-feature` -6. Submit a pull request :sunglasses: - -## License - -[MIT License](https://andreasonny.mit-license.org/2018) © Andrea SonnY - -## Contributors ✨ - -Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): - - - - - - - - - - - - - - - - - - - -

Andrea Sonny

💻 📖 💬 📆 🤔 🖋

Abhijit Mehta

🤔

Grant Blakeman

💻 🐛

Deepak

📖 🤔

Anurag Jain

🤔

Digibake

🐛

Chase Moskal

🤔

tholst

📖

Johan Gustafsson

💻 🤔

Alex Wild

🐛 💻
- - - - - - -This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/adjectives.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/adjectives.d.ts deleted file mode 100644 index d451d2bc57..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/adjectives.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const _default: string[]; -export default _default; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/animals.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/animals.d.ts deleted file mode 100644 index d451d2bc57..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/animals.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const _default: string[]; -export default _default; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/colors.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/colors.d.ts deleted file mode 100644 index d451d2bc57..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/colors.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const _default: string[]; -export default _default; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/countries.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/countries.d.ts deleted file mode 100644 index d451d2bc57..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/countries.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const _default: string[]; -export default _default; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/index.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/index.d.ts deleted file mode 100644 index 815d810674..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/index.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import adjectives from './adjectives'; -import animals from './animals'; -import colors from './colors'; -import countries from './countries'; -import languages from './languages'; -import names from './names'; -import starWars from './star-wars'; -import { NumberDictionary } from './numbers'; -export { adjectives, animals, colors, countries, languages, names, starWars, NumberDictionary }; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/languages.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/languages.d.ts deleted file mode 100644 index d451d2bc57..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/languages.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const _default: string[]; -export default _default; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/names.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/names.d.ts deleted file mode 100644 index d451d2bc57..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/names.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const _default: string[]; -export default _default; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/numbers.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/numbers.d.ts deleted file mode 100644 index 067dcd2b0f..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/numbers.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -interface Config { - min: number; - max: number; - length?: number; -} -export declare class NumberDictionary { - static generate(config?: Partial): string[]; -} -export {}; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/star-wars.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/star-wars.d.ts deleted file mode 100644 index d451d2bc57..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/dictionaries/star-wars.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const _default: string[]; -export default _default; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.d.ts deleted file mode 100644 index ccbcfb4719..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { uniqueNamesGenerator } from './unique-names-generator'; -export { Config } from './unique-names-generator.constructor'; -export { adjectives, animals, colors, countries, languages, names, starWars, NumberDictionary, } from './dictionaries/index'; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.js b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.js deleted file mode 100644 index 5c32582261..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.js +++ /dev/null @@ -1,2 +0,0 @@ -class a{constructor(a){this.dictionaries=void 0,this.length=void 0,this.separator=void 0,this.style=void 0,this.seed=void 0;const{length:e,separator:i,dictionaries:n,style:l,seed:r}=a;this.dictionaries=n,this.separator=i,this.length=e,this.style=l,this.seed=r}generate(){if(!this.dictionaries)throw new Error('Cannot find any dictionary. Please provide at least one, or leave the "dictionary" field empty in the config object');if(this.length<=0)throw new Error("Invalid length provided");if(this.length>this.dictionaries.length)throw new Error(`The length cannot be bigger than the number of dictionaries.\nLength provided: ${this.length}. Number of dictionaries provided: ${this.dictionaries.length}`);return this.dictionaries.slice(0,this.length).reduce((a,e)=>{let i=e[Math.floor((this.seed?(n=this.seed,(a=>{a=1831565813+(a|=0)|0;let e=Math.imul(a^a>>>15,1|a);return e=e+Math.imul(e^e>>>7,61|e)^e,((e^e>>>14)>>>0)/4294967296})(n)):Math.random())*e.length)]||"";var n;if("lowerCase"===this.style)i=i.toLowerCase();else if("capital"===this.style){const[a,...e]=i.split("");i=a.toUpperCase()+e.join("")}else"upperCase"===this.style&&(i=i.toUpperCase());return a?`${a}${this.separator}${i}`:`${i}`},"")}}const e={separator:"_",dictionaries:[]};exports.NumberDictionary=class{static generate(a={}){let e=a.min||1,i=a.max||999;if(a.length){const n=Math.pow(10,a.length);return e=n/10,i=n-1,[`${Math.floor(Math.random()*(i-e))+e}`]}return[`${Math.floor(Math.random()*(i-e))+e}`]}},exports.adjectives=["able","above","absent","absolute","abstract","abundant","academic","acceptable","accepted","accessible","accurate","accused","active","actual","acute","added","additional","adequate","adjacent","administrative","adorable","advanced","adverse","advisory","aesthetic","afraid","aggregate","aggressive","agreeable","agreed","agricultural","alert","alive","alleged","allied","alone","alright","alternative","amateur","amazing","ambitious","amused","ancient","angry","annoyed","annual","anonymous","anxious","appalling","apparent","applicable","appropriate","arbitrary","architectural","armed","arrogant","artificial","artistic","ashamed","asleep","assistant","associated","atomic","attractive","automatic","autonomous","available","average","awake","aware","awful","awkward","back","bad","balanced","bare","basic","beautiful","beneficial","better","bewildered","big","binding","biological","bitter","bizarre","blank","blind","blonde","bloody","blushing","boiling","bold","bored","boring","bottom","brainy","brave","breakable","breezy","brief","bright","brilliant","broad","broken","bumpy","burning","busy","calm","capable","capitalist","careful","casual","causal","cautious","central","certain","changing","characteristic","charming","cheap","cheerful","chemical","chief","chilly","chosen","christian","chronic","chubby","circular","civic","civil","civilian","classic","classical","clean","clear","clever","clinical","close","closed","cloudy","clumsy","coastal","cognitive","coherent","cold","collective","colonial","colorful","colossal","coloured","colourful","combative","combined","comfortable","coming","commercial","common","communist","compact","comparable","comparative","compatible","competent","competitive","complete","complex","complicated","comprehensive","compulsory","conceptual","concerned","concrete","condemned","confident","confidential","confused","conscious","conservation","conservative","considerable","consistent","constant","constitutional","contemporary","content","continental","continued","continuing","continuous","controlled","controversial","convenient","conventional","convinced","convincing","cooing","cool","cooperative","corporate","correct","corresponding","costly","courageous","crazy","creative","creepy","criminal","critical","crooked","crowded","crucial","crude","cruel","cuddly","cultural","curious","curly","current","curved","cute","daily","damaged","damp","dangerous","dark","dead","deaf","deafening","dear","decent","decisive","deep","defeated","defensive","defiant","definite","deliberate","delicate","delicious","delighted","delightful","democratic","dependent","depressed","desirable","desperate","detailed","determined","developed","developing","devoted","different","difficult","digital","diplomatic","direct","dirty","disabled","disappointed","disastrous","disciplinary","disgusted","distant","distinct","distinctive","distinguished","disturbed","disturbing","diverse","divine","dizzy","domestic","dominant","double","doubtful","drab","dramatic","dreadful","driving","drunk","dry","dual","due","dull","dusty","dutch","dying","dynamic","eager","early","eastern","easy","economic","educational","eerie","effective","efficient","elaborate","elated","elderly","eldest","electoral","electric","electrical","electronic","elegant","eligible","embarrassed","embarrassing","emotional","empirical","empty","enchanting","encouraging","endless","energetic","enormous","enthusiastic","entire","entitled","envious","environmental","equal","equivalent","essential","established","estimated","ethical","ethnic","eventual","everyday","evident","evil","evolutionary","exact","excellent","exceptional","excess","excessive","excited","exciting","exclusive","existing","exotic","expected","expensive","experienced","experimental","explicit","extended","extensive","external","extra","extraordinary","extreme","exuberant","faint","fair","faithful","familiar","famous","fancy","fantastic","far","fascinating","fashionable","fast","fat","fatal","favourable","favourite","federal","fellow","female","feminist","few","fierce","filthy","final","financial","fine","firm","fiscal","fit","fixed","flaky","flat","flexible","fluffy","fluttering","flying","following","fond","foolish","foreign","formal","formidable","forthcoming","fortunate","forward","fragile","frail","frantic","free","frequent","fresh","friendly","frightened","front","frozen","full","fun","functional","fundamental","funny","furious","future","fuzzy","gastric","gay","general","generous","genetic","gentle","genuine","geographical","giant","gigantic","given","glad","glamorous","gleaming","global","glorious","golden","good","gorgeous","gothic","governing","graceful","gradual","grand","grateful","greasy","great","grieving","grim","gross","grotesque","growing","grubby","grumpy","guilty","handicapped","handsome","happy","hard","harsh","head","healthy","heavy","helpful","helpless","hidden","high","hilarious","hissing","historic","historical","hollow","holy","homeless","homely","hon","honest","horizontal","horrible","hostile","hot","huge","human","hungry","hurt","hushed","husky","icy","ideal","identical","ideological","ill","illegal","imaginative","immediate","immense","imperial","implicit","important","impossible","impressed","impressive","improved","inadequate","inappropriate","inc","inclined","increased","increasing","incredible","independent","indirect","individual","industrial","inevitable","influential","informal","inherent","initial","injured","inland","inner","innocent","innovative","inquisitive","instant","institutional","insufficient","intact","integral","integrated","intellectual","intelligent","intense","intensive","interested","interesting","interim","interior","intermediate","internal","international","intimate","invisible","involved","irrelevant","isolated","itchy","jealous","jittery","joint","jolly","joyous","judicial","juicy","junior","just","keen","key","kind","known","labour","large","late","latin","lazy","leading","left","legal","legislative","legitimate","lengthy","lesser","level","lexical","liable","liberal","light","like","likely","limited","linear","linguistic","liquid","literary","little","live","lively","living","local","logical","lonely","long","loose","lost","loud","lovely","low","loyal","ltd","lucky","mad","magic","magnetic","magnificent","main","major","male","mammoth","managerial","managing","manual","many","marginal","marine","marked","married","marvellous","marxist","mass","massive","mathematical","mature","maximum","mean","meaningful","mechanical","medical","medieval","melodic","melted","mental","mere","metropolitan","mid","middle","mighty","mild","military","miniature","minimal","minimum","ministerial","minor","miserable","misleading","missing","misty","mixed","moaning","mobile","moderate","modern","modest","molecular","monetary","monthly","moral","motionless","muddy","multiple","mushy","musical","mute","mutual","mysterious","naked","narrow","nasty","national","native","natural","naughty","naval","near","nearby","neat","necessary","negative","neighbouring","nervous","net","neutral","new","nice","noble","noisy","normal","northern","nosy","notable","novel","nuclear","numerous","nursing","nutritious","nutty","obedient","objective","obliged","obnoxious","obvious","occasional","occupational","odd","official","ok","okay","old","olympic","only","open","operational","opposite","optimistic","oral","ordinary","organic","organisational","original","orthodox","other","outdoor","outer","outrageous","outside","outstanding","overall","overseas","overwhelming","painful","pale","panicky","parallel","parental","parliamentary","partial","particular","passing","passive","past","patient","payable","peaceful","peculiar","perfect","permanent","persistent","personal","petite","philosophical","physical","plain","planned","plastic","pleasant","pleased","poised","polite","political","poor","popular","positive","possible","potential","powerful","practical","precious","precise","preferred","pregnant","preliminary","premier","prepared","present","presidential","pretty","previous","prickly","primary","prime","primitive","principal","printed","prior","private","probable","productive","professional","profitable","profound","progressive","prominent","promising","proper","proposed","prospective","protective","protestant","proud","provincial","psychiatric","psychological","public","puny","pure","purring","puzzled","quaint","qualified","quarrelsome","querulous","quick","quickest","quiet","quintessential","quixotic","racial","radical","rainy","random","rapid","rare","raspy","rational","ratty","raw","ready","real","realistic","rear","reasonable","recent","reduced","redundant","regional","registered","regular","regulatory","related","relative","relaxed","relevant","reliable","relieved","religious","reluctant","remaining","remarkable","remote","renewed","representative","repulsive","required","resident","residential","resonant","respectable","respective","responsible","resulting","retail","retired","revolutionary","rich","ridiculous","right","rigid","ripe","rising","rival","roasted","robust","rolling","romantic","rotten","rough","round","royal","rubber","rude","ruling","running","rural","sacred","sad","safe","salty","satisfactory","satisfied","scared","scary","scattered","scientific","scornful","scrawny","screeching","secondary","secret","secure","select","selected","selective","selfish","semantic","senior","sensible","sensitive","separate","serious","severe","sexual","shaggy","shaky","shallow","shared","sharp","sheer","shiny","shivering","shocked","short","shrill","shy","sick","significant","silent","silky","silly","similar","simple","single","skilled","skinny","sleepy","slight","slim","slimy","slippery","slow","small","smart","smiling","smoggy","smooth","social","socialist","soft","solar","sole","solid","sophisticated","sore","sorry","sound","sour","southern","soviet","spare","sparkling","spatial","special","specific","specified","spectacular","spicy","spiritual","splendid","spontaneous","sporting","spotless","spotty","square","squealing","stable","stale","standard","static","statistical","statutory","steady","steep","sticky","stiff","still","stingy","stormy","straight","straightforward","strange","strategic","strict","striking","striped","strong","structural","stuck","stupid","subjective","subsequent","substantial","subtle","successful","successive","sudden","sufficient","suitable","sunny","super","superb","superior","supporting","supposed","supreme","sure","surprised","surprising","surrounding","surviving","suspicious","sweet","swift","symbolic","sympathetic","systematic","tall","tame","tart","tasteless","tasty","technical","technological","teenage","temporary","tender","tense","terrible","territorial","testy","then","theoretical","thick","thin","thirsty","thorough","thoughtful","thoughtless","thundering","tight","tiny","tired","top","tory","total","tough","toxic","traditional","tragic","tremendous","tricky","tropical","troubled","typical","ugliest","ugly","ultimate","unable","unacceptable","unaware","uncertain","unchanged","uncomfortable","unconscious","underground","underlying","unemployed","uneven","unexpected","unfair","unfortunate","unhappy","uniform","uninterested","unique","united","universal","unknown","unlikely","unnecessary","unpleasant","unsightly","unusual","unwilling","upper","upset","uptight","urban","urgent","used","useful","useless","usual","vague","valid","valuable","variable","varied","various","varying","vast","verbal","vertical","very","vicarious","vicious","victorious","violent","visible","visiting","visual","vital","vitreous","vivacious","vivid","vocal","vocational","voiceless","voluminous","voluntary","vulnerable","wandering","warm","wasteful","watery","weak","wealthy","weary","wee","weekly","weird","welcome","well","western","wet","whispering","whole","wicked","wide","widespread","wild","wilful","willing","willowy","wily","wise","wispy","wittering","witty","wonderful","wooden","working","worldwide","worried","worrying","worthwhile","worthy","written","wrong","xenacious","xenial","xenogeneic","xenophobic","xeric","xerothermic","yabbering","yammering","yappiest","yappy","yawning","yearling","yearning","yeasty","yelling","yelping","yielding","yodelling","young","youngest","youthful","ytterbic","yucky","yummy","zany","zealous","zeroth","zestful","zesty","zippy","zonal","zoophagous","zygomorphic","zygotic"],exports.animals=["aardvark","aardwolf","albatross","alligator","alpaca","amphibian","anaconda","angelfish","anglerfish","ant","anteater","antelope","antlion","ape","aphid","armadillo","asp","baboon","badger","bandicoot","barnacle","barracuda","basilisk","bass","bat","bear","beaver","bedbug","bee","beetle","bird","bison","blackbird","boa","boar","bobcat","bobolink","bonobo","booby","bovid","bug","butterfly","buzzard","camel","canid","canidae","capybara","cardinal","caribou","carp","cat","caterpillar","catfish","catshark","cattle","centipede","cephalopod","chameleon","cheetah","chickadee","chicken","chimpanzee","chinchilla","chipmunk","cicada","clam","clownfish","cobra","cockroach","cod","condor","constrictor","coral","cougar","cow","coyote","crab","crane","crawdad","crayfish","cricket","crocodile","crow","cuckoo","damselfly","deer","dingo","dinosaur","dog","dolphin","donkey","dormouse","dove","dragon","dragonfly","duck","eagle","earthworm","earwig","echidna","eel","egret","elephant","elk","emu","ermine","falcon","felidae","ferret","finch","firefly","fish","flamingo","flea","fly","flyingfish","fowl","fox","frog","galliform","gamefowl","gayal","gazelle","gecko","gerbil","gibbon","giraffe","goat","goldfish","goose","gopher","gorilla","grasshopper","grouse","guan","guanaco","guineafowl","gull","guppy","haddock","halibut","hamster","hare","harrier","hawk","hedgehog","heron","herring","hippopotamus","hookworm","hornet","horse","hoverfly","hummingbird","hyena","iguana","impala","jackal","jaguar","jay","jellyfish","junglefowl","kangaroo","kingfisher","kite","kiwi","koala","koi","krill","ladybug","lamprey","landfowl","lark","leech","lemming","lemur","leopard","leopon","limpet","lion","lizard","llama","lobster","locust","loon","louse","lungfish","lynx","macaw","mackerel","magpie","mammal","manatee","mandrill","marlin","marmoset","marmot","marsupial","marten","mastodon","meadowlark","meerkat","mink","minnow","mite","mockingbird","mole","mollusk","mongoose","monkey","moose","mosquito","moth","mouse","mule","muskox","narwhal","newt","nightingale","ocelot","octopus","opossum","orangutan","orca","ostrich","otter","owl","ox","panda","panther","parakeet","parrot","parrotfish","partridge","peacock","peafowl","pelican","penguin","perch","pheasant","pig","pigeon","pike","pinniped","piranha","planarian","platypus","pony","porcupine","porpoise","possum","prawn","primate","ptarmigan","puffin","puma","python","quail","quelea","quokka","rabbit","raccoon","rat","rattlesnake","raven","reindeer","reptile","rhinoceros","roadrunner","rodent","rook","rooster","roundworm","sailfish","salamander","salmon","sawfish","scallop","scorpion","seahorse","shark","sheep","shrew","shrimp","silkworm","silverfish","skink","skunk","sloth","slug","smelt","snail","snake","snipe","sole","sparrow","spider","spoonbill","squid","squirrel","starfish","stingray","stoat","stork","sturgeon","swallow","swan","swift","swordfish","swordtail","tahr","takin","tapir","tarantula","tarsier","termite","tern","thrush","tick","tiger","tiglon","toad","tortoise","toucan","trout","tuna","turkey","turtle","tyrannosaurus","unicorn","urial","vicuna","viper","vole","vulture","wallaby","walrus","warbler","wasp","weasel","whale","whippet","whitefish","wildcat","wildebeest","wildfowl","wolf","wolverine","wombat","woodpecker","worm","wren","xerinae","yak","zebra"],exports.colors=["amaranth","amber","amethyst","apricot","aqua","aquamarine","azure","beige","black","blue","blush","bronze","brown","chocolate","coffee","copper","coral","crimson","cyan","emerald","fuchsia","gold","gray","green","harlequin","indigo","ivory","jade","lavender","lime","magenta","maroon","moccasin","olive","orange","peach","pink","plum","purple","red","rose","salmon","sapphire","scarlet","silver","tan","teal","tomato","turquoise","violet","white","yellow"],exports.countries=["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","Andorra","Angola","Anguilla","Antarctica","Antigua & Barbuda","Argentina","Armenia","Aruba","Ascension Island","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia & Herzegovina","Botswana","Brazil","British Indian Ocean Territory","British Virgin Islands","Brunei","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Canary Islands","Cape Verde","Caribbean Netherlands","Cayman Islands","Central African Republic","Ceuta & Melilla","Chad","Chile","China","Christmas Island","Cocos Islands","Colombia","Comoros","Congo","Cook Islands","Costa Rica","Côte d'Ivoire","Croatia","Cuba","Curaçao","Cyprus","Czechia","Denmark","Diego Garcia","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Eurozone","Falkland Islands","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Honduras","Hong Kong SAR China","Hungary","Iceland","India","Indonesia","Iran","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Kosovo","Kuwait","Kyrgyzstan","Laos","Latvia","Lebanon","Lesotho","Liberia","Libya","Liechtenstein","Lithuania","Luxembourg","Macau SAR China","Macedonia","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia","Moldova","Monaco","Mongolia","Montenegro","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","North Korea","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territories","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn Islands","Poland","Portugal","Puerto Rico","Qatar","Réunion","Romania","Russia","Rwanda","Samoa","San Marino","São Tomé & Príncipe","Saudi Arabia","Senegal","Serbia","Seychelles","Sierra Leone","Singapore","Sint Maarten","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia & South Sandwich Islands","South Korea","South Sudan","Spain","Sri Lanka","St. Barthélemy","St. Helena","St. Kitts & Nevis","St. Lucia","St. Martin","St. Pierre & Miquelon","St. Vincent & Grenadines","Sudan","Suriname","Svalbard & Jan Mayen","Swaziland","Sweden","Switzerland","Syria","Taiwan","Tajikistan","Tanzania","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad & Tobago","Tristan da Cunha","Tunisia","Turkey","Turkmenistan","Turks & Caicos Islands","Tuvalu","U.S. Outlying Islands","U.S. Virgin Islands","Uganda","Ukraine","United Arab Emirates","United Kingdom","United Nations","United States","Uruguay","Uzbekistan","Vanuatu","Vatican City","Venezuela","Vietnam","Wallis & Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"],exports.languages=["Akan","Amharic","Arabic","Assamese","Awadhi","Azerbaijani","Balochi","Belarusian","Bengali","Bhojpuri","Burmese","Cebuano","Chewa","Chhattisgarhi","Chittagonian","Czech","Deccan","Dhundhari","Dutch","English","French","Fula","Gan","German","Greek","Gujarati","Hakka","Haryanvi","Hausa","Hiligaynon","Hindi","Hmong","Hungarian","Igbo","Ilocano","Italian","Japanese","Javanese","Jin","Kannada","Kazakh","Khmer","Kinyarwanda","Kirundi","Konkani","Korean","Kurdish","Madurese","Magahi","Maithili","Malagasy","Malay","Malayalam","Mandarin","Marathi","Marwari","Min","Mossi","Nepali","Odia","Oromo","Pashto","Persian","Polish","Portuguese","Punjabi","Quechua","Romanian","Russian","Saraiki","Shona","Sindhi","Sinhala","Somali","Spanish","Sundanese","Swedish","Sylheti","Tagalog","Tamil","Telugu","Thai","Turkish","Turkmen","Ukrainian","Urdu","Uyghur","Uzbek","Vietnamese","Wu","Xhosa","Xiang","Yoruba","Yue","Zhuang","Zulu"],exports.names=["Aaren","Aarika","Abagael","Abagail","Abbe","Abbey","Abbi","Abbie","Abby","Abbye","Abigael","Abigail","Abigale","Abra","Ada","Adah","Adaline","Adan","Adara","Adda","Addi","Addia","Addie","Addy","Adel","Adela","Adelaida","Adelaide","Adele","Adelheid","Adelice","Adelina","Adelind","Adeline","Adella","Adelle","Adena","Adey","Adi","Adiana","Adina","Adora","Adore","Adoree","Adorne","Adrea","Adria","Adriaens","Adrian","Adriana","Adriane","Adrianna","Adrianne","Adriena","Adrienne","Aeriel","Aeriela","Aeriell","Afton","Ag","Agace","Agata","Agatha","Agathe","Aggi","Aggie","Aggy","Agna","Agnella","Agnes","Agnese","Agnesse","Agneta","Agnola","Agretha","Aida","Aidan","Aigneis","Aila","Aile","Ailee","Aileen","Ailene","Ailey","Aili","Ailina","Ailis","Ailsun","Ailyn","Aime","Aimee","Aimil","Aindrea","Ainslee","Ainsley","Ainslie","Ajay","Alaine","Alameda","Alana","Alanah","Alane","Alanna","Alayne","Alberta","Albertina","Albertine","Albina","Alecia","Aleda","Aleece","Aleen","Alejandra","Alejandrina","Alena","Alene","Alessandra","Aleta","Alethea","Alex","Alexa","Alexandra","Alexandrina","Alexi","Alexia","Alexina","Alexine","Alexis","Alfi","Alfie","Alfreda","Alfy","Ali","Alia","Alica","Alice","Alicea","Alicia","Alida","Alidia","Alie","Alika","Alikee","Alina","Aline","Alis","Alisa","Alisha","Alison","Alissa","Alisun","Alix","Aliza","Alla","Alleen","Allegra","Allene","Alli","Allianora","Allie","Allina","Allis","Allison","Allissa","Allix","Allsun","Allx","Ally","Allyce","Allyn","Allys","Allyson","Alma","Almeda","Almeria","Almeta","Almira","Almire","Aloise","Aloisia","Aloysia","Alta","Althea","Alvera","Alverta","Alvina","Alvinia","Alvira","Alyce","Alyda","Alys","Alysa","Alyse","Alysia","Alyson","Alyss","Alyssa","Amabel","Amabelle","Amalea","Amalee","Amaleta","Amalia","Amalie","Amalita","Amalle","Amanda","Amandi","Amandie","Amandy","Amara","Amargo","Amata","Amber","Amberly","Ambur","Ame","Amelia","Amelie","Amelina","Ameline","Amelita","Ami","Amie","Amii","Amil","Amitie","Amity","Ammamaria","Amy","Amye","Ana","Anabal","Anabel","Anabella","Anabelle","Analiese","Analise","Anallese","Anallise","Anastasia","Anastasie","Anastassia","Anatola","Andee","Andeee","Anderea","Andi","Andie","Andra","Andrea","Andreana","Andree","Andrei","Andria","Andriana","Andriette","Andromache","Andy","Anestassia","Anet","Anett","Anetta","Anette","Ange","Angel","Angela","Angele","Angelia","Angelica","Angelika","Angelina","Angeline","Angelique","Angelita","Angelle","Angie","Angil","Angy","Ania","Anica","Anissa","Anita","Anitra","Anjanette","Anjela","Ann","Ann-marie","Anna","Anna-diana","Anna-diane","Anna-maria","Annabal","Annabel","Annabela","Annabell","Annabella","Annabelle","Annadiana","Annadiane","Annalee","Annaliese","Annalise","Annamaria","Annamarie","Anne","Anne-corinne","Anne-marie","Annecorinne","Anneliese","Annelise","Annemarie","Annetta","Annette","Anni","Annice","Annie","Annis","Annissa","Annmaria","Annmarie","Annnora","Annora","Anny","Anselma","Ansley","Anstice","Anthe","Anthea","Anthia","Anthiathia","Antoinette","Antonella","Antonetta","Antonia","Antonie","Antonietta","Antonina","Anya","Appolonia","April","Aprilette","Ara","Arabel","Arabela","Arabele","Arabella","Arabelle","Arda","Ardath","Ardeen","Ardelia","Ardelis","Ardella","Ardelle","Arden","Ardene","Ardenia","Ardine","Ardis","Ardisj","Ardith","Ardra","Ardyce","Ardys","Ardyth","Aretha","Ariadne","Ariana","Aridatha","Ariel","Ariela","Ariella","Arielle","Arlana","Arlee","Arleen","Arlen","Arlena","Arlene","Arleta","Arlette","Arleyne","Arlie","Arliene","Arlina","Arlinda","Arline","Arluene","Arly","Arlyn","Arlyne","Aryn","Ashely","Ashia","Ashien","Ashil","Ashla","Ashlan","Ashlee","Ashleigh","Ashlen","Ashley","Ashli","Ashlie","Ashly","Asia","Astra","Astrid","Astrix","Atalanta","Athena","Athene","Atlanta","Atlante","Auberta","Aubine","Aubree","Aubrette","Aubrey","Aubrie","Aubry","Audi","Audie","Audra","Audre","Audrey","Audrie","Audry","Audrye","Audy","Augusta","Auguste","Augustina","Augustine","Aundrea","Aura","Aurea","Aurel","Aurelea","Aurelia","Aurelie","Auria","Aurie","Aurilia","Aurlie","Auroora","Aurora","Aurore","Austin","Austina","Austine","Ava","Aveline","Averil","Averyl","Avie","Avis","Aviva","Avivah","Avril","Avrit","Ayn","Bab","Babara","Babb","Babbette","Babbie","Babette","Babita","Babs","Bambi","Bambie","Bamby","Barb","Barbabra","Barbara","Barbara-anne","Barbaraanne","Barbe","Barbee","Barbette","Barbey","Barbi","Barbie","Barbra","Barby","Bari","Barrie","Barry","Basia","Bathsheba","Batsheva","Bea","Beatrice","Beatrisa","Beatrix","Beatriz","Bebe","Becca","Becka","Becki","Beckie","Becky","Bee","Beilul","Beitris","Bekki","Bel","Belia","Belicia","Belinda","Belita","Bell","Bella","Bellanca","Belle","Bellina","Belva","Belvia","Bendite","Benedetta","Benedicta","Benedikta","Benetta","Benita","Benni","Bennie","Benny","Benoite","Berenice","Beret","Berget","Berna","Bernadene","Bernadette","Bernadina","Bernadine","Bernardina","Bernardine","Bernelle","Bernete","Bernetta","Bernette","Berni","Bernice","Bernie","Bernita","Berny","Berri","Berrie","Berry","Bert","Berta","Berte","Bertha","Berthe","Berti","Bertie","Bertina","Bertine","Berty","Beryl","Beryle","Bess","Bessie","Bessy","Beth","Bethanne","Bethany","Bethena","Bethina","Betsey","Betsy","Betta","Bette","Bette-ann","Betteann","Betteanne","Betti","Bettina","Bettine","Betty","Bettye","Beulah","Bev","Beverie","Beverlee","Beverley","Beverlie","Beverly","Bevvy","Bianca","Bianka","Bibbie","Bibby","Bibbye","Bibi","Biddie","Biddy","Bidget","Bili","Bill","Billi","Billie","Billy","Billye","Binni","Binnie","Binny","Bird","Birdie","Birgit","Birgitta","Blair","Blaire","Blake","Blakelee","Blakeley","Blanca","Blanch","Blancha","Blanche","Blinni","Blinnie","Blinny","Bliss","Blisse","Blithe","Blondell","Blondelle","Blondie","Blondy","Blythe","Bobbe","Bobbee","Bobbette","Bobbi","Bobbie","Bobby","Bobbye","Bobette","Bobina","Bobine","Bobinette","Bonita","Bonnee","Bonni","Bonnibelle","Bonnie","Bonny","Brana","Brandais","Brande","Brandea","Brandi","Brandice","Brandie","Brandise","Brandy","Breanne","Brear","Bree","Breena","Bren","Brena","Brenda","Brenn","Brenna","Brett","Bria","Briana","Brianna","Brianne","Bride","Bridget","Bridgette","Bridie","Brier","Brietta","Brigid","Brigida","Brigit","Brigitta","Brigitte","Brina","Briney","Brinn","Brinna","Briny","Brit","Brita","Britney","Britni","Britt","Britta","Brittan","Brittaney","Brittani","Brittany","Britte","Britteny","Brittne","Brittney","Brittni","Brook","Brooke","Brooks","Brunhilda","Brunhilde","Bryana","Bryn","Bryna","Brynn","Brynna","Brynne","Buffy","Bunni","Bunnie","Bunny","Cacilia","Cacilie","Cahra","Cairistiona","Caitlin","Caitrin","Cal","Calida","Calla","Calley","Calli","Callida","Callie","Cally","Calypso","Cam","Camala","Camel","Camella","Camellia","Cami","Camila","Camile","Camilla","Camille","Cammi","Cammie","Cammy","Candace","Candi","Candice","Candida","Candide","Candie","Candis","Candra","Candy","Caprice","Cara","Caralie","Caren","Carena","Caresa","Caressa","Caresse","Carey","Cari","Caria","Carie","Caril","Carilyn","Carin","Carina","Carine","Cariotta","Carissa","Carita","Caritta","Carla","Carlee","Carleen","Carlen","Carlene","Carley","Carlie","Carlin","Carlina","Carline","Carlita","Carlota","Carlotta","Carly","Carlye","Carlyn","Carlynn","Carlynne","Carma","Carmel","Carmela","Carmelia","Carmelina","Carmelita","Carmella","Carmelle","Carmen","Carmencita","Carmina","Carmine","Carmita","Carmon","Caro","Carol","Carol-jean","Carola","Carolan","Carolann","Carole","Carolee","Carolin","Carolina","Caroline","Caroljean","Carolyn","Carolyne","Carolynn","Caron","Carree","Carri","Carrie","Carrissa","Carroll","Carry","Cary","Caryl","Caryn","Casandra","Casey","Casi","Casie","Cass","Cassandra","Cassandre","Cassandry","Cassaundra","Cassey","Cassi","Cassie","Cassondra","Cassy","Catarina","Cate","Caterina","Catha","Catharina","Catharine","Cathe","Cathee","Catherin","Catherina","Catherine","Cathi","Cathie","Cathleen","Cathlene","Cathrin","Cathrine","Cathryn","Cathy","Cathyleen","Cati","Catie","Catina","Catlaina","Catlee","Catlin","Catrina","Catriona","Caty","Caye","Cayla","Cecelia","Cecil","Cecile","Ceciley","Cecilia","Cecilla","Cecily","Ceil","Cele","Celene","Celesta","Celeste","Celestia","Celestina","Celestine","Celestyn","Celestyna","Celia","Celie","Celina","Celinda","Celine","Celinka","Celisse","Celka","Celle","Cesya","Chad","Chanda","Chandal","Chandra","Channa","Chantal","Chantalle","Charil","Charin","Charis","Charissa","Charisse","Charita","Charity","Charla","Charlean","Charleen","Charlena","Charlene","Charline","Charlot","Charlotta","Charlotte","Charmain","Charmaine","Charmane","Charmian","Charmine","Charmion","Charo","Charyl","Chastity","Chelsae","Chelsea","Chelsey","Chelsie","Chelsy","Cher","Chere","Cherey","Cheri","Cherianne","Cherice","Cherida","Cherie","Cherilyn","Cherilynn","Cherin","Cherise","Cherish","Cherlyn","Cherri","Cherrita","Cherry","Chery","Cherye","Cheryl","Cheslie","Chiarra","Chickie","Chicky","Chiquia","Chiquita","Chlo","Chloe","Chloette","Chloris","Chris","Chrissie","Chrissy","Christa","Christabel","Christabella","Christal","Christalle","Christan","Christean","Christel","Christen","Christi","Christian","Christiana","Christiane","Christie","Christin","Christina","Christine","Christy","Christye","Christyna","Chrysa","Chrysler","Chrystal","Chryste","Chrystel","Cicely","Cicily","Ciel","Cilka","Cinda","Cindee","Cindelyn","Cinderella","Cindi","Cindie","Cindra","Cindy","Cinnamon","Cissiee","Cissy","Clair","Claire","Clara","Clarabelle","Clare","Claresta","Clareta","Claretta","Clarette","Clarey","Clari","Claribel","Clarice","Clarie","Clarinda","Clarine","Clarissa","Clarisse","Clarita","Clary","Claude","Claudelle","Claudetta","Claudette","Claudia","Claudie","Claudina","Claudine","Clea","Clem","Clemence","Clementia","Clementina","Clementine","Clemmie","Clemmy","Cleo","Cleopatra","Clerissa","Clio","Clo","Cloe","Cloris","Clotilda","Clovis","Codee","Codi","Codie","Cody","Coleen","Colene","Coletta","Colette","Colleen","Collen","Collete","Collette","Collie","Colline","Colly","Con","Concettina","Conchita","Concordia","Conni","Connie","Conny","Consolata","Constance","Constancia","Constancy","Constanta","Constantia","Constantina","Constantine","Consuela","Consuelo","Cookie","Cora","Corabel","Corabella","Corabelle","Coral","Coralie","Coraline","Coralyn","Cordelia","Cordelie","Cordey","Cordi","Cordie","Cordula","Cordy","Coreen","Corella","Corenda","Corene","Coretta","Corette","Corey","Cori","Corie","Corilla","Corina","Corine","Corinna","Corinne","Coriss","Corissa","Corliss","Corly","Cornela","Cornelia","Cornelle","Cornie","Corny","Correna","Correy","Corri","Corrianne","Corrie","Corrina","Corrine","Corrinne","Corry","Cortney","Cory","Cosetta","Cosette","Costanza","Courtenay","Courtnay","Courtney","Crin","Cris","Crissie","Crissy","Crista","Cristabel","Cristal","Cristen","Cristi","Cristie","Cristin","Cristina","Cristine","Cristionna","Cristy","Crysta","Crystal","Crystie","Cthrine","Cyb","Cybil","Cybill","Cymbre","Cynde","Cyndi","Cyndia","Cyndie","Cyndy","Cynthea","Cynthia","Cynthie","Cynthy","Dacey","Dacia","Dacie","Dacy","Dael","Daffi","Daffie","Daffy","Dagmar","Dahlia","Daile","Daisey","Daisi","Daisie","Daisy","Dale","Dalenna","Dalia","Dalila","Dallas","Daloris","Damara","Damaris","Damita","Dana","Danell","Danella","Danette","Dani","Dania","Danica","Danice","Daniela","Daniele","Daniella","Danielle","Danika","Danila","Danit","Danita","Danna","Danni","Dannie","Danny","Dannye","Danya","Danyelle","Danyette","Daphene","Daphna","Daphne","Dara","Darb","Darbie","Darby","Darcee","Darcey","Darci","Darcie","Darcy","Darda","Dareen","Darell","Darelle","Dari","Daria","Darice","Darla","Darleen","Darlene","Darline","Darlleen","Daron","Darrelle","Darryl","Darsey","Darsie","Darya","Daryl","Daryn","Dasha","Dasi","Dasie","Dasya","Datha","Daune","Daveen","Daveta","Davida","Davina","Davine","Davita","Dawn","Dawna","Dayle","Dayna","Ddene","De","Deana","Deane","Deanna","Deanne","Deb","Debbi","Debbie","Debby","Debee","Debera","Debi","Debor","Debora","Deborah","Debra","Dede","Dedie","Dedra","Dee","Deeann","Deeanne","Deedee","Deena","Deerdre","Deeyn","Dehlia","Deidre","Deina","Deirdre","Del","Dela","Delcina","Delcine","Delia","Delila","Delilah","Delinda","Dell","Della","Delly","Delora","Delores","Deloria","Deloris","Delphine","Delphinia","Demeter","Demetra","Demetria","Demetris","Dena","Deni","Denice","Denise","Denna","Denni","Dennie","Denny","Deny","Denys","Denyse","Deonne","Desdemona","Desirae","Desiree","Desiri","Deva","Devan","Devi","Devin","Devina","Devinne","Devon","Devondra","Devonna","Devonne","Devora","Di","Diahann","Dian","Diana","Diandra","Diane","Diane-marie","Dianemarie","Diann","Dianna","Dianne","Diannne","Didi","Dido","Diena","Dierdre","Dina","Dinah","Dinnie","Dinny","Dion","Dione","Dionis","Dionne","Dita","Dix","Dixie","Dniren","Dode","Dodi","Dodie","Dody","Doe","Doll","Dolley","Dolli","Dollie","Dolly","Dolores","Dolorita","Doloritas","Domeniga","Dominga","Domini","Dominica","Dominique","Dona","Donella","Donelle","Donetta","Donia","Donica","Donielle","Donna","Donnamarie","Donni","Donnie","Donny","Dora","Doralia","Doralin","Doralyn","Doralynn","Doralynne","Dore","Doreen","Dorelia","Dorella","Dorelle","Dorena","Dorene","Doretta","Dorette","Dorey","Dori","Doria","Dorian","Dorice","Dorie","Dorine","Doris","Dorisa","Dorise","Dorita","Doro","Dorolice","Dorolisa","Dorotea","Doroteya","Dorothea","Dorothee","Dorothy","Dorree","Dorri","Dorrie","Dorris","Dorry","Dorthea","Dorthy","Dory","Dosi","Dot","Doti","Dotti","Dottie","Dotty","Dre","Dreddy","Dredi","Drona","Dru","Druci","Drucie","Drucill","Drucy","Drusi","Drusie","Drusilla","Drusy","Dulce","Dulcea","Dulci","Dulcia","Dulciana","Dulcie","Dulcine","Dulcinea","Dulcy","Dulsea","Dusty","Dyan","Dyana","Dyane","Dyann","Dyanna","Dyanne","Dyna","Dynah","Eachelle","Eada","Eadie","Eadith","Ealasaid","Eartha","Easter","Eba","Ebba","Ebonee","Ebony","Eda","Eddi","Eddie","Eddy","Ede","Edee","Edeline","Eden","Edi","Edie","Edin","Edita","Edith","Editha","Edithe","Ediva","Edna","Edwina","Edy","Edyth","Edythe","Effie","Eileen","Eilis","Eimile","Eirena","Ekaterina","Elaina","Elaine","Elana","Elane","Elayne","Elberta","Elbertina","Elbertine","Eleanor","Eleanora","Eleanore","Electra","Eleen","Elena","Elene","Eleni","Elenore","Eleonora","Eleonore","Elfie","Elfreda","Elfrida","Elfrieda","Elga","Elianora","Elianore","Elicia","Elie","Elinor","Elinore","Elisa","Elisabet","Elisabeth","Elisabetta","Elise","Elisha","Elissa","Elita","Eliza","Elizabet","Elizabeth","Elka","Elke","Ella","Elladine","Elle","Ellen","Ellene","Ellette","Elli","Ellie","Ellissa","Elly","Ellyn","Ellynn","Elmira","Elna","Elnora","Elnore","Eloisa","Eloise","Elonore","Elora","Elsa","Elsbeth","Else","Elset","Elsey","Elsi","Elsie","Elsinore","Elspeth","Elsy","Elva","Elvera","Elvina","Elvira","Elwira","Elyn","Elyse","Elysee","Elysha","Elysia","Elyssa","Em","Ema","Emalee","Emalia","Emelda","Emelia","Emelina","Emeline","Emelita","Emelyne","Emera","Emilee","Emili","Emilia","Emilie","Emiline","Emily","Emlyn","Emlynn","Emlynne","Emma","Emmalee","Emmaline","Emmalyn","Emmalynn","Emmalynne","Emmeline","Emmey","Emmi","Emmie","Emmy","Emmye","Emogene","Emyle","Emylee","Engracia","Enid","Enrica","Enrichetta","Enrika","Enriqueta","Eolanda","Eolande","Eran","Erda","Erena","Erica","Ericha","Ericka","Erika","Erin","Erina","Erinn","Erinna","Erma","Ermengarde","Ermentrude","Ermina","Erminia","Erminie","Erna","Ernaline","Ernesta","Ernestine","Ertha","Eryn","Esma","Esmaria","Esme","Esmeralda","Essa","Essie","Essy","Esta","Estel","Estele","Estell","Estella","Estelle","Ester","Esther","Estrella","Estrellita","Ethel","Ethelda","Ethelin","Ethelind","Etheline","Ethelyn","Ethyl","Etta","Etti","Ettie","Etty","Eudora","Eugenia","Eugenie","Eugine","Eula","Eulalie","Eunice","Euphemia","Eustacia","Eva","Evaleen","Evangelia","Evangelin","Evangelina","Evangeline","Evania","Evanne","Eve","Eveleen","Evelina","Eveline","Evelyn","Evey","Evie","Evita","Evonne","Evvie","Evvy","Evy","Eyde","Eydie","Ezmeralda","Fae","Faina","Faith","Fallon","Fan","Fanchette","Fanchon","Fancie","Fancy","Fanechka","Fania","Fanni","Fannie","Fanny","Fanya","Fara","Farah","Farand","Farica","Farra","Farrah","Farrand","Faun","Faunie","Faustina","Faustine","Fawn","Fawne","Fawnia","Fay","Faydra","Faye","Fayette","Fayina","Fayre","Fayth","Faythe","Federica","Fedora","Felecia","Felicdad","Felice","Felicia","Felicity","Felicle","Felipa","Felisha","Felita","Feliza","Fenelia","Feodora","Ferdinanda","Ferdinande","Fern","Fernanda","Fernande","Fernandina","Ferne","Fey","Fiann","Fianna","Fidela","Fidelia","Fidelity","Fifi","Fifine","Filia","Filide","Filippa","Fina","Fiona","Fionna","Fionnula","Fiorenze","Fleur","Fleurette","Flo","Flor","Flora","Florance","Flore","Florella","Florence","Florencia","Florentia","Florenza","Florette","Flori","Floria","Florida","Florie","Florina","Florinda","Floris","Florri","Florrie","Florry","Flory","Flossi","Flossie","Flossy","Flss","Fran","Francene","Frances","Francesca","Francine","Francisca","Franciska","Francoise","Francyne","Frank","Frankie","Franky","Franni","Frannie","Franny","Frayda","Fred","Freda","Freddi","Freddie","Freddy","Fredelia","Frederica","Fredericka","Frederique","Fredi","Fredia","Fredra","Fredrika","Freida","Frieda","Friederike","Fulvia","Gabbey","Gabbi","Gabbie","Gabey","Gabi","Gabie","Gabriel","Gabriela","Gabriell","Gabriella","Gabrielle","Gabriellia","Gabrila","Gaby","Gae","Gael","Gail","Gale","Galina","Garland","Garnet","Garnette","Gates","Gavra","Gavrielle","Gay","Gaye","Gayel","Gayla","Gayle","Gayleen","Gaylene","Gaynor","Gelya","Gena","Gene","Geneva","Genevieve","Genevra","Genia","Genna","Genni","Gennie","Gennifer","Genny","Genovera","Genvieve","George","Georgeanna","Georgeanne","Georgena","Georgeta","Georgetta","Georgette","Georgia","Georgiana","Georgianna","Georgianne","Georgie","Georgina","Georgine","Geralda","Geraldine","Gerda","Gerhardine","Geri","Gerianna","Gerianne","Gerladina","Germain","Germaine","Germana","Gerri","Gerrie","Gerrilee","Gerry","Gert","Gerta","Gerti","Gertie","Gertrud","Gertruda","Gertrude","Gertrudis","Gerty","Giacinta","Giana","Gianina","Gianna","Gigi","Gilberta","Gilberte","Gilbertina","Gilbertine","Gilda","Gilemette","Gill","Gillan","Gilli","Gillian","Gillie","Gilligan","Gilly","Gina","Ginelle","Ginevra","Ginger","Ginni","Ginnie","Ginnifer","Ginny","Giorgia","Giovanna","Gipsy","Giralda","Gisela","Gisele","Gisella","Giselle","Giuditta","Giulia","Giulietta","Giustina","Gizela","Glad","Gladi","Gladys","Gleda","Glen","Glenda","Glenine","Glenn","Glenna","Glennie","Glennis","Glori","Gloria","Gloriana","Gloriane","Glory","Glyn","Glynda","Glynis","Glynnis","Gnni","Godiva","Golda","Goldarina","Goldi","Goldia","Goldie","Goldina","Goldy","Grace","Gracia","Gracie","Grata","Gratia","Gratiana","Gray","Grayce","Grazia","Greer","Greta","Gretal","Gretchen","Grete","Gretel","Grethel","Gretna","Gretta","Grier","Griselda","Grissel","Guendolen","Guenevere","Guenna","Guglielma","Gui","Guillema","Guillemette","Guinevere","Guinna","Gunilla","Gus","Gusella","Gussi","Gussie","Gussy","Gusta","Gusti","Gustie","Gusty","Gwen","Gwendolen","Gwendolin","Gwendolyn","Gweneth","Gwenette","Gwenneth","Gwenni","Gwennie","Gwenny","Gwenora","Gwenore","Gwyn","Gwyneth","Gwynne","Gypsy","Hadria","Hailee","Haily","Haleigh","Halette","Haley","Hali","Halie","Halimeda","Halley","Halli","Hallie","Hally","Hana","Hanna","Hannah","Hanni","Hannie","Hannis","Hanny","Happy","Harlene","Harley","Harli","Harlie","Harmonia","Harmonie","Harmony","Harri","Harrie","Harriet","Harriett","Harrietta","Harriette","Harriot","Harriott","Hatti","Hattie","Hatty","Hayley","Hazel","Heath","Heather","Heda","Hedda","Heddi","Heddie","Hedi","Hedvig","Hedvige","Hedwig","Hedwiga","Hedy","Heida","Heidi","Heidie","Helaina","Helaine","Helen","Helen-elizabeth","Helena","Helene","Helenka","Helga","Helge","Helli","Heloise","Helsa","Helyn","Hendrika","Henka","Henrie","Henrieta","Henrietta","Henriette","Henryetta","Hephzibah","Hermia","Hermina","Hermine","Herminia","Hermione","Herta","Hertha","Hester","Hesther","Hestia","Hetti","Hettie","Hetty","Hilary","Hilda","Hildagard","Hildagarde","Hilde","Hildegaard","Hildegarde","Hildy","Hillary","Hilliary","Hinda","Holli","Hollie","Holly","Holly-anne","Hollyanne","Honey","Honor","Honoria","Hope","Horatia","Hortense","Hortensia","Hulda","Hyacinth","Hyacintha","Hyacinthe","Hyacinthia","Hyacinthie","Hynda","Ianthe","Ibbie","Ibby","Ida","Idalia","Idalina","Idaline","Idell","Idelle","Idette","Ileana","Ileane","Ilene","Ilise","Ilka","Illa","Ilsa","Ilse","Ilysa","Ilyse","Ilyssa","Imelda","Imogen","Imogene","Imojean","Ina","Indira","Ines","Inesita","Inessa","Inez","Inga","Ingaberg","Ingaborg","Inge","Ingeberg","Ingeborg","Inger","Ingrid","Ingunna","Inna","Iolande","Iolanthe","Iona","Iormina","Ira","Irena","Irene","Irina","Iris","Irita","Irma","Isa","Isabel","Isabelita","Isabella","Isabelle","Isadora","Isahella","Iseabal","Isidora","Isis","Isobel","Issi","Issie","Issy","Ivett","Ivette","Ivie","Ivonne","Ivory","Ivy","Izabel","Jacenta","Jacinda","Jacinta","Jacintha","Jacinthe","Jackelyn","Jacki","Jackie","Jacklin","Jacklyn","Jackquelin","Jackqueline","Jacky","Jaclin","Jaclyn","Jacquelin","Jacqueline","Jacquelyn","Jacquelynn","Jacquenetta","Jacquenette","Jacquetta","Jacquette","Jacqui","Jacquie","Jacynth","Jada","Jade","Jaime","Jaimie","Jaine","Jami","Jamie","Jamima","Jammie","Jan","Jana","Janaya","Janaye","Jandy","Jane","Janean","Janeczka","Janeen","Janel","Janela","Janella","Janelle","Janene","Janenna","Janessa","Janet","Janeta","Janetta","Janette","Janeva","Janey","Jania","Janice","Janie","Janifer","Janina","Janine","Janis","Janith","Janka","Janna","Jannel","Jannelle","Janot","Jany","Jaquelin","Jaquelyn","Jaquenetta","Jaquenette","Jaquith","Jasmin","Jasmina","Jasmine","Jayme","Jaymee","Jayne","Jaynell","Jazmin","Jean","Jeana","Jeane","Jeanelle","Jeanette","Jeanie","Jeanine","Jeanna","Jeanne","Jeannette","Jeannie","Jeannine","Jehanna","Jelene","Jemie","Jemima","Jemimah","Jemmie","Jemmy","Jen","Jena","Jenda","Jenelle","Jeni","Jenica","Jeniece","Jenifer","Jeniffer","Jenilee","Jenine","Jenn","Jenna","Jennee","Jennette","Jenni","Jennica","Jennie","Jennifer","Jennilee","Jennine","Jenny","Jeralee","Jere","Jeri","Jermaine","Jerrie","Jerrilee","Jerrilyn","Jerrine","Jerry","Jerrylee","Jess","Jessa","Jessalin","Jessalyn","Jessamine","Jessamyn","Jesse","Jesselyn","Jessi","Jessica","Jessie","Jessika","Jessy","Jewel","Jewell","Jewelle","Jill","Jillana","Jillane","Jillayne","Jilleen","Jillene","Jilli","Jillian","Jillie","Jilly","Jinny","Jo","Jo-ann","Jo-anne","Joan","Joana","Joane","Joanie","Joann","Joanna","Joanne","Joannes","Jobey","Jobi","Jobie","Jobina","Joby","Jobye","Jobyna","Jocelin","Joceline","Jocelyn","Jocelyne","Jodee","Jodi","Jodie","Jody","Joeann","Joela","Joelie","Joell","Joella","Joelle","Joellen","Joelly","Joellyn","Joelynn","Joete","Joey","Johanna","Johannah","Johna","Johnath","Johnette","Johnna","Joice","Jojo","Jolee","Joleen","Jolene","Joletta","Joli","Jolie","Joline","Joly","Jolyn","Jolynn","Jonell","Joni","Jonie","Jonis","Jordain","Jordan","Jordana","Jordanna","Jorey","Jori","Jorie","Jorrie","Jorry","Joscelin","Josee","Josefa","Josefina","Josepha","Josephina","Josephine","Josey","Josi","Josie","Josselyn","Josy","Jourdan","Joy","Joya","Joyan","Joyann","Joyce","Joycelin","Joye","Jsandye","Juana","Juanita","Judi","Judie","Judith","Juditha","Judy","Judye","Juieta","Julee","Juli","Julia","Juliana","Juliane","Juliann","Julianna","Julianne","Julie","Julienne","Juliet","Julieta","Julietta","Juliette","Julina","Juline","Julissa","Julita","June","Junette","Junia","Junie","Junina","Justina","Justine","Justinn","Jyoti","Kacey","Kacie","Kacy","Kaela","Kai","Kaia","Kaila","Kaile","Kailey","Kaitlin","Kaitlyn","Kaitlynn","Kaja","Kakalina","Kala","Kaleena","Kali","Kalie","Kalila","Kalina","Kalinda","Kalindi","Kalli","Kally","Kameko","Kamila","Kamilah","Kamillah","Kandace","Kandy","Kania","Kanya","Kara","Kara-lynn","Karalee","Karalynn","Kare","Karee","Karel","Karen","Karena","Kari","Karia","Karie","Karil","Karilynn","Karin","Karina","Karine","Kariotta","Karisa","Karissa","Karita","Karla","Karlee","Karleen","Karlen","Karlene","Karlie","Karlotta","Karlotte","Karly","Karlyn","Karmen","Karna","Karol","Karola","Karole","Karolina","Karoline","Karoly","Karon","Karrah","Karrie","Karry","Kary","Karyl","Karylin","Karyn","Kasey","Kass","Kassandra","Kassey","Kassi","Kassia","Kassie","Kat","Kata","Katalin","Kate","Katee","Katerina","Katerine","Katey","Kath","Katha","Katharina","Katharine","Katharyn","Kathe","Katherina","Katherine","Katheryn","Kathi","Kathie","Kathleen","Kathlin","Kathrine","Kathryn","Kathryne","Kathy","Kathye","Kati","Katie","Katina","Katine","Katinka","Katleen","Katlin","Katrina","Katrine","Katrinka","Katti","Kattie","Katuscha","Katusha","Katy","Katya","Kay","Kaycee","Kaye","Kayla","Kayle","Kaylee","Kayley","Kaylil","Kaylyn","Keeley","Keelia","Keely","Kelcey","Kelci","Kelcie","Kelcy","Kelila","Kellen","Kelley","Kelli","Kellia","Kellie","Kellina","Kellsie","Kelly","Kellyann","Kelsey","Kelsi","Kelsy","Kendra","Kendre","Kenna","Keri","Keriann","Kerianne","Kerri","Kerrie","Kerrill","Kerrin","Kerry","Kerstin","Kesley","Keslie","Kessia","Kessiah","Ketti","Kettie","Ketty","Kevina","Kevyn","Ki","Kiah","Kial","Kiele","Kiersten","Kikelia","Kiley","Kim","Kimberlee","Kimberley","Kimberli","Kimberly","Kimberlyn","Kimbra","Kimmi","Kimmie","Kimmy","Kinna","Kip","Kipp","Kippie","Kippy","Kira","Kirbee","Kirbie","Kirby","Kiri","Kirsten","Kirsteni","Kirsti","Kirstin","Kirstyn","Kissee","Kissiah","Kissie","Kit","Kitti","Kittie","Kitty","Kizzee","Kizzie","Klara","Klarika","Klarrisa","Konstance","Konstanze","Koo","Kora","Koral","Koralle","Kordula","Kore","Korella","Koren","Koressa","Kori","Korie","Korney","Korrie","Korry","Kris","Krissie","Krissy","Krista","Kristal","Kristan","Kriste","Kristel","Kristen","Kristi","Kristien","Kristin","Kristina","Kristine","Kristy","Kristyn","Krysta","Krystal","Krystalle","Krystle","Krystyna","Kyla","Kyle","Kylen","Kylie","Kylila","Kylynn","Kym","Kynthia","Kyrstin","Lacee","Lacey","Lacie","Lacy","Ladonna","Laetitia","Laina","Lainey","Lana","Lanae","Lane","Lanette","Laney","Lani","Lanie","Lanita","Lanna","Lanni","Lanny","Lara","Laraine","Lari","Larina","Larine","Larisa","Larissa","Lark","Laryssa","Latashia","Latia","Latisha","Latrena","Latrina","Laura","Lauraine","Laural","Lauralee","Laure","Lauree","Laureen","Laurel","Laurella","Lauren","Laurena","Laurene","Lauretta","Laurette","Lauri","Laurianne","Laurice","Laurie","Lauryn","Lavena","Laverna","Laverne","Lavina","Lavinia","Lavinie","Layla","Layne","Layney","Lea","Leah","Leandra","Leann","Leanna","Leanor","Leanora","Lebbie","Leda","Lee","Leeann","Leeanne","Leela","Leelah","Leena","Leesa","Leese","Legra","Leia","Leigh","Leigha","Leila","Leilah","Leisha","Lela","Lelah","Leland","Lelia","Lena","Lenee","Lenette","Lenka","Lenna","Lenora","Lenore","Leodora","Leoine","Leola","Leoline","Leona","Leonanie","Leone","Leonelle","Leonie","Leonora","Leonore","Leontine","Leontyne","Leora","Leshia","Lesley","Lesli","Leslie","Lesly","Lesya","Leta","Lethia","Leticia","Letisha","Letitia","Letizia","Letta","Letti","Lettie","Letty","Lexi","Lexie","Lexine","Lexis","Lexy","Leyla","Lezlie","Lia","Lian","Liana","Liane","Lianna","Lianne","Lib","Libbey","Libbi","Libbie","Libby","Licha","Lida","Lidia","Liesa","Lil","Lila","Lilah","Lilas","Lilia","Lilian","Liliane","Lilias","Lilith","Lilla","Lilli","Lillian","Lillis","Lilllie","Lilly","Lily","Lilyan","Lin","Lina","Lind","Linda","Lindi","Lindie","Lindsay","Lindsey","Lindsy","Lindy","Linea","Linell","Linet","Linette","Linn","Linnea","Linnell","Linnet","Linnie","Linzy","Lira","Lisa","Lisabeth","Lisbeth","Lise","Lisetta","Lisette","Lisha","Lishe","Lissa","Lissi","Lissie","Lissy","Lita","Liuka","Liv","Liva","Livia","Livvie","Livvy","Livvyy","Livy","Liz","Liza","Lizabeth","Lizbeth","Lizette","Lizzie","Lizzy","Loella","Lois","Loise","Lola","Loleta","Lolita","Lolly","Lona","Lonee","Loni","Lonna","Lonni","Lonnie","Lora","Lorain","Loraine","Loralee","Loralie","Loralyn","Loree","Loreen","Lorelei","Lorelle","Loren","Lorena","Lorene","Lorenza","Loretta","Lorette","Lori","Loria","Lorianna","Lorianne","Lorie","Lorilee","Lorilyn","Lorinda","Lorine","Lorita","Lorna","Lorne","Lorraine","Lorrayne","Lorri","Lorrie","Lorrin","Lorry","Lory","Lotta","Lotte","Lotti","Lottie","Lotty","Lou","Louella","Louisa","Louise","Louisette","Loutitia","Lu","Luce","Luci","Lucia","Luciana","Lucie","Lucienne","Lucila","Lucilia","Lucille","Lucina","Lucinda","Lucine","Lucita","Lucky","Lucretia","Lucy","Ludovika","Luella","Luelle","Luisa","Luise","Lula","Lulita","Lulu","Lura","Lurette","Lurleen","Lurlene","Lurline","Lusa","Luz","Lyda","Lydia","Lydie","Lyn","Lynda","Lynde","Lyndel","Lyndell","Lyndsay","Lyndsey","Lyndsie","Lyndy","Lynea","Lynelle","Lynett","Lynette","Lynn","Lynna","Lynne","Lynnea","Lynnell","Lynnelle","Lynnet","Lynnett","Lynnette","Lynsey","Lyssa","Mab","Mabel","Mabelle","Mable","Mada","Madalena","Madalyn","Maddalena","Maddi","Maddie","Maddy","Madel","Madelaine","Madeleine","Madelena","Madelene","Madelin","Madelina","Madeline","Madella","Madelle","Madelon","Madelyn","Madge","Madlen","Madlin","Madonna","Mady","Mae","Maegan","Mag","Magda","Magdaia","Magdalen","Magdalena","Magdalene","Maggee","Maggi","Maggie","Maggy","Mahala","Mahalia","Maia","Maible","Maiga","Maighdiln","Mair","Maire","Maisey","Maisie","Maitilde","Mala","Malanie","Malena","Malia","Malina","Malinda","Malinde","Malissa","Malissia","Mallissa","Mallorie","Mallory","Malorie","Malory","Malva","Malvina","Malynda","Mame","Mamie","Manda","Mandi","Mandie","Mandy","Manon","Manya","Mara","Marabel","Marcela","Marcelia","Marcella","Marcelle","Marcellina","Marcelline","Marchelle","Marci","Marcia","Marcie","Marcile","Marcille","Marcy","Mareah","Maren","Marena","Maressa","Marga","Margalit","Margalo","Margaret","Margareta","Margarete","Margaretha","Margarethe","Margaretta","Margarette","Margarita","Margaux","Marge","Margeaux","Margery","Marget","Margette","Margi","Margie","Margit","Margo","Margot","Margret","Marguerite","Margy","Mari","Maria","Mariam","Marian","Mariana","Mariann","Marianna","Marianne","Maribel","Maribelle","Maribeth","Marice","Maridel","Marie","Marie-ann","Marie-jeanne","Marieann","Mariejeanne","Mariel","Mariele","Marielle","Mariellen","Marietta","Mariette","Marigold","Marijo","Marika","Marilee","Marilin","Marillin","Marilyn","Marin","Marina","Marinna","Marion","Mariquilla","Maris","Marisa","Mariska","Marissa","Marita","Maritsa","Mariya","Marj","Marja","Marje","Marji","Marjie","Marjorie","Marjory","Marjy","Marketa","Marla","Marlane","Marleah","Marlee","Marleen","Marlena","Marlene","Marley","Marlie","Marline","Marlo","Marlyn","Marna","Marne","Marney","Marni","Marnia","Marnie","Marquita","Marrilee","Marris","Marrissa","Marsha","Marsiella","Marta","Martelle","Martguerita","Martha","Marthe","Marthena","Marti","Martica","Martie","Martina","Martita","Marty","Martynne","Mary","Marya","Maryann","Maryanna","Maryanne","Marybelle","Marybeth","Maryellen","Maryjane","Maryjo","Maryl","Marylee","Marylin","Marylinda","Marylou","Marylynne","Maryrose","Marys","Marysa","Masha","Matelda","Mathilda","Mathilde","Matilda","Matilde","Matti","Mattie","Matty","Maud","Maude","Maudie","Maura","Maure","Maureen","Maureene","Maurene","Maurine","Maurise","Maurita","Maurizia","Mavis","Mavra","Max","Maxi","Maxie","Maxine","Maxy","May","Maybelle","Maye","Mead","Meade","Meagan","Meaghan","Meara","Mechelle","Meg","Megan","Megen","Meggi","Meggie","Meggy","Meghan","Meghann","Mehetabel","Mei","Mel","Mela","Melamie","Melania","Melanie","Melantha","Melany","Melba","Melesa","Melessa","Melicent","Melina","Melinda","Melinde","Melisa","Melisande","Melisandra","Melisenda","Melisent","Melissa","Melisse","Melita","Melitta","Mella","Melli","Mellicent","Mellie","Mellisa","Mellisent","Melloney","Melly","Melodee","Melodie","Melody","Melonie","Melony","Melosa","Melva","Mercedes","Merci","Mercie","Mercy","Meredith","Meredithe","Meridel","Meridith","Meriel","Merilee","Merilyn","Meris","Merissa","Merl","Merla","Merle","Merlina","Merline","Merna","Merola","Merralee","Merridie","Merrie","Merrielle","Merrile","Merrilee","Merrili","Merrill","Merrily","Merry","Mersey","Meryl","Meta","Mia","Micaela","Michaela","Michaelina","Michaeline","Michaella","Michal","Michel","Michele","Michelina","Micheline","Michell","Michelle","Micki","Mickie","Micky","Midge","Mignon","Mignonne","Miguela","Miguelita","Mikaela","Mil","Mildred","Mildrid","Milena","Milicent","Milissent","Milka","Milli","Millicent","Millie","Millisent","Milly","Milzie","Mimi","Min","Mina","Minda","Mindy","Minerva","Minetta","Minette","Minna","Minnaminnie","Minne","Minni","Minnie","Minnnie","Minny","Minta","Miquela","Mira","Mirabel","Mirabella","Mirabelle","Miran","Miranda","Mireielle","Mireille","Mirella","Mirelle","Miriam","Mirilla","Mirna","Misha","Missie","Missy","Misti","Misty","Mitzi","Modesta","Modestia","Modestine","Modesty","Moina","Moira","Moll","Mollee","Molli","Mollie","Molly","Mommy","Mona","Monah","Monica","Monika","Monique","Mora","Moreen","Morena","Morgan","Morgana","Morganica","Morganne","Morgen","Moria","Morissa","Morna","Moselle","Moyna","Moyra","Mozelle","Muffin","Mufi","Mufinella","Muire","Mureil","Murial","Muriel","Murielle","Myra","Myrah","Myranda","Myriam","Myrilla","Myrle","Myrlene","Myrna","Myrta","Myrtia","Myrtice","Myrtie","Myrtle","Nada","Nadean","Nadeen","Nadia","Nadine","Nadiya","Nady","Nadya","Nalani","Nan","Nana","Nananne","Nance","Nancee","Nancey","Nanci","Nancie","Nancy","Nanete","Nanette","Nani","Nanice","Nanine","Nannette","Nanni","Nannie","Nanny","Nanon","Naoma","Naomi","Nara","Nari","Nariko","Nat","Nata","Natala","Natalee","Natalie","Natalina","Nataline","Natalya","Natasha","Natassia","Nathalia","Nathalie","Natividad","Natka","Natty","Neala","Neda","Nedda","Nedi","Neely","Neila","Neile","Neilla","Neille","Nelia","Nelie","Nell","Nelle","Nelli","Nellie","Nelly","Nerissa","Nerita","Nert","Nerta","Nerte","Nerti","Nertie","Nerty","Nessa","Nessi","Nessie","Nessy","Nesta","Netta","Netti","Nettie","Nettle","Netty","Nevsa","Neysa","Nichol","Nichole","Nicholle","Nicki","Nickie","Nicky","Nicol","Nicola","Nicole","Nicolea","Nicolette","Nicoli","Nicolina","Nicoline","Nicolle","Nikaniki","Nike","Niki","Nikki","Nikkie","Nikoletta","Nikolia","Nina","Ninetta","Ninette","Ninnetta","Ninnette","Ninon","Nissa","Nisse","Nissie","Nissy","Nita","Nixie","Noami","Noel","Noelani","Noell","Noella","Noelle","Noellyn","Noelyn","Noemi","Nola","Nolana","Nolie","Nollie","Nomi","Nona","Nonah","Noni","Nonie","Nonna","Nonnah","Nora","Norah","Norean","Noreen","Norene","Norina","Norine","Norma","Norri","Norrie","Norry","Novelia","Nydia","Nyssa","Octavia","Odele","Odelia","Odelinda","Odella","Odelle","Odessa","Odetta","Odette","Odilia","Odille","Ofelia","Ofella","Ofilia","Ola","Olenka","Olga","Olia","Olimpia","Olive","Olivette","Olivia","Olivie","Oliy","Ollie","Olly","Olva","Olwen","Olympe","Olympia","Olympie","Ondrea","Oneida","Onida","Oona","Opal","Opalina","Opaline","Ophelia","Ophelie","Ora","Oralee","Oralia","Oralie","Oralla","Oralle","Orel","Orelee","Orelia","Orelie","Orella","Orelle","Oriana","Orly","Orsa","Orsola","Ortensia","Otha","Othelia","Othella","Othilia","Othilie","Ottilie","Page","Paige","Paloma","Pam","Pamela","Pamelina","Pamella","Pammi","Pammie","Pammy","Pandora","Pansie","Pansy","Paola","Paolina","Papagena","Pat","Patience","Patrica","Patrice","Patricia","Patrizia","Patsy","Patti","Pattie","Patty","Paula","Paule","Pauletta","Paulette","Pauli","Paulie","Paulina","Pauline","Paulita","Pauly","Pavia","Pavla","Pearl","Pearla","Pearle","Pearline","Peg","Pegeen","Peggi","Peggie","Peggy","Pen","Penelopa","Penelope","Penni","Pennie","Penny","Pepi","Pepita","Peri","Peria","Perl","Perla","Perle","Perri","Perrine","Perry","Persis","Pet","Peta","Petra","Petrina","Petronella","Petronia","Petronilla","Petronille","Petunia","Phaedra","Phaidra","Phebe","Phedra","Phelia","Phil","Philipa","Philippa","Philippe","Philippine","Philis","Phillida","Phillie","Phillis","Philly","Philomena","Phoebe","Phylis","Phyllida","Phyllis","Phyllys","Phylys","Pia","Pier","Pierette","Pierrette","Pietra","Piper","Pippa","Pippy","Polly","Pollyanna","Pooh","Poppy","Portia","Pris","Prisca","Priscella","Priscilla","Prissie","Pru","Prudence","Prudi","Prudy","Prue","Queenie","Quentin","Querida","Quinn","Quinta","Quintana","Quintilla","Quintina","Rachael","Rachel","Rachele","Rachelle","Rae","Raeann","Raf","Rafa","Rafaela","Rafaelia","Rafaelita","Rahal","Rahel","Raina","Raine","Rakel","Ralina","Ramona","Ramonda","Rana","Randa","Randee","Randene","Randi","Randie","Randy","Ranee","Rani","Rania","Ranice","Ranique","Ranna","Raphaela","Raquel","Raquela","Rasia","Rasla","Raven","Ray","Raychel","Raye","Rayna","Raynell","Rayshell","Rea","Reba","Rebbecca","Rebe","Rebeca","Rebecca","Rebecka","Rebeka","Rebekah","Rebekkah","Ree","Reeba","Reena","Reeta","Reeva","Regan","Reggi","Reggie","Regina","Regine","Reiko","Reina","Reine","Remy","Rena","Renae","Renata","Renate","Rene","Renee","Renell","Renelle","Renie","Rennie","Reta","Retha","Revkah","Rey","Reyna","Rhea","Rheba","Rheta","Rhetta","Rhiamon","Rhianna","Rhianon","Rhoda","Rhodia","Rhodie","Rhody","Rhona","Rhonda","Riane","Riannon","Rianon","Rica","Ricca","Rici","Ricki","Rickie","Ricky","Riki","Rikki","Rina","Risa","Rita","Riva","Rivalee","Rivi","Rivkah","Rivy","Roana","Roanna","Roanne","Robbi","Robbie","Robbin","Robby","Robbyn","Robena","Robenia","Roberta","Robin","Robina","Robinet","Robinett","Robinetta","Robinette","Robinia","Roby","Robyn","Roch","Rochell","Rochella","Rochelle","Rochette","Roda","Rodi","Rodie","Rodina","Rois","Romola","Romona","Romonda","Romy","Rona","Ronalda","Ronda","Ronica","Ronna","Ronni","Ronnica","Ronnie","Ronny","Roobbie","Rora","Rori","Rorie","Rory","Ros","Rosa","Rosabel","Rosabella","Rosabelle","Rosaleen","Rosalia","Rosalie","Rosalind","Rosalinda","Rosalinde","Rosaline","Rosalyn","Rosalynd","Rosamond","Rosamund","Rosana","Rosanna","Rosanne","Rose","Roseann","Roseanna","Roseanne","Roselia","Roselin","Roseline","Rosella","Roselle","Rosemaria","Rosemarie","Rosemary","Rosemonde","Rosene","Rosetta","Rosette","Roshelle","Rosie","Rosina","Rosita","Roslyn","Rosmunda","Rosy","Row","Rowe","Rowena","Roxana","Roxane","Roxanna","Roxanne","Roxi","Roxie","Roxine","Roxy","Roz","Rozalie","Rozalin","Rozamond","Rozanna","Rozanne","Roze","Rozele","Rozella","Rozelle","Rozina","Rubetta","Rubi","Rubia","Rubie","Rubina","Ruby","Ruperta","Ruth","Ruthann","Ruthanne","Ruthe","Ruthi","Ruthie","Ruthy","Ryann","Rycca","Saba","Sabina","Sabine","Sabra","Sabrina","Sacha","Sada","Sadella","Sadie","Sadye","Saidee","Sal","Salaidh","Sallee","Salli","Sallie","Sally","Sallyann","Sallyanne","Saloma","Salome","Salomi","Sam","Samantha","Samara","Samaria","Sammy","Sande","Sandi","Sandie","Sandra","Sandy","Sandye","Sapphira","Sapphire","Sara","Sara-ann","Saraann","Sarah","Sarajane","Saree","Sarena","Sarene","Sarette","Sari","Sarina","Sarine","Sarita","Sascha","Sasha","Sashenka","Saudra","Saundra","Savina","Sayre","Scarlet","Scarlett","Sean","Seana","Seka","Sela","Selena","Selene","Selestina","Selia","Selie","Selina","Selinda","Seline","Sella","Selle","Selma","Sena","Sephira","Serena","Serene","Shae","Shaina","Shaine","Shalna","Shalne","Shana","Shanda","Shandee","Shandeigh","Shandie","Shandra","Shandy","Shane","Shani","Shanie","Shanna","Shannah","Shannen","Shannon","Shanon","Shanta","Shantee","Shara","Sharai","Shari","Sharia","Sharity","Sharl","Sharla","Sharleen","Sharlene","Sharline","Sharon","Sharona","Sharron","Sharyl","Shaun","Shauna","Shawn","Shawna","Shawnee","Shay","Shayla","Shaylah","Shaylyn","Shaylynn","Shayna","Shayne","Shea","Sheba","Sheela","Sheelagh","Sheelah","Sheena","Sheeree","Sheila","Sheila-kathryn","Sheilah","Shel","Shela","Shelagh","Shelba","Shelbi","Shelby","Shelia","Shell","Shelley","Shelli","Shellie","Shelly","Shena","Sher","Sheree","Sheri","Sherie","Sherill","Sherilyn","Sherline","Sherri","Sherrie","Sherry","Sherye","Sheryl","Shina","Shir","Shirl","Shirlee","Shirleen","Shirlene","Shirley","Shirline","Shoshana","Shoshanna","Siana","Sianna","Sib","Sibbie","Sibby","Sibeal","Sibel","Sibella","Sibelle","Sibilla","Sibley","Sibyl","Sibylla","Sibylle","Sidoney","Sidonia","Sidonnie","Sigrid","Sile","Sileas","Silva","Silvana","Silvia","Silvie","Simona","Simone","Simonette","Simonne","Sindee","Siobhan","Sioux","Siouxie","Sisely","Sisile","Sissie","Sissy","Siusan","Sofia","Sofie","Sondra","Sonia","Sonja","Sonni","Sonnie","Sonnnie","Sonny","Sonya","Sophey","Sophi","Sophia","Sophie","Sophronia","Sorcha","Sosanna","Stace","Stacee","Stacey","Staci","Stacia","Stacie","Stacy","Stafani","Star","Starla","Starlene","Starlin","Starr","Stefa","Stefania","Stefanie","Steffane","Steffi","Steffie","Stella","Stepha","Stephana","Stephani","Stephanie","Stephannie","Stephenie","Stephi","Stephie","Stephine","Stesha","Stevana","Stevena","Stoddard","Storm","Stormi","Stormie","Stormy","Sue","Suellen","Sukey","Suki","Sula","Sunny","Sunshine","Susan","Susana","Susanetta","Susann","Susanna","Susannah","Susanne","Susette","Susi","Susie","Susy","Suzann","Suzanna","Suzanne","Suzette","Suzi","Suzie","Suzy","Sybil","Sybila","Sybilla","Sybille","Sybyl","Sydel","Sydelle","Sydney","Sylvia","Tabatha","Tabbatha","Tabbi","Tabbie","Tabbitha","Tabby","Tabina","Tabitha","Taffy","Talia","Tallia","Tallie","Tallou","Tallulah","Tally","Talya","Talyah","Tamar","Tamara","Tamarah","Tamarra","Tamera","Tami","Tamiko","Tamma","Tammara","Tammi","Tammie","Tammy","Tamqrah","Tamra","Tana","Tandi","Tandie","Tandy","Tanhya","Tani","Tania","Tanitansy","Tansy","Tanya","Tara","Tarah","Tarra","Tarrah","Taryn","Tasha","Tasia","Tate","Tatiana","Tatiania","Tatum","Tawnya","Tawsha","Ted","Tedda","Teddi","Teddie","Teddy","Tedi","Tedra","Teena","Teirtza","Teodora","Tera","Teresa","Terese","Teresina","Teresita","Teressa","Teri","Teriann","Terra","Terri","Terrie","Terrijo","Terry","Terrye","Tersina","Terza","Tess","Tessa","Tessi","Tessie","Tessy","Thalia","Thea","Theadora","Theda","Thekla","Thelma","Theo","Theodora","Theodosia","Theresa","Therese","Theresina","Theresita","Theressa","Therine","Thia","Thomasa","Thomasin","Thomasina","Thomasine","Tiena","Tierney","Tiertza","Tiff","Tiffani","Tiffanie","Tiffany","Tiffi","Tiffie","Tiffy","Tilda","Tildi","Tildie","Tildy","Tillie","Tilly","Tim","Timi","Timmi","Timmie","Timmy","Timothea","Tina","Tine","Tiphani","Tiphanie","Tiphany","Tish","Tisha","Tobe","Tobey","Tobi","Toby","Tobye","Toinette","Toma","Tomasina","Tomasine","Tomi","Tommi","Tommie","Tommy","Toni","Tonia","Tonie","Tony","Tonya","Tonye","Tootsie","Torey","Tori","Torie","Torrie","Tory","Tova","Tove","Tracee","Tracey","Traci","Tracie","Tracy","Trenna","Tresa","Trescha","Tressa","Tricia","Trina","Trish","Trisha","Trista","Trix","Trixi","Trixie","Trixy","Truda","Trude","Trudey","Trudi","Trudie","Trudy","Trula","Tuesday","Twila","Twyla","Tybi","Tybie","Tyne","Ula","Ulla","Ulrica","Ulrika","Ulrikaumeko","Ulrike","Umeko","Una","Ursa","Ursala","Ursola","Ursula","Ursulina","Ursuline","Uta","Val","Valaree","Valaria","Vale","Valeda","Valencia","Valene","Valenka","Valentia","Valentina","Valentine","Valera","Valeria","Valerie","Valery","Valerye","Valida","Valina","Valli","Vallie","Vally","Valma","Valry","Van","Vanda","Vanessa","Vania","Vanna","Vanni","Vannie","Vanny","Vanya","Veda","Velma","Velvet","Venita","Venus","Vera","Veradis","Vere","Verena","Verene","Veriee","Verile","Verina","Verine","Verla","Verna","Vernice","Veronica","Veronika","Veronike","Veronique","Vevay","Vi","Vicki","Vickie","Vicky","Victoria","Vida","Viki","Vikki","Vikky","Vilhelmina","Vilma","Vin","Vina","Vinita","Vinni","Vinnie","Vinny","Viola","Violante","Viole","Violet","Violetta","Violette","Virgie","Virgina","Virginia","Virginie","Vita","Vitia","Vitoria","Vittoria","Viv","Viva","Vivi","Vivia","Vivian","Viviana","Vivianna","Vivianne","Vivie","Vivien","Viviene","Vivienne","Viviyan","Vivyan","Vivyanne","Vonni","Vonnie","Vonny","Vyky","Wallie","Wallis","Walliw","Wally","Waly","Wanda","Wandie","Wandis","Waneta","Wanids","Wenda","Wendeline","Wendi","Wendie","Wendy","Wendye","Wenona","Wenonah","Whitney","Wileen","Wilhelmina","Wilhelmine","Wilie","Willa","Willabella","Willamina","Willetta","Willette","Willi","Willie","Willow","Willy","Willyt","Wilma","Wilmette","Wilona","Wilone","Wilow","Windy","Wini","Winifred","Winna","Winnah","Winne","Winni","Winnie","Winnifred","Winny","Winona","Winonah","Wren","Wrennie","Wylma","Wynn","Wynne","Wynnie","Wynny","Xaviera","Xena","Xenia","Xylia","Xylina","Yalonda","Yasmeen","Yasmin","Yelena","Yetta","Yettie","Yetty","Yevette","Ynes","Ynez","Yoko","Yolanda","Yolande","Yolane","Yolanthe","Yoshi","Yoshiko","Yovonnda","Ysabel","Yvette","Yvonne","Zabrina","Zahara","Zandra","Zaneta","Zara","Zarah","Zaria","Zarla","Zea","Zelda","Zelma","Zena","Zenia","Zia","Zilvia","Zita","Zitella","Zoe","Zola","Zonda","Zondra","Zonnya","Zora","Zorah","Zorana","Zorina","Zorine","Zsazsa","Zulema","Zuzana"],exports.starWars=["Ackbar","Adi Gallia","Anakin Skywalker","Arvel Crynyd","Ayla Secura","Bail Prestor Organa","Barriss Offee","Ben Quadinaros","Beru Whitesun lars","Bib Fortuna","Biggs Darklighter","Boba Fett","Bossk","C-3PO","Chewbacca","Cliegg Lars","Cordé","Darth Maul","Darth Vader","Dexter Jettster","Dooku","Dormé","Dud Bolt","Eeth Koth","Finis Valorum","Gasgano","Greedo","Gregar Typho","Grievous","Han Solo","IG-88","Jabba Desilijic Tiure","Jango Fett","Jar Jar Binks","Jek Tono Porkins","Jocasta Nu","Ki-Adi-Mundi","Kit Fisto","Lama Su","Lando Calrissian","Leia Organa","Lobot","Luke Skywalker","Luminara Unduli","Mace Windu","Mas Amedda","Mon Mothma","Nien Nunb","Nute Gunray","Obi-Wan Kenobi","Owen Lars","Padmé Amidala","Palpatine","Plo Koon","Poggle the Lesser","Quarsh Panaka","Qui-Gon Jinn","R2-D2","R4-P17","R5-D4","Ratts Tyerel","Raymus Antilles","Ric Olié","Roos Tarpals","Rugor Nass","Saesee Tiin","San Hill","Sebulba","Shaak Ti","Shmi Skywalker","Sly Moore","Tarfful","Taun We","Tion Medon","Wat Tambor","Watto","Wedge Antilles","Wicket Systri Warrick","Wilhuff Tarkin","Yarael Poof","Yoda","Zam Wesell"],exports.uniqueNamesGenerator=i=>{const n=[...i&&i.dictionaries||e.dictionaries],l={...e,...i,length:i&&i.length||n.length,dictionaries:n};if(!i||!i.dictionaries||!i.dictionaries.length)throw new Error('A "dictionaries" array must be provided. This is a breaking change introduced starting from Unique Name Generator v4. Read more about the breaking change here: https://github.com/andreasonny83/unique-names-generator#migration-guide');return new a(l).generate()}; -//# sourceMappingURL=index.js.map diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.m.js b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.m.js deleted file mode 100644 index 542b7942ea..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.m.js +++ /dev/null @@ -1,2 +0,0 @@ -class a{constructor(a){this.dictionaries=void 0,this.length=void 0,this.separator=void 0,this.style=void 0,this.seed=void 0;const{length:e,separator:i,dictionaries:n,style:l,seed:r}=a;this.dictionaries=n,this.separator=i,this.length=e,this.style=l,this.seed=r}generate(){if(!this.dictionaries)throw new Error('Cannot find any dictionary. Please provide at least one, or leave the "dictionary" field empty in the config object');if(this.length<=0)throw new Error("Invalid length provided");if(this.length>this.dictionaries.length)throw new Error(`The length cannot be bigger than the number of dictionaries.\nLength provided: ${this.length}. Number of dictionaries provided: ${this.dictionaries.length}`);return this.dictionaries.slice(0,this.length).reduce((a,e)=>{let i=e[Math.floor((this.seed?(n=this.seed,(a=>{a=1831565813+(a|=0)|0;let e=Math.imul(a^a>>>15,1|a);return e=e+Math.imul(e^e>>>7,61|e)^e,((e^e>>>14)>>>0)/4294967296})(n)):Math.random())*e.length)]||"";var n;if("lowerCase"===this.style)i=i.toLowerCase();else if("capital"===this.style){const[a,...e]=i.split("");i=a.toUpperCase()+e.join("")}else"upperCase"===this.style&&(i=i.toUpperCase());return a?`${a}${this.separator}${i}`:`${i}`},"")}}const e={separator:"_",dictionaries:[]},i=i=>{const n=[...i&&i.dictionaries||e.dictionaries],l={...e,...i,length:i&&i.length||n.length,dictionaries:n};if(!i||!i.dictionaries||!i.dictionaries.length)throw new Error('A "dictionaries" array must be provided. This is a breaking change introduced starting from Unique Name Generator v4. Read more about the breaking change here: https://github.com/andreasonny83/unique-names-generator#migration-guide');return new a(l).generate()};var n=["able","above","absent","absolute","abstract","abundant","academic","acceptable","accepted","accessible","accurate","accused","active","actual","acute","added","additional","adequate","adjacent","administrative","adorable","advanced","adverse","advisory","aesthetic","afraid","aggregate","aggressive","agreeable","agreed","agricultural","alert","alive","alleged","allied","alone","alright","alternative","amateur","amazing","ambitious","amused","ancient","angry","annoyed","annual","anonymous","anxious","appalling","apparent","applicable","appropriate","arbitrary","architectural","armed","arrogant","artificial","artistic","ashamed","asleep","assistant","associated","atomic","attractive","automatic","autonomous","available","average","awake","aware","awful","awkward","back","bad","balanced","bare","basic","beautiful","beneficial","better","bewildered","big","binding","biological","bitter","bizarre","blank","blind","blonde","bloody","blushing","boiling","bold","bored","boring","bottom","brainy","brave","breakable","breezy","brief","bright","brilliant","broad","broken","bumpy","burning","busy","calm","capable","capitalist","careful","casual","causal","cautious","central","certain","changing","characteristic","charming","cheap","cheerful","chemical","chief","chilly","chosen","christian","chronic","chubby","circular","civic","civil","civilian","classic","classical","clean","clear","clever","clinical","close","closed","cloudy","clumsy","coastal","cognitive","coherent","cold","collective","colonial","colorful","colossal","coloured","colourful","combative","combined","comfortable","coming","commercial","common","communist","compact","comparable","comparative","compatible","competent","competitive","complete","complex","complicated","comprehensive","compulsory","conceptual","concerned","concrete","condemned","confident","confidential","confused","conscious","conservation","conservative","considerable","consistent","constant","constitutional","contemporary","content","continental","continued","continuing","continuous","controlled","controversial","convenient","conventional","convinced","convincing","cooing","cool","cooperative","corporate","correct","corresponding","costly","courageous","crazy","creative","creepy","criminal","critical","crooked","crowded","crucial","crude","cruel","cuddly","cultural","curious","curly","current","curved","cute","daily","damaged","damp","dangerous","dark","dead","deaf","deafening","dear","decent","decisive","deep","defeated","defensive","defiant","definite","deliberate","delicate","delicious","delighted","delightful","democratic","dependent","depressed","desirable","desperate","detailed","determined","developed","developing","devoted","different","difficult","digital","diplomatic","direct","dirty","disabled","disappointed","disastrous","disciplinary","disgusted","distant","distinct","distinctive","distinguished","disturbed","disturbing","diverse","divine","dizzy","domestic","dominant","double","doubtful","drab","dramatic","dreadful","driving","drunk","dry","dual","due","dull","dusty","dutch","dying","dynamic","eager","early","eastern","easy","economic","educational","eerie","effective","efficient","elaborate","elated","elderly","eldest","electoral","electric","electrical","electronic","elegant","eligible","embarrassed","embarrassing","emotional","empirical","empty","enchanting","encouraging","endless","energetic","enormous","enthusiastic","entire","entitled","envious","environmental","equal","equivalent","essential","established","estimated","ethical","ethnic","eventual","everyday","evident","evil","evolutionary","exact","excellent","exceptional","excess","excessive","excited","exciting","exclusive","existing","exotic","expected","expensive","experienced","experimental","explicit","extended","extensive","external","extra","extraordinary","extreme","exuberant","faint","fair","faithful","familiar","famous","fancy","fantastic","far","fascinating","fashionable","fast","fat","fatal","favourable","favourite","federal","fellow","female","feminist","few","fierce","filthy","final","financial","fine","firm","fiscal","fit","fixed","flaky","flat","flexible","fluffy","fluttering","flying","following","fond","foolish","foreign","formal","formidable","forthcoming","fortunate","forward","fragile","frail","frantic","free","frequent","fresh","friendly","frightened","front","frozen","full","fun","functional","fundamental","funny","furious","future","fuzzy","gastric","gay","general","generous","genetic","gentle","genuine","geographical","giant","gigantic","given","glad","glamorous","gleaming","global","glorious","golden","good","gorgeous","gothic","governing","graceful","gradual","grand","grateful","greasy","great","grieving","grim","gross","grotesque","growing","grubby","grumpy","guilty","handicapped","handsome","happy","hard","harsh","head","healthy","heavy","helpful","helpless","hidden","high","hilarious","hissing","historic","historical","hollow","holy","homeless","homely","hon","honest","horizontal","horrible","hostile","hot","huge","human","hungry","hurt","hushed","husky","icy","ideal","identical","ideological","ill","illegal","imaginative","immediate","immense","imperial","implicit","important","impossible","impressed","impressive","improved","inadequate","inappropriate","inc","inclined","increased","increasing","incredible","independent","indirect","individual","industrial","inevitable","influential","informal","inherent","initial","injured","inland","inner","innocent","innovative","inquisitive","instant","institutional","insufficient","intact","integral","integrated","intellectual","intelligent","intense","intensive","interested","interesting","interim","interior","intermediate","internal","international","intimate","invisible","involved","irrelevant","isolated","itchy","jealous","jittery","joint","jolly","joyous","judicial","juicy","junior","just","keen","key","kind","known","labour","large","late","latin","lazy","leading","left","legal","legislative","legitimate","lengthy","lesser","level","lexical","liable","liberal","light","like","likely","limited","linear","linguistic","liquid","literary","little","live","lively","living","local","logical","lonely","long","loose","lost","loud","lovely","low","loyal","ltd","lucky","mad","magic","magnetic","magnificent","main","major","male","mammoth","managerial","managing","manual","many","marginal","marine","marked","married","marvellous","marxist","mass","massive","mathematical","mature","maximum","mean","meaningful","mechanical","medical","medieval","melodic","melted","mental","mere","metropolitan","mid","middle","mighty","mild","military","miniature","minimal","minimum","ministerial","minor","miserable","misleading","missing","misty","mixed","moaning","mobile","moderate","modern","modest","molecular","monetary","monthly","moral","motionless","muddy","multiple","mushy","musical","mute","mutual","mysterious","naked","narrow","nasty","national","native","natural","naughty","naval","near","nearby","neat","necessary","negative","neighbouring","nervous","net","neutral","new","nice","noble","noisy","normal","northern","nosy","notable","novel","nuclear","numerous","nursing","nutritious","nutty","obedient","objective","obliged","obnoxious","obvious","occasional","occupational","odd","official","ok","okay","old","olympic","only","open","operational","opposite","optimistic","oral","ordinary","organic","organisational","original","orthodox","other","outdoor","outer","outrageous","outside","outstanding","overall","overseas","overwhelming","painful","pale","panicky","parallel","parental","parliamentary","partial","particular","passing","passive","past","patient","payable","peaceful","peculiar","perfect","permanent","persistent","personal","petite","philosophical","physical","plain","planned","plastic","pleasant","pleased","poised","polite","political","poor","popular","positive","possible","potential","powerful","practical","precious","precise","preferred","pregnant","preliminary","premier","prepared","present","presidential","pretty","previous","prickly","primary","prime","primitive","principal","printed","prior","private","probable","productive","professional","profitable","profound","progressive","prominent","promising","proper","proposed","prospective","protective","protestant","proud","provincial","psychiatric","psychological","public","puny","pure","purring","puzzled","quaint","qualified","quarrelsome","querulous","quick","quickest","quiet","quintessential","quixotic","racial","radical","rainy","random","rapid","rare","raspy","rational","ratty","raw","ready","real","realistic","rear","reasonable","recent","reduced","redundant","regional","registered","regular","regulatory","related","relative","relaxed","relevant","reliable","relieved","religious","reluctant","remaining","remarkable","remote","renewed","representative","repulsive","required","resident","residential","resonant","respectable","respective","responsible","resulting","retail","retired","revolutionary","rich","ridiculous","right","rigid","ripe","rising","rival","roasted","robust","rolling","romantic","rotten","rough","round","royal","rubber","rude","ruling","running","rural","sacred","sad","safe","salty","satisfactory","satisfied","scared","scary","scattered","scientific","scornful","scrawny","screeching","secondary","secret","secure","select","selected","selective","selfish","semantic","senior","sensible","sensitive","separate","serious","severe","sexual","shaggy","shaky","shallow","shared","sharp","sheer","shiny","shivering","shocked","short","shrill","shy","sick","significant","silent","silky","silly","similar","simple","single","skilled","skinny","sleepy","slight","slim","slimy","slippery","slow","small","smart","smiling","smoggy","smooth","social","socialist","soft","solar","sole","solid","sophisticated","sore","sorry","sound","sour","southern","soviet","spare","sparkling","spatial","special","specific","specified","spectacular","spicy","spiritual","splendid","spontaneous","sporting","spotless","spotty","square","squealing","stable","stale","standard","static","statistical","statutory","steady","steep","sticky","stiff","still","stingy","stormy","straight","straightforward","strange","strategic","strict","striking","striped","strong","structural","stuck","stupid","subjective","subsequent","substantial","subtle","successful","successive","sudden","sufficient","suitable","sunny","super","superb","superior","supporting","supposed","supreme","sure","surprised","surprising","surrounding","surviving","suspicious","sweet","swift","symbolic","sympathetic","systematic","tall","tame","tart","tasteless","tasty","technical","technological","teenage","temporary","tender","tense","terrible","territorial","testy","then","theoretical","thick","thin","thirsty","thorough","thoughtful","thoughtless","thundering","tight","tiny","tired","top","tory","total","tough","toxic","traditional","tragic","tremendous","tricky","tropical","troubled","typical","ugliest","ugly","ultimate","unable","unacceptable","unaware","uncertain","unchanged","uncomfortable","unconscious","underground","underlying","unemployed","uneven","unexpected","unfair","unfortunate","unhappy","uniform","uninterested","unique","united","universal","unknown","unlikely","unnecessary","unpleasant","unsightly","unusual","unwilling","upper","upset","uptight","urban","urgent","used","useful","useless","usual","vague","valid","valuable","variable","varied","various","varying","vast","verbal","vertical","very","vicarious","vicious","victorious","violent","visible","visiting","visual","vital","vitreous","vivacious","vivid","vocal","vocational","voiceless","voluminous","voluntary","vulnerable","wandering","warm","wasteful","watery","weak","wealthy","weary","wee","weekly","weird","welcome","well","western","wet","whispering","whole","wicked","wide","widespread","wild","wilful","willing","willowy","wily","wise","wispy","wittering","witty","wonderful","wooden","working","worldwide","worried","worrying","worthwhile","worthy","written","wrong","xenacious","xenial","xenogeneic","xenophobic","xeric","xerothermic","yabbering","yammering","yappiest","yappy","yawning","yearling","yearning","yeasty","yelling","yelping","yielding","yodelling","young","youngest","youthful","ytterbic","yucky","yummy","zany","zealous","zeroth","zestful","zesty","zippy","zonal","zoophagous","zygomorphic","zygotic"],l=["aardvark","aardwolf","albatross","alligator","alpaca","amphibian","anaconda","angelfish","anglerfish","ant","anteater","antelope","antlion","ape","aphid","armadillo","asp","baboon","badger","bandicoot","barnacle","barracuda","basilisk","bass","bat","bear","beaver","bedbug","bee","beetle","bird","bison","blackbird","boa","boar","bobcat","bobolink","bonobo","booby","bovid","bug","butterfly","buzzard","camel","canid","canidae","capybara","cardinal","caribou","carp","cat","caterpillar","catfish","catshark","cattle","centipede","cephalopod","chameleon","cheetah","chickadee","chicken","chimpanzee","chinchilla","chipmunk","cicada","clam","clownfish","cobra","cockroach","cod","condor","constrictor","coral","cougar","cow","coyote","crab","crane","crawdad","crayfish","cricket","crocodile","crow","cuckoo","damselfly","deer","dingo","dinosaur","dog","dolphin","donkey","dormouse","dove","dragon","dragonfly","duck","eagle","earthworm","earwig","echidna","eel","egret","elephant","elk","emu","ermine","falcon","felidae","ferret","finch","firefly","fish","flamingo","flea","fly","flyingfish","fowl","fox","frog","galliform","gamefowl","gayal","gazelle","gecko","gerbil","gibbon","giraffe","goat","goldfish","goose","gopher","gorilla","grasshopper","grouse","guan","guanaco","guineafowl","gull","guppy","haddock","halibut","hamster","hare","harrier","hawk","hedgehog","heron","herring","hippopotamus","hookworm","hornet","horse","hoverfly","hummingbird","hyena","iguana","impala","jackal","jaguar","jay","jellyfish","junglefowl","kangaroo","kingfisher","kite","kiwi","koala","koi","krill","ladybug","lamprey","landfowl","lark","leech","lemming","lemur","leopard","leopon","limpet","lion","lizard","llama","lobster","locust","loon","louse","lungfish","lynx","macaw","mackerel","magpie","mammal","manatee","mandrill","marlin","marmoset","marmot","marsupial","marten","mastodon","meadowlark","meerkat","mink","minnow","mite","mockingbird","mole","mollusk","mongoose","monkey","moose","mosquito","moth","mouse","mule","muskox","narwhal","newt","nightingale","ocelot","octopus","opossum","orangutan","orca","ostrich","otter","owl","ox","panda","panther","parakeet","parrot","parrotfish","partridge","peacock","peafowl","pelican","penguin","perch","pheasant","pig","pigeon","pike","pinniped","piranha","planarian","platypus","pony","porcupine","porpoise","possum","prawn","primate","ptarmigan","puffin","puma","python","quail","quelea","quokka","rabbit","raccoon","rat","rattlesnake","raven","reindeer","reptile","rhinoceros","roadrunner","rodent","rook","rooster","roundworm","sailfish","salamander","salmon","sawfish","scallop","scorpion","seahorse","shark","sheep","shrew","shrimp","silkworm","silverfish","skink","skunk","sloth","slug","smelt","snail","snake","snipe","sole","sparrow","spider","spoonbill","squid","squirrel","starfish","stingray","stoat","stork","sturgeon","swallow","swan","swift","swordfish","swordtail","tahr","takin","tapir","tarantula","tarsier","termite","tern","thrush","tick","tiger","tiglon","toad","tortoise","toucan","trout","tuna","turkey","turtle","tyrannosaurus","unicorn","urial","vicuna","viper","vole","vulture","wallaby","walrus","warbler","wasp","weasel","whale","whippet","whitefish","wildcat","wildebeest","wildfowl","wolf","wolverine","wombat","woodpecker","worm","wren","xerinae","yak","zebra"],r=["amaranth","amber","amethyst","apricot","aqua","aquamarine","azure","beige","black","blue","blush","bronze","brown","chocolate","coffee","copper","coral","crimson","cyan","emerald","fuchsia","gold","gray","green","harlequin","indigo","ivory","jade","lavender","lime","magenta","maroon","moccasin","olive","orange","peach","pink","plum","purple","red","rose","salmon","sapphire","scarlet","silver","tan","teal","tomato","turquoise","violet","white","yellow"],t=["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","Andorra","Angola","Anguilla","Antarctica","Antigua & Barbuda","Argentina","Armenia","Aruba","Ascension Island","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia & Herzegovina","Botswana","Brazil","British Indian Ocean Territory","British Virgin Islands","Brunei","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Canary Islands","Cape Verde","Caribbean Netherlands","Cayman Islands","Central African Republic","Ceuta & Melilla","Chad","Chile","China","Christmas Island","Cocos Islands","Colombia","Comoros","Congo","Cook Islands","Costa Rica","Côte d'Ivoire","Croatia","Cuba","Curaçao","Cyprus","Czechia","Denmark","Diego Garcia","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Eurozone","Falkland Islands","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Honduras","Hong Kong SAR China","Hungary","Iceland","India","Indonesia","Iran","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Kosovo","Kuwait","Kyrgyzstan","Laos","Latvia","Lebanon","Lesotho","Liberia","Libya","Liechtenstein","Lithuania","Luxembourg","Macau SAR China","Macedonia","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia","Moldova","Monaco","Mongolia","Montenegro","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","North Korea","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territories","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn Islands","Poland","Portugal","Puerto Rico","Qatar","Réunion","Romania","Russia","Rwanda","Samoa","San Marino","São Tomé & Príncipe","Saudi Arabia","Senegal","Serbia","Seychelles","Sierra Leone","Singapore","Sint Maarten","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia & South Sandwich Islands","South Korea","South Sudan","Spain","Sri Lanka","St. Barthélemy","St. Helena","St. Kitts & Nevis","St. Lucia","St. Martin","St. Pierre & Miquelon","St. Vincent & Grenadines","Sudan","Suriname","Svalbard & Jan Mayen","Swaziland","Sweden","Switzerland","Syria","Taiwan","Tajikistan","Tanzania","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad & Tobago","Tristan da Cunha","Tunisia","Turkey","Turkmenistan","Turks & Caicos Islands","Tuvalu","U.S. Outlying Islands","U.S. Virgin Islands","Uganda","Ukraine","United Arab Emirates","United Kingdom","United Nations","United States","Uruguay","Uzbekistan","Vanuatu","Vatican City","Venezuela","Vietnam","Wallis & Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"],o=["Akan","Amharic","Arabic","Assamese","Awadhi","Azerbaijani","Balochi","Belarusian","Bengali","Bhojpuri","Burmese","Cebuano","Chewa","Chhattisgarhi","Chittagonian","Czech","Deccan","Dhundhari","Dutch","English","French","Fula","Gan","German","Greek","Gujarati","Hakka","Haryanvi","Hausa","Hiligaynon","Hindi","Hmong","Hungarian","Igbo","Ilocano","Italian","Japanese","Javanese","Jin","Kannada","Kazakh","Khmer","Kinyarwanda","Kirundi","Konkani","Korean","Kurdish","Madurese","Magahi","Maithili","Malagasy","Malay","Malayalam","Mandarin","Marathi","Marwari","Min","Mossi","Nepali","Odia","Oromo","Pashto","Persian","Polish","Portuguese","Punjabi","Quechua","Romanian","Russian","Saraiki","Shona","Sindhi","Sinhala","Somali","Spanish","Sundanese","Swedish","Sylheti","Tagalog","Tamil","Telugu","Thai","Turkish","Turkmen","Ukrainian","Urdu","Uyghur","Uzbek","Vietnamese","Wu","Xhosa","Xiang","Yoruba","Yue","Zhuang","Zulu"],s=["Aaren","Aarika","Abagael","Abagail","Abbe","Abbey","Abbi","Abbie","Abby","Abbye","Abigael","Abigail","Abigale","Abra","Ada","Adah","Adaline","Adan","Adara","Adda","Addi","Addia","Addie","Addy","Adel","Adela","Adelaida","Adelaide","Adele","Adelheid","Adelice","Adelina","Adelind","Adeline","Adella","Adelle","Adena","Adey","Adi","Adiana","Adina","Adora","Adore","Adoree","Adorne","Adrea","Adria","Adriaens","Adrian","Adriana","Adriane","Adrianna","Adrianne","Adriena","Adrienne","Aeriel","Aeriela","Aeriell","Afton","Ag","Agace","Agata","Agatha","Agathe","Aggi","Aggie","Aggy","Agna","Agnella","Agnes","Agnese","Agnesse","Agneta","Agnola","Agretha","Aida","Aidan","Aigneis","Aila","Aile","Ailee","Aileen","Ailene","Ailey","Aili","Ailina","Ailis","Ailsun","Ailyn","Aime","Aimee","Aimil","Aindrea","Ainslee","Ainsley","Ainslie","Ajay","Alaine","Alameda","Alana","Alanah","Alane","Alanna","Alayne","Alberta","Albertina","Albertine","Albina","Alecia","Aleda","Aleece","Aleen","Alejandra","Alejandrina","Alena","Alene","Alessandra","Aleta","Alethea","Alex","Alexa","Alexandra","Alexandrina","Alexi","Alexia","Alexina","Alexine","Alexis","Alfi","Alfie","Alfreda","Alfy","Ali","Alia","Alica","Alice","Alicea","Alicia","Alida","Alidia","Alie","Alika","Alikee","Alina","Aline","Alis","Alisa","Alisha","Alison","Alissa","Alisun","Alix","Aliza","Alla","Alleen","Allegra","Allene","Alli","Allianora","Allie","Allina","Allis","Allison","Allissa","Allix","Allsun","Allx","Ally","Allyce","Allyn","Allys","Allyson","Alma","Almeda","Almeria","Almeta","Almira","Almire","Aloise","Aloisia","Aloysia","Alta","Althea","Alvera","Alverta","Alvina","Alvinia","Alvira","Alyce","Alyda","Alys","Alysa","Alyse","Alysia","Alyson","Alyss","Alyssa","Amabel","Amabelle","Amalea","Amalee","Amaleta","Amalia","Amalie","Amalita","Amalle","Amanda","Amandi","Amandie","Amandy","Amara","Amargo","Amata","Amber","Amberly","Ambur","Ame","Amelia","Amelie","Amelina","Ameline","Amelita","Ami","Amie","Amii","Amil","Amitie","Amity","Ammamaria","Amy","Amye","Ana","Anabal","Anabel","Anabella","Anabelle","Analiese","Analise","Anallese","Anallise","Anastasia","Anastasie","Anastassia","Anatola","Andee","Andeee","Anderea","Andi","Andie","Andra","Andrea","Andreana","Andree","Andrei","Andria","Andriana","Andriette","Andromache","Andy","Anestassia","Anet","Anett","Anetta","Anette","Ange","Angel","Angela","Angele","Angelia","Angelica","Angelika","Angelina","Angeline","Angelique","Angelita","Angelle","Angie","Angil","Angy","Ania","Anica","Anissa","Anita","Anitra","Anjanette","Anjela","Ann","Ann-marie","Anna","Anna-diana","Anna-diane","Anna-maria","Annabal","Annabel","Annabela","Annabell","Annabella","Annabelle","Annadiana","Annadiane","Annalee","Annaliese","Annalise","Annamaria","Annamarie","Anne","Anne-corinne","Anne-marie","Annecorinne","Anneliese","Annelise","Annemarie","Annetta","Annette","Anni","Annice","Annie","Annis","Annissa","Annmaria","Annmarie","Annnora","Annora","Anny","Anselma","Ansley","Anstice","Anthe","Anthea","Anthia","Anthiathia","Antoinette","Antonella","Antonetta","Antonia","Antonie","Antonietta","Antonina","Anya","Appolonia","April","Aprilette","Ara","Arabel","Arabela","Arabele","Arabella","Arabelle","Arda","Ardath","Ardeen","Ardelia","Ardelis","Ardella","Ardelle","Arden","Ardene","Ardenia","Ardine","Ardis","Ardisj","Ardith","Ardra","Ardyce","Ardys","Ardyth","Aretha","Ariadne","Ariana","Aridatha","Ariel","Ariela","Ariella","Arielle","Arlana","Arlee","Arleen","Arlen","Arlena","Arlene","Arleta","Arlette","Arleyne","Arlie","Arliene","Arlina","Arlinda","Arline","Arluene","Arly","Arlyn","Arlyne","Aryn","Ashely","Ashia","Ashien","Ashil","Ashla","Ashlan","Ashlee","Ashleigh","Ashlen","Ashley","Ashli","Ashlie","Ashly","Asia","Astra","Astrid","Astrix","Atalanta","Athena","Athene","Atlanta","Atlante","Auberta","Aubine","Aubree","Aubrette","Aubrey","Aubrie","Aubry","Audi","Audie","Audra","Audre","Audrey","Audrie","Audry","Audrye","Audy","Augusta","Auguste","Augustina","Augustine","Aundrea","Aura","Aurea","Aurel","Aurelea","Aurelia","Aurelie","Auria","Aurie","Aurilia","Aurlie","Auroora","Aurora","Aurore","Austin","Austina","Austine","Ava","Aveline","Averil","Averyl","Avie","Avis","Aviva","Avivah","Avril","Avrit","Ayn","Bab","Babara","Babb","Babbette","Babbie","Babette","Babita","Babs","Bambi","Bambie","Bamby","Barb","Barbabra","Barbara","Barbara-anne","Barbaraanne","Barbe","Barbee","Barbette","Barbey","Barbi","Barbie","Barbra","Barby","Bari","Barrie","Barry","Basia","Bathsheba","Batsheva","Bea","Beatrice","Beatrisa","Beatrix","Beatriz","Bebe","Becca","Becka","Becki","Beckie","Becky","Bee","Beilul","Beitris","Bekki","Bel","Belia","Belicia","Belinda","Belita","Bell","Bella","Bellanca","Belle","Bellina","Belva","Belvia","Bendite","Benedetta","Benedicta","Benedikta","Benetta","Benita","Benni","Bennie","Benny","Benoite","Berenice","Beret","Berget","Berna","Bernadene","Bernadette","Bernadina","Bernadine","Bernardina","Bernardine","Bernelle","Bernete","Bernetta","Bernette","Berni","Bernice","Bernie","Bernita","Berny","Berri","Berrie","Berry","Bert","Berta","Berte","Bertha","Berthe","Berti","Bertie","Bertina","Bertine","Berty","Beryl","Beryle","Bess","Bessie","Bessy","Beth","Bethanne","Bethany","Bethena","Bethina","Betsey","Betsy","Betta","Bette","Bette-ann","Betteann","Betteanne","Betti","Bettina","Bettine","Betty","Bettye","Beulah","Bev","Beverie","Beverlee","Beverley","Beverlie","Beverly","Bevvy","Bianca","Bianka","Bibbie","Bibby","Bibbye","Bibi","Biddie","Biddy","Bidget","Bili","Bill","Billi","Billie","Billy","Billye","Binni","Binnie","Binny","Bird","Birdie","Birgit","Birgitta","Blair","Blaire","Blake","Blakelee","Blakeley","Blanca","Blanch","Blancha","Blanche","Blinni","Blinnie","Blinny","Bliss","Blisse","Blithe","Blondell","Blondelle","Blondie","Blondy","Blythe","Bobbe","Bobbee","Bobbette","Bobbi","Bobbie","Bobby","Bobbye","Bobette","Bobina","Bobine","Bobinette","Bonita","Bonnee","Bonni","Bonnibelle","Bonnie","Bonny","Brana","Brandais","Brande","Brandea","Brandi","Brandice","Brandie","Brandise","Brandy","Breanne","Brear","Bree","Breena","Bren","Brena","Brenda","Brenn","Brenna","Brett","Bria","Briana","Brianna","Brianne","Bride","Bridget","Bridgette","Bridie","Brier","Brietta","Brigid","Brigida","Brigit","Brigitta","Brigitte","Brina","Briney","Brinn","Brinna","Briny","Brit","Brita","Britney","Britni","Britt","Britta","Brittan","Brittaney","Brittani","Brittany","Britte","Britteny","Brittne","Brittney","Brittni","Brook","Brooke","Brooks","Brunhilda","Brunhilde","Bryana","Bryn","Bryna","Brynn","Brynna","Brynne","Buffy","Bunni","Bunnie","Bunny","Cacilia","Cacilie","Cahra","Cairistiona","Caitlin","Caitrin","Cal","Calida","Calla","Calley","Calli","Callida","Callie","Cally","Calypso","Cam","Camala","Camel","Camella","Camellia","Cami","Camila","Camile","Camilla","Camille","Cammi","Cammie","Cammy","Candace","Candi","Candice","Candida","Candide","Candie","Candis","Candra","Candy","Caprice","Cara","Caralie","Caren","Carena","Caresa","Caressa","Caresse","Carey","Cari","Caria","Carie","Caril","Carilyn","Carin","Carina","Carine","Cariotta","Carissa","Carita","Caritta","Carla","Carlee","Carleen","Carlen","Carlene","Carley","Carlie","Carlin","Carlina","Carline","Carlita","Carlota","Carlotta","Carly","Carlye","Carlyn","Carlynn","Carlynne","Carma","Carmel","Carmela","Carmelia","Carmelina","Carmelita","Carmella","Carmelle","Carmen","Carmencita","Carmina","Carmine","Carmita","Carmon","Caro","Carol","Carol-jean","Carola","Carolan","Carolann","Carole","Carolee","Carolin","Carolina","Caroline","Caroljean","Carolyn","Carolyne","Carolynn","Caron","Carree","Carri","Carrie","Carrissa","Carroll","Carry","Cary","Caryl","Caryn","Casandra","Casey","Casi","Casie","Cass","Cassandra","Cassandre","Cassandry","Cassaundra","Cassey","Cassi","Cassie","Cassondra","Cassy","Catarina","Cate","Caterina","Catha","Catharina","Catharine","Cathe","Cathee","Catherin","Catherina","Catherine","Cathi","Cathie","Cathleen","Cathlene","Cathrin","Cathrine","Cathryn","Cathy","Cathyleen","Cati","Catie","Catina","Catlaina","Catlee","Catlin","Catrina","Catriona","Caty","Caye","Cayla","Cecelia","Cecil","Cecile","Ceciley","Cecilia","Cecilla","Cecily","Ceil","Cele","Celene","Celesta","Celeste","Celestia","Celestina","Celestine","Celestyn","Celestyna","Celia","Celie","Celina","Celinda","Celine","Celinka","Celisse","Celka","Celle","Cesya","Chad","Chanda","Chandal","Chandra","Channa","Chantal","Chantalle","Charil","Charin","Charis","Charissa","Charisse","Charita","Charity","Charla","Charlean","Charleen","Charlena","Charlene","Charline","Charlot","Charlotta","Charlotte","Charmain","Charmaine","Charmane","Charmian","Charmine","Charmion","Charo","Charyl","Chastity","Chelsae","Chelsea","Chelsey","Chelsie","Chelsy","Cher","Chere","Cherey","Cheri","Cherianne","Cherice","Cherida","Cherie","Cherilyn","Cherilynn","Cherin","Cherise","Cherish","Cherlyn","Cherri","Cherrita","Cherry","Chery","Cherye","Cheryl","Cheslie","Chiarra","Chickie","Chicky","Chiquia","Chiquita","Chlo","Chloe","Chloette","Chloris","Chris","Chrissie","Chrissy","Christa","Christabel","Christabella","Christal","Christalle","Christan","Christean","Christel","Christen","Christi","Christian","Christiana","Christiane","Christie","Christin","Christina","Christine","Christy","Christye","Christyna","Chrysa","Chrysler","Chrystal","Chryste","Chrystel","Cicely","Cicily","Ciel","Cilka","Cinda","Cindee","Cindelyn","Cinderella","Cindi","Cindie","Cindra","Cindy","Cinnamon","Cissiee","Cissy","Clair","Claire","Clara","Clarabelle","Clare","Claresta","Clareta","Claretta","Clarette","Clarey","Clari","Claribel","Clarice","Clarie","Clarinda","Clarine","Clarissa","Clarisse","Clarita","Clary","Claude","Claudelle","Claudetta","Claudette","Claudia","Claudie","Claudina","Claudine","Clea","Clem","Clemence","Clementia","Clementina","Clementine","Clemmie","Clemmy","Cleo","Cleopatra","Clerissa","Clio","Clo","Cloe","Cloris","Clotilda","Clovis","Codee","Codi","Codie","Cody","Coleen","Colene","Coletta","Colette","Colleen","Collen","Collete","Collette","Collie","Colline","Colly","Con","Concettina","Conchita","Concordia","Conni","Connie","Conny","Consolata","Constance","Constancia","Constancy","Constanta","Constantia","Constantina","Constantine","Consuela","Consuelo","Cookie","Cora","Corabel","Corabella","Corabelle","Coral","Coralie","Coraline","Coralyn","Cordelia","Cordelie","Cordey","Cordi","Cordie","Cordula","Cordy","Coreen","Corella","Corenda","Corene","Coretta","Corette","Corey","Cori","Corie","Corilla","Corina","Corine","Corinna","Corinne","Coriss","Corissa","Corliss","Corly","Cornela","Cornelia","Cornelle","Cornie","Corny","Correna","Correy","Corri","Corrianne","Corrie","Corrina","Corrine","Corrinne","Corry","Cortney","Cory","Cosetta","Cosette","Costanza","Courtenay","Courtnay","Courtney","Crin","Cris","Crissie","Crissy","Crista","Cristabel","Cristal","Cristen","Cristi","Cristie","Cristin","Cristina","Cristine","Cristionna","Cristy","Crysta","Crystal","Crystie","Cthrine","Cyb","Cybil","Cybill","Cymbre","Cynde","Cyndi","Cyndia","Cyndie","Cyndy","Cynthea","Cynthia","Cynthie","Cynthy","Dacey","Dacia","Dacie","Dacy","Dael","Daffi","Daffie","Daffy","Dagmar","Dahlia","Daile","Daisey","Daisi","Daisie","Daisy","Dale","Dalenna","Dalia","Dalila","Dallas","Daloris","Damara","Damaris","Damita","Dana","Danell","Danella","Danette","Dani","Dania","Danica","Danice","Daniela","Daniele","Daniella","Danielle","Danika","Danila","Danit","Danita","Danna","Danni","Dannie","Danny","Dannye","Danya","Danyelle","Danyette","Daphene","Daphna","Daphne","Dara","Darb","Darbie","Darby","Darcee","Darcey","Darci","Darcie","Darcy","Darda","Dareen","Darell","Darelle","Dari","Daria","Darice","Darla","Darleen","Darlene","Darline","Darlleen","Daron","Darrelle","Darryl","Darsey","Darsie","Darya","Daryl","Daryn","Dasha","Dasi","Dasie","Dasya","Datha","Daune","Daveen","Daveta","Davida","Davina","Davine","Davita","Dawn","Dawna","Dayle","Dayna","Ddene","De","Deana","Deane","Deanna","Deanne","Deb","Debbi","Debbie","Debby","Debee","Debera","Debi","Debor","Debora","Deborah","Debra","Dede","Dedie","Dedra","Dee","Deeann","Deeanne","Deedee","Deena","Deerdre","Deeyn","Dehlia","Deidre","Deina","Deirdre","Del","Dela","Delcina","Delcine","Delia","Delila","Delilah","Delinda","Dell","Della","Delly","Delora","Delores","Deloria","Deloris","Delphine","Delphinia","Demeter","Demetra","Demetria","Demetris","Dena","Deni","Denice","Denise","Denna","Denni","Dennie","Denny","Deny","Denys","Denyse","Deonne","Desdemona","Desirae","Desiree","Desiri","Deva","Devan","Devi","Devin","Devina","Devinne","Devon","Devondra","Devonna","Devonne","Devora","Di","Diahann","Dian","Diana","Diandra","Diane","Diane-marie","Dianemarie","Diann","Dianna","Dianne","Diannne","Didi","Dido","Diena","Dierdre","Dina","Dinah","Dinnie","Dinny","Dion","Dione","Dionis","Dionne","Dita","Dix","Dixie","Dniren","Dode","Dodi","Dodie","Dody","Doe","Doll","Dolley","Dolli","Dollie","Dolly","Dolores","Dolorita","Doloritas","Domeniga","Dominga","Domini","Dominica","Dominique","Dona","Donella","Donelle","Donetta","Donia","Donica","Donielle","Donna","Donnamarie","Donni","Donnie","Donny","Dora","Doralia","Doralin","Doralyn","Doralynn","Doralynne","Dore","Doreen","Dorelia","Dorella","Dorelle","Dorena","Dorene","Doretta","Dorette","Dorey","Dori","Doria","Dorian","Dorice","Dorie","Dorine","Doris","Dorisa","Dorise","Dorita","Doro","Dorolice","Dorolisa","Dorotea","Doroteya","Dorothea","Dorothee","Dorothy","Dorree","Dorri","Dorrie","Dorris","Dorry","Dorthea","Dorthy","Dory","Dosi","Dot","Doti","Dotti","Dottie","Dotty","Dre","Dreddy","Dredi","Drona","Dru","Druci","Drucie","Drucill","Drucy","Drusi","Drusie","Drusilla","Drusy","Dulce","Dulcea","Dulci","Dulcia","Dulciana","Dulcie","Dulcine","Dulcinea","Dulcy","Dulsea","Dusty","Dyan","Dyana","Dyane","Dyann","Dyanna","Dyanne","Dyna","Dynah","Eachelle","Eada","Eadie","Eadith","Ealasaid","Eartha","Easter","Eba","Ebba","Ebonee","Ebony","Eda","Eddi","Eddie","Eddy","Ede","Edee","Edeline","Eden","Edi","Edie","Edin","Edita","Edith","Editha","Edithe","Ediva","Edna","Edwina","Edy","Edyth","Edythe","Effie","Eileen","Eilis","Eimile","Eirena","Ekaterina","Elaina","Elaine","Elana","Elane","Elayne","Elberta","Elbertina","Elbertine","Eleanor","Eleanora","Eleanore","Electra","Eleen","Elena","Elene","Eleni","Elenore","Eleonora","Eleonore","Elfie","Elfreda","Elfrida","Elfrieda","Elga","Elianora","Elianore","Elicia","Elie","Elinor","Elinore","Elisa","Elisabet","Elisabeth","Elisabetta","Elise","Elisha","Elissa","Elita","Eliza","Elizabet","Elizabeth","Elka","Elke","Ella","Elladine","Elle","Ellen","Ellene","Ellette","Elli","Ellie","Ellissa","Elly","Ellyn","Ellynn","Elmira","Elna","Elnora","Elnore","Eloisa","Eloise","Elonore","Elora","Elsa","Elsbeth","Else","Elset","Elsey","Elsi","Elsie","Elsinore","Elspeth","Elsy","Elva","Elvera","Elvina","Elvira","Elwira","Elyn","Elyse","Elysee","Elysha","Elysia","Elyssa","Em","Ema","Emalee","Emalia","Emelda","Emelia","Emelina","Emeline","Emelita","Emelyne","Emera","Emilee","Emili","Emilia","Emilie","Emiline","Emily","Emlyn","Emlynn","Emlynne","Emma","Emmalee","Emmaline","Emmalyn","Emmalynn","Emmalynne","Emmeline","Emmey","Emmi","Emmie","Emmy","Emmye","Emogene","Emyle","Emylee","Engracia","Enid","Enrica","Enrichetta","Enrika","Enriqueta","Eolanda","Eolande","Eran","Erda","Erena","Erica","Ericha","Ericka","Erika","Erin","Erina","Erinn","Erinna","Erma","Ermengarde","Ermentrude","Ermina","Erminia","Erminie","Erna","Ernaline","Ernesta","Ernestine","Ertha","Eryn","Esma","Esmaria","Esme","Esmeralda","Essa","Essie","Essy","Esta","Estel","Estele","Estell","Estella","Estelle","Ester","Esther","Estrella","Estrellita","Ethel","Ethelda","Ethelin","Ethelind","Etheline","Ethelyn","Ethyl","Etta","Etti","Ettie","Etty","Eudora","Eugenia","Eugenie","Eugine","Eula","Eulalie","Eunice","Euphemia","Eustacia","Eva","Evaleen","Evangelia","Evangelin","Evangelina","Evangeline","Evania","Evanne","Eve","Eveleen","Evelina","Eveline","Evelyn","Evey","Evie","Evita","Evonne","Evvie","Evvy","Evy","Eyde","Eydie","Ezmeralda","Fae","Faina","Faith","Fallon","Fan","Fanchette","Fanchon","Fancie","Fancy","Fanechka","Fania","Fanni","Fannie","Fanny","Fanya","Fara","Farah","Farand","Farica","Farra","Farrah","Farrand","Faun","Faunie","Faustina","Faustine","Fawn","Fawne","Fawnia","Fay","Faydra","Faye","Fayette","Fayina","Fayre","Fayth","Faythe","Federica","Fedora","Felecia","Felicdad","Felice","Felicia","Felicity","Felicle","Felipa","Felisha","Felita","Feliza","Fenelia","Feodora","Ferdinanda","Ferdinande","Fern","Fernanda","Fernande","Fernandina","Ferne","Fey","Fiann","Fianna","Fidela","Fidelia","Fidelity","Fifi","Fifine","Filia","Filide","Filippa","Fina","Fiona","Fionna","Fionnula","Fiorenze","Fleur","Fleurette","Flo","Flor","Flora","Florance","Flore","Florella","Florence","Florencia","Florentia","Florenza","Florette","Flori","Floria","Florida","Florie","Florina","Florinda","Floris","Florri","Florrie","Florry","Flory","Flossi","Flossie","Flossy","Flss","Fran","Francene","Frances","Francesca","Francine","Francisca","Franciska","Francoise","Francyne","Frank","Frankie","Franky","Franni","Frannie","Franny","Frayda","Fred","Freda","Freddi","Freddie","Freddy","Fredelia","Frederica","Fredericka","Frederique","Fredi","Fredia","Fredra","Fredrika","Freida","Frieda","Friederike","Fulvia","Gabbey","Gabbi","Gabbie","Gabey","Gabi","Gabie","Gabriel","Gabriela","Gabriell","Gabriella","Gabrielle","Gabriellia","Gabrila","Gaby","Gae","Gael","Gail","Gale","Galina","Garland","Garnet","Garnette","Gates","Gavra","Gavrielle","Gay","Gaye","Gayel","Gayla","Gayle","Gayleen","Gaylene","Gaynor","Gelya","Gena","Gene","Geneva","Genevieve","Genevra","Genia","Genna","Genni","Gennie","Gennifer","Genny","Genovera","Genvieve","George","Georgeanna","Georgeanne","Georgena","Georgeta","Georgetta","Georgette","Georgia","Georgiana","Georgianna","Georgianne","Georgie","Georgina","Georgine","Geralda","Geraldine","Gerda","Gerhardine","Geri","Gerianna","Gerianne","Gerladina","Germain","Germaine","Germana","Gerri","Gerrie","Gerrilee","Gerry","Gert","Gerta","Gerti","Gertie","Gertrud","Gertruda","Gertrude","Gertrudis","Gerty","Giacinta","Giana","Gianina","Gianna","Gigi","Gilberta","Gilberte","Gilbertina","Gilbertine","Gilda","Gilemette","Gill","Gillan","Gilli","Gillian","Gillie","Gilligan","Gilly","Gina","Ginelle","Ginevra","Ginger","Ginni","Ginnie","Ginnifer","Ginny","Giorgia","Giovanna","Gipsy","Giralda","Gisela","Gisele","Gisella","Giselle","Giuditta","Giulia","Giulietta","Giustina","Gizela","Glad","Gladi","Gladys","Gleda","Glen","Glenda","Glenine","Glenn","Glenna","Glennie","Glennis","Glori","Gloria","Gloriana","Gloriane","Glory","Glyn","Glynda","Glynis","Glynnis","Gnni","Godiva","Golda","Goldarina","Goldi","Goldia","Goldie","Goldina","Goldy","Grace","Gracia","Gracie","Grata","Gratia","Gratiana","Gray","Grayce","Grazia","Greer","Greta","Gretal","Gretchen","Grete","Gretel","Grethel","Gretna","Gretta","Grier","Griselda","Grissel","Guendolen","Guenevere","Guenna","Guglielma","Gui","Guillema","Guillemette","Guinevere","Guinna","Gunilla","Gus","Gusella","Gussi","Gussie","Gussy","Gusta","Gusti","Gustie","Gusty","Gwen","Gwendolen","Gwendolin","Gwendolyn","Gweneth","Gwenette","Gwenneth","Gwenni","Gwennie","Gwenny","Gwenora","Gwenore","Gwyn","Gwyneth","Gwynne","Gypsy","Hadria","Hailee","Haily","Haleigh","Halette","Haley","Hali","Halie","Halimeda","Halley","Halli","Hallie","Hally","Hana","Hanna","Hannah","Hanni","Hannie","Hannis","Hanny","Happy","Harlene","Harley","Harli","Harlie","Harmonia","Harmonie","Harmony","Harri","Harrie","Harriet","Harriett","Harrietta","Harriette","Harriot","Harriott","Hatti","Hattie","Hatty","Hayley","Hazel","Heath","Heather","Heda","Hedda","Heddi","Heddie","Hedi","Hedvig","Hedvige","Hedwig","Hedwiga","Hedy","Heida","Heidi","Heidie","Helaina","Helaine","Helen","Helen-elizabeth","Helena","Helene","Helenka","Helga","Helge","Helli","Heloise","Helsa","Helyn","Hendrika","Henka","Henrie","Henrieta","Henrietta","Henriette","Henryetta","Hephzibah","Hermia","Hermina","Hermine","Herminia","Hermione","Herta","Hertha","Hester","Hesther","Hestia","Hetti","Hettie","Hetty","Hilary","Hilda","Hildagard","Hildagarde","Hilde","Hildegaard","Hildegarde","Hildy","Hillary","Hilliary","Hinda","Holli","Hollie","Holly","Holly-anne","Hollyanne","Honey","Honor","Honoria","Hope","Horatia","Hortense","Hortensia","Hulda","Hyacinth","Hyacintha","Hyacinthe","Hyacinthia","Hyacinthie","Hynda","Ianthe","Ibbie","Ibby","Ida","Idalia","Idalina","Idaline","Idell","Idelle","Idette","Ileana","Ileane","Ilene","Ilise","Ilka","Illa","Ilsa","Ilse","Ilysa","Ilyse","Ilyssa","Imelda","Imogen","Imogene","Imojean","Ina","Indira","Ines","Inesita","Inessa","Inez","Inga","Ingaberg","Ingaborg","Inge","Ingeberg","Ingeborg","Inger","Ingrid","Ingunna","Inna","Iolande","Iolanthe","Iona","Iormina","Ira","Irena","Irene","Irina","Iris","Irita","Irma","Isa","Isabel","Isabelita","Isabella","Isabelle","Isadora","Isahella","Iseabal","Isidora","Isis","Isobel","Issi","Issie","Issy","Ivett","Ivette","Ivie","Ivonne","Ivory","Ivy","Izabel","Jacenta","Jacinda","Jacinta","Jacintha","Jacinthe","Jackelyn","Jacki","Jackie","Jacklin","Jacklyn","Jackquelin","Jackqueline","Jacky","Jaclin","Jaclyn","Jacquelin","Jacqueline","Jacquelyn","Jacquelynn","Jacquenetta","Jacquenette","Jacquetta","Jacquette","Jacqui","Jacquie","Jacynth","Jada","Jade","Jaime","Jaimie","Jaine","Jami","Jamie","Jamima","Jammie","Jan","Jana","Janaya","Janaye","Jandy","Jane","Janean","Janeczka","Janeen","Janel","Janela","Janella","Janelle","Janene","Janenna","Janessa","Janet","Janeta","Janetta","Janette","Janeva","Janey","Jania","Janice","Janie","Janifer","Janina","Janine","Janis","Janith","Janka","Janna","Jannel","Jannelle","Janot","Jany","Jaquelin","Jaquelyn","Jaquenetta","Jaquenette","Jaquith","Jasmin","Jasmina","Jasmine","Jayme","Jaymee","Jayne","Jaynell","Jazmin","Jean","Jeana","Jeane","Jeanelle","Jeanette","Jeanie","Jeanine","Jeanna","Jeanne","Jeannette","Jeannie","Jeannine","Jehanna","Jelene","Jemie","Jemima","Jemimah","Jemmie","Jemmy","Jen","Jena","Jenda","Jenelle","Jeni","Jenica","Jeniece","Jenifer","Jeniffer","Jenilee","Jenine","Jenn","Jenna","Jennee","Jennette","Jenni","Jennica","Jennie","Jennifer","Jennilee","Jennine","Jenny","Jeralee","Jere","Jeri","Jermaine","Jerrie","Jerrilee","Jerrilyn","Jerrine","Jerry","Jerrylee","Jess","Jessa","Jessalin","Jessalyn","Jessamine","Jessamyn","Jesse","Jesselyn","Jessi","Jessica","Jessie","Jessika","Jessy","Jewel","Jewell","Jewelle","Jill","Jillana","Jillane","Jillayne","Jilleen","Jillene","Jilli","Jillian","Jillie","Jilly","Jinny","Jo","Jo-ann","Jo-anne","Joan","Joana","Joane","Joanie","Joann","Joanna","Joanne","Joannes","Jobey","Jobi","Jobie","Jobina","Joby","Jobye","Jobyna","Jocelin","Joceline","Jocelyn","Jocelyne","Jodee","Jodi","Jodie","Jody","Joeann","Joela","Joelie","Joell","Joella","Joelle","Joellen","Joelly","Joellyn","Joelynn","Joete","Joey","Johanna","Johannah","Johna","Johnath","Johnette","Johnna","Joice","Jojo","Jolee","Joleen","Jolene","Joletta","Joli","Jolie","Joline","Joly","Jolyn","Jolynn","Jonell","Joni","Jonie","Jonis","Jordain","Jordan","Jordana","Jordanna","Jorey","Jori","Jorie","Jorrie","Jorry","Joscelin","Josee","Josefa","Josefina","Josepha","Josephina","Josephine","Josey","Josi","Josie","Josselyn","Josy","Jourdan","Joy","Joya","Joyan","Joyann","Joyce","Joycelin","Joye","Jsandye","Juana","Juanita","Judi","Judie","Judith","Juditha","Judy","Judye","Juieta","Julee","Juli","Julia","Juliana","Juliane","Juliann","Julianna","Julianne","Julie","Julienne","Juliet","Julieta","Julietta","Juliette","Julina","Juline","Julissa","Julita","June","Junette","Junia","Junie","Junina","Justina","Justine","Justinn","Jyoti","Kacey","Kacie","Kacy","Kaela","Kai","Kaia","Kaila","Kaile","Kailey","Kaitlin","Kaitlyn","Kaitlynn","Kaja","Kakalina","Kala","Kaleena","Kali","Kalie","Kalila","Kalina","Kalinda","Kalindi","Kalli","Kally","Kameko","Kamila","Kamilah","Kamillah","Kandace","Kandy","Kania","Kanya","Kara","Kara-lynn","Karalee","Karalynn","Kare","Karee","Karel","Karen","Karena","Kari","Karia","Karie","Karil","Karilynn","Karin","Karina","Karine","Kariotta","Karisa","Karissa","Karita","Karla","Karlee","Karleen","Karlen","Karlene","Karlie","Karlotta","Karlotte","Karly","Karlyn","Karmen","Karna","Karol","Karola","Karole","Karolina","Karoline","Karoly","Karon","Karrah","Karrie","Karry","Kary","Karyl","Karylin","Karyn","Kasey","Kass","Kassandra","Kassey","Kassi","Kassia","Kassie","Kat","Kata","Katalin","Kate","Katee","Katerina","Katerine","Katey","Kath","Katha","Katharina","Katharine","Katharyn","Kathe","Katherina","Katherine","Katheryn","Kathi","Kathie","Kathleen","Kathlin","Kathrine","Kathryn","Kathryne","Kathy","Kathye","Kati","Katie","Katina","Katine","Katinka","Katleen","Katlin","Katrina","Katrine","Katrinka","Katti","Kattie","Katuscha","Katusha","Katy","Katya","Kay","Kaycee","Kaye","Kayla","Kayle","Kaylee","Kayley","Kaylil","Kaylyn","Keeley","Keelia","Keely","Kelcey","Kelci","Kelcie","Kelcy","Kelila","Kellen","Kelley","Kelli","Kellia","Kellie","Kellina","Kellsie","Kelly","Kellyann","Kelsey","Kelsi","Kelsy","Kendra","Kendre","Kenna","Keri","Keriann","Kerianne","Kerri","Kerrie","Kerrill","Kerrin","Kerry","Kerstin","Kesley","Keslie","Kessia","Kessiah","Ketti","Kettie","Ketty","Kevina","Kevyn","Ki","Kiah","Kial","Kiele","Kiersten","Kikelia","Kiley","Kim","Kimberlee","Kimberley","Kimberli","Kimberly","Kimberlyn","Kimbra","Kimmi","Kimmie","Kimmy","Kinna","Kip","Kipp","Kippie","Kippy","Kira","Kirbee","Kirbie","Kirby","Kiri","Kirsten","Kirsteni","Kirsti","Kirstin","Kirstyn","Kissee","Kissiah","Kissie","Kit","Kitti","Kittie","Kitty","Kizzee","Kizzie","Klara","Klarika","Klarrisa","Konstance","Konstanze","Koo","Kora","Koral","Koralle","Kordula","Kore","Korella","Koren","Koressa","Kori","Korie","Korney","Korrie","Korry","Kris","Krissie","Krissy","Krista","Kristal","Kristan","Kriste","Kristel","Kristen","Kristi","Kristien","Kristin","Kristina","Kristine","Kristy","Kristyn","Krysta","Krystal","Krystalle","Krystle","Krystyna","Kyla","Kyle","Kylen","Kylie","Kylila","Kylynn","Kym","Kynthia","Kyrstin","Lacee","Lacey","Lacie","Lacy","Ladonna","Laetitia","Laina","Lainey","Lana","Lanae","Lane","Lanette","Laney","Lani","Lanie","Lanita","Lanna","Lanni","Lanny","Lara","Laraine","Lari","Larina","Larine","Larisa","Larissa","Lark","Laryssa","Latashia","Latia","Latisha","Latrena","Latrina","Laura","Lauraine","Laural","Lauralee","Laure","Lauree","Laureen","Laurel","Laurella","Lauren","Laurena","Laurene","Lauretta","Laurette","Lauri","Laurianne","Laurice","Laurie","Lauryn","Lavena","Laverna","Laverne","Lavina","Lavinia","Lavinie","Layla","Layne","Layney","Lea","Leah","Leandra","Leann","Leanna","Leanor","Leanora","Lebbie","Leda","Lee","Leeann","Leeanne","Leela","Leelah","Leena","Leesa","Leese","Legra","Leia","Leigh","Leigha","Leila","Leilah","Leisha","Lela","Lelah","Leland","Lelia","Lena","Lenee","Lenette","Lenka","Lenna","Lenora","Lenore","Leodora","Leoine","Leola","Leoline","Leona","Leonanie","Leone","Leonelle","Leonie","Leonora","Leonore","Leontine","Leontyne","Leora","Leshia","Lesley","Lesli","Leslie","Lesly","Lesya","Leta","Lethia","Leticia","Letisha","Letitia","Letizia","Letta","Letti","Lettie","Letty","Lexi","Lexie","Lexine","Lexis","Lexy","Leyla","Lezlie","Lia","Lian","Liana","Liane","Lianna","Lianne","Lib","Libbey","Libbi","Libbie","Libby","Licha","Lida","Lidia","Liesa","Lil","Lila","Lilah","Lilas","Lilia","Lilian","Liliane","Lilias","Lilith","Lilla","Lilli","Lillian","Lillis","Lilllie","Lilly","Lily","Lilyan","Lin","Lina","Lind","Linda","Lindi","Lindie","Lindsay","Lindsey","Lindsy","Lindy","Linea","Linell","Linet","Linette","Linn","Linnea","Linnell","Linnet","Linnie","Linzy","Lira","Lisa","Lisabeth","Lisbeth","Lise","Lisetta","Lisette","Lisha","Lishe","Lissa","Lissi","Lissie","Lissy","Lita","Liuka","Liv","Liva","Livia","Livvie","Livvy","Livvyy","Livy","Liz","Liza","Lizabeth","Lizbeth","Lizette","Lizzie","Lizzy","Loella","Lois","Loise","Lola","Loleta","Lolita","Lolly","Lona","Lonee","Loni","Lonna","Lonni","Lonnie","Lora","Lorain","Loraine","Loralee","Loralie","Loralyn","Loree","Loreen","Lorelei","Lorelle","Loren","Lorena","Lorene","Lorenza","Loretta","Lorette","Lori","Loria","Lorianna","Lorianne","Lorie","Lorilee","Lorilyn","Lorinda","Lorine","Lorita","Lorna","Lorne","Lorraine","Lorrayne","Lorri","Lorrie","Lorrin","Lorry","Lory","Lotta","Lotte","Lotti","Lottie","Lotty","Lou","Louella","Louisa","Louise","Louisette","Loutitia","Lu","Luce","Luci","Lucia","Luciana","Lucie","Lucienne","Lucila","Lucilia","Lucille","Lucina","Lucinda","Lucine","Lucita","Lucky","Lucretia","Lucy","Ludovika","Luella","Luelle","Luisa","Luise","Lula","Lulita","Lulu","Lura","Lurette","Lurleen","Lurlene","Lurline","Lusa","Luz","Lyda","Lydia","Lydie","Lyn","Lynda","Lynde","Lyndel","Lyndell","Lyndsay","Lyndsey","Lyndsie","Lyndy","Lynea","Lynelle","Lynett","Lynette","Lynn","Lynna","Lynne","Lynnea","Lynnell","Lynnelle","Lynnet","Lynnett","Lynnette","Lynsey","Lyssa","Mab","Mabel","Mabelle","Mable","Mada","Madalena","Madalyn","Maddalena","Maddi","Maddie","Maddy","Madel","Madelaine","Madeleine","Madelena","Madelene","Madelin","Madelina","Madeline","Madella","Madelle","Madelon","Madelyn","Madge","Madlen","Madlin","Madonna","Mady","Mae","Maegan","Mag","Magda","Magdaia","Magdalen","Magdalena","Magdalene","Maggee","Maggi","Maggie","Maggy","Mahala","Mahalia","Maia","Maible","Maiga","Maighdiln","Mair","Maire","Maisey","Maisie","Maitilde","Mala","Malanie","Malena","Malia","Malina","Malinda","Malinde","Malissa","Malissia","Mallissa","Mallorie","Mallory","Malorie","Malory","Malva","Malvina","Malynda","Mame","Mamie","Manda","Mandi","Mandie","Mandy","Manon","Manya","Mara","Marabel","Marcela","Marcelia","Marcella","Marcelle","Marcellina","Marcelline","Marchelle","Marci","Marcia","Marcie","Marcile","Marcille","Marcy","Mareah","Maren","Marena","Maressa","Marga","Margalit","Margalo","Margaret","Margareta","Margarete","Margaretha","Margarethe","Margaretta","Margarette","Margarita","Margaux","Marge","Margeaux","Margery","Marget","Margette","Margi","Margie","Margit","Margo","Margot","Margret","Marguerite","Margy","Mari","Maria","Mariam","Marian","Mariana","Mariann","Marianna","Marianne","Maribel","Maribelle","Maribeth","Marice","Maridel","Marie","Marie-ann","Marie-jeanne","Marieann","Mariejeanne","Mariel","Mariele","Marielle","Mariellen","Marietta","Mariette","Marigold","Marijo","Marika","Marilee","Marilin","Marillin","Marilyn","Marin","Marina","Marinna","Marion","Mariquilla","Maris","Marisa","Mariska","Marissa","Marita","Maritsa","Mariya","Marj","Marja","Marje","Marji","Marjie","Marjorie","Marjory","Marjy","Marketa","Marla","Marlane","Marleah","Marlee","Marleen","Marlena","Marlene","Marley","Marlie","Marline","Marlo","Marlyn","Marna","Marne","Marney","Marni","Marnia","Marnie","Marquita","Marrilee","Marris","Marrissa","Marsha","Marsiella","Marta","Martelle","Martguerita","Martha","Marthe","Marthena","Marti","Martica","Martie","Martina","Martita","Marty","Martynne","Mary","Marya","Maryann","Maryanna","Maryanne","Marybelle","Marybeth","Maryellen","Maryjane","Maryjo","Maryl","Marylee","Marylin","Marylinda","Marylou","Marylynne","Maryrose","Marys","Marysa","Masha","Matelda","Mathilda","Mathilde","Matilda","Matilde","Matti","Mattie","Matty","Maud","Maude","Maudie","Maura","Maure","Maureen","Maureene","Maurene","Maurine","Maurise","Maurita","Maurizia","Mavis","Mavra","Max","Maxi","Maxie","Maxine","Maxy","May","Maybelle","Maye","Mead","Meade","Meagan","Meaghan","Meara","Mechelle","Meg","Megan","Megen","Meggi","Meggie","Meggy","Meghan","Meghann","Mehetabel","Mei","Mel","Mela","Melamie","Melania","Melanie","Melantha","Melany","Melba","Melesa","Melessa","Melicent","Melina","Melinda","Melinde","Melisa","Melisande","Melisandra","Melisenda","Melisent","Melissa","Melisse","Melita","Melitta","Mella","Melli","Mellicent","Mellie","Mellisa","Mellisent","Melloney","Melly","Melodee","Melodie","Melody","Melonie","Melony","Melosa","Melva","Mercedes","Merci","Mercie","Mercy","Meredith","Meredithe","Meridel","Meridith","Meriel","Merilee","Merilyn","Meris","Merissa","Merl","Merla","Merle","Merlina","Merline","Merna","Merola","Merralee","Merridie","Merrie","Merrielle","Merrile","Merrilee","Merrili","Merrill","Merrily","Merry","Mersey","Meryl","Meta","Mia","Micaela","Michaela","Michaelina","Michaeline","Michaella","Michal","Michel","Michele","Michelina","Micheline","Michell","Michelle","Micki","Mickie","Micky","Midge","Mignon","Mignonne","Miguela","Miguelita","Mikaela","Mil","Mildred","Mildrid","Milena","Milicent","Milissent","Milka","Milli","Millicent","Millie","Millisent","Milly","Milzie","Mimi","Min","Mina","Minda","Mindy","Minerva","Minetta","Minette","Minna","Minnaminnie","Minne","Minni","Minnie","Minnnie","Minny","Minta","Miquela","Mira","Mirabel","Mirabella","Mirabelle","Miran","Miranda","Mireielle","Mireille","Mirella","Mirelle","Miriam","Mirilla","Mirna","Misha","Missie","Missy","Misti","Misty","Mitzi","Modesta","Modestia","Modestine","Modesty","Moina","Moira","Moll","Mollee","Molli","Mollie","Molly","Mommy","Mona","Monah","Monica","Monika","Monique","Mora","Moreen","Morena","Morgan","Morgana","Morganica","Morganne","Morgen","Moria","Morissa","Morna","Moselle","Moyna","Moyra","Mozelle","Muffin","Mufi","Mufinella","Muire","Mureil","Murial","Muriel","Murielle","Myra","Myrah","Myranda","Myriam","Myrilla","Myrle","Myrlene","Myrna","Myrta","Myrtia","Myrtice","Myrtie","Myrtle","Nada","Nadean","Nadeen","Nadia","Nadine","Nadiya","Nady","Nadya","Nalani","Nan","Nana","Nananne","Nance","Nancee","Nancey","Nanci","Nancie","Nancy","Nanete","Nanette","Nani","Nanice","Nanine","Nannette","Nanni","Nannie","Nanny","Nanon","Naoma","Naomi","Nara","Nari","Nariko","Nat","Nata","Natala","Natalee","Natalie","Natalina","Nataline","Natalya","Natasha","Natassia","Nathalia","Nathalie","Natividad","Natka","Natty","Neala","Neda","Nedda","Nedi","Neely","Neila","Neile","Neilla","Neille","Nelia","Nelie","Nell","Nelle","Nelli","Nellie","Nelly","Nerissa","Nerita","Nert","Nerta","Nerte","Nerti","Nertie","Nerty","Nessa","Nessi","Nessie","Nessy","Nesta","Netta","Netti","Nettie","Nettle","Netty","Nevsa","Neysa","Nichol","Nichole","Nicholle","Nicki","Nickie","Nicky","Nicol","Nicola","Nicole","Nicolea","Nicolette","Nicoli","Nicolina","Nicoline","Nicolle","Nikaniki","Nike","Niki","Nikki","Nikkie","Nikoletta","Nikolia","Nina","Ninetta","Ninette","Ninnetta","Ninnette","Ninon","Nissa","Nisse","Nissie","Nissy","Nita","Nixie","Noami","Noel","Noelani","Noell","Noella","Noelle","Noellyn","Noelyn","Noemi","Nola","Nolana","Nolie","Nollie","Nomi","Nona","Nonah","Noni","Nonie","Nonna","Nonnah","Nora","Norah","Norean","Noreen","Norene","Norina","Norine","Norma","Norri","Norrie","Norry","Novelia","Nydia","Nyssa","Octavia","Odele","Odelia","Odelinda","Odella","Odelle","Odessa","Odetta","Odette","Odilia","Odille","Ofelia","Ofella","Ofilia","Ola","Olenka","Olga","Olia","Olimpia","Olive","Olivette","Olivia","Olivie","Oliy","Ollie","Olly","Olva","Olwen","Olympe","Olympia","Olympie","Ondrea","Oneida","Onida","Oona","Opal","Opalina","Opaline","Ophelia","Ophelie","Ora","Oralee","Oralia","Oralie","Oralla","Oralle","Orel","Orelee","Orelia","Orelie","Orella","Orelle","Oriana","Orly","Orsa","Orsola","Ortensia","Otha","Othelia","Othella","Othilia","Othilie","Ottilie","Page","Paige","Paloma","Pam","Pamela","Pamelina","Pamella","Pammi","Pammie","Pammy","Pandora","Pansie","Pansy","Paola","Paolina","Papagena","Pat","Patience","Patrica","Patrice","Patricia","Patrizia","Patsy","Patti","Pattie","Patty","Paula","Paule","Pauletta","Paulette","Pauli","Paulie","Paulina","Pauline","Paulita","Pauly","Pavia","Pavla","Pearl","Pearla","Pearle","Pearline","Peg","Pegeen","Peggi","Peggie","Peggy","Pen","Penelopa","Penelope","Penni","Pennie","Penny","Pepi","Pepita","Peri","Peria","Perl","Perla","Perle","Perri","Perrine","Perry","Persis","Pet","Peta","Petra","Petrina","Petronella","Petronia","Petronilla","Petronille","Petunia","Phaedra","Phaidra","Phebe","Phedra","Phelia","Phil","Philipa","Philippa","Philippe","Philippine","Philis","Phillida","Phillie","Phillis","Philly","Philomena","Phoebe","Phylis","Phyllida","Phyllis","Phyllys","Phylys","Pia","Pier","Pierette","Pierrette","Pietra","Piper","Pippa","Pippy","Polly","Pollyanna","Pooh","Poppy","Portia","Pris","Prisca","Priscella","Priscilla","Prissie","Pru","Prudence","Prudi","Prudy","Prue","Queenie","Quentin","Querida","Quinn","Quinta","Quintana","Quintilla","Quintina","Rachael","Rachel","Rachele","Rachelle","Rae","Raeann","Raf","Rafa","Rafaela","Rafaelia","Rafaelita","Rahal","Rahel","Raina","Raine","Rakel","Ralina","Ramona","Ramonda","Rana","Randa","Randee","Randene","Randi","Randie","Randy","Ranee","Rani","Rania","Ranice","Ranique","Ranna","Raphaela","Raquel","Raquela","Rasia","Rasla","Raven","Ray","Raychel","Raye","Rayna","Raynell","Rayshell","Rea","Reba","Rebbecca","Rebe","Rebeca","Rebecca","Rebecka","Rebeka","Rebekah","Rebekkah","Ree","Reeba","Reena","Reeta","Reeva","Regan","Reggi","Reggie","Regina","Regine","Reiko","Reina","Reine","Remy","Rena","Renae","Renata","Renate","Rene","Renee","Renell","Renelle","Renie","Rennie","Reta","Retha","Revkah","Rey","Reyna","Rhea","Rheba","Rheta","Rhetta","Rhiamon","Rhianna","Rhianon","Rhoda","Rhodia","Rhodie","Rhody","Rhona","Rhonda","Riane","Riannon","Rianon","Rica","Ricca","Rici","Ricki","Rickie","Ricky","Riki","Rikki","Rina","Risa","Rita","Riva","Rivalee","Rivi","Rivkah","Rivy","Roana","Roanna","Roanne","Robbi","Robbie","Robbin","Robby","Robbyn","Robena","Robenia","Roberta","Robin","Robina","Robinet","Robinett","Robinetta","Robinette","Robinia","Roby","Robyn","Roch","Rochell","Rochella","Rochelle","Rochette","Roda","Rodi","Rodie","Rodina","Rois","Romola","Romona","Romonda","Romy","Rona","Ronalda","Ronda","Ronica","Ronna","Ronni","Ronnica","Ronnie","Ronny","Roobbie","Rora","Rori","Rorie","Rory","Ros","Rosa","Rosabel","Rosabella","Rosabelle","Rosaleen","Rosalia","Rosalie","Rosalind","Rosalinda","Rosalinde","Rosaline","Rosalyn","Rosalynd","Rosamond","Rosamund","Rosana","Rosanna","Rosanne","Rose","Roseann","Roseanna","Roseanne","Roselia","Roselin","Roseline","Rosella","Roselle","Rosemaria","Rosemarie","Rosemary","Rosemonde","Rosene","Rosetta","Rosette","Roshelle","Rosie","Rosina","Rosita","Roslyn","Rosmunda","Rosy","Row","Rowe","Rowena","Roxana","Roxane","Roxanna","Roxanne","Roxi","Roxie","Roxine","Roxy","Roz","Rozalie","Rozalin","Rozamond","Rozanna","Rozanne","Roze","Rozele","Rozella","Rozelle","Rozina","Rubetta","Rubi","Rubia","Rubie","Rubina","Ruby","Ruperta","Ruth","Ruthann","Ruthanne","Ruthe","Ruthi","Ruthie","Ruthy","Ryann","Rycca","Saba","Sabina","Sabine","Sabra","Sabrina","Sacha","Sada","Sadella","Sadie","Sadye","Saidee","Sal","Salaidh","Sallee","Salli","Sallie","Sally","Sallyann","Sallyanne","Saloma","Salome","Salomi","Sam","Samantha","Samara","Samaria","Sammy","Sande","Sandi","Sandie","Sandra","Sandy","Sandye","Sapphira","Sapphire","Sara","Sara-ann","Saraann","Sarah","Sarajane","Saree","Sarena","Sarene","Sarette","Sari","Sarina","Sarine","Sarita","Sascha","Sasha","Sashenka","Saudra","Saundra","Savina","Sayre","Scarlet","Scarlett","Sean","Seana","Seka","Sela","Selena","Selene","Selestina","Selia","Selie","Selina","Selinda","Seline","Sella","Selle","Selma","Sena","Sephira","Serena","Serene","Shae","Shaina","Shaine","Shalna","Shalne","Shana","Shanda","Shandee","Shandeigh","Shandie","Shandra","Shandy","Shane","Shani","Shanie","Shanna","Shannah","Shannen","Shannon","Shanon","Shanta","Shantee","Shara","Sharai","Shari","Sharia","Sharity","Sharl","Sharla","Sharleen","Sharlene","Sharline","Sharon","Sharona","Sharron","Sharyl","Shaun","Shauna","Shawn","Shawna","Shawnee","Shay","Shayla","Shaylah","Shaylyn","Shaylynn","Shayna","Shayne","Shea","Sheba","Sheela","Sheelagh","Sheelah","Sheena","Sheeree","Sheila","Sheila-kathryn","Sheilah","Shel","Shela","Shelagh","Shelba","Shelbi","Shelby","Shelia","Shell","Shelley","Shelli","Shellie","Shelly","Shena","Sher","Sheree","Sheri","Sherie","Sherill","Sherilyn","Sherline","Sherri","Sherrie","Sherry","Sherye","Sheryl","Shina","Shir","Shirl","Shirlee","Shirleen","Shirlene","Shirley","Shirline","Shoshana","Shoshanna","Siana","Sianna","Sib","Sibbie","Sibby","Sibeal","Sibel","Sibella","Sibelle","Sibilla","Sibley","Sibyl","Sibylla","Sibylle","Sidoney","Sidonia","Sidonnie","Sigrid","Sile","Sileas","Silva","Silvana","Silvia","Silvie","Simona","Simone","Simonette","Simonne","Sindee","Siobhan","Sioux","Siouxie","Sisely","Sisile","Sissie","Sissy","Siusan","Sofia","Sofie","Sondra","Sonia","Sonja","Sonni","Sonnie","Sonnnie","Sonny","Sonya","Sophey","Sophi","Sophia","Sophie","Sophronia","Sorcha","Sosanna","Stace","Stacee","Stacey","Staci","Stacia","Stacie","Stacy","Stafani","Star","Starla","Starlene","Starlin","Starr","Stefa","Stefania","Stefanie","Steffane","Steffi","Steffie","Stella","Stepha","Stephana","Stephani","Stephanie","Stephannie","Stephenie","Stephi","Stephie","Stephine","Stesha","Stevana","Stevena","Stoddard","Storm","Stormi","Stormie","Stormy","Sue","Suellen","Sukey","Suki","Sula","Sunny","Sunshine","Susan","Susana","Susanetta","Susann","Susanna","Susannah","Susanne","Susette","Susi","Susie","Susy","Suzann","Suzanna","Suzanne","Suzette","Suzi","Suzie","Suzy","Sybil","Sybila","Sybilla","Sybille","Sybyl","Sydel","Sydelle","Sydney","Sylvia","Tabatha","Tabbatha","Tabbi","Tabbie","Tabbitha","Tabby","Tabina","Tabitha","Taffy","Talia","Tallia","Tallie","Tallou","Tallulah","Tally","Talya","Talyah","Tamar","Tamara","Tamarah","Tamarra","Tamera","Tami","Tamiko","Tamma","Tammara","Tammi","Tammie","Tammy","Tamqrah","Tamra","Tana","Tandi","Tandie","Tandy","Tanhya","Tani","Tania","Tanitansy","Tansy","Tanya","Tara","Tarah","Tarra","Tarrah","Taryn","Tasha","Tasia","Tate","Tatiana","Tatiania","Tatum","Tawnya","Tawsha","Ted","Tedda","Teddi","Teddie","Teddy","Tedi","Tedra","Teena","Teirtza","Teodora","Tera","Teresa","Terese","Teresina","Teresita","Teressa","Teri","Teriann","Terra","Terri","Terrie","Terrijo","Terry","Terrye","Tersina","Terza","Tess","Tessa","Tessi","Tessie","Tessy","Thalia","Thea","Theadora","Theda","Thekla","Thelma","Theo","Theodora","Theodosia","Theresa","Therese","Theresina","Theresita","Theressa","Therine","Thia","Thomasa","Thomasin","Thomasina","Thomasine","Tiena","Tierney","Tiertza","Tiff","Tiffani","Tiffanie","Tiffany","Tiffi","Tiffie","Tiffy","Tilda","Tildi","Tildie","Tildy","Tillie","Tilly","Tim","Timi","Timmi","Timmie","Timmy","Timothea","Tina","Tine","Tiphani","Tiphanie","Tiphany","Tish","Tisha","Tobe","Tobey","Tobi","Toby","Tobye","Toinette","Toma","Tomasina","Tomasine","Tomi","Tommi","Tommie","Tommy","Toni","Tonia","Tonie","Tony","Tonya","Tonye","Tootsie","Torey","Tori","Torie","Torrie","Tory","Tova","Tove","Tracee","Tracey","Traci","Tracie","Tracy","Trenna","Tresa","Trescha","Tressa","Tricia","Trina","Trish","Trisha","Trista","Trix","Trixi","Trixie","Trixy","Truda","Trude","Trudey","Trudi","Trudie","Trudy","Trula","Tuesday","Twila","Twyla","Tybi","Tybie","Tyne","Ula","Ulla","Ulrica","Ulrika","Ulrikaumeko","Ulrike","Umeko","Una","Ursa","Ursala","Ursola","Ursula","Ursulina","Ursuline","Uta","Val","Valaree","Valaria","Vale","Valeda","Valencia","Valene","Valenka","Valentia","Valentina","Valentine","Valera","Valeria","Valerie","Valery","Valerye","Valida","Valina","Valli","Vallie","Vally","Valma","Valry","Van","Vanda","Vanessa","Vania","Vanna","Vanni","Vannie","Vanny","Vanya","Veda","Velma","Velvet","Venita","Venus","Vera","Veradis","Vere","Verena","Verene","Veriee","Verile","Verina","Verine","Verla","Verna","Vernice","Veronica","Veronika","Veronike","Veronique","Vevay","Vi","Vicki","Vickie","Vicky","Victoria","Vida","Viki","Vikki","Vikky","Vilhelmina","Vilma","Vin","Vina","Vinita","Vinni","Vinnie","Vinny","Viola","Violante","Viole","Violet","Violetta","Violette","Virgie","Virgina","Virginia","Virginie","Vita","Vitia","Vitoria","Vittoria","Viv","Viva","Vivi","Vivia","Vivian","Viviana","Vivianna","Vivianne","Vivie","Vivien","Viviene","Vivienne","Viviyan","Vivyan","Vivyanne","Vonni","Vonnie","Vonny","Vyky","Wallie","Wallis","Walliw","Wally","Waly","Wanda","Wandie","Wandis","Waneta","Wanids","Wenda","Wendeline","Wendi","Wendie","Wendy","Wendye","Wenona","Wenonah","Whitney","Wileen","Wilhelmina","Wilhelmine","Wilie","Willa","Willabella","Willamina","Willetta","Willette","Willi","Willie","Willow","Willy","Willyt","Wilma","Wilmette","Wilona","Wilone","Wilow","Windy","Wini","Winifred","Winna","Winnah","Winne","Winni","Winnie","Winnifred","Winny","Winona","Winonah","Wren","Wrennie","Wylma","Wynn","Wynne","Wynnie","Wynny","Xaviera","Xena","Xenia","Xylia","Xylina","Yalonda","Yasmeen","Yasmin","Yelena","Yetta","Yettie","Yetty","Yevette","Ynes","Ynez","Yoko","Yolanda","Yolande","Yolane","Yolanthe","Yoshi","Yoshiko","Yovonnda","Ysabel","Yvette","Yvonne","Zabrina","Zahara","Zandra","Zaneta","Zara","Zarah","Zaria","Zarla","Zea","Zelda","Zelma","Zena","Zenia","Zia","Zilvia","Zita","Zitella","Zoe","Zola","Zonda","Zondra","Zonnya","Zora","Zorah","Zorana","Zorina","Zorine","Zsazsa","Zulema","Zuzana"],d=["Ackbar","Adi Gallia","Anakin Skywalker","Arvel Crynyd","Ayla Secura","Bail Prestor Organa","Barriss Offee","Ben Quadinaros","Beru Whitesun lars","Bib Fortuna","Biggs Darklighter","Boba Fett","Bossk","C-3PO","Chewbacca","Cliegg Lars","Cordé","Darth Maul","Darth Vader","Dexter Jettster","Dooku","Dormé","Dud Bolt","Eeth Koth","Finis Valorum","Gasgano","Greedo","Gregar Typho","Grievous","Han Solo","IG-88","Jabba Desilijic Tiure","Jango Fett","Jar Jar Binks","Jek Tono Porkins","Jocasta Nu","Ki-Adi-Mundi","Kit Fisto","Lama Su","Lando Calrissian","Leia Organa","Lobot","Luke Skywalker","Luminara Unduli","Mace Windu","Mas Amedda","Mon Mothma","Nien Nunb","Nute Gunray","Obi-Wan Kenobi","Owen Lars","Padmé Amidala","Palpatine","Plo Koon","Poggle the Lesser","Quarsh Panaka","Qui-Gon Jinn","R2-D2","R4-P17","R5-D4","Ratts Tyerel","Raymus Antilles","Ric Olié","Roos Tarpals","Rugor Nass","Saesee Tiin","San Hill","Sebulba","Shaak Ti","Shmi Skywalker","Sly Moore","Tarfful","Taun We","Tion Medon","Wat Tambor","Watto","Wedge Antilles","Wicket Systri Warrick","Wilhuff Tarkin","Yarael Poof","Yoda","Zam Wesell"];class y{static generate(a={}){let e=a.min||1,i=a.max||999;if(a.length){const n=Math.pow(10,a.length);return e=n/10,i=n-1,[`${Math.floor(Math.random()*(i-e))+e}`]}return[`${Math.floor(Math.random()*(i-e))+e}`]}}export{y as NumberDictionary,n as adjectives,l as animals,r as colors,t as countries,o as languages,s as names,d as starWars,i as uniqueNamesGenerator}; -//# sourceMappingURL=index.m.js.map diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.modern.js b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.modern.js deleted file mode 100644 index f10585b0c8..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.modern.js +++ /dev/null @@ -1,2 +0,0 @@ -function a(){return(a=Object.assign||function(a){for(var e=1;ethis.dictionaries.length)throw new Error(`The length cannot be bigger than the number of dictionaries.\nLength provided: ${this.length}. Number of dictionaries provided: ${this.dictionaries.length}`);return this.dictionaries.slice(0,this.length).reduce((a,e)=>{let i=e[Math.floor((this.seed?(n=this.seed,(a=>{a=1831565813+(a|=0)|0;let e=Math.imul(a^a>>>15,1|a);return e=e+Math.imul(e^e>>>7,61|e)^e,((e^e>>>14)>>>0)/4294967296})(n)):Math.random())*e.length)]||"";var n;if("lowerCase"===this.style)i=i.toLowerCase();else if("capital"===this.style){const[a,...e]=i.split("");i=a.toUpperCase()+e.join("")}else"upperCase"===this.style&&(i=i.toUpperCase());return a?`${a}${this.separator}${i}`:`${i}`},"")}}const i={separator:"_",dictionaries:[]},n=n=>{const l=[...n&&n.dictionaries||i.dictionaries],r=a({},i,n,{length:n&&n.length||l.length,dictionaries:l});if(!n||!n.dictionaries||!n.dictionaries.length)throw new Error('A "dictionaries" array must be provided. This is a breaking change introduced starting from Unique Name Generator v4. Read more about the breaking change here: https://github.com/andreasonny83/unique-names-generator#migration-guide');return new e(r).generate()};var l=["able","above","absent","absolute","abstract","abundant","academic","acceptable","accepted","accessible","accurate","accused","active","actual","acute","added","additional","adequate","adjacent","administrative","adorable","advanced","adverse","advisory","aesthetic","afraid","aggregate","aggressive","agreeable","agreed","agricultural","alert","alive","alleged","allied","alone","alright","alternative","amateur","amazing","ambitious","amused","ancient","angry","annoyed","annual","anonymous","anxious","appalling","apparent","applicable","appropriate","arbitrary","architectural","armed","arrogant","artificial","artistic","ashamed","asleep","assistant","associated","atomic","attractive","automatic","autonomous","available","average","awake","aware","awful","awkward","back","bad","balanced","bare","basic","beautiful","beneficial","better","bewildered","big","binding","biological","bitter","bizarre","blank","blind","blonde","bloody","blushing","boiling","bold","bored","boring","bottom","brainy","brave","breakable","breezy","brief","bright","brilliant","broad","broken","bumpy","burning","busy","calm","capable","capitalist","careful","casual","causal","cautious","central","certain","changing","characteristic","charming","cheap","cheerful","chemical","chief","chilly","chosen","christian","chronic","chubby","circular","civic","civil","civilian","classic","classical","clean","clear","clever","clinical","close","closed","cloudy","clumsy","coastal","cognitive","coherent","cold","collective","colonial","colorful","colossal","coloured","colourful","combative","combined","comfortable","coming","commercial","common","communist","compact","comparable","comparative","compatible","competent","competitive","complete","complex","complicated","comprehensive","compulsory","conceptual","concerned","concrete","condemned","confident","confidential","confused","conscious","conservation","conservative","considerable","consistent","constant","constitutional","contemporary","content","continental","continued","continuing","continuous","controlled","controversial","convenient","conventional","convinced","convincing","cooing","cool","cooperative","corporate","correct","corresponding","costly","courageous","crazy","creative","creepy","criminal","critical","crooked","crowded","crucial","crude","cruel","cuddly","cultural","curious","curly","current","curved","cute","daily","damaged","damp","dangerous","dark","dead","deaf","deafening","dear","decent","decisive","deep","defeated","defensive","defiant","definite","deliberate","delicate","delicious","delighted","delightful","democratic","dependent","depressed","desirable","desperate","detailed","determined","developed","developing","devoted","different","difficult","digital","diplomatic","direct","dirty","disabled","disappointed","disastrous","disciplinary","disgusted","distant","distinct","distinctive","distinguished","disturbed","disturbing","diverse","divine","dizzy","domestic","dominant","double","doubtful","drab","dramatic","dreadful","driving","drunk","dry","dual","due","dull","dusty","dutch","dying","dynamic","eager","early","eastern","easy","economic","educational","eerie","effective","efficient","elaborate","elated","elderly","eldest","electoral","electric","electrical","electronic","elegant","eligible","embarrassed","embarrassing","emotional","empirical","empty","enchanting","encouraging","endless","energetic","enormous","enthusiastic","entire","entitled","envious","environmental","equal","equivalent","essential","established","estimated","ethical","ethnic","eventual","everyday","evident","evil","evolutionary","exact","excellent","exceptional","excess","excessive","excited","exciting","exclusive","existing","exotic","expected","expensive","experienced","experimental","explicit","extended","extensive","external","extra","extraordinary","extreme","exuberant","faint","fair","faithful","familiar","famous","fancy","fantastic","far","fascinating","fashionable","fast","fat","fatal","favourable","favourite","federal","fellow","female","feminist","few","fierce","filthy","final","financial","fine","firm","fiscal","fit","fixed","flaky","flat","flexible","fluffy","fluttering","flying","following","fond","foolish","foreign","formal","formidable","forthcoming","fortunate","forward","fragile","frail","frantic","free","frequent","fresh","friendly","frightened","front","frozen","full","fun","functional","fundamental","funny","furious","future","fuzzy","gastric","gay","general","generous","genetic","gentle","genuine","geographical","giant","gigantic","given","glad","glamorous","gleaming","global","glorious","golden","good","gorgeous","gothic","governing","graceful","gradual","grand","grateful","greasy","great","grieving","grim","gross","grotesque","growing","grubby","grumpy","guilty","handicapped","handsome","happy","hard","harsh","head","healthy","heavy","helpful","helpless","hidden","high","hilarious","hissing","historic","historical","hollow","holy","homeless","homely","hon","honest","horizontal","horrible","hostile","hot","huge","human","hungry","hurt","hushed","husky","icy","ideal","identical","ideological","ill","illegal","imaginative","immediate","immense","imperial","implicit","important","impossible","impressed","impressive","improved","inadequate","inappropriate","inc","inclined","increased","increasing","incredible","independent","indirect","individual","industrial","inevitable","influential","informal","inherent","initial","injured","inland","inner","innocent","innovative","inquisitive","instant","institutional","insufficient","intact","integral","integrated","intellectual","intelligent","intense","intensive","interested","interesting","interim","interior","intermediate","internal","international","intimate","invisible","involved","irrelevant","isolated","itchy","jealous","jittery","joint","jolly","joyous","judicial","juicy","junior","just","keen","key","kind","known","labour","large","late","latin","lazy","leading","left","legal","legislative","legitimate","lengthy","lesser","level","lexical","liable","liberal","light","like","likely","limited","linear","linguistic","liquid","literary","little","live","lively","living","local","logical","lonely","long","loose","lost","loud","lovely","low","loyal","ltd","lucky","mad","magic","magnetic","magnificent","main","major","male","mammoth","managerial","managing","manual","many","marginal","marine","marked","married","marvellous","marxist","mass","massive","mathematical","mature","maximum","mean","meaningful","mechanical","medical","medieval","melodic","melted","mental","mere","metropolitan","mid","middle","mighty","mild","military","miniature","minimal","minimum","ministerial","minor","miserable","misleading","missing","misty","mixed","moaning","mobile","moderate","modern","modest","molecular","monetary","monthly","moral","motionless","muddy","multiple","mushy","musical","mute","mutual","mysterious","naked","narrow","nasty","national","native","natural","naughty","naval","near","nearby","neat","necessary","negative","neighbouring","nervous","net","neutral","new","nice","noble","noisy","normal","northern","nosy","notable","novel","nuclear","numerous","nursing","nutritious","nutty","obedient","objective","obliged","obnoxious","obvious","occasional","occupational","odd","official","ok","okay","old","olympic","only","open","operational","opposite","optimistic","oral","ordinary","organic","organisational","original","orthodox","other","outdoor","outer","outrageous","outside","outstanding","overall","overseas","overwhelming","painful","pale","panicky","parallel","parental","parliamentary","partial","particular","passing","passive","past","patient","payable","peaceful","peculiar","perfect","permanent","persistent","personal","petite","philosophical","physical","plain","planned","plastic","pleasant","pleased","poised","polite","political","poor","popular","positive","possible","potential","powerful","practical","precious","precise","preferred","pregnant","preliminary","premier","prepared","present","presidential","pretty","previous","prickly","primary","prime","primitive","principal","printed","prior","private","probable","productive","professional","profitable","profound","progressive","prominent","promising","proper","proposed","prospective","protective","protestant","proud","provincial","psychiatric","psychological","public","puny","pure","purring","puzzled","quaint","qualified","quarrelsome","querulous","quick","quickest","quiet","quintessential","quixotic","racial","radical","rainy","random","rapid","rare","raspy","rational","ratty","raw","ready","real","realistic","rear","reasonable","recent","reduced","redundant","regional","registered","regular","regulatory","related","relative","relaxed","relevant","reliable","relieved","religious","reluctant","remaining","remarkable","remote","renewed","representative","repulsive","required","resident","residential","resonant","respectable","respective","responsible","resulting","retail","retired","revolutionary","rich","ridiculous","right","rigid","ripe","rising","rival","roasted","robust","rolling","romantic","rotten","rough","round","royal","rubber","rude","ruling","running","rural","sacred","sad","safe","salty","satisfactory","satisfied","scared","scary","scattered","scientific","scornful","scrawny","screeching","secondary","secret","secure","select","selected","selective","selfish","semantic","senior","sensible","sensitive","separate","serious","severe","sexual","shaggy","shaky","shallow","shared","sharp","sheer","shiny","shivering","shocked","short","shrill","shy","sick","significant","silent","silky","silly","similar","simple","single","skilled","skinny","sleepy","slight","slim","slimy","slippery","slow","small","smart","smiling","smoggy","smooth","social","socialist","soft","solar","sole","solid","sophisticated","sore","sorry","sound","sour","southern","soviet","spare","sparkling","spatial","special","specific","specified","spectacular","spicy","spiritual","splendid","spontaneous","sporting","spotless","spotty","square","squealing","stable","stale","standard","static","statistical","statutory","steady","steep","sticky","stiff","still","stingy","stormy","straight","straightforward","strange","strategic","strict","striking","striped","strong","structural","stuck","stupid","subjective","subsequent","substantial","subtle","successful","successive","sudden","sufficient","suitable","sunny","super","superb","superior","supporting","supposed","supreme","sure","surprised","surprising","surrounding","surviving","suspicious","sweet","swift","symbolic","sympathetic","systematic","tall","tame","tart","tasteless","tasty","technical","technological","teenage","temporary","tender","tense","terrible","territorial","testy","then","theoretical","thick","thin","thirsty","thorough","thoughtful","thoughtless","thundering","tight","tiny","tired","top","tory","total","tough","toxic","traditional","tragic","tremendous","tricky","tropical","troubled","typical","ugliest","ugly","ultimate","unable","unacceptable","unaware","uncertain","unchanged","uncomfortable","unconscious","underground","underlying","unemployed","uneven","unexpected","unfair","unfortunate","unhappy","uniform","uninterested","unique","united","universal","unknown","unlikely","unnecessary","unpleasant","unsightly","unusual","unwilling","upper","upset","uptight","urban","urgent","used","useful","useless","usual","vague","valid","valuable","variable","varied","various","varying","vast","verbal","vertical","very","vicarious","vicious","victorious","violent","visible","visiting","visual","vital","vitreous","vivacious","vivid","vocal","vocational","voiceless","voluminous","voluntary","vulnerable","wandering","warm","wasteful","watery","weak","wealthy","weary","wee","weekly","weird","welcome","well","western","wet","whispering","whole","wicked","wide","widespread","wild","wilful","willing","willowy","wily","wise","wispy","wittering","witty","wonderful","wooden","working","worldwide","worried","worrying","worthwhile","worthy","written","wrong","xenacious","xenial","xenogeneic","xenophobic","xeric","xerothermic","yabbering","yammering","yappiest","yappy","yawning","yearling","yearning","yeasty","yelling","yelping","yielding","yodelling","young","youngest","youthful","ytterbic","yucky","yummy","zany","zealous","zeroth","zestful","zesty","zippy","zonal","zoophagous","zygomorphic","zygotic"],r=["aardvark","aardwolf","albatross","alligator","alpaca","amphibian","anaconda","angelfish","anglerfish","ant","anteater","antelope","antlion","ape","aphid","armadillo","asp","baboon","badger","bandicoot","barnacle","barracuda","basilisk","bass","bat","bear","beaver","bedbug","bee","beetle","bird","bison","blackbird","boa","boar","bobcat","bobolink","bonobo","booby","bovid","bug","butterfly","buzzard","camel","canid","canidae","capybara","cardinal","caribou","carp","cat","caterpillar","catfish","catshark","cattle","centipede","cephalopod","chameleon","cheetah","chickadee","chicken","chimpanzee","chinchilla","chipmunk","cicada","clam","clownfish","cobra","cockroach","cod","condor","constrictor","coral","cougar","cow","coyote","crab","crane","crawdad","crayfish","cricket","crocodile","crow","cuckoo","damselfly","deer","dingo","dinosaur","dog","dolphin","donkey","dormouse","dove","dragon","dragonfly","duck","eagle","earthworm","earwig","echidna","eel","egret","elephant","elk","emu","ermine","falcon","felidae","ferret","finch","firefly","fish","flamingo","flea","fly","flyingfish","fowl","fox","frog","galliform","gamefowl","gayal","gazelle","gecko","gerbil","gibbon","giraffe","goat","goldfish","goose","gopher","gorilla","grasshopper","grouse","guan","guanaco","guineafowl","gull","guppy","haddock","halibut","hamster","hare","harrier","hawk","hedgehog","heron","herring","hippopotamus","hookworm","hornet","horse","hoverfly","hummingbird","hyena","iguana","impala","jackal","jaguar","jay","jellyfish","junglefowl","kangaroo","kingfisher","kite","kiwi","koala","koi","krill","ladybug","lamprey","landfowl","lark","leech","lemming","lemur","leopard","leopon","limpet","lion","lizard","llama","lobster","locust","loon","louse","lungfish","lynx","macaw","mackerel","magpie","mammal","manatee","mandrill","marlin","marmoset","marmot","marsupial","marten","mastodon","meadowlark","meerkat","mink","minnow","mite","mockingbird","mole","mollusk","mongoose","monkey","moose","mosquito","moth","mouse","mule","muskox","narwhal","newt","nightingale","ocelot","octopus","opossum","orangutan","orca","ostrich","otter","owl","ox","panda","panther","parakeet","parrot","parrotfish","partridge","peacock","peafowl","pelican","penguin","perch","pheasant","pig","pigeon","pike","pinniped","piranha","planarian","platypus","pony","porcupine","porpoise","possum","prawn","primate","ptarmigan","puffin","puma","python","quail","quelea","quokka","rabbit","raccoon","rat","rattlesnake","raven","reindeer","reptile","rhinoceros","roadrunner","rodent","rook","rooster","roundworm","sailfish","salamander","salmon","sawfish","scallop","scorpion","seahorse","shark","sheep","shrew","shrimp","silkworm","silverfish","skink","skunk","sloth","slug","smelt","snail","snake","snipe","sole","sparrow","spider","spoonbill","squid","squirrel","starfish","stingray","stoat","stork","sturgeon","swallow","swan","swift","swordfish","swordtail","tahr","takin","tapir","tarantula","tarsier","termite","tern","thrush","tick","tiger","tiglon","toad","tortoise","toucan","trout","tuna","turkey","turtle","tyrannosaurus","unicorn","urial","vicuna","viper","vole","vulture","wallaby","walrus","warbler","wasp","weasel","whale","whippet","whitefish","wildcat","wildebeest","wildfowl","wolf","wolverine","wombat","woodpecker","worm","wren","xerinae","yak","zebra"],t=["amaranth","amber","amethyst","apricot","aqua","aquamarine","azure","beige","black","blue","blush","bronze","brown","chocolate","coffee","copper","coral","crimson","cyan","emerald","fuchsia","gold","gray","green","harlequin","indigo","ivory","jade","lavender","lime","magenta","maroon","moccasin","olive","orange","peach","pink","plum","purple","red","rose","salmon","sapphire","scarlet","silver","tan","teal","tomato","turquoise","violet","white","yellow"],o=["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","Andorra","Angola","Anguilla","Antarctica","Antigua & Barbuda","Argentina","Armenia","Aruba","Ascension Island","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia & Herzegovina","Botswana","Brazil","British Indian Ocean Territory","British Virgin Islands","Brunei","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Canary Islands","Cape Verde","Caribbean Netherlands","Cayman Islands","Central African Republic","Ceuta & Melilla","Chad","Chile","China","Christmas Island","Cocos Islands","Colombia","Comoros","Congo","Cook Islands","Costa Rica","Côte d'Ivoire","Croatia","Cuba","Curaçao","Cyprus","Czechia","Denmark","Diego Garcia","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Eurozone","Falkland Islands","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Honduras","Hong Kong SAR China","Hungary","Iceland","India","Indonesia","Iran","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Kosovo","Kuwait","Kyrgyzstan","Laos","Latvia","Lebanon","Lesotho","Liberia","Libya","Liechtenstein","Lithuania","Luxembourg","Macau SAR China","Macedonia","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia","Moldova","Monaco","Mongolia","Montenegro","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","North Korea","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territories","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn Islands","Poland","Portugal","Puerto Rico","Qatar","Réunion","Romania","Russia","Rwanda","Samoa","San Marino","São Tomé & Príncipe","Saudi Arabia","Senegal","Serbia","Seychelles","Sierra Leone","Singapore","Sint Maarten","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia & South Sandwich Islands","South Korea","South Sudan","Spain","Sri Lanka","St. Barthélemy","St. Helena","St. Kitts & Nevis","St. Lucia","St. Martin","St. Pierre & Miquelon","St. Vincent & Grenadines","Sudan","Suriname","Svalbard & Jan Mayen","Swaziland","Sweden","Switzerland","Syria","Taiwan","Tajikistan","Tanzania","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad & Tobago","Tristan da Cunha","Tunisia","Turkey","Turkmenistan","Turks & Caicos Islands","Tuvalu","U.S. Outlying Islands","U.S. Virgin Islands","Uganda","Ukraine","United Arab Emirates","United Kingdom","United Nations","United States","Uruguay","Uzbekistan","Vanuatu","Vatican City","Venezuela","Vietnam","Wallis & Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"],s=["Akan","Amharic","Arabic","Assamese","Awadhi","Azerbaijani","Balochi","Belarusian","Bengali","Bhojpuri","Burmese","Cebuano","Chewa","Chhattisgarhi","Chittagonian","Czech","Deccan","Dhundhari","Dutch","English","French","Fula","Gan","German","Greek","Gujarati","Hakka","Haryanvi","Hausa","Hiligaynon","Hindi","Hmong","Hungarian","Igbo","Ilocano","Italian","Japanese","Javanese","Jin","Kannada","Kazakh","Khmer","Kinyarwanda","Kirundi","Konkani","Korean","Kurdish","Madurese","Magahi","Maithili","Malagasy","Malay","Malayalam","Mandarin","Marathi","Marwari","Min","Mossi","Nepali","Odia","Oromo","Pashto","Persian","Polish","Portuguese","Punjabi","Quechua","Romanian","Russian","Saraiki","Shona","Sindhi","Sinhala","Somali","Spanish","Sundanese","Swedish","Sylheti","Tagalog","Tamil","Telugu","Thai","Turkish","Turkmen","Ukrainian","Urdu","Uyghur","Uzbek","Vietnamese","Wu","Xhosa","Xiang","Yoruba","Yue","Zhuang","Zulu"],d=["Aaren","Aarika","Abagael","Abagail","Abbe","Abbey","Abbi","Abbie","Abby","Abbye","Abigael","Abigail","Abigale","Abra","Ada","Adah","Adaline","Adan","Adara","Adda","Addi","Addia","Addie","Addy","Adel","Adela","Adelaida","Adelaide","Adele","Adelheid","Adelice","Adelina","Adelind","Adeline","Adella","Adelle","Adena","Adey","Adi","Adiana","Adina","Adora","Adore","Adoree","Adorne","Adrea","Adria","Adriaens","Adrian","Adriana","Adriane","Adrianna","Adrianne","Adriena","Adrienne","Aeriel","Aeriela","Aeriell","Afton","Ag","Agace","Agata","Agatha","Agathe","Aggi","Aggie","Aggy","Agna","Agnella","Agnes","Agnese","Agnesse","Agneta","Agnola","Agretha","Aida","Aidan","Aigneis","Aila","Aile","Ailee","Aileen","Ailene","Ailey","Aili","Ailina","Ailis","Ailsun","Ailyn","Aime","Aimee","Aimil","Aindrea","Ainslee","Ainsley","Ainslie","Ajay","Alaine","Alameda","Alana","Alanah","Alane","Alanna","Alayne","Alberta","Albertina","Albertine","Albina","Alecia","Aleda","Aleece","Aleen","Alejandra","Alejandrina","Alena","Alene","Alessandra","Aleta","Alethea","Alex","Alexa","Alexandra","Alexandrina","Alexi","Alexia","Alexina","Alexine","Alexis","Alfi","Alfie","Alfreda","Alfy","Ali","Alia","Alica","Alice","Alicea","Alicia","Alida","Alidia","Alie","Alika","Alikee","Alina","Aline","Alis","Alisa","Alisha","Alison","Alissa","Alisun","Alix","Aliza","Alla","Alleen","Allegra","Allene","Alli","Allianora","Allie","Allina","Allis","Allison","Allissa","Allix","Allsun","Allx","Ally","Allyce","Allyn","Allys","Allyson","Alma","Almeda","Almeria","Almeta","Almira","Almire","Aloise","Aloisia","Aloysia","Alta","Althea","Alvera","Alverta","Alvina","Alvinia","Alvira","Alyce","Alyda","Alys","Alysa","Alyse","Alysia","Alyson","Alyss","Alyssa","Amabel","Amabelle","Amalea","Amalee","Amaleta","Amalia","Amalie","Amalita","Amalle","Amanda","Amandi","Amandie","Amandy","Amara","Amargo","Amata","Amber","Amberly","Ambur","Ame","Amelia","Amelie","Amelina","Ameline","Amelita","Ami","Amie","Amii","Amil","Amitie","Amity","Ammamaria","Amy","Amye","Ana","Anabal","Anabel","Anabella","Anabelle","Analiese","Analise","Anallese","Anallise","Anastasia","Anastasie","Anastassia","Anatola","Andee","Andeee","Anderea","Andi","Andie","Andra","Andrea","Andreana","Andree","Andrei","Andria","Andriana","Andriette","Andromache","Andy","Anestassia","Anet","Anett","Anetta","Anette","Ange","Angel","Angela","Angele","Angelia","Angelica","Angelika","Angelina","Angeline","Angelique","Angelita","Angelle","Angie","Angil","Angy","Ania","Anica","Anissa","Anita","Anitra","Anjanette","Anjela","Ann","Ann-marie","Anna","Anna-diana","Anna-diane","Anna-maria","Annabal","Annabel","Annabela","Annabell","Annabella","Annabelle","Annadiana","Annadiane","Annalee","Annaliese","Annalise","Annamaria","Annamarie","Anne","Anne-corinne","Anne-marie","Annecorinne","Anneliese","Annelise","Annemarie","Annetta","Annette","Anni","Annice","Annie","Annis","Annissa","Annmaria","Annmarie","Annnora","Annora","Anny","Anselma","Ansley","Anstice","Anthe","Anthea","Anthia","Anthiathia","Antoinette","Antonella","Antonetta","Antonia","Antonie","Antonietta","Antonina","Anya","Appolonia","April","Aprilette","Ara","Arabel","Arabela","Arabele","Arabella","Arabelle","Arda","Ardath","Ardeen","Ardelia","Ardelis","Ardella","Ardelle","Arden","Ardene","Ardenia","Ardine","Ardis","Ardisj","Ardith","Ardra","Ardyce","Ardys","Ardyth","Aretha","Ariadne","Ariana","Aridatha","Ariel","Ariela","Ariella","Arielle","Arlana","Arlee","Arleen","Arlen","Arlena","Arlene","Arleta","Arlette","Arleyne","Arlie","Arliene","Arlina","Arlinda","Arline","Arluene","Arly","Arlyn","Arlyne","Aryn","Ashely","Ashia","Ashien","Ashil","Ashla","Ashlan","Ashlee","Ashleigh","Ashlen","Ashley","Ashli","Ashlie","Ashly","Asia","Astra","Astrid","Astrix","Atalanta","Athena","Athene","Atlanta","Atlante","Auberta","Aubine","Aubree","Aubrette","Aubrey","Aubrie","Aubry","Audi","Audie","Audra","Audre","Audrey","Audrie","Audry","Audrye","Audy","Augusta","Auguste","Augustina","Augustine","Aundrea","Aura","Aurea","Aurel","Aurelea","Aurelia","Aurelie","Auria","Aurie","Aurilia","Aurlie","Auroora","Aurora","Aurore","Austin","Austina","Austine","Ava","Aveline","Averil","Averyl","Avie","Avis","Aviva","Avivah","Avril","Avrit","Ayn","Bab","Babara","Babb","Babbette","Babbie","Babette","Babita","Babs","Bambi","Bambie","Bamby","Barb","Barbabra","Barbara","Barbara-anne","Barbaraanne","Barbe","Barbee","Barbette","Barbey","Barbi","Barbie","Barbra","Barby","Bari","Barrie","Barry","Basia","Bathsheba","Batsheva","Bea","Beatrice","Beatrisa","Beatrix","Beatriz","Bebe","Becca","Becka","Becki","Beckie","Becky","Bee","Beilul","Beitris","Bekki","Bel","Belia","Belicia","Belinda","Belita","Bell","Bella","Bellanca","Belle","Bellina","Belva","Belvia","Bendite","Benedetta","Benedicta","Benedikta","Benetta","Benita","Benni","Bennie","Benny","Benoite","Berenice","Beret","Berget","Berna","Bernadene","Bernadette","Bernadina","Bernadine","Bernardina","Bernardine","Bernelle","Bernete","Bernetta","Bernette","Berni","Bernice","Bernie","Bernita","Berny","Berri","Berrie","Berry","Bert","Berta","Berte","Bertha","Berthe","Berti","Bertie","Bertina","Bertine","Berty","Beryl","Beryle","Bess","Bessie","Bessy","Beth","Bethanne","Bethany","Bethena","Bethina","Betsey","Betsy","Betta","Bette","Bette-ann","Betteann","Betteanne","Betti","Bettina","Bettine","Betty","Bettye","Beulah","Bev","Beverie","Beverlee","Beverley","Beverlie","Beverly","Bevvy","Bianca","Bianka","Bibbie","Bibby","Bibbye","Bibi","Biddie","Biddy","Bidget","Bili","Bill","Billi","Billie","Billy","Billye","Binni","Binnie","Binny","Bird","Birdie","Birgit","Birgitta","Blair","Blaire","Blake","Blakelee","Blakeley","Blanca","Blanch","Blancha","Blanche","Blinni","Blinnie","Blinny","Bliss","Blisse","Blithe","Blondell","Blondelle","Blondie","Blondy","Blythe","Bobbe","Bobbee","Bobbette","Bobbi","Bobbie","Bobby","Bobbye","Bobette","Bobina","Bobine","Bobinette","Bonita","Bonnee","Bonni","Bonnibelle","Bonnie","Bonny","Brana","Brandais","Brande","Brandea","Brandi","Brandice","Brandie","Brandise","Brandy","Breanne","Brear","Bree","Breena","Bren","Brena","Brenda","Brenn","Brenna","Brett","Bria","Briana","Brianna","Brianne","Bride","Bridget","Bridgette","Bridie","Brier","Brietta","Brigid","Brigida","Brigit","Brigitta","Brigitte","Brina","Briney","Brinn","Brinna","Briny","Brit","Brita","Britney","Britni","Britt","Britta","Brittan","Brittaney","Brittani","Brittany","Britte","Britteny","Brittne","Brittney","Brittni","Brook","Brooke","Brooks","Brunhilda","Brunhilde","Bryana","Bryn","Bryna","Brynn","Brynna","Brynne","Buffy","Bunni","Bunnie","Bunny","Cacilia","Cacilie","Cahra","Cairistiona","Caitlin","Caitrin","Cal","Calida","Calla","Calley","Calli","Callida","Callie","Cally","Calypso","Cam","Camala","Camel","Camella","Camellia","Cami","Camila","Camile","Camilla","Camille","Cammi","Cammie","Cammy","Candace","Candi","Candice","Candida","Candide","Candie","Candis","Candra","Candy","Caprice","Cara","Caralie","Caren","Carena","Caresa","Caressa","Caresse","Carey","Cari","Caria","Carie","Caril","Carilyn","Carin","Carina","Carine","Cariotta","Carissa","Carita","Caritta","Carla","Carlee","Carleen","Carlen","Carlene","Carley","Carlie","Carlin","Carlina","Carline","Carlita","Carlota","Carlotta","Carly","Carlye","Carlyn","Carlynn","Carlynne","Carma","Carmel","Carmela","Carmelia","Carmelina","Carmelita","Carmella","Carmelle","Carmen","Carmencita","Carmina","Carmine","Carmita","Carmon","Caro","Carol","Carol-jean","Carola","Carolan","Carolann","Carole","Carolee","Carolin","Carolina","Caroline","Caroljean","Carolyn","Carolyne","Carolynn","Caron","Carree","Carri","Carrie","Carrissa","Carroll","Carry","Cary","Caryl","Caryn","Casandra","Casey","Casi","Casie","Cass","Cassandra","Cassandre","Cassandry","Cassaundra","Cassey","Cassi","Cassie","Cassondra","Cassy","Catarina","Cate","Caterina","Catha","Catharina","Catharine","Cathe","Cathee","Catherin","Catherina","Catherine","Cathi","Cathie","Cathleen","Cathlene","Cathrin","Cathrine","Cathryn","Cathy","Cathyleen","Cati","Catie","Catina","Catlaina","Catlee","Catlin","Catrina","Catriona","Caty","Caye","Cayla","Cecelia","Cecil","Cecile","Ceciley","Cecilia","Cecilla","Cecily","Ceil","Cele","Celene","Celesta","Celeste","Celestia","Celestina","Celestine","Celestyn","Celestyna","Celia","Celie","Celina","Celinda","Celine","Celinka","Celisse","Celka","Celle","Cesya","Chad","Chanda","Chandal","Chandra","Channa","Chantal","Chantalle","Charil","Charin","Charis","Charissa","Charisse","Charita","Charity","Charla","Charlean","Charleen","Charlena","Charlene","Charline","Charlot","Charlotta","Charlotte","Charmain","Charmaine","Charmane","Charmian","Charmine","Charmion","Charo","Charyl","Chastity","Chelsae","Chelsea","Chelsey","Chelsie","Chelsy","Cher","Chere","Cherey","Cheri","Cherianne","Cherice","Cherida","Cherie","Cherilyn","Cherilynn","Cherin","Cherise","Cherish","Cherlyn","Cherri","Cherrita","Cherry","Chery","Cherye","Cheryl","Cheslie","Chiarra","Chickie","Chicky","Chiquia","Chiquita","Chlo","Chloe","Chloette","Chloris","Chris","Chrissie","Chrissy","Christa","Christabel","Christabella","Christal","Christalle","Christan","Christean","Christel","Christen","Christi","Christian","Christiana","Christiane","Christie","Christin","Christina","Christine","Christy","Christye","Christyna","Chrysa","Chrysler","Chrystal","Chryste","Chrystel","Cicely","Cicily","Ciel","Cilka","Cinda","Cindee","Cindelyn","Cinderella","Cindi","Cindie","Cindra","Cindy","Cinnamon","Cissiee","Cissy","Clair","Claire","Clara","Clarabelle","Clare","Claresta","Clareta","Claretta","Clarette","Clarey","Clari","Claribel","Clarice","Clarie","Clarinda","Clarine","Clarissa","Clarisse","Clarita","Clary","Claude","Claudelle","Claudetta","Claudette","Claudia","Claudie","Claudina","Claudine","Clea","Clem","Clemence","Clementia","Clementina","Clementine","Clemmie","Clemmy","Cleo","Cleopatra","Clerissa","Clio","Clo","Cloe","Cloris","Clotilda","Clovis","Codee","Codi","Codie","Cody","Coleen","Colene","Coletta","Colette","Colleen","Collen","Collete","Collette","Collie","Colline","Colly","Con","Concettina","Conchita","Concordia","Conni","Connie","Conny","Consolata","Constance","Constancia","Constancy","Constanta","Constantia","Constantina","Constantine","Consuela","Consuelo","Cookie","Cora","Corabel","Corabella","Corabelle","Coral","Coralie","Coraline","Coralyn","Cordelia","Cordelie","Cordey","Cordi","Cordie","Cordula","Cordy","Coreen","Corella","Corenda","Corene","Coretta","Corette","Corey","Cori","Corie","Corilla","Corina","Corine","Corinna","Corinne","Coriss","Corissa","Corliss","Corly","Cornela","Cornelia","Cornelle","Cornie","Corny","Correna","Correy","Corri","Corrianne","Corrie","Corrina","Corrine","Corrinne","Corry","Cortney","Cory","Cosetta","Cosette","Costanza","Courtenay","Courtnay","Courtney","Crin","Cris","Crissie","Crissy","Crista","Cristabel","Cristal","Cristen","Cristi","Cristie","Cristin","Cristina","Cristine","Cristionna","Cristy","Crysta","Crystal","Crystie","Cthrine","Cyb","Cybil","Cybill","Cymbre","Cynde","Cyndi","Cyndia","Cyndie","Cyndy","Cynthea","Cynthia","Cynthie","Cynthy","Dacey","Dacia","Dacie","Dacy","Dael","Daffi","Daffie","Daffy","Dagmar","Dahlia","Daile","Daisey","Daisi","Daisie","Daisy","Dale","Dalenna","Dalia","Dalila","Dallas","Daloris","Damara","Damaris","Damita","Dana","Danell","Danella","Danette","Dani","Dania","Danica","Danice","Daniela","Daniele","Daniella","Danielle","Danika","Danila","Danit","Danita","Danna","Danni","Dannie","Danny","Dannye","Danya","Danyelle","Danyette","Daphene","Daphna","Daphne","Dara","Darb","Darbie","Darby","Darcee","Darcey","Darci","Darcie","Darcy","Darda","Dareen","Darell","Darelle","Dari","Daria","Darice","Darla","Darleen","Darlene","Darline","Darlleen","Daron","Darrelle","Darryl","Darsey","Darsie","Darya","Daryl","Daryn","Dasha","Dasi","Dasie","Dasya","Datha","Daune","Daveen","Daveta","Davida","Davina","Davine","Davita","Dawn","Dawna","Dayle","Dayna","Ddene","De","Deana","Deane","Deanna","Deanne","Deb","Debbi","Debbie","Debby","Debee","Debera","Debi","Debor","Debora","Deborah","Debra","Dede","Dedie","Dedra","Dee","Deeann","Deeanne","Deedee","Deena","Deerdre","Deeyn","Dehlia","Deidre","Deina","Deirdre","Del","Dela","Delcina","Delcine","Delia","Delila","Delilah","Delinda","Dell","Della","Delly","Delora","Delores","Deloria","Deloris","Delphine","Delphinia","Demeter","Demetra","Demetria","Demetris","Dena","Deni","Denice","Denise","Denna","Denni","Dennie","Denny","Deny","Denys","Denyse","Deonne","Desdemona","Desirae","Desiree","Desiri","Deva","Devan","Devi","Devin","Devina","Devinne","Devon","Devondra","Devonna","Devonne","Devora","Di","Diahann","Dian","Diana","Diandra","Diane","Diane-marie","Dianemarie","Diann","Dianna","Dianne","Diannne","Didi","Dido","Diena","Dierdre","Dina","Dinah","Dinnie","Dinny","Dion","Dione","Dionis","Dionne","Dita","Dix","Dixie","Dniren","Dode","Dodi","Dodie","Dody","Doe","Doll","Dolley","Dolli","Dollie","Dolly","Dolores","Dolorita","Doloritas","Domeniga","Dominga","Domini","Dominica","Dominique","Dona","Donella","Donelle","Donetta","Donia","Donica","Donielle","Donna","Donnamarie","Donni","Donnie","Donny","Dora","Doralia","Doralin","Doralyn","Doralynn","Doralynne","Dore","Doreen","Dorelia","Dorella","Dorelle","Dorena","Dorene","Doretta","Dorette","Dorey","Dori","Doria","Dorian","Dorice","Dorie","Dorine","Doris","Dorisa","Dorise","Dorita","Doro","Dorolice","Dorolisa","Dorotea","Doroteya","Dorothea","Dorothee","Dorothy","Dorree","Dorri","Dorrie","Dorris","Dorry","Dorthea","Dorthy","Dory","Dosi","Dot","Doti","Dotti","Dottie","Dotty","Dre","Dreddy","Dredi","Drona","Dru","Druci","Drucie","Drucill","Drucy","Drusi","Drusie","Drusilla","Drusy","Dulce","Dulcea","Dulci","Dulcia","Dulciana","Dulcie","Dulcine","Dulcinea","Dulcy","Dulsea","Dusty","Dyan","Dyana","Dyane","Dyann","Dyanna","Dyanne","Dyna","Dynah","Eachelle","Eada","Eadie","Eadith","Ealasaid","Eartha","Easter","Eba","Ebba","Ebonee","Ebony","Eda","Eddi","Eddie","Eddy","Ede","Edee","Edeline","Eden","Edi","Edie","Edin","Edita","Edith","Editha","Edithe","Ediva","Edna","Edwina","Edy","Edyth","Edythe","Effie","Eileen","Eilis","Eimile","Eirena","Ekaterina","Elaina","Elaine","Elana","Elane","Elayne","Elberta","Elbertina","Elbertine","Eleanor","Eleanora","Eleanore","Electra","Eleen","Elena","Elene","Eleni","Elenore","Eleonora","Eleonore","Elfie","Elfreda","Elfrida","Elfrieda","Elga","Elianora","Elianore","Elicia","Elie","Elinor","Elinore","Elisa","Elisabet","Elisabeth","Elisabetta","Elise","Elisha","Elissa","Elita","Eliza","Elizabet","Elizabeth","Elka","Elke","Ella","Elladine","Elle","Ellen","Ellene","Ellette","Elli","Ellie","Ellissa","Elly","Ellyn","Ellynn","Elmira","Elna","Elnora","Elnore","Eloisa","Eloise","Elonore","Elora","Elsa","Elsbeth","Else","Elset","Elsey","Elsi","Elsie","Elsinore","Elspeth","Elsy","Elva","Elvera","Elvina","Elvira","Elwira","Elyn","Elyse","Elysee","Elysha","Elysia","Elyssa","Em","Ema","Emalee","Emalia","Emelda","Emelia","Emelina","Emeline","Emelita","Emelyne","Emera","Emilee","Emili","Emilia","Emilie","Emiline","Emily","Emlyn","Emlynn","Emlynne","Emma","Emmalee","Emmaline","Emmalyn","Emmalynn","Emmalynne","Emmeline","Emmey","Emmi","Emmie","Emmy","Emmye","Emogene","Emyle","Emylee","Engracia","Enid","Enrica","Enrichetta","Enrika","Enriqueta","Eolanda","Eolande","Eran","Erda","Erena","Erica","Ericha","Ericka","Erika","Erin","Erina","Erinn","Erinna","Erma","Ermengarde","Ermentrude","Ermina","Erminia","Erminie","Erna","Ernaline","Ernesta","Ernestine","Ertha","Eryn","Esma","Esmaria","Esme","Esmeralda","Essa","Essie","Essy","Esta","Estel","Estele","Estell","Estella","Estelle","Ester","Esther","Estrella","Estrellita","Ethel","Ethelda","Ethelin","Ethelind","Etheline","Ethelyn","Ethyl","Etta","Etti","Ettie","Etty","Eudora","Eugenia","Eugenie","Eugine","Eula","Eulalie","Eunice","Euphemia","Eustacia","Eva","Evaleen","Evangelia","Evangelin","Evangelina","Evangeline","Evania","Evanne","Eve","Eveleen","Evelina","Eveline","Evelyn","Evey","Evie","Evita","Evonne","Evvie","Evvy","Evy","Eyde","Eydie","Ezmeralda","Fae","Faina","Faith","Fallon","Fan","Fanchette","Fanchon","Fancie","Fancy","Fanechka","Fania","Fanni","Fannie","Fanny","Fanya","Fara","Farah","Farand","Farica","Farra","Farrah","Farrand","Faun","Faunie","Faustina","Faustine","Fawn","Fawne","Fawnia","Fay","Faydra","Faye","Fayette","Fayina","Fayre","Fayth","Faythe","Federica","Fedora","Felecia","Felicdad","Felice","Felicia","Felicity","Felicle","Felipa","Felisha","Felita","Feliza","Fenelia","Feodora","Ferdinanda","Ferdinande","Fern","Fernanda","Fernande","Fernandina","Ferne","Fey","Fiann","Fianna","Fidela","Fidelia","Fidelity","Fifi","Fifine","Filia","Filide","Filippa","Fina","Fiona","Fionna","Fionnula","Fiorenze","Fleur","Fleurette","Flo","Flor","Flora","Florance","Flore","Florella","Florence","Florencia","Florentia","Florenza","Florette","Flori","Floria","Florida","Florie","Florina","Florinda","Floris","Florri","Florrie","Florry","Flory","Flossi","Flossie","Flossy","Flss","Fran","Francene","Frances","Francesca","Francine","Francisca","Franciska","Francoise","Francyne","Frank","Frankie","Franky","Franni","Frannie","Franny","Frayda","Fred","Freda","Freddi","Freddie","Freddy","Fredelia","Frederica","Fredericka","Frederique","Fredi","Fredia","Fredra","Fredrika","Freida","Frieda","Friederike","Fulvia","Gabbey","Gabbi","Gabbie","Gabey","Gabi","Gabie","Gabriel","Gabriela","Gabriell","Gabriella","Gabrielle","Gabriellia","Gabrila","Gaby","Gae","Gael","Gail","Gale","Galina","Garland","Garnet","Garnette","Gates","Gavra","Gavrielle","Gay","Gaye","Gayel","Gayla","Gayle","Gayleen","Gaylene","Gaynor","Gelya","Gena","Gene","Geneva","Genevieve","Genevra","Genia","Genna","Genni","Gennie","Gennifer","Genny","Genovera","Genvieve","George","Georgeanna","Georgeanne","Georgena","Georgeta","Georgetta","Georgette","Georgia","Georgiana","Georgianna","Georgianne","Georgie","Georgina","Georgine","Geralda","Geraldine","Gerda","Gerhardine","Geri","Gerianna","Gerianne","Gerladina","Germain","Germaine","Germana","Gerri","Gerrie","Gerrilee","Gerry","Gert","Gerta","Gerti","Gertie","Gertrud","Gertruda","Gertrude","Gertrudis","Gerty","Giacinta","Giana","Gianina","Gianna","Gigi","Gilberta","Gilberte","Gilbertina","Gilbertine","Gilda","Gilemette","Gill","Gillan","Gilli","Gillian","Gillie","Gilligan","Gilly","Gina","Ginelle","Ginevra","Ginger","Ginni","Ginnie","Ginnifer","Ginny","Giorgia","Giovanna","Gipsy","Giralda","Gisela","Gisele","Gisella","Giselle","Giuditta","Giulia","Giulietta","Giustina","Gizela","Glad","Gladi","Gladys","Gleda","Glen","Glenda","Glenine","Glenn","Glenna","Glennie","Glennis","Glori","Gloria","Gloriana","Gloriane","Glory","Glyn","Glynda","Glynis","Glynnis","Gnni","Godiva","Golda","Goldarina","Goldi","Goldia","Goldie","Goldina","Goldy","Grace","Gracia","Gracie","Grata","Gratia","Gratiana","Gray","Grayce","Grazia","Greer","Greta","Gretal","Gretchen","Grete","Gretel","Grethel","Gretna","Gretta","Grier","Griselda","Grissel","Guendolen","Guenevere","Guenna","Guglielma","Gui","Guillema","Guillemette","Guinevere","Guinna","Gunilla","Gus","Gusella","Gussi","Gussie","Gussy","Gusta","Gusti","Gustie","Gusty","Gwen","Gwendolen","Gwendolin","Gwendolyn","Gweneth","Gwenette","Gwenneth","Gwenni","Gwennie","Gwenny","Gwenora","Gwenore","Gwyn","Gwyneth","Gwynne","Gypsy","Hadria","Hailee","Haily","Haleigh","Halette","Haley","Hali","Halie","Halimeda","Halley","Halli","Hallie","Hally","Hana","Hanna","Hannah","Hanni","Hannie","Hannis","Hanny","Happy","Harlene","Harley","Harli","Harlie","Harmonia","Harmonie","Harmony","Harri","Harrie","Harriet","Harriett","Harrietta","Harriette","Harriot","Harriott","Hatti","Hattie","Hatty","Hayley","Hazel","Heath","Heather","Heda","Hedda","Heddi","Heddie","Hedi","Hedvig","Hedvige","Hedwig","Hedwiga","Hedy","Heida","Heidi","Heidie","Helaina","Helaine","Helen","Helen-elizabeth","Helena","Helene","Helenka","Helga","Helge","Helli","Heloise","Helsa","Helyn","Hendrika","Henka","Henrie","Henrieta","Henrietta","Henriette","Henryetta","Hephzibah","Hermia","Hermina","Hermine","Herminia","Hermione","Herta","Hertha","Hester","Hesther","Hestia","Hetti","Hettie","Hetty","Hilary","Hilda","Hildagard","Hildagarde","Hilde","Hildegaard","Hildegarde","Hildy","Hillary","Hilliary","Hinda","Holli","Hollie","Holly","Holly-anne","Hollyanne","Honey","Honor","Honoria","Hope","Horatia","Hortense","Hortensia","Hulda","Hyacinth","Hyacintha","Hyacinthe","Hyacinthia","Hyacinthie","Hynda","Ianthe","Ibbie","Ibby","Ida","Idalia","Idalina","Idaline","Idell","Idelle","Idette","Ileana","Ileane","Ilene","Ilise","Ilka","Illa","Ilsa","Ilse","Ilysa","Ilyse","Ilyssa","Imelda","Imogen","Imogene","Imojean","Ina","Indira","Ines","Inesita","Inessa","Inez","Inga","Ingaberg","Ingaborg","Inge","Ingeberg","Ingeborg","Inger","Ingrid","Ingunna","Inna","Iolande","Iolanthe","Iona","Iormina","Ira","Irena","Irene","Irina","Iris","Irita","Irma","Isa","Isabel","Isabelita","Isabella","Isabelle","Isadora","Isahella","Iseabal","Isidora","Isis","Isobel","Issi","Issie","Issy","Ivett","Ivette","Ivie","Ivonne","Ivory","Ivy","Izabel","Jacenta","Jacinda","Jacinta","Jacintha","Jacinthe","Jackelyn","Jacki","Jackie","Jacklin","Jacklyn","Jackquelin","Jackqueline","Jacky","Jaclin","Jaclyn","Jacquelin","Jacqueline","Jacquelyn","Jacquelynn","Jacquenetta","Jacquenette","Jacquetta","Jacquette","Jacqui","Jacquie","Jacynth","Jada","Jade","Jaime","Jaimie","Jaine","Jami","Jamie","Jamima","Jammie","Jan","Jana","Janaya","Janaye","Jandy","Jane","Janean","Janeczka","Janeen","Janel","Janela","Janella","Janelle","Janene","Janenna","Janessa","Janet","Janeta","Janetta","Janette","Janeva","Janey","Jania","Janice","Janie","Janifer","Janina","Janine","Janis","Janith","Janka","Janna","Jannel","Jannelle","Janot","Jany","Jaquelin","Jaquelyn","Jaquenetta","Jaquenette","Jaquith","Jasmin","Jasmina","Jasmine","Jayme","Jaymee","Jayne","Jaynell","Jazmin","Jean","Jeana","Jeane","Jeanelle","Jeanette","Jeanie","Jeanine","Jeanna","Jeanne","Jeannette","Jeannie","Jeannine","Jehanna","Jelene","Jemie","Jemima","Jemimah","Jemmie","Jemmy","Jen","Jena","Jenda","Jenelle","Jeni","Jenica","Jeniece","Jenifer","Jeniffer","Jenilee","Jenine","Jenn","Jenna","Jennee","Jennette","Jenni","Jennica","Jennie","Jennifer","Jennilee","Jennine","Jenny","Jeralee","Jere","Jeri","Jermaine","Jerrie","Jerrilee","Jerrilyn","Jerrine","Jerry","Jerrylee","Jess","Jessa","Jessalin","Jessalyn","Jessamine","Jessamyn","Jesse","Jesselyn","Jessi","Jessica","Jessie","Jessika","Jessy","Jewel","Jewell","Jewelle","Jill","Jillana","Jillane","Jillayne","Jilleen","Jillene","Jilli","Jillian","Jillie","Jilly","Jinny","Jo","Jo-ann","Jo-anne","Joan","Joana","Joane","Joanie","Joann","Joanna","Joanne","Joannes","Jobey","Jobi","Jobie","Jobina","Joby","Jobye","Jobyna","Jocelin","Joceline","Jocelyn","Jocelyne","Jodee","Jodi","Jodie","Jody","Joeann","Joela","Joelie","Joell","Joella","Joelle","Joellen","Joelly","Joellyn","Joelynn","Joete","Joey","Johanna","Johannah","Johna","Johnath","Johnette","Johnna","Joice","Jojo","Jolee","Joleen","Jolene","Joletta","Joli","Jolie","Joline","Joly","Jolyn","Jolynn","Jonell","Joni","Jonie","Jonis","Jordain","Jordan","Jordana","Jordanna","Jorey","Jori","Jorie","Jorrie","Jorry","Joscelin","Josee","Josefa","Josefina","Josepha","Josephina","Josephine","Josey","Josi","Josie","Josselyn","Josy","Jourdan","Joy","Joya","Joyan","Joyann","Joyce","Joycelin","Joye","Jsandye","Juana","Juanita","Judi","Judie","Judith","Juditha","Judy","Judye","Juieta","Julee","Juli","Julia","Juliana","Juliane","Juliann","Julianna","Julianne","Julie","Julienne","Juliet","Julieta","Julietta","Juliette","Julina","Juline","Julissa","Julita","June","Junette","Junia","Junie","Junina","Justina","Justine","Justinn","Jyoti","Kacey","Kacie","Kacy","Kaela","Kai","Kaia","Kaila","Kaile","Kailey","Kaitlin","Kaitlyn","Kaitlynn","Kaja","Kakalina","Kala","Kaleena","Kali","Kalie","Kalila","Kalina","Kalinda","Kalindi","Kalli","Kally","Kameko","Kamila","Kamilah","Kamillah","Kandace","Kandy","Kania","Kanya","Kara","Kara-lynn","Karalee","Karalynn","Kare","Karee","Karel","Karen","Karena","Kari","Karia","Karie","Karil","Karilynn","Karin","Karina","Karine","Kariotta","Karisa","Karissa","Karita","Karla","Karlee","Karleen","Karlen","Karlene","Karlie","Karlotta","Karlotte","Karly","Karlyn","Karmen","Karna","Karol","Karola","Karole","Karolina","Karoline","Karoly","Karon","Karrah","Karrie","Karry","Kary","Karyl","Karylin","Karyn","Kasey","Kass","Kassandra","Kassey","Kassi","Kassia","Kassie","Kat","Kata","Katalin","Kate","Katee","Katerina","Katerine","Katey","Kath","Katha","Katharina","Katharine","Katharyn","Kathe","Katherina","Katherine","Katheryn","Kathi","Kathie","Kathleen","Kathlin","Kathrine","Kathryn","Kathryne","Kathy","Kathye","Kati","Katie","Katina","Katine","Katinka","Katleen","Katlin","Katrina","Katrine","Katrinka","Katti","Kattie","Katuscha","Katusha","Katy","Katya","Kay","Kaycee","Kaye","Kayla","Kayle","Kaylee","Kayley","Kaylil","Kaylyn","Keeley","Keelia","Keely","Kelcey","Kelci","Kelcie","Kelcy","Kelila","Kellen","Kelley","Kelli","Kellia","Kellie","Kellina","Kellsie","Kelly","Kellyann","Kelsey","Kelsi","Kelsy","Kendra","Kendre","Kenna","Keri","Keriann","Kerianne","Kerri","Kerrie","Kerrill","Kerrin","Kerry","Kerstin","Kesley","Keslie","Kessia","Kessiah","Ketti","Kettie","Ketty","Kevina","Kevyn","Ki","Kiah","Kial","Kiele","Kiersten","Kikelia","Kiley","Kim","Kimberlee","Kimberley","Kimberli","Kimberly","Kimberlyn","Kimbra","Kimmi","Kimmie","Kimmy","Kinna","Kip","Kipp","Kippie","Kippy","Kira","Kirbee","Kirbie","Kirby","Kiri","Kirsten","Kirsteni","Kirsti","Kirstin","Kirstyn","Kissee","Kissiah","Kissie","Kit","Kitti","Kittie","Kitty","Kizzee","Kizzie","Klara","Klarika","Klarrisa","Konstance","Konstanze","Koo","Kora","Koral","Koralle","Kordula","Kore","Korella","Koren","Koressa","Kori","Korie","Korney","Korrie","Korry","Kris","Krissie","Krissy","Krista","Kristal","Kristan","Kriste","Kristel","Kristen","Kristi","Kristien","Kristin","Kristina","Kristine","Kristy","Kristyn","Krysta","Krystal","Krystalle","Krystle","Krystyna","Kyla","Kyle","Kylen","Kylie","Kylila","Kylynn","Kym","Kynthia","Kyrstin","Lacee","Lacey","Lacie","Lacy","Ladonna","Laetitia","Laina","Lainey","Lana","Lanae","Lane","Lanette","Laney","Lani","Lanie","Lanita","Lanna","Lanni","Lanny","Lara","Laraine","Lari","Larina","Larine","Larisa","Larissa","Lark","Laryssa","Latashia","Latia","Latisha","Latrena","Latrina","Laura","Lauraine","Laural","Lauralee","Laure","Lauree","Laureen","Laurel","Laurella","Lauren","Laurena","Laurene","Lauretta","Laurette","Lauri","Laurianne","Laurice","Laurie","Lauryn","Lavena","Laverna","Laverne","Lavina","Lavinia","Lavinie","Layla","Layne","Layney","Lea","Leah","Leandra","Leann","Leanna","Leanor","Leanora","Lebbie","Leda","Lee","Leeann","Leeanne","Leela","Leelah","Leena","Leesa","Leese","Legra","Leia","Leigh","Leigha","Leila","Leilah","Leisha","Lela","Lelah","Leland","Lelia","Lena","Lenee","Lenette","Lenka","Lenna","Lenora","Lenore","Leodora","Leoine","Leola","Leoline","Leona","Leonanie","Leone","Leonelle","Leonie","Leonora","Leonore","Leontine","Leontyne","Leora","Leshia","Lesley","Lesli","Leslie","Lesly","Lesya","Leta","Lethia","Leticia","Letisha","Letitia","Letizia","Letta","Letti","Lettie","Letty","Lexi","Lexie","Lexine","Lexis","Lexy","Leyla","Lezlie","Lia","Lian","Liana","Liane","Lianna","Lianne","Lib","Libbey","Libbi","Libbie","Libby","Licha","Lida","Lidia","Liesa","Lil","Lila","Lilah","Lilas","Lilia","Lilian","Liliane","Lilias","Lilith","Lilla","Lilli","Lillian","Lillis","Lilllie","Lilly","Lily","Lilyan","Lin","Lina","Lind","Linda","Lindi","Lindie","Lindsay","Lindsey","Lindsy","Lindy","Linea","Linell","Linet","Linette","Linn","Linnea","Linnell","Linnet","Linnie","Linzy","Lira","Lisa","Lisabeth","Lisbeth","Lise","Lisetta","Lisette","Lisha","Lishe","Lissa","Lissi","Lissie","Lissy","Lita","Liuka","Liv","Liva","Livia","Livvie","Livvy","Livvyy","Livy","Liz","Liza","Lizabeth","Lizbeth","Lizette","Lizzie","Lizzy","Loella","Lois","Loise","Lola","Loleta","Lolita","Lolly","Lona","Lonee","Loni","Lonna","Lonni","Lonnie","Lora","Lorain","Loraine","Loralee","Loralie","Loralyn","Loree","Loreen","Lorelei","Lorelle","Loren","Lorena","Lorene","Lorenza","Loretta","Lorette","Lori","Loria","Lorianna","Lorianne","Lorie","Lorilee","Lorilyn","Lorinda","Lorine","Lorita","Lorna","Lorne","Lorraine","Lorrayne","Lorri","Lorrie","Lorrin","Lorry","Lory","Lotta","Lotte","Lotti","Lottie","Lotty","Lou","Louella","Louisa","Louise","Louisette","Loutitia","Lu","Luce","Luci","Lucia","Luciana","Lucie","Lucienne","Lucila","Lucilia","Lucille","Lucina","Lucinda","Lucine","Lucita","Lucky","Lucretia","Lucy","Ludovika","Luella","Luelle","Luisa","Luise","Lula","Lulita","Lulu","Lura","Lurette","Lurleen","Lurlene","Lurline","Lusa","Luz","Lyda","Lydia","Lydie","Lyn","Lynda","Lynde","Lyndel","Lyndell","Lyndsay","Lyndsey","Lyndsie","Lyndy","Lynea","Lynelle","Lynett","Lynette","Lynn","Lynna","Lynne","Lynnea","Lynnell","Lynnelle","Lynnet","Lynnett","Lynnette","Lynsey","Lyssa","Mab","Mabel","Mabelle","Mable","Mada","Madalena","Madalyn","Maddalena","Maddi","Maddie","Maddy","Madel","Madelaine","Madeleine","Madelena","Madelene","Madelin","Madelina","Madeline","Madella","Madelle","Madelon","Madelyn","Madge","Madlen","Madlin","Madonna","Mady","Mae","Maegan","Mag","Magda","Magdaia","Magdalen","Magdalena","Magdalene","Maggee","Maggi","Maggie","Maggy","Mahala","Mahalia","Maia","Maible","Maiga","Maighdiln","Mair","Maire","Maisey","Maisie","Maitilde","Mala","Malanie","Malena","Malia","Malina","Malinda","Malinde","Malissa","Malissia","Mallissa","Mallorie","Mallory","Malorie","Malory","Malva","Malvina","Malynda","Mame","Mamie","Manda","Mandi","Mandie","Mandy","Manon","Manya","Mara","Marabel","Marcela","Marcelia","Marcella","Marcelle","Marcellina","Marcelline","Marchelle","Marci","Marcia","Marcie","Marcile","Marcille","Marcy","Mareah","Maren","Marena","Maressa","Marga","Margalit","Margalo","Margaret","Margareta","Margarete","Margaretha","Margarethe","Margaretta","Margarette","Margarita","Margaux","Marge","Margeaux","Margery","Marget","Margette","Margi","Margie","Margit","Margo","Margot","Margret","Marguerite","Margy","Mari","Maria","Mariam","Marian","Mariana","Mariann","Marianna","Marianne","Maribel","Maribelle","Maribeth","Marice","Maridel","Marie","Marie-ann","Marie-jeanne","Marieann","Mariejeanne","Mariel","Mariele","Marielle","Mariellen","Marietta","Mariette","Marigold","Marijo","Marika","Marilee","Marilin","Marillin","Marilyn","Marin","Marina","Marinna","Marion","Mariquilla","Maris","Marisa","Mariska","Marissa","Marita","Maritsa","Mariya","Marj","Marja","Marje","Marji","Marjie","Marjorie","Marjory","Marjy","Marketa","Marla","Marlane","Marleah","Marlee","Marleen","Marlena","Marlene","Marley","Marlie","Marline","Marlo","Marlyn","Marna","Marne","Marney","Marni","Marnia","Marnie","Marquita","Marrilee","Marris","Marrissa","Marsha","Marsiella","Marta","Martelle","Martguerita","Martha","Marthe","Marthena","Marti","Martica","Martie","Martina","Martita","Marty","Martynne","Mary","Marya","Maryann","Maryanna","Maryanne","Marybelle","Marybeth","Maryellen","Maryjane","Maryjo","Maryl","Marylee","Marylin","Marylinda","Marylou","Marylynne","Maryrose","Marys","Marysa","Masha","Matelda","Mathilda","Mathilde","Matilda","Matilde","Matti","Mattie","Matty","Maud","Maude","Maudie","Maura","Maure","Maureen","Maureene","Maurene","Maurine","Maurise","Maurita","Maurizia","Mavis","Mavra","Max","Maxi","Maxie","Maxine","Maxy","May","Maybelle","Maye","Mead","Meade","Meagan","Meaghan","Meara","Mechelle","Meg","Megan","Megen","Meggi","Meggie","Meggy","Meghan","Meghann","Mehetabel","Mei","Mel","Mela","Melamie","Melania","Melanie","Melantha","Melany","Melba","Melesa","Melessa","Melicent","Melina","Melinda","Melinde","Melisa","Melisande","Melisandra","Melisenda","Melisent","Melissa","Melisse","Melita","Melitta","Mella","Melli","Mellicent","Mellie","Mellisa","Mellisent","Melloney","Melly","Melodee","Melodie","Melody","Melonie","Melony","Melosa","Melva","Mercedes","Merci","Mercie","Mercy","Meredith","Meredithe","Meridel","Meridith","Meriel","Merilee","Merilyn","Meris","Merissa","Merl","Merla","Merle","Merlina","Merline","Merna","Merola","Merralee","Merridie","Merrie","Merrielle","Merrile","Merrilee","Merrili","Merrill","Merrily","Merry","Mersey","Meryl","Meta","Mia","Micaela","Michaela","Michaelina","Michaeline","Michaella","Michal","Michel","Michele","Michelina","Micheline","Michell","Michelle","Micki","Mickie","Micky","Midge","Mignon","Mignonne","Miguela","Miguelita","Mikaela","Mil","Mildred","Mildrid","Milena","Milicent","Milissent","Milka","Milli","Millicent","Millie","Millisent","Milly","Milzie","Mimi","Min","Mina","Minda","Mindy","Minerva","Minetta","Minette","Minna","Minnaminnie","Minne","Minni","Minnie","Minnnie","Minny","Minta","Miquela","Mira","Mirabel","Mirabella","Mirabelle","Miran","Miranda","Mireielle","Mireille","Mirella","Mirelle","Miriam","Mirilla","Mirna","Misha","Missie","Missy","Misti","Misty","Mitzi","Modesta","Modestia","Modestine","Modesty","Moina","Moira","Moll","Mollee","Molli","Mollie","Molly","Mommy","Mona","Monah","Monica","Monika","Monique","Mora","Moreen","Morena","Morgan","Morgana","Morganica","Morganne","Morgen","Moria","Morissa","Morna","Moselle","Moyna","Moyra","Mozelle","Muffin","Mufi","Mufinella","Muire","Mureil","Murial","Muriel","Murielle","Myra","Myrah","Myranda","Myriam","Myrilla","Myrle","Myrlene","Myrna","Myrta","Myrtia","Myrtice","Myrtie","Myrtle","Nada","Nadean","Nadeen","Nadia","Nadine","Nadiya","Nady","Nadya","Nalani","Nan","Nana","Nananne","Nance","Nancee","Nancey","Nanci","Nancie","Nancy","Nanete","Nanette","Nani","Nanice","Nanine","Nannette","Nanni","Nannie","Nanny","Nanon","Naoma","Naomi","Nara","Nari","Nariko","Nat","Nata","Natala","Natalee","Natalie","Natalina","Nataline","Natalya","Natasha","Natassia","Nathalia","Nathalie","Natividad","Natka","Natty","Neala","Neda","Nedda","Nedi","Neely","Neila","Neile","Neilla","Neille","Nelia","Nelie","Nell","Nelle","Nelli","Nellie","Nelly","Nerissa","Nerita","Nert","Nerta","Nerte","Nerti","Nertie","Nerty","Nessa","Nessi","Nessie","Nessy","Nesta","Netta","Netti","Nettie","Nettle","Netty","Nevsa","Neysa","Nichol","Nichole","Nicholle","Nicki","Nickie","Nicky","Nicol","Nicola","Nicole","Nicolea","Nicolette","Nicoli","Nicolina","Nicoline","Nicolle","Nikaniki","Nike","Niki","Nikki","Nikkie","Nikoletta","Nikolia","Nina","Ninetta","Ninette","Ninnetta","Ninnette","Ninon","Nissa","Nisse","Nissie","Nissy","Nita","Nixie","Noami","Noel","Noelani","Noell","Noella","Noelle","Noellyn","Noelyn","Noemi","Nola","Nolana","Nolie","Nollie","Nomi","Nona","Nonah","Noni","Nonie","Nonna","Nonnah","Nora","Norah","Norean","Noreen","Norene","Norina","Norine","Norma","Norri","Norrie","Norry","Novelia","Nydia","Nyssa","Octavia","Odele","Odelia","Odelinda","Odella","Odelle","Odessa","Odetta","Odette","Odilia","Odille","Ofelia","Ofella","Ofilia","Ola","Olenka","Olga","Olia","Olimpia","Olive","Olivette","Olivia","Olivie","Oliy","Ollie","Olly","Olva","Olwen","Olympe","Olympia","Olympie","Ondrea","Oneida","Onida","Oona","Opal","Opalina","Opaline","Ophelia","Ophelie","Ora","Oralee","Oralia","Oralie","Oralla","Oralle","Orel","Orelee","Orelia","Orelie","Orella","Orelle","Oriana","Orly","Orsa","Orsola","Ortensia","Otha","Othelia","Othella","Othilia","Othilie","Ottilie","Page","Paige","Paloma","Pam","Pamela","Pamelina","Pamella","Pammi","Pammie","Pammy","Pandora","Pansie","Pansy","Paola","Paolina","Papagena","Pat","Patience","Patrica","Patrice","Patricia","Patrizia","Patsy","Patti","Pattie","Patty","Paula","Paule","Pauletta","Paulette","Pauli","Paulie","Paulina","Pauline","Paulita","Pauly","Pavia","Pavla","Pearl","Pearla","Pearle","Pearline","Peg","Pegeen","Peggi","Peggie","Peggy","Pen","Penelopa","Penelope","Penni","Pennie","Penny","Pepi","Pepita","Peri","Peria","Perl","Perla","Perle","Perri","Perrine","Perry","Persis","Pet","Peta","Petra","Petrina","Petronella","Petronia","Petronilla","Petronille","Petunia","Phaedra","Phaidra","Phebe","Phedra","Phelia","Phil","Philipa","Philippa","Philippe","Philippine","Philis","Phillida","Phillie","Phillis","Philly","Philomena","Phoebe","Phylis","Phyllida","Phyllis","Phyllys","Phylys","Pia","Pier","Pierette","Pierrette","Pietra","Piper","Pippa","Pippy","Polly","Pollyanna","Pooh","Poppy","Portia","Pris","Prisca","Priscella","Priscilla","Prissie","Pru","Prudence","Prudi","Prudy","Prue","Queenie","Quentin","Querida","Quinn","Quinta","Quintana","Quintilla","Quintina","Rachael","Rachel","Rachele","Rachelle","Rae","Raeann","Raf","Rafa","Rafaela","Rafaelia","Rafaelita","Rahal","Rahel","Raina","Raine","Rakel","Ralina","Ramona","Ramonda","Rana","Randa","Randee","Randene","Randi","Randie","Randy","Ranee","Rani","Rania","Ranice","Ranique","Ranna","Raphaela","Raquel","Raquela","Rasia","Rasla","Raven","Ray","Raychel","Raye","Rayna","Raynell","Rayshell","Rea","Reba","Rebbecca","Rebe","Rebeca","Rebecca","Rebecka","Rebeka","Rebekah","Rebekkah","Ree","Reeba","Reena","Reeta","Reeva","Regan","Reggi","Reggie","Regina","Regine","Reiko","Reina","Reine","Remy","Rena","Renae","Renata","Renate","Rene","Renee","Renell","Renelle","Renie","Rennie","Reta","Retha","Revkah","Rey","Reyna","Rhea","Rheba","Rheta","Rhetta","Rhiamon","Rhianna","Rhianon","Rhoda","Rhodia","Rhodie","Rhody","Rhona","Rhonda","Riane","Riannon","Rianon","Rica","Ricca","Rici","Ricki","Rickie","Ricky","Riki","Rikki","Rina","Risa","Rita","Riva","Rivalee","Rivi","Rivkah","Rivy","Roana","Roanna","Roanne","Robbi","Robbie","Robbin","Robby","Robbyn","Robena","Robenia","Roberta","Robin","Robina","Robinet","Robinett","Robinetta","Robinette","Robinia","Roby","Robyn","Roch","Rochell","Rochella","Rochelle","Rochette","Roda","Rodi","Rodie","Rodina","Rois","Romola","Romona","Romonda","Romy","Rona","Ronalda","Ronda","Ronica","Ronna","Ronni","Ronnica","Ronnie","Ronny","Roobbie","Rora","Rori","Rorie","Rory","Ros","Rosa","Rosabel","Rosabella","Rosabelle","Rosaleen","Rosalia","Rosalie","Rosalind","Rosalinda","Rosalinde","Rosaline","Rosalyn","Rosalynd","Rosamond","Rosamund","Rosana","Rosanna","Rosanne","Rose","Roseann","Roseanna","Roseanne","Roselia","Roselin","Roseline","Rosella","Roselle","Rosemaria","Rosemarie","Rosemary","Rosemonde","Rosene","Rosetta","Rosette","Roshelle","Rosie","Rosina","Rosita","Roslyn","Rosmunda","Rosy","Row","Rowe","Rowena","Roxana","Roxane","Roxanna","Roxanne","Roxi","Roxie","Roxine","Roxy","Roz","Rozalie","Rozalin","Rozamond","Rozanna","Rozanne","Roze","Rozele","Rozella","Rozelle","Rozina","Rubetta","Rubi","Rubia","Rubie","Rubina","Ruby","Ruperta","Ruth","Ruthann","Ruthanne","Ruthe","Ruthi","Ruthie","Ruthy","Ryann","Rycca","Saba","Sabina","Sabine","Sabra","Sabrina","Sacha","Sada","Sadella","Sadie","Sadye","Saidee","Sal","Salaidh","Sallee","Salli","Sallie","Sally","Sallyann","Sallyanne","Saloma","Salome","Salomi","Sam","Samantha","Samara","Samaria","Sammy","Sande","Sandi","Sandie","Sandra","Sandy","Sandye","Sapphira","Sapphire","Sara","Sara-ann","Saraann","Sarah","Sarajane","Saree","Sarena","Sarene","Sarette","Sari","Sarina","Sarine","Sarita","Sascha","Sasha","Sashenka","Saudra","Saundra","Savina","Sayre","Scarlet","Scarlett","Sean","Seana","Seka","Sela","Selena","Selene","Selestina","Selia","Selie","Selina","Selinda","Seline","Sella","Selle","Selma","Sena","Sephira","Serena","Serene","Shae","Shaina","Shaine","Shalna","Shalne","Shana","Shanda","Shandee","Shandeigh","Shandie","Shandra","Shandy","Shane","Shani","Shanie","Shanna","Shannah","Shannen","Shannon","Shanon","Shanta","Shantee","Shara","Sharai","Shari","Sharia","Sharity","Sharl","Sharla","Sharleen","Sharlene","Sharline","Sharon","Sharona","Sharron","Sharyl","Shaun","Shauna","Shawn","Shawna","Shawnee","Shay","Shayla","Shaylah","Shaylyn","Shaylynn","Shayna","Shayne","Shea","Sheba","Sheela","Sheelagh","Sheelah","Sheena","Sheeree","Sheila","Sheila-kathryn","Sheilah","Shel","Shela","Shelagh","Shelba","Shelbi","Shelby","Shelia","Shell","Shelley","Shelli","Shellie","Shelly","Shena","Sher","Sheree","Sheri","Sherie","Sherill","Sherilyn","Sherline","Sherri","Sherrie","Sherry","Sherye","Sheryl","Shina","Shir","Shirl","Shirlee","Shirleen","Shirlene","Shirley","Shirline","Shoshana","Shoshanna","Siana","Sianna","Sib","Sibbie","Sibby","Sibeal","Sibel","Sibella","Sibelle","Sibilla","Sibley","Sibyl","Sibylla","Sibylle","Sidoney","Sidonia","Sidonnie","Sigrid","Sile","Sileas","Silva","Silvana","Silvia","Silvie","Simona","Simone","Simonette","Simonne","Sindee","Siobhan","Sioux","Siouxie","Sisely","Sisile","Sissie","Sissy","Siusan","Sofia","Sofie","Sondra","Sonia","Sonja","Sonni","Sonnie","Sonnnie","Sonny","Sonya","Sophey","Sophi","Sophia","Sophie","Sophronia","Sorcha","Sosanna","Stace","Stacee","Stacey","Staci","Stacia","Stacie","Stacy","Stafani","Star","Starla","Starlene","Starlin","Starr","Stefa","Stefania","Stefanie","Steffane","Steffi","Steffie","Stella","Stepha","Stephana","Stephani","Stephanie","Stephannie","Stephenie","Stephi","Stephie","Stephine","Stesha","Stevana","Stevena","Stoddard","Storm","Stormi","Stormie","Stormy","Sue","Suellen","Sukey","Suki","Sula","Sunny","Sunshine","Susan","Susana","Susanetta","Susann","Susanna","Susannah","Susanne","Susette","Susi","Susie","Susy","Suzann","Suzanna","Suzanne","Suzette","Suzi","Suzie","Suzy","Sybil","Sybila","Sybilla","Sybille","Sybyl","Sydel","Sydelle","Sydney","Sylvia","Tabatha","Tabbatha","Tabbi","Tabbie","Tabbitha","Tabby","Tabina","Tabitha","Taffy","Talia","Tallia","Tallie","Tallou","Tallulah","Tally","Talya","Talyah","Tamar","Tamara","Tamarah","Tamarra","Tamera","Tami","Tamiko","Tamma","Tammara","Tammi","Tammie","Tammy","Tamqrah","Tamra","Tana","Tandi","Tandie","Tandy","Tanhya","Tani","Tania","Tanitansy","Tansy","Tanya","Tara","Tarah","Tarra","Tarrah","Taryn","Tasha","Tasia","Tate","Tatiana","Tatiania","Tatum","Tawnya","Tawsha","Ted","Tedda","Teddi","Teddie","Teddy","Tedi","Tedra","Teena","Teirtza","Teodora","Tera","Teresa","Terese","Teresina","Teresita","Teressa","Teri","Teriann","Terra","Terri","Terrie","Terrijo","Terry","Terrye","Tersina","Terza","Tess","Tessa","Tessi","Tessie","Tessy","Thalia","Thea","Theadora","Theda","Thekla","Thelma","Theo","Theodora","Theodosia","Theresa","Therese","Theresina","Theresita","Theressa","Therine","Thia","Thomasa","Thomasin","Thomasina","Thomasine","Tiena","Tierney","Tiertza","Tiff","Tiffani","Tiffanie","Tiffany","Tiffi","Tiffie","Tiffy","Tilda","Tildi","Tildie","Tildy","Tillie","Tilly","Tim","Timi","Timmi","Timmie","Timmy","Timothea","Tina","Tine","Tiphani","Tiphanie","Tiphany","Tish","Tisha","Tobe","Tobey","Tobi","Toby","Tobye","Toinette","Toma","Tomasina","Tomasine","Tomi","Tommi","Tommie","Tommy","Toni","Tonia","Tonie","Tony","Tonya","Tonye","Tootsie","Torey","Tori","Torie","Torrie","Tory","Tova","Tove","Tracee","Tracey","Traci","Tracie","Tracy","Trenna","Tresa","Trescha","Tressa","Tricia","Trina","Trish","Trisha","Trista","Trix","Trixi","Trixie","Trixy","Truda","Trude","Trudey","Trudi","Trudie","Trudy","Trula","Tuesday","Twila","Twyla","Tybi","Tybie","Tyne","Ula","Ulla","Ulrica","Ulrika","Ulrikaumeko","Ulrike","Umeko","Una","Ursa","Ursala","Ursola","Ursula","Ursulina","Ursuline","Uta","Val","Valaree","Valaria","Vale","Valeda","Valencia","Valene","Valenka","Valentia","Valentina","Valentine","Valera","Valeria","Valerie","Valery","Valerye","Valida","Valina","Valli","Vallie","Vally","Valma","Valry","Van","Vanda","Vanessa","Vania","Vanna","Vanni","Vannie","Vanny","Vanya","Veda","Velma","Velvet","Venita","Venus","Vera","Veradis","Vere","Verena","Verene","Veriee","Verile","Verina","Verine","Verla","Verna","Vernice","Veronica","Veronika","Veronike","Veronique","Vevay","Vi","Vicki","Vickie","Vicky","Victoria","Vida","Viki","Vikki","Vikky","Vilhelmina","Vilma","Vin","Vina","Vinita","Vinni","Vinnie","Vinny","Viola","Violante","Viole","Violet","Violetta","Violette","Virgie","Virgina","Virginia","Virginie","Vita","Vitia","Vitoria","Vittoria","Viv","Viva","Vivi","Vivia","Vivian","Viviana","Vivianna","Vivianne","Vivie","Vivien","Viviene","Vivienne","Viviyan","Vivyan","Vivyanne","Vonni","Vonnie","Vonny","Vyky","Wallie","Wallis","Walliw","Wally","Waly","Wanda","Wandie","Wandis","Waneta","Wanids","Wenda","Wendeline","Wendi","Wendie","Wendy","Wendye","Wenona","Wenonah","Whitney","Wileen","Wilhelmina","Wilhelmine","Wilie","Willa","Willabella","Willamina","Willetta","Willette","Willi","Willie","Willow","Willy","Willyt","Wilma","Wilmette","Wilona","Wilone","Wilow","Windy","Wini","Winifred","Winna","Winnah","Winne","Winni","Winnie","Winnifred","Winny","Winona","Winonah","Wren","Wrennie","Wylma","Wynn","Wynne","Wynnie","Wynny","Xaviera","Xena","Xenia","Xylia","Xylina","Yalonda","Yasmeen","Yasmin","Yelena","Yetta","Yettie","Yetty","Yevette","Ynes","Ynez","Yoko","Yolanda","Yolande","Yolane","Yolanthe","Yoshi","Yoshiko","Yovonnda","Ysabel","Yvette","Yvonne","Zabrina","Zahara","Zandra","Zaneta","Zara","Zarah","Zaria","Zarla","Zea","Zelda","Zelma","Zena","Zenia","Zia","Zilvia","Zita","Zitella","Zoe","Zola","Zonda","Zondra","Zonnya","Zora","Zorah","Zorana","Zorina","Zorine","Zsazsa","Zulema","Zuzana"],y=["Ackbar","Adi Gallia","Anakin Skywalker","Arvel Crynyd","Ayla Secura","Bail Prestor Organa","Barriss Offee","Ben Quadinaros","Beru Whitesun lars","Bib Fortuna","Biggs Darklighter","Boba Fett","Bossk","C-3PO","Chewbacca","Cliegg Lars","Cordé","Darth Maul","Darth Vader","Dexter Jettster","Dooku","Dormé","Dud Bolt","Eeth Koth","Finis Valorum","Gasgano","Greedo","Gregar Typho","Grievous","Han Solo","IG-88","Jabba Desilijic Tiure","Jango Fett","Jar Jar Binks","Jek Tono Porkins","Jocasta Nu","Ki-Adi-Mundi","Kit Fisto","Lama Su","Lando Calrissian","Leia Organa","Lobot","Luke Skywalker","Luminara Unduli","Mace Windu","Mas Amedda","Mon Mothma","Nien Nunb","Nute Gunray","Obi-Wan Kenobi","Owen Lars","Padmé Amidala","Palpatine","Plo Koon","Poggle the Lesser","Quarsh Panaka","Qui-Gon Jinn","R2-D2","R4-P17","R5-D4","Ratts Tyerel","Raymus Antilles","Ric Olié","Roos Tarpals","Rugor Nass","Saesee Tiin","San Hill","Sebulba","Shaak Ti","Shmi Skywalker","Sly Moore","Tarfful","Taun We","Tion Medon","Wat Tambor","Watto","Wedge Antilles","Wicket Systri Warrick","Wilhuff Tarkin","Yarael Poof","Yoda","Zam Wesell"];class h{static generate(a={}){let e=a.min||1,i=a.max||999;if(a.length){const n=Math.pow(10,a.length);return e=n/10,i=n-1,[`${Math.floor(Math.random()*(i-e))+e}`]}return[`${Math.floor(Math.random()*(i-e))+e}`]}}export{h as NumberDictionary,l as adjectives,r as animals,t as colors,o as countries,s as languages,d as names,y as starWars,n as uniqueNamesGenerator}; -//# sourceMappingURL=index.modern.js.map diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.umd.js b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.umd.js deleted file mode 100644 index eeb90c449e..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/index.umd.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(a,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((a||self).uniqueNamesGenerator={})}(this,function(a){class e{constructor(a){this.dictionaries=void 0,this.length=void 0,this.separator=void 0,this.style=void 0,this.seed=void 0;const{length:e,separator:i,dictionaries:n,style:l,seed:r}=a;this.dictionaries=n,this.separator=i,this.length=e,this.style=l,this.seed=r}generate(){if(!this.dictionaries)throw new Error('Cannot find any dictionary. Please provide at least one, or leave the "dictionary" field empty in the config object');if(this.length<=0)throw new Error("Invalid length provided");if(this.length>this.dictionaries.length)throw new Error(`The length cannot be bigger than the number of dictionaries.\nLength provided: ${this.length}. Number of dictionaries provided: ${this.dictionaries.length}`);return this.dictionaries.slice(0,this.length).reduce((a,e)=>{let i=e[Math.floor((this.seed?(n=this.seed,(a=>{a=1831565813+(a|=0)|0;let e=Math.imul(a^a>>>15,1|a);return e=e+Math.imul(e^e>>>7,61|e)^e,((e^e>>>14)>>>0)/4294967296})(n)):Math.random())*e.length)]||"";var n;if("lowerCase"===this.style)i=i.toLowerCase();else if("capital"===this.style){const[a,...e]=i.split("");i=a.toUpperCase()+e.join("")}else"upperCase"===this.style&&(i=i.toUpperCase());return a?`${a}${this.separator}${i}`:`${i}`},"")}}const i={separator:"_",dictionaries:[]};a.NumberDictionary=class{static generate(a={}){let e=a.min||1,i=a.max||999;if(a.length){const n=Math.pow(10,a.length);return e=n/10,i=n-1,[`${Math.floor(Math.random()*(i-e))+e}`]}return[`${Math.floor(Math.random()*(i-e))+e}`]}},a.adjectives=["able","above","absent","absolute","abstract","abundant","academic","acceptable","accepted","accessible","accurate","accused","active","actual","acute","added","additional","adequate","adjacent","administrative","adorable","advanced","adverse","advisory","aesthetic","afraid","aggregate","aggressive","agreeable","agreed","agricultural","alert","alive","alleged","allied","alone","alright","alternative","amateur","amazing","ambitious","amused","ancient","angry","annoyed","annual","anonymous","anxious","appalling","apparent","applicable","appropriate","arbitrary","architectural","armed","arrogant","artificial","artistic","ashamed","asleep","assistant","associated","atomic","attractive","automatic","autonomous","available","average","awake","aware","awful","awkward","back","bad","balanced","bare","basic","beautiful","beneficial","better","bewildered","big","binding","biological","bitter","bizarre","blank","blind","blonde","bloody","blushing","boiling","bold","bored","boring","bottom","brainy","brave","breakable","breezy","brief","bright","brilliant","broad","broken","bumpy","burning","busy","calm","capable","capitalist","careful","casual","causal","cautious","central","certain","changing","characteristic","charming","cheap","cheerful","chemical","chief","chilly","chosen","christian","chronic","chubby","circular","civic","civil","civilian","classic","classical","clean","clear","clever","clinical","close","closed","cloudy","clumsy","coastal","cognitive","coherent","cold","collective","colonial","colorful","colossal","coloured","colourful","combative","combined","comfortable","coming","commercial","common","communist","compact","comparable","comparative","compatible","competent","competitive","complete","complex","complicated","comprehensive","compulsory","conceptual","concerned","concrete","condemned","confident","confidential","confused","conscious","conservation","conservative","considerable","consistent","constant","constitutional","contemporary","content","continental","continued","continuing","continuous","controlled","controversial","convenient","conventional","convinced","convincing","cooing","cool","cooperative","corporate","correct","corresponding","costly","courageous","crazy","creative","creepy","criminal","critical","crooked","crowded","crucial","crude","cruel","cuddly","cultural","curious","curly","current","curved","cute","daily","damaged","damp","dangerous","dark","dead","deaf","deafening","dear","decent","decisive","deep","defeated","defensive","defiant","definite","deliberate","delicate","delicious","delighted","delightful","democratic","dependent","depressed","desirable","desperate","detailed","determined","developed","developing","devoted","different","difficult","digital","diplomatic","direct","dirty","disabled","disappointed","disastrous","disciplinary","disgusted","distant","distinct","distinctive","distinguished","disturbed","disturbing","diverse","divine","dizzy","domestic","dominant","double","doubtful","drab","dramatic","dreadful","driving","drunk","dry","dual","due","dull","dusty","dutch","dying","dynamic","eager","early","eastern","easy","economic","educational","eerie","effective","efficient","elaborate","elated","elderly","eldest","electoral","electric","electrical","electronic","elegant","eligible","embarrassed","embarrassing","emotional","empirical","empty","enchanting","encouraging","endless","energetic","enormous","enthusiastic","entire","entitled","envious","environmental","equal","equivalent","essential","established","estimated","ethical","ethnic","eventual","everyday","evident","evil","evolutionary","exact","excellent","exceptional","excess","excessive","excited","exciting","exclusive","existing","exotic","expected","expensive","experienced","experimental","explicit","extended","extensive","external","extra","extraordinary","extreme","exuberant","faint","fair","faithful","familiar","famous","fancy","fantastic","far","fascinating","fashionable","fast","fat","fatal","favourable","favourite","federal","fellow","female","feminist","few","fierce","filthy","final","financial","fine","firm","fiscal","fit","fixed","flaky","flat","flexible","fluffy","fluttering","flying","following","fond","foolish","foreign","formal","formidable","forthcoming","fortunate","forward","fragile","frail","frantic","free","frequent","fresh","friendly","frightened","front","frozen","full","fun","functional","fundamental","funny","furious","future","fuzzy","gastric","gay","general","generous","genetic","gentle","genuine","geographical","giant","gigantic","given","glad","glamorous","gleaming","global","glorious","golden","good","gorgeous","gothic","governing","graceful","gradual","grand","grateful","greasy","great","grieving","grim","gross","grotesque","growing","grubby","grumpy","guilty","handicapped","handsome","happy","hard","harsh","head","healthy","heavy","helpful","helpless","hidden","high","hilarious","hissing","historic","historical","hollow","holy","homeless","homely","hon","honest","horizontal","horrible","hostile","hot","huge","human","hungry","hurt","hushed","husky","icy","ideal","identical","ideological","ill","illegal","imaginative","immediate","immense","imperial","implicit","important","impossible","impressed","impressive","improved","inadequate","inappropriate","inc","inclined","increased","increasing","incredible","independent","indirect","individual","industrial","inevitable","influential","informal","inherent","initial","injured","inland","inner","innocent","innovative","inquisitive","instant","institutional","insufficient","intact","integral","integrated","intellectual","intelligent","intense","intensive","interested","interesting","interim","interior","intermediate","internal","international","intimate","invisible","involved","irrelevant","isolated","itchy","jealous","jittery","joint","jolly","joyous","judicial","juicy","junior","just","keen","key","kind","known","labour","large","late","latin","lazy","leading","left","legal","legislative","legitimate","lengthy","lesser","level","lexical","liable","liberal","light","like","likely","limited","linear","linguistic","liquid","literary","little","live","lively","living","local","logical","lonely","long","loose","lost","loud","lovely","low","loyal","ltd","lucky","mad","magic","magnetic","magnificent","main","major","male","mammoth","managerial","managing","manual","many","marginal","marine","marked","married","marvellous","marxist","mass","massive","mathematical","mature","maximum","mean","meaningful","mechanical","medical","medieval","melodic","melted","mental","mere","metropolitan","mid","middle","mighty","mild","military","miniature","minimal","minimum","ministerial","minor","miserable","misleading","missing","misty","mixed","moaning","mobile","moderate","modern","modest","molecular","monetary","monthly","moral","motionless","muddy","multiple","mushy","musical","mute","mutual","mysterious","naked","narrow","nasty","national","native","natural","naughty","naval","near","nearby","neat","necessary","negative","neighbouring","nervous","net","neutral","new","nice","noble","noisy","normal","northern","nosy","notable","novel","nuclear","numerous","nursing","nutritious","nutty","obedient","objective","obliged","obnoxious","obvious","occasional","occupational","odd","official","ok","okay","old","olympic","only","open","operational","opposite","optimistic","oral","ordinary","organic","organisational","original","orthodox","other","outdoor","outer","outrageous","outside","outstanding","overall","overseas","overwhelming","painful","pale","panicky","parallel","parental","parliamentary","partial","particular","passing","passive","past","patient","payable","peaceful","peculiar","perfect","permanent","persistent","personal","petite","philosophical","physical","plain","planned","plastic","pleasant","pleased","poised","polite","political","poor","popular","positive","possible","potential","powerful","practical","precious","precise","preferred","pregnant","preliminary","premier","prepared","present","presidential","pretty","previous","prickly","primary","prime","primitive","principal","printed","prior","private","probable","productive","professional","profitable","profound","progressive","prominent","promising","proper","proposed","prospective","protective","protestant","proud","provincial","psychiatric","psychological","public","puny","pure","purring","puzzled","quaint","qualified","quarrelsome","querulous","quick","quickest","quiet","quintessential","quixotic","racial","radical","rainy","random","rapid","rare","raspy","rational","ratty","raw","ready","real","realistic","rear","reasonable","recent","reduced","redundant","regional","registered","regular","regulatory","related","relative","relaxed","relevant","reliable","relieved","religious","reluctant","remaining","remarkable","remote","renewed","representative","repulsive","required","resident","residential","resonant","respectable","respective","responsible","resulting","retail","retired","revolutionary","rich","ridiculous","right","rigid","ripe","rising","rival","roasted","robust","rolling","romantic","rotten","rough","round","royal","rubber","rude","ruling","running","rural","sacred","sad","safe","salty","satisfactory","satisfied","scared","scary","scattered","scientific","scornful","scrawny","screeching","secondary","secret","secure","select","selected","selective","selfish","semantic","senior","sensible","sensitive","separate","serious","severe","sexual","shaggy","shaky","shallow","shared","sharp","sheer","shiny","shivering","shocked","short","shrill","shy","sick","significant","silent","silky","silly","similar","simple","single","skilled","skinny","sleepy","slight","slim","slimy","slippery","slow","small","smart","smiling","smoggy","smooth","social","socialist","soft","solar","sole","solid","sophisticated","sore","sorry","sound","sour","southern","soviet","spare","sparkling","spatial","special","specific","specified","spectacular","spicy","spiritual","splendid","spontaneous","sporting","spotless","spotty","square","squealing","stable","stale","standard","static","statistical","statutory","steady","steep","sticky","stiff","still","stingy","stormy","straight","straightforward","strange","strategic","strict","striking","striped","strong","structural","stuck","stupid","subjective","subsequent","substantial","subtle","successful","successive","sudden","sufficient","suitable","sunny","super","superb","superior","supporting","supposed","supreme","sure","surprised","surprising","surrounding","surviving","suspicious","sweet","swift","symbolic","sympathetic","systematic","tall","tame","tart","tasteless","tasty","technical","technological","teenage","temporary","tender","tense","terrible","territorial","testy","then","theoretical","thick","thin","thirsty","thorough","thoughtful","thoughtless","thundering","tight","tiny","tired","top","tory","total","tough","toxic","traditional","tragic","tremendous","tricky","tropical","troubled","typical","ugliest","ugly","ultimate","unable","unacceptable","unaware","uncertain","unchanged","uncomfortable","unconscious","underground","underlying","unemployed","uneven","unexpected","unfair","unfortunate","unhappy","uniform","uninterested","unique","united","universal","unknown","unlikely","unnecessary","unpleasant","unsightly","unusual","unwilling","upper","upset","uptight","urban","urgent","used","useful","useless","usual","vague","valid","valuable","variable","varied","various","varying","vast","verbal","vertical","very","vicarious","vicious","victorious","violent","visible","visiting","visual","vital","vitreous","vivacious","vivid","vocal","vocational","voiceless","voluminous","voluntary","vulnerable","wandering","warm","wasteful","watery","weak","wealthy","weary","wee","weekly","weird","welcome","well","western","wet","whispering","whole","wicked","wide","widespread","wild","wilful","willing","willowy","wily","wise","wispy","wittering","witty","wonderful","wooden","working","worldwide","worried","worrying","worthwhile","worthy","written","wrong","xenacious","xenial","xenogeneic","xenophobic","xeric","xerothermic","yabbering","yammering","yappiest","yappy","yawning","yearling","yearning","yeasty","yelling","yelping","yielding","yodelling","young","youngest","youthful","ytterbic","yucky","yummy","zany","zealous","zeroth","zestful","zesty","zippy","zonal","zoophagous","zygomorphic","zygotic"],a.animals=["aardvark","aardwolf","albatross","alligator","alpaca","amphibian","anaconda","angelfish","anglerfish","ant","anteater","antelope","antlion","ape","aphid","armadillo","asp","baboon","badger","bandicoot","barnacle","barracuda","basilisk","bass","bat","bear","beaver","bedbug","bee","beetle","bird","bison","blackbird","boa","boar","bobcat","bobolink","bonobo","booby","bovid","bug","butterfly","buzzard","camel","canid","canidae","capybara","cardinal","caribou","carp","cat","caterpillar","catfish","catshark","cattle","centipede","cephalopod","chameleon","cheetah","chickadee","chicken","chimpanzee","chinchilla","chipmunk","cicada","clam","clownfish","cobra","cockroach","cod","condor","constrictor","coral","cougar","cow","coyote","crab","crane","crawdad","crayfish","cricket","crocodile","crow","cuckoo","damselfly","deer","dingo","dinosaur","dog","dolphin","donkey","dormouse","dove","dragon","dragonfly","duck","eagle","earthworm","earwig","echidna","eel","egret","elephant","elk","emu","ermine","falcon","felidae","ferret","finch","firefly","fish","flamingo","flea","fly","flyingfish","fowl","fox","frog","galliform","gamefowl","gayal","gazelle","gecko","gerbil","gibbon","giraffe","goat","goldfish","goose","gopher","gorilla","grasshopper","grouse","guan","guanaco","guineafowl","gull","guppy","haddock","halibut","hamster","hare","harrier","hawk","hedgehog","heron","herring","hippopotamus","hookworm","hornet","horse","hoverfly","hummingbird","hyena","iguana","impala","jackal","jaguar","jay","jellyfish","junglefowl","kangaroo","kingfisher","kite","kiwi","koala","koi","krill","ladybug","lamprey","landfowl","lark","leech","lemming","lemur","leopard","leopon","limpet","lion","lizard","llama","lobster","locust","loon","louse","lungfish","lynx","macaw","mackerel","magpie","mammal","manatee","mandrill","marlin","marmoset","marmot","marsupial","marten","mastodon","meadowlark","meerkat","mink","minnow","mite","mockingbird","mole","mollusk","mongoose","monkey","moose","mosquito","moth","mouse","mule","muskox","narwhal","newt","nightingale","ocelot","octopus","opossum","orangutan","orca","ostrich","otter","owl","ox","panda","panther","parakeet","parrot","parrotfish","partridge","peacock","peafowl","pelican","penguin","perch","pheasant","pig","pigeon","pike","pinniped","piranha","planarian","platypus","pony","porcupine","porpoise","possum","prawn","primate","ptarmigan","puffin","puma","python","quail","quelea","quokka","rabbit","raccoon","rat","rattlesnake","raven","reindeer","reptile","rhinoceros","roadrunner","rodent","rook","rooster","roundworm","sailfish","salamander","salmon","sawfish","scallop","scorpion","seahorse","shark","sheep","shrew","shrimp","silkworm","silverfish","skink","skunk","sloth","slug","smelt","snail","snake","snipe","sole","sparrow","spider","spoonbill","squid","squirrel","starfish","stingray","stoat","stork","sturgeon","swallow","swan","swift","swordfish","swordtail","tahr","takin","tapir","tarantula","tarsier","termite","tern","thrush","tick","tiger","tiglon","toad","tortoise","toucan","trout","tuna","turkey","turtle","tyrannosaurus","unicorn","urial","vicuna","viper","vole","vulture","wallaby","walrus","warbler","wasp","weasel","whale","whippet","whitefish","wildcat","wildebeest","wildfowl","wolf","wolverine","wombat","woodpecker","worm","wren","xerinae","yak","zebra"],a.colors=["amaranth","amber","amethyst","apricot","aqua","aquamarine","azure","beige","black","blue","blush","bronze","brown","chocolate","coffee","copper","coral","crimson","cyan","emerald","fuchsia","gold","gray","green","harlequin","indigo","ivory","jade","lavender","lime","magenta","maroon","moccasin","olive","orange","peach","pink","plum","purple","red","rose","salmon","sapphire","scarlet","silver","tan","teal","tomato","turquoise","violet","white","yellow"],a.countries=["Afghanistan","Åland Islands","Albania","Algeria","American Samoa","Andorra","Angola","Anguilla","Antarctica","Antigua & Barbuda","Argentina","Armenia","Aruba","Ascension Island","Australia","Austria","Azerbaijan","Bahamas","Bahrain","Bangladesh","Barbados","Belarus","Belgium","Belize","Benin","Bermuda","Bhutan","Bolivia","Bosnia & Herzegovina","Botswana","Brazil","British Indian Ocean Territory","British Virgin Islands","Brunei","Bulgaria","Burkina Faso","Burundi","Cambodia","Cameroon","Canada","Canary Islands","Cape Verde","Caribbean Netherlands","Cayman Islands","Central African Republic","Ceuta & Melilla","Chad","Chile","China","Christmas Island","Cocos Islands","Colombia","Comoros","Congo","Cook Islands","Costa Rica","Côte d'Ivoire","Croatia","Cuba","Curaçao","Cyprus","Czechia","Denmark","Diego Garcia","Djibouti","Dominica","Dominican Republic","Ecuador","Egypt","El Salvador","Equatorial Guinea","Eritrea","Estonia","Ethiopia","Eurozone","Falkland Islands","Faroe Islands","Fiji","Finland","France","French Guiana","French Polynesia","French Southern Territories","Gabon","Gambia","Georgia","Germany","Ghana","Gibraltar","Greece","Greenland","Grenada","Guadeloupe","Guam","Guatemala","Guernsey","Guinea","Guinea-Bissau","Guyana","Haiti","Honduras","Hong Kong SAR China","Hungary","Iceland","India","Indonesia","Iran","Iraq","Ireland","Isle of Man","Israel","Italy","Jamaica","Japan","Jersey","Jordan","Kazakhstan","Kenya","Kiribati","Kosovo","Kuwait","Kyrgyzstan","Laos","Latvia","Lebanon","Lesotho","Liberia","Libya","Liechtenstein","Lithuania","Luxembourg","Macau SAR China","Macedonia","Madagascar","Malawi","Malaysia","Maldives","Mali","Malta","Marshall Islands","Martinique","Mauritania","Mauritius","Mayotte","Mexico","Micronesia","Moldova","Monaco","Mongolia","Montenegro","Montserrat","Morocco","Mozambique","Myanmar","Namibia","Nauru","Nepal","Netherlands","New Caledonia","New Zealand","Nicaragua","Niger","Nigeria","Niue","Norfolk Island","North Korea","Northern Mariana Islands","Norway","Oman","Pakistan","Palau","Palestinian Territories","Panama","Papua New Guinea","Paraguay","Peru","Philippines","Pitcairn Islands","Poland","Portugal","Puerto Rico","Qatar","Réunion","Romania","Russia","Rwanda","Samoa","San Marino","São Tomé & Príncipe","Saudi Arabia","Senegal","Serbia","Seychelles","Sierra Leone","Singapore","Sint Maarten","Slovakia","Slovenia","Solomon Islands","Somalia","South Africa","South Georgia & South Sandwich Islands","South Korea","South Sudan","Spain","Sri Lanka","St. Barthélemy","St. Helena","St. Kitts & Nevis","St. Lucia","St. Martin","St. Pierre & Miquelon","St. Vincent & Grenadines","Sudan","Suriname","Svalbard & Jan Mayen","Swaziland","Sweden","Switzerland","Syria","Taiwan","Tajikistan","Tanzania","Thailand","Timor-Leste","Togo","Tokelau","Tonga","Trinidad & Tobago","Tristan da Cunha","Tunisia","Turkey","Turkmenistan","Turks & Caicos Islands","Tuvalu","U.S. Outlying Islands","U.S. Virgin Islands","Uganda","Ukraine","United Arab Emirates","United Kingdom","United Nations","United States","Uruguay","Uzbekistan","Vanuatu","Vatican City","Venezuela","Vietnam","Wallis & Futuna","Western Sahara","Yemen","Zambia","Zimbabwe"],a.languages=["Akan","Amharic","Arabic","Assamese","Awadhi","Azerbaijani","Balochi","Belarusian","Bengali","Bhojpuri","Burmese","Cebuano","Chewa","Chhattisgarhi","Chittagonian","Czech","Deccan","Dhundhari","Dutch","English","French","Fula","Gan","German","Greek","Gujarati","Hakka","Haryanvi","Hausa","Hiligaynon","Hindi","Hmong","Hungarian","Igbo","Ilocano","Italian","Japanese","Javanese","Jin","Kannada","Kazakh","Khmer","Kinyarwanda","Kirundi","Konkani","Korean","Kurdish","Madurese","Magahi","Maithili","Malagasy","Malay","Malayalam","Mandarin","Marathi","Marwari","Min","Mossi","Nepali","Odia","Oromo","Pashto","Persian","Polish","Portuguese","Punjabi","Quechua","Romanian","Russian","Saraiki","Shona","Sindhi","Sinhala","Somali","Spanish","Sundanese","Swedish","Sylheti","Tagalog","Tamil","Telugu","Thai","Turkish","Turkmen","Ukrainian","Urdu","Uyghur","Uzbek","Vietnamese","Wu","Xhosa","Xiang","Yoruba","Yue","Zhuang","Zulu"],a.names=["Aaren","Aarika","Abagael","Abagail","Abbe","Abbey","Abbi","Abbie","Abby","Abbye","Abigael","Abigail","Abigale","Abra","Ada","Adah","Adaline","Adan","Adara","Adda","Addi","Addia","Addie","Addy","Adel","Adela","Adelaida","Adelaide","Adele","Adelheid","Adelice","Adelina","Adelind","Adeline","Adella","Adelle","Adena","Adey","Adi","Adiana","Adina","Adora","Adore","Adoree","Adorne","Adrea","Adria","Adriaens","Adrian","Adriana","Adriane","Adrianna","Adrianne","Adriena","Adrienne","Aeriel","Aeriela","Aeriell","Afton","Ag","Agace","Agata","Agatha","Agathe","Aggi","Aggie","Aggy","Agna","Agnella","Agnes","Agnese","Agnesse","Agneta","Agnola","Agretha","Aida","Aidan","Aigneis","Aila","Aile","Ailee","Aileen","Ailene","Ailey","Aili","Ailina","Ailis","Ailsun","Ailyn","Aime","Aimee","Aimil","Aindrea","Ainslee","Ainsley","Ainslie","Ajay","Alaine","Alameda","Alana","Alanah","Alane","Alanna","Alayne","Alberta","Albertina","Albertine","Albina","Alecia","Aleda","Aleece","Aleen","Alejandra","Alejandrina","Alena","Alene","Alessandra","Aleta","Alethea","Alex","Alexa","Alexandra","Alexandrina","Alexi","Alexia","Alexina","Alexine","Alexis","Alfi","Alfie","Alfreda","Alfy","Ali","Alia","Alica","Alice","Alicea","Alicia","Alida","Alidia","Alie","Alika","Alikee","Alina","Aline","Alis","Alisa","Alisha","Alison","Alissa","Alisun","Alix","Aliza","Alla","Alleen","Allegra","Allene","Alli","Allianora","Allie","Allina","Allis","Allison","Allissa","Allix","Allsun","Allx","Ally","Allyce","Allyn","Allys","Allyson","Alma","Almeda","Almeria","Almeta","Almira","Almire","Aloise","Aloisia","Aloysia","Alta","Althea","Alvera","Alverta","Alvina","Alvinia","Alvira","Alyce","Alyda","Alys","Alysa","Alyse","Alysia","Alyson","Alyss","Alyssa","Amabel","Amabelle","Amalea","Amalee","Amaleta","Amalia","Amalie","Amalita","Amalle","Amanda","Amandi","Amandie","Amandy","Amara","Amargo","Amata","Amber","Amberly","Ambur","Ame","Amelia","Amelie","Amelina","Ameline","Amelita","Ami","Amie","Amii","Amil","Amitie","Amity","Ammamaria","Amy","Amye","Ana","Anabal","Anabel","Anabella","Anabelle","Analiese","Analise","Anallese","Anallise","Anastasia","Anastasie","Anastassia","Anatola","Andee","Andeee","Anderea","Andi","Andie","Andra","Andrea","Andreana","Andree","Andrei","Andria","Andriana","Andriette","Andromache","Andy","Anestassia","Anet","Anett","Anetta","Anette","Ange","Angel","Angela","Angele","Angelia","Angelica","Angelika","Angelina","Angeline","Angelique","Angelita","Angelle","Angie","Angil","Angy","Ania","Anica","Anissa","Anita","Anitra","Anjanette","Anjela","Ann","Ann-marie","Anna","Anna-diana","Anna-diane","Anna-maria","Annabal","Annabel","Annabela","Annabell","Annabella","Annabelle","Annadiana","Annadiane","Annalee","Annaliese","Annalise","Annamaria","Annamarie","Anne","Anne-corinne","Anne-marie","Annecorinne","Anneliese","Annelise","Annemarie","Annetta","Annette","Anni","Annice","Annie","Annis","Annissa","Annmaria","Annmarie","Annnora","Annora","Anny","Anselma","Ansley","Anstice","Anthe","Anthea","Anthia","Anthiathia","Antoinette","Antonella","Antonetta","Antonia","Antonie","Antonietta","Antonina","Anya","Appolonia","April","Aprilette","Ara","Arabel","Arabela","Arabele","Arabella","Arabelle","Arda","Ardath","Ardeen","Ardelia","Ardelis","Ardella","Ardelle","Arden","Ardene","Ardenia","Ardine","Ardis","Ardisj","Ardith","Ardra","Ardyce","Ardys","Ardyth","Aretha","Ariadne","Ariana","Aridatha","Ariel","Ariela","Ariella","Arielle","Arlana","Arlee","Arleen","Arlen","Arlena","Arlene","Arleta","Arlette","Arleyne","Arlie","Arliene","Arlina","Arlinda","Arline","Arluene","Arly","Arlyn","Arlyne","Aryn","Ashely","Ashia","Ashien","Ashil","Ashla","Ashlan","Ashlee","Ashleigh","Ashlen","Ashley","Ashli","Ashlie","Ashly","Asia","Astra","Astrid","Astrix","Atalanta","Athena","Athene","Atlanta","Atlante","Auberta","Aubine","Aubree","Aubrette","Aubrey","Aubrie","Aubry","Audi","Audie","Audra","Audre","Audrey","Audrie","Audry","Audrye","Audy","Augusta","Auguste","Augustina","Augustine","Aundrea","Aura","Aurea","Aurel","Aurelea","Aurelia","Aurelie","Auria","Aurie","Aurilia","Aurlie","Auroora","Aurora","Aurore","Austin","Austina","Austine","Ava","Aveline","Averil","Averyl","Avie","Avis","Aviva","Avivah","Avril","Avrit","Ayn","Bab","Babara","Babb","Babbette","Babbie","Babette","Babita","Babs","Bambi","Bambie","Bamby","Barb","Barbabra","Barbara","Barbara-anne","Barbaraanne","Barbe","Barbee","Barbette","Barbey","Barbi","Barbie","Barbra","Barby","Bari","Barrie","Barry","Basia","Bathsheba","Batsheva","Bea","Beatrice","Beatrisa","Beatrix","Beatriz","Bebe","Becca","Becka","Becki","Beckie","Becky","Bee","Beilul","Beitris","Bekki","Bel","Belia","Belicia","Belinda","Belita","Bell","Bella","Bellanca","Belle","Bellina","Belva","Belvia","Bendite","Benedetta","Benedicta","Benedikta","Benetta","Benita","Benni","Bennie","Benny","Benoite","Berenice","Beret","Berget","Berna","Bernadene","Bernadette","Bernadina","Bernadine","Bernardina","Bernardine","Bernelle","Bernete","Bernetta","Bernette","Berni","Bernice","Bernie","Bernita","Berny","Berri","Berrie","Berry","Bert","Berta","Berte","Bertha","Berthe","Berti","Bertie","Bertina","Bertine","Berty","Beryl","Beryle","Bess","Bessie","Bessy","Beth","Bethanne","Bethany","Bethena","Bethina","Betsey","Betsy","Betta","Bette","Bette-ann","Betteann","Betteanne","Betti","Bettina","Bettine","Betty","Bettye","Beulah","Bev","Beverie","Beverlee","Beverley","Beverlie","Beverly","Bevvy","Bianca","Bianka","Bibbie","Bibby","Bibbye","Bibi","Biddie","Biddy","Bidget","Bili","Bill","Billi","Billie","Billy","Billye","Binni","Binnie","Binny","Bird","Birdie","Birgit","Birgitta","Blair","Blaire","Blake","Blakelee","Blakeley","Blanca","Blanch","Blancha","Blanche","Blinni","Blinnie","Blinny","Bliss","Blisse","Blithe","Blondell","Blondelle","Blondie","Blondy","Blythe","Bobbe","Bobbee","Bobbette","Bobbi","Bobbie","Bobby","Bobbye","Bobette","Bobina","Bobine","Bobinette","Bonita","Bonnee","Bonni","Bonnibelle","Bonnie","Bonny","Brana","Brandais","Brande","Brandea","Brandi","Brandice","Brandie","Brandise","Brandy","Breanne","Brear","Bree","Breena","Bren","Brena","Brenda","Brenn","Brenna","Brett","Bria","Briana","Brianna","Brianne","Bride","Bridget","Bridgette","Bridie","Brier","Brietta","Brigid","Brigida","Brigit","Brigitta","Brigitte","Brina","Briney","Brinn","Brinna","Briny","Brit","Brita","Britney","Britni","Britt","Britta","Brittan","Brittaney","Brittani","Brittany","Britte","Britteny","Brittne","Brittney","Brittni","Brook","Brooke","Brooks","Brunhilda","Brunhilde","Bryana","Bryn","Bryna","Brynn","Brynna","Brynne","Buffy","Bunni","Bunnie","Bunny","Cacilia","Cacilie","Cahra","Cairistiona","Caitlin","Caitrin","Cal","Calida","Calla","Calley","Calli","Callida","Callie","Cally","Calypso","Cam","Camala","Camel","Camella","Camellia","Cami","Camila","Camile","Camilla","Camille","Cammi","Cammie","Cammy","Candace","Candi","Candice","Candida","Candide","Candie","Candis","Candra","Candy","Caprice","Cara","Caralie","Caren","Carena","Caresa","Caressa","Caresse","Carey","Cari","Caria","Carie","Caril","Carilyn","Carin","Carina","Carine","Cariotta","Carissa","Carita","Caritta","Carla","Carlee","Carleen","Carlen","Carlene","Carley","Carlie","Carlin","Carlina","Carline","Carlita","Carlota","Carlotta","Carly","Carlye","Carlyn","Carlynn","Carlynne","Carma","Carmel","Carmela","Carmelia","Carmelina","Carmelita","Carmella","Carmelle","Carmen","Carmencita","Carmina","Carmine","Carmita","Carmon","Caro","Carol","Carol-jean","Carola","Carolan","Carolann","Carole","Carolee","Carolin","Carolina","Caroline","Caroljean","Carolyn","Carolyne","Carolynn","Caron","Carree","Carri","Carrie","Carrissa","Carroll","Carry","Cary","Caryl","Caryn","Casandra","Casey","Casi","Casie","Cass","Cassandra","Cassandre","Cassandry","Cassaundra","Cassey","Cassi","Cassie","Cassondra","Cassy","Catarina","Cate","Caterina","Catha","Catharina","Catharine","Cathe","Cathee","Catherin","Catherina","Catherine","Cathi","Cathie","Cathleen","Cathlene","Cathrin","Cathrine","Cathryn","Cathy","Cathyleen","Cati","Catie","Catina","Catlaina","Catlee","Catlin","Catrina","Catriona","Caty","Caye","Cayla","Cecelia","Cecil","Cecile","Ceciley","Cecilia","Cecilla","Cecily","Ceil","Cele","Celene","Celesta","Celeste","Celestia","Celestina","Celestine","Celestyn","Celestyna","Celia","Celie","Celina","Celinda","Celine","Celinka","Celisse","Celka","Celle","Cesya","Chad","Chanda","Chandal","Chandra","Channa","Chantal","Chantalle","Charil","Charin","Charis","Charissa","Charisse","Charita","Charity","Charla","Charlean","Charleen","Charlena","Charlene","Charline","Charlot","Charlotta","Charlotte","Charmain","Charmaine","Charmane","Charmian","Charmine","Charmion","Charo","Charyl","Chastity","Chelsae","Chelsea","Chelsey","Chelsie","Chelsy","Cher","Chere","Cherey","Cheri","Cherianne","Cherice","Cherida","Cherie","Cherilyn","Cherilynn","Cherin","Cherise","Cherish","Cherlyn","Cherri","Cherrita","Cherry","Chery","Cherye","Cheryl","Cheslie","Chiarra","Chickie","Chicky","Chiquia","Chiquita","Chlo","Chloe","Chloette","Chloris","Chris","Chrissie","Chrissy","Christa","Christabel","Christabella","Christal","Christalle","Christan","Christean","Christel","Christen","Christi","Christian","Christiana","Christiane","Christie","Christin","Christina","Christine","Christy","Christye","Christyna","Chrysa","Chrysler","Chrystal","Chryste","Chrystel","Cicely","Cicily","Ciel","Cilka","Cinda","Cindee","Cindelyn","Cinderella","Cindi","Cindie","Cindra","Cindy","Cinnamon","Cissiee","Cissy","Clair","Claire","Clara","Clarabelle","Clare","Claresta","Clareta","Claretta","Clarette","Clarey","Clari","Claribel","Clarice","Clarie","Clarinda","Clarine","Clarissa","Clarisse","Clarita","Clary","Claude","Claudelle","Claudetta","Claudette","Claudia","Claudie","Claudina","Claudine","Clea","Clem","Clemence","Clementia","Clementina","Clementine","Clemmie","Clemmy","Cleo","Cleopatra","Clerissa","Clio","Clo","Cloe","Cloris","Clotilda","Clovis","Codee","Codi","Codie","Cody","Coleen","Colene","Coletta","Colette","Colleen","Collen","Collete","Collette","Collie","Colline","Colly","Con","Concettina","Conchita","Concordia","Conni","Connie","Conny","Consolata","Constance","Constancia","Constancy","Constanta","Constantia","Constantina","Constantine","Consuela","Consuelo","Cookie","Cora","Corabel","Corabella","Corabelle","Coral","Coralie","Coraline","Coralyn","Cordelia","Cordelie","Cordey","Cordi","Cordie","Cordula","Cordy","Coreen","Corella","Corenda","Corene","Coretta","Corette","Corey","Cori","Corie","Corilla","Corina","Corine","Corinna","Corinne","Coriss","Corissa","Corliss","Corly","Cornela","Cornelia","Cornelle","Cornie","Corny","Correna","Correy","Corri","Corrianne","Corrie","Corrina","Corrine","Corrinne","Corry","Cortney","Cory","Cosetta","Cosette","Costanza","Courtenay","Courtnay","Courtney","Crin","Cris","Crissie","Crissy","Crista","Cristabel","Cristal","Cristen","Cristi","Cristie","Cristin","Cristina","Cristine","Cristionna","Cristy","Crysta","Crystal","Crystie","Cthrine","Cyb","Cybil","Cybill","Cymbre","Cynde","Cyndi","Cyndia","Cyndie","Cyndy","Cynthea","Cynthia","Cynthie","Cynthy","Dacey","Dacia","Dacie","Dacy","Dael","Daffi","Daffie","Daffy","Dagmar","Dahlia","Daile","Daisey","Daisi","Daisie","Daisy","Dale","Dalenna","Dalia","Dalila","Dallas","Daloris","Damara","Damaris","Damita","Dana","Danell","Danella","Danette","Dani","Dania","Danica","Danice","Daniela","Daniele","Daniella","Danielle","Danika","Danila","Danit","Danita","Danna","Danni","Dannie","Danny","Dannye","Danya","Danyelle","Danyette","Daphene","Daphna","Daphne","Dara","Darb","Darbie","Darby","Darcee","Darcey","Darci","Darcie","Darcy","Darda","Dareen","Darell","Darelle","Dari","Daria","Darice","Darla","Darleen","Darlene","Darline","Darlleen","Daron","Darrelle","Darryl","Darsey","Darsie","Darya","Daryl","Daryn","Dasha","Dasi","Dasie","Dasya","Datha","Daune","Daveen","Daveta","Davida","Davina","Davine","Davita","Dawn","Dawna","Dayle","Dayna","Ddene","De","Deana","Deane","Deanna","Deanne","Deb","Debbi","Debbie","Debby","Debee","Debera","Debi","Debor","Debora","Deborah","Debra","Dede","Dedie","Dedra","Dee","Deeann","Deeanne","Deedee","Deena","Deerdre","Deeyn","Dehlia","Deidre","Deina","Deirdre","Del","Dela","Delcina","Delcine","Delia","Delila","Delilah","Delinda","Dell","Della","Delly","Delora","Delores","Deloria","Deloris","Delphine","Delphinia","Demeter","Demetra","Demetria","Demetris","Dena","Deni","Denice","Denise","Denna","Denni","Dennie","Denny","Deny","Denys","Denyse","Deonne","Desdemona","Desirae","Desiree","Desiri","Deva","Devan","Devi","Devin","Devina","Devinne","Devon","Devondra","Devonna","Devonne","Devora","Di","Diahann","Dian","Diana","Diandra","Diane","Diane-marie","Dianemarie","Diann","Dianna","Dianne","Diannne","Didi","Dido","Diena","Dierdre","Dina","Dinah","Dinnie","Dinny","Dion","Dione","Dionis","Dionne","Dita","Dix","Dixie","Dniren","Dode","Dodi","Dodie","Dody","Doe","Doll","Dolley","Dolli","Dollie","Dolly","Dolores","Dolorita","Doloritas","Domeniga","Dominga","Domini","Dominica","Dominique","Dona","Donella","Donelle","Donetta","Donia","Donica","Donielle","Donna","Donnamarie","Donni","Donnie","Donny","Dora","Doralia","Doralin","Doralyn","Doralynn","Doralynne","Dore","Doreen","Dorelia","Dorella","Dorelle","Dorena","Dorene","Doretta","Dorette","Dorey","Dori","Doria","Dorian","Dorice","Dorie","Dorine","Doris","Dorisa","Dorise","Dorita","Doro","Dorolice","Dorolisa","Dorotea","Doroteya","Dorothea","Dorothee","Dorothy","Dorree","Dorri","Dorrie","Dorris","Dorry","Dorthea","Dorthy","Dory","Dosi","Dot","Doti","Dotti","Dottie","Dotty","Dre","Dreddy","Dredi","Drona","Dru","Druci","Drucie","Drucill","Drucy","Drusi","Drusie","Drusilla","Drusy","Dulce","Dulcea","Dulci","Dulcia","Dulciana","Dulcie","Dulcine","Dulcinea","Dulcy","Dulsea","Dusty","Dyan","Dyana","Dyane","Dyann","Dyanna","Dyanne","Dyna","Dynah","Eachelle","Eada","Eadie","Eadith","Ealasaid","Eartha","Easter","Eba","Ebba","Ebonee","Ebony","Eda","Eddi","Eddie","Eddy","Ede","Edee","Edeline","Eden","Edi","Edie","Edin","Edita","Edith","Editha","Edithe","Ediva","Edna","Edwina","Edy","Edyth","Edythe","Effie","Eileen","Eilis","Eimile","Eirena","Ekaterina","Elaina","Elaine","Elana","Elane","Elayne","Elberta","Elbertina","Elbertine","Eleanor","Eleanora","Eleanore","Electra","Eleen","Elena","Elene","Eleni","Elenore","Eleonora","Eleonore","Elfie","Elfreda","Elfrida","Elfrieda","Elga","Elianora","Elianore","Elicia","Elie","Elinor","Elinore","Elisa","Elisabet","Elisabeth","Elisabetta","Elise","Elisha","Elissa","Elita","Eliza","Elizabet","Elizabeth","Elka","Elke","Ella","Elladine","Elle","Ellen","Ellene","Ellette","Elli","Ellie","Ellissa","Elly","Ellyn","Ellynn","Elmira","Elna","Elnora","Elnore","Eloisa","Eloise","Elonore","Elora","Elsa","Elsbeth","Else","Elset","Elsey","Elsi","Elsie","Elsinore","Elspeth","Elsy","Elva","Elvera","Elvina","Elvira","Elwira","Elyn","Elyse","Elysee","Elysha","Elysia","Elyssa","Em","Ema","Emalee","Emalia","Emelda","Emelia","Emelina","Emeline","Emelita","Emelyne","Emera","Emilee","Emili","Emilia","Emilie","Emiline","Emily","Emlyn","Emlynn","Emlynne","Emma","Emmalee","Emmaline","Emmalyn","Emmalynn","Emmalynne","Emmeline","Emmey","Emmi","Emmie","Emmy","Emmye","Emogene","Emyle","Emylee","Engracia","Enid","Enrica","Enrichetta","Enrika","Enriqueta","Eolanda","Eolande","Eran","Erda","Erena","Erica","Ericha","Ericka","Erika","Erin","Erina","Erinn","Erinna","Erma","Ermengarde","Ermentrude","Ermina","Erminia","Erminie","Erna","Ernaline","Ernesta","Ernestine","Ertha","Eryn","Esma","Esmaria","Esme","Esmeralda","Essa","Essie","Essy","Esta","Estel","Estele","Estell","Estella","Estelle","Ester","Esther","Estrella","Estrellita","Ethel","Ethelda","Ethelin","Ethelind","Etheline","Ethelyn","Ethyl","Etta","Etti","Ettie","Etty","Eudora","Eugenia","Eugenie","Eugine","Eula","Eulalie","Eunice","Euphemia","Eustacia","Eva","Evaleen","Evangelia","Evangelin","Evangelina","Evangeline","Evania","Evanne","Eve","Eveleen","Evelina","Eveline","Evelyn","Evey","Evie","Evita","Evonne","Evvie","Evvy","Evy","Eyde","Eydie","Ezmeralda","Fae","Faina","Faith","Fallon","Fan","Fanchette","Fanchon","Fancie","Fancy","Fanechka","Fania","Fanni","Fannie","Fanny","Fanya","Fara","Farah","Farand","Farica","Farra","Farrah","Farrand","Faun","Faunie","Faustina","Faustine","Fawn","Fawne","Fawnia","Fay","Faydra","Faye","Fayette","Fayina","Fayre","Fayth","Faythe","Federica","Fedora","Felecia","Felicdad","Felice","Felicia","Felicity","Felicle","Felipa","Felisha","Felita","Feliza","Fenelia","Feodora","Ferdinanda","Ferdinande","Fern","Fernanda","Fernande","Fernandina","Ferne","Fey","Fiann","Fianna","Fidela","Fidelia","Fidelity","Fifi","Fifine","Filia","Filide","Filippa","Fina","Fiona","Fionna","Fionnula","Fiorenze","Fleur","Fleurette","Flo","Flor","Flora","Florance","Flore","Florella","Florence","Florencia","Florentia","Florenza","Florette","Flori","Floria","Florida","Florie","Florina","Florinda","Floris","Florri","Florrie","Florry","Flory","Flossi","Flossie","Flossy","Flss","Fran","Francene","Frances","Francesca","Francine","Francisca","Franciska","Francoise","Francyne","Frank","Frankie","Franky","Franni","Frannie","Franny","Frayda","Fred","Freda","Freddi","Freddie","Freddy","Fredelia","Frederica","Fredericka","Frederique","Fredi","Fredia","Fredra","Fredrika","Freida","Frieda","Friederike","Fulvia","Gabbey","Gabbi","Gabbie","Gabey","Gabi","Gabie","Gabriel","Gabriela","Gabriell","Gabriella","Gabrielle","Gabriellia","Gabrila","Gaby","Gae","Gael","Gail","Gale","Galina","Garland","Garnet","Garnette","Gates","Gavra","Gavrielle","Gay","Gaye","Gayel","Gayla","Gayle","Gayleen","Gaylene","Gaynor","Gelya","Gena","Gene","Geneva","Genevieve","Genevra","Genia","Genna","Genni","Gennie","Gennifer","Genny","Genovera","Genvieve","George","Georgeanna","Georgeanne","Georgena","Georgeta","Georgetta","Georgette","Georgia","Georgiana","Georgianna","Georgianne","Georgie","Georgina","Georgine","Geralda","Geraldine","Gerda","Gerhardine","Geri","Gerianna","Gerianne","Gerladina","Germain","Germaine","Germana","Gerri","Gerrie","Gerrilee","Gerry","Gert","Gerta","Gerti","Gertie","Gertrud","Gertruda","Gertrude","Gertrudis","Gerty","Giacinta","Giana","Gianina","Gianna","Gigi","Gilberta","Gilberte","Gilbertina","Gilbertine","Gilda","Gilemette","Gill","Gillan","Gilli","Gillian","Gillie","Gilligan","Gilly","Gina","Ginelle","Ginevra","Ginger","Ginni","Ginnie","Ginnifer","Ginny","Giorgia","Giovanna","Gipsy","Giralda","Gisela","Gisele","Gisella","Giselle","Giuditta","Giulia","Giulietta","Giustina","Gizela","Glad","Gladi","Gladys","Gleda","Glen","Glenda","Glenine","Glenn","Glenna","Glennie","Glennis","Glori","Gloria","Gloriana","Gloriane","Glory","Glyn","Glynda","Glynis","Glynnis","Gnni","Godiva","Golda","Goldarina","Goldi","Goldia","Goldie","Goldina","Goldy","Grace","Gracia","Gracie","Grata","Gratia","Gratiana","Gray","Grayce","Grazia","Greer","Greta","Gretal","Gretchen","Grete","Gretel","Grethel","Gretna","Gretta","Grier","Griselda","Grissel","Guendolen","Guenevere","Guenna","Guglielma","Gui","Guillema","Guillemette","Guinevere","Guinna","Gunilla","Gus","Gusella","Gussi","Gussie","Gussy","Gusta","Gusti","Gustie","Gusty","Gwen","Gwendolen","Gwendolin","Gwendolyn","Gweneth","Gwenette","Gwenneth","Gwenni","Gwennie","Gwenny","Gwenora","Gwenore","Gwyn","Gwyneth","Gwynne","Gypsy","Hadria","Hailee","Haily","Haleigh","Halette","Haley","Hali","Halie","Halimeda","Halley","Halli","Hallie","Hally","Hana","Hanna","Hannah","Hanni","Hannie","Hannis","Hanny","Happy","Harlene","Harley","Harli","Harlie","Harmonia","Harmonie","Harmony","Harri","Harrie","Harriet","Harriett","Harrietta","Harriette","Harriot","Harriott","Hatti","Hattie","Hatty","Hayley","Hazel","Heath","Heather","Heda","Hedda","Heddi","Heddie","Hedi","Hedvig","Hedvige","Hedwig","Hedwiga","Hedy","Heida","Heidi","Heidie","Helaina","Helaine","Helen","Helen-elizabeth","Helena","Helene","Helenka","Helga","Helge","Helli","Heloise","Helsa","Helyn","Hendrika","Henka","Henrie","Henrieta","Henrietta","Henriette","Henryetta","Hephzibah","Hermia","Hermina","Hermine","Herminia","Hermione","Herta","Hertha","Hester","Hesther","Hestia","Hetti","Hettie","Hetty","Hilary","Hilda","Hildagard","Hildagarde","Hilde","Hildegaard","Hildegarde","Hildy","Hillary","Hilliary","Hinda","Holli","Hollie","Holly","Holly-anne","Hollyanne","Honey","Honor","Honoria","Hope","Horatia","Hortense","Hortensia","Hulda","Hyacinth","Hyacintha","Hyacinthe","Hyacinthia","Hyacinthie","Hynda","Ianthe","Ibbie","Ibby","Ida","Idalia","Idalina","Idaline","Idell","Idelle","Idette","Ileana","Ileane","Ilene","Ilise","Ilka","Illa","Ilsa","Ilse","Ilysa","Ilyse","Ilyssa","Imelda","Imogen","Imogene","Imojean","Ina","Indira","Ines","Inesita","Inessa","Inez","Inga","Ingaberg","Ingaborg","Inge","Ingeberg","Ingeborg","Inger","Ingrid","Ingunna","Inna","Iolande","Iolanthe","Iona","Iormina","Ira","Irena","Irene","Irina","Iris","Irita","Irma","Isa","Isabel","Isabelita","Isabella","Isabelle","Isadora","Isahella","Iseabal","Isidora","Isis","Isobel","Issi","Issie","Issy","Ivett","Ivette","Ivie","Ivonne","Ivory","Ivy","Izabel","Jacenta","Jacinda","Jacinta","Jacintha","Jacinthe","Jackelyn","Jacki","Jackie","Jacklin","Jacklyn","Jackquelin","Jackqueline","Jacky","Jaclin","Jaclyn","Jacquelin","Jacqueline","Jacquelyn","Jacquelynn","Jacquenetta","Jacquenette","Jacquetta","Jacquette","Jacqui","Jacquie","Jacynth","Jada","Jade","Jaime","Jaimie","Jaine","Jami","Jamie","Jamima","Jammie","Jan","Jana","Janaya","Janaye","Jandy","Jane","Janean","Janeczka","Janeen","Janel","Janela","Janella","Janelle","Janene","Janenna","Janessa","Janet","Janeta","Janetta","Janette","Janeva","Janey","Jania","Janice","Janie","Janifer","Janina","Janine","Janis","Janith","Janka","Janna","Jannel","Jannelle","Janot","Jany","Jaquelin","Jaquelyn","Jaquenetta","Jaquenette","Jaquith","Jasmin","Jasmina","Jasmine","Jayme","Jaymee","Jayne","Jaynell","Jazmin","Jean","Jeana","Jeane","Jeanelle","Jeanette","Jeanie","Jeanine","Jeanna","Jeanne","Jeannette","Jeannie","Jeannine","Jehanna","Jelene","Jemie","Jemima","Jemimah","Jemmie","Jemmy","Jen","Jena","Jenda","Jenelle","Jeni","Jenica","Jeniece","Jenifer","Jeniffer","Jenilee","Jenine","Jenn","Jenna","Jennee","Jennette","Jenni","Jennica","Jennie","Jennifer","Jennilee","Jennine","Jenny","Jeralee","Jere","Jeri","Jermaine","Jerrie","Jerrilee","Jerrilyn","Jerrine","Jerry","Jerrylee","Jess","Jessa","Jessalin","Jessalyn","Jessamine","Jessamyn","Jesse","Jesselyn","Jessi","Jessica","Jessie","Jessika","Jessy","Jewel","Jewell","Jewelle","Jill","Jillana","Jillane","Jillayne","Jilleen","Jillene","Jilli","Jillian","Jillie","Jilly","Jinny","Jo","Jo-ann","Jo-anne","Joan","Joana","Joane","Joanie","Joann","Joanna","Joanne","Joannes","Jobey","Jobi","Jobie","Jobina","Joby","Jobye","Jobyna","Jocelin","Joceline","Jocelyn","Jocelyne","Jodee","Jodi","Jodie","Jody","Joeann","Joela","Joelie","Joell","Joella","Joelle","Joellen","Joelly","Joellyn","Joelynn","Joete","Joey","Johanna","Johannah","Johna","Johnath","Johnette","Johnna","Joice","Jojo","Jolee","Joleen","Jolene","Joletta","Joli","Jolie","Joline","Joly","Jolyn","Jolynn","Jonell","Joni","Jonie","Jonis","Jordain","Jordan","Jordana","Jordanna","Jorey","Jori","Jorie","Jorrie","Jorry","Joscelin","Josee","Josefa","Josefina","Josepha","Josephina","Josephine","Josey","Josi","Josie","Josselyn","Josy","Jourdan","Joy","Joya","Joyan","Joyann","Joyce","Joycelin","Joye","Jsandye","Juana","Juanita","Judi","Judie","Judith","Juditha","Judy","Judye","Juieta","Julee","Juli","Julia","Juliana","Juliane","Juliann","Julianna","Julianne","Julie","Julienne","Juliet","Julieta","Julietta","Juliette","Julina","Juline","Julissa","Julita","June","Junette","Junia","Junie","Junina","Justina","Justine","Justinn","Jyoti","Kacey","Kacie","Kacy","Kaela","Kai","Kaia","Kaila","Kaile","Kailey","Kaitlin","Kaitlyn","Kaitlynn","Kaja","Kakalina","Kala","Kaleena","Kali","Kalie","Kalila","Kalina","Kalinda","Kalindi","Kalli","Kally","Kameko","Kamila","Kamilah","Kamillah","Kandace","Kandy","Kania","Kanya","Kara","Kara-lynn","Karalee","Karalynn","Kare","Karee","Karel","Karen","Karena","Kari","Karia","Karie","Karil","Karilynn","Karin","Karina","Karine","Kariotta","Karisa","Karissa","Karita","Karla","Karlee","Karleen","Karlen","Karlene","Karlie","Karlotta","Karlotte","Karly","Karlyn","Karmen","Karna","Karol","Karola","Karole","Karolina","Karoline","Karoly","Karon","Karrah","Karrie","Karry","Kary","Karyl","Karylin","Karyn","Kasey","Kass","Kassandra","Kassey","Kassi","Kassia","Kassie","Kat","Kata","Katalin","Kate","Katee","Katerina","Katerine","Katey","Kath","Katha","Katharina","Katharine","Katharyn","Kathe","Katherina","Katherine","Katheryn","Kathi","Kathie","Kathleen","Kathlin","Kathrine","Kathryn","Kathryne","Kathy","Kathye","Kati","Katie","Katina","Katine","Katinka","Katleen","Katlin","Katrina","Katrine","Katrinka","Katti","Kattie","Katuscha","Katusha","Katy","Katya","Kay","Kaycee","Kaye","Kayla","Kayle","Kaylee","Kayley","Kaylil","Kaylyn","Keeley","Keelia","Keely","Kelcey","Kelci","Kelcie","Kelcy","Kelila","Kellen","Kelley","Kelli","Kellia","Kellie","Kellina","Kellsie","Kelly","Kellyann","Kelsey","Kelsi","Kelsy","Kendra","Kendre","Kenna","Keri","Keriann","Kerianne","Kerri","Kerrie","Kerrill","Kerrin","Kerry","Kerstin","Kesley","Keslie","Kessia","Kessiah","Ketti","Kettie","Ketty","Kevina","Kevyn","Ki","Kiah","Kial","Kiele","Kiersten","Kikelia","Kiley","Kim","Kimberlee","Kimberley","Kimberli","Kimberly","Kimberlyn","Kimbra","Kimmi","Kimmie","Kimmy","Kinna","Kip","Kipp","Kippie","Kippy","Kira","Kirbee","Kirbie","Kirby","Kiri","Kirsten","Kirsteni","Kirsti","Kirstin","Kirstyn","Kissee","Kissiah","Kissie","Kit","Kitti","Kittie","Kitty","Kizzee","Kizzie","Klara","Klarika","Klarrisa","Konstance","Konstanze","Koo","Kora","Koral","Koralle","Kordula","Kore","Korella","Koren","Koressa","Kori","Korie","Korney","Korrie","Korry","Kris","Krissie","Krissy","Krista","Kristal","Kristan","Kriste","Kristel","Kristen","Kristi","Kristien","Kristin","Kristina","Kristine","Kristy","Kristyn","Krysta","Krystal","Krystalle","Krystle","Krystyna","Kyla","Kyle","Kylen","Kylie","Kylila","Kylynn","Kym","Kynthia","Kyrstin","Lacee","Lacey","Lacie","Lacy","Ladonna","Laetitia","Laina","Lainey","Lana","Lanae","Lane","Lanette","Laney","Lani","Lanie","Lanita","Lanna","Lanni","Lanny","Lara","Laraine","Lari","Larina","Larine","Larisa","Larissa","Lark","Laryssa","Latashia","Latia","Latisha","Latrena","Latrina","Laura","Lauraine","Laural","Lauralee","Laure","Lauree","Laureen","Laurel","Laurella","Lauren","Laurena","Laurene","Lauretta","Laurette","Lauri","Laurianne","Laurice","Laurie","Lauryn","Lavena","Laverna","Laverne","Lavina","Lavinia","Lavinie","Layla","Layne","Layney","Lea","Leah","Leandra","Leann","Leanna","Leanor","Leanora","Lebbie","Leda","Lee","Leeann","Leeanne","Leela","Leelah","Leena","Leesa","Leese","Legra","Leia","Leigh","Leigha","Leila","Leilah","Leisha","Lela","Lelah","Leland","Lelia","Lena","Lenee","Lenette","Lenka","Lenna","Lenora","Lenore","Leodora","Leoine","Leola","Leoline","Leona","Leonanie","Leone","Leonelle","Leonie","Leonora","Leonore","Leontine","Leontyne","Leora","Leshia","Lesley","Lesli","Leslie","Lesly","Lesya","Leta","Lethia","Leticia","Letisha","Letitia","Letizia","Letta","Letti","Lettie","Letty","Lexi","Lexie","Lexine","Lexis","Lexy","Leyla","Lezlie","Lia","Lian","Liana","Liane","Lianna","Lianne","Lib","Libbey","Libbi","Libbie","Libby","Licha","Lida","Lidia","Liesa","Lil","Lila","Lilah","Lilas","Lilia","Lilian","Liliane","Lilias","Lilith","Lilla","Lilli","Lillian","Lillis","Lilllie","Lilly","Lily","Lilyan","Lin","Lina","Lind","Linda","Lindi","Lindie","Lindsay","Lindsey","Lindsy","Lindy","Linea","Linell","Linet","Linette","Linn","Linnea","Linnell","Linnet","Linnie","Linzy","Lira","Lisa","Lisabeth","Lisbeth","Lise","Lisetta","Lisette","Lisha","Lishe","Lissa","Lissi","Lissie","Lissy","Lita","Liuka","Liv","Liva","Livia","Livvie","Livvy","Livvyy","Livy","Liz","Liza","Lizabeth","Lizbeth","Lizette","Lizzie","Lizzy","Loella","Lois","Loise","Lola","Loleta","Lolita","Lolly","Lona","Lonee","Loni","Lonna","Lonni","Lonnie","Lora","Lorain","Loraine","Loralee","Loralie","Loralyn","Loree","Loreen","Lorelei","Lorelle","Loren","Lorena","Lorene","Lorenza","Loretta","Lorette","Lori","Loria","Lorianna","Lorianne","Lorie","Lorilee","Lorilyn","Lorinda","Lorine","Lorita","Lorna","Lorne","Lorraine","Lorrayne","Lorri","Lorrie","Lorrin","Lorry","Lory","Lotta","Lotte","Lotti","Lottie","Lotty","Lou","Louella","Louisa","Louise","Louisette","Loutitia","Lu","Luce","Luci","Lucia","Luciana","Lucie","Lucienne","Lucila","Lucilia","Lucille","Lucina","Lucinda","Lucine","Lucita","Lucky","Lucretia","Lucy","Ludovika","Luella","Luelle","Luisa","Luise","Lula","Lulita","Lulu","Lura","Lurette","Lurleen","Lurlene","Lurline","Lusa","Luz","Lyda","Lydia","Lydie","Lyn","Lynda","Lynde","Lyndel","Lyndell","Lyndsay","Lyndsey","Lyndsie","Lyndy","Lynea","Lynelle","Lynett","Lynette","Lynn","Lynna","Lynne","Lynnea","Lynnell","Lynnelle","Lynnet","Lynnett","Lynnette","Lynsey","Lyssa","Mab","Mabel","Mabelle","Mable","Mada","Madalena","Madalyn","Maddalena","Maddi","Maddie","Maddy","Madel","Madelaine","Madeleine","Madelena","Madelene","Madelin","Madelina","Madeline","Madella","Madelle","Madelon","Madelyn","Madge","Madlen","Madlin","Madonna","Mady","Mae","Maegan","Mag","Magda","Magdaia","Magdalen","Magdalena","Magdalene","Maggee","Maggi","Maggie","Maggy","Mahala","Mahalia","Maia","Maible","Maiga","Maighdiln","Mair","Maire","Maisey","Maisie","Maitilde","Mala","Malanie","Malena","Malia","Malina","Malinda","Malinde","Malissa","Malissia","Mallissa","Mallorie","Mallory","Malorie","Malory","Malva","Malvina","Malynda","Mame","Mamie","Manda","Mandi","Mandie","Mandy","Manon","Manya","Mara","Marabel","Marcela","Marcelia","Marcella","Marcelle","Marcellina","Marcelline","Marchelle","Marci","Marcia","Marcie","Marcile","Marcille","Marcy","Mareah","Maren","Marena","Maressa","Marga","Margalit","Margalo","Margaret","Margareta","Margarete","Margaretha","Margarethe","Margaretta","Margarette","Margarita","Margaux","Marge","Margeaux","Margery","Marget","Margette","Margi","Margie","Margit","Margo","Margot","Margret","Marguerite","Margy","Mari","Maria","Mariam","Marian","Mariana","Mariann","Marianna","Marianne","Maribel","Maribelle","Maribeth","Marice","Maridel","Marie","Marie-ann","Marie-jeanne","Marieann","Mariejeanne","Mariel","Mariele","Marielle","Mariellen","Marietta","Mariette","Marigold","Marijo","Marika","Marilee","Marilin","Marillin","Marilyn","Marin","Marina","Marinna","Marion","Mariquilla","Maris","Marisa","Mariska","Marissa","Marita","Maritsa","Mariya","Marj","Marja","Marje","Marji","Marjie","Marjorie","Marjory","Marjy","Marketa","Marla","Marlane","Marleah","Marlee","Marleen","Marlena","Marlene","Marley","Marlie","Marline","Marlo","Marlyn","Marna","Marne","Marney","Marni","Marnia","Marnie","Marquita","Marrilee","Marris","Marrissa","Marsha","Marsiella","Marta","Martelle","Martguerita","Martha","Marthe","Marthena","Marti","Martica","Martie","Martina","Martita","Marty","Martynne","Mary","Marya","Maryann","Maryanna","Maryanne","Marybelle","Marybeth","Maryellen","Maryjane","Maryjo","Maryl","Marylee","Marylin","Marylinda","Marylou","Marylynne","Maryrose","Marys","Marysa","Masha","Matelda","Mathilda","Mathilde","Matilda","Matilde","Matti","Mattie","Matty","Maud","Maude","Maudie","Maura","Maure","Maureen","Maureene","Maurene","Maurine","Maurise","Maurita","Maurizia","Mavis","Mavra","Max","Maxi","Maxie","Maxine","Maxy","May","Maybelle","Maye","Mead","Meade","Meagan","Meaghan","Meara","Mechelle","Meg","Megan","Megen","Meggi","Meggie","Meggy","Meghan","Meghann","Mehetabel","Mei","Mel","Mela","Melamie","Melania","Melanie","Melantha","Melany","Melba","Melesa","Melessa","Melicent","Melina","Melinda","Melinde","Melisa","Melisande","Melisandra","Melisenda","Melisent","Melissa","Melisse","Melita","Melitta","Mella","Melli","Mellicent","Mellie","Mellisa","Mellisent","Melloney","Melly","Melodee","Melodie","Melody","Melonie","Melony","Melosa","Melva","Mercedes","Merci","Mercie","Mercy","Meredith","Meredithe","Meridel","Meridith","Meriel","Merilee","Merilyn","Meris","Merissa","Merl","Merla","Merle","Merlina","Merline","Merna","Merola","Merralee","Merridie","Merrie","Merrielle","Merrile","Merrilee","Merrili","Merrill","Merrily","Merry","Mersey","Meryl","Meta","Mia","Micaela","Michaela","Michaelina","Michaeline","Michaella","Michal","Michel","Michele","Michelina","Micheline","Michell","Michelle","Micki","Mickie","Micky","Midge","Mignon","Mignonne","Miguela","Miguelita","Mikaela","Mil","Mildred","Mildrid","Milena","Milicent","Milissent","Milka","Milli","Millicent","Millie","Millisent","Milly","Milzie","Mimi","Min","Mina","Minda","Mindy","Minerva","Minetta","Minette","Minna","Minnaminnie","Minne","Minni","Minnie","Minnnie","Minny","Minta","Miquela","Mira","Mirabel","Mirabella","Mirabelle","Miran","Miranda","Mireielle","Mireille","Mirella","Mirelle","Miriam","Mirilla","Mirna","Misha","Missie","Missy","Misti","Misty","Mitzi","Modesta","Modestia","Modestine","Modesty","Moina","Moira","Moll","Mollee","Molli","Mollie","Molly","Mommy","Mona","Monah","Monica","Monika","Monique","Mora","Moreen","Morena","Morgan","Morgana","Morganica","Morganne","Morgen","Moria","Morissa","Morna","Moselle","Moyna","Moyra","Mozelle","Muffin","Mufi","Mufinella","Muire","Mureil","Murial","Muriel","Murielle","Myra","Myrah","Myranda","Myriam","Myrilla","Myrle","Myrlene","Myrna","Myrta","Myrtia","Myrtice","Myrtie","Myrtle","Nada","Nadean","Nadeen","Nadia","Nadine","Nadiya","Nady","Nadya","Nalani","Nan","Nana","Nananne","Nance","Nancee","Nancey","Nanci","Nancie","Nancy","Nanete","Nanette","Nani","Nanice","Nanine","Nannette","Nanni","Nannie","Nanny","Nanon","Naoma","Naomi","Nara","Nari","Nariko","Nat","Nata","Natala","Natalee","Natalie","Natalina","Nataline","Natalya","Natasha","Natassia","Nathalia","Nathalie","Natividad","Natka","Natty","Neala","Neda","Nedda","Nedi","Neely","Neila","Neile","Neilla","Neille","Nelia","Nelie","Nell","Nelle","Nelli","Nellie","Nelly","Nerissa","Nerita","Nert","Nerta","Nerte","Nerti","Nertie","Nerty","Nessa","Nessi","Nessie","Nessy","Nesta","Netta","Netti","Nettie","Nettle","Netty","Nevsa","Neysa","Nichol","Nichole","Nicholle","Nicki","Nickie","Nicky","Nicol","Nicola","Nicole","Nicolea","Nicolette","Nicoli","Nicolina","Nicoline","Nicolle","Nikaniki","Nike","Niki","Nikki","Nikkie","Nikoletta","Nikolia","Nina","Ninetta","Ninette","Ninnetta","Ninnette","Ninon","Nissa","Nisse","Nissie","Nissy","Nita","Nixie","Noami","Noel","Noelani","Noell","Noella","Noelle","Noellyn","Noelyn","Noemi","Nola","Nolana","Nolie","Nollie","Nomi","Nona","Nonah","Noni","Nonie","Nonna","Nonnah","Nora","Norah","Norean","Noreen","Norene","Norina","Norine","Norma","Norri","Norrie","Norry","Novelia","Nydia","Nyssa","Octavia","Odele","Odelia","Odelinda","Odella","Odelle","Odessa","Odetta","Odette","Odilia","Odille","Ofelia","Ofella","Ofilia","Ola","Olenka","Olga","Olia","Olimpia","Olive","Olivette","Olivia","Olivie","Oliy","Ollie","Olly","Olva","Olwen","Olympe","Olympia","Olympie","Ondrea","Oneida","Onida","Oona","Opal","Opalina","Opaline","Ophelia","Ophelie","Ora","Oralee","Oralia","Oralie","Oralla","Oralle","Orel","Orelee","Orelia","Orelie","Orella","Orelle","Oriana","Orly","Orsa","Orsola","Ortensia","Otha","Othelia","Othella","Othilia","Othilie","Ottilie","Page","Paige","Paloma","Pam","Pamela","Pamelina","Pamella","Pammi","Pammie","Pammy","Pandora","Pansie","Pansy","Paola","Paolina","Papagena","Pat","Patience","Patrica","Patrice","Patricia","Patrizia","Patsy","Patti","Pattie","Patty","Paula","Paule","Pauletta","Paulette","Pauli","Paulie","Paulina","Pauline","Paulita","Pauly","Pavia","Pavla","Pearl","Pearla","Pearle","Pearline","Peg","Pegeen","Peggi","Peggie","Peggy","Pen","Penelopa","Penelope","Penni","Pennie","Penny","Pepi","Pepita","Peri","Peria","Perl","Perla","Perle","Perri","Perrine","Perry","Persis","Pet","Peta","Petra","Petrina","Petronella","Petronia","Petronilla","Petronille","Petunia","Phaedra","Phaidra","Phebe","Phedra","Phelia","Phil","Philipa","Philippa","Philippe","Philippine","Philis","Phillida","Phillie","Phillis","Philly","Philomena","Phoebe","Phylis","Phyllida","Phyllis","Phyllys","Phylys","Pia","Pier","Pierette","Pierrette","Pietra","Piper","Pippa","Pippy","Polly","Pollyanna","Pooh","Poppy","Portia","Pris","Prisca","Priscella","Priscilla","Prissie","Pru","Prudence","Prudi","Prudy","Prue","Queenie","Quentin","Querida","Quinn","Quinta","Quintana","Quintilla","Quintina","Rachael","Rachel","Rachele","Rachelle","Rae","Raeann","Raf","Rafa","Rafaela","Rafaelia","Rafaelita","Rahal","Rahel","Raina","Raine","Rakel","Ralina","Ramona","Ramonda","Rana","Randa","Randee","Randene","Randi","Randie","Randy","Ranee","Rani","Rania","Ranice","Ranique","Ranna","Raphaela","Raquel","Raquela","Rasia","Rasla","Raven","Ray","Raychel","Raye","Rayna","Raynell","Rayshell","Rea","Reba","Rebbecca","Rebe","Rebeca","Rebecca","Rebecka","Rebeka","Rebekah","Rebekkah","Ree","Reeba","Reena","Reeta","Reeva","Regan","Reggi","Reggie","Regina","Regine","Reiko","Reina","Reine","Remy","Rena","Renae","Renata","Renate","Rene","Renee","Renell","Renelle","Renie","Rennie","Reta","Retha","Revkah","Rey","Reyna","Rhea","Rheba","Rheta","Rhetta","Rhiamon","Rhianna","Rhianon","Rhoda","Rhodia","Rhodie","Rhody","Rhona","Rhonda","Riane","Riannon","Rianon","Rica","Ricca","Rici","Ricki","Rickie","Ricky","Riki","Rikki","Rina","Risa","Rita","Riva","Rivalee","Rivi","Rivkah","Rivy","Roana","Roanna","Roanne","Robbi","Robbie","Robbin","Robby","Robbyn","Robena","Robenia","Roberta","Robin","Robina","Robinet","Robinett","Robinetta","Robinette","Robinia","Roby","Robyn","Roch","Rochell","Rochella","Rochelle","Rochette","Roda","Rodi","Rodie","Rodina","Rois","Romola","Romona","Romonda","Romy","Rona","Ronalda","Ronda","Ronica","Ronna","Ronni","Ronnica","Ronnie","Ronny","Roobbie","Rora","Rori","Rorie","Rory","Ros","Rosa","Rosabel","Rosabella","Rosabelle","Rosaleen","Rosalia","Rosalie","Rosalind","Rosalinda","Rosalinde","Rosaline","Rosalyn","Rosalynd","Rosamond","Rosamund","Rosana","Rosanna","Rosanne","Rose","Roseann","Roseanna","Roseanne","Roselia","Roselin","Roseline","Rosella","Roselle","Rosemaria","Rosemarie","Rosemary","Rosemonde","Rosene","Rosetta","Rosette","Roshelle","Rosie","Rosina","Rosita","Roslyn","Rosmunda","Rosy","Row","Rowe","Rowena","Roxana","Roxane","Roxanna","Roxanne","Roxi","Roxie","Roxine","Roxy","Roz","Rozalie","Rozalin","Rozamond","Rozanna","Rozanne","Roze","Rozele","Rozella","Rozelle","Rozina","Rubetta","Rubi","Rubia","Rubie","Rubina","Ruby","Ruperta","Ruth","Ruthann","Ruthanne","Ruthe","Ruthi","Ruthie","Ruthy","Ryann","Rycca","Saba","Sabina","Sabine","Sabra","Sabrina","Sacha","Sada","Sadella","Sadie","Sadye","Saidee","Sal","Salaidh","Sallee","Salli","Sallie","Sally","Sallyann","Sallyanne","Saloma","Salome","Salomi","Sam","Samantha","Samara","Samaria","Sammy","Sande","Sandi","Sandie","Sandra","Sandy","Sandye","Sapphira","Sapphire","Sara","Sara-ann","Saraann","Sarah","Sarajane","Saree","Sarena","Sarene","Sarette","Sari","Sarina","Sarine","Sarita","Sascha","Sasha","Sashenka","Saudra","Saundra","Savina","Sayre","Scarlet","Scarlett","Sean","Seana","Seka","Sela","Selena","Selene","Selestina","Selia","Selie","Selina","Selinda","Seline","Sella","Selle","Selma","Sena","Sephira","Serena","Serene","Shae","Shaina","Shaine","Shalna","Shalne","Shana","Shanda","Shandee","Shandeigh","Shandie","Shandra","Shandy","Shane","Shani","Shanie","Shanna","Shannah","Shannen","Shannon","Shanon","Shanta","Shantee","Shara","Sharai","Shari","Sharia","Sharity","Sharl","Sharla","Sharleen","Sharlene","Sharline","Sharon","Sharona","Sharron","Sharyl","Shaun","Shauna","Shawn","Shawna","Shawnee","Shay","Shayla","Shaylah","Shaylyn","Shaylynn","Shayna","Shayne","Shea","Sheba","Sheela","Sheelagh","Sheelah","Sheena","Sheeree","Sheila","Sheila-kathryn","Sheilah","Shel","Shela","Shelagh","Shelba","Shelbi","Shelby","Shelia","Shell","Shelley","Shelli","Shellie","Shelly","Shena","Sher","Sheree","Sheri","Sherie","Sherill","Sherilyn","Sherline","Sherri","Sherrie","Sherry","Sherye","Sheryl","Shina","Shir","Shirl","Shirlee","Shirleen","Shirlene","Shirley","Shirline","Shoshana","Shoshanna","Siana","Sianna","Sib","Sibbie","Sibby","Sibeal","Sibel","Sibella","Sibelle","Sibilla","Sibley","Sibyl","Sibylla","Sibylle","Sidoney","Sidonia","Sidonnie","Sigrid","Sile","Sileas","Silva","Silvana","Silvia","Silvie","Simona","Simone","Simonette","Simonne","Sindee","Siobhan","Sioux","Siouxie","Sisely","Sisile","Sissie","Sissy","Siusan","Sofia","Sofie","Sondra","Sonia","Sonja","Sonni","Sonnie","Sonnnie","Sonny","Sonya","Sophey","Sophi","Sophia","Sophie","Sophronia","Sorcha","Sosanna","Stace","Stacee","Stacey","Staci","Stacia","Stacie","Stacy","Stafani","Star","Starla","Starlene","Starlin","Starr","Stefa","Stefania","Stefanie","Steffane","Steffi","Steffie","Stella","Stepha","Stephana","Stephani","Stephanie","Stephannie","Stephenie","Stephi","Stephie","Stephine","Stesha","Stevana","Stevena","Stoddard","Storm","Stormi","Stormie","Stormy","Sue","Suellen","Sukey","Suki","Sula","Sunny","Sunshine","Susan","Susana","Susanetta","Susann","Susanna","Susannah","Susanne","Susette","Susi","Susie","Susy","Suzann","Suzanna","Suzanne","Suzette","Suzi","Suzie","Suzy","Sybil","Sybila","Sybilla","Sybille","Sybyl","Sydel","Sydelle","Sydney","Sylvia","Tabatha","Tabbatha","Tabbi","Tabbie","Tabbitha","Tabby","Tabina","Tabitha","Taffy","Talia","Tallia","Tallie","Tallou","Tallulah","Tally","Talya","Talyah","Tamar","Tamara","Tamarah","Tamarra","Tamera","Tami","Tamiko","Tamma","Tammara","Tammi","Tammie","Tammy","Tamqrah","Tamra","Tana","Tandi","Tandie","Tandy","Tanhya","Tani","Tania","Tanitansy","Tansy","Tanya","Tara","Tarah","Tarra","Tarrah","Taryn","Tasha","Tasia","Tate","Tatiana","Tatiania","Tatum","Tawnya","Tawsha","Ted","Tedda","Teddi","Teddie","Teddy","Tedi","Tedra","Teena","Teirtza","Teodora","Tera","Teresa","Terese","Teresina","Teresita","Teressa","Teri","Teriann","Terra","Terri","Terrie","Terrijo","Terry","Terrye","Tersina","Terza","Tess","Tessa","Tessi","Tessie","Tessy","Thalia","Thea","Theadora","Theda","Thekla","Thelma","Theo","Theodora","Theodosia","Theresa","Therese","Theresina","Theresita","Theressa","Therine","Thia","Thomasa","Thomasin","Thomasina","Thomasine","Tiena","Tierney","Tiertza","Tiff","Tiffani","Tiffanie","Tiffany","Tiffi","Tiffie","Tiffy","Tilda","Tildi","Tildie","Tildy","Tillie","Tilly","Tim","Timi","Timmi","Timmie","Timmy","Timothea","Tina","Tine","Tiphani","Tiphanie","Tiphany","Tish","Tisha","Tobe","Tobey","Tobi","Toby","Tobye","Toinette","Toma","Tomasina","Tomasine","Tomi","Tommi","Tommie","Tommy","Toni","Tonia","Tonie","Tony","Tonya","Tonye","Tootsie","Torey","Tori","Torie","Torrie","Tory","Tova","Tove","Tracee","Tracey","Traci","Tracie","Tracy","Trenna","Tresa","Trescha","Tressa","Tricia","Trina","Trish","Trisha","Trista","Trix","Trixi","Trixie","Trixy","Truda","Trude","Trudey","Trudi","Trudie","Trudy","Trula","Tuesday","Twila","Twyla","Tybi","Tybie","Tyne","Ula","Ulla","Ulrica","Ulrika","Ulrikaumeko","Ulrike","Umeko","Una","Ursa","Ursala","Ursola","Ursula","Ursulina","Ursuline","Uta","Val","Valaree","Valaria","Vale","Valeda","Valencia","Valene","Valenka","Valentia","Valentina","Valentine","Valera","Valeria","Valerie","Valery","Valerye","Valida","Valina","Valli","Vallie","Vally","Valma","Valry","Van","Vanda","Vanessa","Vania","Vanna","Vanni","Vannie","Vanny","Vanya","Veda","Velma","Velvet","Venita","Venus","Vera","Veradis","Vere","Verena","Verene","Veriee","Verile","Verina","Verine","Verla","Verna","Vernice","Veronica","Veronika","Veronike","Veronique","Vevay","Vi","Vicki","Vickie","Vicky","Victoria","Vida","Viki","Vikki","Vikky","Vilhelmina","Vilma","Vin","Vina","Vinita","Vinni","Vinnie","Vinny","Viola","Violante","Viole","Violet","Violetta","Violette","Virgie","Virgina","Virginia","Virginie","Vita","Vitia","Vitoria","Vittoria","Viv","Viva","Vivi","Vivia","Vivian","Viviana","Vivianna","Vivianne","Vivie","Vivien","Viviene","Vivienne","Viviyan","Vivyan","Vivyanne","Vonni","Vonnie","Vonny","Vyky","Wallie","Wallis","Walliw","Wally","Waly","Wanda","Wandie","Wandis","Waneta","Wanids","Wenda","Wendeline","Wendi","Wendie","Wendy","Wendye","Wenona","Wenonah","Whitney","Wileen","Wilhelmina","Wilhelmine","Wilie","Willa","Willabella","Willamina","Willetta","Willette","Willi","Willie","Willow","Willy","Willyt","Wilma","Wilmette","Wilona","Wilone","Wilow","Windy","Wini","Winifred","Winna","Winnah","Winne","Winni","Winnie","Winnifred","Winny","Winona","Winonah","Wren","Wrennie","Wylma","Wynn","Wynne","Wynnie","Wynny","Xaviera","Xena","Xenia","Xylia","Xylina","Yalonda","Yasmeen","Yasmin","Yelena","Yetta","Yettie","Yetty","Yevette","Ynes","Ynez","Yoko","Yolanda","Yolande","Yolane","Yolanthe","Yoshi","Yoshiko","Yovonnda","Ysabel","Yvette","Yvonne","Zabrina","Zahara","Zandra","Zaneta","Zara","Zarah","Zaria","Zarla","Zea","Zelda","Zelma","Zena","Zenia","Zia","Zilvia","Zita","Zitella","Zoe","Zola","Zonda","Zondra","Zonnya","Zora","Zorah","Zorana","Zorina","Zorine","Zsazsa","Zulema","Zuzana"],a.starWars=["Ackbar","Adi Gallia","Anakin Skywalker","Arvel Crynyd","Ayla Secura","Bail Prestor Organa","Barriss Offee","Ben Quadinaros","Beru Whitesun lars","Bib Fortuna","Biggs Darklighter","Boba Fett","Bossk","C-3PO","Chewbacca","Cliegg Lars","Cordé","Darth Maul","Darth Vader","Dexter Jettster","Dooku","Dormé","Dud Bolt","Eeth Koth","Finis Valorum","Gasgano","Greedo","Gregar Typho","Grievous","Han Solo","IG-88","Jabba Desilijic Tiure","Jango Fett","Jar Jar Binks","Jek Tono Porkins","Jocasta Nu","Ki-Adi-Mundi","Kit Fisto","Lama Su","Lando Calrissian","Leia Organa","Lobot","Luke Skywalker","Luminara Unduli","Mace Windu","Mas Amedda","Mon Mothma","Nien Nunb","Nute Gunray","Obi-Wan Kenobi","Owen Lars","Padmé Amidala","Palpatine","Plo Koon","Poggle the Lesser","Quarsh Panaka","Qui-Gon Jinn","R2-D2","R4-P17","R5-D4","Ratts Tyerel","Raymus Antilles","Ric Olié","Roos Tarpals","Rugor Nass","Saesee Tiin","San Hill","Sebulba","Shaak Ti","Shmi Skywalker","Sly Moore","Tarfful","Taun We","Tion Medon","Wat Tambor","Watto","Wedge Antilles","Wicket Systri Warrick","Wilhuff Tarkin","Yarael Poof","Yoda","Zam Wesell"],a.uniqueNamesGenerator=a=>{const n=[...a&&a.dictionaries||i.dictionaries],l={...i,...a,length:a&&a.length||n.length,dictionaries:n};if(!a||!a.dictionaries||!a.dictionaries.length)throw new Error('A "dictionaries" array must be provided. This is a breaking change introduced starting from Unique Name Generator v4. Read more about the breaking change here: https://github.com/andreasonny83/unique-names-generator#migration-guide');return new e(l).generate()}}); -//# sourceMappingURL=index.umd.js.map diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/seed.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/seed.d.ts deleted file mode 100644 index a4423da905..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/seed.d.ts +++ /dev/null @@ -1 +0,0 @@ -export declare const getFromSeed: (seed: number) => number; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/unique-names-generator.constructor.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/unique-names-generator.constructor.d.ts deleted file mode 100644 index a93b786381..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/unique-names-generator.constructor.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -declare type Style = 'lowerCase' | 'upperCase' | 'capital'; -export interface Config { - dictionaries: string[][]; - separator?: string; - length?: number; - style?: Style; - seed?: number; -} -export declare class UniqueNamesGenerator { - private dictionaries; - private length; - private separator; - private style; - private seed; - constructor(config: Config); - generate(): string; -} -export {}; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/unique-names-generator.d.ts b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/unique-names-generator.d.ts deleted file mode 100644 index b406bfb375..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/dist/unique-names-generator.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { Config } from './unique-names-generator.constructor'; -export declare const uniqueNamesGenerator: (customConfig: Config) => string; diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/package.json b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/package.json deleted file mode 100644 index 7011c0ae5c..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/node_modules/unique-names-generator/package.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "name": "unique-names-generator", - "version": "4.6.0", - "description": "Generate unique and memorable names", - "engines": { - "node": ">=8" - }, - "scripts": { - "lint": "eslint --ext .ts ./src", - "test": "npm run format && npm run lint && jest --coverage", - "test:watch": "jest --coverage --watchAll", - "format": "prettier --write src/**/*.ts", - "prebuild": "rimraf dist && npm run format", - "build": "microbundle", - "prepublishOnly": "npm run build", - "prerelease": "npm run build", - "release": "np" - }, - "sideEffects": false, - "main": "dist/index.js", - "umd:main": "dist/index.umd.js", - "module": "dist/index.m.js", - "source": "src/index.ts", - "types": "dist/index.d.ts", - "tags": [ - "name-generator", - "unique-names", - "typescript" - ], - "files": [ - "dist" - ], - "author": "AndreaSonny ", - "license": "MIT", - "private": false, - "repository": { - "type": "git", - "url": "git@github.com:andreasonny83/unique-names-generator.git" - }, - "devDependencies": { - "@babel/core": "^7.13.15", - "@babel/preset-env": "^7.13.15", - "@babel/preset-typescript": "^7.13.0", - "@types/jest": "^26.0.22", - "@typescript-eslint/eslint-plugin": "^4.21.0", - "@typescript-eslint/parser": "^4.21.0", - "all-contributors-cli": "^6.20.0", - "babel-jest": "^26.6.3", - "eslint": "^7.23.0", - "eslint-config-prettier": "^8.1.0", - "eslint-plugin-prettier": "^3.3.1", - "jest": "^26.6.3", - "microbundle": "^0.13.0", - "np": "^7.4.0", - "prettier": "^2.2.1", - "prettier-eslint": "^12.0.0", - "rimraf": "^3.0.2", - "typescript": "^4.2.4" - } -} diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/package-lock.json b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/package-lock.json deleted file mode 100644 index 0cc1b77c43..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/package-lock.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "hello_world", - "version": "1.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "hello_world", - "version": "1.0.0", - "dependencies": { - "unique-names-generator": "^4.6.0" - } - }, - "node_modules/unique-names-generator": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.6.0.tgz", - "integrity": "sha512-m0fke1emBeT96UYn2psPQYwljooDWRTKt9oUZ5vlt88ZFMBGxqwPyLHXwCfkbgdm8jzioCp7oIpo6KdM+fnUlQ==", - "engines": { - "node": ">=8" - } - } - }, - "dependencies": { - "unique-names-generator": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.6.0.tgz", - "integrity": "sha512-m0fke1emBeT96UYn2psPQYwljooDWRTKt9oUZ5vlt88ZFMBGxqwPyLHXwCfkbgdm8jzioCp7oIpo6KdM+fnUlQ==" - } - } -} diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/package.json b/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/package.json deleted file mode 100644 index 1dc652ae81..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/NodeJsFunctionConstruct/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "hello_world", - "version": "1.0.0", - "main": "app.js", - "dependencies": { - "unique-names-generator": "^4.6.0" - } -} diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/layers/LayerVersion/layer_version_dependency.js b/tests/iac_integration/cdk/testdata/src/nodejs/layers/LayerVersion/layer_version_dependency.js deleted file mode 100644 index dcb5e89bc4..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/layers/LayerVersion/layer_version_dependency.js +++ /dev/null @@ -1,9 +0,0 @@ -const unique_names_generator = require('unique-names-generator'); - -const characterName = unique_names_generator.uniqueNamesGenerator({ - dictionaries: [unique_names_generator.animals] -}); - -exports.get_dependency = () => { - return 7 -}; \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/nodejs/layers/LayerVersion/package.json b/tests/iac_integration/cdk/testdata/src/nodejs/layers/LayerVersion/package.json deleted file mode 100644 index c4afa6360b..0000000000 --- a/tests/iac_integration/cdk/testdata/src/nodejs/layers/LayerVersion/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "dependencies", - "version": "1.0.0", - "main": "layer_version_dependency.js", - "dependencies": { - "unique-names-generator": "^4.6.0" - } -} diff --git a/tests/iac_integration/cdk/testdata/src/python/BuiltFunctionConstruct/app.py b/tests/iac_integration/cdk/testdata/src/python/BuiltFunctionConstruct/app.py deleted file mode 100644 index f9d25fe615..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/BuiltFunctionConstruct/app.py +++ /dev/null @@ -1,18 +0,0 @@ -import layer_version_dependency -import python_layer_version_dependency -import json - - -def lambda_handler(event, context): - depend1 = layer_version_dependency.get_dependency() - depend2 = python_layer_version_dependency.get_dependency() - - response = { - "statusCode": 200, - "body": json.dumps( - { - "message": f"Hello World from python pre built function {depend1+depend2}", - } - ), - } - return response diff --git a/tests/iac_integration/cdk/testdata/src/python/BundledFunctionConstruct/app.py b/tests/iac_integration/cdk/testdata/src/python/BundledFunctionConstruct/app.py deleted file mode 100644 index d377bfffea..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/BundledFunctionConstruct/app.py +++ /dev/null @@ -1,19 +0,0 @@ -import layer_version_dependency -import python_layer_version_dependency -from geonamescache import GeonamesCache -import json - - -def lambda_handler(event, context): - depend1 = layer_version_dependency.get_dependency() - depend2 = python_layer_version_dependency.get_dependency() - - response = { - "statusCode": 200, - "body": json.dumps( - { - "message": f"Hello World from bundled function construct with python runtime {depend1+depend2}", - } - ), - } - return response diff --git a/tests/iac_integration/cdk/testdata/src/python/BundledFunctionConstruct/requirements.txt b/tests/iac_integration/cdk/testdata/src/python/BundledFunctionConstruct/requirements.txt deleted file mode 100644 index 63ce47ab44..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/BundledFunctionConstruct/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -geonamescache \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/python/FunctionConstruct/app.py b/tests/iac_integration/cdk/testdata/src/python/FunctionConstruct/app.py deleted file mode 100644 index dc1f0d02c0..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/FunctionConstruct/app.py +++ /dev/null @@ -1,19 +0,0 @@ -import layer_version_dependency -import python_layer_version_dependency -from geonamescache import GeonamesCache -import json - - -def lambda_handler(event, context): - depend1 = layer_version_dependency.get_dependency() - depend2 = python_layer_version_dependency.get_dependency() - - response = { - "statusCode": 200, - "body": json.dumps( - { - "message": f"Hello World from function construct with python runtime {depend1+depend2}", - } - ), - } - return response diff --git a/tests/iac_integration/cdk/testdata/src/python/FunctionConstruct/requirements.txt b/tests/iac_integration/cdk/testdata/src/python/FunctionConstruct/requirements.txt deleted file mode 100644 index 63ce47ab44..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/FunctionConstruct/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -geonamescache \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/python/NestedPythonFunctionConstruct/app.py b/tests/iac_integration/cdk/testdata/src/python/NestedPythonFunctionConstruct/app.py deleted file mode 100644 index ffa1cde4a9..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/NestedPythonFunctionConstruct/app.py +++ /dev/null @@ -1,19 +0,0 @@ -import layer_version_dependency -import python_layer_version_dependency -from geonamescache import GeonamesCache -import json - - -def lambda_handler(event, context): - depend1 = layer_version_dependency.get_dependency() - depend2 = python_layer_version_dependency.get_dependency() - - response = { - "statusCode": 200, - "body": json.dumps( - { - "message": f"Hello World from Nested Python Function Construct {depend1+depend2}", - } - ), - } - return response diff --git a/tests/iac_integration/cdk/testdata/src/python/NestedPythonFunctionConstruct/requirements.txt b/tests/iac_integration/cdk/testdata/src/python/NestedPythonFunctionConstruct/requirements.txt deleted file mode 100644 index 63ce47ab44..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/NestedPythonFunctionConstruct/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -geonamescache \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/python/PythonFunctionConstruct/app.py b/tests/iac_integration/cdk/testdata/src/python/PythonFunctionConstruct/app.py deleted file mode 100644 index cb4d8f11e7..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/PythonFunctionConstruct/app.py +++ /dev/null @@ -1,19 +0,0 @@ -import layer_version_dependency -import python_layer_version_dependency -from geonamescache import GeonamesCache -import json - - -def lambda_handler(event, context): - depend1 = layer_version_dependency.get_dependency() - depend2 = python_layer_version_dependency.get_dependency() - - response = { - "statusCode": 200, - "body": json.dumps( - { - "message": f"Hello World from python function construct {depend1+depend2}", - } - ), - } - return response diff --git a/tests/iac_integration/cdk/testdata/src/python/PythonFunctionConstruct/requirements.txt b/tests/iac_integration/cdk/testdata/src/python/PythonFunctionConstruct/requirements.txt deleted file mode 100644 index 63ce47ab44..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/PythonFunctionConstruct/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -geonamescache \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/python/layers/BundledLayerVersion/layer_version_dependency.py b/tests/iac_integration/cdk/testdata/src/python/layers/BundledLayerVersion/layer_version_dependency.py deleted file mode 100644 index e0d6427f81..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/layers/BundledLayerVersion/layer_version_dependency.py +++ /dev/null @@ -1,5 +0,0 @@ -from geonamescache import GeonamesCache - - -def get_dependency(): - return 5 diff --git a/tests/iac_integration/cdk/testdata/src/python/layers/BundledLayerVersion/requirements.txt b/tests/iac_integration/cdk/testdata/src/python/layers/BundledLayerVersion/requirements.txt deleted file mode 100644 index 63ce47ab44..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/layers/BundledLayerVersion/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -geonamescache \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/python/layers/LayerVersion/layer_version_dependency.py b/tests/iac_integration/cdk/testdata/src/python/layers/LayerVersion/layer_version_dependency.py deleted file mode 100644 index e0d6427f81..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/layers/LayerVersion/layer_version_dependency.py +++ /dev/null @@ -1,5 +0,0 @@ -from geonamescache import GeonamesCache - - -def get_dependency(): - return 5 diff --git a/tests/iac_integration/cdk/testdata/src/python/layers/LayerVersion/requirements.txt b/tests/iac_integration/cdk/testdata/src/python/layers/LayerVersion/requirements.txt deleted file mode 100644 index 63ce47ab44..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/layers/LayerVersion/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -geonamescache \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/python/layers/PythonLayerVersion/python_layer_version_dependency.py b/tests/iac_integration/cdk/testdata/src/python/layers/PythonLayerVersion/python_layer_version_dependency.py deleted file mode 100644 index 78a427ccd1..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/layers/PythonLayerVersion/python_layer_version_dependency.py +++ /dev/null @@ -1,7 +0,0 @@ -from art import * - -ART = art("random") - - -def get_dependency(): - return 2 diff --git a/tests/iac_integration/cdk/testdata/src/python/layers/PythonLayerVersion/requirements.txt b/tests/iac_integration/cdk/testdata/src/python/layers/PythonLayerVersion/requirements.txt deleted file mode 100644 index e36ff697c4..0000000000 --- a/tests/iac_integration/cdk/testdata/src/python/layers/PythonLayerVersion/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -art \ No newline at end of file diff --git a/tests/iac_integration/cdk/testdata/src/rest-api-definition.yaml b/tests/iac_integration/cdk/testdata/src/rest-api-definition.yaml deleted file mode 100644 index 517eaaaea9..0000000000 --- a/tests/iac_integration/cdk/testdata/src/rest-api-definition.yaml +++ /dev/null @@ -1,12 +0,0 @@ -openapi: '3.0.2' -info: - title: API Gateway IP Filtering Example API - -paths: - "/restapis/spec/pythonFunction": - get: - x-amazon-apigateway-integration: - httpMethod: POST - type: AWS_PROXY - uri: arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:pythonFunc:$LATEST/invocations - credentials: arn:${AWS::Partition}:iam::${AWS::AccountId}:role/SpecRestApiRole \ No newline at end of file From 025357d474ba37a8212cf4741d894eff4d3b8cd8 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 4 Jul 2023 17:24:40 -0700 Subject: [PATCH 090/107] chore: use amazon ecr credential helper in windows appveyor (#5446) --- appveyor-windows.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/appveyor-windows.yml b/appveyor-windows.yml index 886e65e0c4..28567c956f 100644 --- a/appveyor-windows.yml +++ b/appveyor-windows.yml @@ -134,7 +134,14 @@ install: # Echo final Path - "echo %PATH%" - - "IF DEFINED BY_CANARY ECHO Logging in Public ECR && aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws" + # use amazon-ecr-credential-helper + - choco install amazon-ecr-credential-helper + - ps: " + $docker_config = Get-Content $env:HOME/.docker/config.json -raw | ConvertFrom-Json; + $docker_config.credsStore = 'ecr-login'; + $docker_config | ConvertTo-Json | set-content $env:HOME/.docker/config.json; + " + - ps: "get-content $env:HOME/.docker/config.json" # claim some disk space before starting the tests - "docker system prune -a -f" From b6b4e398d5410f419cfe31c8db7eeaad4955cf00 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 4 Jul 2023 22:08:35 -0700 Subject: [PATCH 091/107] chore: bump version to 1.90.0 (#5448) --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index 48bdee91f7..1fea4bd55f 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.89.0" +__version__ = "1.90.0" From 3603e1247fbe655689b1239536914fd47a2ddc74 Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Wed, 5 Jul 2023 15:46:42 -0700 Subject: [PATCH 092/107] fix: Handler path mapping for layer-wrapped esbuild functions (#5450) * fix: Layer wrapping esbuild function handlers * Remove unused import * Use nodejs18 in tests --- samcli/lib/build/bundler.py | 4 +++ tests/end_to_end/test_runtimes_e2e.py | 27 +++++++++++++++++++ .../esbuild-datadog-integration/main.js | 8 ++++++ .../esbuild-datadog-integration/template.yaml | 27 +++++++++++++++++++ tests/unit/lib/build_module/test_bundler.py | 7 +++++ 5 files changed, 73 insertions(+) create mode 100644 tests/end_to_end/testdata/esbuild-datadog-integration/main.js create mode 100644 tests/end_to_end/testdata/esbuild-datadog-integration/template.yaml diff --git a/samcli/lib/build/bundler.py b/samcli/lib/build/bundler.py index bc3774d3b9..cd69604083 100644 --- a/samcli/lib/build/bundler.py +++ b/samcli/lib/build/bundler.py @@ -12,6 +12,7 @@ LOG = logging.getLogger(__name__) +LAYER_PREFIX = "/opt" ESBUILD_PROPERTY = "esbuild" @@ -157,6 +158,9 @@ def _should_update_handler(self, handler: str, name: str) -> bool: if not handler_filename: LOG.debug("Unable to parse handler, continuing without post-processing template.") return False + if handler_filename.startswith(LAYER_PREFIX): + LOG.debug("Skipping updating the handler path as it is pointing to a layer.") + return False expected_artifact_path = Path(self._build_dir, name, handler_filename) return not expected_artifact_path.is_file() diff --git a/tests/end_to_end/test_runtimes_e2e.py b/tests/end_to_end/test_runtimes_e2e.py index ffe304b705..9955c28341 100644 --- a/tests/end_to_end/test_runtimes_e2e.py +++ b/tests/end_to_end/test_runtimes_e2e.py @@ -1,8 +1,10 @@ +from distutils.dir_util import copy_tree from unittest import skipIf import json from pathlib import Path +import os from parameterized import parameterized_class from tests.end_to_end.end_to_end_base import EndToEndBase @@ -163,3 +165,28 @@ def test_go_hello_world_default_workflow(self): DefaultDeleteStage(BaseValidator(e2e_context), e2e_context, delete_command_list, stack_name), ] self._run_tests(stages) + + +class TestEsbuildDatadogLayerIntegration(EndToEndBase): + app_template = "" + + def test_integration(self): + function_name = "HelloWorldFunction" + event = '{"hello": "world"}' + stack_name = self._method_to_stack_name(self.id()) + with EndToEndTestContext(self.app_name) as e2e_context: + project_path = str(Path("testdata") / "esbuild-datadog-integration") + os.mkdir(e2e_context.project_directory) + copy_tree(project_path, e2e_context.project_directory) + self.template_path = e2e_context.template_path + build_command_list = self.get_command_list() + deploy_command_list = self._get_deploy_command(stack_name) + remote_invoke_command_list = self._get_remote_invoke_command(stack_name, function_name, event, "json") + delete_command_list = self._get_delete_command(stack_name) + stages = [ + EndToEndBaseStage(BuildValidator(e2e_context), e2e_context, build_command_list), + EndToEndBaseStage(BaseValidator(e2e_context), e2e_context, deploy_command_list), + EndToEndBaseStage(RemoteInvokeValidator(e2e_context), e2e_context, remote_invoke_command_list), + DefaultDeleteStage(BaseValidator(e2e_context), e2e_context, delete_command_list, stack_name), + ] + self._run_tests(stages) diff --git a/tests/end_to_end/testdata/esbuild-datadog-integration/main.js b/tests/end_to_end/testdata/esbuild-datadog-integration/main.js new file mode 100644 index 0000000000..5ba65da324 --- /dev/null +++ b/tests/end_to_end/testdata/esbuild-datadog-integration/main.js @@ -0,0 +1,8 @@ +exports.lambdaHandler = async (event, context) => { + return { + statusCode: 200, + body: JSON.stringify({ + message: 'hello world!', + }), + }; +}; diff --git a/tests/end_to_end/testdata/esbuild-datadog-integration/template.yaml b/tests/end_to_end/testdata/esbuild-datadog-integration/template.yaml new file mode 100644 index 0000000000..3341557f3e --- /dev/null +++ b/tests/end_to_end/testdata/esbuild-datadog-integration/template.yaml @@ -0,0 +1,27 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +# Latest extension version: https://github.com/DataDog/datadog-lambda-extension/releases +# Latest Node.js layer version: https://github.com/DataDog/datadog-lambda-js/releases + +Parameters: + DataDogLayers: + Description: DataDog layers + Type: CommaDelimitedList + Default: "arn:aws:lambda:us-east-1:464622532012:layer:Datadog-Node18-x:93, arn:aws:lambda:us-east-1:464622532012:layer:Datadog-Extension:44" + +Resources: + HelloWorldFunction: + Type: AWS::Serverless::Function + Properties: + Handler: /opt/nodejs/node_modules/datadog-lambda-js/handler.handler + Runtime: nodejs18.x + Environment: + Variables: + DD_LAMBDA_HANDLER: main.lambdaHandler + Layers: !Ref DataDogLayers + Metadata: + BuildMethod: esbuild + BuildProperties: + EntryPoints: + - main.js \ No newline at end of file diff --git a/tests/unit/lib/build_module/test_bundler.py b/tests/unit/lib/build_module/test_bundler.py index 39fb1f7c32..c25543b09e 100644 --- a/tests/unit/lib/build_module/test_bundler.py +++ b/tests/unit/lib/build_module/test_bundler.py @@ -195,6 +195,13 @@ def test_check_invalid_lambda_handler_none_build_dir(self): return_val = bundler_manager._should_update_handler("", "") self.assertFalse(return_val) + def test_should_not_update_layer_path(self): + bundler_manager = EsbuildBundlerManager(Mock(), build_dir="/build/dir") + bundler_manager._get_path_and_filename_from_handler = Mock() + bundler_manager._get_path_and_filename_from_handler.return_value = "/opt/nodejs/node_modules/d/handler.handler" + return_val = bundler_manager._should_update_handler("", "") + self.assertFalse(return_val) + def test_update_function_handler(self): resources = { "FunctionA": { From cee2d3d05aa63024729d0f58e31454cc721f5c73 Mon Sep 17 00:00:00 2001 From: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:49:39 -0700 Subject: [PATCH 093/107] fix: fix macos reproducable task and gh actions (#5455) --- .../automated-updates-to-sam-cli.yml | 4 ++-- Makefile | 2 +- requirements/base.txt | 2 +- requirements/reproducible-linux.txt | 6 +++--- requirements/reproducible-mac.txt | 19 +++++-------------- 5 files changed, 12 insertions(+), 21 deletions(-) diff --git a/.github/workflows/automated-updates-to-sam-cli.yml b/.github/workflows/automated-updates-to-sam-cli.yml index 45c49f727a..a42123ea5b 100644 --- a/.github/workflows/automated-updates-to-sam-cli.yml +++ b/.github/workflows/automated-updates-to-sam-cli.yml @@ -75,7 +75,7 @@ jobs: - uses: actions/setup-python@v4 # used for make update-reproducible-reqs below with: python-version: | - 3.7 + 3.8 3.11 - name: Update aws-sam-translator & commit @@ -132,7 +132,7 @@ jobs: - uses: actions/setup-python@v4 # used for make update-reproducible-reqs below with: python-version: | - 3.7 + 3.8 3.11 - name: Upgrade aws_lambda_builders & commit diff --git a/Makefile b/Makefile index 8876d482b2..dd80957cae 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,7 @@ update-reproducible-linux-reqs: venv-update-reproducible-linux/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-linux.txt update-reproducible-mac-reqs: - python3.7 -m venv venv-update-reproducible-mac + python3.8 -m venv venv-update-reproducible-mac venv-update-reproducible-mac/bin/pip install --upgrade pip-tools pip venv-update-reproducible-mac/bin/pip install -r requirements/base.txt venv-update-reproducible-mac/bin/pip-compile --generate-hashes --allow-unsafe -o requirements/reproducible-mac.txt diff --git a/requirements/base.txt b/requirements/base.txt index f0626e7145..707300f4b8 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,7 +7,7 @@ jmespath~=1.0.1 ruamel_yaml~=0.17.32 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 -aws-sam-translator==1.70.0 +aws-sam-translator==1.71.0 #docker minor version updates can include breaking changes. Auto update micro version only. docker~=6.1.0 dateparser~=1.1 diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index f946536226..7ed8c51903 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.34.0 \ --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) -aws-sam-translator==1.70.0 \ - --hash=sha256:753288eda07b057e5350773b7617076962b59404d49cd05e2259ac96a7694436 \ - --hash=sha256:a2df321607d29791893707ef2ded9e79be00dbb71ac430696f6e6d7d0b0301a5 +aws-sam-translator==1.71.0 \ + --hash=sha256:17fb87c8137d8d49e7a978396b2b3b279211819dee44618415aab1e99c2cb659 \ + --hash=sha256:a3ea80aeb116d7978b26ac916d2a5a24d012b742bf28262b17769c4b886e8fba # via # aws-sam-cli (setup.py) # cfn-lint diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index a1db7a41cf..4ac1c38a21 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.7 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile --allow-unsafe --generate-hashes --output-file=requirements/reproducible-mac.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.34.0 \ --hash=sha256:0790f7e9b7ee7286b96fbcf49450c5be0341bb7cb852ca7d74beae190139eb48 \ --hash=sha256:20456a942a417407b42ecf8ab7fce6a47306fd063051e7cb09d02d1be24d5cf3 # via aws-sam-cli (setup.py) -aws-sam-translator==1.70.0 \ - --hash=sha256:753288eda07b057e5350773b7617076962b59404d49cd05e2259ac96a7694436 \ - --hash=sha256:a2df321607d29791893707ef2ded9e79be00dbb71ac430696f6e6d7d0b0301a5 +aws-sam-translator==1.71.0 \ + --hash=sha256:17fb87c8137d8d49e7a978396b2b3b279211819dee44618415aab1e99c2cb659 \ + --hash=sha256:a3ea80aeb116d7978b26ac916d2a5a24d012b742bf28262b17769c4b886e8fba # via # aws-sam-cli (setup.py) # cfn-lint @@ -270,12 +270,7 @@ idna==3.4 \ importlib-metadata==6.7.0 \ --hash=sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4 \ --hash=sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5 - # via - # attrs - # click - # flask - # jsonpickle - # jsonschema + # via flask importlib-resources==5.12.0 \ --hash=sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6 \ --hash=sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a @@ -722,12 +717,8 @@ typing-extensions==4.4.0 \ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e # via - # arrow # aws-sam-cli (setup.py) # aws-sam-translator - # importlib-metadata - # jsonschema - # markdown-it-py # pydantic # rich tzlocal==3.0 \ From 16a1740b635d366de19cbb54dd549cd27abdbc63 Mon Sep 17 00:00:00 2001 From: Elvis Henrique Pereira Date: Thu, 6 Jul 2023 18:12:15 -0300 Subject: [PATCH 094/107] feat(sync): support build-image option (#5441) * feat(sync): support build-image option * chore: adding build image option on help option --- samcli/commands/_utils/options.py | 23 +++++++++++++++++++ samcli/commands/build/command.py | 17 ++------------ samcli/commands/sync/command.py | 12 +++++++++- samcli/commands/sync/core/options.py | 1 + .../unit/commands/samconfig/test_samconfig.py | 1 + tests/unit/commands/sync/test_command.py | 6 +++++ 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/samcli/commands/_utils/options.py b/samcli/commands/_utils/options.py index 5b1b55cc32..24f5eef3a6 100644 --- a/samcli/commands/_utils/options.py +++ b/samcli/commands/_utils/options.py @@ -796,6 +796,29 @@ def use_container_build_option(f): return use_container_build_click_option()(f) +def build_image_click_option(cls): + return click.option( + "--build-image", + "-bi", + default=None, + multiple=True, # Can pass in multiple build images + required=False, + help="Container image URIs for building functions/layers. " + "You can specify for all functions/layers with just the image URI " + "(--build-image public.ecr.aws/sam/build-nodejs18.x:latest). " + "You can specify for each individual function with " + "(--build-image FunctionLogicalID=public.ecr.aws/sam/build-nodejs18.x:latest). " + "A combination of the two can be used. If a function does not have build image specified or " + "an image URI for all functions, the default SAM CLI build images will be used.", + cls=cls, + ) + + +@parameterized_option +def build_image_option(f, cls): + return build_image_click_option(cls)(f) + + def _space_separated_list_func_type(value): if isinstance(value, str): return value.split(" ") diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index 92a2c6578c..a60f9954e1 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -20,6 +20,7 @@ manifest_option, cached_option, use_container_build_option, + build_image_option, hook_name_click_option, ) from samcli.commands._utils.option_value_processor import process_env_var, process_image_options @@ -94,21 +95,7 @@ help="Environment variables json file (e.g., env_vars.json) to be passed to build containers.", cls=ContainerOptions, ) -@click.option( - "--build-image", - "-bi", - default=None, - multiple=True, # Can pass in multiple build images - required=False, - help="Container image URIs for building functions/layers. " - "You can specify for all functions/layers with just the image URI " - "(--build-image public.ecr.aws/sam/build-nodejs18.x:latest). " - "You can specify for each individual function with " - "(--build-image FunctionLogicalID=public.ecr.aws/sam/build-nodejs18.x:latest). " - "A combination of the two can be used. If a function does not have build image specified or " - "an image URI for all functions, the default SAM CLI build images will be used.", - cls=ContainerOptions, -) +@build_image_option(cls=ContainerOptions) @click.option( "--exclude", "-x", diff --git a/samcli/commands/sync/command.py b/samcli/commands/sync/command.py index 26eccac4b3..81f1222207 100644 --- a/samcli/commands/sync/command.py +++ b/samcli/commands/sync/command.py @@ -1,7 +1,7 @@ """CLI command for "sync" command.""" import logging import os -from typing import TYPE_CHECKING, List, Optional, Set +from typing import TYPE_CHECKING, List, Optional, Set, Tuple import click @@ -18,8 +18,10 @@ DEFAULT_CACHE_DIR, ) from samcli.commands._utils.custom_options.replace_help_option import ReplaceHelpSummaryOption +from samcli.commands._utils.option_value_processor import process_image_options from samcli.commands._utils.options import ( base_dir_option, + build_image_option, capabilities_option, image_repositories_option, image_repository_option, @@ -35,6 +37,7 @@ template_option_without_build, use_container_build_option, ) +from samcli.commands.build.click_container import ContainerOptions from samcli.commands.build.command import _get_mode_value_from_envvar from samcli.commands.sync.core.command import SyncCommand from samcli.commands.sync.sync_context import SyncContext @@ -155,6 +158,7 @@ @stack_name_option(required=True) # pylint: disable=E1120 @base_dir_option @use_container_build_option +@build_image_option(cls=ContainerOptions) @image_repository_option @image_repositories_option @s3_bucket_option(disable_callback=True) # pylint: disable=E1120 @@ -202,6 +206,7 @@ def cli( use_container: bool, config_file: str, config_env: str, + build_image: Optional[Tuple[str]], ) -> None: """ `sam sync` command entry point @@ -234,6 +239,7 @@ def cli( tags, metadata, use_container, + build_image, config_file, config_env, None, # TODO: replace with build_in_source once it's added as a click option @@ -265,6 +271,7 @@ def do_cli( tags: dict, metadata: dict, use_container: bool, + build_image: Optional[Tuple[str]], config_file: str, config_env: str, build_in_source: Optional[bool], @@ -303,6 +310,8 @@ def do_cli( LOG.debug("Using build directory as %s", build_dir) EventTracker.track_event("UsedFeature", "Accelerate") + processed_build_images = process_image_options(build_image) + with BuildContext( resource_identifier=None, template_file=template_file, @@ -320,6 +329,7 @@ def do_cli( print_success_message=False, locate_layer_nested=True, build_in_source=build_in_source, + build_images=processed_build_images, ) as build_context: built_template = os.path.join(build_dir, DEFAULT_TEMPLATE_NAME) diff --git a/samcli/commands/sync/core/options.py b/samcli/commands/sync/core/options.py index 43a63e92e7..ee7ead9799 100644 --- a/samcli/commands/sync/core/options.py +++ b/samcli/commands/sync/core/options.py @@ -25,6 +25,7 @@ "notification_arns", "tags", "metadata", + "build_image", ] CONFIGURATION_OPTION_NAMES: List[str] = ["config_env", "config_file"] diff --git a/tests/unit/commands/samconfig/test_samconfig.py b/tests/unit/commands/samconfig/test_samconfig.py index 19b4a7672d..675f22a4bf 100644 --- a/tests/unit/commands/samconfig/test_samconfig.py +++ b/tests/unit/commands/samconfig/test_samconfig.py @@ -1007,6 +1007,7 @@ def test_sync( {"a": "tag1", "b": "tag with spaces"}, {"m1": "value1", "m2": "value2"}, True, + (), "samconfig.toml", "default", None, diff --git a/tests/unit/commands/sync/test_command.py b/tests/unit/commands/sync/test_command.py index b0bcede4d0..6599f04076 100644 --- a/tests/unit/commands/sync/test_command.py +++ b/tests/unit/commands/sync/test_command.py @@ -57,6 +57,7 @@ def setUp(self): self.clean = True self.config_env = "mock-default-env" self.config_file = "mock-default-filename" + self.build_image = None MOCK_SAM_CONFIG.reset_mock() @parameterized.expand( @@ -141,6 +142,7 @@ def test_infra_must_succeed_sync( self.tags, self.metadata, use_container, + self.build_image, self.config_file, self.config_env, build_in_source=False, @@ -167,6 +169,7 @@ def test_infra_must_succeed_sync( print_success_message=False, locate_layer_nested=True, build_in_source=False, + build_images={}, ) PackageContextMock.assert_called_with( @@ -298,6 +301,7 @@ def test_watch_must_succeed_sync( self.tags, self.metadata, use_container, + self.build_image, self.config_file, self.config_env, build_in_source=False, @@ -320,6 +324,7 @@ def test_watch_must_succeed_sync( print_success_message=False, locate_layer_nested=True, build_in_source=False, + build_images={}, ) PackageContextMock.assert_called_with( @@ -443,6 +448,7 @@ def test_code_must_succeed_sync( self.tags, self.metadata, use_container, + self.build_image, self.config_file, self.config_env, build_in_source=None, From 58faff0dfc597016f945ca215925ca26b950c770 Mon Sep 17 00:00:00 2001 From: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Date: Thu, 6 Jul 2023 18:33:07 -0700 Subject: [PATCH 095/107] fix: Avoid Certain Depedendency Version (#5460) * Avoid broken click version * Pin boto3 and jsonschema * Update reproducible reqs * Ignore deprecation warnings in pytest * Pin jsonschema --- pytest.ini | 4 ++++ requirements/base.txt | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index 9d19677545..80263254ba 100644 --- a/pytest.ini +++ b/pytest.ini @@ -6,3 +6,7 @@ filterwarnings = error ignore::DeprecationWarning:docker default::ResourceWarning +; The following deprecation warnings are treated as failures unless we explicitly tell pytest not to +; Remove once we no longer support python3.7 + ignore::boto3.exceptions.PythonDeprecationWarning + diff --git a/requirements/base.txt b/requirements/base.txt index 707300f4b8..38532b4714 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,5 +1,6 @@ chevron~=0.12 -click~=8.0 +# 8.1.4 of Click has an issue with the typing breaking the linter - https://github.com/pallets/click/issues/2558 +click~=8.0,!=8.1.4 Flask<2.3 #Need to add latest lambda changes which will return invoke mode details boto3>=1.26.109,==1.* @@ -18,6 +19,8 @@ tomlkit==0.11.8 watchdog==2.1.2 rich~=13.3.3 pyopenssl~=23.2.0 +# Pin to <4.18 to until SAM-T no longer uses RefResolver +jsonschema<4.18 # Needed for supporting Protocol in Python 3.7, Protocol class became public with python3.8 typing_extensions~=4.4.0 From 068b7e25a27a09d8cf0452889dab20d3367217bd Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Fri, 2 Jun 2023 11:57:11 -0700 Subject: [PATCH 096/107] feat: Abstract SamConfig (#5208) * Abstract SamConfig and decouple TOML logic * Fix documentation and comments * Generalize exception for FileManager * Remove FileManager logic to its own file * Fix bug in setting a default FileManager * Implement requested changes This includes additional logging messages, as well as explicitly requiring file extensions * Include supported extensions in log call * Implement requested changes * Update docstrings * Refactor changes to preserve TOML comments * Allow file document to update properly * Remove duplicate data Since TOMLDocument wraps a Python dictionary anyway, we don't need the separate information * Add put comment for FileManager * Implement requested changes * Format files according to standard * Implement helper method for dict-like to TOMLDocument --------- Co-authored-by: Leonardo Gama --- samcli/commands/deploy/guided_config.py | 11 +- samcli/lib/config/exceptions.py | 10 +- samcli/lib/config/file_manager.py | 161 ++++++++++++++++++ samcli/lib/config/samconfig.py | 97 +++++------ tests/unit/cli/test_cli_config_file.py | 16 +- .../unit/commands/samconfig/test_samconfig.py | 20 ++- tests/unit/lib/samconfig/test_file_manager.py | 101 +++++++++++ tests/unit/lib/samconfig/test_samconfig.py | 16 +- 8 files changed, 356 insertions(+), 76 deletions(-) create mode 100644 samcli/lib/config/file_manager.py create mode 100644 tests/unit/lib/samconfig/test_file_manager.py diff --git a/samcli/commands/deploy/guided_config.py b/samcli/commands/deploy/guided_config.py index b9d8ea59b5..10b80189db 100644 --- a/samcli/commands/deploy/guided_config.py +++ b/samcli/commands/deploy/guided_config.py @@ -7,6 +7,7 @@ from samcli.cli.context import get_cmd_names from samcli.commands.deploy.exceptions import GuidedDeployFailedError +from samcli.lib.config.exceptions import SamConfigFileReadException from samcli.lib.config.samconfig import DEFAULT_CONFIG_FILE_NAME, DEFAULT_ENV, SamConfig @@ -26,13 +27,17 @@ def get_config_ctx(self, config_file=None): return ctx, samconfig def read_config_showcase(self, config_file=None): - _, samconfig = self.get_config_ctx(config_file) - - status = "Found" if samconfig.exists() else "Not found" msg = ( "Syntax invalid in samconfig.toml; save values " "through sam deploy --guided to overwrite file with a valid set of values." ) + try: + _, samconfig = self.get_config_ctx(config_file) + except SamConfigFileReadException: + raise GuidedDeployFailedError(msg) + + status = "Found" if samconfig.exists() else "Not found" + config_sanity = samconfig.sanity_check() click.secho("\nConfiguring SAM deploy\n======================", fg="yellow") click.echo(f"\n\tLooking for config file [{config_file}] : {status}") diff --git a/samcli/lib/config/exceptions.py b/samcli/lib/config/exceptions.py index 50297ce722..c179b4a13c 100644 --- a/samcli/lib/config/exceptions.py +++ b/samcli/lib/config/exceptions.py @@ -4,4 +4,12 @@ class SamConfigVersionException(Exception): - pass + """Exception for the `samconfig` file being not present or in unrecognized format""" + + +class FileParseException(Exception): + """Exception when a file is incorrectly parsed by a FileManager object.""" + + +class SamConfigFileReadException(Exception): + """Exception when a `samconfig` file is read incorrectly.""" diff --git a/samcli/lib/config/file_manager.py b/samcli/lib/config/file_manager.py new file mode 100644 index 0000000000..146946056f --- /dev/null +++ b/samcli/lib/config/file_manager.py @@ -0,0 +1,161 @@ +""" +Class to represent the parsing of different file types into Python objects. +""" + + +import logging +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Any + +import tomlkit + +from samcli.lib.config.exceptions import FileParseException + +LOG = logging.getLogger(__name__) +COMMENT_KEY = "__comment__" + + +class FileManager(ABC): + """ + Abstract class to be overridden by file managers for specific file extensions. + """ + + @staticmethod + @abstractmethod + def read(filepath: Path) -> Any: + """ + Read a file at a given path. + + Parameters + ---------- + filepath: Path + The Path object that points to the file to be read. + + Returns + ------- + Any + A dictionary-like representation of the contents at the filepath location, along with a specialized + representation of the file that was read, if there is a specialization of it. + """ + raise NotImplementedError("Read method not implemented.") + + @staticmethod + @abstractmethod + def write(document: dict, filepath: Path): + """ + Write a dictionary or dictionary-like object to a given file. + + Parameters + ---------- + document: dict + The object to write. + filepath: Path + The final location for the document to be written. + """ + raise NotImplementedError("Write method not implemented.") + + @staticmethod + @abstractmethod + def put_comment(document: Any, comment: str) -> Any: + """ + Put a comment in a document object. + + Parameters + ---------- + document: Any + The object to write + comment: str + The comment to include in the document. + + Returns + ------- + Any + The new document, with the comment added to it. + """ + raise NotImplementedError("Put comment method not implemented.") + + +class TomlFileManager(FileManager): + """ + Static class to read and write toml files. + """ + + file_format = "TOML" + + @staticmethod + def read(filepath: Path) -> Any: + """ + Read a TOML file at the given path. + + Parameters + ---------- + filepath: Path + The Path object that points to the file to be read. + + Returns + ------- + Any + A dictionary-like tomlkit.TOMLDocument object, which represents the contents of the TOML file at the + provided location. + """ + toml_doc = tomlkit.document() + try: + txt = filepath.read_text() + toml_doc = tomlkit.loads(txt) + except OSError as e: + LOG.debug(f"OSError occurred while reading {TomlFileManager.file_format} file: {str(e)}") + except tomlkit.exceptions.TOMLKitError as e: + raise FileParseException(e) from e + + return toml_doc + + @staticmethod + def write(document: dict, filepath: Path): + """ + Write the contents of a dictionary or tomlkit.TOMLDocument to a TOML file at the provided location. + + Parameters + ---------- + document: dict + The object to write. + filepath: Path + The final location for the TOML file to be written. + """ + if not document: + LOG.debug("Nothing for TomlFileManager to write.") + return + + toml_document = TomlFileManager._to_toml(document) + + if toml_document.get(COMMENT_KEY, None): # Remove dunder comments that may be residue from other formats + toml_document.add(tomlkit.comment(toml_document[COMMENT_KEY])) + toml_document.pop(COMMENT_KEY) + + filepath.write_text(tomlkit.dumps(toml_document)) + + @staticmethod + def put_comment(document: dict, comment: str) -> Any: + """ + Put a comment in a document object. + + Parameters + ---------- + document: Any + The tomlkit.TOMLDocument object to write + comment: str + The comment to include in the document. + + Returns + ------- + Any + The new TOMLDocument, with the comment added to it. + """ + document = TomlFileManager._to_toml(document) + document.add(tomlkit.comment(comment)) + return document + + @staticmethod + def _to_toml(document: dict) -> tomlkit.TOMLDocument: + """Ensure that a dictionary-like object is a TOMLDocument.""" + return tomlkit.parse(tomlkit.dumps(document)) diff --git a/samcli/lib/config/samconfig.py b/samcli/lib/config/samconfig.py index e48e53d625..d70b8fe1bb 100644 --- a/samcli/lib/config/samconfig.py +++ b/samcli/lib/config/samconfig.py @@ -5,27 +5,28 @@ import logging import os from pathlib import Path -from typing import Any, Iterable +from typing import Any, Dict, Iterable, Type -import tomlkit - -from samcli.lib.config.exceptions import SamConfigVersionException +from samcli.lib.config.exceptions import FileParseException, SamConfigFileReadException, SamConfigVersionException +from samcli.lib.config.file_manager import FileManager, TomlFileManager from samcli.lib.config.version import SAM_CONFIG_VERSION, VERSION_KEY LOG = logging.getLogger(__name__) -DEFAULT_CONFIG_FILE_EXTENSION = "toml" -DEFAULT_CONFIG_FILE_NAME = f"samconfig.{DEFAULT_CONFIG_FILE_EXTENSION}" +DEFAULT_CONFIG_FILE_EXTENSION = ".toml" +DEFAULT_CONFIG_FILE_NAME = f"samconfig{DEFAULT_CONFIG_FILE_EXTENSION}" DEFAULT_ENV = "default" DEFAULT_GLOBAL_CMDNAME = "global" class SamConfig: """ - Class to interface with `samconfig.toml` file. + Class to represent `samconfig` config options. """ - document = None + FILE_MANAGER_MAPPER: Dict[str, Type[FileManager]] = { + ".toml": TomlFileManager, + } def __init__(self, config_dir, filename=None): """ @@ -39,11 +40,21 @@ def __init__(self, config_dir, filename=None): Optional. Name of the configuration file. It is recommended to stick with default so in the future we could automatically support auto-resolving multiple config files within same directory. """ + self.document = {} self.filepath = Path(config_dir, filename or DEFAULT_CONFIG_FILE_NAME) + self.file_manager = self.FILE_MANAGER_MAPPER.get(self.filepath.suffix, None) + if not self.file_manager: + LOG.warning( + f"The config file extension '{self.filepath.suffix}' is not supported. " + f"Supported formats are: [{'|'.join(self.FILE_MANAGER_MAPPER.keys())}]" + ) + raise SamConfigFileReadException( + f"The config file {self.filepath} uses an unsupported extension, and cannot be read." + ) + self._read() def get_stage_configuration_names(self): - self._read() - if isinstance(self.document, dict): + if self.document: return [stage for stage, value in self.document.items() if isinstance(value, dict)] return [] @@ -69,23 +80,19 @@ def get_all(self, cmd_names, section, env=DEFAULT_ENV): ------ KeyError If the config file does *not* have the specific section - - tomlkit.exceptions.TOMLKitError - If the configuration file is invalid """ env = env or DEFAULT_ENV - self._read() - if isinstance(self.document, dict): - toml_content = self.document.get(env, {}) - params = toml_content.get(self._to_key(cmd_names), {}).get(section, {}) - if DEFAULT_GLOBAL_CMDNAME in toml_content: - global_params = toml_content.get(DEFAULT_GLOBAL_CMDNAME, {}).get(section, {}) - global_params.update(params.copy()) - params = global_params.copy() - return params - return {} + self.document = self._read() + + config_content = self.document.get(env, {}) + params = config_content.get(self._to_key(cmd_names), {}).get(section, {}) + if DEFAULT_GLOBAL_CMDNAME in config_content: + global_params = config_content.get(DEFAULT_GLOBAL_CMDNAME, {}).get(section, {}) + global_params.update(params.copy()) + params = global_params.copy() + return params def put(self, cmd_names, section, key, value, env=DEFAULT_ENV): """ @@ -102,20 +109,10 @@ def put(self, cmd_names, section, key, value, env=DEFAULT_ENV): key : str Key to write the data under value : Any - Value to write. Could be any of the supported TOML types. + Value to write. Could be any of the supported types. env : str Optional, Name of the environment - - Raises - ------ - tomlkit.exceptions.TOMLKitError - If the data is invalid """ - - if self.document is None: - # Checking for None here since a TOMLDocument can include a - # 'body' property but still be falsy without a 'value' property - self._read() # Empty document prepare the initial structure. # self.document is a nested dict, we need to check each layer and add new tables, otherwise duplicated key # in parent layer will override the whole child layer @@ -144,20 +141,12 @@ def put_comment(self, comment): comment: str A comment to write to the samconfg file """ - if self.document is None: - self._read() - self.document.add(tomlkit.comment(comment)) + self.document = self.file_manager.put_comment(self.document, comment) def flush(self): """ Write the data back to file - - Raises - ------ - tomlkit.exceptions.TOMLKitError - If the data is invalid - """ self._write() @@ -167,7 +156,7 @@ def sanity_check(self): """ try: self._read() - except tomlkit.exceptions.TOMLKitError: + except SamConfigFileReadException: return False else: return True @@ -196,13 +185,10 @@ def config_dir(template_file_path=None): def _read(self): if not self.document: try: - txt = self.filepath.read_text() - self.document = tomlkit.loads(txt) - self._version_sanity_check(self._version()) - except OSError: - self.document = tomlkit.document() - - if self.document.body: + self.document = self.file_manager.read(self.filepath) + except FileParseException as e: + raise SamConfigFileReadException(e) from e + if self.document: self._version_sanity_check(self._version()) return self.document @@ -213,12 +199,9 @@ def _write(self): self._ensure_exists() current_version = self._version() if self._version() else SAM_CONFIG_VERSION - try: - self.document.add(VERSION_KEY, current_version) - except tomlkit.exceptions.KeyAlreadyPresent: - # NOTE(TheSriram): Do not attempt to re-write an existing version - pass - self.filepath.write_text(tomlkit.dumps(self.document)) + self.document.update({VERSION_KEY: current_version}) + + self.file_manager.write(self.document, self.filepath) def _version(self): return self.document.get(VERSION_KEY, None) diff --git a/tests/unit/cli/test_cli_config_file.py b/tests/unit/cli/test_cli_config_file.py index 606e0a004e..3395c12786 100644 --- a/tests/unit/cli/test_cli_config_file.py +++ b/tests/unit/cli/test_cli_config_file.py @@ -5,9 +5,12 @@ from unittest import TestCase, skipIf from unittest.mock import MagicMock, patch +import tomlkit + from samcli.commands.exceptions import ConfigException from samcli.cli.cli_config_file import TomlProvider, configuration_option, configuration_callback, get_ctx_defaults -from samcli.lib.config.samconfig import DEFAULT_ENV +from samcli.lib.config.exceptions import FileParseException, SamConfigFileReadException, SamConfigVersionException +from samcli.lib.config.samconfig import DEFAULT_ENV, TomlFileManager from tests.testing_utils import IS_WINDOWS @@ -40,14 +43,14 @@ def test_toml_valid_with_no_version(self): config_dir = tempfile.gettempdir() config_path = Path(config_dir, "samconfig.toml") config_path.write_text("[config_env.topic.parameters]\nword='clarity'\n") - with self.assertRaises(ConfigException): + with self.assertRaises(SamConfigVersionException): TomlProvider(section=self.parameters)(config_path, self.config_env, [self.cmd_name]) def test_toml_valid_with_invalid_version(self): config_dir = tempfile.gettempdir() config_path = Path(config_dir, "samconfig.toml") config_path.write_text("version='abc'\n[config_env.topic.parameters]\nword='clarity'\n") - with self.assertRaises(ConfigException): + with self.assertRaises(SamConfigVersionException): TomlProvider(section=self.parameters)(config_path, self.config_env, [self.cmd_name]) def test_toml_invalid_empty_dict(self): @@ -55,7 +58,8 @@ def test_toml_invalid_empty_dict(self): config_path = Path(config_dir, "samconfig.toml") config_path.write_text("[topic]\nword=clarity\n") - self.assertEqual(self.toml_provider(config_dir, self.config_env, [self.cmd_name]), {}) + with self.assertRaises(SamConfigFileReadException): + self.toml_provider(config_path, self.config_env, [self.cmd_name]) def test_toml_invalid_file_name(self): config_dir = tempfile.gettempdir() @@ -63,7 +67,7 @@ def test_toml_invalid_file_name(self): config_path.write_text("version=0.1\n[config_env.topic.parameters]\nword='clarity'\n") config_path_invalid = Path(config_dir, "samconfig.toml") - with self.assertRaises(ConfigException): + with self.assertRaises(SamConfigFileReadException): self.toml_provider(config_path_invalid, self.config_env, [self.cmd_name]) def test_toml_invalid_syntax(self): @@ -71,7 +75,7 @@ def test_toml_invalid_syntax(self): config_path = Path(config_dir, "samconfig.toml") config_path.write_text("version=0.1\n[config_env.topic.parameters]\nword=_clarity'\n") - with self.assertRaises(ConfigException): + with self.assertRaises(SamConfigFileReadException): self.toml_provider(config_path, self.config_env, [self.cmd_name]) diff --git a/tests/unit/commands/samconfig/test_samconfig.py b/tests/unit/commands/samconfig/test_samconfig.py index 675f22a4bf..d5d857c3cd 100644 --- a/tests/unit/commands/samconfig/test_samconfig.py +++ b/tests/unit/commands/samconfig/test_samconfig.py @@ -8,14 +8,15 @@ import tempfile from pathlib import Path from contextlib import contextmanager -from samcli.lib.config.samconfig import SamConfig, DEFAULT_ENV from click.testing import CliRunner from unittest import TestCase from unittest.mock import patch, ANY import logging +from samcli.lib.config.exceptions import SamConfigFileReadException +from samcli.lib.config.samconfig import SamConfig, DEFAULT_ENV, TomlFileManager from samcli.lib.utils.packagetype import ZIP, IMAGE LOG = logging.getLogger() @@ -1247,6 +1248,23 @@ def test_secondary_option_name_template_validate(self, do_cli_mock): do_cli_mock.assert_called_with(ANY, str(Path(os.getcwd(), "mytemplate.yaml")), False) +class TestSamConfigFileManager(TestCase): + def test_file_manager_not_declared(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig") + + with self.assertRaises(SamConfigFileReadException): + SamConfig(config_path, filename="samconfig") + + def test_file_manager_toml(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.toml") + + samconfig = SamConfig(config_path, filename="samconfig.toml") + + self.assertIs(samconfig.file_manager, TomlFileManager) + + @contextmanager def samconfig_parameters(cmd_names, config_dir=None, env=None, **kwargs): """ diff --git a/tests/unit/lib/samconfig/test_file_manager.py b/tests/unit/lib/samconfig/test_file_manager.py new file mode 100644 index 0000000000..0973637023 --- /dev/null +++ b/tests/unit/lib/samconfig/test_file_manager.py @@ -0,0 +1,101 @@ +from pathlib import Path +import tempfile +from unittest import TestCase + +import tomlkit +from samcli.lib.config.exceptions import FileParseException + +from samcli.lib.config.file_manager import COMMENT_KEY, TomlFileManager + + +class TestTomlFileManager(TestCase): + def test_read_toml(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.toml") + config_path.write_text("version=0.1\n[config_env.topic1.parameters]\nword='clarity'\n") + config_doc = TomlFileManager.read(config_path) + self.assertEqual( + config_doc, + {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}}, + ) + + def test_read_toml_invalid_toml(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.toml") + config_path.write_text("fake='not real'\nimproper toml file\n") + with self.assertRaises(FileParseException): + TomlFileManager.read(config_path) + + def test_read_toml_file_path_not_valid(self): + config_dir = "path/that/doesnt/exist" + config_path = Path(config_dir, "samconfig.toml") + config_doc = TomlFileManager.read(config_path) + self.assertEqual(config_doc, tomlkit.document()) + + def test_write_toml(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.toml") + toml = { + "version": 0.1, + "config_env": {"topic2": {"parameters": {"word": "clarity"}}}, + COMMENT_KEY: "This is a comment", + } + + TomlFileManager.write(toml, config_path) + + txt = config_path.read_text() + self.assertIn("version = 0.1", txt) + self.assertIn("[config_env.topic2.parameters]", txt) + self.assertIn('word = "clarity"', txt) + self.assertIn("# This is a comment", txt) + + def test_dont_write_toml_if_empty(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.toml") + config_path.write_text("nothing to see here\n") + toml = {} + + TomlFileManager.write(toml, config_path) + + self.assertEqual(config_path.read_text(), "nothing to see here\n") + + def test_write_toml_bad_path(self): + config_path = Path("path/to/some", "file_that_doesnt_exist.toml") + with self.assertRaises(FileNotFoundError): + TomlFileManager.write({"key": "some value"}, config_path) + + def test_write_toml_file(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.toml") + toml = tomlkit.parse('# This is a comment\nversion = 0.1\n[config_env.topic2.parameters]\nword = "clarity"\n') + + TomlFileManager.write(toml, config_path) + + txt = config_path.read_text() + self.assertIn("version = 0.1", txt) + self.assertIn("[config_env.topic2.parameters]", txt) + self.assertIn('word = "clarity"', txt) + self.assertIn("# This is a comment", txt) + + def test_dont_write_toml_file_if_empty(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.toml") + config_path.write_text("nothing to see here\n") + toml = tomlkit.document() + + TomlFileManager.write(toml, config_path) + + self.assertEqual(config_path.read_text(), "nothing to see here\n") + + def test_write_toml_file_bad_path(self): + config_path = Path("path/to/some", "file_that_doesnt_exist.toml") + with self.assertRaises(FileNotFoundError): + TomlFileManager.write(tomlkit.parse('key = "some value"'), config_path) + + def test_toml_put_comment(self): + toml_doc = tomlkit.loads('version = 0.1\n[config_env.topic2.parameters]\nword = "clarity"\n') + + toml_doc = TomlFileManager.put_comment(toml_doc, "This is a comment") + + txt = tomlkit.dumps(toml_doc) + self.assertIn("# This is a comment", txt) diff --git a/tests/unit/lib/samconfig/test_samconfig.py b/tests/unit/lib/samconfig/test_samconfig.py index 7a86e6f97d..346353501b 100644 --- a/tests/unit/lib/samconfig/test_samconfig.py +++ b/tests/unit/lib/samconfig/test_samconfig.py @@ -182,27 +182,27 @@ def test_check_sanity(self): def test_check_version_non_supported_type(self): self._setup_config() - self.samconfig.document.remove(VERSION_KEY) - self.samconfig.document.add(VERSION_KEY, "aadeff") + self.samconfig.document.pop(VERSION_KEY) + self.samconfig.document.update({VERSION_KEY: "aadeff"}) with self.assertRaises(SamConfigVersionException): self.samconfig.sanity_check() def test_check_version_no_version_exists(self): self._setup_config() - self.samconfig.document.remove(VERSION_KEY) + self.samconfig.document.pop(VERSION_KEY) with self.assertRaises(SamConfigVersionException): self.samconfig.sanity_check() def test_check_version_float(self): self._setup_config() - self.samconfig.document.remove(VERSION_KEY) - self.samconfig.document.add(VERSION_KEY, 0.2) + self.samconfig.document.pop(VERSION_KEY) + self.samconfig.document.update({VERSION_KEY: 0.2}) self.samconfig.sanity_check() def test_write_config_file_non_standard_version(self): self._setup_config() - self.samconfig.document.remove(VERSION_KEY) - self.samconfig.document.add(VERSION_KEY, 0.2) + self.samconfig.document.pop(VERSION_KEY) + self.samconfig.document.update({VERSION_KEY: 0.2}) self.samconfig.put(cmd_names=["local", "start", "api"], section="parameters", key="skip_pull_image", value=True) self.samconfig.sanity_check() self.assertEqual(self.samconfig.document.get(VERSION_KEY), 0.2) @@ -210,7 +210,7 @@ def test_write_config_file_non_standard_version(self): def test_write_config_file_will_create_the_file_if_not_exist(self): with osutils.mkdir_temp(ignore_errors=True) as tempdir: non_existing_dir = os.path.join(tempdir, "non-existing-dir") - non_existing_file = "non-existing-file" + non_existing_file = "non-existing-file.toml" samconfig = SamConfig(config_dir=non_existing_dir, filename=non_existing_file) self.assertFalse(samconfig.exists()) From badc2628182a285918edfdc91fac13e212613acf Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Fri, 2 Jun 2023 17:02:25 -0700 Subject: [PATCH 097/107] feat: Add YAML config file option (#5253) * Abstract SamConfig and decouple TOML logic * Fix documentation and comments * Generalize exception for FileManager * Remove FileManager logic to its own file * Fix bug in setting a default FileManager * Implement requested changes This includes additional logging messages, as well as explicitly requiring file extensions * Include supported extensions in log call * Implement requested changes * Update docstrings * Refactor changes to preserve TOML comments * Allow file document to update properly * Remove duplicate data Since TOMLDocument wraps a Python dictionary anyway, we don't need the separate information * Add put comment for FileManager * Implement requested changes * Format files according to standard * Implement helper method for dict-like to TOMLDocument * Implement YamlFileManager * Redefine YAML locally in class * Update YAML-cast method * Format correctly --------- Co-authored-by: Leonardo Gama --- samcli/lib/config/file_manager.py | 101 ++++++++++++++++++ samcli/lib/config/samconfig.py | 4 +- .../unit/commands/samconfig/test_samconfig.py | 17 ++- tests/unit/lib/samconfig/test_file_manager.py | 84 ++++++++++++++- 4 files changed, 199 insertions(+), 7 deletions(-) diff --git a/samcli/lib/config/file_manager.py b/samcli/lib/config/file_manager.py index 146946056f..49a60fdadd 100644 --- a/samcli/lib/config/file_manager.py +++ b/samcli/lib/config/file_manager.py @@ -9,6 +9,8 @@ from typing import Any import tomlkit +from ruamel.yaml import YAML, YAMLError +from ruamel.yaml.compat import StringIO from samcli.lib.config.exceptions import FileParseException @@ -159,3 +161,102 @@ def put_comment(document: dict, comment: str) -> Any: def _to_toml(document: dict) -> tomlkit.TOMLDocument: """Ensure that a dictionary-like object is a TOMLDocument.""" return tomlkit.parse(tomlkit.dumps(document)) + + +class YamlFileManager(FileManager): + """ + Static class to read and write yaml files. + """ + + yaml = YAML() + file_format = "YAML" + + @staticmethod + def read(filepath: Path) -> Any: + """ + Read a YAML file at the given path. + + Parameters + ---------- + filepath: Path + The Path object that points to the file to be read. + + Returns + ------- + Any + A dictionary-like yaml object, which represents the contents of the YAML file at the + provided location. + """ + yaml_doc = YamlFileManager.yaml.load("") + try: + yaml_doc = YamlFileManager.yaml.load(filepath.read_text()) + except OSError as e: + LOG.debug(f"OSError occurred while reading {YamlFileManager.file_format} file: {str(e)}") + except YAMLError as e: + raise FileParseException(e) from e + + return yaml_doc + + @staticmethod + def write(document: dict, filepath: Path): + """ + Write the contents of a dictionary to a YAML file at the provided location. + + Parameters + ---------- + document: dict + The object to write. + filepath: Path + The final location for the YAML file to be written. + """ + if not document: + LOG.debug("No document given to YamlFileManager to write.") + return + + yaml_doc = YamlFileManager._to_yaml(document) + + if yaml_doc.get(COMMENT_KEY, None): # Comment appears at the top of doc + yaml_doc.yaml_set_start_comment(document[COMMENT_KEY]) + yaml_doc.pop(COMMENT_KEY) + + YamlFileManager.yaml.dump(yaml_doc, filepath) + + @staticmethod + def put_comment(document: Any, comment: str) -> Any: + """ + Put a comment in a document object. + + Parameters + ---------- + document: Any + The yaml object to write + comment: str + The comment to include in the document. + + Returns + ------- + Any + The new yaml document, with the comment added to it. + """ + document = YamlFileManager._to_yaml(document) + document.yaml_set_start_comment(comment) + return document + + @staticmethod + def _to_yaml(document: dict) -> Any: + """ + Ensure a dictionary-like object is a YAML document. + + Parameters + ---------- + document: dict + A dictionary-like object to parse. + + Returns + ------- + Any + A dictionary-like YAML object, as derived from `yaml.load()`. + """ + with StringIO() as stream: + YamlFileManager.yaml.dump(document, stream) + return YamlFileManager.yaml.load(stream.getvalue()) diff --git a/samcli/lib/config/samconfig.py b/samcli/lib/config/samconfig.py index d70b8fe1bb..eb8f0f8f1c 100644 --- a/samcli/lib/config/samconfig.py +++ b/samcli/lib/config/samconfig.py @@ -8,7 +8,7 @@ from typing import Any, Dict, Iterable, Type from samcli.lib.config.exceptions import FileParseException, SamConfigFileReadException, SamConfigVersionException -from samcli.lib.config.file_manager import FileManager, TomlFileManager +from samcli.lib.config.file_manager import FileManager, TomlFileManager, YamlFileManager from samcli.lib.config.version import SAM_CONFIG_VERSION, VERSION_KEY LOG = logging.getLogger(__name__) @@ -26,6 +26,8 @@ class SamConfig: FILE_MANAGER_MAPPER: Dict[str, Type[FileManager]] = { ".toml": TomlFileManager, + ".yaml": YamlFileManager, + ".yml": YamlFileManager, } def __init__(self, config_dir, filename=None): diff --git a/tests/unit/commands/samconfig/test_samconfig.py b/tests/unit/commands/samconfig/test_samconfig.py index d5d857c3cd..c00425e5ea 100644 --- a/tests/unit/commands/samconfig/test_samconfig.py +++ b/tests/unit/commands/samconfig/test_samconfig.py @@ -14,7 +14,9 @@ from unittest import TestCase from unittest.mock import patch, ANY import logging +from parameterized import parameterized from samcli.lib.config.exceptions import SamConfigFileReadException +from samcli.lib.config.file_manager import YamlFileManager from samcli.lib.config.samconfig import SamConfig, DEFAULT_ENV, TomlFileManager from samcli.lib.utils.packagetype import ZIP, IMAGE @@ -1256,13 +1258,20 @@ def test_file_manager_not_declared(self): with self.assertRaises(SamConfigFileReadException): SamConfig(config_path, filename="samconfig") - def test_file_manager_toml(self): + @parameterized.expand( + [ + ("samconfig.toml", TomlFileManager), + ("samconfig.yaml", YamlFileManager), + ("samconfig.yml", YamlFileManager), + ] + ) + def test_file_manager(self, filename, expected_file_manager): config_dir = tempfile.gettempdir() - config_path = Path(config_dir, "samconfig.toml") + config_path = Path(config_dir, filename) - samconfig = SamConfig(config_path, filename="samconfig.toml") + samconfig = SamConfig(config_path, filename=filename) - self.assertIs(samconfig.file_manager, TomlFileManager) + self.assertIs(samconfig.file_manager, expected_file_manager) @contextmanager diff --git a/tests/unit/lib/samconfig/test_file_manager.py b/tests/unit/lib/samconfig/test_file_manager.py index 0973637023..d6b4fe82c0 100644 --- a/tests/unit/lib/samconfig/test_file_manager.py +++ b/tests/unit/lib/samconfig/test_file_manager.py @@ -3,9 +3,10 @@ from unittest import TestCase import tomlkit -from samcli.lib.config.exceptions import FileParseException +from ruamel.yaml import YAML -from samcli.lib.config.file_manager import COMMENT_KEY, TomlFileManager +from samcli.lib.config.exceptions import FileParseException +from samcli.lib.config.file_manager import COMMENT_KEY, TomlFileManager, YamlFileManager class TestTomlFileManager(TestCase): @@ -48,6 +49,7 @@ def test_write_toml(self): self.assertIn("[config_env.topic2.parameters]", txt) self.assertIn('word = "clarity"', txt) self.assertIn("# This is a comment", txt) + self.assertNotIn(COMMENT_KEY, txt) def test_dont_write_toml_if_empty(self): config_dir = tempfile.gettempdir() @@ -99,3 +101,81 @@ def test_toml_put_comment(self): txt = tomlkit.dumps(toml_doc) self.assertIn("# This is a comment", txt) + + +class TestYamlFileManager(TestCase): + + yaml = YAML() + + def test_read_yaml(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.yaml") + config_path.write_text("version: 0.1\nconfig_env:\n topic1:\n parameters:\n word: clarity\n") + + config_doc = YamlFileManager.read(config_path) + + self.assertEqual( + config_doc, + {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}}, + ) + + def test_read_yaml_invalid_yaml(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.yaml") + config_path.write_text("fake: not real\nthisYaml isn't correct") + + with self.assertRaises(FileParseException): + YamlFileManager.read(config_path) + + def test_read_yaml_file_path_not_valid(self): + config_dir = "path/that/doesnt/exist" + config_path = Path(config_dir, "samconfig.yaml") + + config_doc = YamlFileManager.read(config_path) + + self.assertEqual(config_doc, self.yaml.load("")) + + def test_write_yaml(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.yaml") + yaml = { + "version": 0.1, + "config_env": {"topic2": {"parameters": {"word": "clarity"}}}, + COMMENT_KEY: "This is a comment", + } + + YamlFileManager.write(yaml, config_path) + + txt = config_path.read_text() + self.assertIn("version: 0.1", txt) + self.assertIn("config_env:\n topic2:\n parameters:\n", txt) + self.assertIn("word: clarity", txt) + self.assertIn("# This is a comment", txt) + self.assertNotIn(COMMENT_KEY, txt) + + def test_dont_write_yaml_if_empty(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.yaml") + config_path.write_text("nothing to see here\n") + yaml = {} + + YamlFileManager.write(yaml, config_path) + + self.assertEqual(config_path.read_text(), "nothing to see here\n") + + def test_write_yaml_file_bad_path(self): + config_path = Path("path/to/some", "file_that_doesnt_exist.yaml") + + with self.assertRaises(FileNotFoundError): + YamlFileManager.write(self.yaml.load("key: some value"), config_path) + + def test_yaml_put_comment(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.yaml") + yaml_doc = self.yaml.load("version: 0.1\nconfig_env:\n topic2:\n parameters:\n word: clarity\n") + + yaml_doc = YamlFileManager.put_comment(yaml_doc, "This is a comment") + + self.yaml.dump(yaml_doc, config_path) + txt = config_path.read_text() + self.assertIn("# This is a comment", txt) From 99c6a1e7da7cc05e5c632fffae63ad789e382406 Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Tue, 6 Jun 2023 10:46:29 -0700 Subject: [PATCH 098/107] feat: Add JSON config file option (#5264) * Add JsonFileManager * Implement requested changes * Remove unused line in test --------- Co-authored-by: Leonardo Gama --- samcli/lib/config/file_manager.py | 76 ++++++++++++++++- samcli/lib/config/samconfig.py | 3 +- .../unit/commands/samconfig/test_samconfig.py | 12 ++- tests/unit/lib/samconfig/test_file_manager.py | 81 ++++++++++++++++++- 4 files changed, 166 insertions(+), 6 deletions(-) diff --git a/samcli/lib/config/file_manager.py b/samcli/lib/config/file_manager.py index 49a60fdadd..a898a5add4 100644 --- a/samcli/lib/config/file_manager.py +++ b/samcli/lib/config/file_manager.py @@ -3,6 +3,7 @@ """ +import json import logging from abc import ABC, abstractmethod from pathlib import Path @@ -37,8 +38,7 @@ def read(filepath: Path) -> Any: Returns ------- Any - A dictionary-like representation of the contents at the filepath location, along with a specialized - representation of the file that was read, if there is a specialization of it. + A dictionary-like representation of the contents at the filepath location. """ raise NotImplementedError("Read method not implemented.") @@ -260,3 +260,75 @@ def _to_yaml(document: dict) -> Any: with StringIO() as stream: YamlFileManager.yaml.dump(document, stream) return YamlFileManager.yaml.load(stream.getvalue()) + + +class JsonFileManager(FileManager): + """ + Static class to read and write json files. + """ + + file_format = "JSON" + INDENT_SIZE = 2 + + @staticmethod + def read(filepath: Path) -> Any: + """ + Read a JSON file at a given path. + + Parameters + ---------- + filepath: Path + The Path object that points to the file to be read. + + Returns + ------- + Any + A dictionary representation of the contents of the JSON document. + """ + json_file = {} + try: + json_file = json.loads(filepath.read_text()) + except OSError as e: + LOG.debug(f"OSError occurred while reading {JsonFileManager.file_format} file: {str(e)}") + except json.JSONDecodeError as e: + raise FileParseException(e) from e + return json_file + + @staticmethod + def write(document: dict, filepath: Path): + """ + Write a dictionary or dictionary-like object to a JSON file. + + Parameters + ---------- + document: dict + The JSON object to write. + filepath: Path + The final location for the document to be written. + """ + if not document: + LOG.debug("No document given to JsonFileManager to write.") + return + + with filepath.open("w") as file: + json.dump(document, file, indent=JsonFileManager.INDENT_SIZE) + + @staticmethod + def put_comment(document: Any, comment: str) -> Any: + """ + Put a comment in a JSON object. + + Parameters + ---------- + document: Any + The JSON object to write + comment: str + The comment to include in the document. + + Returns + ------- + Any + The new JSON dictionary object, with the comment added to it. + """ + document.update({COMMENT_KEY: comment}) + return document diff --git a/samcli/lib/config/samconfig.py b/samcli/lib/config/samconfig.py index eb8f0f8f1c..6b3e99bdd8 100644 --- a/samcli/lib/config/samconfig.py +++ b/samcli/lib/config/samconfig.py @@ -8,7 +8,7 @@ from typing import Any, Dict, Iterable, Type from samcli.lib.config.exceptions import FileParseException, SamConfigFileReadException, SamConfigVersionException -from samcli.lib.config.file_manager import FileManager, TomlFileManager, YamlFileManager +from samcli.lib.config.file_manager import FileManager, JsonFileManager, TomlFileManager, YamlFileManager from samcli.lib.config.version import SAM_CONFIG_VERSION, VERSION_KEY LOG = logging.getLogger(__name__) @@ -28,6 +28,7 @@ class SamConfig: ".toml": TomlFileManager, ".yaml": YamlFileManager, ".yml": YamlFileManager, + ".json": JsonFileManager, } def __init__(self, config_dir, filename=None): diff --git a/tests/unit/commands/samconfig/test_samconfig.py b/tests/unit/commands/samconfig/test_samconfig.py index c00425e5ea..af75bbed85 100644 --- a/tests/unit/commands/samconfig/test_samconfig.py +++ b/tests/unit/commands/samconfig/test_samconfig.py @@ -16,9 +16,9 @@ import logging from parameterized import parameterized from samcli.lib.config.exceptions import SamConfigFileReadException -from samcli.lib.config.file_manager import YamlFileManager +from samcli.lib.config.file_manager import JsonFileManager, TomlFileManager, YamlFileManager -from samcli.lib.config.samconfig import SamConfig, DEFAULT_ENV, TomlFileManager +from samcli.lib.config.samconfig import SamConfig, DEFAULT_ENV from samcli.lib.utils.packagetype import ZIP, IMAGE LOG = logging.getLogger() @@ -1258,11 +1258,19 @@ def test_file_manager_not_declared(self): with self.assertRaises(SamConfigFileReadException): SamConfig(config_path, filename="samconfig") + def test_file_manager_unsupported(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.jpeg") + + with self.assertRaises(SamConfigFileReadException): + SamConfig(config_path, filename="samconfig.jpeg") + @parameterized.expand( [ ("samconfig.toml", TomlFileManager), ("samconfig.yaml", YamlFileManager), ("samconfig.yml", YamlFileManager), + ("samconfig.json", JsonFileManager), ] ) def test_file_manager(self, filename, expected_file_manager): diff --git a/tests/unit/lib/samconfig/test_file_manager.py b/tests/unit/lib/samconfig/test_file_manager.py index d6b4fe82c0..362d0a3b27 100644 --- a/tests/unit/lib/samconfig/test_file_manager.py +++ b/tests/unit/lib/samconfig/test_file_manager.py @@ -1,3 +1,4 @@ +import json from pathlib import Path import tempfile from unittest import TestCase @@ -6,7 +7,7 @@ from ruamel.yaml import YAML from samcli.lib.config.exceptions import FileParseException -from samcli.lib.config.file_manager import COMMENT_KEY, TomlFileManager, YamlFileManager +from samcli.lib.config.file_manager import COMMENT_KEY, JsonFileManager, TomlFileManager, YamlFileManager class TestTomlFileManager(TestCase): @@ -179,3 +180,81 @@ def test_yaml_put_comment(self): self.yaml.dump(yaml_doc, config_path) txt = config_path.read_text() self.assertIn("# This is a comment", txt) + + +class TestJsonFileManager(TestCase): + def test_read_json(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.json") + config_path.write_text( + json.dumps( + {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}}, + indent=JsonFileManager.INDENT_SIZE, + ) + ) + + config_doc = JsonFileManager.read(config_path) + + self.assertEqual( + config_doc, + {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}}, + ) + + def test_read_json_invalid_json(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.json") + config_path.write_text("{\n" + ' "bad_file": "very bad"\n' + ' "improperly": "formatted"\n' + "}\n") + + with self.assertRaises(FileParseException): + JsonFileManager.read(config_path) + + def test_read_json_file_path_not_valid(self): + config_dir = "path/that/doesnt/exist" + config_path = Path(config_dir, "samconfig.json") + + config_doc = JsonFileManager.read(config_path) + + self.assertEqual(config_doc, {}) + + def test_write_json(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.json") + json_doc = { + "version": 0.1, + "config_env": {"topic2": {"parameters": {"word": "clarity"}}}, + COMMENT_KEY: "This is a comment", + } + + JsonFileManager.write(json_doc, config_path) + + txt = config_path.read_text() + self.assertIn('"version": 0.1', txt) + self.assertIn('"config_env": {', txt) + self.assertIn('"topic2": {', txt) + self.assertIn('"parameters": {', txt) + self.assertIn('"word": "clarity"', txt) + self.assertIn(f'"{COMMENT_KEY}": "This is a comment"', txt) + + def test_dont_write_json_if_empty(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.json") + config_path.write_text("nothing to see here\n") + json_doc = {} + + JsonFileManager.write(json_doc, config_path) + + self.assertEqual(config_path.read_text(), "nothing to see here\n") + + def test_write_json_file_bad_path(self): + config_path = Path("path/to/some", "file_that_doesnt_exist.json") + + with self.assertRaises(FileNotFoundError): + JsonFileManager.write({"key": "value"}, config_path) + + def test_json_put_comment(self): + json_doc = {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}} + + json_doc = JsonFileManager.put_comment(json_doc, "This is a comment") + + txt = json.dumps(json_doc) + self.assertIn(f'"{COMMENT_KEY}": "This is a comment"', txt) From 40ddb56f89f4a55eb451886de541bdeb9ede8f6d Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Wed, 7 Jun 2023 11:06:56 -0700 Subject: [PATCH 099/107] chore: Refactor TomlProvider to ConfigProvider (#5273) Also, update docstrings to be in NumPy/SciPy format Co-authored-by: Leonardo Gama --- samcli/cli/cli_config_file.py | 179 ++++++++++++------ .../_utils/custom_options/hook_name_option.py | 2 +- samcli/commands/build/command.py | 4 +- samcli/commands/delete/delete_context.py | 4 +- samcli/commands/deploy/command.py | 4 +- samcli/commands/init/command.py | 4 +- samcli/commands/list/endpoints/command.py | 4 +- samcli/commands/list/resources/command.py | 4 +- samcli/commands/list/stack_outputs/command.py | 4 +- .../local/generate_event/event_generation.py | 4 +- samcli/commands/local/invoke/cli.py | 4 +- samcli/commands/local/start_api/cli.py | 2 +- samcli/commands/local/start_lambda/cli.py | 4 +- samcli/commands/logs/command.py | 2 +- samcli/commands/package/command.py | 2 +- samcli/commands/pipeline/bootstrap/cli.py | 4 +- samcli/commands/pipeline/init/cli.py | 4 +- samcli/commands/publish/command.py | 4 +- samcli/commands/sync/command.py | 4 +- samcli/commands/traces/command.py | 4 +- samcli/commands/validate/validate.py | 4 +- tests/unit/cli/test_cli_config_file.py | 24 +-- .../commands/delete/test_delete_context.py | 8 +- 23 files changed, 178 insertions(+), 105 deletions(-) diff --git a/samcli/cli/cli_config_file.py b/samcli/cli/cli_config_file.py index e4606e4555..8b56d033e9 100644 --- a/samcli/cli/cli_config_file.py +++ b/samcli/cli/cli_config_file.py @@ -10,6 +10,7 @@ import logging import os from pathlib import Path +from typing import Any, Callable, Dict, List, Optional import click @@ -17,38 +18,52 @@ from samcli.commands.exceptions import ConfigException from samcli.lib.config.samconfig import DEFAULT_CONFIG_FILE_NAME, DEFAULT_ENV, SamConfig -__all__ = ("TomlProvider", "configuration_option", "get_ctx_defaults") +__all__ = ("ConfigProvider", "configuration_option", "get_ctx_defaults") LOG = logging.getLogger(__name__) -class TomlProvider: +class ConfigProvider: """ - A parser for toml configuration files + A parser for sam configuration files """ def __init__(self, section=None, cmd_names=None): """ - The constructor for TomlProvider class - :param section: section defined in the configuration file nested within `cmd` - :param cmd_names: cmd_name defined in the configuration file + The constructor for ConfigProvider class + + Parameters + ---------- + section + The section defined in the configuration file nested within `cmd` + cmd_names + The cmd_name defined in the configuration file """ self.section = section self.cmd_names = cmd_names - def __call__(self, config_path, config_env, cmd_names): + def __call__(self, config_path: Path, config_env: str, cmd_names: List[str]) -> dict: """ Get resolved config based on the `file_path` for the configuration file, `config_env` targeted inside the config file and corresponding `cmd_name` as denoted by `click`. - :param config_path: The path of configuration file. - :param config_env: The name of the sectional config_env within configuration file. - :param list cmd_names: sam command name as defined by click - :returns dictionary containing the configuration parameters under specified config_env + Parameters + ---------- + config_path: Path + The path of configuration file. + config_env: str + The name of the sectional config_env within configuration file. + cmd_names: List[str] + The sam command name as defined by click. + + Returns + ------- + dict + A dictionary containing the configuration parameters under specified config_env. """ - resolved_config = {} + resolved_config: dict = {} # Use default sam config file name if config_path only contain the directory config_file_path = ( @@ -105,27 +120,47 @@ def __call__(self, config_path, config_env, cmd_names): return resolved_config -def configuration_callback(cmd_name, option_name, saved_callback, provider, ctx, param, value): +def configuration_callback( + cmd_name: str, + option_name: str, + saved_callback: Optional[Callable], + provider: Callable, + ctx: click.Context, + param: click.Parameter, + value, +): """ Callback for reading the config file. Also takes care of calling user specified custom callback afterwards. - :param cmd_name: `sam` command name derived from click. - :param option_name: The name of the option. This is used for error messages. - :param saved_callback: User-specified callback to be called later. - :param provider: A callable that parses the configuration file and returns a dictionary + Parameters + ---------- + cmd_name: str + The `sam` command name derived from click. + option_name: str + The name of the option. This is used for error messages. + saved_callback: Optional[Callable] + User-specified callback to be called later. + provider: Callable + A callable that parses the configuration file and returns a dictionary of the configuration parameters. Will be called as `provider(file_path, config_env, cmd_name)`. - :param ctx: Click context - :param param: Click parameter - :param value: Specified value for config_env - :returns specified callback or the specified value for config_env. + ctx: click.Context + Click context + param: click.Parameter + Click parameter + value + Specified value for config_env + + Returns + ------- + The specified callback or the specified value for config_env. """ # ctx, param and value are default arguments for click specified callbacks. ctx.default_map = ctx.default_map or {} - cmd_name = cmd_name or ctx.info_name + cmd_name = cmd_name or str(ctx.info_name) param.default = None config_env_name = ctx.params.get("config_env") or DEFAULT_ENV @@ -154,21 +189,35 @@ def configuration_callback(cmd_name, option_name, saved_callback, provider, ctx, return saved_callback(ctx, param, config_env_name) if saved_callback else config_env_name -def get_ctx_defaults(cmd_name, provider, ctx, config_env_name, config_file=None): +def get_ctx_defaults( + cmd_name: str, provider: Callable, ctx: click.Context, config_env_name: str, config_file: Optional[str] = None +) -> Any: """ Get the set of the parameters that are needed to be set into the click command. + This function also figures out the command name by looking up current click context's parent and constructing the parsed command name that is used in default configuration file. If a given cmd_name is start-api, the parsed name is "local_start_api". provider is called with `config_file`, `config_env_name` and `parsed_cmd_name`. - :param cmd_name: `sam` command name - :param provider: provider to be called for reading configuration file - :param ctx: Click context - :param config_env_name: config-env within configuration file, sam configuration file will be relative to the - supplied original template if its path is not specified - :param config_file: configuration file name - :return: dictionary of defaults for parameters + Parameters + ---------- + cmd_name: str + The `sam` command name. + provider: Callable + The provider to be called for reading configuration file. + ctx: click.Context + Click context + config_env_name: str + The config-env within configuration file, sam configuration file will be relative to the + supplied original template if its path is not specified. + config_file: Optional[str] + The configuration file name. + + Returns + ------- + Any + A dictionary of defaults for parameters. """ return provider(config_file, config_env_name, get_cmd_names(cmd_name, ctx)) @@ -180,30 +229,38 @@ def configuration_option(*param_decls, **attrs): """ Adds configuration file support to a click application. - NOTE: This decorator should be added to the top of parameter chain, right below click.command, before - any options are declared. - - Example: - >>> @click.command("hello") - @configuration_option(provider=TomlProvider(section="parameters")) - @click.option('--name', type=click.String) - def hello(name): - print("Hello " + name) - This will create a hidden click option whose callback function loads configuration parameters from default configuration environment [default] in default configuration file [samconfig.toml] in the template file directory. - :param preconfig_decorator_list: A list of click option decorator which need to place before this function. For - exmple, if we want to add option "--config-file" and "--config-env" to allow customized configuration file + + Note + ---- + This decorator should be added to the top of parameter chain, right below click.command, before + any options are declared. + + Example + ------- + >>> @click.command("hello") + @configuration_option(provider=ConfigProvider(section="parameters")) + @click.option('--name', type=click.String) + def hello(name): + print("Hello " + name) + + Parameters + ---------- + preconfig_decorator_list: list + A list of click option decorator which need to place before this function. For + example, if we want to add option "--config-file" and "--config-env" to allow customized configuration file and configuration environment, we will use configuration_option as below: @configuration_option( preconfig_decorator_list=[decorator_customize_config_file, decorator_customize_config_env], - provider=TomlProvider(section=CONFIG_SECTION), + provider=ConfigProvider(section=CONFIG_SECTION), ) By default, we enable these two options. - :param provider: A callable that parses the configuration file and returns a dictionary + provider: Callable + A callable that parses the configuration file and returns a dictionary of the configuration parameters. Will be called as - `provider(file_path, config_env, cmd_name) + `provider(file_path, config_env, cmd_name)` """ def decorator_configuration_setup(f): @@ -240,14 +297,22 @@ def decorator(f): return composed_decorator(decorator_list) -def decorator_customize_config_file(f): +def decorator_customize_config_file(f: Callable) -> Callable: """ CLI option to customize configuration file name. By default it is 'samconfig.toml' in project directory. Ex: --config-file samconfig.toml - :param f: Callback function passed by Click - :return: Callback function + + Parameters + ---------- + f: Callable + Callback function passed by Click + + Returns + ------- + Callable + A Callback function """ - config_file_attrs = {} + config_file_attrs: Dict[str, Any] = {} config_file_param_decls = ("--config-file",) config_file_attrs["help"] = "Configuration file containing default parameter values." config_file_attrs["default"] = "samconfig.toml" @@ -258,14 +323,22 @@ def decorator_customize_config_file(f): return click.option(*config_file_param_decls, **config_file_attrs)(f) -def decorator_customize_config_env(f): +def decorator_customize_config_env(f: Callable) -> Callable: """ CLI option to customize configuration environment name. By default it is 'default'. Ex: --config-env default - :param f: Callback function passed by Click - :return: Callback function + + Parameters + ---------- + f: Callable + Callback function passed by Click + + Returns + ------- + Callable + A Callback function """ - config_env_attrs = {} + config_env_attrs: Dict[str, Any] = {} config_env_param_decls = ("--config-env",) config_env_attrs["help"] = "Environment name specifying default parameter values in the configuration file." config_env_attrs["default"] = "default" diff --git a/samcli/commands/_utils/custom_options/hook_name_option.py b/samcli/commands/_utils/custom_options/hook_name_option.py index 745ab6c64a..a2cb334157 100644 --- a/samcli/commands/_utils/custom_options/hook_name_option.py +++ b/samcli/commands/_utils/custom_options/hook_name_option.py @@ -141,7 +141,7 @@ def _get_customer_input_beta_features_option(default_map, experimental_entry, op if beta_features is not None: return beta_features - # Get the beta-features flag value from the SamConfig toml file if provided. + # Get the beta-features flag value from the SamConfig file if provided. beta_features = default_map.get("beta_features") if beta_features is not None: return beta_features diff --git a/samcli/commands/build/command.py b/samcli/commands/build/command.py index a60f9954e1..86327d411d 100644 --- a/samcli/commands/build/command.py +++ b/samcli/commands/build/command.py @@ -27,7 +27,7 @@ from samcli.cli.main import pass_context, common_options as cli_framework_options, aws_creds_options, print_cmdline_args from samcli.commands.build.core.command import BuildCommand from samcli.lib.telemetry.metric import track_command -from samcli.cli.cli_config_file import configuration_option, TomlProvider +from samcli.cli.cli_config_file import configuration_option, ConfigProvider from samcli.lib.utils.version_checker import check_newer_version from samcli.commands.build.click_container import ContainerOptions from samcli.commands.build.utils import MountMode @@ -69,7 +69,7 @@ short_help=HELP_TEXT, context_settings={"max_content_width": 120}, ) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @hook_name_click_option( force_prepare=True, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"], diff --git a/samcli/commands/delete/delete_context.py b/samcli/commands/delete/delete_context.py index 08d327eceb..3928c0cddb 100644 --- a/samcli/commands/delete/delete_context.py +++ b/samcli/commands/delete/delete_context.py @@ -82,8 +82,8 @@ def parse_config_file(self): """ Read the provided config file if it exists and assign the options values. """ - toml_provider = TomlProvider(CONFIG_SECTION, [CONFIG_COMMAND]) - config_options = toml_provider( + config_provider = ConfigProvider(CONFIG_SECTION, [CONFIG_COMMAND]) + config_options = config_provider( config_path=self.config_file, config_env=self.config_env, cmd_names=[CONFIG_COMMAND] ) if not config_options: diff --git a/samcli/commands/deploy/command.py b/samcli/commands/deploy/command.py index aeefe4d25f..557a601261 100644 --- a/samcli/commands/deploy/command.py +++ b/samcli/commands/deploy/command.py @@ -6,7 +6,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.commands._utils.cdk_support_decorators import unsupported_command_cdk from samcli.commands._utils.click_mutex import ClickMutex @@ -75,7 +75,7 @@ description=DESCRIPTION, requires_credentials=True, ) -@configuration_option(provider=TomlProvider(section=CONFIG_SECTION)) +@configuration_option(provider=ConfigProvider(section=CONFIG_SECTION)) @click.option( "--guided", "-g", diff --git a/samcli/commands/init/command.py b/samcli/commands/init/command.py index 8702b3f9a9..f31e967a04 100644 --- a/samcli/commands/init/command.py +++ b/samcli/commands/init/command.py @@ -7,7 +7,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import common_options, pass_context, print_cmdline_args from samcli.commands._utils.click_mutex import ClickMutex from samcli.commands.init.core.command import InitCommand @@ -112,7 +112,7 @@ def wrapped(*args, **kwargs): description=DESCRIPTION, requires_credentials=False, ) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @click.option( "--no-interactive", is_flag=True, diff --git a/samcli/commands/list/endpoints/command.py b/samcli/commands/list/endpoints/command.py index 6de1c41bb2..f11543ef8c 100644 --- a/samcli/commands/list/endpoints/command.py +++ b/samcli/commands/list/endpoints/command.py @@ -4,7 +4,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.commands._utils.command_exception_handler import command_exception_handler from samcli.commands._utils.options import parameter_override_option, template_option_without_build @@ -21,7 +21,7 @@ @click.command(name="endpoints", help=HELP_TEXT) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @parameter_override_option @stack_name_option @output_option diff --git a/samcli/commands/list/resources/command.py b/samcli/commands/list/resources/command.py index 5dd2b41034..dacfac30e2 100644 --- a/samcli/commands/list/resources/command.py +++ b/samcli/commands/list/resources/command.py @@ -4,7 +4,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.commands._utils.command_exception_handler import command_exception_handler from samcli.commands._utils.options import parameter_override_option, template_option_without_build @@ -20,7 +20,7 @@ @click.command(name="resources", help=HELP_TEXT) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @parameter_override_option @stack_name_option @output_option diff --git a/samcli/commands/list/stack_outputs/command.py b/samcli/commands/list/stack_outputs/command.py index 3800c009b2..e988f98045 100644 --- a/samcli/commands/list/stack_outputs/command.py +++ b/samcli/commands/list/stack_outputs/command.py @@ -4,7 +4,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.commands._utils.command_exception_handler import command_exception_handler from samcli.commands.list.cli_common.options import output_option @@ -23,7 +23,7 @@ required=True, type=click.STRING, ) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @output_option @aws_creds_options @common_options diff --git a/samcli/commands/local/generate_event/event_generation.py b/samcli/commands/local/generate_event/event_generation.py index 5715bded73..9bf8e49e7a 100644 --- a/samcli/commands/local/generate_event/event_generation.py +++ b/samcli/commands/local/generate_event/event_generation.py @@ -6,7 +6,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.options import debug_option from samcli.lib.generated_sample_events import events from samcli.lib.telemetry.metric import track_command @@ -160,7 +160,7 @@ def get_command(self, ctx, cmd_name): callback=command_callback, ) - cmd = configuration_option(provider=TomlProvider(section="parameters"))(debug_option(cmd)) + cmd = configuration_option(provider=ConfigProvider(section="parameters"))(debug_option(cmd)) return cmd def list_commands(self, ctx): diff --git a/samcli/commands/local/invoke/cli.py b/samcli/commands/local/invoke/cli.py index a9a3fc9571..0442e7b7ed 100644 --- a/samcli/commands/local/invoke/cli.py +++ b/samcli/commands/local/invoke/cli.py @@ -6,7 +6,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled @@ -43,7 +43,7 @@ short_help=HELP_TEXT, context_settings={"max_content_width": 120}, ) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index 9de4d7982c..20ba3c4274 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -6,7 +6,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled diff --git a/samcli/commands/local/start_lambda/cli.py b/samcli/commands/local/start_lambda/cli.py index 9aaec50976..ded8b786fc 100644 --- a/samcli/commands/local/start_lambda/cli.py +++ b/samcli/commands/local/start_lambda/cli.py @@ -6,7 +6,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled @@ -52,7 +52,7 @@ requires_credentials=False, context_settings={"max_content_width": 120}, ) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @hook_name_click_option( force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] ) diff --git a/samcli/commands/logs/command.py b/samcli/commands/logs/command.py index 1146767a60..179ec78654 100644 --- a/samcli/commands/logs/command.py +++ b/samcli/commands/logs/command.py @@ -6,7 +6,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.command_exception_handler import command_exception_handler diff --git a/samcli/commands/package/command.py b/samcli/commands/package/command.py index 41fb10b133..5f0c5dd6da 100644 --- a/samcli/commands/package/command.py +++ b/samcli/commands/package/command.py @@ -3,7 +3,7 @@ """ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.commands._utils.cdk_support_decorators import unsupported_command_cdk from samcli.commands._utils.command_exception_handler import command_exception_handler diff --git a/samcli/commands/pipeline/bootstrap/cli.py b/samcli/commands/pipeline/bootstrap/cli.py index d357adf5dd..943c3ef68f 100644 --- a/samcli/commands/pipeline/bootstrap/cli.py +++ b/samcli/commands/pipeline/bootstrap/cli.py @@ -7,7 +7,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.commands._utils.click_mutex import ClickMutex from samcli.commands._utils.command_exception_handler import command_exception_handler @@ -38,7 +38,7 @@ @click.command("bootstrap", short_help=SHORT_HELP, help=HELP_TEXT, context_settings=dict(max_content_width=120)) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @click.option( "--interactive/--no-interactive", is_flag=True, diff --git a/samcli/commands/pipeline/init/cli.py b/samcli/commands/pipeline/init/cli.py index 9e42f6e74b..ec675fb608 100644 --- a/samcli/commands/pipeline/init/cli.py +++ b/samcli/commands/pipeline/init/cli.py @@ -5,7 +5,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import common_options as cli_framework_options from samcli.cli.main import pass_context from samcli.commands._utils.command_exception_handler import command_exception_handler @@ -24,7 +24,7 @@ @click.command("init", help=HELP_TEXT, short_help=SHORT_HELP) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @click.option( "--bootstrap", is_flag=True, diff --git a/samcli/commands/publish/command.py b/samcli/commands/publish/command.py index 091a044192..4ebfb3e8e5 100644 --- a/samcli/commands/publish/command.py +++ b/samcli/commands/publish/command.py @@ -7,7 +7,7 @@ import click from serverlessrepo.publish import CREATE_APPLICATION -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.command_exception_handler import command_exception_handler @@ -44,7 +44,7 @@ @click.command("publish", help=HELP_TEXT, short_help=SHORT_HELP) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @template_common_option @click.option("--semantic-version", help=SEMANTIC_VERSION_HELP) @aws_creds_options diff --git a/samcli/commands/sync/command.py b/samcli/commands/sync/command.py index 81f1222207..ddc5e2a165 100644 --- a/samcli/commands/sync/command.py +++ b/samcli/commands/sync/command.py @@ -5,7 +5,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.context import Context from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options @@ -114,7 +114,7 @@ requires_credentials=True, context_settings={"max_content_width": 120}, ) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @template_option_without_build @click.option( "--code", diff --git a/samcli/commands/traces/command.py b/samcli/commands/traces/command.py index 183a2bd156..d82b6871ab 100644 --- a/samcli/commands/traces/command.py +++ b/samcli/commands/traces/command.py @@ -5,7 +5,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.command_exception_handler import command_exception_handler @@ -28,7 +28,7 @@ @click.command("traces", help=HELP_TEXT, short_help="Fetch AWS X-Ray traces") -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @click.option( "--trace-id", "-ti", diff --git a/samcli/commands/validate/validate.py b/samcli/commands/validate/validate.py index 1c5c2b28b1..221284b1ac 100644 --- a/samcli/commands/validate/validate.py +++ b/samcli/commands/validate/validate.py @@ -8,7 +8,7 @@ from botocore.exceptions import NoCredentialsError from samtranslator.translator.arn_generator import NoRegionFound -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.context import Context from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options @@ -35,7 +35,7 @@ requires_credentials=False, context_settings={"max_content_width": 120}, ) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @template_option_without_build @aws_creds_options @cli_framework_options diff --git a/tests/unit/cli/test_cli_config_file.py b/tests/unit/cli/test_cli_config_file.py index 3395c12786..93502deb44 100644 --- a/tests/unit/cli/test_cli_config_file.py +++ b/tests/unit/cli/test_cli_config_file.py @@ -8,7 +8,7 @@ import tomlkit from samcli.commands.exceptions import ConfigException -from samcli.cli.cli_config_file import TomlProvider, configuration_option, configuration_callback, get_ctx_defaults +from samcli.cli.cli_config_file import ConfigProvider, configuration_option, configuration_callback, get_ctx_defaults from samcli.lib.config.exceptions import FileParseException, SamConfigFileReadException, SamConfigVersionException from samcli.lib.config.samconfig import DEFAULT_ENV, TomlFileManager @@ -24,9 +24,9 @@ def __init__(self, info_name, parent, params=None, command=None, default_map=Non self.default_map = default_map -class TestTomlProvider(TestCase): +class TestConfigProvider(TestCase): def setUp(self): - self.toml_provider = TomlProvider() + self.config_provider = ConfigProvider() self.config_env = "config_env" self.parameters = "parameters" self.cmd_name = "topic" @@ -36,7 +36,7 @@ def test_toml_valid_with_section(self): config_path = Path(config_dir, "samconfig.toml") config_path.write_text("version=0.1\n[config_env.topic.parameters]\nword='clarity'\n") self.assertEqual( - TomlProvider(section=self.parameters)(config_path, self.config_env, [self.cmd_name]), {"word": "clarity"} + ConfigProvider(section=self.parameters)(config_path, self.config_env, [self.cmd_name]), {"word": "clarity"} ) def test_toml_valid_with_no_version(self): @@ -44,14 +44,14 @@ def test_toml_valid_with_no_version(self): config_path = Path(config_dir, "samconfig.toml") config_path.write_text("[config_env.topic.parameters]\nword='clarity'\n") with self.assertRaises(SamConfigVersionException): - TomlProvider(section=self.parameters)(config_path, self.config_env, [self.cmd_name]) + ConfigProvider(section=self.parameters)(config_path, self.config_env, [self.cmd_name]) def test_toml_valid_with_invalid_version(self): config_dir = tempfile.gettempdir() config_path = Path(config_dir, "samconfig.toml") config_path.write_text("version='abc'\n[config_env.topic.parameters]\nword='clarity'\n") with self.assertRaises(SamConfigVersionException): - TomlProvider(section=self.parameters)(config_path, self.config_env, [self.cmd_name]) + ConfigProvider(section=self.parameters)(config_path, self.config_env, [self.cmd_name]) def test_toml_invalid_empty_dict(self): config_dir = tempfile.gettempdir() @@ -59,7 +59,7 @@ def test_toml_invalid_empty_dict(self): config_path.write_text("[topic]\nword=clarity\n") with self.assertRaises(SamConfigFileReadException): - self.toml_provider(config_path, self.config_env, [self.cmd_name]) + self.config_provider(config_path, self.config_env, [self.cmd_name]) def test_toml_invalid_file_name(self): config_dir = tempfile.gettempdir() @@ -68,7 +68,7 @@ def test_toml_invalid_file_name(self): config_path_invalid = Path(config_dir, "samconfig.toml") with self.assertRaises(SamConfigFileReadException): - self.toml_provider(config_path_invalid, self.config_env, [self.cmd_name]) + self.config_provider(config_path_invalid, self.config_env, [self.cmd_name]) def test_toml_invalid_syntax(self): config_dir = tempfile.gettempdir() @@ -76,7 +76,7 @@ def test_toml_invalid_syntax(self): config_path.write_text("version=0.1\n[config_env.topic.parameters]\nword=_clarity'\n") with self.assertRaises(SamConfigFileReadException): - self.toml_provider(config_path, self.config_env, [self.cmd_name]) + self.config_provider(config_path, self.config_env, [self.cmd_name]) class TestCliConfiguration(TestCase): @@ -201,8 +201,8 @@ def test_callback_with_config_file_from_pipe(self): self.assertNotIn(self.value, self.saved_callback.call_args[0]) def test_configuration_option(self): - toml_provider = TomlProvider() - click_option = configuration_option(provider=toml_provider) + config_provider = ConfigProvider() + click_option = configuration_option(provider=config_provider) clc = click_option(self.Dummy()) self.assertEqual(clc.__click_params__[0].is_eager, True) self.assertEqual( @@ -211,7 +211,7 @@ def test_configuration_option(self): ) self.assertEqual(clc.__click_params__[0].hidden, True) self.assertEqual(clc.__click_params__[0].expose_value, False) - self.assertEqual(clc.__click_params__[0].callback.args, (None, None, None, toml_provider)) + self.assertEqual(clc.__click_params__[0].callback.args, (None, None, None, config_provider)) def test_get_ctx_defaults_non_nested(self): provider = MagicMock() diff --git a/tests/unit/commands/delete/test_delete_context.py b/tests/unit/commands/delete/test_delete_context.py index daa72c187b..92e2aa2c6a 100644 --- a/tests/unit/commands/delete/test_delete_context.py +++ b/tests/unit/commands/delete/test_delete_context.py @@ -7,7 +7,7 @@ from samcli.commands.delete.delete_context import DeleteContext from samcli.lib.package.artifact_exporter import Template -from samcli.cli.cli_config_file import TomlProvider +from samcli.cli.cli_config_file import ConfigProvider from samcli.lib.delete.cfn_utils import CfnUtils from samcli.lib.package.s3_uploader import S3Uploader from samcli.lib.package.ecr_uploader import ECRUploader @@ -58,7 +58,7 @@ def test_delete_context_enter(self, get_boto_client_provider_mock): self.assertEqual(delete_context.init_clients.call_count, 1) @patch.object( - TomlProvider, + ConfigProvider, "__call__", MagicMock( return_value=( @@ -123,7 +123,7 @@ def test_delete_no_user_input( self.assertEqual(expected_prompt_calls, patched_prompt.call_args_list) @patch.object( - TomlProvider, + ConfigProvider, "__call__", MagicMock( return_value=( @@ -506,7 +506,7 @@ def test_s3_option_flag(self): self.assertEqual(delete_context.s3_prefix, "s3_prefix") @patch.object( - TomlProvider, + ConfigProvider, "__call__", MagicMock( return_value=( From 6c9ebc326d893ab76d9f39090835c58ca1e95c65 Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Thu, 8 Jun 2023 16:43:09 -0700 Subject: [PATCH 100/107] feat: Add hierarchy for samconfig filetypes (#5297) * Add hierarchy for samconfig default filetypes * Formatting and fixing tests * Implement requested changes * Fix logic to properly allow default name * Fix linting issue * Fix failing Windows test * Update default config name in guided config --------- Co-authored-by: Leonardo Gama --- samcli/cli/cli_config_file.py | 4 +- samcli/commands/deploy/guided_config.py | 7 +- samcli/lib/config/file_manager.py | 2 +- samcli/lib/config/samconfig.py | 41 ++++++++- .../unit/commands/samconfig/test_samconfig.py | 35 -------- tests/unit/lib/samconfig/test_file_manager.py | 2 +- tests/unit/lib/samconfig/test_samconfig.py | 86 ++++++++++++++++++- 7 files changed, 131 insertions(+), 46 deletions(-) diff --git a/samcli/cli/cli_config_file.py b/samcli/cli/cli_config_file.py index 8b56d033e9..2929a434e9 100644 --- a/samcli/cli/cli_config_file.py +++ b/samcli/cli/cli_config_file.py @@ -67,7 +67,9 @@ def __call__(self, config_path: Path, config_env: str, cmd_names: List[str]) -> # Use default sam config file name if config_path only contain the directory config_file_path = ( - Path(os.path.abspath(config_path)) if config_path else Path(os.getcwd(), DEFAULT_CONFIG_FILE_NAME) + Path(os.path.abspath(config_path)) + if config_path + else Path(os.getcwd(), SamConfig.get_default_file(os.getcwd())) ) config_file_name = config_file_path.name config_file_dir = config_file_path.parents[0] diff --git a/samcli/commands/deploy/guided_config.py b/samcli/commands/deploy/guided_config.py index 10b80189db..78866944cd 100644 --- a/samcli/commands/deploy/guided_config.py +++ b/samcli/commands/deploy/guided_config.py @@ -8,7 +8,7 @@ from samcli.cli.context import get_cmd_names from samcli.commands.deploy.exceptions import GuidedDeployFailedError from samcli.lib.config.exceptions import SamConfigFileReadException -from samcli.lib.config.samconfig import DEFAULT_CONFIG_FILE_NAME, DEFAULT_ENV, SamConfig +from samcli.lib.config.samconfig import DEFAULT_ENV, SamConfig class GuidedConfig: @@ -20,9 +20,10 @@ def get_config_ctx(self, config_file=None): ctx = click.get_current_context() samconfig_dir = getattr(ctx, "samconfig_dir", None) + config_dir = samconfig_dir if samconfig_dir else SamConfig.config_dir(template_file_path=self.template_file) samconfig = SamConfig( - config_dir=samconfig_dir if samconfig_dir else SamConfig.config_dir(template_file_path=self.template_file), - filename=config_file or DEFAULT_CONFIG_FILE_NAME, + config_dir=config_dir, + filename=config_file or SamConfig.get_default_file(config_dir=config_dir), ) return ctx, samconfig diff --git a/samcli/lib/config/file_manager.py b/samcli/lib/config/file_manager.py index a898a5add4..abbdade71d 100644 --- a/samcli/lib/config/file_manager.py +++ b/samcli/lib/config/file_manager.py @@ -187,7 +187,7 @@ def read(filepath: Path) -> Any: A dictionary-like yaml object, which represents the contents of the YAML file at the provided location. """ - yaml_doc = YamlFileManager.yaml.load("") + yaml_doc = {} try: yaml_doc = YamlFileManager.yaml.load(filepath.read_text()) except OSError as e: diff --git a/samcli/lib/config/samconfig.py b/samcli/lib/config/samconfig.py index 6b3e99bdd8..a77e44c70c 100644 --- a/samcli/lib/config/samconfig.py +++ b/samcli/lib/config/samconfig.py @@ -14,7 +14,8 @@ LOG = logging.getLogger(__name__) DEFAULT_CONFIG_FILE_EXTENSION = ".toml" -DEFAULT_CONFIG_FILE_NAME = f"samconfig{DEFAULT_CONFIG_FILE_EXTENSION}" +DEFAULT_CONFIG_FILE = "samconfig" +DEFAULT_CONFIG_FILE_NAME = DEFAULT_CONFIG_FILE + DEFAULT_CONFIG_FILE_EXTENSION DEFAULT_ENV = "default" DEFAULT_GLOBAL_CMDNAME = "global" @@ -24,7 +25,7 @@ class SamConfig: Class to represent `samconfig` config options. """ - FILE_MANAGER_MAPPER: Dict[str, Type[FileManager]] = { + FILE_MANAGER_MAPPER: Dict[str, Type[FileManager]] = { # keys ordered by priority ".toml": TomlFileManager, ".yaml": YamlFileManager, ".yml": YamlFileManager, @@ -44,7 +45,7 @@ def __init__(self, config_dir, filename=None): could automatically support auto-resolving multiple config files within same directory. """ self.document = {} - self.filepath = Path(config_dir, filename or DEFAULT_CONFIG_FILE_NAME) + self.filepath = Path(config_dir, filename or self.get_default_file(config_dir=config_dir)) self.file_manager = self.FILE_MANAGER_MAPPER.get(self.filepath.suffix, None) if not self.file_manager: LOG.warning( @@ -247,6 +248,40 @@ def _deduplicate_global_parameters(self, cmd_name_key, section, key, env=DEFAULT # Only keep the global parameter del self.document[env][cmd_name_key][section][key] + @staticmethod + def get_default_file(config_dir: str) -> str: + """Return a defaultly-named config file, if it exists, otherwise the current default. + + Parameters + ---------- + config_dir: str + The name of the directory where the config file is/will be stored. + + Returns + ------- + str + The name of the config file found, if it exists. In the case that it does not exist, the default config + file name is returned instead. + """ + config_files_found = 0 + config_file = DEFAULT_CONFIG_FILE_NAME + + for extension in reversed(list(SamConfig.FILE_MANAGER_MAPPER.keys())): + filename = DEFAULT_CONFIG_FILE + extension + if Path(config_dir, filename).exists(): + config_files_found += 1 + config_file = filename + + if config_files_found == 0: # Config file doesn't exist (yet!) + LOG.info(f"No config file found. Creating one as {config_file}.") + elif config_files_found > 1: # Multiple config files; let user know which is used + LOG.info( + f"More than one samconfig file found; using {config_file}." + f" To use another config file, please specify it using the '--config-file' flag." + ) + + return config_file + @staticmethod def _version_sanity_check(version: Any) -> None: if not isinstance(version, float): diff --git a/tests/unit/commands/samconfig/test_samconfig.py b/tests/unit/commands/samconfig/test_samconfig.py index af75bbed85..b2e0822c78 100644 --- a/tests/unit/commands/samconfig/test_samconfig.py +++ b/tests/unit/commands/samconfig/test_samconfig.py @@ -14,9 +14,6 @@ from unittest import TestCase from unittest.mock import patch, ANY import logging -from parameterized import parameterized -from samcli.lib.config.exceptions import SamConfigFileReadException -from samcli.lib.config.file_manager import JsonFileManager, TomlFileManager, YamlFileManager from samcli.lib.config.samconfig import SamConfig, DEFAULT_ENV from samcli.lib.utils.packagetype import ZIP, IMAGE @@ -1250,38 +1247,6 @@ def test_secondary_option_name_template_validate(self, do_cli_mock): do_cli_mock.assert_called_with(ANY, str(Path(os.getcwd(), "mytemplate.yaml")), False) -class TestSamConfigFileManager(TestCase): - def test_file_manager_not_declared(self): - config_dir = tempfile.gettempdir() - config_path = Path(config_dir, "samconfig") - - with self.assertRaises(SamConfigFileReadException): - SamConfig(config_path, filename="samconfig") - - def test_file_manager_unsupported(self): - config_dir = tempfile.gettempdir() - config_path = Path(config_dir, "samconfig.jpeg") - - with self.assertRaises(SamConfigFileReadException): - SamConfig(config_path, filename="samconfig.jpeg") - - @parameterized.expand( - [ - ("samconfig.toml", TomlFileManager), - ("samconfig.yaml", YamlFileManager), - ("samconfig.yml", YamlFileManager), - ("samconfig.json", JsonFileManager), - ] - ) - def test_file_manager(self, filename, expected_file_manager): - config_dir = tempfile.gettempdir() - config_path = Path(config_dir, filename) - - samconfig = SamConfig(config_path, filename=filename) - - self.assertIs(samconfig.file_manager, expected_file_manager) - - @contextmanager def samconfig_parameters(cmd_names, config_dir=None, env=None, **kwargs): """ diff --git a/tests/unit/lib/samconfig/test_file_manager.py b/tests/unit/lib/samconfig/test_file_manager.py index 362d0a3b27..eb9325fc37 100644 --- a/tests/unit/lib/samconfig/test_file_manager.py +++ b/tests/unit/lib/samconfig/test_file_manager.py @@ -134,7 +134,7 @@ def test_read_yaml_file_path_not_valid(self): config_doc = YamlFileManager.read(config_path) - self.assertEqual(config_doc, self.yaml.load("")) + self.assertEqual(config_doc, {}) def test_write_yaml(self): config_dir = tempfile.gettempdir() diff --git a/tests/unit/lib/samconfig/test_samconfig.py b/tests/unit/lib/samconfig/test_samconfig.py index 346353501b..4250e3d9cd 100644 --- a/tests/unit/lib/samconfig/test_samconfig.py +++ b/tests/unit/lib/samconfig/test_samconfig.py @@ -1,9 +1,18 @@ import os from pathlib import Path +from parameterized import parameterized +import tempfile from unittest import TestCase -from samcli.lib.config.exceptions import SamConfigVersionException -from samcli.lib.config.samconfig import SamConfig, DEFAULT_CONFIG_FILE_NAME, DEFAULT_GLOBAL_CMDNAME, DEFAULT_ENV +from samcli.lib.config.exceptions import SamConfigFileReadException, SamConfigVersionException +from samcli.lib.config.file_manager import JsonFileManager, TomlFileManager, YamlFileManager +from samcli.lib.config.samconfig import ( + DEFAULT_CONFIG_FILE, + SamConfig, + DEFAULT_CONFIG_FILE_NAME, + DEFAULT_GLOBAL_CMDNAME, + DEFAULT_ENV, +) from samcli.lib.config.version import VERSION_KEY, SAM_CONFIG_VERSION from samcli.lib.utils import osutils @@ -221,3 +230,76 @@ def test_write_config_file_will_create_the_file_if_not_exist(self): samconfig.put(cmd_names=["any", "command"], section="any-section", key="any-key", value="any-value") samconfig.flush() self.assertTrue(samconfig.exists()) + + def test_passed_filename_used(self): + config_path = Path(self.config_dir, "myconfigfile.toml") + + self.assertFalse(config_path.exists()) + + self.samconfig = SamConfig(self.config_dir, filename="myconfigfile.toml") + self.samconfig.put( # put some config options so it creates the file + cmd_names=["any", "command"], section="section", key="key", value="value" + ) + self.samconfig.flush() + + self.assertTrue(config_path.exists()) + self.assertFalse(Path(self.config_dir, DEFAULT_CONFIG_FILE_NAME).exists()) + + def test_config_uses_default_if_none_provided(self): + self.samconfig = SamConfig(self.config_dir) + self.samconfig.put( # put some config options so it creates the file + cmd_names=["any", "command"], section="section", key="key", value="value" + ) + self.samconfig.flush() + + self.assertTrue(Path(self.config_dir, DEFAULT_CONFIG_FILE_NAME).exists()) + + def test_config_priority(self): + config_files = [] + extensions_in_priority = list(SamConfig.FILE_MANAGER_MAPPER.keys()) # priority by order in dict + for extension in extensions_in_priority: + filename = DEFAULT_CONFIG_FILE + extension + config = SamConfig(self.config_dir, filename=filename) + config.put( # put some config options so it creates the file + cmd_names=["any", "command"], section="section", key="key", value="value" + ) + config.flush() + config_files.append(config) + + while extensions_in_priority: + config = SamConfig(self.config_dir) + next_priority = extensions_in_priority.pop(0) + self.assertEqual(config.filepath, Path(self.config_dir, DEFAULT_CONFIG_FILE + next_priority)) + os.remove(config.path()) + + +class TestSamConfigFileManager(TestCase): + def test_file_manager_not_declared(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig") + + with self.assertRaises(SamConfigFileReadException): + SamConfig(config_path, filename="samconfig") + + def test_file_manager_unsupported(self): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, "samconfig.jpeg") + + with self.assertRaises(SamConfigFileReadException): + SamConfig(config_path, filename="samconfig.jpeg") + + @parameterized.expand( + [ + ("samconfig.toml", TomlFileManager), + ("samconfig.yaml", YamlFileManager), + ("samconfig.yml", YamlFileManager), + ("samconfig.json", JsonFileManager), + ] + ) + def test_file_manager(self, filename, expected_file_manager): + config_dir = tempfile.gettempdir() + config_path = Path(config_dir, filename) + + samconfig = SamConfig(config_path, filename=filename) + + self.assertIs(samconfig.file_manager, expected_file_manager) From 127fe82e0967d00803c59470e5b896935d652cf9 Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:16:42 -0700 Subject: [PATCH 101/107] feat: Track config file extension (#5315) * Add tracker for config file extensions * Repair broken integration tests * Clean up metric sort logic * Implement requested changes * Add Event unit tests * Fix formatting --------- Co-authored-by: Leonardo Gama --- samcli/lib/config/file_manager.py | 10 +++++++- samcli/lib/config/samconfig.py | 22 +++++++---------- samcli/lib/telemetry/event.py | 3 +++ .../telemetry/test_experimental_metric.py | 7 ++++-- .../telemetry/test_installed_metric.py | 4 +++- .../telemetry/test_telemetry_contract.py | 6 +++-- tests/unit/cli/test_cli_config_file.py | 4 ++-- tests/unit/lib/samconfig/test_samconfig.py | 24 +++++++++++++------ 8 files changed, 52 insertions(+), 28 deletions(-) diff --git a/samcli/lib/config/file_manager.py b/samcli/lib/config/file_manager.py index abbdade71d..ab1e000ff3 100644 --- a/samcli/lib/config/file_manager.py +++ b/samcli/lib/config/file_manager.py @@ -7,7 +7,7 @@ import logging from abc import ABC, abstractmethod from pathlib import Path -from typing import Any +from typing import Any, Dict, Type import tomlkit from ruamel.yaml import YAML, YAMLError @@ -332,3 +332,11 @@ def put_comment(document: Any, comment: str) -> Any: """ document.update({COMMENT_KEY: comment}) return document + + +FILE_MANAGER_MAPPER: Dict[str, Type[FileManager]] = { # keys ordered by priority + ".toml": TomlFileManager, + ".yaml": YamlFileManager, + ".yml": YamlFileManager, + ".json": JsonFileManager, +} diff --git a/samcli/lib/config/samconfig.py b/samcli/lib/config/samconfig.py index a77e44c70c..0762249c8f 100644 --- a/samcli/lib/config/samconfig.py +++ b/samcli/lib/config/samconfig.py @@ -5,11 +5,12 @@ import logging import os from pathlib import Path -from typing import Any, Dict, Iterable, Type +from typing import Any, Iterable from samcli.lib.config.exceptions import FileParseException, SamConfigFileReadException, SamConfigVersionException -from samcli.lib.config.file_manager import FileManager, JsonFileManager, TomlFileManager, YamlFileManager +from samcli.lib.config.file_manager import FILE_MANAGER_MAPPER from samcli.lib.config.version import SAM_CONFIG_VERSION, VERSION_KEY +from samcli.lib.telemetry.event import EventTracker LOG = logging.getLogger(__name__) @@ -25,13 +26,6 @@ class SamConfig: Class to represent `samconfig` config options. """ - FILE_MANAGER_MAPPER: Dict[str, Type[FileManager]] = { # keys ordered by priority - ".toml": TomlFileManager, - ".yaml": YamlFileManager, - ".yml": YamlFileManager, - ".json": JsonFileManager, - } - def __init__(self, config_dir, filename=None): """ Initialize the class @@ -46,16 +40,18 @@ def __init__(self, config_dir, filename=None): """ self.document = {} self.filepath = Path(config_dir, filename or self.get_default_file(config_dir=config_dir)) - self.file_manager = self.FILE_MANAGER_MAPPER.get(self.filepath.suffix, None) + file_extension = self.filepath.suffix + self.file_manager = FILE_MANAGER_MAPPER.get(file_extension, None) if not self.file_manager: LOG.warning( - f"The config file extension '{self.filepath.suffix}' is not supported. " - f"Supported formats are: [{'|'.join(self.FILE_MANAGER_MAPPER.keys())}]" + f"The config file extension '{file_extension}' is not supported. " + f"Supported formats are: [{'|'.join(FILE_MANAGER_MAPPER.keys())}]" ) raise SamConfigFileReadException( f"The config file {self.filepath} uses an unsupported extension, and cannot be read." ) self._read() + EventTracker.track_event("SamConfigFileExtension", file_extension) def get_stage_configuration_names(self): if self.document: @@ -266,7 +262,7 @@ def get_default_file(config_dir: str) -> str: config_files_found = 0 config_file = DEFAULT_CONFIG_FILE_NAME - for extension in reversed(list(SamConfig.FILE_MANAGER_MAPPER.keys())): + for extension in reversed(list(FILE_MANAGER_MAPPER.keys())): filename = DEFAULT_CONFIG_FILE + extension if Path(config_dir, filename).exists(): config_files_found += 1 diff --git a/samcli/lib/telemetry/event.py b/samcli/lib/telemetry/event.py index 2d819e37bf..2c35748951 100644 --- a/samcli/lib/telemetry/event.py +++ b/samcli/lib/telemetry/event.py @@ -11,6 +11,7 @@ from samcli.cli.context import Context from samcli.lib.build.workflows import ALL_CONFIGS +from samcli.lib.config.file_manager import FILE_MANAGER_MAPPER from samcli.lib.telemetry.telemetry import Telemetry from samcli.local.common.runtime_template import INIT_RUNTIMES @@ -26,6 +27,7 @@ class EventName(Enum): SYNC_FLOW_START = "SyncFlowStart" SYNC_FLOW_END = "SyncFlowEnd" BUILD_WORKFLOW_USED = "BuildWorkflowUsed" + CONFIG_FILE_EXTENSION = "SamConfigFileExtension" class UsedFeature(Enum): @@ -69,6 +71,7 @@ class EventType: EventName.SYNC_FLOW_START: _SYNC_FLOWS, EventName.SYNC_FLOW_END: _SYNC_FLOWS, EventName.BUILD_WORKFLOW_USED: _WORKFLOWS, + EventName.CONFIG_FILE_EXTENSION: list(FILE_MANAGER_MAPPER.keys()), } @staticmethod diff --git a/tests/integration/telemetry/test_experimental_metric.py b/tests/integration/telemetry/test_experimental_metric.py index 977e65053f..702ceb4a5f 100644 --- a/tests/integration/telemetry/test_experimental_metric.py +++ b/tests/integration/telemetry/test_experimental_metric.py @@ -211,8 +211,11 @@ def test_must_send_not_experimental_metrics_if_not_experimental(self): self.assertEqual(process.returncode, 2, "Command should fail") all_requests = server.get_all_requests() - self.assertEqual(1, len(all_requests), "Command run metric must be sent") - request = all_requests[0] + self.assertEqual(2, len(all_requests), "Command run and event metrics must be sent") + # NOTE: Since requests happen asynchronously, we cannot guarantee whether the + # commandRun metric will be first or second, so we sort for consistency. + all_requests.sort(key=lambda x: list(x["data"]["metrics"][0].keys())[0]) + request = all_requests[0] # "commandRun" comes before "events" self.assertIn("Content-Type", request["headers"]) self.assertEqual(request["headers"]["Content-Type"], "application/json") diff --git a/tests/integration/telemetry/test_installed_metric.py b/tests/integration/telemetry/test_installed_metric.py index e17459828c..725a2d0ff5 100644 --- a/tests/integration/telemetry/test_installed_metric.py +++ b/tests/integration/telemetry/test_installed_metric.py @@ -24,7 +24,9 @@ def test_send_installed_metric_on_first_run(self): self.assertIn(EXPECTED_TELEMETRY_PROMPT, stderrdata.decode()) all_requests = server.get_all_requests() - self.assertEqual(2, len(all_requests), "There should be exactly two metrics request") + self.assertEqual( + 3, len(all_requests), "There should be exactly three metrics request" + ) # 3 = 2 expected + events # First one is usually the installed metric requests = filter_installed_metric_requests(all_requests) diff --git a/tests/integration/telemetry/test_telemetry_contract.py b/tests/integration/telemetry/test_telemetry_contract.py index 08b3585b99..a3e383bb5d 100644 --- a/tests/integration/telemetry/test_telemetry_contract.py +++ b/tests/integration/telemetry/test_telemetry_contract.py @@ -28,7 +28,9 @@ def test_must_not_send_metrics_if_disabled_using_envvar(self): self.assertEqual(process.returncode, 0, "Command should successfully complete") all_requests = server.get_all_requests() - self.assertEqual(1, len(all_requests), "Command run metric should be sent") + self.assertEqual( + 2, len(all_requests), "Command run and event metrics should be sent" + ) # 2 = cmd_run + events def test_must_send_metrics_if_enabled_via_envvar(self): """ @@ -52,7 +54,7 @@ def test_must_send_metrics_if_enabled_via_envvar(self): self.assertEqual(process.returncode, 0, "Command should successfully complete") all_requests = server.get_all_requests() - self.assertEqual(1, len(all_requests), "Command run metric must be sent") + self.assertEqual(2, len(all_requests), "Command run and event metrics must be sent") # cmd_run + events def test_must_not_crash_when_offline(self): """ diff --git a/tests/unit/cli/test_cli_config_file.py b/tests/unit/cli/test_cli_config_file.py index 93502deb44..782c6bc1de 100644 --- a/tests/unit/cli/test_cli_config_file.py +++ b/tests/unit/cli/test_cli_config_file.py @@ -9,8 +9,8 @@ from samcli.commands.exceptions import ConfigException from samcli.cli.cli_config_file import ConfigProvider, configuration_option, configuration_callback, get_ctx_defaults -from samcli.lib.config.exceptions import FileParseException, SamConfigFileReadException, SamConfigVersionException -from samcli.lib.config.samconfig import DEFAULT_ENV, TomlFileManager +from samcli.lib.config.exceptions import SamConfigFileReadException, SamConfigVersionException +from samcli.lib.config.samconfig import DEFAULT_ENV from tests.testing_utils import IS_WINDOWS diff --git a/tests/unit/lib/samconfig/test_samconfig.py b/tests/unit/lib/samconfig/test_samconfig.py index 4250e3d9cd..df8ceef80b 100644 --- a/tests/unit/lib/samconfig/test_samconfig.py +++ b/tests/unit/lib/samconfig/test_samconfig.py @@ -1,11 +1,12 @@ import os from pathlib import Path +from unittest.mock import patch from parameterized import parameterized import tempfile from unittest import TestCase from samcli.lib.config.exceptions import SamConfigFileReadException, SamConfigVersionException -from samcli.lib.config.file_manager import JsonFileManager, TomlFileManager, YamlFileManager +from samcli.lib.config.file_manager import FILE_MANAGER_MAPPER, JsonFileManager, TomlFileManager, YamlFileManager from samcli.lib.config.samconfig import ( DEFAULT_CONFIG_FILE, SamConfig, @@ -14,6 +15,7 @@ DEFAULT_ENV, ) from samcli.lib.config.version import VERSION_KEY, SAM_CONFIG_VERSION +from samcli.lib.telemetry.event import Event from samcli.lib.utils import osutils @@ -256,7 +258,7 @@ def test_config_uses_default_if_none_provided(self): def test_config_priority(self): config_files = [] - extensions_in_priority = list(SamConfig.FILE_MANAGER_MAPPER.keys()) # priority by order in dict + extensions_in_priority = list(FILE_MANAGER_MAPPER.keys()) # priority by order in dict for extension in extensions_in_priority: filename = DEFAULT_CONFIG_FILE + extension config = SamConfig(self.config_dir, filename=filename) @@ -290,16 +292,24 @@ def test_file_manager_unsupported(self): @parameterized.expand( [ - ("samconfig.toml", TomlFileManager), - ("samconfig.yaml", YamlFileManager), - ("samconfig.yml", YamlFileManager), - ("samconfig.json", JsonFileManager), + ("samconfig.toml", TomlFileManager, ".toml"), + ("samconfig.yaml", YamlFileManager, ".yaml"), + ("samconfig.yml", YamlFileManager, ".yml"), + ("samconfig.json", JsonFileManager, ".json"), ] ) - def test_file_manager(self, filename, expected_file_manager): + @patch("samcli.lib.telemetry.event.EventTracker.track_event") + def test_file_manager(self, filename, expected_file_manager, expected_extension, track_mock): config_dir = tempfile.gettempdir() config_path = Path(config_dir, filename) + tracked_events = [] + + def mock_tracker(name, value): # when track_event is called, just append the Event to our list + tracked_events.append(Event(name, value)) + + track_mock.side_effect = mock_tracker samconfig = SamConfig(config_path, filename=filename) self.assertIs(samconfig.file_manager, expected_file_manager) + self.assertIn(Event("SamConfigFileExtension", expected_extension), tracked_events) From 5a0d3771e11afe994b3ef71f05e392cd84511da1 Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Tue, 20 Jun 2023 14:11:11 -0700 Subject: [PATCH 102/107] feat: Add and fix samconfig integration tests (#5371) * Add samconfig integration tests * Add config checks to guided deploy integration tests * Fix failing integration test on Windows * *Actually* fix failing Windows integration test * Implement requested changes * Fix logging imports * Implement requested changes * Fix bug comparing ParameterSource enum --------- Co-authored-by: Leonardo Gama --- samcli/cli/cli_config_file.py | 7 +- .../integration/buildcmd/build_integ_base.py | 4 + .../buildcmd/test_build_samconfig.py | 113 ++++++++++++++++++ tests/integration/deploy/deploy_integ_base.py | 25 ++++ .../integration/deploy/test_deploy_command.py | 41 ++++++- .../buildcmd/samconfig/samconfig.json | 12 ++ .../buildcmd/samconfig/samconfig.toml | 5 + .../buildcmd/samconfig/samconfig.yaml | 7 ++ .../testdata/buildcmd/samconfig/template.yaml | 37 ++++++ tests/unit/cli/test_cli_config_file.py | 1 + 10 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 tests/integration/buildcmd/test_build_samconfig.py create mode 100644 tests/integration/testdata/buildcmd/samconfig/samconfig.json create mode 100644 tests/integration/testdata/buildcmd/samconfig/samconfig.toml create mode 100644 tests/integration/testdata/buildcmd/samconfig/samconfig.yaml create mode 100644 tests/integration/testdata/buildcmd/samconfig/template.yaml diff --git a/samcli/cli/cli_config_file.py b/samcli/cli/cli_config_file.py index 2929a434e9..0759fabcec 100644 --- a/samcli/cli/cli_config_file.py +++ b/samcli/cli/cli_config_file.py @@ -13,6 +13,7 @@ from typing import Any, Callable, Dict, List, Optional import click +from click.core import ParameterSource from samcli.cli.context import get_cmd_names from samcli.commands.exceptions import ConfigException @@ -166,8 +167,12 @@ def configuration_callback( param.default = None config_env_name = ctx.params.get("config_env") or DEFAULT_ENV - config_file = ctx.params.get("config_file") or DEFAULT_CONFIG_FILE_NAME config_dir = getattr(ctx, "samconfig_dir", None) or os.getcwd() + config_file = ( # If given by default, check for other `samconfig` extensions first. Else use user-provided value + SamConfig.get_default_file(config_dir=config_dir) + if getattr(ctx.get_parameter_source("config_file"), "name", "") == ParameterSource.DEFAULT.name + else ctx.params.get("config_file") or SamConfig.get_default_file(config_dir=config_dir) + ) # If --config-file is an absolute path, use it, if not, start from config_dir config_file_path = config_file if os.path.isabs(config_file) else os.path.join(config_dir, config_file) if ( diff --git a/tests/integration/buildcmd/build_integ_base.py b/tests/integration/buildcmd/build_integ_base.py index 13ba14f310..2dab556843 100644 --- a/tests/integration/buildcmd/build_integ_base.py +++ b/tests/integration/buildcmd/build_integ_base.py @@ -83,6 +83,7 @@ def get_command_list( beta_features=None, build_in_source=None, mount_with=None, + config_file=None, ): command_list = [self.cmd, "build"] @@ -146,6 +147,9 @@ def get_command_list( if build_in_source is not None: command_list += ["--build-in-source"] if build_in_source else ["--no-build-in-source"] + if config_file is not None: + command_list += ["--config-file", config_file] + return command_list def verify_docker_container_cleanedup(self, runtime): diff --git a/tests/integration/buildcmd/test_build_samconfig.py b/tests/integration/buildcmd/test_build_samconfig.py new file mode 100644 index 0000000000..bdfd87762b --- /dev/null +++ b/tests/integration/buildcmd/test_build_samconfig.py @@ -0,0 +1,113 @@ +import os +from pathlib import Path +from parameterized import parameterized, parameterized_class + +from tests.integration.buildcmd.build_integ_base import BuildIntegBase +from tests.testing_utils import run_command + + +configs = { + ".toml": "samconfig/samconfig.toml", + ".yaml": "samconfig/samconfig.yaml", + ".json": "samconfig/samconfig.json", +} + + +class TestSamConfigWithBuild(BuildIntegBase): + @parameterized.expand( + [ + (".toml"), + (".yaml"), + (".json"), + ] + ) + def test_samconfig_works_with_extension(self, extension): + cmdlist = self.get_command_list(config_file=configs[extension]) + + command_result = run_command(cmdlist, cwd=self.working_dir) + stdout = str(command_result[1]) + stderr = str(command_result[2]) + + self.assertEqual(command_result.process.returncode, 0, "Build should succeed") + self.assertIn( + f"Built Artifacts : {extension}", + stdout, + f"Build template should use build_dir from samconfig{extension}", + ) + self.assertIn("Starting Build use cache", stderr, f"'cache'=true should be set in samconfig{extension}") + + @parameterized.expand( + [ + (".toml"), + (".yaml"), + (".json"), + ] + ) + def test_samconfig_parameters_are_overridden(self, extension): + overrides = {"Runtime": "python3.8"} + overridden_build_dir = f"override_{extension}" + + cmdlist = self.get_command_list( + config_file=configs[extension], parameter_overrides=overrides, build_dir=overridden_build_dir + ) + + command_result = run_command(cmdlist, cwd=self.working_dir) + stdout = str(command_result[1]) + stderr = str(command_result[2]) + + self.assertEqual(command_result.process.returncode, 0, "Build should succeed") + self.assertNotIn( + f"Built Artifacts : {extension}", + stdout, + f"Build template should not use build_dir from samconfig{extension}", + ) + self.assertIn( + f"Built Artifacts : {overridden_build_dir}", stdout, f"Build template should use overridden build_dir" + ) + self.assertIn("Starting Build use cache", stderr, f"'cache'=true should be set in samconfig{extension}") + self.assertNotIn("python3.9", stderr, f"parameter_overrides runtime should not read from samconfig{extension}") + self.assertIn(overrides["Runtime"], stderr, "parameter_overrides should use overridden runtime") + self.assertNotIn("SomeURI", stderr, f"parameter_overrides should not read ANY values from samconfig{extension}") + + +@parameterized_class( + [ # Ordered by expected priority + {"extensions": [".toml", ".yaml", ".json"]}, + {"extensions": [".yaml", ".json"]}, + ] +) +class TestSamConfigExtensionHierarchy(BuildIntegBase): + def setUp(self): + super().setUp() + new_template_location = Path(self.working_dir, "template.yaml") + new_template_location.write_text(Path(self.template_path).read_text()) + for extension in self.extensions: + config_contents = Path(self.test_data_path, configs[extension]).read_text() + new_path = Path(self.working_dir, f"samconfig{extension}") + new_path.write_text(config_contents) + self.assertTrue(new_path.exists(), f"File samconfig{extension} should have been created in cwd") + + def tearDown(self): + for extension in self.extensions: + config_path = Path(self.working_dir, f"samconfig{extension}") + os.remove(config_path) + super().tearDown() + + def test_samconfig_pulls_correct_file_if_multiple(self): + self.template_path = str(Path(self.working_dir, "template.yaml")) + cmdlist = self.get_command_list(debug=True) + command_result = run_command(cmdlist, cwd=self.working_dir) + stdout = str(command_result[1]) + + self.assertEqual(command_result.process.returncode, 0, "Build should succeed") + self.assertIn( + f" {self.extensions[0]}", + stdout, + f"samconfig{self.extensions[0]} should take priority in current test group", + ) + for other_extension in self.extensions[1:]: + self.assertNotIn( + f" {other_extension}", + stdout, + f"samconfig{other_extension} should not be read over another, higher priority extension", + ) diff --git a/tests/integration/deploy/deploy_integ_base.py b/tests/integration/deploy/deploy_integ_base.py index f48eae9d61..4db4e5ff6a 100644 --- a/tests/integration/deploy/deploy_integ_base.py +++ b/tests/integration/deploy/deploy_integ_base.py @@ -2,11 +2,13 @@ import tempfile from pathlib import Path from enum import Enum, auto +from typing import List, Optional import boto3 from botocore.config import Config from samcli.lib.bootstrap.bootstrap import SAM_CLI_STACK_NAME +from samcli.lib.config.samconfig import SamConfig from tests.integration.package.package_integ_base import PackageIntegBase from tests.testing_utils import get_sam_command, run_command, run_command_with_input @@ -212,3 +214,26 @@ def get_minimal_build_command_list(template_file=None, build_dir=None): command_list = command_list + ["--build-dir", str(build_dir)] return command_list + + def _assert_deploy_samconfig_parameters( + self, + config: SamConfig, + stack_name: str = SAM_CLI_STACK_NAME, + resolve_s3: bool = True, + region: str = "us-east-1", + capabilities: str = "CAPABILITY_IAM", + confirm_changeset: Optional[bool] = None, + parameter_overrides: Optional[str] = None, + ): + params = config.document["default"]["deploy"]["parameters"] + + self.assertEqual(params["stack_name"], stack_name) + self.assertEqual(params["resolve_s3"], resolve_s3) + self.assertEqual(params["region"], region) + self.assertEqual(params["capabilities"], capabilities) + + if confirm_changeset is not None: + self.assertEqual(params["confirm_changeset"], confirm_changeset) + + if parameter_overrides is not None: + self.assertEqual(params["parameter_overrides"], parameter_overrides) diff --git a/tests/integration/deploy/test_deploy_command.py b/tests/integration/deploy/test_deploy_command.py index 131d3969ab..7221c40264 100644 --- a/tests/integration/deploy/test_deploy_command.py +++ b/tests/integration/deploy/test_deploy_command.py @@ -10,7 +10,7 @@ from parameterized import parameterized from samcli.lib.bootstrap.bootstrap import SAM_CLI_STACK_NAME -from samcli.lib.config.samconfig import DEFAULT_CONFIG_FILE_NAME +from samcli.lib.config.samconfig import DEFAULT_CONFIG_FILE_NAME, SamConfig from tests.integration.deploy.deploy_integ_base import DeployIntegBase from tests.testing_utils import RUNNING_ON_CI, RUNNING_TEST_FOR_MASTER_ON_CI, RUN_BY_CANARY, UpdatableSARTemplate @@ -613,6 +613,13 @@ def test_deploy_guided_zip(self, template_file): # Deploy should succeed with a managed stack self.assertEqual(deploy_process_execute.process.returncode, 0) self.stacks.append({"name": SAM_CLI_STACK_NAME}) + # Verify the contents in samconfig + config = SamConfig(self.test_data_path) + deploy_config_params = config.document["default"]["deploy"]["parameters"] + self.assertEqual(deploy_config_params["stack_name"], stack_name) + self.assertTrue(deploy_config_params["resolve_s3"]) + self.assertEqual(deploy_config_params["region"], "us-east-1") + self.assertEqual(deploy_config_params["capabilities"], "CAPABILITY_IAM") # Remove samconfig.toml os.remove(self.test_data_path.joinpath(DEFAULT_CONFIG_FILE_NAME)) @@ -627,7 +634,7 @@ def test_deploy_guided_image_auto(self, template_file): deploy_command_list = self.get_deploy_command_list(template_file=template_path, guided=True) deploy_process_execute = self.run_command_with_input( - deploy_command_list, f"{stack_name}\n\n\n\n\ny\n\n\ny\n\n\n\n".encode() + deploy_command_list, f"{stack_name}\n\n\n\n\ny\n\n\n\n\n\n\n".encode() ) # Deploy should succeed with a managed stack @@ -638,6 +645,10 @@ def test_deploy_guided_image_auto(self, template_file): self._assert_companion_stack(self.cfn_client, companion_stack_name) self._assert_companion_stack_content(self.ecr_client, companion_stack_name) + # Verify the contents in samconfig + config = SamConfig(self.test_data_path) + self._assert_deploy_samconfig_parameters(config, stack_name=stack_name) + # Remove samconfig.toml os.remove(self.test_data_path.joinpath(DEFAULT_CONFIG_FILE_NAME)) @@ -669,6 +680,9 @@ def test_deploy_guided_image_specify(self, template_file, does_ask_for_authoriza self.fail("Companion stack was created. This should not happen with specifying image repos.") self.stacks.append({"name": SAM_CLI_STACK_NAME}) + # Verify the contents in samconfig + config = SamConfig(self.test_data_path) + self._assert_deploy_samconfig_parameters(config, stack_name=stack_name) # Remove samconfig.toml os.remove(self.test_data_path.joinpath(DEFAULT_CONFIG_FILE_NAME)) @@ -690,6 +704,11 @@ def test_deploy_guided_set_parameter(self, template_file): # Deploy should succeed with a managed stack self.assertEqual(deploy_process_execute.process.returncode, 0) self.stacks.append({"name": SAM_CLI_STACK_NAME}) + # Verify the contents in samconfig + config = SamConfig(self.test_data_path) + self._assert_deploy_samconfig_parameters( + config, stack_name=stack_name, parameter_overrides='Parameter="SuppliedParameter"' + ) # Remove samconfig.toml os.remove(self.test_data_path.joinpath(DEFAULT_CONFIG_FILE_NAME)) @@ -710,6 +729,14 @@ def test_deploy_guided_set_capabilities(self, template_file): # Deploy should succeed with a managed stack self.assertEqual(deploy_process_execute.process.returncode, 0) self.stacks.append({"name": SAM_CLI_STACK_NAME}) + # Verify the contents in samconfig + config = SamConfig(self.test_data_path) + self._assert_deploy_samconfig_parameters( + config, + stack_name=stack_name, + capabilities="CAPABILITY_IAM CAPABILITY_NAMED_IAM", + parameter_overrides='Parameter="SuppliedParameter"', + ) # Remove samconfig.toml os.remove(self.test_data_path.joinpath(DEFAULT_CONFIG_FILE_NAME)) @@ -731,6 +758,11 @@ def test_deploy_guided_capabilities_default(self, template_file): # Deploy should succeed with a managed stack self.assertEqual(deploy_process_execute.process.returncode, 0) self.stacks.append({"name": SAM_CLI_STACK_NAME}) + # Verify the contents in samconfig + config = SamConfig(self.test_data_path) + self._assert_deploy_samconfig_parameters( + config, stack_name=stack_name, parameter_overrides='Parameter="SuppliedParameter"' + ) # Remove samconfig.toml os.remove(self.test_data_path.joinpath(DEFAULT_CONFIG_FILE_NAME)) @@ -752,6 +784,11 @@ def test_deploy_guided_set_confirm_changeset(self, template_file): # Deploy should succeed with a managed stack self.assertEqual(deploy_process_execute.process.returncode, 0) self.stacks.append({"name": SAM_CLI_STACK_NAME}) + # Verify the contents in samconfig + config = SamConfig(self.test_data_path) + self._assert_deploy_samconfig_parameters( + config, stack_name=stack_name, confirm_changeset=True, parameter_overrides='Parameter="SuppliedParameter"' + ) # Remove samconfig.toml os.remove(self.test_data_path.joinpath(DEFAULT_CONFIG_FILE_NAME)) diff --git a/tests/integration/testdata/buildcmd/samconfig/samconfig.json b/tests/integration/testdata/buildcmd/samconfig/samconfig.json new file mode 100644 index 0000000000..6fa18e1c11 --- /dev/null +++ b/tests/integration/testdata/buildcmd/samconfig/samconfig.json @@ -0,0 +1,12 @@ +{ + "version": 0.1, + "default": { + "build": { + "parameters": { + "build_dir": ".json", + "cached": true, + "parameter_overrides": "Runtime=python3.9 CodeUri=SomeURI Handler=SomeHandler" + } + } + } +} \ No newline at end of file diff --git a/tests/integration/testdata/buildcmd/samconfig/samconfig.toml b/tests/integration/testdata/buildcmd/samconfig/samconfig.toml new file mode 100644 index 0000000000..23a769ff5e --- /dev/null +++ b/tests/integration/testdata/buildcmd/samconfig/samconfig.toml @@ -0,0 +1,5 @@ +version = 0.1 +[default.build.parameters] +build_dir = ".toml" +cached = true +parameter_overrides = "Runtime=python3.9 CodeUri=SomeURI Handler=SomeHandler" \ No newline at end of file diff --git a/tests/integration/testdata/buildcmd/samconfig/samconfig.yaml b/tests/integration/testdata/buildcmd/samconfig/samconfig.yaml new file mode 100644 index 0000000000..63af206238 --- /dev/null +++ b/tests/integration/testdata/buildcmd/samconfig/samconfig.yaml @@ -0,0 +1,7 @@ +version: 0.1 +default: + build: + parameters: + build_dir: .yaml + cached: true + parameter_overrides: Runtime=python3.9 CodeUri=SomeURI Handler=SomeHandler \ No newline at end of file diff --git a/tests/integration/testdata/buildcmd/samconfig/template.yaml b/tests/integration/testdata/buildcmd/samconfig/template.yaml new file mode 100644 index 0000000000..6944799912 --- /dev/null +++ b/tests/integration/testdata/buildcmd/samconfig/template.yaml @@ -0,0 +1,37 @@ +AWSTemplateFormatVersion : '2010-09-09' +Transform: AWS::Serverless-2016-10-31 + +Parameters: + Runtime: + Type: String + CodeUri: + Type: String + Handler: + Type: String + +Resources: + + Function: + Type: AWS::Serverless::Function + Properties: + Handler: !Ref Handler + Runtime: !Ref Runtime + CodeUri: !Ref CodeUri + Timeout: 600 + + + OtherRelativePathResource: + Type: AWS::ApiGateway::RestApi + Properties: + BodyS3Location: SomeRelativePath + + GlueResource: + Type: AWS::Glue::Job + Properties: + Command: + ScriptLocation: SomeRelativePath + + ExampleNestedStack: + Type: AWS::CloudFormation::Stack + Properties: + TemplateURL: https://s3.amazonaws.com/examplebucket/exampletemplate.yml diff --git a/tests/unit/cli/test_cli_config_file.py b/tests/unit/cli/test_cli_config_file.py index 782c6bc1de..19bfcfa011 100644 --- a/tests/unit/cli/test_cli_config_file.py +++ b/tests/unit/cli/test_cli_config_file.py @@ -125,6 +125,7 @@ def test_callback_with_invalid_config_file(self): self.ctx.parent = mock_context3 self.ctx.info_name = "test_info" self.ctx.params = {"config_file": "invalid_config_file"} + self.ctx._parameter_source.__get__ = "COMMANDLINE" setattr(self.ctx, "samconfig_dir", None) with self.assertRaises(ConfigException): configuration_callback( From ec9a675735c9b8c3c5d9d79394bef6f25e1e56cb Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:56:38 -0700 Subject: [PATCH 103/107] feat: Fix message when no config file is found (#5394) * Fix message when no config file found * Formatting --------- Co-authored-by: Leonardo Gama --- samcli/lib/config/samconfig.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/lib/config/samconfig.py b/samcli/lib/config/samconfig.py index 0762249c8f..e9acafd557 100644 --- a/samcli/lib/config/samconfig.py +++ b/samcli/lib/config/samconfig.py @@ -269,7 +269,7 @@ def get_default_file(config_dir: str) -> str: config_file = filename if config_files_found == 0: # Config file doesn't exist (yet!) - LOG.info(f"No config file found. Creating one as {config_file}.") + LOG.debug("No config file found in this directory.") elif config_files_found > 1: # Multiple config files; let user know which is used LOG.info( f"More than one samconfig file found; using {config_file}." From 24ddca1d6953b2042db91abbd351b8d2da19db47 Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Fri, 23 Jun 2023 09:26:59 -0700 Subject: [PATCH 104/107] chore: Rebase config project to develop (#5406) * fix: fix the hardcoded number of stages printed in logs. (#5210) * feat: Linking Authorizers to Lambda functions using the invocation URI (#5196) * Link authorizer to lambda function invoke URI * Updated doc string * Updated exception messages back * Added check for one element in reference list * Updated empty ref list check to not block * Updated log message * Fix long line lint error --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * chore(deps-dev): bump parameterized from 0.8.1 to 0.9.0 in /requirements (#5214) Bumps [parameterized](https://github.com/wolever/parameterized) from 0.8.1 to 0.9.0. - [Changelog](https://github.com/wolever/parameterized/blob/master/CHANGELOG.txt) - [Commits](https://github.com/wolever/parameterized/compare/v0.8.1...v0.9.0) --- updated-dependencies: - dependency-name: parameterized dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump filelock from 3.10.7 to 3.12.0 in /requirements (#5213) Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.10.7 to 3.12.0. - [Release notes](https://github.com/tox-dev/py-filelock/releases) - [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/py-filelock/compare/3.10.7...3.12.0) --- updated-dependencies: - dependency-name: filelock dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump attrs from 22.2.0 to 23.1.0 in /requirements (#5212) Bumps [attrs](https://github.com/python-attrs/attrs) from 22.2.0 to 23.1.0. - [Release notes](https://github.com/python-attrs/attrs/releases) - [Changelog](https://github.com/python-attrs/attrs/blob/main/CHANGELOG.md) - [Commits](https://github.com/python-attrs/attrs/compare/22.2.0...23.1.0) --- updated-dependencies: - dependency-name: attrs dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: update SAM CLI with latest App Templates commit hash (#5211) * feat: updating app templates repo hash with (a34f563f067e13df3eb350d36461b99397b6cda6) * dummy change to trigger checks * revert dummy commit --------- Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * fix: fix failing Terraform integration test cases (#5218) * fix: fix the failing terraform integration test cases * fix: fix the resource address while accessing the module config resources * fix: fix checking the experimental log integration test cases * chore: bump version to 1.85.0 (#5226) * chore: use the SAR Application created in testing accounts (#5221) * chore: update aws_lambda_builders to 1.32.0 (#5215) Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * feat: Added linking Gateway Method to Lambda Authorizer (#5228) * Added linking method to authorizer * Fixed docstring spelling mistake --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * feat: Return early during linking if no destination resources are found (#5220) * Returns during linking if no destination resources are found * Updated comment to correctly reflect state * Cleaned extra word --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * chore: Strengthen wording on "no Auth" during deploy (#5231) Co-authored-by: Jacob Fuss Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> * feat: Link Lambda Authorizer to Rest API (#5219) * Link RestApiId property for Lambda Authorizers * Updated docstring * Format files --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * feat: updating app templates repo hash with (9ee7db342025a42023882960b23ebfcde1d87422) (#5242) Co-authored-by: GitHub Action * fix: handle edge cases with function sync flow in sam sync command (#5222) * fix: handle special cases for function sync flow * update with unit tests * add integration tests * set ADL to false * fix update file methods * address comments * address comments to instantiate FunctionBuildInfo in the beginning * chore: Upgrade Mac installer to Py3.11 (#5223) * chore: Upgrade Mac installer to Py3.11 * Remove python in mac installer build process * Update hardcoded python version in build-mac.sh --------- Co-authored-by: Jacob Fuss * feat: updating app templates repo hash with (66f4a230d1c939a0c3f7b5647710c694c3a486f7) (#5245) Co-authored-by: GitHub Action * Revert "chore: Upgrade Mac installer to Py3.11 (#5223)" (#5252) This reverts commit 5954042d0bced7fea329c06930f021915ed9b746. * fix: add 3.11 to classifiers and upgrade Docker (#5225) * fix: add 3.11 to classifiers - update dependencies, need to nail down the versions. * Pin dev dependencies and handle excluding folders for mypy * Remove unneeded type: ignores * Fix name-match mypy errors * Fix empty-body error from mypy * Fix mypy errors by ignoring and get pytest to run/pass * Force mypy to not fail hopefully * Remove unneeded assignment * Update pinned requirements file --------- Co-authored-by: Jacob Fuss Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> * fix: fix build and deploy SAR integration test cases (#5244) * fix: fix build SAR integration test cases * add comments to the UpdatableSARTemplate class usage. * fix black check * chore(deps): bump markupsafe from 2.1.2 to 2.1.3 in /requirements (#5257) Bumps [markupsafe](https://github.com/pallets/markupsafe) from 2.1.2 to 2.1.3. - [Release notes](https://github.com/pallets/markupsafe/releases) - [Changelog](https://github.com/pallets/markupsafe/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/markupsafe/compare/2.1.2...2.1.3) --- updated-dependencies: - dependency-name: markupsafe dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump pydantic from 1.10.7 to 1.10.8 in /requirements (#5258) Bumps [pydantic](https://github.com/pydantic/pydantic) from 1.10.7 to 1.10.8. - [Release notes](https://github.com/pydantic/pydantic/releases) - [Changelog](https://github.com/pydantic/pydantic/blob/v1.10.8/HISTORY.md) - [Commits](https://github.com/pydantic/pydantic/compare/v1.10.7...v1.10.8) --- updated-dependencies: - dependency-name: pydantic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: Add click command for cloud invoke command (#5238) * Add custom click option for cloud invoke called parameter * Added more error handling to executors and updated output-format enum to use auto * Add new CLI command for cloud invoke * Update samcli/commands/remote_invoke/invoke/cli.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Update samcli/commands/remote_invoke/invoke/cli.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Update samcli/commands/remote_invoke/cloud.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Update samcli/cli/types.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Address feedback * Moved all command options to be handled by click configuration * Updated validation function doc-string * Updated debug logs in types.py * Changed remote_invoke dir to cloud and updated log level for validation * Address feedback --------- Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * chore(deps-dev): bump boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray] (#5256) Bumps [boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray]](https://github.com/youtype/mypy_boto3_builder) from 1.26.131 to 1.26.146. - [Release notes](https://github.com/youtype/mypy_boto3_builder/releases) - [Commits](https://github.com/youtype/mypy_boto3_builder/commits) --- updated-dependencies: - dependency-name: boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,signer,stepfunctions,sts,xray] dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * pin pytest-metadata to avoid its breaking change (#5261) * chore: update aws_lambda_builders to 1.33.0 (#5262) Co-authored-by: GitHub Action Co-authored-by: Daniel Mil <84205762+mildaniel@users.noreply.github.com> * chore: Add python3.11 to canaries (#5263) * chore: Add python3.11 to canaries * Remove python3.9 * Artifact export for GraphQLApi (#5250) * Artifact export for GraphQLApi * format * docstrings * fix unit tests * fix mypy issues * improve search method signature * chore: bump version to 1.86.0 (#5266) * fix: add constant str for enums to support deepcopy operation (#5265) * fix: add constant str for enums to support deepcopy operation * add unit tests * formatting * update automated updates gha to force restart of status checks (#5269) * integration tests for graphql resource package (#5271) * Revert "fix: add 3.11 to classifiers and upgrade Docker (#5225)" This reverts commit b51d6617340853d891469ff7a4dcc5bb88175389. * chore: bump version to 1.86.1 * chore: Upgrade Docker-py/ Support Py3.11 for running tests (#5279) * fix: add 3.11 to classifiers and upgrade Docker (#5225) * fix: add 3.11 to classifiers - update dependencies, need to nail down the versions. * Pin dev dependencies and handle excluding folders for mypy * Remove unneeded type: ignores * Fix name-match mypy errors * Fix empty-body error from mypy * Fix mypy errors by ignoring and get pytest to run/pass * Force mypy to not fail hopefully * Remove unneeded assignment * Update pinned requirements file --------- Co-authored-by: Jacob Fuss Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> * chore: Force version on docker and allow unit test to run when docker not running In order for the docker.from_env() not to fail when docker is not installed/running, we force the min version on client creation. This was the default behavior in 4.X of docker-py but not longer in the latest version. --------- Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Jacob Fuss * test: GHA to Execute Test without Docker Running (#5290) * test: Test without Docker running * Add build test * Run install * Remove success condition * Add continue on error * Add continue on error * Separate tests * Fix test name * Require new test * Address comments * Attempt to parameterize for windows * Attempt to parameterize for windows * Attempt to parameterize for windows * Set samdev in environment * Move skip to top of test class * fix: remove ruby3.2 from preview runtimes (#5296) * fix: remove ruby3.2 from preview runtimes * update {} with set() * Fix: Force docker version to match 4.2's default version (#5305) Co-authored-by: Jacob Fuss * chore: cleanup appveyor definitions for not running jobs which is already run with GHA & add docker info/version commands (#5306) * chore: remove redundant tests and setup from appveyor definitions * add/update docker info and docker version commands * add 3.11 and macos to GHAs * add some explanations to Windows section * fix: Fix failing tests on Python3.11 (#5317) * chore(deps): bump cryptography from 39.0.2 to 41.0.0 in /requirements (#5251) * chore(deps): bump cryptography from 39.0.2 to 41.0.0 in /requirements Bumps [cryptography](https://github.com/pyca/cryptography) from 39.0.2 to 41.0.0. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/39.0.2...41.0.0) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Bump pyopenssl version to support newer cryptography lib --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * add sleep between close and reopen (#5320) * GraphQLApi support for `sam deploy` (#5294) * GraphQLApi support for `sam deploy` * unit tests and format fixes * fix: Update Arn parsing logic and fix some edge cases/bug fixes for remote invoke (#5295) * Fix some edge cases and bug fixes for remote invoke and update Arn parsing logic * Address feedback * Add unit test for s3 with no region/accoint_id provided * Renamed command to sam remote invoke * chore: update aws_lambda_builders to 1.34.0 (#5343) * chore: update aws_lambda_builders to 1.34.0 * Update base.txt --------- Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> * test: test building npm and Typescript projects using external manifest file. (#5283) * test: test building npm and Typescript projects using external manifest file. * fix mypy issues * remove node 12.x, and add the new node versions * run make format * chore(deps-dev): bump ruff from 0.0.261 to 0.0.272 in /requirements (#5337) Bumps [ruff](https://github.com/charliermarsh/ruff) from 0.0.261 to 0.0.272. - [Release notes](https://github.com/charliermarsh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md) - [Commits](https://github.com/charliermarsh/ruff/compare/v0.0.261...v0.0.272) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps-dev): bump pytest-cov from 4.0.0 to 4.1.0 in /requirements (#5335) Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 4.0.0 to 4.1.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v4.0.0...v4.1.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * feat: add lambda streaming support for remote invoke (#5307) * feat: support response streaming with remote invoke * add invoker and mappers * Update output formatting of stream response * add unit tests * fix formatting * Add docs * address comments * formatting * move is_function_invoke_mode_response_stream into lambda invoke executors and add/update string constants * chore: bump version to 1.87.0 * Revert app templates gha (#5356) * Revert "add sleep between close and reopen (#5320)" This reverts commit 5be690c88d580cfeee7731f549c75ed7543f47c5. * Revert "update automated updates gha to force restart of status checks (#5269)" This reverts commit deb212bc21eda2be0290e9a30f296aa74331e6c3. * refactor: make remote invoke reactive to display results as soon as they are available (#5359) * refactor: make remote invoke reactive to display results as soon as they are available * addressed the comments * refactor init_clients in sam delete (#5360) * refactor init_clients in sam delete * remove unused line * use client_provider * fix broken tests * Update samcli/commands/delete/delete_context.py Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * add telemetry * fix format --------- Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * chore: update aws-sam-translator to 1.69.0 (#5370) Co-authored-by: GitHub Action * feat: sam remote invoke help text and UX fixes (#5366) * Improve remote invoke help text and fix some UX bugs * Updated help text for parameter option * Updated test class name * Updated test method name * Updated help text for output-format and event-file * Address feedback * Updated help text for parameter option * Changed --output-format name to output and the values to text/json * Handle empty event for lambda and read from stdin when - is passed for event-file * chore: temporary pin python version to 3.7.16 (#5384) * chore: temporary pin python version to 3.7.16 * fix github action syntax error * Updated cfn-lint to support ruby3.2 in validate (#5375) * Remove unneeded test cases (#5374) * Remove unneeded test cases * Removing the two integ test cases as there is already coverage in unit test for cases that no region is specified * feat: updating app templates repo hash with (67f28fd83477e0e15b394f995afb33b2053b4074) (#5362) Co-authored-by: GitHub Action Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * test: Integration tests for remote invoke on regular lambda functions (#5382) * Created base integ glass for remote invoke tests * Add integration tests for invoking lambda functions * make black * Moved tearDownClass to base class * Removed tearDown class from inherited classes and updated lambda fn timeout * Remove the check to skip appveyor tests on master branch * feat: Make remote invoke command available (#5381) * Enabled remote invoke command and updated docs link * Created base integ glass for remote invoke tests * Added end2end integ tests for remote invoke * make black * Moved tearDownClass to base class * Remove the check to skip appveyor tests on master branch * test: Remote invoke integration tests for response stream configured lambda functions (#5383) * Created base integ glass for remote invoke tests * Add integration tests for invoking response streaming lambda fns * make black * Moved tearDownClass to base class * Moved tearDownClass method to base class and removed architectures from template file * Remove the check to skip appveyor tests on master branch * chore: bump version to 1.88.0 (#5393) * chore: fix issues with appveyor ubuntu setup #5395 * chore: remove deprecated runtime dotnetcore3.1 (#5091) * chore: remove deprecated runtime dotnetcore3.1 * apply pr comments * fix(invoke): Write in UTF-8 string instead of bytes. (#5232) * fix(invoke): Write in UTF-8 string instead of bytes. It appears that we were using sys.stdout.buffer to support python2 and python3 at the same time. Switching to just write to sys.stdout allows us to write a utf-8 encoding string. When using sys.stdout.buffer, we can only write bytes and I couldn't get the correct UTF8 encoded string to print correctly. * Fix ruff errors * Update log_streamer.py to remove encoding * More updates to make everything work better in general * Fix with ruff again * Explictingly write to stream for building images * More patching writes * More patching * Fix long line * Use mock over io.string * More fixing of tests * Assert mock instead of data directly * More small edits in test * Verify through calls instead of value * run make black * Fix when we flush to match pervious behavior and output * add integration tests * run make black --------- Co-authored-by: Jacob Fuss Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> * Revert "fix(invoke): Write in UTF-8 string instead of bytes. (#5232)" (#5401) This reverts commit 97104eac05c47aec1c7db62cb98cd050c7656d3d. * Add sanity check script and use it in pyinstaller GHA (#5400) * Add sanity check script and use it in pyinstaller GHA * set pipefail in sanity-check.sh * Make CI_OVERRIDE a global env var in the GHA workflow * setup go in GHA * disable telemetry * Update script to check binary existence and to fix an issue in go build * Resolve changes --------- Signed-off-by: dependabot[bot] Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Co-authored-by: Lucas <12496191+lucashuy@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: GitHub Action Co-authored-by: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Co-authored-by: Jacob Fuss Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> Co-authored-by: Mehmet Nuri Deveci <5735811+mndeveci@users.noreply.github.com> Co-authored-by: Wing Fung Lau <4760060+hawflau@users.noreply.github.com> Co-authored-by: hnnasit <84355507+hnnasit@users.noreply.github.com> Co-authored-by: Daniel Mil <84205762+mildaniel@users.noreply.github.com> Co-authored-by: Slava Senchenko Co-authored-by: Leonardo Gama --- .github/workflows/build.yml | 8 ++++++++ .github/workflows/validate_pyinstaller.yml | 4 ++++ appveyor-windows.yml | 7 +++++++ samcli/__init__.py | 4 ++++ samcli/commands/delete/delete_context.py | 2 +- samcli/commands/remote/invoke/cli.py | 4 ++-- samcli/lib/config/file_manager.py | 2 +- 7 files changed, 27 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03e29508fe..a48b384170 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,9 +57,17 @@ jobs: - "3.11" steps: - uses: actions/checkout@v3 + # @melasmar + # TODO: Revert back to use 3.7 to all operating systems after the regression issue in Python + # https://github.com/actions/setup-python/issues/682 in github action got resolved - uses: actions/setup-python@v4 + if: matrix.os != 'macos-latest' || ( matrix.os == 'macos-latest' && matrix.python != '3.7' ) with: python-version: ${{ matrix.python }} + - uses: actions/setup-python@v4 + if: matrix.os == 'macos-latest' && matrix.python == '3.7' + with: + python-version: "3.7.16" - run: test -f "./.github/ISSUE_TEMPLATE/Bug_report.md" # prevent Bug_report.md from being renamed or deleted - run: make init - run: make pr diff --git a/.github/workflows/validate_pyinstaller.yml b/.github/workflows/validate_pyinstaller.yml index b611420310..697e163a16 100644 --- a/.github/workflows/validate_pyinstaller.yml +++ b/.github/workflows/validate_pyinstaller.yml @@ -50,7 +50,11 @@ jobs: with: python-version: "3.7" - name: Set up Go +<<<<<<< HEAD uses: actions/setup-go@v4 +======= + uses: actions/setup-go@v3 +>>>>>>> 44daa48b (chore: Rebase config project to develop (#5406)) with: go-version: "1.20" - name: Build PyInstaller diff --git a/appveyor-windows.yml b/appveyor-windows.yml index 28567c956f..2d11524025 100644 --- a/appveyor-windows.yml +++ b/appveyor-windows.yml @@ -150,6 +150,13 @@ install: + # claim some disk space before starting the tests + - "docker system prune -a -f" + # activate virtual environment + - "venv\\Scripts\\activate" + + + # Final clean up no matter success or failure on_finish: # Upload test reports as artifacts diff --git a/samcli/__init__.py b/samcli/__init__.py index 1fea4bd55f..d19c41cc78 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,8 @@ SAM CLI version """ +<<<<<<< HEAD __version__ = "1.90.0" +======= +__version__ = "1.88.0" +>>>>>>> 44daa48b (chore: Rebase config project to develop (#5406)) diff --git a/samcli/commands/delete/delete_context.py b/samcli/commands/delete/delete_context.py index 3928c0cddb..1424c87f4a 100644 --- a/samcli/commands/delete/delete_context.py +++ b/samcli/commands/delete/delete_context.py @@ -9,7 +9,7 @@ from botocore.exceptions import NoCredentialsError, NoRegionError from click import confirm, prompt -from samcli.cli.cli_config_file import TomlProvider +from samcli.cli.cli_config_file import ConfigProvider from samcli.commands.delete.exceptions import CfDeleteFailedStatusError from samcli.commands.exceptions import AWSServiceClientError, RegionError from samcli.lib.bootstrap.companion_stack.companion_stack_builder import CompanionStack diff --git a/samcli/commands/remote/invoke/cli.py b/samcli/commands/remote/invoke/cli.py index 3f3a771ea1..0318566b4a 100644 --- a/samcli/commands/remote/invoke/cli.py +++ b/samcli/commands/remote/invoke/cli.py @@ -4,7 +4,7 @@ import click -from samcli.cli.cli_config_file import TomlProvider, configuration_option +from samcli.cli.cli_config_file import ConfigProvider, configuration_option from samcli.cli.context import Context from samcli.cli.main import aws_creds_options, common_options, pass_context, print_cmdline_args from samcli.cli.types import RemoteInvokeOutputFormatType @@ -42,7 +42,7 @@ requires_credentials=True, context_settings={"max_content_width": 120}, ) -@configuration_option(provider=TomlProvider(section="parameters")) +@configuration_option(provider=ConfigProvider(section="parameters")) @click.option("--stack-name", required=False, help="Name of the stack to get the resource information from") @click.argument("resource-id", required=False) @click.option( diff --git a/samcli/lib/config/file_manager.py b/samcli/lib/config/file_manager.py index ab1e000ff3..43f2609e4d 100644 --- a/samcli/lib/config/file_manager.py +++ b/samcli/lib/config/file_manager.py @@ -131,7 +131,7 @@ def write(document: dict, filepath: Path): toml_document = TomlFileManager._to_toml(document) if toml_document.get(COMMENT_KEY, None): # Remove dunder comments that may be residue from other formats - toml_document.add(tomlkit.comment(toml_document[COMMENT_KEY])) + toml_document.add(tomlkit.comment(toml_document.get(COMMENT_KEY, ""))) toml_document.pop(COMMENT_KEY) filepath.write_text(tomlkit.dumps(toml_document)) From d6c91a449332365a00ddf927fba4e37662f5e52c Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Thu, 29 Jun 2023 11:11:27 -0700 Subject: [PATCH 105/107] Disable JSON file extension support (#5426) Co-authored-by: Leonardo Gama --- samcli/lib/config/file_manager.py | 2 +- tests/integration/buildcmd/test_build_samconfig.py | 9 +++++---- .../testdata/buildcmd/samconfig/samconfig.yml | 7 +++++++ tests/unit/lib/samconfig/test_file_manager.py | 3 ++- tests/unit/lib/samconfig/test_samconfig.py | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 tests/integration/testdata/buildcmd/samconfig/samconfig.yml diff --git a/samcli/lib/config/file_manager.py b/samcli/lib/config/file_manager.py index 43f2609e4d..0629ace318 100644 --- a/samcli/lib/config/file_manager.py +++ b/samcli/lib/config/file_manager.py @@ -338,5 +338,5 @@ def put_comment(document: Any, comment: str) -> Any: ".toml": TomlFileManager, ".yaml": YamlFileManager, ".yml": YamlFileManager, - ".json": JsonFileManager, + # ".json": JsonFileManager, # JSON support disabled } diff --git a/tests/integration/buildcmd/test_build_samconfig.py b/tests/integration/buildcmd/test_build_samconfig.py index bdfd87762b..3df5052599 100644 --- a/tests/integration/buildcmd/test_build_samconfig.py +++ b/tests/integration/buildcmd/test_build_samconfig.py @@ -9,6 +9,7 @@ configs = { ".toml": "samconfig/samconfig.toml", ".yaml": "samconfig/samconfig.yaml", + ".yml": "samconfig/samconfig.yml", ".json": "samconfig/samconfig.json", } @@ -18,7 +19,7 @@ class TestSamConfigWithBuild(BuildIntegBase): [ (".toml"), (".yaml"), - (".json"), + # (".json"), ] ) def test_samconfig_works_with_extension(self, extension): @@ -40,7 +41,7 @@ def test_samconfig_works_with_extension(self, extension): [ (".toml"), (".yaml"), - (".json"), + # (".json"), ] ) def test_samconfig_parameters_are_overridden(self, extension): @@ -72,8 +73,8 @@ def test_samconfig_parameters_are_overridden(self, extension): @parameterized_class( [ # Ordered by expected priority - {"extensions": [".toml", ".yaml", ".json"]}, - {"extensions": [".yaml", ".json"]}, + {"extensions": [".toml", ".yaml", ".yml"]}, + {"extensions": [".yaml", ".yml"]}, ] ) class TestSamConfigExtensionHierarchy(BuildIntegBase): diff --git a/tests/integration/testdata/buildcmd/samconfig/samconfig.yml b/tests/integration/testdata/buildcmd/samconfig/samconfig.yml new file mode 100644 index 0000000000..4af8baa434 --- /dev/null +++ b/tests/integration/testdata/buildcmd/samconfig/samconfig.yml @@ -0,0 +1,7 @@ +version: 0.1 +default: + build: + parameters: + build_dir: .yml + cached: true + parameter_overrides: Runtime=python3.9 CodeUri=SomeURI Handler=SomeHandler \ No newline at end of file diff --git a/tests/unit/lib/samconfig/test_file_manager.py b/tests/unit/lib/samconfig/test_file_manager.py index eb9325fc37..3b5ddce96c 100644 --- a/tests/unit/lib/samconfig/test_file_manager.py +++ b/tests/unit/lib/samconfig/test_file_manager.py @@ -1,7 +1,7 @@ import json from pathlib import Path import tempfile -from unittest import TestCase +from unittest import TestCase, skip import tomlkit from ruamel.yaml import YAML @@ -182,6 +182,7 @@ def test_yaml_put_comment(self): self.assertIn("# This is a comment", txt) +@skip("JSON config support disabled") class TestJsonFileManager(TestCase): def test_read_json(self): config_dir = tempfile.gettempdir() diff --git a/tests/unit/lib/samconfig/test_samconfig.py b/tests/unit/lib/samconfig/test_samconfig.py index df8ceef80b..c58f0709a6 100644 --- a/tests/unit/lib/samconfig/test_samconfig.py +++ b/tests/unit/lib/samconfig/test_samconfig.py @@ -295,7 +295,7 @@ def test_file_manager_unsupported(self): ("samconfig.toml", TomlFileManager, ".toml"), ("samconfig.yaml", YamlFileManager, ".yaml"), ("samconfig.yml", YamlFileManager, ".yml"), - ("samconfig.json", JsonFileManager, ".json"), + # ("samconfig.json", JsonFileManager, ".json"), ] ) @patch("samcli.lib.telemetry.event.EventTracker.track_event") From 6cb6f25a403014e6f4fe4d1d8303150dc28f2a3f Mon Sep 17 00:00:00 2001 From: Leonardo Gama <51037424+Leo10Gama@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:26:40 -0400 Subject: [PATCH 106/107] feat: Repair and refine tests (#5431) * Fix failing integration test * Add FileManager check for array param --------- Co-authored-by: Leonardo Gama --- .../integration/deploy/test_deploy_command.py | 2 +- tests/unit/lib/samconfig/test_file_manager.py | 28 +++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/integration/deploy/test_deploy_command.py b/tests/integration/deploy/test_deploy_command.py index 7221c40264..a7ece04a5c 100644 --- a/tests/integration/deploy/test_deploy_command.py +++ b/tests/integration/deploy/test_deploy_command.py @@ -826,7 +826,7 @@ def test_deploy_with_invalid_config(self, template_file, config_file): deploy_process_execute = self.run_command(deploy_command_list) self.assertEqual(deploy_process_execute.process.returncode, 1) - self.assertIn("Error reading configuration: Unexpected character", str(deploy_process_execute.stderr)) + self.assertIn("SamConfigFileReadException: Unexpected character", str(deploy_process_execute.stderr)) @parameterized.expand([("aws-serverless-function.yaml", "samconfig-tags-list.toml")]) def test_deploy_with_valid_config_tags_list(self, template_file, config_file): diff --git a/tests/unit/lib/samconfig/test_file_manager.py b/tests/unit/lib/samconfig/test_file_manager.py index 3b5ddce96c..18df66474c 100644 --- a/tests/unit/lib/samconfig/test_file_manager.py +++ b/tests/unit/lib/samconfig/test_file_manager.py @@ -14,11 +14,16 @@ class TestTomlFileManager(TestCase): def test_read_toml(self): config_dir = tempfile.gettempdir() config_path = Path(config_dir, "samconfig.toml") - config_path.write_text("version=0.1\n[config_env.topic1.parameters]\nword='clarity'\n") + config_path.write_text( + "version=0.1\n[config_env.topic1.parameters]\nword='clarity'\nmultiword=['thing 1', 'thing 2']" + ) config_doc = TomlFileManager.read(config_path) self.assertEqual( config_doc, - {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}}, + { + "version": 0.1, + "config_env": {"topic1": {"parameters": {"word": "clarity", "multiword": ["thing 1", "thing 2"]}}}, + }, ) def test_read_toml_invalid_toml(self): @@ -111,13 +116,18 @@ class TestYamlFileManager(TestCase): def test_read_yaml(self): config_dir = tempfile.gettempdir() config_path = Path(config_dir, "samconfig.yaml") - config_path.write_text("version: 0.1\nconfig_env:\n topic1:\n parameters:\n word: clarity\n") + config_path.write_text( + "version: 0.1\nconfig_env:\n topic1:\n parameters:\n word: clarity\n multiword: [thing 1, thing 2]" + ) config_doc = YamlFileManager.read(config_path) self.assertEqual( config_doc, - {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}}, + { + "version": 0.1, + "config_env": {"topic1": {"parameters": {"word": "clarity", "multiword": ["thing 1", "thing 2"]}}}, + }, ) def test_read_yaml_invalid_yaml(self): @@ -189,7 +199,10 @@ def test_read_json(self): config_path = Path(config_dir, "samconfig.json") config_path.write_text( json.dumps( - {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}}, + { + "version": 0.1, + "config_env": {"topic1": {"parameters": {"word": "clarity", "multiword": ["thing 1", "thing 2"]}}}, + }, indent=JsonFileManager.INDENT_SIZE, ) ) @@ -198,7 +211,10 @@ def test_read_json(self): self.assertEqual( config_doc, - {"version": 0.1, "config_env": {"topic1": {"parameters": {"word": "clarity"}}}}, + { + "version": 0.1, + "config_env": {"topic1": {"parameters": {"word": "clarity", "multiword": ["thing 1", "thing 2"]}}}, + }, ) def test_read_json_invalid_json(self): From 390f00813aaea6abb12ba8d32014b111241f912e Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Fri, 7 Jul 2023 11:12:03 -0700 Subject: [PATCH 107/107] Fixed merge error --- .github/workflows/validate_pyinstaller.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/validate_pyinstaller.yml b/.github/workflows/validate_pyinstaller.yml index 47725933f9..b611420310 100644 --- a/.github/workflows/validate_pyinstaller.yml +++ b/.github/workflows/validate_pyinstaller.yml @@ -50,15 +50,7 @@ jobs: with: python-version: "3.7" - name: Set up Go -<<<<<<< HEAD -<<<<<<< HEAD uses: actions/setup-go@v4 -======= - uses: actions/setup-go@v3 ->>>>>>> 44daa48b (chore: Rebase config project to develop (#5406)) -======= - uses: actions/setup-go@v3 ->>>>>>> 2cf0bdfb1744f9745458757711a9a3f734c9108c with: go-version: "1.20" - name: Build PyInstaller