From 66e3adee075081337cca56e7a3128aa74bbdb07a Mon Sep 17 00:00:00 2001 From: Haresh Nasit Date: Wed, 7 Jun 2023 15:24:13 -0700 Subject: [PATCH 1/4] Fix some edge cases and bug fixes for remote invoke and update Arn parsing logic --- samcli/commands/cloud/exceptions.py | 4 + .../commands/cloud/remote_invoke_context.py | 41 +++--- samcli/lib/pipeline/bootstrap/resource.py | 5 +- .../remote_invoke/lambda_invoke_executors.py | 4 +- samcli/lib/utils/arn_utils.py | 41 +++++- .../cloud/test_remote_invoke_context.py | 13 +- tests/unit/lib/utils/test_arn_utils.py | 126 ++++++++++++++++-- 7 files changed, 195 insertions(+), 39 deletions(-) diff --git a/samcli/commands/cloud/exceptions.py b/samcli/commands/cloud/exceptions.py index f55b773624..68b0d5983f 100644 --- a/samcli/commands/cloud/exceptions.py +++ b/samcli/commands/cloud/exceptions.py @@ -22,3 +22,7 @@ class UnsupportedServiceForRemoteInvoke(UserException): class NoExecutorFoundForRemoteInvoke(UserException): pass + + +class InvalidStackNameProvidedForRemoteInvoke(UserException): + pass diff --git a/samcli/commands/cloud/remote_invoke_context.py b/samcli/commands/cloud/remote_invoke_context.py index 7c9dda4fcd..6aa07840bc 100644 --- a/samcli/commands/cloud/remote_invoke_context.py +++ b/samcli/commands/cloud/remote_invoke_context.py @@ -4,9 +4,12 @@ import logging from typing import Optional, cast +from botocore.exceptions import ClientError + from samcli.commands.cloud.exceptions import ( AmbiguousResourceForRemoteInvoke, InvalidRemoteInvokeParameters, + InvalidStackNameProvidedForRemoteInvoke, NoExecutorFoundForRemoteInvoke, NoResourceFoundForRemoteInvoke, UnsupportedServiceForRemoteInvoke, @@ -15,7 +18,7 @@ from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeExecutionInfo from samcli.lib.utils import osutils from samcli.lib.utils.arn_utils import ARNParts, InvalidArnValue -from samcli.lib.utils.boto_utils import BotoProviderType +from samcli.lib.utils.boto_utils import BotoProviderType, get_client_error_code from samcli.lib.utils.cloudformation import ( CloudFormationResourceSummary, get_resource_summaries, @@ -105,20 +108,28 @@ def _populate_resource_summary(self) -> None: if not self._stack_name and not self._resource_id: raise InvalidRemoteInvokeParameters("Either --stack-name or --resource-id parameter should be provided") - if not self._resource_id: - # no resource id provided, list all resources from stack and try to find one - self._resource_summary = self._get_single_resource_from_stack() - self._resource_id = self._resource_summary.logical_resource_id - return - - if not self._stack_name: - # no stack name provided, resource id should be physical id so that we can use it - self._resource_summary = self._get_from_physical_resource_id() - return - - self._resource_summary = get_resource_summary( - self._boto_resource_provider, self._boto_client_provider, self._stack_name, self._resource_id - ) + try: + if not self._resource_id: + # no resource id provided, list all resources from stack and try to find one + self._resource_summary = self._get_single_resource_from_stack() + self._resource_id = self._resource_summary.logical_resource_id + return + + if not self._stack_name: + # no stack name provided, resource id should be physical id so that we can use it + self._resource_summary = self._get_from_physical_resource_id() + return + + self._resource_summary = get_resource_summary( + self._boto_resource_provider, self._boto_client_provider, self._stack_name, self._resource_id + ) + except ClientError as ex: + error_code = get_client_error_code(ex) + if error_code == "ValidationError": + raise InvalidStackNameProvidedForRemoteInvoke( + f"Invalid --stack-name parameter. Stack with id '{self._stack_name}' does not exist" + ) + raise ex def _get_single_resource_from_stack(self) -> CloudFormationResourceSummary: """ diff --git a/samcli/lib/pipeline/bootstrap/resource.py b/samcli/lib/pipeline/bootstrap/resource.py index 168da9f9f0..cb5eb000e3 100644 --- a/samcli/lib/pipeline/bootstrap/resource.py +++ b/samcli/lib/pipeline/bootstrap/resource.py @@ -99,10 +99,9 @@ def get_uri(self) -> Optional[str]: # ECR's resource_id contains the resource-type("resource") which is excluded from the URL # from docs: https://docs.aws.amazon.com/AmazonECR/latest/userguide/security_iam_service-with-iam.html # ECR's ARN: arn:${Partition}:ecr:${Region}:${Account}:repository/${Repository-name} - if not arn_parts.resource_id.startswith("repository/"): + if arn_parts.resource_type != "repository": raise ValueError(f"Invalid ECR ARN ({self.arn}), can't extract the URL from it.") - i = len("repository/") - repo_name = arn_parts.resource_id[i:] + repo_name = arn_parts.resource_id return f"{arn_parts.account_id}.dkr.ecr.{arn_parts.region}.amazonaws.com/{repo_name}" diff --git a/samcli/lib/remote_invoke/lambda_invoke_executors.py b/samcli/lib/remote_invoke/lambda_invoke_executors.py index 49245adf99..82e65b8117 100644 --- a/samcli/lib/remote_invoke/lambda_invoke_executors.py +++ b/samcli/lib/remote_invoke/lambda_invoke_executors.py @@ -52,9 +52,7 @@ def validate_action_parameters(self, parameters: dict) -> None: if parameter_key == FUNCTION_NAME: LOG.warning("FunctionName is defined using the value provided for --resource-id option.") elif parameter_key == PAYLOAD: - LOG.warning( - "Payload is defined using the value provided for either --payload or --payload-file options." - ) + LOG.warning("Payload is defined using the value provided for either --event or --event-file options.") else: self.request_parameters[parameter_key] = parameter_value diff --git a/samcli/lib/utils/arn_utils.py b/samcli/lib/utils/arn_utils.py index c610442290..a1569a7d32 100644 --- a/samcli/lib/utils/arn_utils.py +++ b/samcli/lib/utils/arn_utils.py @@ -1,6 +1,7 @@ """ Module for utilities for ARN (Amazon Resource Names) """ +import re class InvalidArnValue(ValueError): @@ -31,11 +32,47 @@ class ARNParts: service: str region: str account_id: str + resource_type: str resource_id: str def __init__(self, arn: str) -> None: - parts = arn.split(":") try: - [_, self.partition, self.service, self.region, self.account_id, self.resource_id] = parts + # Regex pattern formed based on the 3 ARN general formats found here: + # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html + arn_pattern = ( + r"arn:([a-zA-Z0-9_-]+):" # Pattern for partition + r"([a-zA-Z0-9_-]+):" # Pattern for service + r"([a-zA-Z0-9_-]*):" # Pattern for region + r"([a-zA-Z0-9_-]*)" # Pattern for account_id + r"(?::([a-zA-Z0-9_-]+))?" # Pattern for resource_type if it exists + r"(?::(.+))" # Pattern for resource_id if it exists + ) + print(arn_pattern) + matched_arn = re.match(arn_pattern, arn) + if not matched_arn: + raise ValueError() + + self.partition = matched_arn.group(1) + self.service = matched_arn.group(2) + self.region = matched_arn.group(3) if matched_arn.group(3) else "" + self.account_id = matched_arn.group(4) if matched_arn.group(4) else "" + self.resource_type = matched_arn.group(5) if matched_arn.group(5) else "" + if matched_arn.group(5): + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-type:resource-id + self.resource_type = matched_arn.group(5) + self.resource_id = matched_arn.group(6) if matched_arn.group(6) else "" + elif "/" in matched_arn.group(6): + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-type/resource-id + split_resource_type_and_id = matched_arn.group(6).split("/", 1) + self.resource_type = split_resource_type_and_id[0] + self.resource_id = split_resource_type_and_id[1] + else: + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-id + self.resource_type = "" + self.resource_id = matched_arn.group(6) if matched_arn.group(6) else "" + except ValueError as ex: raise InvalidArnValue(f"Invalid ARN ({arn})") from ex diff --git a/tests/unit/commands/cloud/test_remote_invoke_context.py b/tests/unit/commands/cloud/test_remote_invoke_context.py index d7e55dce06..687f105ef2 100644 --- a/tests/unit/commands/cloud/test_remote_invoke_context.py +++ b/tests/unit/commands/cloud/test_remote_invoke_context.py @@ -8,6 +8,7 @@ NoResourceFoundForRemoteInvoke, UnsupportedServiceForRemoteInvoke, NoExecutorFoundForRemoteInvoke, + InvalidStackNameProvidedForRemoteInvoke, ) from samcli.commands.cloud.remote_invoke_context import RemoteInvokeContext, SUPPORTED_SERVICES from samcli.lib.utils.cloudformation import CloudFormationResourceSummary @@ -32,6 +33,14 @@ def test_no_stack_name_and_no_resource_id_should_fail(self): with self._get_remote_invoke_context(): pass + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") + def test_invalid_stack_name_with_no_resource_should_fail(self, patched_resource_summaries): + self.resource_id = None + patched_resource_summaries.side_effect = InvalidStackNameProvidedForRemoteInvoke("Invalid stack-name") + with self.assertRaises(InvalidStackNameProvidedForRemoteInvoke): + with self._get_remote_invoke_context(): + pass + @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_no_resource_should_fail(self, patched_resource_summaries): self.resource_id = None @@ -58,7 +67,7 @@ def test_only_stack_name_with_single_resource_should_be_valid(self, patched_reso def test_only_resource_id_unsupported_service_arn_should_fail(self): self.stack_name = None - self.resource_id = "arn:aws:unsupported-service:region:account:resource_id" + self.resource_id = "arn:aws:unsupported-service:region:account:resource_type:resource_id" with self.assertRaises(UnsupportedServiceForRemoteInvoke): with self._get_remote_invoke_context(): pass @@ -66,7 +75,7 @@ def test_only_resource_id_unsupported_service_arn_should_fail(self): def test_only_resource_id_supported_service_arn_should_be_valid(self): self.stack_name = None service = "lambda" - self.resource_id = f"arn:aws:{service}:region:account:{self.resource_id}" + self.resource_id = f"arn:aws:{service}:region:account:resource_type:{self.resource_id}" with self._get_remote_invoke_context() as remote_invoke_context: self.assertEqual( remote_invoke_context._resource_summary, diff --git a/tests/unit/lib/utils/test_arn_utils.py b/tests/unit/lib/utils/test_arn_utils.py index cf235c1e5d..f12e0103a8 100644 --- a/tests/unit/lib/utils/test_arn_utils.py +++ b/tests/unit/lib/utils/test_arn_utils.py @@ -1,4 +1,5 @@ from unittest import TestCase +from parameterized import parameterized from samcli.lib.utils.arn_utils import InvalidArnValue, ARNParts @@ -8,18 +9,115 @@ def test_invalid_arn_should_fail(self): with self.assertRaises(InvalidArnValue): _ = ARNParts("invalid_arn") - def test_valid_arn(self): - partition = "aws" - service = "lambda" - region = "us-east-1" - account_id = "0123456789" - resource_id = "resource_id" - arn_value = f"arn:{partition}:{service}:{region}:{account_id}:{resource_id}" + @parameterized.expand( + [ + ( + "arn:aws:service:region:account-id:resource-id", + { + "partition": "aws", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "", + "resource_id": "resource-id", + }, + ), + ( + "arn:aws:service:region:account-id:resource-type/resource-id", + { + "partition": "aws", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "resource-type", + "resource_id": "resource-id", + }, + ), + ( + "arn:aws:service:region:account-id:resource-type:resource-id", + { + "partition": "aws", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "resource-type", + "resource_id": "resource-id", + }, + ), + ( + "arn:partition:service:region:account-id:repository/repository-name", + { + "partition": "partition", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "repository", + "resource_id": "repository-name", + }, + ), + ( + "arn:partition:service:region:account-id:s3-bucket-name", + { + "partition": "partition", + "service": "service", + "region": "region", + "account_id": "account-id", + "resource_type": "", + "resource_id": "s3-bucket-name", + }, + ), + ( + "arn:aws:lambda:us-west-2:123456789012:function:my-function", + { + "partition": "aws", + "service": "lambda", + "region": "us-west-2", + "account_id": "123456789012", + "resource_type": "function", + "resource_id": "my-function", + }, + ), + ( + "arn:aws:states:us-east-1:111122223333:stateMachine:HelloWorld-StateMachine", + { + "partition": "aws", + "service": "states", + "region": "us-east-1", + "account_id": "111122223333", + "resource_type": "stateMachine", + "resource_id": "HelloWorld-StateMachine", + }, + ), + ( + "arn:aws:sqs:region:account_id:queue_name", + { + "partition": "aws", + "service": "sqs", + "region": "region", + "account_id": "account_id", + "resource_type": "", + "resource_id": "queue_name", + }, + ), + ( + "arn:aws:kinesis:us-east-2:123456789012:stream/mystream", + { + "partition": "aws", + "service": "kinesis", + "region": "us-east-2", + "account_id": "123456789012", + "resource_type": "stream", + "resource_id": "mystream", + }, + ), + ] + ) + def test_valid_arn(self, given_arn, expected_result): + arn_parts = ARNParts(given_arn) - arn_parts = ARNParts(arn_value) - - self.assertEqual(arn_parts.partition, partition) - self.assertEqual(arn_parts.service, service) - self.assertEqual(arn_parts.region, region) - self.assertEqual(arn_parts.account_id, account_id) - self.assertEqual(arn_parts.resource_id, resource_id) + self.assertEqual(arn_parts.partition, expected_result["partition"]) + self.assertEqual(arn_parts.service, expected_result["service"]) + self.assertEqual(arn_parts.region, expected_result["region"]) + self.assertEqual(arn_parts.account_id, expected_result["account_id"]) + self.assertEqual(arn_parts.resource_type, expected_result["resource_type"]) + self.assertEqual(arn_parts.resource_id, expected_result["resource_id"]) From d8473abe5c2fab8464487b1e4332375241c1d1c8 Mon Sep 17 00:00:00 2001 From: Haresh Nasit Date: Wed, 7 Jun 2023 16:47:37 -0700 Subject: [PATCH 2/4] Address feedback --- samcli/lib/utils/arn_utils.py | 72 +++++++++++++++++------------------ 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/samcli/lib/utils/arn_utils.py b/samcli/lib/utils/arn_utils.py index a1569a7d32..2df2aef9ea 100644 --- a/samcli/lib/utils/arn_utils.py +++ b/samcli/lib/utils/arn_utils.py @@ -36,43 +36,39 @@ class ARNParts: resource_id: str def __init__(self, arn: str) -> None: - try: - # Regex pattern formed based on the 3 ARN general formats found here: - # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html - arn_pattern = ( - r"arn:([a-zA-Z0-9_-]+):" # Pattern for partition - r"([a-zA-Z0-9_-]+):" # Pattern for service - r"([a-zA-Z0-9_-]*):" # Pattern for region - r"([a-zA-Z0-9_-]*)" # Pattern for account_id - r"(?::([a-zA-Z0-9_-]+))?" # Pattern for resource_type if it exists - r"(?::(.+))" # Pattern for resource_id if it exists - ) - print(arn_pattern) - matched_arn = re.match(arn_pattern, arn) - if not matched_arn: - raise ValueError() + # Regex pattern formed based on the 3 ARN general formats found here: + # https://docs.aws.amazon.com/IAM/latest/UserGuide/reference-arns.html + arn_pattern = ( + r"arn:([a-zA-Z0-9_-]+):" # Pattern for partition + r"([a-zA-Z0-9_-]+):" # Pattern for service + r"([a-zA-Z0-9_-]*):" # Pattern for region + r"([a-zA-Z0-9_-]*)" # Pattern for account_id + r"(?::([a-zA-Z0-9_-]+))?" # Pattern for resource_type if it exists + r"(?::(.+))" # Pattern for resource_id if it exists + ) - self.partition = matched_arn.group(1) - self.service = matched_arn.group(2) - self.region = matched_arn.group(3) if matched_arn.group(3) else "" - self.account_id = matched_arn.group(4) if matched_arn.group(4) else "" - self.resource_type = matched_arn.group(5) if matched_arn.group(5) else "" - if matched_arn.group(5): - # This handles the Arns of services with the format: - # arn:partition:service:region:account-id:resource-type:resource-id - self.resource_type = matched_arn.group(5) - self.resource_id = matched_arn.group(6) if matched_arn.group(6) else "" - elif "/" in matched_arn.group(6): - # This handles the Arns of services with the format: - # arn:partition:service:region:account-id:resource-type/resource-id - split_resource_type_and_id = matched_arn.group(6).split("/", 1) - self.resource_type = split_resource_type_and_id[0] - self.resource_id = split_resource_type_and_id[1] - else: - # This handles the Arns of services with the format: - # arn:partition:service:region:account-id:resource-id - self.resource_type = "" - self.resource_id = matched_arn.group(6) if matched_arn.group(6) else "" + matched_arn = re.match(arn_pattern, arn) + if not matched_arn: + raise InvalidArnValue(f"Invalid ARN ({arn}) provided") - except ValueError as ex: - raise InvalidArnValue(f"Invalid ARN ({arn})") from ex + self.partition = matched_arn.group(1) + self.service = matched_arn.group(2) + self.region = matched_arn.group(3) if matched_arn.group(3) else "" + self.account_id = matched_arn.group(4) if matched_arn.group(4) else "" + self.resource_type = matched_arn.group(5) if matched_arn.group(5) else "" + if matched_arn.group(5): + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-type:resource-id + self.resource_type = matched_arn.group(5) + self.resource_id = matched_arn.group(6) if matched_arn.group(6) else "" + elif "/" in matched_arn.group(6): + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-type/resource-id + split_resource_type_and_id = matched_arn.group(6).split("/", 1) + self.resource_type = split_resource_type_and_id[0] + self.resource_id = split_resource_type_and_id[1] + else: + # This handles the Arns of services with the format: + # arn:partition:service:region:account-id:resource-id + self.resource_type = "" + self.resource_id = matched_arn.group(6) if matched_arn.group(6) else "" From 8dc32b9763f08081ccca2084c578f030d871dd79 Mon Sep 17 00:00:00 2001 From: Haresh Nasit Date: Wed, 7 Jun 2023 18:14:30 -0700 Subject: [PATCH 3/4] Add unit test for s3 with no region/accoint_id provided --- tests/unit/lib/utils/test_arn_utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/unit/lib/utils/test_arn_utils.py b/tests/unit/lib/utils/test_arn_utils.py index f12e0103a8..935c724c31 100644 --- a/tests/unit/lib/utils/test_arn_utils.py +++ b/tests/unit/lib/utils/test_arn_utils.py @@ -66,6 +66,17 @@ def test_invalid_arn_should_fail(self): "resource_id": "s3-bucket-name", }, ), + ( + "arn:partition:service:::s3-bucket-name", + { + "partition": "partition", + "service": "service", + "region": "", + "account_id": "", + "resource_type": "", + "resource_id": "s3-bucket-name", + }, + ), ( "arn:aws:lambda:us-west-2:123456789012:function:my-function", { From 24453915d7880849e1dbccf3964c88dca396788f Mon Sep 17 00:00:00 2001 From: Haresh Nasit Date: Thu, 8 Jun 2023 13:30:49 -0700 Subject: [PATCH 4/4] Renamed command to sam remote invoke --- samcli/cli/command.py | 2 +- samcli/commands/{cloud => remote}/__init__.py | 0 .../commands/{cloud => remote}/exceptions.py | 0 .../{cloud => remote}/invoke/__init__.py | 0 .../commands/{cloud => remote}/invoke/cli.py | 4 +-- .../{cloud/cloud.py => remote/remote.py} | 4 +-- .../remote_invoke_context.py | 2 +- .../remote_invoke_options_validations.py | 4 +-- .../commands/{cloud => remote}/__init__.py | 0 .../{cloud => remote}/invoke/__init__.py | 0 .../{cloud => remote}/invoke/test_cli.py | 6 ++--- .../test_remote_invoke_context.py | 26 +++++++++---------- 12 files changed, 24 insertions(+), 24 deletions(-) rename samcli/commands/{cloud => remote}/__init__.py (100%) rename samcli/commands/{cloud => remote}/exceptions.py (100%) rename samcli/commands/{cloud => remote}/invoke/__init__.py (100%) rename samcli/commands/{cloud => remote}/invoke/cli.py (97%) rename samcli/commands/{cloud/cloud.py => remote/remote.py} (62%) rename samcli/commands/{cloud => remote}/remote_invoke_context.py (99%) rename tests/unit/commands/{cloud => remote}/__init__.py (100%) rename tests/unit/commands/{cloud => remote}/invoke/__init__.py (100%) rename tests/unit/commands/{cloud => remote}/invoke/test_cli.py (96%) rename tests/unit/commands/{cloud => remote}/test_remote_invoke_context.py (85%) diff --git a/samcli/cli/command.py b/samcli/cli/command.py index b3fab518bf..23934f9eb4 100644 --- a/samcli/cli/command.py +++ b/samcli/cli/command.py @@ -30,7 +30,7 @@ "samcli.commands.pipeline.pipeline", "samcli.commands.list.list", "samcli.commands.docs", - # "samcli.commands.cloud.cloud", + # "samcli.commands.remote.remote", # We intentionally do not expose the `bootstrap` command for now. We might open it up later # "samcli.commands.bootstrap", ] diff --git a/samcli/commands/cloud/__init__.py b/samcli/commands/remote/__init__.py similarity index 100% rename from samcli/commands/cloud/__init__.py rename to samcli/commands/remote/__init__.py diff --git a/samcli/commands/cloud/exceptions.py b/samcli/commands/remote/exceptions.py similarity index 100% rename from samcli/commands/cloud/exceptions.py rename to samcli/commands/remote/exceptions.py diff --git a/samcli/commands/cloud/invoke/__init__.py b/samcli/commands/remote/invoke/__init__.py similarity index 100% rename from samcli/commands/cloud/invoke/__init__.py rename to samcli/commands/remote/invoke/__init__.py diff --git a/samcli/commands/cloud/invoke/cli.py b/samcli/commands/remote/invoke/cli.py similarity index 97% rename from samcli/commands/cloud/invoke/cli.py rename to samcli/commands/remote/invoke/cli.py index 532ff26efd..d3ebd5d36f 100644 --- a/samcli/commands/cloud/invoke/cli.py +++ b/samcli/commands/remote/invoke/cli.py @@ -68,7 +68,7 @@ def cli( config_env: str, ) -> None: """ - `sam cloud invoke` command entry point + `sam remote invoke` command entry point """ do_cli( @@ -100,8 +100,8 @@ def do_cli( """ Implementation of the ``cli`` method """ - from samcli.commands.cloud.remote_invoke_context import RemoteInvokeContext from samcli.commands.exceptions import UserException + from samcli.commands.remote.remote_invoke_context import RemoteInvokeContext from samcli.lib.remote_invoke.exceptions import ( ErrorBotoApiCallException, InvalideBotoResponseException, diff --git a/samcli/commands/cloud/cloud.py b/samcli/commands/remote/remote.py similarity index 62% rename from samcli/commands/cloud/cloud.py rename to samcli/commands/remote/remote.py index a2cf4c1f70..eac2e7028f 100644 --- a/samcli/commands/cloud/cloud.py +++ b/samcli/commands/remote/remote.py @@ -1,11 +1,11 @@ """ -Command group for "cloud" suite for commands. It provides common CLI arguments for interacting with +Command group for "remote" suite for commands. It provides common CLI arguments for interacting with cloud resources such as Lambda Function. """ import click -from samcli.commands.cloud.invoke.cli import cli as invoke_cli +from samcli.commands.remote.invoke.cli import cli as invoke_cli @click.group() diff --git a/samcli/commands/cloud/remote_invoke_context.py b/samcli/commands/remote/remote_invoke_context.py similarity index 99% rename from samcli/commands/cloud/remote_invoke_context.py rename to samcli/commands/remote/remote_invoke_context.py index 6aa07840bc..254f504b33 100644 --- a/samcli/commands/cloud/remote_invoke_context.py +++ b/samcli/commands/remote/remote_invoke_context.py @@ -6,7 +6,7 @@ from botocore.exceptions import ClientError -from samcli.commands.cloud.exceptions import ( +from samcli.commands.remote.exceptions import ( AmbiguousResourceForRemoteInvoke, InvalidRemoteInvokeParameters, InvalidStackNameProvidedForRemoteInvoke, diff --git a/samcli/lib/cli_validation/remote_invoke_options_validations.py b/samcli/lib/cli_validation/remote_invoke_options_validations.py index 6db65e2c20..dbb0cde6ef 100644 --- a/samcli/lib/cli_validation/remote_invoke_options_validations.py +++ b/samcli/lib/cli_validation/remote_invoke_options_validations.py @@ -22,7 +22,7 @@ def event_and_event_file_options_validation(func): Parameters ---------- func : - Command that would be executed, in this case it is 'sam cloud invoke' + Command that would be executed, in this case it is 'sam remote invoke' Returns ------- @@ -64,7 +64,7 @@ def stack_name_or_resource_id_atleast_one_option_validation(func): Parameters ---------- func : - Command that would be executed, in this case it is 'sam cloud invoke' + Command that would be executed, in this case it is 'sam remote invoke' Returns ------- diff --git a/tests/unit/commands/cloud/__init__.py b/tests/unit/commands/remote/__init__.py similarity index 100% rename from tests/unit/commands/cloud/__init__.py rename to tests/unit/commands/remote/__init__.py diff --git a/tests/unit/commands/cloud/invoke/__init__.py b/tests/unit/commands/remote/invoke/__init__.py similarity index 100% rename from tests/unit/commands/cloud/invoke/__init__.py rename to tests/unit/commands/remote/invoke/__init__.py diff --git a/tests/unit/commands/cloud/invoke/test_cli.py b/tests/unit/commands/remote/invoke/test_cli.py similarity index 96% rename from tests/unit/commands/cloud/invoke/test_cli.py rename to tests/unit/commands/remote/invoke/test_cli.py index ae7dc7f333..6e97251b5a 100644 --- a/tests/unit/commands/cloud/invoke/test_cli.py +++ b/tests/unit/commands/remote/invoke/test_cli.py @@ -3,7 +3,7 @@ from parameterized import parameterized -from samcli.commands.cloud.invoke.cli import do_cli +from samcli.commands.remote.invoke.cli import do_cli from samcli.lib.remote_invoke.remote_invoke_executors import RemoteInvokeOutputFormat from samcli.lib.remote_invoke.exceptions import ( ErrorBotoApiCallException, @@ -37,7 +37,7 @@ def setUp(self) -> None: @patch("samcli.lib.remote_invoke.remote_invoke_executors.RemoteInvokeExecutionInfo") @patch("samcli.lib.utils.boto_utils.get_boto_client_provider_with_config") @patch("samcli.lib.utils.boto_utils.get_boto_resource_provider_with_config") - @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeContext") + @patch("samcli.commands.remote.remote_invoke_context.RemoteInvokeContext") def test_remote_invoke_command( self, event, @@ -112,7 +112,7 @@ def test_remote_invoke_command( (InvalidResourceBotoParameterException,), ] ) - @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeContext") + @patch("samcli.commands.remote.remote_invoke_context.RemoteInvokeContext") def test_raise_user_exception_invoke_not_successfull(self, exeception_to_raise, mock_invoke_context): context_mock = Mock() diff --git a/tests/unit/commands/cloud/test_remote_invoke_context.py b/tests/unit/commands/remote/test_remote_invoke_context.py similarity index 85% rename from tests/unit/commands/cloud/test_remote_invoke_context.py rename to tests/unit/commands/remote/test_remote_invoke_context.py index 687f105ef2..0c04a01713 100644 --- a/tests/unit/commands/cloud/test_remote_invoke_context.py +++ b/tests/unit/commands/remote/test_remote_invoke_context.py @@ -2,7 +2,7 @@ from unittest.mock import Mock, patch from uuid import uuid4 -from samcli.commands.cloud.exceptions import ( +from samcli.commands.remote.exceptions import ( InvalidRemoteInvokeParameters, AmbiguousResourceForRemoteInvoke, NoResourceFoundForRemoteInvoke, @@ -10,7 +10,7 @@ NoExecutorFoundForRemoteInvoke, InvalidStackNameProvidedForRemoteInvoke, ) -from samcli.commands.cloud.remote_invoke_context import RemoteInvokeContext, SUPPORTED_SERVICES +from samcli.commands.remote.remote_invoke_context import RemoteInvokeContext, SUPPORTED_SERVICES from samcli.lib.utils.cloudformation import CloudFormationResourceSummary @@ -33,7 +33,7 @@ def test_no_stack_name_and_no_resource_id_should_fail(self): with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summaries") def test_invalid_stack_name_with_no_resource_should_fail(self, patched_resource_summaries): self.resource_id = None patched_resource_summaries.side_effect = InvalidStackNameProvidedForRemoteInvoke("Invalid stack-name") @@ -41,7 +41,7 @@ def test_invalid_stack_name_with_no_resource_should_fail(self, patched_resource_ with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_no_resource_should_fail(self, patched_resource_summaries): self.resource_id = None patched_resource_summaries.return_value = {} @@ -49,7 +49,7 @@ def test_only_stack_name_with_no_resource_should_fail(self, patched_resource_sum with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_multiple_resource_should_fail(self, patched_resource_summaries): self.resource_id = None patched_resource_summaries.return_value = {"resource1": Mock(), "resource2": Mock()} @@ -57,7 +57,7 @@ def test_only_stack_name_with_multiple_resource_should_fail(self, patched_resour with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summaries") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summaries") def test_only_stack_name_with_single_resource_should_be_valid(self, patched_resource_summaries): self.resource_id = None resource_summary = Mock(logical_resource_id=self.resource_id) @@ -84,7 +84,7 @@ def test_only_resource_id_supported_service_arn_should_be_valid(self): ), ) - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary_from_physical_id") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary_from_physical_id") def test_only_resource_id_as_invalid_physical_id_should_fail(self, patched_resource_summary_from_physical_id): self.stack_name = None patched_resource_summary_from_physical_id.return_value = None @@ -92,14 +92,14 @@ def test_only_resource_id_as_invalid_physical_id_should_fail(self, patched_resou with self._get_remote_invoke_context(): pass - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary") def test_if_no_resource_found_with_given_stack_and_resource_id_should_fail(self, patched_get_resource_summary): patched_get_resource_summary.return_value = None with self.assertRaises(AmbiguousResourceForRemoteInvoke): with self._get_remote_invoke_context() as remote_invoke_context: remote_invoke_context.run(Mock()) - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary_from_physical_id") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary_from_physical_id") def test_only_resource_id_as_valid_physical_id_should_be_valid(self, patched_resource_summary_from_physical_id): self.stack_name = None resource_summary = Mock() @@ -107,22 +107,22 @@ def test_only_resource_id_as_valid_physical_id_should_be_valid(self, patched_res with self._get_remote_invoke_context() as remote_invoke_context: self.assertEqual(remote_invoke_context._resource_summary, resource_summary) - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary") def test_running_without_resource_summary_should_raise_exception(self, patched_get_resource_summary): patched_get_resource_summary.return_value = None with self._get_remote_invoke_context() as remote_invoke_context: with self.assertRaises(AmbiguousResourceForRemoteInvoke): remote_invoke_context.run(Mock()) - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary") def test_running_with_unsupported_resource_should_raise_exception(self, patched_get_resource_summary): patched_get_resource_summary.return_value = Mock(resource_type="UnSupportedResource") with self._get_remote_invoke_context() as remote_invoke_context: with self.assertRaises(NoExecutorFoundForRemoteInvoke): remote_invoke_context.run(Mock()) - @patch("samcli.commands.cloud.remote_invoke_context.RemoteInvokeExecutorFactory") - @patch("samcli.commands.cloud.remote_invoke_context.get_resource_summary") + @patch("samcli.commands.remote.remote_invoke_context.RemoteInvokeExecutorFactory") + @patch("samcli.commands.remote.remote_invoke_context.get_resource_summary") def test_running_should_execute_remote_invoke_executor_instance( self, patched_get_resource_summary, patched_remote_invoke_executor_factory ):