diff --git a/appveyor-ubuntu.yml b/appveyor-ubuntu.yml index e4168dacb9..80ea8bfa25 100644 --- a/appveyor-ubuntu.yml +++ b/appveyor-ubuntu.yml @@ -303,7 +303,7 @@ for: test_script: - "pip install -e \".[dev]\"" - - sh: "pytest -vv tests/integration/sync --json-report --json-report-file=TEST_REPORT-integration-sync.json" + - 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 - diff --git a/appveyor-windows.yml b/appveyor-windows.yml index 6cdfadc038..5cd829513f 100644 --- a/appveyor-windows.yml +++ b/appveyor-windows.yml @@ -293,7 +293,7 @@ for: - "git --version" - "venv\\Scripts\\activate" - "docker system prune -a -f" - - ps: "pytest -vv tests/integration/sync --json-report --json-report-file=TEST_REPORT-integration-sync.json" + - 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 - matrix: diff --git a/requirements/base.txt b/requirements/base.txt index e0b9064d86..f2a0a511ae 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -7,7 +7,7 @@ jmespath~=0.10.0 ruamel_yaml==0.17.21 PyYAML>=5.4.1,==5.* cookiecutter~=2.1.1 -aws-sam-translator==1.64.0 +aws-sam-translator==1.65.0 #docker minor version updates can include breaking changes. Auto update micro version only. docker~=4.2.0 dateparser~=1.1 diff --git a/requirements/dev.txt b/requirements/dev.txt index 1210c0603f..1da9b0f01c 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -12,7 +12,7 @@ boto3-stubs[apigateway,cloudformation,ecr,iam,lambda,s3,schemas,secretsmanager,s types-pywin32==306.0.0.0 types-PyYAML==6.0.12 types-chevron==0.14.2 -types-psutil==5.9.5.1 +types-psutil==5.9.5.12 types-setuptools==65.4.0.0 # Test requirements diff --git a/requirements/reproducible-linux.txt b/requirements/reproducible-linux.txt index 5ef8898dfe..8f57ed3887 100644 --- a/requirements/reproducible-linux.txt +++ b/requirements/reproducible-linux.txt @@ -19,9 +19,9 @@ aws-lambda-builders==1.30.0 \ --hash=sha256:861d3e6099590562fcb9f6fc5d014a26f5b5fc8aebb1d18529cd4179514f2a48 \ --hash=sha256:ab22ffe47995bbbaff531af453746f3365fe01947c1ea8d98a7ae3ab52b46426 # via aws-sam-cli (setup.py) -aws-sam-translator==1.64.0 \ - --hash=sha256:0cc5b07dd6ef1de3525d887a3b9557168e04cb44327706a43661653bad30687f \ - --hash=sha256:c44725f12b05d4881e3bc077f70e23ebce56ea78c729acf0ca9f51302b27d304 +aws-sam-translator==1.65.0 \ + --hash=sha256:34dcd1f86bfc3ae11f3defe6f67b74fe863dacf0a3b04156c41dc92600e8dc8d \ + --hash=sha256:49cf0583a1bc17c686417c11132c5122b40ecbcb9980e0c9e58b65914dd76ebc # via # aws-sam-cli (setup.py) # cfn-lint diff --git a/samcli/__init__.py b/samcli/__init__.py index b74a5d3be2..e70b7156df 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.81.0" +__version__ = "1.82.0" diff --git a/samcli/hook_packages/terraform/designs/resource_linking_generalized.md b/samcli/hook_packages/terraform/designs/resource_linking_generalized.md new file mode 100644 index 0000000000..4ae98c2d28 --- /dev/null +++ b/samcli/hook_packages/terraform/designs/resource_linking_generalized.md @@ -0,0 +1,163 @@ +# Resource Linking + +After translating a resource from Terraform to CloudFormation, +that resource often needs to be linked to another based on a field. + +For example, AWS Lambda Functions are linked to AWS Lambda Layers through the +`Layers` property of a Lambda Function. + +E.g. +```yaml +Resources: + AwsLambdaFunction: + Type: AWS::Lambda::Function + Properties: + FunctionName: s3_lambda_function + Code: function.zip + Handler: app.lambda_handler + PackageType: Zip + Runtime: python3.8 + Layers: + - Ref: AwsLambdaLayerVersion + AwsLambdaLayerVersion: + Type: AWS::Lambda::LayerVersion + Properties: + LayerName: lambda_layer1 + CompatibleRuntimes: + - python3.8 + Content: layer.zip +``` + +In this case, the function will be defined as the `source` resource and the layer +as the `destination` resource. The `Layers` field links to the destination resource +using a CloudFormation intrinsic function `!Ref`, and points to the Lambda Layer Arn. + +Today, the linking logic is specific to Functions and Layers. The section below proposes +a method of generalization to make the linking of two resources more extensible and easier to implement. + +## Proposed Solution +Currently, the linking design is functional and passes certain required parameters from +the translation function to a specific Function to Layer linking function. Since the fields required to link any two resources primarily the same, they can be abstracted away to a data class +that will house them. + +```python +@dataclass +class ResourceLinkingPair: + source_resource_cfn_resource: Dict[str, List] + source_resource_tf_config: Dict[str, TFResource] + destination_resource_tf: Dict[str, Dict] + intrinsic_type: LinkerIntrinsics + cfn_intrinsic_attribute: Optional[str] + source_link_field_name: str + terraform_resource_type_prefix: str + linking_exceptions: ResourcePairLinkingExceptions +``` + +- `source_resource_cfn_resource` this the CFN representation of the resource to which the linked resource will be added +- `source_resource_tf_config` the Terraform configuration object for the source resource +- `destination_resource_tf` the destination resources' Terraform planned values (not the configuration object) +- `intrinsic_type` the CFN intrinsic on which to link (`Ref` or `GetAtt`) +- `cfn_intrinsic_attribute` in the case of `GetAtt`, the resource attribute to link to (should be None for `Ref`) +- `source_link_field_name` the name in the source CFN resource to add the linked resource to +- `terraform_resource_type_prefix` the Terraform resource type prefix used for finding all resource of that type +- `linking_exceptions` these are exceptions that need to be created with messages specific to the resource linking pair to be used +by the `ResourceLinker`. With specific exception types, metric data can be collected specific to the resources being linked. + +```python +class ResourcePairLinkingExceptions: + multiple_resource_linking_exception: Type[UserException] + local_variable_linking_exception: Type[UserException] +``` + + +Only the first three fields are computed, and need to be collected the same way they are today, when parsing the Terraform modules. +E.g. collecting the Lambda function `source_resource_cfn_resource` and `source_resource_tf_config` looks like this: +```python +if resource_type == TF_AWS_LAMBDA_FUNCTION: + resolved_config_address = _get_configuration_address(resource_full_address) + matched_lambdas = source_resource_cfn_resource.get(resolved_config_address, []) + matched_lambdas.append(translated_resource) + source_resource_cfn_resource[resolved_config_address] = matched_lambdas + source_resource_tf_config[resolved_config_address] = config_resource +``` + +and on the destination Layer resource, collecting the `destination_resource_tf` +```python +if resource_type == TF_AWS_LAMBDA_LAYER_VERSION: + destination_resource_tf[logical_id] = resource +``` + +After collecting those three fields, a `ResourceLinkingPair` object can be instantiated. + +Once all `ResourceLinkingPair` objects have been created, a list of these objects can be passed to the `ResourceLinker`. +```python +class ResourceLinker: + _resource_pairs: List[ResourceLinkingPair] + + def link_resources(self): + """ + Iterate through all of the ResourceLinkingPair items and link the + corresponding source resource to destination resource + """ + + def _update_mapped_parent_resource_with_resolved_child_resources(self, destination_resources: List): + """ + Set the resolved destination resource list to the mapped source resources. + + Parameters + ---------- + destination_resources: List + The resolved destination resource values that will be used as a value for the mapped CFN resource attribute. + """ + + def _process_reference_resource_value(self, resolved_destination_resource: ResolvedReference): + """ + Process the a reference destination resource value of type ResolvedReference. + + Parameters + ---------- + resolved_destination_resource: ResolvedReference + The resolved destination resource reference. + + Returns + ------- + List[Dict[str, str]] + The resolved values that will be used as a value for the mapped CFN resource attribute. + """ + + def _process_resolved_resources(self, resolved_destination_resource: List[Union[ConstantValue, ResolvedReference]]): + """ + Process the resolved destination resources. + + Parameters + ---------- + resolved_destination_resource: List[Union[ConstantValue, ResolvedReference]] + The resolved destination resources to be processed for the input source resource. + + Returns + -------- + List[Dict[str, str]]: + The list of destination resources after processing + """ +``` + +The `ResourceLinker` contains a public `link_resources` method to begin execution of the resource linking. The methods +shown in the class are all generalized versions of the existing Function to Layer linking functions that exist today. + +`link_resources` ← `_link_lambda_function_to_layer`, `_link_lambda_functions_to_layers` +`_update_mapped_parent_resource_with_resolved_child_resources` ← `_update_mapped_lambda_function_with_resolved_layers` +`_process_reference_resource_value` ← `_process_reference_layer_value` +`_process_resolved_resources` ← `_process_resolved_layers` + +To generalize these functions: +- All instances of hard-coded properties need to be replaced with the corresponding values +defined in the `ResourceLinkingPair` instance +- Exception messages should be updated to be more generic +- The `_update_mapped_parent_resource_with_resolved_child_resources` method should be updated to support writing other intrinsic types. + + +### Adding New Resource Links +With the proposed change, creating a new link between two resources will include: +1. Collecting the three required fields (`source_resource_cfn_resource`, `source_resource_tf_config` and `destination_resource_tf`) +2. Creating a `ResourceLinkingPair` instance +3. Appending the `ResourceLinkingPair` to the list of pairs \ No newline at end of file diff --git a/samcli/hook_packages/terraform/hooks/prepare/property_builder.py b/samcli/hook_packages/terraform/hooks/prepare/property_builder.py index fc33a9ba02..014e8d6a52 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/property_builder.py +++ b/samcli/hook_packages/terraform/hooks/prepare/property_builder.py @@ -12,6 +12,7 @@ ) from samcli.lib.hook.exceptions import PrepareHookException from samcli.lib.utils.packagetype import IMAGE, ZIP +from samcli.lib.utils.resources import AWS_APIGATEWAY_METHOD as CFN_AWS_APIGATEWAY_METHOD from samcli.lib.utils.resources import AWS_APIGATEWAY_RESOURCE as CFN_AWS_APIGATEWAY_RESOURCE from samcli.lib.utils.resources import AWS_APIGATEWAY_RESTAPI as CFN_AWS_APIGATEWAY_RESTAPI from samcli.lib.utils.resources import AWS_APIGATEWAY_STAGE as CFN_AWS_APIGATEWAY_STAGE @@ -25,6 +26,7 @@ TF_AWS_API_GATEWAY_RESOURCE = "aws_api_gateway_resource" TF_AWS_API_GATEWAY_REST_API = "aws_api_gateway_rest_api" TF_AWS_API_GATEWAY_STAGE = "aws_api_gateway_stage" +TF_AWS_API_GATEWAY_METHOD = "aws_api_gateway_method" def _build_code_property(tf_properties: dict, resource: TFResource) -> Any: @@ -241,6 +243,13 @@ def _check_image_config_value(image_config: Any) -> bool: "PathPart": _get_property_extractor("path_part"), } +AWS_API_GATEWAY_METHOD_PROPERTY_BUILDER_MAPPING: PropertyBuilderMapping = { + "RestApiId": _get_property_extractor("rest_api_id"), + "ResourceId": _get_property_extractor("resource_id"), + "HttpMethod": _get_property_extractor("http_method"), + "OperationName": _get_property_extractor("operation_name"), +} + RESOURCE_TRANSLATOR_MAPPING: Dict[str, ResourceTranslator] = { TF_AWS_LAMBDA_FUNCTION: ResourceTranslator(CFN_AWS_LAMBDA_FUNCTION, AWS_LAMBDA_FUNCTION_PROPERTY_BUILDER_MAPPING), TF_AWS_LAMBDA_LAYER_VERSION: ResourceTranslator( @@ -255,4 +264,7 @@ def _check_image_config_value(image_config: Any) -> bool: TF_AWS_API_GATEWAY_RESOURCE: ResourceTranslator( CFN_AWS_APIGATEWAY_RESOURCE, AWS_API_GATEWAY_RESOURCE_PROPERTY_BUILDER_MAPPING ), + TF_AWS_API_GATEWAY_METHOD: ResourceTranslator( + CFN_AWS_APIGATEWAY_METHOD, AWS_API_GATEWAY_METHOD_PROPERTY_BUILDER_MAPPING + ), } diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index dc8ad55e30..3bb167eefb 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -4,8 +4,11 @@ """ import logging import re -from typing import Dict, List, Optional, Union +from dataclasses import dataclass +from enum import Enum +from typing import Dict, List, Optional, Type, Union +from samcli.commands.exceptions import UserException from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( InvalidResourceLinkingException, LocalVariablesLinkingLimitationException, @@ -29,6 +32,78 @@ COMPILED_REGULAR_EXPRESSION = re.compile(r"\[[^\[\]]*\]") +class LinkerIntrinsics(Enum): + Ref = "Ref" + GetAtt = "GetAtt" + + +class ResourcePairLinkingExceptions: + multiple_resource_linking_exception: Type[UserException] + local_variable_linking_exception: Type[UserException] + + +@dataclass +class ResourceLinkingPair: + source_resource_cfn_resource: Dict[str, List] + source_resource_tf_config: Dict[str, TFResource] + destination_resource_tf: Dict[str, Dict] + intrinsic_type: LinkerIntrinsics + cfn_intrinsic_attribute: Optional[str] + source_link_field_name: str + terraform_resource_type_prefix: str + linking_exceptions: ResourcePairLinkingExceptions + + +class ResourceLinker: + _resource_pairs: List[ResourceLinkingPair] + + def link_resources(self): + """ + Iterate through all of the ResourceLinkingPair items and link the + corresponding source resource to destination resource + """ + + def _update_mapped_parent_resource_with_resolved_child_resources(self, destination_resources: List): + """ + Set the resolved destination resource list to the mapped source resources. + + Parameters + ---------- + destination_resources: List + The resolved destination resource values that will be used as a value for the mapped CFN resource attribute. + """ + + def _process_reference_resource_value(self, resolved_destination_resource: ResolvedReference): + """ + Process the a reference destination resource value of type ResolvedReference. + + Parameters + ---------- + resolved_destination_resource: ResolvedReference + The resolved destination resource reference. + + Returns + ------- + List[Dict[str, str]] + The resolved values that will be used as a value for the mapped CFN resource attribute. + """ + + def _process_resolved_resources(self, resolved_destination_resource: List[Union[ConstantValue, ResolvedReference]]): + """ + Process the resolved destination resources. + + Parameters + ---------- + resolved_destination_resource: List[Union[ConstantValue, ResolvedReference]] + The resolved destination resources to be processed for the input source resource. + + Returns + -------- + List[Dict[str, str]]: + The list of destination resources after processing + """ + + def _build_module( module_name: Optional[str], module_configuration: Dict, diff --git a/samcli/lib/package/utils.py b/samcli/lib/package/utils.py index 4a4c93b1f7..6434a70d5f 100644 --- a/samcli/lib/package/utils.py +++ b/samcli/lib/package/utils.py @@ -225,7 +225,7 @@ def zip_folder(folder_path, zip_method): The md5 hash of the directory """ md5hash = dir_checksum(folder_path, followlinks=True) - filename = os.path.join(tempfile.gettempdir(), "data-" + md5hash) + filename = os.path.join(tempfile.mkdtemp(), "data-" + md5hash) zipfile_name = zip_method(filename, folder_path) try: diff --git a/samcli/lib/providers/cfn_api_provider.py b/samcli/lib/providers/cfn_api_provider.py index 081ffca2ed..0006793270 100644 --- a/samcli/lib/providers/cfn_api_provider.py +++ b/samcli/lib/providers/cfn_api_provider.py @@ -145,7 +145,7 @@ def _extract_cloud_formation_authorizer(logical_id: str, resource: dict, collect validation_string=validation_expression, ) - collector.add_authorizers(rest_api_id, {name: lambda_authorizer}) + collector.add_authorizers(rest_api_id, {logical_id: lambda_authorizer}) @staticmethod def _extract_cfn_gateway_v2_authorizer(logical_id: str, resource: dict, collector: ApiCollector) -> None: @@ -185,7 +185,7 @@ def _extract_cfn_gateway_v2_authorizer(logical_id: str, resource: dict, collecto use_simple_response=simple_responses, ) - collector.add_authorizers(api_id, {name: lambda_authorizer}) + collector.add_authorizers(api_id, {logical_id: lambda_authorizer}) @staticmethod def _extract_cloud_formation_route( diff --git a/samcli/lib/utils/preview_runtimes.py b/samcli/lib/utils/preview_runtimes.py index 4b6a526219..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] = {"java17"} +PREVIEW_RUNTIMES: Set[str] = set() diff --git a/samcli/local/apigw/local_apigw_service.py b/samcli/local/apigw/local_apigw_service.py index 5c96efd109..4434d1480a 100644 --- a/samcli/local/apigw/local_apigw_service.py +++ b/samcli/local/apigw/local_apigw_service.py @@ -546,12 +546,14 @@ def _build_v2_context(self, route: Route) -> Dict[str, Any]: return context.to_dict() - def _valid_identity_sources(self, route: Route) -> bool: + def _valid_identity_sources(self, request: Request, route: Route) -> bool: """ Validates if the route contains all the valid identity sources defined in the route's Lambda Authorizer Parameters ---------- + request: Request + Flask request object containing incoming request variables route: Route the Route object that contains the Lambda Authorizer definition @@ -654,7 +656,7 @@ def _request_handler(self, **kwargs): return self.service_response("", headers, 200) # check for LambdaAuthorizer since that is the only authorizer we currently support - if isinstance(lambda_authorizer, LambdaAuthorizer) and not self._valid_identity_sources(route): + if isinstance(lambda_authorizer, LambdaAuthorizer) and not self._valid_identity_sources(request, route): return ServiceErrorResponses.missing_lambda_auth_identity_sources() try: diff --git a/samcli/runtime_config.json b/samcli/runtime_config.json index 575111caad..aa47847af9 100644 --- a/samcli/runtime_config.json +++ b/samcli/runtime_config.json @@ -1,3 +1,3 @@ { - "app_template_repo_commit": "6777f618c08af939c4f10cb6404c238a2d06fd34" + "app_template_repo_commit": "c351e6150ad3989d8d062436f283612378ffef75" } diff --git a/tests/functional/commands/validate/lib/models/api_merge_definitions_with_any_method.yaml b/tests/functional/commands/validate/lib/models/api_merge_definitions_with_any_method.yaml new file mode 100644 index 0000000000..0b50b1aedd --- /dev/null +++ b/tests/functional/commands/validate/lib/models/api_merge_definitions_with_any_method.yaml @@ -0,0 +1,25 @@ +Resources: + WebhooksApi: + Type: AWS::Serverless::Api + Properties: + StageName: live + MergeDefinitions: true + DefinitionBody: + swagger: 2 + x-amazon-apigateway-policy: + Version: '2012-10-17' + + + WebhooksReceiver: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://bucket/key + Handler: code/handler + Runtime: python3.8 + Events: + AllEvent: + Type: Api + Properties: + RestApiId: !Ref WebhooksApi + Path: /proxy + Method: any diff --git a/tests/functional/commands/validate/lib/models/connector_with_non_id_source_and_destination.yaml b/tests/functional/commands/validate/lib/models/connector_with_non_id_source_and_destination.yaml new file mode 100644 index 0000000000..e34b713f7c --- /dev/null +++ b/tests/functional/commands/validate/lib/models/connector_with_non_id_source_and_destination.yaml @@ -0,0 +1,110 @@ +Transform: AWS::Serverless-2016-10-31 + +Resources: + MyRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Effect: Allow + Action: sts:AssumeRole + Principal: + Service: lambda.amazonaws.com + ManagedPolicyArns: + - arn:{AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + + SamFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: nodejs14.x + Handler: index.handler + Role: !GetAtt MyRole.Arn + InlineCode: | + const AWS = require('aws-sdk'); + exports.handler = async (event) => { + console.log(JSON.stringify(event)); + }; + + SamTable: + Type: AWS::Serverless::SimpleTable + Properties: + PrimaryKey: + Name: NoteId + Type: String + + SamStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + # Express state machine support sync execution + # which allows us to get the error message quickly in trigger function. + Type: EXPRESS + Role: !GetAtt MyRole.Arn + Definition: + StartAt: MyLambdaState + States: + MyLambdaState: + Type: Task + Resource: !GetAtt SamFunction.Arn + End: true + + SamApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionUri: s3://sam-demo-bucket/webpage_swagger.json + Description: my description + + SamHttpApi: + Type: AWS::Serverless::HttpApi + Properties: + StageName: Prod + + Connector1: + Type: AWS::Serverless::Connector + Properties: + Source: + Type: AWS::Serverless::Function + RoleName: MyRole + Destination: + Type: AWS::Serverless::SimpleTable + Arn: !GetAtt SamTable.Arn + Permissions: + - Read + + Connector2: + Type: AWS::Serverless::Connector + Properties: + Source: + Type: AWS::Serverless::Api + ResourceId: !Ref SamApi + Qualifier: Prod/GET/foobar + Destination: + Type: AWS::Serverless::Function + Arn: !GetAtt SamFunction.Arn + Permissions: + - Write + + Connector3: + Type: AWS::Serverless::Connector + Properties: + Source: + Type: AWS::Serverless::StateMachine + RoleName: MyRole + Destination: + Type: AWS::Serverless::Function + Arn: !GetAtt SamFunction.Arn + Permissions: + - Write + + Connector4: + Type: AWS::Serverless::Connector + Properties: + Source: + Type: AWS::Serverless::HttpApi + ResourceId: !Ref SamHttpApi + Qualifier: Prod/GET/foobar + Destination: + Type: AWS::Serverless::Function + Arn: !GetAtt SamFunction.Arn + Permissions: + - Write diff --git a/tests/functional/commands/validate/lib/models/schema_validation_4.yaml b/tests/functional/commands/validate/lib/models/schema_validation_4.yaml index 90e23fc18f..d790743dcb 100644 --- a/tests/functional/commands/validate/lib/models/schema_validation_4.yaml +++ b/tests/functional/commands/validate/lib/models/schema_validation_4.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=../../../samtranslator/schema/schema.json Transform: AWS::Serverless-2016-10-31 AWSTemplateFormatVersion: '2010-09-09' Description: Some description @@ -55,6 +56,16 @@ Resources: - subnet-071f712345678e7c8 - subnet-07fd123456788a036 + OtherFunction: + Type: AWS::Serverless::Function + Properties: + Runtime: python3.8 + Handler: foo + InlineCode: bar + Environment: + Variables: + Some: variable + Outputs: BackupLoadBalancerDNSName: Description: The DNSName of the backup load balancer diff --git a/tests/integration/sync/sync_integ_base.py b/tests/integration/sync/sync_integ_base.py index 7c5ca68cbe..575cfc1b00 100644 --- a/tests/integration/sync/sync_integ_base.py +++ b/tests/integration/sync/sync_integ_base.py @@ -28,11 +28,22 @@ class SyncIntegBase(BuildIntegBase, PackageIntegBase): + test_data_path = None + @classmethod - def setUpClass(cls): + def setUpClass(cls) -> None: PackageIntegBase.setUpClass() - cls.test_data_path = Path(__file__).resolve().parents[1].joinpath("testdata", "sync") + original_test_data_path = Path(__file__).resolve().parents[1].joinpath("testdata", "sync") + cls.test_data_path = Path(tempfile.mkdtemp()) + # since dirs_exist_ok=True only supported after py3.7, first delete the parent folder and run copytree after + shutil.rmtree(cls.test_data_path) + shutil.copytree(original_test_data_path, cls.test_data_path) + + @classmethod + def tearDownClass(cls) -> None: + if cls.test_data_path: + shutil.rmtree(cls.test_data_path, ignore_errors=True) def setUp(self): self.cfn_client = boto3.client("cloudformation") @@ -258,7 +269,10 @@ def get_sync_command_list( if profile: command_list += ["--profile", str(profile)] if parameter_overrides: - command_list += ["--parameter-overrides", str(parameter_overrides)] + arg_value = " ".join( + ["ParameterKey={},ParameterValue={}".format(key, value) for key, value in parameter_overrides.items()] + ) + command_list = command_list + ["--parameter-overrides", arg_value] if base_dir: command_list += ["-s", str(base_dir)] if image_repository: diff --git a/tests/integration/sync/test_sync_adl.py b/tests/integration/sync/test_sync_adl.py index ba7ac3d8e5..854325af20 100644 --- a/tests/integration/sync/test_sync_adl.py +++ b/tests/integration/sync/test_sync_adl.py @@ -28,7 +28,7 @@ def test_sync_code_function_without_dependencies(self): # update app.py with updated response self.update_file( self.test_data_path.joinpath("code", "after", "python_function_no_deps", "app_without_numpy.py"), - TestSyncCode.temp_dir.joinpath("python_function_no_deps", "app.py"), + self.test_data_path.joinpath("code", "before", "python_function_no_deps", "app.py"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -38,13 +38,12 @@ def test_sync_code_function_without_dependencies(self): resource_id_list=["HelloWorldFunction"], dependency_layer=True, stack_name=TestSyncCode.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # Confirm lambda returns updated response @@ -55,7 +54,7 @@ def test_sync_code_function_without_dependencies(self): # update app.py with some dependency which is missing in requirements.txt self.update_file( self.test_data_path.joinpath("code", "after", "python_function_no_deps", "app_with_numpy.py"), - TestSyncCode.temp_dir.joinpath("python_function_no_deps", "app.py"), + self.test_data_path.joinpath("code", "before", "python_function_no_deps", "app.py"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -65,13 +64,12 @@ def test_sync_code_function_without_dependencies(self): resource_id_list=["HelloWorldFunction"], dependency_layer=True, stack_name=TestSyncCode.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # confirm that lambda execution will fail @@ -81,7 +79,7 @@ def test_sync_code_function_without_dependencies(self): # finally, update requirements.txt with missing dependency self.update_file( self.test_data_path.joinpath("code", "after", "python_function_no_deps", "requirements.txt"), - TestSyncCode.temp_dir.joinpath("python_function_no_deps", "requirements.txt"), + self.test_data_path.joinpath("code", "before", "python_function_no_deps", "requirements.txt"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -91,13 +89,12 @@ def test_sync_code_function_without_dependencies(self): resource_id_list=["HelloWorldFunction"], dependency_layer=True, stack_name=TestSyncCode.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) lambda_functions = self.stack_resources.get(AWS_LAMBDA_FUNCTION) @@ -109,11 +106,9 @@ def test_sync_code_function_without_dependencies(self): @skipIf(SKIP_SYNC_TESTS or IS_WINDOWS, "Skip sync tests in CI/CD only") class TestSyncAdlWithWatchStartWithNoDependencies(TestSyncWatchBase): - @classmethod - def setUpClass(cls): - cls.template_before = os.path.join("code", "before", "template-python-no-dependencies.yaml") - cls.dependency_layer = True - super().setUpClass() + template_before = os.path.join("code", "before", "template-python-no-dependencies.yaml") + folder = "code" + dependency_layer = True def run_initial_infra_validation(self): self.stack_resources = self._get_stacks(self.stack_name) @@ -128,8 +123,8 @@ def test_sync_watch_code(self): # change lambda with another output self.update_file( - self.test_dir.joinpath("code", "after", "python_function_no_deps", "app_without_numpy.py"), - self.test_dir.joinpath("code", "before", "python_function_no_deps", "app.py"), + self.test_data_path.joinpath("code", "after", "python_function_no_deps", "app_without_numpy.py"), + self.test_data_path.joinpath("code", "before", "python_function_no_deps", "app.py"), ) read_until_string( self.watch_process, @@ -142,8 +137,8 @@ def test_sync_watch_code(self): # change lambda with import with missing dependency self.update_file( - self.test_dir.joinpath("code", "after", "python_function_no_deps", "app_with_numpy.py"), - self.test_dir.joinpath("code", "before", "python_function_no_deps", "app.py"), + self.test_data_path.joinpath("code", "after", "python_function_no_deps", "app_with_numpy.py"), + self.test_data_path.joinpath("code", "before", "python_function_no_deps", "app.py"), ) read_until_string( self.watch_process, @@ -154,8 +149,8 @@ def test_sync_watch_code(self): # add dependency and confirm it executes as expected self.update_file( - self.test_dir.joinpath("code", "after", "python_function_no_deps", "requirements.txt"), - self.test_dir.joinpath("code", "before", "python_function_no_deps", "requirements.txt"), + self.test_data_path.joinpath("code", "after", "python_function_no_deps", "requirements.txt"), + self.test_data_path.joinpath("code", "before", "python_function_no_deps", "requirements.txt"), ) read_until_string( self.watch_process, @@ -186,14 +181,13 @@ def test_sync_esbuild(self): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, capabilities_list=["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"], tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) self.assertIn("Sync infra completed.", str(sync_process_execute.stderr)) diff --git a/tests/integration/sync/test_sync_build_in_source.py b/tests/integration/sync/test_sync_build_in_source.py index b6969cdcd4..5af1a917ce 100644 --- a/tests/integration/sync/test_sync_build_in_source.py +++ b/tests/integration/sync/test_sync_build_in_source.py @@ -50,7 +50,6 @@ def test_sync_builds_and_deploys_successfully(self, build_in_source, new_file_sh template_file=template_path, stack_name=stack_name, dependency_layer=self.dependency_layer, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -118,7 +117,6 @@ def test_sync_code_builds_and_deploys_successfully(self, build_in_source, new_fi stack_name=TestSyncCodeBase.stack_name, code=True, dependency_layer=self.dependency_layer, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -177,7 +175,6 @@ def test_sync_builds_successfully_without_local_dependencies( template_file=template_path, stack_name=stack_name, dependency_layer=self.dependency_layer, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -212,7 +209,7 @@ def test_sync_builds_successfully_with_local_dependency(self): template_file=template_path, stack_name=stack_name, dependency_layer=self.dependency_layer, - parameter_overrides=f"Parameter=Clarity CodeUri={codeuri}", + parameter_overrides={"CodeUri": codeuri}, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -270,7 +267,6 @@ def test_sync_code_builds_successfully_without_local_dependencies( stack_name=TestSyncCodeBase.stack_name, code=True, dependency_layer=self.dependency_layer, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -300,7 +296,7 @@ def test_sync_code_builds_successfully_with_local_dependencies(self): stack_name=TestSyncCodeBase.stack_name, code=True, dependency_layer=self.dependency_layer, - parameter_overrides=f"Parameter=Clarity CodeUri={codeuri}", + parameter_overrides={"CodeUri": codeuri}, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, diff --git a/tests/integration/sync/test_sync_code.py b/tests/integration/sync/test_sync_code.py index 59e83a613e..22210a241a 100644 --- a/tests/integration/sync/test_sync_code.py +++ b/tests/integration/sync/test_sync_code.py @@ -8,6 +8,7 @@ import time import uuid from pathlib import Path +from typing import Dict from unittest import skipIf import pytest @@ -37,48 +38,43 @@ class TestSyncCodeBase(SyncIntegBase): - temp_dir = "" stack_name = "" template_path = "" template = "" folder = "" + parameter_overrides: Dict[str, str] = {} @pytest.fixture(scope="class") def execute_infra_sync(self): - with tempfile.TemporaryDirectory() as temp: - TestSyncCodeBase.temp_dir = Path(temp).joinpath(self.folder) - shutil.copytree(self.test_data_path.joinpath(self.folder).joinpath("before"), TestSyncCodeBase.temp_dir) - - TestSyncCodeBase.template_path = TestSyncCodeBase.temp_dir.joinpath(self.template) - TestSyncCodeBase.stack_name = self._method_to_stack_name(self.id()) - - # Run infra sync - sync_command_list = self.get_sync_command_list( - template_file=TestSyncCodeBase.template_path, - code=False, - watch=False, - dependency_layer=self.dependency_layer, - stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", - image_repository=self.ecr_repo_name, - s3_prefix=uuid.uuid4().hex, - kms_key_id=self.kms_key, - tags="integ=true clarity=yes foo_bar=baz", - ) + TestSyncCodeBase.template_path = self.test_data_path.joinpath(self.folder, "before", self.template) + TestSyncCodeBase.stack_name = self._method_to_stack_name(self.id()) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + # Run infra sync + sync_command_list = self.get_sync_command_list( + template_file=TestSyncCodeBase.template_path, + code=False, + watch=False, + dependency_layer=self.dependency_layer, + stack_name=TestSyncCodeBase.stack_name, + parameter_overrides=self.parameter_overrides, + image_repository=self.ecr_repo_name, + s3_prefix=uuid.uuid4().hex, + kms_key_id=self.kms_key, + tags="integ=true clarity=yes foo_bar=baz", + ) - yield sync_process_execute + sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_data_path) - shutil.rmtree(os.path.join(os.getcwd(), ".aws-sam", "build"), ignore_errors=True) - cfn_client = boto3.client("cloudformation") - ecr_client = boto3.client("ecr") - self._delete_companion_stack( - cfn_client, ecr_client, self._stack_name_to_companion_stack(TestSyncCodeBase.stack_name) - ) + yield sync_process_execute + + cfn_client = boto3.client("cloudformation") + ecr_client = boto3.client("ecr") + self._delete_companion_stack( + cfn_client, ecr_client, self._stack_name_to_companion_stack(TestSyncCodeBase.stack_name) + ) - cfn_client = boto3.client("cloudformation") - cfn_client.delete_stack(StackName=TestSyncCodeBase.stack_name) + cfn_client = boto3.client("cloudformation") + cfn_client.delete_stack(StackName=TestSyncCodeBase.stack_name) @pytest.fixture(autouse=True, scope="class") def sync_code_base(self, execute_infra_sync): @@ -100,11 +96,16 @@ class TestSyncCode(TestSyncCodeBase): template = "template-python.yaml" folder = "code" + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.parameter_overrides["HelloWorldLayerName"] = f"HelloWorldLayer-{uuid.uuid4().hex}"[:140] + def test_sync_code_function(self): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("function"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "function")) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("after").joinpath("function"), - TestSyncCodeBase.temp_dir.joinpath("function"), + self.test_data_path.joinpath(self.folder, "after", "function"), + self.test_data_path.joinpath(self.folder, "before", "function"), ) self.stack_resources = self._get_stacks(TestSyncCodeBase.stack_name) @@ -121,14 +122,13 @@ def test_sync_code_function(self): resource_list=["AWS::Serverless::Function"], dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", use_container=self.use_container, ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -146,10 +146,10 @@ def test_sync_code_function(self): self.assertIn("requests", layer_contents) def test_sync_code_layer(self): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("layer"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "layer")) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("after").joinpath("layer"), - TestSyncCodeBase.temp_dir.joinpath("layer"), + self.test_data_path.joinpath(self.folder, "after", "layer"), + self.test_data_path.joinpath(self.folder, "before", "layer"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -159,14 +159,13 @@ def test_sync_code_layer(self): resource_list=["AWS::Serverless::LayerVersion"], dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", use_container=self.use_container, ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -181,15 +180,15 @@ def test_sync_code_layer(self): @pytest.mark.flaky(reruns=3) def test_sync_function_layer_race_condition(self): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("function"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "function")) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("before").joinpath("function"), - TestSyncCodeBase.temp_dir.joinpath("function"), + self.test_data_path.joinpath(self.folder, "after", "function"), + self.test_data_path.joinpath(self.folder, "before", "function"), ) - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("layer"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "layer")) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("before").joinpath("layer"), - TestSyncCodeBase.temp_dir.joinpath("layer"), + self.test_data_path.joinpath(self.folder, "after", "layer"), + self.test_data_path.joinpath(self.folder, "before", "layer"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -199,13 +198,12 @@ def test_sync_function_layer_race_condition(self): dependency_layer=self.dependency_layer, resource_list=["AWS::Serverless::LayerVersion", "AWS::Serverless::Function"], stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -219,10 +217,10 @@ def test_sync_function_layer_race_condition(self): self.assertEqual(lambda_response.get("message"), "7") def test_sync_code_rest_api(self): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("apigateway"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "apigateway")) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("after").joinpath("apigateway"), - TestSyncCodeBase.temp_dir.joinpath("apigateway"), + self.test_data_path.joinpath(self.folder, "after", "apigateway"), + self.test_data_path.joinpath(self.folder, "before", "apigateway"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -231,13 +229,12 @@ def test_sync_code_rest_api(self): watch=False, resource_list=["AWS::Serverless::Api"], stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) time.sleep(API_SLEEP) @@ -248,10 +245,10 @@ def test_sync_code_rest_api(self): self.assertEqual(self._get_api_message(rest_api), '{"message": "hello 2"}') def test_sync_code_state_machine(self): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("statemachine"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "statemachine")) shutil.copytree( - self.test_data_path.joinpath("code").joinpath("after").joinpath("statemachine"), - TestSyncCodeBase.temp_dir.joinpath("statemachine"), + self.test_data_path.joinpath(self.folder, "after", "statemachine"), + self.test_data_path.joinpath(self.folder, "before", "statemachine"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -260,13 +257,12 @@ def test_sync_code_state_machine(self): watch=False, resource_list=["AWS::Serverless::StateMachine"], stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -284,10 +280,10 @@ class TestSyncCodeDotnetFunctionTemplate(TestSyncCodeBase): folder = "code" def test_sync_code_shared_codeuri(self): - shutil.rmtree(Path(TestSyncCodeBase.temp_dir).joinpath("dotnet_function"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "dotnet_function")) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("after").joinpath("dotnet_function"), - Path(TestSyncCodeBase.temp_dir).joinpath("dotnet_function"), + self.test_data_path.joinpath(self.folder, "after", "dotnet_function"), + self.test_data_path.joinpath(self.folder, "before", "dotnet_function"), ) # Run code sync @@ -298,13 +294,12 @@ def test_sync_code_shared_codeuri(self): resource_list=["AWS::Serverless::Function"], dependency_layer=True, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -325,10 +320,10 @@ class TestSyncCodeNodejsFunctionTemplate(TestSyncCodeBase): folder = "code" def test_sync_code_nodejs_function(self): - shutil.rmtree(Path(TestSyncCodeBase.temp_dir).joinpath("nodejs_function"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "nodejs_function")) shutil.copytree( - self.test_data_path.joinpath("code").joinpath("after").joinpath("nodejs_function"), - Path(TestSyncCodeBase.temp_dir).joinpath("nodejs_function"), + self.test_data_path.joinpath(self.folder, "after", "nodejs_function"), + self.test_data_path.joinpath(self.folder, "before", "nodejs_function"), ) self.stack_resources = self._get_stacks(TestSyncCodeBase.stack_name) @@ -347,13 +342,12 @@ def test_sync_code_nodejs_function(self): resource_list=["AWS::Serverless::Function"], dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -379,14 +373,19 @@ class TestSyncCodeNested(TestSyncCodeBase): template = "template.yaml" folder = "nested" + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.parameter_overrides = { + "HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140], + "ChildStackHelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140], + } + def test_sync_code_nested_function(self): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_functions"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_functions")) shutil.copytree( - self.test_data_path.joinpath(self.folder) - .joinpath("after") - .joinpath("child_stack") - .joinpath("child_functions"), - TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_functions"), + self.test_data_path.joinpath(self.folder, "after", "child_stack", "child_functions"), + self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_functions"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -396,13 +395,12 @@ def test_sync_code_nested_function(self): resource_list=["AWS::Serverless::Function"], dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -416,10 +414,10 @@ def test_sync_code_nested_function(self): self.assertEqual(lambda_response.get("message"), "11") def test_sync_code_nested_layer(self): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("root_layer"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "root_layer")) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("after").joinpath("root_layer"), - TestSyncCodeBase.temp_dir.joinpath("root_layer"), + self.test_data_path.joinpath(self.folder, "after", "root_layer"), + self.test_data_path.joinpath(self.folder, "before", "root_layer"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -429,13 +427,12 @@ def test_sync_code_nested_layer(self): resource_list=["AWS::Serverless::LayerVersion"], dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -450,18 +447,15 @@ def test_sync_code_nested_layer(self): @pytest.mark.flaky(reruns=3) def test_sync_nested_function_layer_race_condition(self): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_functions"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_functions")) shutil.copytree( - self.test_data_path.joinpath(self.folder) - .joinpath("before") - .joinpath("child_stack") - .joinpath("child_functions"), - TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_functions"), + self.test_data_path.joinpath(self.folder, "after", "child_stack", "child_functions"), + self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_functions"), ) - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath("root_layer"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "root_layer")) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("before").joinpath("root_layer"), - TestSyncCodeBase.temp_dir.joinpath("root_layer"), + self.test_data_path.joinpath(self.folder, "after", "root_layer"), + self.test_data_path.joinpath(self.folder, "before", "root_layer"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -471,13 +465,12 @@ def test_sync_nested_function_layer_race_condition(self): dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, resource_list=["AWS::Serverless::LayerVersion", "AWS::Serverless::Function"], - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -492,16 +485,11 @@ def test_sync_nested_function_layer_race_condition(self): def test_sync_code_nested_rest_api(self): shutil.rmtree( - TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_child_stack").joinpath("apigateway"), - ignore_errors=True, + self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_child_stack", "apigateway") ) shutil.copytree( - self.test_data_path.joinpath(self.folder) - .joinpath("after") - .joinpath("child_stack") - .joinpath("child_child_stack") - .joinpath("apigateway"), - TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_child_stack").joinpath("apigateway"), + self.test_data_path.joinpath(self.folder, "after", "child_stack", "child_child_stack", "apigateway"), + self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_child_stack", "apigateway"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -511,13 +499,12 @@ def test_sync_code_nested_rest_api(self): dependency_layer=self.dependency_layer, resource_list=["AWS::Serverless::Api"], stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) time.sleep(API_SLEEP) @@ -529,16 +516,11 @@ def test_sync_code_nested_rest_api(self): def test_sync_code_nested_state_machine(self): shutil.rmtree( - TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_child_stack").joinpath("statemachine"), - ignore_errors=True, + self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_child_stack", "statemachine"), ) shutil.copytree( - self.test_data_path.joinpath(self.folder) - .joinpath("after") - .joinpath("child_stack") - .joinpath("child_child_stack") - .joinpath("statemachine"), - TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_child_stack").joinpath("statemachine"), + self.test_data_path.joinpath(self.folder, "after", "child_stack", "child_child_stack", "statemachine"), + self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_child_stack", "statemachine"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -547,13 +529,12 @@ def test_sync_code_nested_state_machine(self): watch=False, resource_list=["AWS::Serverless::StateMachine"], stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -570,18 +551,20 @@ class TestSyncCodeNestedWithIntrinsics(TestSyncCodeBase): template = "template.yaml" folder = "nested_intrinsics" + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.parameter_overrides = { + "ChildStackHelloWorldLayerName": f"ChildStackHelloWorldLayerName-{uuid.uuid4().hex}"[:140] + } + def test_sync_code_nested_getattr_layer(self): shutil.rmtree( - TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_layer").joinpath("layer"), - ignore_errors=True, + self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_layer", "layer"), ) shutil.copytree( - self.test_data_path.joinpath(self.folder) - .joinpath("after") - .joinpath("child_stack") - .joinpath("child_layer") - .joinpath("layer"), - TestSyncCodeBase.temp_dir.joinpath("child_stack").joinpath("child_layer").joinpath("layer"), + self.test_data_path.joinpath(self.folder, "after", "child_stack", "child_layer", "layer"), + self.test_data_path.joinpath(self.folder, "before", "child_stack", "child_layer", "layer"), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -591,13 +574,12 @@ def test_sync_code_nested_getattr_layer(self): resource_list=["AWS::Serverless::LayerVersion"], dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources @@ -618,10 +600,10 @@ class TestSyncCodeEsbuildFunctionTemplate(TestSyncCodeBase): dependency_layer = False def test_sync_code_esbuild_function(self): - shutil.rmtree(Path(TestSyncCodeBase.temp_dir).joinpath("esbuild_function"), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", "esbuild_function")) shutil.copytree( - self.test_data_path.joinpath("code").joinpath("after").joinpath("esbuild_function"), - Path(TestSyncCodeBase.temp_dir).joinpath("esbuild_function"), + self.test_data_path.joinpath(self.folder, "after", "esbuild_function"), + self.test_data_path.joinpath(self.folder, "before", "esbuild_function"), ) self.stack_resources = self._get_stacks(TestSyncCodeBase.stack_name) @@ -634,13 +616,12 @@ def test_sync_code_esbuild_function(self): resource_list=["AWS::Serverless::Function"], dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) @@ -668,6 +649,15 @@ class TestSyncLayerCode(TestSyncCodeBase): template = "template-python-code-only-layer.yaml" folder = "code" + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.parameter_overrides = { + "HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140], + "HelloWorldLayerWithoutBuildMethodName": f"HelloWorldLayerWithoutBuildMethod-{uuid.uuid4().hex}"[:140], + "HelloWorldPreBuiltZipLayerName": f"HelloWorldPreBuiltZipLayer-{uuid.uuid4().hex}"[:140], + } + @parameterized.expand( [ ("layer", "HelloWorldLayer", "HelloWorldFunction", "7"), @@ -681,10 +671,10 @@ class TestSyncLayerCode(TestSyncCodeBase): ] ) def test_sync_code_layer(self, layer_path, layer_logical_id, function_logical_id, expected_value): - shutil.rmtree(TestSyncCodeBase.temp_dir.joinpath(layer_path), ignore_errors=True) + shutil.rmtree(self.test_data_path.joinpath(self.folder, "before", layer_path)) shutil.copytree( - self.test_data_path.joinpath(self.folder).joinpath("after").joinpath(layer_path), - TestSyncCodeBase.temp_dir.joinpath(layer_path), + self.test_data_path.joinpath(self.folder, "after", layer_path), + self.test_data_path.joinpath(self.folder, "before", layer_path), ) # Run code sync sync_command_list = self.get_sync_command_list( @@ -694,14 +684,13 @@ def test_sync_code_layer(self, layer_path, layer_logical_id, function_logical_id resource_id_list=[layer_logical_id], dependency_layer=self.dependency_layer, stack_name=TestSyncCodeBase.stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", use_container=self.use_container, ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) # CFN Api call here to collect all the stack resources diff --git a/tests/integration/sync/test_sync_infra.py b/tests/integration/sync/test_sync_infra.py index e9cd985203..4f0b54cb74 100644 --- a/tests/integration/sync/test_sync_infra.py +++ b/tests/integration/sync/test_sync_infra.py @@ -1,11 +1,12 @@ +import json +import logging import os import platform - -import logging -import json import shutil import tempfile +import uuid from pathlib import Path +from typing import Dict from unittest import skipIf import pytest @@ -17,7 +18,6 @@ AWS_STEPFUNCTIONS_STATEMACHINE, ) from tests.integration.sync.sync_integ_base import SyncIntegBase - from tests.testing_utils import RUNNING_ON_CI, RUNNING_TEST_FOR_MASTER_ON_CI, RUN_BY_CANARY from tests.testing_utils import run_command_with_input @@ -36,15 +36,18 @@ @skipIf(SKIP_SYNC_TESTS, "Skip sync tests in CI/CD only") @parameterized_class([{"dependency_layer": True}, {"dependency_layer": False}]) class TestSyncInfra(SyncIntegBase): + parameter_overrides: Dict[str, str] = {} + def setUp(self): - self.test_dir = Path(tempfile.mkdtemp()) - shutil.rmtree(self.test_dir) - shutil.copytree(self.test_data_path, self.test_dir) super().setUp() - def tearDown(self): - shutil.rmtree(self.test_dir) - super().tearDown() + original_test_data_path = Path(__file__).resolve().parents[1].joinpath("testdata", "sync") + self.test_data_path = Path(tempfile.mkdtemp()) + # since dirs_exist_ok=True only supported after py3.7, first delete the parent folder and run copytree after + shutil.rmtree(self.test_data_path) + shutil.copytree(original_test_data_path, self.test_data_path) + + self.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} def _verify_infra_changes(self, resources): # Lambda @@ -70,7 +73,7 @@ def _verify_infra_changes(self, resources): @parameterized.expand([["ruby", False], ["python", False], ["python", True]]) def test_sync_infra(self, runtime, use_container): template_before = f"infra/template-{runtime}-before.yaml" - template_path = str(self.test_dir.joinpath(template_before)) + template_path = str(self.test_data_path.joinpath(template_before)) stack_name = self._method_to_stack_name(self.id()) self.stacks.append({"name": stack_name}) @@ -81,7 +84,7 @@ def test_sync_infra(self, runtime, use_container): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -89,7 +92,7 @@ def test_sync_infra(self, runtime, use_container): use_container=use_container, ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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) self.assertIn("Stack creation succeeded. Sync infra completed.", str(sync_process_execute.stderr)) @@ -110,7 +113,7 @@ def test_sync_infra(self, runtime, use_container): self.assertEqual(self._get_sfn_response(state_machine), '"World 1"') template_after = f"infra/template-{runtime}-after.yaml" - template_path = str(self.test_dir.joinpath(template_after)) + template_path = str(self.test_data_path.joinpath(template_after)) # Run infra sync sync_command_list = self.get_sync_command_list( @@ -119,7 +122,7 @@ def test_sync_infra(self, runtime, use_container): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -127,7 +130,7 @@ def test_sync_infra(self, runtime, use_container): use_container=use_container, ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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) self.assertIn("Stack update succeeded. Sync infra completed.", str(sync_process_execute.stderr)) self.assertNotIn("Commands you can use next", str(sync_process_execute.stderr)) @@ -148,7 +151,7 @@ def test_sync_infra(self, runtime, use_container): @parameterized.expand([["python", False], ["python", True]]) def test_sync_infra_auto_skip(self, runtime, use_container): template_before = f"infra/template-{runtime}-before.yaml" - template_path = str(self.test_dir.joinpath(template_before)) + template_path = str(self.test_data_path.joinpath(template_before)) stack_name = self._method_to_stack_name(self.id()) self.stacks.append({"name": stack_name}) @@ -159,7 +162,7 @@ def test_sync_infra_auto_skip(self, runtime, use_container): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -167,12 +170,12 @@ def test_sync_infra_auto_skip(self, runtime, use_container): use_container=use_container, ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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) self.assertIn("Stack creation succeeded. Sync infra completed.", str(sync_process_execute.stderr)) template_after = f"infra/template-{runtime}-auto-skip.yaml" - template_path = str(self.test_dir.joinpath(template_after)) + template_path = str(self.test_data_path.joinpath(template_after)) # Run infra sync sync_command_list = self.get_sync_command_list( @@ -181,7 +184,7 @@ def test_sync_infra_auto_skip(self, runtime, use_container): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -189,7 +192,7 @@ def test_sync_infra_auto_skip(self, runtime, use_container): use_container=use_container, ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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) self.assertIn( "Template haven't been changed since last deployment, skipping infra sync...", @@ -209,7 +212,7 @@ def test_sync_infra_auto_skip(self, runtime, use_container): @parameterized.expand([["python", False], ["python", True]]) def test_sync_infra_auto_skip_nested(self, runtime, use_container): template_before = str(Path("infra", "parent-stack.yaml")) - template_path = str(self.test_dir.joinpath(template_before)) + template_path = str(self.test_data_path.joinpath(template_before)) stack_name = self._method_to_stack_name(self.id()) self.stacks.append({"name": stack_name}) @@ -221,7 +224,7 @@ def test_sync_infra_auto_skip_nested(self, runtime, use_container): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -229,16 +232,16 @@ def test_sync_infra_auto_skip_nested(self, runtime, use_container): use_container=use_container, ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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) self.assertIn("Stack creation succeeded. Sync infra completed.", str(sync_process_execute.stderr)) self.update_file( - self.test_dir.joinpath("infra", f"template-{runtime}-auto-skip.yaml"), - self.test_dir.joinpath("infra", f"template-{runtime}-before.yaml"), + self.test_data_path.joinpath("infra", f"template-{runtime}-auto-skip.yaml"), + self.test_data_path.joinpath("infra", f"template-{runtime}-before.yaml"), ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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) self.assertIn( "Template haven't been changed since last deployment, skipping infra sync...", @@ -256,7 +259,7 @@ def test_sync_infra_auto_skip_nested(self, runtime, use_container): @parameterized.expand(["infra/template-python-before.yaml"]) def test_sync_infra_no_confirm(self, template_file): - template_path = str(self.test_dir.joinpath(template_file)) + template_path = str(self.test_data_path.joinpath(template_file)) stack_name = self._method_to_stack_name(self.id()) # Run infra sync @@ -266,20 +269,20 @@ def test_sync_infra_no_confirm(self, template_file): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "n\n".encode(), cwd=self.test_dir) + sync_process_execute = run_command_with_input(sync_command_list, "n\n".encode(), cwd=self.test_data_path) self.assertEqual(sync_process_execute.process.returncode, 0) self.assertNotIn("Build Succeeded", str(sync_process_execute.stderr)) @parameterized.expand(["infra/template-python-before.yaml"]) def test_sync_infra_no_stack_name(self, template_file): - template_path = str(self.test_dir.joinpath(template_file)) + template_path = str(self.test_data_path.joinpath(template_file)) # Run infra sync sync_command_list = self.get_sync_command_list( @@ -287,20 +290,20 @@ def test_sync_infra_no_stack_name(self, template_file): code=False, watch=False, dependency_layer=self.dependency_layer, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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, 2) self.assertIn("Error: Missing option '--stack-name'.", str(sync_process_execute.stderr)) @parameterized.expand(["infra/template-python-before.yaml"]) def test_sync_infra_no_capabilities(self, template_file): - template_path = str(self.test_dir.joinpath(template_file)) + template_path = str(self.test_data_path.joinpath(template_file)) stack_name = self._method_to_stack_name(self.id()) self.stacks.append({"name": stack_name}) @@ -311,7 +314,7 @@ def test_sync_infra_no_capabilities(self, template_file): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -319,7 +322,7 @@ def test_sync_infra_no_capabilities(self, template_file): tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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, 1) self.assertIn( "An error occurred (InsufficientCapabilitiesException) when calling the CreateStack operation: \ @@ -329,7 +332,7 @@ def test_sync_infra_no_capabilities(self, template_file): @parameterized.expand(["infra/template-python-before.yaml"]) def test_sync_infra_s3_bucket_option(self, template_file): - template_path = str(self.test_dir.joinpath(template_file)) + template_path = str(self.test_data_path.joinpath(template_file)) stack_name = self._method_to_stack_name(self.id()) sync_command_list = self.get_sync_command_list( @@ -338,7 +341,7 @@ def test_sync_infra_s3_bucket_option(self, template_file): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_bucket=self.bucket_name, s3_prefix=self.s3_prefix, @@ -347,7 +350,7 @@ def test_sync_infra_s3_bucket_option(self, template_file): tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode(), cwd=self.test_dir) + 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) self.assertIn("Stack creation succeeded. Sync infra completed.", str(sync_process_execute.stderr)) @@ -431,72 +434,76 @@ def test_cdk_templates(self, template_file, template_after, function_id, depende repository = "" if function_id: repository = f"{function_id}={self.ecr_repo_name}" - with tempfile.TemporaryDirectory() as temp: - temp_path = Path(temp) - shutil.copytree(str(self.test_data_path.joinpath("infra/cdk")), str(temp_path.joinpath("cdk"))) - template_path = str(temp_path.joinpath("cdk").joinpath(template_file)) - stack_name = self._method_to_stack_name(self.id()) - self.stacks.append({"name": stack_name}) - - # Run infra sync - sync_command_list = self.get_sync_command_list( - template_file=template_path, - code=False, - watch=False, - dependency_layer=dependency_layer, - stack_name=stack_name, - parameter_overrides="Parameter=Clarity", - image_repositories=repository, - s3_prefix=self.s3_prefix, - kms_key_id=self.kms_key, - tags="integ=true clarity=yes foo_bar=baz", - ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) - self.assertEqual(sync_process_execute.process.returncode, 0) - self.assertIn("Stack creation succeeded. Sync infra completed.", str(sync_process_execute.stderr)) - - # CFN Api call here to collect all the stack resources - self.stack_resources = self._get_stacks(stack_name) - # Lambda Api call here, which tests both the python function and the layer - lambda_functions = self.stack_resources.get(AWS_LAMBDA_FUNCTION) - for lambda_function in lambda_functions: - lambda_response = json.loads(self._get_lambda_response(lambda_function)) - self.assertIn("extra_message", lambda_response) - self.assertEqual(lambda_response.get("message"), "7") - - template_path = str(temp_path.joinpath("cdk").joinpath(template_after)) - - # Run infra sync - sync_command_list = self.get_sync_command_list( - template_file=template_path, - code=False, - watch=False, - dependency_layer=dependency_layer, - stack_name=stack_name, - parameter_overrides="Parameter=Clarity", - image_repositories=repository, - s3_prefix=self.s3_prefix, - kms_key_id=self.kms_key, - tags="integ=true clarity=yes foo_bar=baz", - ) - - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) - self.assertEqual(sync_process_execute.process.returncode, 0) - self.assertIn("Stack update succeeded. Sync infra completed.", str(sync_process_execute.stderr)) - - # CFN Api call here to collect all the stack resources - self.stack_resources = self._get_stacks(stack_name) - # Lambda Api call here, which tests both the python function and the layer - lambda_functions = self.stack_resources.get(AWS_LAMBDA_FUNCTION) - for lambda_function in lambda_functions: - lambda_response = json.loads(self._get_lambda_response(lambda_function)) - self.assertIn("extra_message", lambda_response) - self.assertEqual(lambda_response.get("message"), "9") + template_path = str(self.test_data_path.joinpath("infra/cdk").joinpath(template_file)) + stack_name = self._method_to_stack_name(self.id()) + self.stacks.append({"name": stack_name}) + + # Run infra sync + sync_command_list = self.get_sync_command_list( + template_file=template_path, + code=False, + watch=False, + dependency_layer=dependency_layer, + stack_name=stack_name, + image_repositories=repository, + s3_prefix=self.s3_prefix, + kms_key_id=self.kms_key, + tags="integ=true clarity=yes foo_bar=baz", + ) + 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) + self.assertIn("Stack creation succeeded. Sync infra completed.", str(sync_process_execute.stderr)) + + # CFN Api call here to collect all the stack resources + self.stack_resources = self._get_stacks(stack_name) + # Lambda Api call here, which tests both the python function and the layer + lambda_functions = self.stack_resources.get(AWS_LAMBDA_FUNCTION) + for lambda_function in lambda_functions: + lambda_response = json.loads(self._get_lambda_response(lambda_function)) + self.assertIn("extra_message", lambda_response) + self.assertEqual(lambda_response.get("message"), "7") + + template_path = str(self.test_data_path.joinpath("infra/cdk").joinpath(template_after)) + + # Run infra sync + sync_command_list = self.get_sync_command_list( + template_file=template_path, + code=False, + watch=False, + dependency_layer=dependency_layer, + stack_name=stack_name, + image_repositories=repository, + s3_prefix=self.s3_prefix, + kms_key_id=self.kms_key, + tags="integ=true clarity=yes foo_bar=baz", + ) + + 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) + self.assertIn("Stack update succeeded. Sync infra completed.", str(sync_process_execute.stderr)) + + # CFN Api call here to collect all the stack resources + self.stack_resources = self._get_stacks(stack_name) + # Lambda Api call here, which tests both the python function and the layer + lambda_functions = self.stack_resources.get(AWS_LAMBDA_FUNCTION) + for lambda_function in lambda_functions: + lambda_response = json.loads(self._get_lambda_response(lambda_function)) + self.assertIn("extra_message", lambda_response) + self.assertEqual(lambda_response.get("message"), "9") @skipIf(SKIP_SYNC_TESTS, "Skip sync tests in CI/CD only") @parameterized_class([{"dependency_layer": True}, {"dependency_layer": False}]) class TestSyncInfraWithJava(SyncIntegBase): + ecr_repo_name = None + kms_key = None + parameter_overrides: Dict[str, str] = {} + + @classmethod + def setUpClass(cls) -> None: + super().setUpClass() + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} + @parameterized.expand(["infra/template-java.yaml"]) def test_sync_infra_with_java(self, template_file): """This will test a case where user will flip ADL flag between sync sessions""" @@ -518,14 +525,14 @@ def _run_sync_and_validate_lambda_call(self, dependency_layer: bool, template_pa watch=False, dependency_layer=dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, capabilities_list=["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"], tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) self.assertIn("Sync infra completed.", str(sync_process_execute.stderr)) @@ -555,14 +562,13 @@ def test_sync_infra_esbuild(self, template_file): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, capabilities_list=["CAPABILITY_IAM", "CAPABILITY_AUTO_EXPAND"], tags="integ=true clarity=yes foo_bar=baz", ) - sync_process_execute = run_command_with_input(sync_command_list, "y\n".encode()) + 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) self.assertIn("Sync infra completed.", str(sync_process_execute.stderr)) diff --git a/tests/integration/sync/test_sync_watch.py b/tests/integration/sync/test_sync_watch.py index e64f084465..4967d78e9d 100644 --- a/tests/integration/sync/test_sync_watch.py +++ b/tests/integration/sync/test_sync_watch.py @@ -1,12 +1,13 @@ import os import shutil +import tempfile import time import uuid import logging import json -import tempfile from pathlib import Path +from typing import Dict from unittest import skipIf import pytest @@ -21,7 +22,6 @@ AWS_STEPFUNCTIONS_STATEMACHINE, ) from tests.integration.sync.sync_integ_base import SyncIntegBase -from tests.integration.package.package_integ_base import PackageIntegBase from tests.integration.sync.test_sync_code import API_SLEEP, SFN_SLEEP from tests.testing_utils import ( @@ -53,32 +53,17 @@ @skipIf(SKIP_SYNC_TESTS, "Skip sync tests in CI/CD only") class TestSyncWatchBase(SyncIntegBase): - @classmethod - def setUpClass(cls): - PackageIntegBase.setUpClass() - cls.test_data_path = Path(__file__).resolve().parents[1].joinpath("testdata", "sync") + template_before = "" + parameter_overrides: Dict[str, str] = {} def setUp(self): - self.cfn_client = boto3.client("cloudformation") - self.ecr_client = boto3.client("ecr") - self.lambda_client = boto3.client("lambda") - self.api_client = boto3.client("apigateway") - self.sfn_client = boto3.client("stepfunctions") - self.stacks = [] self.s3_prefix = uuid.uuid4().hex - self.test_dir = Path(tempfile.mkdtemp()) - self.template_before = "" if not self.template_before else self.template_before self.stack_name = self._method_to_stack_name(self.id()) - # Remove temp dir so that shutil.copytree will not throw an error - # Needed for python 3.7 as these versions don't have dirs_exist_ok - shutil.rmtree(self.test_dir) - shutil.copytree(self.test_data_path, self.test_dir) super().setUp() self._setup_verify_infra() def tearDown(self): kill_process(self.watch_process) - shutil.rmtree(self.test_dir) for stack in self.stacks: # because of the termination protection, do not delete aws-sam-cli-managed-default stack stack_name = stack["name"] @@ -106,7 +91,7 @@ def run_initial_infra_validation(self) -> None: self.assertEqual(self._get_sfn_response(state_machine), '"World 1"') def _setup_verify_infra(self): - template_path = self.test_dir.joinpath(self.template_before) + template_path = self.test_data_path.joinpath(self.template_before) self.stacks.append({"name": self.stack_name}) # Start watch @@ -116,13 +101,13 @@ def _setup_verify_infra(self): watch=True, dependency_layer=self.dependency_layer, stack_name=self.stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - self.watch_process = start_persistent_process(sync_command_list, cwd=self.test_dir) + self.watch_process = start_persistent_process(sync_command_list, cwd=self.test_data_path) read_until_string(self.watch_process, "Enter Y to proceed with the command, or enter N to cancel:\n") self.watch_process.stdin.write("y\n") @@ -150,16 +135,8 @@ def _verify_infra_changes(self, resources): @skipIf(SKIP_SYNC_TESTS, "Skip sync tests in CI/CD only") class TestSyncWatchEsbuildBase(TestSyncWatchBase): - @classmethod - def setUpClass(cls): - PackageIntegBase.setUpClass() - cls.test_data_path = Path(__file__).resolve().parents[1].joinpath("testdata", "sync") - - def setUp(self): - super().setUp() - def _setup_verify_infra(self): - template_path = self.test_dir.joinpath(self.template_before) + template_path = self.test_data_path.joinpath(self.template_before) self.stacks.append({"name": self.stack_name}) # Start watch @@ -169,13 +146,13 @@ def _setup_verify_infra(self): watch=True, dependency_layer=self.dependency_layer, stack_name=self.stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - self.watch_process = start_persistent_process(sync_command_list, cwd=self.test_dir) + self.watch_process = start_persistent_process(sync_command_list, cwd=self.test_data_path) read_until_string(self.watch_process, "Enter Y to proceed with the command, or enter N to cancel:\n") self.watch_process.stdin.write("y\n") @@ -196,12 +173,13 @@ class TestSyncWatchInfra(TestSyncWatchBase): @classmethod def setUpClass(cls): cls.template_before = f"infra/template-{cls.runtime}-before.yaml" + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} super(TestSyncWatchInfra, cls).setUpClass() def test_sync_watch_infra(self): self.update_file( - self.test_dir.joinpath(f"infra/template-{self.runtime}-after.yaml"), - self.test_dir.joinpath(f"infra/template-{self.runtime}-before.yaml"), + self.test_data_path.joinpath(f"infra/template-{self.runtime}-after.yaml"), + self.test_data_path.joinpath(f"infra/template-{self.runtime}-before.yaml"), ) read_until_string(self.watch_process, "\x1b[32mInfra sync completed.\x1b[0m\n", timeout=600) @@ -215,6 +193,11 @@ def test_sync_watch_infra(self): class TestSyncWatchCode(TestSyncWatchBase): template_before = str(Path("code", "before", "template-python.yaml")) + @classmethod + def setUpClass(cls): + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} + super().setUpClass() + def test_sync_watch_code(self): self.stack_resources = self._get_stacks(self.stack_name) @@ -223,8 +206,8 @@ def test_sync_watch_code(self): layer_contents = self.get_dependency_layer_contents_from_arn(self.stack_resources, "python", 1) self.assertNotIn("requests", layer_contents) self.update_file( - self.test_dir.joinpath("code", "after", "function", "requirements.txt"), - self.test_dir.joinpath("code", "before", "function", "requirements.txt"), + self.test_data_path.joinpath("code", "after", "function", "requirements.txt"), + self.test_data_path.joinpath("code", "before", "function", "requirements.txt"), ) read_until_string( self.watch_process, @@ -236,8 +219,8 @@ def test_sync_watch_code(self): # Test Lambda Function self.update_file( - self.test_dir.joinpath("code", "after", "function", "app.py"), - self.test_dir.joinpath("code", "before", "function", "app.py"), + self.test_data_path.joinpath("code", "after", "function", "app.py"), + self.test_data_path.joinpath("code", "before", "function", "app.py"), ) read_until_string( self.watch_process, "\x1b[32mFinished syncing Lambda Function HelloWorldFunction.\x1b[0m\n", timeout=30 @@ -250,8 +233,8 @@ def test_sync_watch_code(self): # Test Lambda Layer self.update_file( - self.test_dir.joinpath("code", "after", "layer", "layer_method.py"), - self.test_dir.joinpath("code", "before", "layer", "layer_method.py"), + self.test_data_path.joinpath("code", "after", "layer", "layer_method.py"), + self.test_data_path.joinpath("code", "before", "layer", "layer_method.py"), ) read_until_string( self.watch_process, @@ -266,8 +249,8 @@ def test_sync_watch_code(self): # Test APIGW self.update_file( - self.test_dir.joinpath("code", "after", "apigateway", "definition.json"), - self.test_dir.joinpath("code", "before", "apigateway", "definition.json"), + self.test_data_path.joinpath("code", "after", "apigateway", "definition.json"), + self.test_data_path.joinpath("code", "before", "apigateway", "definition.json"), ) read_until_string(self.watch_process, "\x1b[32mFinished syncing RestApi HelloWorldApi.\x1b[0m\n", timeout=20) time.sleep(API_SLEEP) @@ -276,8 +259,8 @@ def test_sync_watch_code(self): # Test SFN self.update_file( - self.test_dir.joinpath("code", "after", "statemachine", "function.asl.json"), - self.test_dir.joinpath("code", "before", "statemachine", "function.asl.json"), + self.test_data_path.joinpath("code", "after", "statemachine", "function.asl.json"), + self.test_data_path.joinpath("code", "before", "statemachine", "function.asl.json"), ) read_until_string( self.watch_process, "\x1b[32mFinished syncing StepFunctions HelloStepFunction.\x1b[0m\n", timeout=20 @@ -291,10 +274,15 @@ def test_sync_watch_code(self): class TestSyncInfraNestedStacks(TestSyncWatchBase): template_before = str(Path("infra", "parent-stack.yaml")) + @classmethod + def setUpClass(cls): + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} + super().setUpClass() + def test_sync_watch_infra_nested_stack(self): self.update_file( - self.test_dir.joinpath("infra", "template-python-after.yaml"), - self.test_dir.joinpath("infra", "template-python-before.yaml"), + self.test_data_path.joinpath("infra", "template-python-after.yaml"), + self.test_data_path.joinpath("infra", "template-python-before.yaml"), ) read_until_string(self.watch_process, "\x1b[32mInfra sync completed.\x1b[0m\n", timeout=600) @@ -308,6 +296,11 @@ def test_sync_watch_infra_nested_stack(self): class TestSyncCodeWatchNestedStacks(TestSyncWatchBase): template_before = str(Path("code", "before", "parent-stack.yaml")) + @classmethod + def setUpClass(cls): + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} + super().setUpClass() + def test_sync_watch_code_nested_stack(self): self.stack_resources = self._get_stacks(self.stack_name) @@ -316,8 +309,8 @@ def test_sync_watch_code_nested_stack(self): layer_contents = self.get_dependency_layer_contents_from_arn(self.stack_resources, "python", 1) self.assertNotIn("requests", layer_contents) self.update_file( - self.test_dir.joinpath("code", "after", "function", "requirements.txt"), - self.test_dir.joinpath("code", "before", "function", "requirements.txt"), + self.test_data_path.joinpath("code", "after", "function", "requirements.txt"), + self.test_data_path.joinpath("code", "before", "function", "requirements.txt"), ) read_until_string( self.watch_process, @@ -330,8 +323,8 @@ def test_sync_watch_code_nested_stack(self): # Test Lambda Function self.update_file( - self.test_dir.joinpath("code", "after", "function", "app.py"), - self.test_dir.joinpath("code", "before", "function", "app.py"), + self.test_data_path.joinpath("code", "after", "function", "app.py"), + self.test_data_path.joinpath("code", "before", "function", "app.py"), ) read_until_string( self.watch_process, @@ -346,8 +339,8 @@ def test_sync_watch_code_nested_stack(self): # Test Lambda Layer self.update_file( - self.test_dir.joinpath("code", "after", "layer", "layer_method.py"), - self.test_dir.joinpath("code", "before", "layer", "layer_method.py"), + self.test_data_path.joinpath("code", "after", "layer", "layer_method.py"), + self.test_data_path.joinpath("code", "before", "layer", "layer_method.py"), ) read_until_string( self.watch_process, @@ -362,8 +355,8 @@ def test_sync_watch_code_nested_stack(self): # Test APIGW self.update_file( - self.test_dir.joinpath("code", "after", "apigateway", "definition.json"), - self.test_dir.joinpath("code", "before", "apigateway", "definition.json"), + self.test_data_path.joinpath("code", "after", "apigateway", "definition.json"), + self.test_data_path.joinpath("code", "before", "apigateway", "definition.json"), ) read_until_string( self.watch_process, @@ -376,8 +369,8 @@ def test_sync_watch_code_nested_stack(self): # Test SFN self.update_file( - self.test_dir.joinpath("code", "after", "statemachine", "function.asl.json"), - self.test_dir.joinpath("code", "before", "statemachine", "function.asl.json"), + self.test_data_path.joinpath("code", "after", "statemachine", "function.asl.json"), + self.test_data_path.joinpath("code", "before", "statemachine", "function.asl.json"), ) read_until_string( self.watch_process, @@ -404,8 +397,8 @@ def test_sync_watch_code(self): self.assertEqual(lambda_response.get("message"), "hello world") self.update_file( - self.test_dir.joinpath("code", "after", "esbuild_function", "app.ts"), - self.test_dir.joinpath("code", "before", "esbuild_function", "app.ts"), + self.test_data_path.joinpath("code", "after", "esbuild_function", "app.ts"), + self.test_data_path.joinpath("code", "before", "esbuild_function", "app.ts"), ) read_until_string( self.watch_process, "\x1b[32mFinished syncing Lambda Function HelloWorldFunction.\x1b[0m\n", timeout=30 @@ -418,11 +411,8 @@ def test_sync_watch_code(self): class TestSyncWatchUseContainer(TestSyncWatchBase): - @classmethod - def setUpClass(cls): - super(TestSyncWatchBase, cls).setUpClass() - cls.use_container = True - cls.dependency_layer = False + use_container = True + dependency_layer = False def _verify_infra_changes(self, resources): # Lambda @@ -436,10 +426,15 @@ def _verify_infra_changes(self, resources): class TestSyncWatchInfraUseContainer(TestSyncWatchUseContainer): template_before = f"infra/template-python-before.yaml" + @classmethod + def setUpClass(cls): + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} + super().setUpClass() + def test_sync_watch_infra(self): self.update_file( - self.test_dir.joinpath(f"infra/template-python-after.yaml"), - self.test_dir.joinpath(f"infra/template-python-before.yaml"), + self.test_data_path.joinpath(f"infra/template-python-after.yaml"), + self.test_data_path.joinpath(f"infra/template-python-before.yaml"), ) read_until_string(self.watch_process, "\x1b[32mInfra sync completed.\x1b[0m\n", timeout=600) @@ -452,13 +447,18 @@ def test_sync_watch_infra(self): class TestSyncWatchCodeUseContainer(TestSyncWatchUseContainer): template_before = str(Path("code", "before", "template-python.yaml")) + @classmethod + def setUpClass(cls): + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} + super().setUpClass() + def test_sync_watch_code(self): self.stack_resources = self._get_stacks(self.stack_name) # Test Lambda Function self.update_file( - self.test_dir.joinpath("code", "after", "function", "requirements.txt"), - self.test_dir.joinpath("code", "before", "function", "requirements.txt"), + self.test_data_path.joinpath("code", "after", "function", "requirements.txt"), + self.test_data_path.joinpath("code", "before", "function", "requirements.txt"), ) read_until_string( self.watch_process, "\x1b[32mFinished syncing Lambda Function HelloWorldFunction.\x1b[0m\n", timeout=45 @@ -477,6 +477,11 @@ def test_sync_watch_code(self): class TestSyncWatchCodeOnly(TestSyncWatchBase): template_before = str(Path("code", "before", "template-python-code-only.yaml")) + @classmethod + def setUpClass(cls): + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} + super().setUpClass() + def run_initial_infra_validation(self) -> None: """Runs initial infra validation after deployment is completed""" self.stack_resources = self._get_stacks(self.stack_name) @@ -490,20 +495,20 @@ def test_sync_watch_code(self): # first kill previously started sync process kill_process(self.watch_process) # start new one with code only - template_path = self.test_dir.joinpath(self.template_before) + template_path = self.test_data_path.joinpath(self.template_before) sync_command_list = self.get_sync_command_list( template_file=str(template_path), code=True, watch=True, dependency_layer=self.dependency_layer, stack_name=self.stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, tags="integ=true clarity=yes foo_bar=baz", ) - self.watch_process = start_persistent_process(sync_command_list, cwd=self.test_dir) + self.watch_process = start_persistent_process(sync_command_list, cwd=self.test_data_path) read_until_string(self.watch_process, "\x1b[32mSync watch started.\x1b[0m\n", timeout=30) self.stack_resources = self._get_stacks(self.stack_name) @@ -513,8 +518,8 @@ def test_sync_watch_code(self): layer_contents = self.get_dependency_layer_contents_from_arn(self.stack_resources, "python", 1) self.assertNotIn("requests", layer_contents) self.update_file( - self.test_dir.joinpath("code", "after", "function", "requirements.txt"), - self.test_dir.joinpath("code", "before", "function", "requirements.txt"), + self.test_data_path.joinpath("code", "after", "function", "requirements.txt"), + self.test_data_path.joinpath("code", "before", "function", "requirements.txt"), ) read_until_string( self.watch_process, @@ -526,8 +531,8 @@ def test_sync_watch_code(self): # Test Lambda Function self.update_file( - self.test_dir.joinpath("code", "after", "function", "app.py"), - self.test_dir.joinpath("code", "before", "function", "app.py"), + self.test_data_path.joinpath("code", "after", "function", "app.py"), + self.test_data_path.joinpath("code", "before", "function", "app.py"), ) read_until_string( self.watch_process, "\x1b[32mFinished syncing Lambda Function HelloWorldFunction.\x1b[0m\n", timeout=30 @@ -540,8 +545,8 @@ def test_sync_watch_code(self): # Test Lambda Layer self.update_file( - self.test_dir.joinpath("code", "after", "layer", "layer_method.py"), - self.test_dir.joinpath("code", "before", "layer", "layer_method.py"), + self.test_data_path.joinpath("code", "after", "layer", "layer_method.py"), + self.test_data_path.joinpath("code", "before", "layer", "layer_method.py"), ) read_until_string( self.watch_process, @@ -556,8 +561,8 @@ def test_sync_watch_code(self): # updating infra should not trigger an infra sync self.update_file( - self.test_dir.joinpath(f"infra/template-{self.runtime}-after.yaml"), - self.test_dir.joinpath(f"code/before/template-{self.runtime}-code-only.yaml"), + self.test_data_path.joinpath(f"infra/template-{self.runtime}-after.yaml"), + self.test_data_path.joinpath(f"code/before/template-{self.runtime}-code-only.yaml"), ) read_until_string( @@ -573,6 +578,11 @@ def test_sync_watch_code(self): [{"runtime": "python", "dependency_layer": True}, {"runtime": "python", "dependency_layer": False}] ) class TestSyncWatchAutoSkipInfra(SyncIntegBase): + @classmethod + def setUpClass(cls): + cls.parameter_overrides = {"HelloWorldLayerName": f"HelloWorldLayer-{uuid.uuid4().hex}"[:140]} + super().setUpClass() + def setUp(self): self.runtime = "python" self.dependency_layer = True @@ -600,7 +610,7 @@ def test_sync_watch_auto_skip_infra(self): watch=False, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -619,7 +629,7 @@ def test_sync_watch_auto_skip_infra(self): watch=True, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -648,7 +658,7 @@ def test_sync_watch_auto_skip_infra(self): watch=True, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -682,7 +692,7 @@ def test_sync_watch_auto_skip_infra(self): watch=True, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -717,7 +727,7 @@ def test_sync_watch_auto_skip_infra(self): watch=True, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, @@ -749,7 +759,7 @@ def test_sync_watch_auto_skip_infra(self): watch=True, dependency_layer=self.dependency_layer, stack_name=stack_name, - parameter_overrides="Parameter=Clarity", + parameter_overrides=self.parameter_overrides, image_repository=self.ecr_repo_name, s3_prefix=self.s3_prefix, kms_key_id=self.kms_key, diff --git a/tests/integration/testdata/sync/code/before/parent-stack.yaml b/tests/integration/testdata/sync/code/before/parent-stack.yaml index 6fbf38d9d3..4b37cc1855 100644 --- a/tests/integration/testdata/sync/code/before/parent-stack.yaml +++ b/tests/integration/testdata/sync/code/before/parent-stack.yaml @@ -5,8 +5,15 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: LocalNestedChildStack: Type: AWS::Serverless::Application Properties: Location: ./template-python.yaml + Parameters: + HelloWorldLayerName: !Ref HelloWorldLayerName diff --git a/tests/integration/testdata/sync/code/before/template-python-code-only-layer.yaml b/tests/integration/testdata/sync/code/before/template-python-code-only-layer.yaml index b30a528e4e..133dc915de 100644 --- a/tests/integration/testdata/sync/code/before/template-python-code-only-layer.yaml +++ b/tests/integration/testdata/sync/code/before/template-python-code-only-layer.yaml @@ -5,6 +5,17 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + HelloWorldLayerWithoutBuildMethodName: + Type: String + Description: Name of the HelloWorldLayerWithoutBuildMethodName which will be used with current deployment + HelloWorldPreBuiltZipLayerName: + Type: String + Description: Name of the HelloWorldPreBuiltZipLayer which will be used with current deployment + Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -39,7 +50,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: - LayerName: HelloWorldLayer + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer ContentUri: layer/ CompatibleRuntimes: @@ -50,7 +61,7 @@ Resources: HelloWorldLayerWithoutBuildMethod: Type: AWS::Serverless::LayerVersion Properties: - LayerName: HelloWorldLayerWithoutBuild + LayerName: !Ref HelloWorldLayerWithoutBuildMethodName Description: Hello World Layer without BuildMethod ContentUri: layer_without_build_method/ CompatibleRuntimes: @@ -59,7 +70,7 @@ Resources: HelloWorldPreBuiltZipLayer: Type: AWS::Serverless::LayerVersion Properties: - LayerName: HelloWorldPreBuiltZipLayer + LayerName: !Ref HelloWorldPreBuiltZipLayerName Description: Hello World Layer which is pre-built as ZIP file ContentUri: layer_zip/layer.zip CompatibleRuntimes: diff --git a/tests/integration/testdata/sync/code/before/template-python-code-only.yaml b/tests/integration/testdata/sync/code/before/template-python-code-only.yaml index f788e2e386..a90129ba1e 100644 --- a/tests/integration/testdata/sync/code/before/template-python-code-only.yaml +++ b/tests/integration/testdata/sync/code/before/template-python-code-only.yaml @@ -5,6 +5,11 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -19,7 +24,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: - LayerName: HelloWorldLayer + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer ContentUri: layer/ CompatibleRuntimes: diff --git a/tests/integration/testdata/sync/code/before/template-python.yaml b/tests/integration/testdata/sync/code/before/template-python.yaml index 07f5e8284a..56cb642235 100644 --- a/tests/integration/testdata/sync/code/before/template-python.yaml +++ b/tests/integration/testdata/sync/code/before/template-python.yaml @@ -5,6 +5,11 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -31,7 +36,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: - LayerName: HelloWorldLayer + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer ContentUri: layer/ CompatibleRuntimes: diff --git a/tests/integration/testdata/sync/infra/parent-stack.yaml b/tests/integration/testdata/sync/infra/parent-stack.yaml index 6657d488c9..f7a9e68fa9 100644 --- a/tests/integration/testdata/sync/infra/parent-stack.yaml +++ b/tests/integration/testdata/sync/infra/parent-stack.yaml @@ -5,8 +5,15 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: LocalNestedChildStack: Type: AWS::Serverless::Application Properties: - Location: ./template-python-before.yaml \ No newline at end of file + Location: ./template-python-before.yaml + Parameters: + HelloWorldLayerName: !Ref HelloWorldLayerName \ No newline at end of file diff --git a/tests/integration/testdata/sync/infra/template-java.yaml b/tests/integration/testdata/sync/infra/template-java.yaml index 5e93e2368c..a7b75ff016 100644 --- a/tests/integration/testdata/sync/infra/template-java.yaml +++ b/tests/integration/testdata/sync/infra/template-java.yaml @@ -5,6 +5,11 @@ Globals: Function: Timeout: 30 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -19,6 +24,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName ContentUri: before/Java/HelloWorldLayer CompatibleRuntimes: - java8 diff --git a/tests/integration/testdata/sync/infra/template-python-after.yaml b/tests/integration/testdata/sync/infra/template-python-after.yaml index 2e94292fc3..add839c507 100644 --- a/tests/integration/testdata/sync/infra/template-python-after.yaml +++ b/tests/integration/testdata/sync/infra/template-python-after.yaml @@ -5,6 +5,11 @@ Globals: Function: Timeout: 30 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -31,6 +36,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer ContentUri: after/Python/layer/ CompatibleRuntimes: diff --git a/tests/integration/testdata/sync/infra/template-python-auto-skip.yaml b/tests/integration/testdata/sync/infra/template-python-auto-skip.yaml index 5275a0bf6f..bfe4ca5425 100644 --- a/tests/integration/testdata/sync/infra/template-python-auto-skip.yaml +++ b/tests/integration/testdata/sync/infra/template-python-auto-skip.yaml @@ -5,6 +5,11 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -31,6 +36,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer ContentUri: after/Python/layer/ CompatibleRuntimes: @@ -44,4 +50,4 @@ Resources: DefinitionUri: after/Python/statemachine/function.asl.json Policies: - LambdaInvokePolicy: - FunctionName: !Ref HelloWorldFunction \ No newline at end of file + FunctionName: !Ref HelloWorldFunction diff --git a/tests/integration/testdata/sync/infra/template-python-before.yaml b/tests/integration/testdata/sync/infra/template-python-before.yaml index 407f643797..2ed2834830 100644 --- a/tests/integration/testdata/sync/infra/template-python-before.yaml +++ b/tests/integration/testdata/sync/infra/template-python-before.yaml @@ -5,6 +5,11 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -31,6 +36,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer ContentUri: before/Python/layer/ CompatibleRuntimes: diff --git a/tests/integration/testdata/sync/infra/template-ruby-after.yaml b/tests/integration/testdata/sync/infra/template-ruby-after.yaml index f861ce593d..a83fc29aa8 100644 --- a/tests/integration/testdata/sync/infra/template-ruby-after.yaml +++ b/tests/integration/testdata/sync/infra/template-ruby-after.yaml @@ -5,6 +5,11 @@ Globals: Function: Timeout: 30 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldRubyFunction: Type: AWS::Serverless::Function @@ -21,6 +26,7 @@ Resources: HelloWorldRubyLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName Description: Hello World Ruby Layer ContentUri: after/Ruby/layer/ CompatibleRuntimes: diff --git a/tests/integration/testdata/sync/infra/template-ruby-before.yaml b/tests/integration/testdata/sync/infra/template-ruby-before.yaml index c57469007e..9c58efdb11 100644 --- a/tests/integration/testdata/sync/infra/template-ruby-before.yaml +++ b/tests/integration/testdata/sync/infra/template-ruby-before.yaml @@ -5,6 +5,11 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldRubyFunction: Type: AWS::Serverless::Function @@ -21,6 +26,7 @@ Resources: HelloWorldRubyLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName Description: Hello World Ruby Layer ContentUri: before/Ruby/layer/ CompatibleRuntimes: diff --git a/tests/integration/testdata/sync/nested/before/child_stack/template.yaml b/tests/integration/testdata/sync/nested/before/child_stack/template.yaml index 91caa7142a..c90b60f94a 100644 --- a/tests/integration/testdata/sync/nested/before/child_stack/template.yaml +++ b/tests/integration/testdata/sync/nested/before/child_stack/template.yaml @@ -9,6 +9,9 @@ Parameters: ParentLayer: Description: The name of the layer Type: String + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment Resources: @@ -29,6 +32,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer # Currently if a base_dir option is provided, the nested stack code URIs # Needs to be relative to the base_dir instead of the child templates diff --git a/tests/integration/testdata/sync/nested/before/template.yaml b/tests/integration/testdata/sync/nested/before/template.yaml index 7afeb10382..ebe0cafc9e 100644 --- a/tests/integration/testdata/sync/nested/before/template.yaml +++ b/tests/integration/testdata/sync/nested/before/template.yaml @@ -5,6 +5,14 @@ Globals: Function: Timeout: 10 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + ChildStackHelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer in ChildStack which will be used with current deployment + Resources: HelloWorldFunction: Type: AWS::Serverless::Function @@ -19,6 +27,7 @@ Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer ContentUri: root_layer/ CompatibleRuntimes: @@ -32,3 +41,4 @@ Resources: Location: child_stack/template.yaml Parameters: ParentLayer: !Ref HelloWorldLayer + HelloWorldLayerName: !Ref ChildStackHelloWorldLayerName diff --git a/tests/integration/testdata/sync/nested_intrinsics/before/child_stack/child_layer/template.yaml b/tests/integration/testdata/sync/nested_intrinsics/before/child_stack/child_layer/template.yaml index 6c2089f68d..2313c0bf7d 100644 --- a/tests/integration/testdata/sync/nested_intrinsics/before/child_stack/child_layer/template.yaml +++ b/tests/integration/testdata/sync/nested_intrinsics/before/child_stack/child_layer/template.yaml @@ -1,10 +1,16 @@ AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 +Parameters: + HelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer which will be used with current deployment + Resources: HelloWorldLayer: Type: AWS::Serverless::LayerVersion Properties: + LayerName: !Ref HelloWorldLayerName Description: Hello World Layer ContentUri: layer/ CompatibleRuntimes: diff --git a/tests/integration/testdata/sync/nested_intrinsics/before/child_stack/template.yaml b/tests/integration/testdata/sync/nested_intrinsics/before/child_stack/template.yaml index 7730ace81a..10addb045a 100644 --- a/tests/integration/testdata/sync/nested_intrinsics/before/child_stack/template.yaml +++ b/tests/integration/testdata/sync/nested_intrinsics/before/child_stack/template.yaml @@ -5,12 +5,19 @@ Globals: Function: Timeout: 10 +Parameters: + ChildStackHelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer in child stack which will be used with current deployment + Resources: LayerStack: Type: AWS::Serverless::Application Properties: Location: child_layer/template.yaml + Parameters: + HelloWorldLayerName: !Ref ChildStackHelloWorldLayerName FunctionStack: Type: AWS::Serverless::Application diff --git a/tests/integration/testdata/sync/nested_intrinsics/before/template.yaml b/tests/integration/testdata/sync/nested_intrinsics/before/template.yaml index 4b2fcc98bf..dcc34e62e8 100644 --- a/tests/integration/testdata/sync/nested_intrinsics/before/template.yaml +++ b/tests/integration/testdata/sync/nested_intrinsics/before/template.yaml @@ -5,8 +5,15 @@ Globals: Function: Timeout: 10 +Parameters: + ChildStackHelloWorldLayerName: + Type: String + Description: Name of the HelloWorldLayer in child stack which will be used with current deployment + Resources: ChildStack: Type: AWS::Serverless::Application Properties: Location: child_stack/template.yaml + Parameters: + ChildStackHelloWorldLayerName: !Ref ChildStackHelloWorldLayerName diff --git a/tests/unit/commands/local/lib/test_cfn_api_provider.py b/tests/unit/commands/local/lib/test_cfn_api_provider.py index b8ac3a5e01..39458f0e8b 100644 --- a/tests/unit/commands/local/lib/test_cfn_api_provider.py +++ b/tests/unit/commands/local/lib/test_cfn_api_provider.py @@ -1201,7 +1201,7 @@ class TestCollectLambdaAuthorizersWithApiGatewayV1Resources(TestCase): } }, { - "my-auth-name": LambdaAuthorizer( + "my-auth-id": LambdaAuthorizer( payload_version="1.0", authorizer_name="my-auth-name", type=LambdaAuthorizer.TOKEN, @@ -1222,7 +1222,7 @@ class TestCollectLambdaAuthorizersWithApiGatewayV1Resources(TestCase): } }, { - "my-auth-name": LambdaAuthorizer( + "my-auth-id": LambdaAuthorizer( payload_version="1.0", authorizer_name="my-auth-name", type=LambdaAuthorizer.TOKEN, @@ -1243,7 +1243,7 @@ class TestCollectLambdaAuthorizersWithApiGatewayV1Resources(TestCase): } }, { - "my-auth-name": LambdaAuthorizer( + "my-auth-id": LambdaAuthorizer( payload_version="1.0", authorizer_name="my-auth-name", type=LambdaAuthorizer.REQUEST, @@ -1291,7 +1291,7 @@ def test_collect_v2_lambda_authorizer(self, validator_mock, get_func_name_mock): } expected_authorizers = { - "my-auth-name": LambdaAuthorizer( + "my-auth-id": LambdaAuthorizer( payload_version="2.0", authorizer_name="my-auth-name", type=LambdaAuthorizer.REQUEST, diff --git a/tests/unit/commands/local/lib/test_sam_api_provider.py b/tests/unit/commands/local/lib/test_sam_api_provider.py index 945bdb79dc..c941aa28ad 100644 --- a/tests/unit/commands/local/lib/test_sam_api_provider.py +++ b/tests/unit/commands/local/lib/test_sam_api_provider.py @@ -1996,9 +1996,9 @@ def test_extract_serverless_api_extracts_default_authorizer( def test_extract_lambda_authorizers_from_properties( self, properties, expected_authorizers, event_type, function_name_mock ): - logical_id = Mock() + logical_id = "mycoolauthorizer" - function_name_mock.return_value = Mock() + function_name_mock.return_value = logical_id collector_mock = Mock() collector_mock.add_authorizers = Mock() diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/prepare_base.py b/tests/unit/hook_packages/terraform/hooks/prepare/prepare_base.py index ee226d532c..2f53e8a485 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/prepare_base.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/prepare_base.py @@ -10,6 +10,7 @@ AWS_APIGATEWAY_RESOURCE, AWS_APIGATEWAY_RESTAPI, AWS_APIGATEWAY_STAGE, + AWS_APIGATEWAY_METHOD, ) @@ -40,6 +41,7 @@ def setUp(self) -> None: self.apigw_resource_name = "my_resource" self.apigw_stage_name = "my_stage" self.apigw_rest_api_name = "my_rest_api" + self.apigw_method_name = "my_method" self.tf_function_common_properties: dict = { "function_name": self.zip_function_name, @@ -320,6 +322,11 @@ def setUp(self) -> None: "provider_name": AWS_PROVIDER_NAME, } + self.tf_apigw_method_common_attributes: dict = { + "type": "aws_api_gateway_method", + "provider_name": AWS_PROVIDER_NAME, + } + self.tf_lambda_function_resource_common_attributes: dict = { "type": "aws_lambda_function", "provider_name": AWS_PROVIDER_NAME, @@ -514,6 +521,33 @@ def setUp(self) -> None: "Metadata": {"SamResourceId": f"aws_api_gateway_resource.{self.apigw_resource_name}"}, } + self.tf_apigw_method_properties: dict = { + "rest_api_id": "aws_api_gateway_rest_api.MyDemoAPI.id", + "resource_id": "aws_api_gateway_resource.MyDemoResource.id", + "http_method": "ANY", + "operation_name": "AnyOperation", + } + + self.expected_cfn_apigw_method_properties: dict = { + "RestApiId": "aws_api_gateway_rest_api.MyDemoAPI.id", + "ResourceId": "aws_api_gateway_resource.MyDemoResource.id", + "HttpMethod": "ANY", + "OperationName": "AnyOperation", + } + + self.tf_apigw_method_resource: dict = { + **self.tf_apigw_method_common_attributes, + "values": self.tf_apigw_method_properties, + "address": f"aws_api_gateway_method.{self.apigw_method_name}", + "name": self.apigw_method_name, + } + + self.expected_cfn_apigw_method: dict = { + "Type": AWS_APIGATEWAY_METHOD, + "Properties": self.expected_cfn_apigw_method_properties, + "Metadata": {"SamResourceId": f"aws_api_gateway_method.{self.apigw_method_name}"}, + } + self.tf_apigw_stage_properties: dict = { "rest_api_id": "aws_api_gateway_rest_api.MyDemoAPI.id", "stage_name": "test", @@ -588,6 +622,7 @@ def setUp(self) -> None: self.tf_apigw_resource_resource, self.tf_apigw_rest_api_resource, self.tf_apigw_stage_resource, + self.tf_apigw_method_resource, ] } } @@ -601,6 +636,7 @@ def setUp(self) -> None: f"AwsApiGatewayResourceMyResource{self.mock_logical_id_hash}": self.expected_cfn_apigw_resource, f"AwsApiGatewayRestApiMyRestApi{self.mock_logical_id_hash}": self.expected_cfn_apigw_rest_api, f"AwsApiGatewayStageMyStage{self.mock_logical_id_hash}": self.expected_cfn_apigw_stage_resource, + f"AwsApiGatewayMethodMyMethod{self.mock_logical_id_hash}": self.expected_cfn_apigw_method, }, } 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 94f374b3ef..fbdf15e4d4 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py @@ -10,6 +10,7 @@ AWS_API_GATEWAY_REST_API_PROPERTY_BUILDER_MAPPING, AWS_API_GATEWAY_STAGE_PROPERTY_BUILDER_MAPPING, TF_AWS_API_GATEWAY_REST_API, + AWS_API_GATEWAY_METHOD_PROPERTY_BUILDER_MAPPING, ) from samcli.hook_packages.terraform.hooks.prepare.types import ( SamMetadataResource, @@ -1068,3 +1069,9 @@ def test_translating_apigw_rest_api(self): self.tf_apigw_rest_api_properties, AWS_API_GATEWAY_REST_API_PROPERTY_BUILDER_MAPPING, Mock() ) self.assertEqual(translated_cfn_properties, self.expected_cfn_apigw_rest_api_properties) + + def test_translating_apigw_rest_method(self): + translated_cfn_properties = _translate_properties( + self.tf_apigw_method_properties, AWS_API_GATEWAY_METHOD_PROPERTY_BUILDER_MAPPING, Mock() + ) + self.assertEqual(translated_cfn_properties, self.expected_cfn_apigw_method_properties) diff --git a/tests/unit/lib/package/test_utils.py b/tests/unit/lib/package/test_utils.py index 6fe6107f76..073802f531 100644 --- a/tests/unit/lib/package/test_utils.py +++ b/tests/unit/lib/package/test_utils.py @@ -1,8 +1,10 @@ +import tempfile from unittest import TestCase from parameterized import parameterized from samcli.lib.package import utils +from samcli.lib.package.utils import zip_folder, make_zip class TestPackageUtils(TestCase): @@ -46,3 +48,16 @@ def test_is_s3_url(self, url): ) def test_is_not_s3_url(self, url): self.assertFalse(utils.is_s3_url(url)) + + def test_zip_folder_uses_different_path_for_same_file_in_different_run(self): + all_zip_files = set() + previous_md5_hash = None + for i in range(5): + tmp_folder = tempfile.mkdtemp() + with zip_folder(tmp_folder, make_zip) as (zip_file, md5_hash): + self.assertNotIn(zip_file, all_zip_files, "Each zip file should be unique!") + all_zip_files.add(zip_file) + if not previous_md5_hash: + previous_md5_hash = md5_hash + else: + self.assertEqual(previous_md5_hash, md5_hash) diff --git a/tests/unit/local/apigw/test_local_apigw_service.py b/tests/unit/local/apigw/test_local_apigw_service.py index 707de65b51..2f1f67d0b6 100644 --- a/tests/unit/local/apigw/test_local_apigw_service.py +++ b/tests/unit/local/apigw/test_local_apigw_service.py @@ -586,7 +586,7 @@ def test_valid_identity_sources_not_lambda_auth(self): route = self.api_gateway_route route.authorizer_object = None - self.assertFalse(self.api_service._valid_identity_sources(route)) + self.assertFalse(self.api_service._valid_identity_sources(Mock(), route)) @parameterized.expand( [ @@ -597,8 +597,10 @@ def test_valid_identity_sources_not_lambda_auth(self): @patch("samcli.local.apigw.authorizers.lambda_authorizer.LambdaAuthorizer._parse_identity_sources") @patch("samcli.local.apigw.authorizers.lambda_authorizer.LambdaAuthorizer.identity_sources") @patch("samcli.local.apigw.path_converter.PathConverter.convert_path_to_api_gateway") + @patch("samcli.local.apigw.local_apigw_service.LocalApigwService._build_v2_context") + @patch("samcli.local.apigw.local_apigw_service.LocalApigwService._build_v1_context") def test_valid_identity_sources_id_source( - self, is_valid, path_convert_mock, id_source_prop_mock, lambda_auth_parse_mock + self, is_valid, v1_mock, v2_mock, path_convert_mock, id_source_prop_mock, lambda_auth_parse_mock ): route = self.api_gateway_route route.authorizer_object = LambdaAuthorizer("", "", "", [], "") @@ -607,11 +609,7 @@ def test_valid_identity_sources_id_source( mocked_id_source_obj.is_valid = Mock(return_value=is_valid) route.authorizer_object.identity_sources = [mocked_id_source_obj] - # create a dummy Flask app to populate the request object with testing data - # using Flask's dummy values for request is fine in this context since - # the variables are being passed and not validated - with flask.Flask(__name__).test_request_context(): - self.assertEqual(self.api_service._valid_identity_sources(route), is_valid) + self.assertEqual(self.api_service._valid_identity_sources(Mock(), route), is_valid) @patch.object(LocalApigwService, "get_request_methods_endpoints") def test_create_method_arn(self, method_endpoint_mock):