-
Notifications
You must be signed in to change notification settings - Fork 0
CFn: Improve stack set support #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3f40cdc
12e1a88
ec8b9d2
ba98527
205cd64
3f6054c
0fd6b8b
a2f88f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,12 @@ | ||
import logging | ||
from typing import Optional, TypedDict | ||
|
||
from localstack.aws.api.cloudformation import Capability, ChangeSetType, Parameter | ||
from localstack.aws.api.cloudformation import ( | ||
Capability, | ||
ChangeSetType, | ||
CreateStackSetInput, | ||
Parameter, | ||
) | ||
from localstack.services.cloudformation.engine.parameters import ( | ||
StackParameter, | ||
convert_stack_parameters_to_list, | ||
|
@@ -17,30 +22,62 @@ | |
LOG = logging.getLogger(__name__) | ||
|
||
|
||
class StackSet: | ||
"""A stack set contains multiple stack instances.""" | ||
class StackInstance: | ||
"""A stack instance belongs to a stack set and is specific to a region / account ID.""" | ||
|
||
# FIXME: confusing name. metadata is the complete incoming request object | ||
def __init__(self, metadata: dict): | ||
self.metadata = metadata | ||
# reference to the deployed stack belonging to this stack instance | ||
self.stack = None | ||
|
||
@property | ||
def account(self) -> str: | ||
return self.metadata["Account"] | ||
|
||
@property | ||
def region(self) -> str: | ||
return self.metadata["Region"] | ||
|
||
|
||
class StackSet: | ||
"""A stack set contains multiple stack instances.""" | ||
|
||
def __init__(self, request: CreateStackSetInput): | ||
self.request = request | ||
# list of stack instances | ||
self.stack_instances = [] | ||
self.stack_instances: list[StackInstance] = [] | ||
# maps operation ID to stack set operation details | ||
self.operations = {} | ||
|
||
# compatibility with old API | ||
@property | ||
def metadata(self) -> CreateStackSetInput: | ||
return self.request | ||
|
||
@property | ||
def stack_set_name(self): | ||
return self.metadata.get("StackSetName") | ||
return self.request.get("StackSetName") | ||
|
||
@property | ||
def template_url(self) -> str | None: | ||
return self.request.get("TemplateURL") | ||
|
||
class StackInstance: | ||
"""A stack instance belongs to a stack set and is specific to a region / account ID.""" | ||
@property | ||
def template_body(self) -> str | None: | ||
return self.request.get("TemplateBody") | ||
|
||
# FIXME: confusing name. metadata is the complete incoming request object | ||
def __init__(self, metadata: dict): | ||
self.metadata = metadata | ||
# reference to the deployed stack belonging to this stack instance | ||
self.stack = None | ||
def get_template(self): | ||
if body := self.template_body: | ||
return body | ||
|
||
raise NotImplementedError("template URL") | ||
|
||
def get_instance(self, account: str, region: str) -> StackInstance | None: | ||
for instance in self.stack_instances: | ||
if instance.metadata["Account"] == account and instance.metadata["Region"] == region: | ||
return instance | ||
return None | ||
Comment on lines
+76
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider using a dictionary for faster lookup of stack instances instead of iterating through a list. |
||
|
||
|
||
class StackMetadata(TypedDict): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,6 +61,14 @@ def find_stack(account_id: str, region_name: str, stack_name: str) -> Stack | No | |
)[0] | ||
|
||
|
||
def find_stack_set(account_id: str, region_name: str, stack_set_name: str) -> StackSet | None: | ||
state = get_cloudformation_store(account_id, region_name) | ||
for name, stack_set in state.stack_sets.items(): | ||
# TODO: stack set id? | ||
if stack_set_name in [name, stack_set.stack_set_name]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: This comparison might lead to unexpected matches if stack_set_name is a substring of name or stack_set_name |
||
return stack_set | ||
Comment on lines
+64
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider adding a docstring to explain the function's purpose and parameters |
||
|
||
|
||
def find_stack_by_id(account_id: str, region_name: str, stack_id: str) -> Stack | None: | ||
""" | ||
Find the stack by id. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,7 @@ | |
) | ||
|
||
import cachetools | ||
import jsonpath_ng | ||
|
||
LOG = logging.getLogger(__name__) | ||
|
||
|
@@ -533,3 +534,15 @@ def is_comma_delimited_list(string: str, item_regex: Optional[str] = None) -> bo | |
if pattern.match(string) is None: | ||
return False | ||
return True | ||
|
||
|
||
def convert_in_place_at_jsonpath(params: dict, jsonpath: str, conversion_fn: Callable[[Any], Any]): | ||
""" | ||
Invokes a conversion function on a dictionary nested entry at a specific jsonpath with `conversion_fn` | ||
""" | ||
jp = jsonpath_ng.parse(jsonpath) | ||
old_value = jp.find(params)[0].value | ||
if not old_value: | ||
return | ||
Comment on lines
+545
to
+546
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: This check might skip valid falsy values (e.g., False, 0). Consider using 'is None' instead |
||
new_value = conversion_fn(old_value) | ||
jp.update(params, new_value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Template URL handling is not implemented. This could lead to issues if users try to use template URLs.