Skip to content

Commit

Permalink
feat(Accelerate): Updated Sync Code to Sync All Code Resources by Def…
Browse files Browse the repository at this point in the history
…ault (aws#357)

* Updated Sync Code to Sync All Code Resources

* Added Tests

* Formatted with Black

* Refactored Out get_unique_resource_ids
  • Loading branch information
CoshUS authored Jul 8, 2021
1 parent 4564ebe commit fd8c322
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 45 deletions.
26 changes: 12 additions & 14 deletions samcli/commands/sync/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@
from samcli.lib.sync.sync_flow_factory import SyncFlowFactory
from samcli.lib.sync.sync_flow_executor import SyncFlowExecutor
from samcli.lib.providers.sam_stack_provider import SamLocalStackProvider
from samcli.lib.providers.provider import ResourceIdentifier, get_resources_by_type
from samcli.lib.providers.provider import (
ResourceIdentifier,
get_all_resource_ids,
get_unique_resource_ids,
)
from samcli.commands._utils.options import DEFAULT_BUILD_DIR, DEFAULT_CACHE_DIR
from samcli.cli.context import Context

Expand Down Expand Up @@ -273,7 +277,7 @@ def execute_code_sync(
template: str,
build_context: "BuildContext",
deploy_context: "DeployContext",
resources_ids: Optional[Tuple[str]],
resource_ids: Optional[Tuple[str]],
resource_types: Optional[Tuple[str]],
) -> None:
"""Executes the sync flow for code.
Expand All @@ -286,7 +290,7 @@ def execute_code_sync(
BuildContext
deploy_context : DeployContext
DeployContext
resources_ids : List[str]
resource_ids : List[str]
List of resource IDs to be synced.
resource_types : List[str]
List of resource types to be synced.
Expand All @@ -296,17 +300,11 @@ def execute_code_sync(
factory.load_physical_id_mapping()
executor = SyncFlowExecutor()

sync_flow_resource_ids: Set[ResourceIdentifier] = set()

if resources_ids:
for resources_id in resources_ids:
sync_flow_resource_ids.add(ResourceIdentifier(resources_id))

if resource_types:
for resource_type in resource_types:
resource_type_ids = get_resources_by_type(stacks, resource_type)
for resource_id in resource_type_ids:
sync_flow_resource_ids.add(resource_id)
sync_flow_resource_ids: Set[ResourceIdentifier] = (
get_unique_resource_ids(stacks, resource_ids, resource_types)
if resource_ids or resource_types
else set(get_all_resource_ids(stacks))
)

for resource_id in sync_flow_resource_ids:
sync_flow = factory.create_sync_flow(resource_id)
Expand Down
65 changes: 59 additions & 6 deletions samcli/lib/providers/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import os
import posixpath
from collections import namedtuple
from typing import Any, Set, NamedTuple, Optional, List, Dict, Union, cast, Iterator, TYPE_CHECKING
from typing import Any, Set, NamedTuple, Optional, List, Dict, Tuple, Union, cast, Iterator, TYPE_CHECKING

from samcli.commands.local.cli_common.user_exceptions import InvalidLayerVersionArn, UnsupportedIntrinsic
from samcli.lib.providers.sam_base_provider import SamBaseProvider
Expand Down Expand Up @@ -495,6 +495,8 @@ def get_full_path(stack_path: str, logical_id: str) -> str:
Return the unique posix path-like identifier
while will used for identify a resource from resources in a multi-stack situation
"""
if not stack_path:
return logical_id
return posixpath.join(stack_path, logical_id)


Expand Down Expand Up @@ -528,7 +530,7 @@ def get_resource_by_id(
return None


def get_resources_by_type(stacks: List[Stack], resource_type: str) -> List[ResourceIdentifier]:
def get_resource_ids_by_type(stacks: List[Stack], resource_type: str) -> List[ResourceIdentifier]:
"""Return list of resource IDs
Parameters
Expand All @@ -547,13 +549,64 @@ def get_resources_by_type(stacks: List[Stack], resource_type: str) -> List[Resou
for stack in stacks:
for resource_id, resource in stack.resources.items():
if resource.get("Type", "") == resource_type:
if stack.stack_path:
resource_ids.append(ResourceIdentifier(stack.stack_path + posixpath.sep + resource_id))
else:
resource_ids.append(ResourceIdentifier(resource_id))
resource_ids.append(ResourceIdentifier(get_full_path(stack.stack_path, resource_id)))
return resource_ids


def get_all_resource_ids(stacks: List[Stack]) -> List[ResourceIdentifier]:
"""Return all resource IDs in stacks
Parameters
----------
stacks : List[Stack]
List of stacks
Returns
-------
List[ResourceIdentifier]
List of ResourceIdentifiers
"""
resource_ids: List[ResourceIdentifier] = list()
for stack in stacks:
for resource_id, _ in stack.resources.items():
resource_ids.append(ResourceIdentifier(get_full_path(stack.stack_path, resource_id)))
return resource_ids


def get_unique_resource_ids(
stacks: List[Stack],
resource_ids: Optional[Union[List[str], Tuple[str]]],
resource_types: Optional[Union[List[str], Tuple[str]]],
) -> Set[ResourceIdentifier]:
"""Get unique resource IDs for resource_ids and resource_types
Parameters
----------
stacks : List[Stack]
Stacks
resource_ids : Optional[Union[List[str], Tuple[str]]]
Resource ID strings
resource_types : Optional[Union[List[str], Tuple[str]]]
Resource types
Returns
-------
Set[ResourceIdentifier]
Set of ResourceIdentifier either in resource_ids or has the type in resource_types
"""
output_resource_ids: Set[ResourceIdentifier] = set()
if resource_ids:
for resources_id in resource_ids:
output_resource_ids.add(ResourceIdentifier(resources_id))

if resource_types:
for resource_type in resource_types:
resource_type_ids = get_resource_ids_by_type(stacks, resource_type)
for resource_id in resource_type_ids:
output_resource_ids.add(resource_id)
return output_resource_ids


def _get_build_dir(resource: Union[Function, LayerVersion], build_root: str) -> str:
"""
Return the build directory to place build artifact
Expand Down
82 changes: 75 additions & 7 deletions tests/unit/commands/local/lib/test_provider.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from unittest import TestCase
from unittest.mock import MagicMock, Mock
from unittest.mock import MagicMock, Mock, patch

from parameterized import parameterized

Expand All @@ -10,8 +10,10 @@
ResourceIdentifier,
Stack,
_get_build_dir,
get_all_resource_ids,
get_resource_by_id,
get_resources_by_type,
get_resource_ids_by_type,
get_unique_resource_ids,
)
from samcli.commands.local.cli_common.user_exceptions import InvalidLayerVersionArn, UnsupportedIntrinsic

Expand Down Expand Up @@ -261,7 +263,7 @@ def test_get_resource_by_id_not_found(
self.assertEqual(result, None)


class TestGetResourcesByType(TestCase):
class TestGetResourceIDsByType(TestCase):
def setUp(self) -> None:
super().setUp()
self.root_stack = MagicMock()
Expand All @@ -276,14 +278,80 @@ def setUp(self) -> None:
self.nested_nested_stack.stack_path = "NestedStack1/NestedNestedStack1"
self.nested_nested_stack.resources = {"Function2": {"Type": "TypeB"}}

def test_get_resources_by_type_single_nested(
def test_get_resource_ids_by_type_single_nested(
self,
):
result = get_resources_by_type([self.root_stack, self.nested_stack, self.nested_nested_stack], "TypeB")
result = get_resource_ids_by_type([self.root_stack, self.nested_stack, self.nested_nested_stack], "TypeB")
self.assertEqual(result, [ResourceIdentifier("NestedStack1/NestedNestedStack1/Function2")])

def test_get_resources_by_type_multiple_nested(
def test_get_resource_ids_by_type_multiple_nested(
self,
):
result = get_resources_by_type([self.root_stack, self.nested_stack, self.nested_nested_stack], "TypeA")
result = get_resource_ids_by_type([self.root_stack, self.nested_stack, self.nested_nested_stack], "TypeA")
self.assertEqual(result, [ResourceIdentifier("Function1"), ResourceIdentifier("NestedStack1/Function1")])


class TestGetAllResourceIDs(TestCase):
def setUp(self) -> None:
super().setUp()
self.root_stack = MagicMock()
self.root_stack.stack_path = ""
self.root_stack.resources = {"Function1": {"Type": "TypeA"}}

self.nested_stack = MagicMock()
self.nested_stack.stack_path = "NestedStack1"
self.nested_stack.resources = {"Function1": {"Type": "TypeA"}}

self.nested_nested_stack = MagicMock()
self.nested_nested_stack.stack_path = "NestedStack1/NestedNestedStack1"
self.nested_nested_stack.resources = {"Function2": {"Type": "TypeB"}}

def test_get_all_resource_ids(
self,
):
result = get_all_resource_ids([self.root_stack, self.nested_stack, self.nested_nested_stack])
self.assertEqual(
result,
[
ResourceIdentifier("Function1"),
ResourceIdentifier("NestedStack1/Function1"),
ResourceIdentifier("NestedStack1/NestedNestedStack1/Function2"),
],
)


class TestGetUniqueResourceIDs(TestCase):
def setUp(self) -> None:
super().setUp()
self.stacks = MagicMock()

@patch("samcli.lib.providers.provider.get_resource_ids_by_type")
def test_only_resource_ids(self, get_resource_ids_by_type_mock):
resource_ids = ["Function1", "Function2"]
resource_types = []
get_resource_ids_by_type_mock.return_value = {}
result = get_unique_resource_ids(self.stacks, resource_ids, resource_types)
get_resource_ids_by_type_mock.assert_not_called()
self.assertEqual(result, {ResourceIdentifier("Function1"), ResourceIdentifier("Function2")})

@patch("samcli.lib.providers.provider.get_resource_ids_by_type")
def test_only_resource_types(self, get_resource_ids_by_type_mock):
resource_ids = []
resource_types = ["Type1", "Type2"]
get_resource_ids_by_type_mock.return_value = {ResourceIdentifier("Function1"), ResourceIdentifier("Function2")}
result = get_unique_resource_ids(self.stacks, resource_ids, resource_types)
get_resource_ids_by_type_mock.assert_any_call(self.stacks, "Type1")
get_resource_ids_by_type_mock.assert_any_call(self.stacks, "Type2")
self.assertEqual(result, {ResourceIdentifier("Function1"), ResourceIdentifier("Function2")})

@patch("samcli.lib.providers.provider.get_resource_ids_by_type")
def test_duplicates(self, get_resource_ids_by_type_mock):
resource_ids = ["Function1", "Function2"]
resource_types = ["Type1", "Type2"]
get_resource_ids_by_type_mock.return_value = {ResourceIdentifier("Function2"), ResourceIdentifier("Function3")}
result = get_unique_resource_ids(self.stacks, resource_ids, resource_types)
get_resource_ids_by_type_mock.assert_any_call(self.stacks, "Type1")
get_resource_ids_by_type_mock.assert_any_call(self.stacks, "Type2")
self.assertEqual(
result, {ResourceIdentifier("Function1"), ResourceIdentifier("Function2"), ResourceIdentifier("Function3")}
)
Loading

0 comments on commit fd8c322

Please sign in to comment.