• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

localstack / localstack / 0e75e353-4494-4b65-a715-a78cf84b0b94

02 Apr 2025 01:40PM UTC coverage: 86.857% (+0.05%) from 86.807%
0e75e353-4494-4b65-a715-a78cf84b0b94

push

circleci

web-flow
CFn: WIP POC v2 executor (#12396)

Co-authored-by: Marco Edoardo Palma <64580864+MEPalma@users.noreply.github.com>

36 of 112 new or added lines in 6 files covered. (32.14%)

144 existing lines in 9 files now uncovered.

63556 of 73173 relevant lines covered (86.86%)

0.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.0
/localstack-core/localstack/services/cloudformation/engine/v2/change_set_model_executor.py
1
import logging
1✔
2
import uuid
1✔
3
from typing import Final
1✔
4

5
from localstack.aws.api.cloudformation import ChangeAction
1✔
6
from localstack.constants import INTERNAL_AWS_SECRET_ACCESS_KEY
1✔
7
from localstack.services.cloudformation.engine.v2.change_set_model import (
1✔
8
    NodeIntrinsicFunction,
9
    NodeResource,
10
    NodeTemplate,
11
    TerminalValue,
12
)
13
from localstack.services.cloudformation.engine.v2.change_set_model_describer import (
1✔
14
    ChangeSetModelDescriber,
15
    DescribeUnit,
16
)
17
from localstack.services.cloudformation.resource_provider import (
1✔
18
    Credentials,
19
    OperationStatus,
20
    ProgressEvent,
21
    ResourceProviderExecutor,
22
    ResourceProviderPayload,
23
    get_resource_type,
24
)
25

26
LOG = logging.getLogger(__name__)
1✔
27

28

29
class ChangeSetModelExecutor(ChangeSetModelDescriber):
1✔
30
    account_id: Final[str]
1✔
31
    region: Final[str]
1✔
32

33
    def __init__(
1✔
34
        self,
35
        node_template: NodeTemplate,
36
        account_id: str,
37
        region: str,
38
        stack_name: str,
39
        stack_id: str,
40
    ):
NEW
41
        super().__init__(node_template)
×
NEW
42
        self.account_id = account_id
×
NEW
43
        self.region = region
×
NEW
44
        self.stack_name = stack_name
×
NEW
45
        self.stack_id = stack_id
×
NEW
46
        self.resources = {}
×
47

48
    def execute(self) -> dict:
1✔
NEW
49
        self.visit(self._node_template)
×
NEW
50
        return self.resources
×
51

52
    def visit_node_resource(self, node_resource: NodeResource) -> DescribeUnit:
1✔
NEW
53
        resource_provider_executor = ResourceProviderExecutor(
×
54
            stack_name=self.stack_name, stack_id=self.stack_id
55
        )
56

57
        # TODO: investigate effects on type changes
NEW
58
        properties_describe_unit = self.visit_node_properties(node_resource.properties)
×
NEW
59
        LOG.info("SRW: describe unit: %s", properties_describe_unit)
×
60

NEW
61
        action = node_resource.change_type.to_action()
×
NEW
62
        if action is None:
×
NEW
63
            raise RuntimeError(
×
64
                f"Action should always be present, got change type: {node_resource.change_type}"
65
            )
66

67
        # TODO
NEW
68
        resource_type = get_resource_type({"Type": "AWS::SSM::Parameter"})
×
NEW
69
        payload = self.create_resource_provider_payload(
×
70
            properties_describe_unit,
71
            action,
72
            node_resource.name,
73
            resource_type,
74
        )
NEW
75
        resource_provider = resource_provider_executor.try_load_resource_provider(resource_type)
×
76

NEW
77
        extra_resource_properties = {}
×
NEW
78
        if resource_provider is not None:
×
79
            # TODO: stack events
NEW
80
            event = resource_provider_executor.deploy_loop(
×
81
                resource_provider, extra_resource_properties, payload
82
            )
83
        else:
NEW
84
            event = ProgressEvent(OperationStatus.SUCCESS, resource_model={})
×
85

NEW
86
        self.resources.setdefault(node_resource.name, {"Properties": {}})
×
NEW
87
        match event.status:
×
NEW
88
            case OperationStatus.SUCCESS:
×
89
                # merge the resources state with the external state
90
                # TODO: this is likely a duplicate of updating from extra_resource_properties
NEW
91
                self.resources[node_resource.name]["Properties"].update(event.resource_model)
×
NEW
92
                self.resources[node_resource.name].update(extra_resource_properties)
×
93
                # XXX for legacy delete_stack compatibility
NEW
94
                self.resources[node_resource.name]["LogicalResourceId"] = node_resource.name
×
NEW
95
                self.resources[node_resource.name]["Type"] = resource_type
×
NEW
96
            case any:
×
97
                raise NotImplementedError(f"Event status '{any}' not handled")
98

NEW
99
        return DescribeUnit(before_context=None, after_context={})
×
100

101
    def visit_node_intrinsic_function_fn_get_att(
1✔
102
        self, node_intrinsic_function: NodeIntrinsicFunction
103
    ) -> DescribeUnit:
NEW
104
        arguments_unit = self.visit(node_intrinsic_function.arguments)
×
NEW
105
        before_arguments_list = arguments_unit.before_context
×
NEW
106
        after_arguments_list = arguments_unit.after_context
×
NEW
107
        if before_arguments_list:
×
NEW
108
            logical_name_of_resource = before_arguments_list[0]
×
NEW
109
            attribute_name = before_arguments_list[1]
×
NEW
110
            before_node_resource = self._get_node_resource_for(
×
111
                resource_name=logical_name_of_resource, node_template=self._node_template
112
            )
NEW
113
            node_property: TerminalValue = self._get_node_property_for(
×
114
                property_name=attribute_name, node_resource=before_node_resource
115
            )
NEW
116
            before_context = self.visit(node_property.value).before_context
×
117
        else:
NEW
118
            before_context = None
×
119

NEW
120
        if after_arguments_list:
×
NEW
121
            logical_name_of_resource = after_arguments_list[0]
×
NEW
122
            attribute_name = after_arguments_list[1]
×
NEW
123
            after_node_resource = self._get_node_resource_for(
×
124
                resource_name=logical_name_of_resource, node_template=self._node_template
125
            )
NEW
126
            node_property: TerminalValue = self._get_node_property_for(
×
127
                property_name=attribute_name, node_resource=after_node_resource
128
            )
NEW
129
            after_context = self.visit(node_property.value).after_context
×
130
        else:
NEW
131
            after_context = None
×
132

NEW
133
        return DescribeUnit(before_context=before_context, after_context=after_context)
×
134

135
    def create_resource_provider_payload(
1✔
136
        self,
137
        describe_unit: DescribeUnit,
138
        action: ChangeAction,
139
        logical_resource_id: str,
140
        resource_type: str,
141
    ) -> ResourceProviderPayload:
142
        # FIXME: use proper credentials
NEW
143
        creds: Credentials = {
×
144
            "accessKeyId": self.account_id,
145
            "secretAccessKey": INTERNAL_AWS_SECRET_ACCESS_KEY,
146
            "sessionToken": "",
147
        }
NEW
148
        resource_provider_payload: ResourceProviderPayload = {
×
149
            "awsAccountId": self.account_id,
150
            "callbackContext": {},
151
            "stackId": self.stack_name,
152
            "resourceType": resource_type,
153
            "resourceTypeVersion": "000000",
154
            # TODO: not actually a UUID
155
            "bearerToken": str(uuid.uuid4()),
156
            "region": self.region,
157
            "action": str(action),
158
            "requestData": {
159
                "logicalResourceId": logical_resource_id,
160
                "resourceProperties": describe_unit.after_context["Properties"],
161
                "previousResourceProperties": describe_unit.before_context["Properties"],
162
                "callerCredentials": creds,
163
                "providerCredentials": creds,
164
                "systemTags": {},
165
                "previousSystemTags": {},
166
                "stackTags": {},
167
                "previousStackTags": {},
168
            },
169
        }
NEW
170
        return resource_provider_payload
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc