Skip to content

Commit 386d241

Browse files
Merge pull request #372 from iriusrisk/release/1.24.0
Release/1.24.0 to main
2 parents 651909b + 6270954 commit 386d241

File tree

12 files changed

+250
-25
lines changed

12 files changed

+250
-25
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
'dependency-injector==4.41.0',
3636
'google-re2==1.0',
3737
'xmlschema==2.5.0',
38+
'word2number==1.1',
3839
# Do not upgrade pygraphviz unless security issues because it is heavily dependent on the underlying OS
3940
'pygraphviz==1.10'
4041
],

sl_util/sl_util/str_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import random
22
import uuid
3+
from word2number import w2n
34

45

56
def deterministic_uuid(source):
@@ -10,3 +11,13 @@ def deterministic_uuid(source):
1011

1112
def get_bytes(s: str, encoding='utf-8') -> bytes:
1213
return bytes(s, encoding)
14+
15+
16+
def to_number(input, default_value: int = 0) -> int:
17+
try:
18+
return int(input)
19+
except ValueError:
20+
try:
21+
return w2n.word_to_num(input)
22+
except ValueError:
23+
return default_value

sl_util/tests/unit/test_str_utils.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from pytest import mark
2-
3-
from sl_util.sl_util.str_utils import deterministic_uuid
1+
from pytest import mark, param
2+
import random
3+
from unittest.mock import patch
4+
from sl_util.sl_util.str_utils import deterministic_uuid, to_number
45

56

67
class TestStrUtils:
@@ -28,3 +29,50 @@ def test_deterministic_uuid_without_source(self, source):
2829
uuid2 = deterministic_uuid(source)
2930
# Then we obtain two different values
3031
assert uuid1 != uuid2
32+
33+
@mark.parametrize('source', [
34+
param(random.randint(0, 100)),
35+
param(str(random.randint(0, 100)))
36+
])
37+
def test_to_number(self, source: any):
38+
# GIVEN a random integer
39+
40+
# WHEN it is transformed to a number
41+
result = to_number(source)
42+
43+
# THEN we obtain the original number
44+
assert result == int(source)
45+
46+
@patch('sl_util.sl_util.str_utils.w2n.word_to_num', return_value=2)
47+
def test_text_to_number(self, mocked_word_to_otm):
48+
# GIVEN a text number
49+
source = 'two'
50+
51+
# WHEN it is transformed to a number
52+
result = to_number(source)
53+
54+
# THEN we obtain the number
55+
assert result == 2
56+
57+
def test_unknown_to_number(self):
58+
# GIVEN an unkown
59+
source = 'unknown'
60+
61+
# AND a default value
62+
default_value = 5
63+
64+
# WHEN it is transformed to a number
65+
result = to_number(source, default_value)
66+
67+
# THEN we obtain the default value
68+
assert result == 5
69+
70+
@mark.parametrize('source', ['sandbox', ''])
71+
def test_number_conversions_to_alphanumeric(self, source):
72+
# Given the source
73+
# when passed an alphanumeric to function
74+
number1 = to_number(source)
75+
# when passed an empty string to function
76+
number2 = to_number(source)
77+
# Then we obtain default value 0
78+
assert number1 == number2 == 0

slp_tfplan/slp_tfplan/load/tfplan_to_resource_dict.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
from typing import Dict, List
22

33
import sl_util.sl_util.secure_regex as re
4+
from sl_util.sl_util.str_utils import to_number
45

56

67
def is_not_cloned_resource(resource: Dict) -> bool:
7-
return 'index' not in resource or resource['index'] == '0' or resource['index'] == 0 or resource['index'] == 'zero'
8+
return to_number(resource['index']) == 0 if 'index' in resource else True
89

910

1011
def get_resource_id(resource: Dict) -> str:
11-
return parse_address(resource['address']) \
12-
if 'index' in resource \
13-
else resource['address']
12+
return parse_address(resource['address']) if 'address' in resource else None
1413

1514

1615
def get_resource_name(resource: Dict, parent: str) -> str:
1716
return resource['name'] if not parent else f'{parent}.{resource["name"]}'
1817

1918

2019
def get_module_address(module: Dict, parent: str) -> str:
21-
if 'address' in module:
22-
module_address = parse_address(module['address'])
23-
return f'{parent}.{module_address}' if parent else module_address
20+
return parse_address(module['address']) if 'address' in module else parent
2421

