8000 remove CFn legacy template deployer (#11414) · localstack/localstack@97a1b95 · GitHub
[go: up one dir, main page]

Skip to content

Commit 97a1b95

Browse files
authored
remove CFn legacy template deployer (#11414)
1 parent 36f0090 commit 97a1b95

File tree

3 files changed

+7
-255
lines changed

3 files changed

+7
-255
lines changed

localstack-core/localstack/config.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,9 +1080,6 @@ def populate_edge_configuration(
10801080
# Show exceptions for CloudFormation deploy errors
10811081
CFN_VERBOSE_ERRORS = is_env_true("CFN_VERBOSE_ERRORS")
10821082

1083-
# Allow fallback to previous template deployer implementation
1084-
CFN_LEGACY_TEMPLATE_DEPLOYER = is_env_true("CFN_LEGACY_TEMPLATE_DEPLOYER")
1085-
10861083
# The CFN_STRING_REPLACEMENT_DENY_LIST env variable is a comma separated list of strings that are not allowed to be
10871084
# replaced in CloudFormation templates (e.g. AWS URLs that are usually edited by Localstack to point to itself if found
10881085
# in a CFN template). They are extracted to a list of strings if the env variable is set.
@@ -1156,7 +1153,6 @@ def use_custom_dns():
11561153
"BOTO_WAITER_MAX_ATTEMPTS",
11571154
"BUCKET_MARKER_LOCAL",
11581155
"CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES",
1159-
"CFN_LEGACY_TEMPLATE_DEPLOYER",
11601156
"CFN_PER_RESOURCE_TIMEOUT",
11611157
"CFN_STRING_REPLACEMENT_DENY_LIST",
11621158
"CFN_VERBOSE_ERRORS",

localstack-core/localstack/services/cloudformation/engine/template_deployer.py

Lines changed: 1 addition & 239 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import re
55
import traceback
66
import uuid
7-
from abc import ABC, abstractmethod
87
from typing import Optional
98

109
from botocore.exceptions import ClientError
@@ -831,7 +830,7 @@ def evaluate_resource_condition(conditions: dict[str, bool], resource: dict) ->
831830
# -----------------------
832831

833832

834-
class TemplateDeployerBase(ABC):
833+
class TemplateDeployer:
835834
def __init__(self, account_id: str, region_name: str, stack):
836835
self.stack = stack
837836
self.account_id = account_id
@@ -1338,23 +1337,6 @@ def create_resource_provider_payload(
13381337
}
13391338
return resource_provider_payload
13401339

1341-
@abstractmethod
1342-
def delete_stack(self):
1343-
pass
1344-
1345-
@abstractmethod
1346-
def do_apply_changes_in_loop(self, changes: list[ChangeConfig], stack: Stack) -> list:
1347-
pass
1348-
1349-
@classmethod
1350-
def factory(cls, *args, **kwargs):
1351-
if config.CFN_LEGACY_TEMPLATE_DEPLOYER:
1352-
return TemplateDeployerLegacy(*args, **kwargs)
1353-
else:
1354-
return TemplateDeployerV2(*args, **kwargs)
1355-
1356-
1357-
class TemplateDeployerV2(TemplateDeployerBase):
13581340
def delete_stack(self):
13591341
if not self.stack:
13601342
return
@@ -1531,226 +1513,6 @@ def do_apply_changes_in_loop(self, changes: list[ChangeConfig], stack: Stack) ->
15311513
return changes_done
15321514

15331515

1534-
class TemplateDeployerLegacy(TemplateDeployerBase):
1535-
def delete_stack(self):
1536-
if not self.stack:
1537-
return
1538-
self.stack.set_stack_status("DELETE_IN_PROGRESS")
1539-
stack_resources = list(self.stack.resources.values())
1540-
resources = {r["LogicalResourceId"]: clone_safe(r) for r in stack_resources}
1541-
1542-
# TODO: what is this doing?
1543-
for key, resource in resources.items():
1544-
resource["Properties"] = resource.get(
1545-
"Properties", clone_safe(resource)
1546-
) # TODO: why is there a fallback?
1547-
resource["ResourceType"] = get_resource_type(resource)
1548-
1549-
def _safe_lookup_is_deleted(r_id):
1550-
"""handles the case where self.stack.resource_status(..) fails for whatever reason"""
1551-
try:
1552-
return self.stack.resource_status(r_id).get("ResourceStatus") == "DELETE_COMPLETE"
1553-
except Exception:
1554-
if config.CFN_VERBOSE_ERRORS:
1555-
LOG.exception(f"failed to lookup if resource {r_id} is deleted")
1556-
return True # just an assumption
1557-
1558-
# a bit of a workaround until we have a proper dependency graph
1559-
max_cycle = 10 # 10 cycles should be a safe choice for now
1560-
for iteration_cycle in range(1, max_cycle + 1):
1561-
resources = {
1562-
r_id: r for r_id, r in resources.items() if not _safe_lookup_is_deleted(r_id)
1563-
}
1564-
if len(resources) == 0:
1565-
break
1566-
for i, (resource_id, resource) in enumerate(resources.items()):
1567-
try:
1568-
# TODO: cache condition value in resource details on deployment and use cached value here
1569-
if evaluate_resource_condition(
1570-
self.stack.resolved_conditions,
1571-
resource,
1572-
):
1573-
executor = self.create_resource_provider_executor()
1574-
resource_provider_payload = self.create_resource_provider_payload(
1575-
"Remove", logical_resource_id=resource_id
1576-
)
1577-
# TODO: check actual return value
1578-
LOG.debug(
1579-
'Handling "Remove" for resource "%s" (%s/%s) type "%s" in loop iteration %s',
1580-
resource_id,
1581-
i + 1,
1582-
len(resources),
1583-
resource["ResourceType"],
1584-
iteration_cycle,
1585-
)
1586-
resource_provider = executor.try_load_resource_provider(
1587-
get_resource_type(resource)
1588-
)
1589-
if resource_provider is not None:
1590-
event = executor.deploy_loop(
1591-
resource_provider, resource, resource_provider_payload
1592-
)
1593-
else:
1594-
event = ProgressEvent(OperationStatus.SUCCESS, resource_model={})
1595-
match event.status:
1596-
case OperationStatus.SUCCESS:
1597-
self.stack.set_resource_status(resource_id, "DELETE_COMPLETE")
1598-
case OperationStatus.PENDING:
1599-
# the resource is still being deleted, specifically the provider has
1600-
10000 # signalled that the deployment loop should skip this resource this
1601-
# time and come back to it later, likely due to unmet child
1602-
# resources still existing because we don't delete things in the
1603-
# correct order yet.
1604-
continue
1605-
case OperationStatus.FAILED:
1606-
if iteration_cycle == max_cycle:
1607-
LOG.exception(
1608-
"Last cycle failed to delete resource with id %s. Reason: %s",
1609-
resource_id,
1610-
event.message or "unknown",
1611-
)
1612-
else:
1613-
# the resource failed to delete this time, but we have more
1614-
# iterations left to complete the process
1615-
continue
1616-
case OperationStatus.IN_PROGRESS:
1617-
# the resource provider executor should not return this state, so
1618-
# this state is a programming error
1619-
raise Exception(
1620-
"Programming error: ResourceProviderExecutor cannot return IN_PROGRESS"
1621-
)
1622-
case other_status:
1623-
raise Exception(f"Use of unsupported status found: {other_status}")
1624-
1625-
except Exception as e:
1626-
if iteration_cycle == max_cycle:
1627-
LOG.exception(
1628-
"Last cycle failed to delete resource with id %s. Final exception: %s",
1629-
resource_id,
1630-
e,
1631-
)
1632-
else:
1633-
log_method = LOG.warning
1634-
if config.CFN_VERBOSE_ERRORS:
1635-
log_method = LOG.exception
1636-
log_method(
1637-
"Failed delete of resource with id %s in iteration cycle %d. Retrying in next cycle.",
1638-
resource_id,
1639-
iteration_cycle,
1640-
)
1641-
1642-
# update status
1643-
self.stack.set_stack_status("DELETE_COMPLETE")
1644-
self.stack.set_time_attribute("DeletionTime")
1645-
1646-
def do_apply_changes_in_loop(self, changes: list[ChangeConfig], stack: Stack) -> list:
1647-
# apply changes in a retry loop, to resolve resource dependencies and converge to the target state
1648-
changes_done = []
1649-
max_iters = 30
1650-
new_resources = stack.resources
1651-
1652-
# start deployment loop
1653-
for i in range(max_iters):
1654-
j = 0
1655-
updated = False
1656-
while j < len(changes):
1657-
change = changes[j]
1658-
res_change = change["ResourceChange"]
1659-
action = res_change["Action"]
1660-
is_add_or_modify = action in ["Add", "Modify"]
1661-
resource_id = res_change["LogicalResourceId"]
1662-
1663-
# TODO: do resolve_refs_recursively once here
1664-
try:
1665-
if is_add_or_modify:
1666-
resource = new_resources[resource_id]
1667-
should_deploy = self.prepare_should_deploy_change(
1668-
resource_id, change, stack, new_resources
1669-
)
1670-
LOG.debug(
1671-
'Handling "%s" for resource "%s" (%s/%s) type "%s" in loop iteration %s (should_deploy=%s)',
1672-
action,
1673-
resource_id,
1674-
j + 1,
1675-
len(changes),
1676-
res_change["ResourceType"],
1677-
i + 1,
1678-
should_deploy,
1679-
)
1680-
if not should_deploy:
1681-
del changes[j]
1682-
stack_action = get_action_name_for_resource_change(action)
1683-
stack.set_resource_status(resource_id, f"{stack_action}_COMPLETE")
1684-
continue
1685-
if not self.all_resource_dependencies_satisfied(resource):
1686-
j += 1
1687-
continue
1688-
elif action == "Remove":
1689-
should_remove = self.prepare_should_deploy_change(
1690-
resource_id, change, stack, new_resources
1691-
)
1692-
if not should_remove:
1693-
del changes[j]
1694-
continue
1695-
LOG.debug(
1696-
'Handling "%s" for resource "%s" (%s/%s) type "%s" in loop iteration %s',
1697-
action,
1698-
resource_id,
1699-
j + 1,
1700-
len(changes),
1701-
res_change["ResourceType"],
1702-
i + 1,
1703-
)
1704-
self.apply_change(change, stack=stack)
1705-
changes_done.append(change)
1706-
del changes[j]
1707-
updated = True
1708-
except DependencyNotYetSatisfied as e:
1709-
log_method = LOG.debug
1710-
if config.CFN_VERBOSE_ERRORS:
1711-
log_method = LOG.exception
1712-
log_method(
1713-
'Dependencies for "%s" not yet satisfied, retrying in next loop: %s',
1714-
resource_id,
1715-
e,
1716-
)
1717-
j += 1
1718-
except Exception as e:
1719-
status_action = {
1720-
"Add": "CREATE",
1721-
"Modify": "UPDATE",
1722-
"Dynamic": "UPDATE",
1723-
"Remove": "DELETE",
1724-
}[action]
1725-
stack.add_stack_event(
1726-
resource_id=resource_id,
1727-
physical_res_id=new_resources[resource_id].get("PhysicalResourceId"),
1728-
status=f"{status_action}_FAILED",
1729-
status_reason=str(e),
1730-
)
1731-
if config.CFN_VERBOSE_ERRORS:
1732-
LOG.exception(
1733-
f"Failed to deploy resource {resource_id}, stack deploy failed"
1734-
)
1735-
raise
1736-
if not changes:
1737-
break
1738-
if not updated:
1739-
raise Exception(
1740-
f"Resource deployment loop completed, pending resource changes: {changes}"
1741-
)
1742-
1743-
# clean up references to deleted resources in stack
1744-
deletes = [c for c in changes_done if c["ResourceChange"]["Action"] == "Remove"]
1745-
for delete in deletes:
1746-
stack.template["Resources"].pop(delete["ResourceChange"]["LogicalResourceId"], None)
1747-
1748-
# resolve outputs
1749-
stack.resolved_outputs = resolve_outputs(self.account_id, self.region_name, stack)
1750-
1751-
return changes_done
1752-
1753-
17541516
# FIXME: resolve_refs_recursively should not be needed, the resources themselves should have those values available already
17551517
def resolve_outputs(account_id: str, region_name: str, stack) -> list[dict]:
17561518
result = []

localstack-core/localstack/services/cloudformation/provider.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,7 @@ def create_stack(self, context: RequestContext, request: CreateStackInput) -> Cr
287287
stack.stack_name,
288288
len(stack.template_resources),
289289
)
290-
deployer = template_deployer.TemplateDeployerBase.factory(
291-
context.account_id, context.region, stack
292-
)
290+
deployer = template_deployer.TemplateDeployer(context.account_id, context.region, stack)
293291
try:
294292
deployer.deploy_stack()
295293
except Exception as e:
@@ -315,9 +313,7 @@ def delete_stack(
315313
if not stack:
316314
# aws will silently ignore invalid stack names - we should do the same
317315
return
318-
deployer = template_deployer.TemplateDeployerBase.factory(
319-
context.account_id, context.region, stack
320-
)
316+
deployer = template_deployer.TemplateDeployer(context.account_id, context.region, stack)
321317
deployer.delete_stack()
322318

323319
@handler("UpdateStack", expand=False)
@@ -395,9 +391,7 @@ def update_stack(
395391
# update the template
396392
stack.template_original = template
397393

398-
deployer = template_deployer.TemplateDeployerBase.factory(
399-
context.account_id, context.region, stack
400-
)
394+
deployer = template_deployer.TemplateDeployer(context.account_id, context.region, stack)
401395
# TODO: there shouldn't be a "new" stack on update
402396
new_stack = Stack(
403397
context.account_id, context.region, request, template, request["TemplateBody"]
@@ -745,7 +739,7 @@ def create_change_set(
745739
except NoResourceInStack as e:
746740
raise ValidationError(str(e)) from e
747741

748-
deployer = template_deployer.TemplateDeployerBase.factory(
742+
deployer = template_deployer.TemplateDeployer(
749743
context.account_id, context.region, change_set
750744
)
751745
changes = deployer.construct_changes(
@@ -872,7 +866,7 @@ def execute_change_set(
872866
stack_name,
873867
len(change_set.template_resources),
874868
)
875-
deployer = template_deployer.TemplateDeployerBase.factory(
869+
deployer = template_deployer.TemplateDeployer(
876870
context.account_id, context.region, change_set.stack
877871
)
878872
try:
@@ -1137,7 +1131,7 @@ def delete_stack_set(
11371131
# TODO: add a check for remaining stack instances
11381132

11391133
for instance in stack_set[0].stack_instances:
1140-
deployer = template_deployer.TemplateDeployerBase.factory(
1134+
deployer = template_deployer.TemplateDeployer(
11411135
context.account_id, context.region, instance.stack
11421136
)
11431137
deployer.delete_stack()

0 commit comments

Comments
 (0)
0