Skip to content

Commit dfb4390

Browse files
authored
Dynamically determine Account ID during transform (#3749)
* Dynamically determine Account ID during transform
1 parent 07652d4 commit dfb4390

File tree

2 files changed

+151
-3
lines changed

2 files changed

+151
-3
lines changed

src/cfnlint/template/transforms/_language_extensions.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
_SCALAR_TYPES = (str, int, float, bool)
3030

31+
_ACCOUNT_ID = None
32+
3133

3234
class _ResolveError(Exception):
3335
def __init__(self, message: str, key: Any) -> None:
@@ -371,7 +373,12 @@ def value(
371373
for k, v in mapping.items():
372374
if isinstance(v, dict):
373375
if t_map[2].value(cfn, params, only_params) in v:
376+
if isinstance(t_map[1], _ForEachValueRef):
377+
if t_map[1]._ref._value == "AWS::AccountId":
378+
global _ACCOUNT_ID
379+
_ACCOUNT_ID = k
374380
t_map[1] = _ForEachValue.create(k)
381+
break
375382
except _ResolveError:
376383
pass
377384

@@ -439,7 +446,9 @@ def value(
439446
return region
440447

441448
if v == "AWS::AccountId":
442-
return account_id
449+
if _ACCOUNT_ID is None:
450+
raise _ResolveError("Can't resolve Fn::Ref", self._obj)
451+
return _ACCOUNT_ID
443452

444453
if v == "AWS::NotificationARNs":
445454
return [f"arn:{partition}:sns:{region}:{account_id}:notification"]

test/unit/module/template/transforms/test_language_extensions.py

+141-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
"""
55

66
from copy import deepcopy
7-
from unittest import TestCase
7+
from unittest import TestCase, mock
88

9+
import cfnlint.template.transforms._language_extensions
910
from cfnlint.decode import convert_dict
1011
from cfnlint.template import Template
1112
from cfnlint.template.transforms._language_extensions import (
@@ -122,7 +123,15 @@ def test_ref(self):
122123
self.assertEqual(fe.value(self.cfn), "us-west-2")
123124

124125
fe = _ForEachValue.create({"Ref": "AWS::AccountId"})
125-
self.assertEqual(fe.value(self.cfn), "123456789012")
126+
with self.assertRaises(_ResolveError):
127+
fe.value(self.cfn)
128+
129+
with mock.patch(
130+
"cfnlint.template.transforms._language_extensions._ACCOUNT_ID",
131+
"123456789012",
132+
):
133+
fe = _ForEachValue.create({"Ref": "AWS::AccountId"})
134+
self.assertEqual(fe.value(self.cfn), "123456789012")
126135

127136
fe = _ForEachValue.create({"Ref": "AWS::NotificationARNs"})
128137
self.assertListEqual(
@@ -357,6 +366,31 @@ def test_two_mappings(self):
357366
with self.assertRaises(_ResolveError):
358367
fe.value(self.cfn)
359368

369+
def test_account_id(self):
370+
371+
cfnlint.template.transforms._language_extensions._ACCOUNT_ID = None
372+
373+
with mock.patch(
374+
"cfnlint.template.transforms._language_extensions._ACCOUNT_ID", None
375+
):
376+
self.assertIsNone(
377+
cfnlint.template.transforms._language_extensions._ACCOUNT_ID
378+
)
379+
fe = _ForEachValueFnFindInMap(
380+
"a",
381+
[
382+
"Bucket",
383+
{"Ref": "AWS::AccountId"},
384+
"Names",
385+
],
386+
)
387+
self.assertListEqual(fe.value(self.cfn), ["foo", "bar"])
388+
389+
self.assertEqual(
390+
cfnlint.template.transforms._language_extensions._ACCOUNT_ID,
391+
"Production",
392+
)
393+
360394

361395
class TestTransform(TestCase):
362396
def setUp(self) -> None:
@@ -832,3 +866,108 @@ def nested_set(dic, keys, value):
832866
if isinstance(key, int):
833867
dic = dic[key]
834868
dic[keys[-1]] = value
869+
870+
871+
class TestTransformValueAccountId(TestCase):
872+
def setUp(self) -> None:
873+
self.template_obj = convert_dict(
874+
{
875+
"Transform": ["AWS::LanguageExtensions"],
876+
"Mappings": {
877+
"Accounts": {
878+
"111111111111": {"AppName": ["A", "B"]},
879+
"222222222222": {"AppName": ["C", "D"]},
880+
},
881+
},
882+
"Resources": {
883+
"Fn::ForEach::Regions": [
884+
"AppName",
885+
{
886+
"Fn::FindInMap": [
887+
"Accounts",
888+
{"Ref": "AWS::AccountId"},
889+
"AppName",
890+
]
891+
},
892+
{
893+
"${AppName}Role": {
894+
"Type": "AWS::IAM::Role",
895+
"Properties": {
896+
"RoleName": {"Ref": "AppName"},
897+
"AssumeRolePolicyDocument": {
898+
"Version": "2012-10-17",
899+
"Statement": [
900+
{
901+
"Effect": "Allow",
902+
"Principal": {
903+
"Service": ["ec2.amazonaws.com"]
904+
},
905+
"Action": ["sts:AssumeRole"],
906+
}
907+
],
908+
},
909+
"Path": "/",
910+
},
911+
}
912+
},
913+
],
914+
},
915+
}
916+
)
917+
918+
self.result = {
919+
"Mappings": {
920+
"Accounts": {
921+
"111111111111": {"AppName": ["A", "B"]},
922+
"222222222222": {"AppName": ["C", "D"]},
923+
},
924+
},
925+
"Resources": {
926+
"ARole": {
927+
"Properties": {
928+
"AssumeRolePolicyDocument": {
929+
"Statement": [
930+
{
931+
"Action": ["sts:AssumeRole"],
932+
"Effect": "Allow",
933+
"Principal": {"Service": ["ec2.amazonaws.com"]},
934+
}
935+
],
936+
"Version": "2012-10-17",
937+
},
938+
"Path": "/",
939+
"RoleName": "A",
940+
},
941+
"Type": "AWS::IAM::Role",
942+
},
943+
"BRole": {
944+
"Properties": {
945+
"AssumeRolePolicyDocument": {
946+
"Statement": [
947+
{
948+
"Action": ["sts:AssumeRole"],
949+
"Effect": "Allow",
950+
"Principal": {"Service": ["ec2.amazonaws.com"]},
951+
}
952+
],
953+
"Version": "2012-10-17",
954+
},
955+
"Path": "/",
956+
"RoleName": "B",
957+
},
958+
"Type": "AWS::IAM::Role",
959+
},
960+
},
961+
"Transform": ["AWS::LanguageExtensions"],
962+
}
963+
964+
def test_transform(self):
965+
self.maxDiff = None
966+
cfn = Template(filename="", template=self.template_obj, regions=["us-east-1"])
967+
matches, template = language_extension(cfn)
968+
self.assertListEqual(matches, [])
969+
self.assertDictEqual(
970+
template,
971+
self.result,
972+
template,
973+
)

0 commit comments

Comments
 (0)