2522

2623
def parse_address(address: str) -> str:

slp_tfplan/slp_tfplan/parse/tfplan_parser.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from networkx import DiGraph
44

5+
from sl_util.sl_util.iterations_utils import remove_duplicates
56
from slp_base import ProviderParser, OTMBuildingError
67
from slp_tfplan.slp_tfplan.load.launch_templates_loader import LaunchTemplatesLoader
78
from slp_tfplan.slp_tfplan.load.security_groups_loader import SecurityGroupsLoader
@@ -46,6 +47,7 @@ def build_otm(self):
4647
self.__calculate_dataflows()
4748
self.__calculate_attack_surface()
4849
self.__calculate_singletons()
50+
self.__remove_duplicates()
4951

5052
except Exception as e:
5153
logger.error(f'{e}')
@@ -77,3 +79,6 @@ def __calculate_attack_surface(self):
7779

7880
def __calculate_singletons(self):
7981
SingletonTransformer(self.otm).transform()
82+
83+
def __remove_duplicates(self):
84+
self.otm.components = remove_duplicates(self.otm.components)

slp_tfplan/tests/unit/load/test_tfplan_loader.py

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
from slp_tfplan.slp_tfplan.load.tfplan_loader import TFPlanLoader
1313
from slp_tfplan.tests.resources import test_resource_paths
1414
from slp_tfplan.tests.util.asserts import assert_resource_values
15-
from slp_tfplan.tests.util.builders import build_tfplan, generate_resources, generate_child_modules
15+
from slp_tfplan.tests.util.builders import build_tfplan, generate_resources, generate_child_modules, \
16+
generate_child_modules_instances
1617

1718
INVALID_YAML = test_resource_paths.invalid_yaml
1819
TF_FILE_YAML_EXCEPTION = JSONDecodeError('HLC2 cannot be processed as JSON', doc='sample-doc', pos=0)
@@ -73,7 +74,7 @@ def test_load_no_modules(self, yaml_mock):
7374

7475
for i, resource in enumerate(resources):
7576
i += 1
76-
assert resource['resource_id'] == f'r{i}-addr'
77+
assert resource['resource_id'] == f'r{i}-type.r{i}-name'
7778
assert resource['resource_type'] == f'r{i}-type'
7879
assert resource['resource_name'] == f'r{i}-name'
7980

@@ -102,7 +103,7 @@ def test_load_only_modules(self, yaml_mock):
102103
for child_index in range(1, 3):
103104
resource = resources[resource_index]
104105

105-
assert resource['resource_id'] == f'r{child_index}-addr'
106+
assert resource['resource_id'] == f'r{child_index}-type.r{child_index}-name'
106107
assert resource['resource_type'] == f'r{child_index}-type'
107108
assert resource['resource_name'] == f'{module_address}.r{child_index}-name'
108109

@@ -130,9 +131,9 @@ def test_load_nested_modules(self, yaml_mock):
130131
# AND resource_id, resource_type, resource_name and resource_properties are right
131132
assert len(resources) == 1
132133

133-
assert resource['resource_id'] == 'r1-addr'
134+
assert resource['resource_id'] == 'r1-type.r1-name'
134135
assert resource['resource_type'] == 'r1-type'
135-
assert resource['resource_name'] == 'cm1-addr.cm1-addr.r1-name'
136+
assert resource['resource_name'] == 'cm1-addr.r1-name'
136137

137138
assert_resource_values(resource['resource_values'])
138139

@@ -155,15 +156,15 @@ def test_load_complex_structure(self, yaml_mock):
155156
# AND resource_type, resource_name and resource_properties from top level are right
156157
resource = resources[0]
157158

158-
assert resource['resource_id'] == 'r1-addr'
159+
assert resource['resource_id'] == 'r1-type.r1-name'
159160
assert resource['resource_type'] == 'r1-type'
160161
assert resource['resource_name'] == 'r1-name'
161162

162163
assert_resource_values(resource['resource_values'])
163164

164165
# AND resource_type, resource_name and resource_properties from child modules are right
165166
resource = resources[1]
166-
assert resource['resource_id'] == 'r1-addr'
167+
assert resource['resource_id'] == 'r1-type.r1-name'
167168
assert resource['resource_type'] == 'r1-type'
168169
assert resource['resource_name'] == 'cm1-addr.r1-name'
169170

@@ -193,7 +194,7 @@ def test_load_resources_same_name(self, yaml_mock):
193194
assert len(resources) == 1
194195

195196
# AND The duplicated resource is unified and the index is no present in name or id
196-
assert resources[0]['resource_id'] == 'r1-addr'
197+
assert resources[0]['resource_id'] == 'r1-type.r1-name'
197198
assert resources[0]['resource_name'] == 'cm1-addr.r1-name'
198199

199200
@patch('json.loads')
@@ -230,9 +231,85 @@ def test_load_modules_same_name(self, yaml_mock):
230231
assert len(resources) == 1
231232

232233
# AND The duplicated resource is unified and the index is not present in name or id
233-
assert resources[0]['resource_id'] == 'cm1-addr.r1-addr'
234+
assert resources[0]['resource_id'] == 'cm1-addr.r1-type.r1-name'
235+
assert resources[0]['resource_name'] == 'cm1-addr.r1-name'
236+
237+
@patch('json.loads')
238+
def test_load_modules_instances(self, yaml_mock):
239+
# GIVEN a valid plain Terraform Plan file with only modules
240+
yaml_mock.side_effect = [build_tfplan(
241+
child_modules=generate_child_modules_instances(module_count=2, resource_count=2))]
242+
243+
# WHEN TFPlanLoader::load is invoked
244+
tfplan_loader = TFPlanLoader(sources=[b'MOCKED', b'MOCKED'])
245+
tfplan_loader.load()
246+
247+
# THEN TF contents are loaded in TfplanLoader.terraform
248+
assert tfplan_loader.terraform
249+
resources = tfplan_loader.terraform['resource']
250+
assert len(resources) == 2
251+
252+
# AND resource_id, resource_type, resource_name and resource_properties are right
253+
resource_index = 0
254+
for _ in range(1, 2):
255+
256+
module_address = 'cm-addr'
257+
258+
for child_index in range(1, 3):
259+
resource = resources[resource_index]
260+
261+
assert (resource['resource_id'] == f'{module_address}.r{child_index}-type.r{child_index}-name')
262+
assert resource['resource_type'] == f'r{child_index}-type'
263+
assert resource['resource_name'] == f'{module_address}.r{child_index}-name'
264+
265+
assert_resource_values(resource['resource_values'])
266+
267+
resource_index += 1
268+
269+
@patch('json.loads')
270+
def test_load_modules_mixed(self, yaml_mock):
271+
# GIVEN a valid plain Terraform Plan file with two instances of a module and two resources each
272+
mixed_modules = generate_child_modules(module_count=1, resource_count=1)
273+
module_instances = generate_child_modules_instances(module_count=2, resource_count=2)
274+
mixed_modules.append(module_instances[0])
275+
mixed_modules.append(module_instances[1])
276+
277+
tfplan = build_tfplan(child_modules=mixed_modules)
278+
279+
# GIVEN a valid plain Terraform Plan file with only modules
280+
yaml_mock.side_effect = [tfplan]
281+
282+
# WHEN TFPlanLoader::load is invoked
283+
tfplan_loader = TFPlanLoader(sources=[b'MOCKED', b'MOCKED'])
284+
tfplan_loader.load()
285+
286+
# THEN TF contents are loaded in TfplanLoader.terraform
287+
assert tfplan_loader.terraform
288+
resources = tfplan_loader.terraform['resource']
289+
assert len(resources) == 3
290+
291+
# AND resource_id, resource_type, resource_name and resource_properties are right
292+
293+
assert resources[0]['resource_id'] == 'r1-type.r1-name'
234294
assert resources[0]['resource_name'] == 'cm1-addr.r1-name'
235295

296+
resource_index = 1
297+
298+
for _ in range(1, 2):
299+
300+
module_address = 'cm-addr'
301+
302+
for child_index in range(1, 2):
303+
resource = resources[resource_index]
304+
305+
assert (resource['resource_id'] == f'{module_address}.r{child_index}-type.r{child_index}-name')
306+
assert resource['resource_type'] == f'r{child_index}-type'
307+
assert resource['resource_name'] == f'{module_address}.r{child_index}-name'
308+
309+
assert_resource_values(resource['resource_values'])
310+
311+
resource_index += 1
312+
236313
@patch('json.loads')
237314
def test_load_no_resources(self, yaml_mock):
238315
# GIVEN a valid Terraform Plan file with no resources

slp_tfplan/tests/util/builders.py

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@ def build_security_group_mock(id: str,
107107

108108
def build_security_group_cidr_mock(cidr_blocks: List[str], description: str = None, from_port: int = None,
109109
to_port: int = None, protocol: str = None):
110-
return Mock(cidr_blocks=cidr_blocks, description=description, type=SecurityGroupCIDRType.INGRESS, from_port=from_port, to_port=to_port,
110+
return Mock(cidr_blocks=cidr_blocks, description=description, type=SecurityGroupCIDRType.INGRESS,
111+
from_port=from_port, to_port=to_port,
111112
protocol=protocol)
112113

113114

@@ -142,11 +143,14 @@ def build_tfplan(resources: List[Dict] = None, child_modules: List[Dict] = None)
142143
def generate_resources(resource_count: int, module_child: bool = False) -> List[Dict]:
143144
resources = []
144145
for i in range(1, resource_count + 1):
146+
resource_name = f'r{i}-name'
147+
resource_type = f'r{i}-type'
148+
145149
resource = {
146-
'address': f'r{i}-addr',
150+
'address': f'{resource_type}.{resource_name}',
147151
'mode': 'managed',
148-
'type': f'r{i}-type',
149-
'name': f'r{i}-name',
152+
'type': resource_type,
153+
'name': resource_name,
150154
'provider_name': 'registry.terraform.io/hashicorp/aws',
151155
'schema_version': 0,
152156
'values': {
@@ -167,6 +171,35 @@ def generate_resources(resource_count: int, module_child: bool = False) -> List[
167171
return resources
168172

169173

174+
def generate_resources_for_module(resource_count: int, module_name: str) -> List[Dict]:
175+
resources = []
176+
for i in range(1, resource_count + 1):
177+
resource_name = f'r{i}-name'
178+
resource_type = f'r{i}-type'
179+
resource_address = f'{module_name}.{resource_type}.{resource_name}'
180+
181+
resource = {
182+
'address': resource_address,
183+
'mode': 'managed',
184+
'type': resource_type,
185+
'name': resource_name,
186+
'provider_name': 'registry.terraform.io/hashicorp/aws',
187+
'schema_version': 0,
188+
'values': {
189+
'val1': 'value1',
190+
'val2': 'value2',
191+
},
192+
'sensitive_values': {
193+
'senval1': 'value1',
194+
'senval2': 'value2',
195+
}
196+
}
197+
198+
resources.append(resource)
199+
200+
return resources
201+
202+
170203
def generate_child_modules(module_count: int,
171204
child_modules: List[Dict] = None,
172205
resource_count: int = None) -> List[Dict]:
@@ -187,6 +220,27 @@ def generate_child_modules(module_count: int,
187220
return modules
188221

189222

223+
def generate_child_modules_instances(module_count: int,
224+
child_modules: List[Dict] = None,
225+
resource_count: int = None) -> List[Dict]:
226+
modules = []
227+
for i in range(1, module_count + 1):
228+
module_name = f'cm-addr[instance-{chr(96 + i)}]'
229+
module = {
230+
'address': module_name,
231+
}
232+
233+
if child_modules:
234+
module['child_modules'] = child_modules
235+
236+
if resource_count:
237+
module['resources'] = generate_resources_for_module(resource_count, module_name)
238+
239+
modules.append(module)
240+
241+
return modules
242+
243+
190244
###########
191245
# TFGRAPH #
192246
###########

startleft/startleft/_version/local_scheme.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def choose_strategy_by_branch(branch_name: str) -> callable:
77
:param branch_name: The name of the branch for which the version is being calculated
88
:return: The callable for the version strategy calculation
99
"""
10-
if branch_name == 'main' or 'release/' in branch_name:
10+
if branch_name == 'main' or 'release/' in branch_name or 'support/' in branch_name:
1111
return _no_local_version_strategy
1212
else:
1313
return _node_strategy

0 commit comments

Comments
 (0)