illumio-pylo 0.2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. illumio_pylo/API/APIConnector.py +1308 -0
  2. illumio_pylo/API/AuditLog.py +42 -0
  3. illumio_pylo/API/ClusterHealth.py +136 -0
  4. illumio_pylo/API/CredentialsManager.py +286 -0
  5. illumio_pylo/API/Explorer.py +1077 -0
  6. illumio_pylo/API/JsonPayloadTypes.py +240 -0
  7. illumio_pylo/API/RuleSearchQuery.py +128 -0
  8. illumio_pylo/API/__init__.py +0 -0
  9. illumio_pylo/AgentStore.py +139 -0
  10. illumio_pylo/Exception.py +44 -0
  11. illumio_pylo/Helpers/__init__.py +3 -0
  12. illumio_pylo/Helpers/exports.py +508 -0
  13. illumio_pylo/Helpers/functions.py +166 -0
  14. illumio_pylo/IPList.py +135 -0
  15. illumio_pylo/IPMap.py +285 -0
  16. illumio_pylo/Label.py +25 -0
  17. illumio_pylo/LabelCommon.py +48 -0
  18. illumio_pylo/LabelGroup.py +68 -0
  19. illumio_pylo/LabelStore.py +403 -0
  20. illumio_pylo/LabeledObject.py +25 -0
  21. illumio_pylo/Organization.py +258 -0
  22. illumio_pylo/Query.py +331 -0
  23. illumio_pylo/ReferenceTracker.py +41 -0
  24. illumio_pylo/Rule.py +671 -0
  25. illumio_pylo/Ruleset.py +306 -0
  26. illumio_pylo/RulesetStore.py +101 -0
  27. illumio_pylo/SecurityPrincipal.py +62 -0
  28. illumio_pylo/Service.py +256 -0
  29. illumio_pylo/SoftwareVersion.py +125 -0
  30. illumio_pylo/VirtualService.py +17 -0
  31. illumio_pylo/VirtualServiceStore.py +75 -0
  32. illumio_pylo/Workload.py +506 -0
  33. illumio_pylo/WorkloadStore.py +289 -0
  34. illumio_pylo/__init__.py +82 -0
  35. illumio_pylo/cli/NativeParsers.py +96 -0
  36. illumio_pylo/cli/__init__.py +134 -0
  37. illumio_pylo/cli/__main__.py +10 -0
  38. illumio_pylo/cli/commands/__init__.py +32 -0
  39. illumio_pylo/cli/commands/credential_manager.py +168 -0
  40. illumio_pylo/cli/commands/iplist_import_from_file.py +185 -0
  41. illumio_pylo/cli/commands/misc.py +7 -0
  42. illumio_pylo/cli/commands/ruleset_export.py +129 -0
  43. illumio_pylo/cli/commands/update_pce_objects_cache.py +44 -0
  44. illumio_pylo/cli/commands/ven_duplicate_remover.py +366 -0
  45. illumio_pylo/cli/commands/ven_idle_to_visibility.py +287 -0
  46. illumio_pylo/cli/commands/ven_upgrader.py +226 -0
  47. illumio_pylo/cli/commands/workload_export.py +251 -0
  48. illumio_pylo/cli/commands/workload_import.py +423 -0
  49. illumio_pylo/cli/commands/workload_relabeler.py +510 -0
  50. illumio_pylo/cli/commands/workload_reset_names_to_null.py +83 -0
  51. illumio_pylo/cli/commands/workload_used_in_rule_finder.py +80 -0
  52. illumio_pylo/docs/Doxygen +1757 -0
  53. illumio_pylo/tmp.py +104 -0
  54. illumio_pylo/utilities/__init__.py +0 -0
  55. illumio_pylo/utilities/cli.py +10 -0
  56. illumio_pylo/utilities/credentials.example.json +20 -0
  57. illumio_pylo/utilities/explorer_report_exporter.py +86 -0
  58. illumio_pylo/utilities/health_monitoring.py +102 -0
  59. illumio_pylo/utilities/iplist_analyzer.py +148 -0
  60. illumio_pylo/utilities/iplists_stats_duplicates_unused_finder.py +75 -0
  61. illumio_pylo/utilities/resources/iplists-import-example.csv +3 -0
  62. illumio_pylo/utilities/resources/iplists-import-example.xlsx +0 -0
  63. illumio_pylo/utilities/resources/workload-exporter-filter-example.csv +3 -0
  64. illumio_pylo/utilities/resources/workloads-import-example.csv +2 -0
  65. illumio_pylo/utilities/resources/workloads-import-example.xlsx +0 -0
  66. illumio_pylo/utilities/ven_compatibility_report_export.py +240 -0
  67. illumio_pylo/utilities/ven_idle_to_illumination.py +344 -0
  68. illumio_pylo/utilities/ven_reassign_pce.py +183 -0
  69. illumio_pylo-0.2.5.dist-info/LICENSE +176 -0
  70. illumio_pylo-0.2.5.dist-info/METADATA +197 -0
  71. illumio_pylo-0.2.5.dist-info/RECORD +73 -0
  72. illumio_pylo-0.2.5.dist-info/WHEEL +5 -0
  73. illumio_pylo-0.2.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,258 @@
1
+ from typing import Optional, List, Callable
2
+ import json
3
+ import os
4
+ import getpass
5
+ import illumio_pylo as pylo
6
+ from .API.JsonPayloadTypes import PCEObjectsJsonStructure, PCECacheFileJsonStructure
7
+ from .API.CredentialsManager import get_credentials_from_file
8
+
9
+
10
+ class Organization:
11
+
12
+ def __init__(self, org_id):
13
+ self.id: int = org_id
14
+ self.connector: Optional['pylo.APIConnector'] = None
15
+ self.LabelStore: 'pylo.LabelStore' = pylo.LabelStore(self)
16
+ self.IPListStore: 'pylo.IPListStore' = pylo.IPListStore(self)
17
+ self.WorkloadStore: 'pylo.WorkloadStore' = pylo.WorkloadStore(self)
18
+ self.VirtualServiceStore: 'pylo.VirtualServiceStore' = pylo.VirtualServiceStore(self)
19
+ self.AgentStore: 'pylo.AgentStore' = pylo.AgentStore(self)
20
+ self.ServiceStore: 'pylo.ServiceStore' = pylo.ServiceStore(self)
21
+ self.RulesetStore: 'pylo.RulesetStore' = pylo.RulesetStore(self)
22
+ self.SecurityPrincipalStore: 'pylo.SecurityPrincipalStore' = pylo.SecurityPrincipalStore(self)
23
+ self.pce_version: Optional['pylo.SoftwareVersion'] = None
24
+
25
+ def load_from_cached_file(self, fqdn: str, no_exception_if_file_does_not_exist=False) -> bool:
26
+ # filename should be like 'cache_xxx.yyy.zzz.json'
27
+ filename = 'cache_' + fqdn + '.json'
28
+
29
+ if os.path.isfile(filename):
30
+ # now we try to open that JSON file
31
+ with open(filename) as json_file:
32
+ data: PCECacheFileJsonStructure = json.load(json_file)
33
+ if 'pce_version' not in data:
34
+ raise pylo.PyloEx("Cannot find PCE version in cache file")
35
+ self.pce_version = pylo.SoftwareVersion(data['pce_version'])
36
+ if 'data' not in data:
37
+ raise pylo.PyloEx("Cache file '%s' was found and successfully loaded but no 'data' object could be found" % filename)
38
+ self.load_from_json(data['data'])
39
+ return True
40
+
41
+ if no_exception_if_file_does_not_exist:
42
+ return False
43
+
44
+ raise pylo.PyloEx("Cache file '%s' was not found!" % filename)
45
+
46
+ @staticmethod
47
+ def get_from_cache_file(fqdn: str) -> 'pylo.Organization':
48
+ org = pylo.Organization(1)
49
+ org.load_from_cached_file(fqdn)
50
+ return org
51
+
52
+ @staticmethod
53
+ def get_from_api_using_credential_file(fqdn_or_profile_name: str = None,
54
+ credential_file: str = None,
55
+ list_of_objects_to_load: Optional[List['pylo.ObjectTypes']] = None,
56
+ include_deleted_workloads: bool = False,
57
+ callback_api_objects_downloaded: Callable = None) -> 'Organization':
58
+ """
59
+ Credentials files will be looked for in the following order:
60
+ 1. The path provided in the credential_file argument
61
+ 2. The path provided in the Pylo_CREDENTIAL_FILE environment variable
62
+ 3. The path ~/.pylo/credentials.json
63
+ 4. Current working directory credentials.json
64
+ :param fqdn_or_profile_name:
65
+ :param credential_file:
66
+ :param list_of_objects_to_load:
67
+ :param include_deleted_workloads:
68
+ :param callback_api_objects_downloaded: callback function that will be called after each API has finished downloading all objects
69
+ :return:
70
+ """
71
+ credentials = get_credentials_from_file(fqdn_or_profile_name, credential_file)
72
+
73
+ connector = pylo.APIConnector(fqdn=credentials.fqdn, port=credentials.port,
74
+ apiuser=credentials.api_user, apikey=credentials.api_key,
75
+ org_id=credentials.org_id,
76
+ skip_ssl_cert_check=not credentials.verify_ssl, name=fqdn_or_profile_name)
77
+
78
+ objects = connector.get_pce_objects(list_of_objects_to_load=list_of_objects_to_load,
79
+ include_deleted_workloads=include_deleted_workloads)
80
+
81
+ if callback_api_objects_downloaded is not None:
82
+ callback_api_objects_downloaded()
83
+
84
+ org = Organization(1)
85
+ org.load_from_json(objects,list_of_objects_to_load=list_of_objects_to_load)
86
+
87
+ return org
88
+
89
+ def load_from_cache_or_saved_credentials(self, fqdn: str, include_deleted_workloads=False, prompt_for_api_key_if_missing=True):
90
+ """
91
+ Load the organization from a cache file on disk or default to the API
92
+ :param fqdn: the hostname of the PCE
93
+ :param include_deleted_workloads: if True, deleted workloads will be loaded from the API
94
+ :param prompt_for_api_key_if_missing: if True, the user will be prompted for an API key if it's unknown
95
+ :return:
96
+ """
97
+ if not self.load_from_cached_file(fqdn, no_exception_if_file_does_not_exist=True):
98
+ self.load_from_saved_credentials(fqdn, include_deleted_workloads=include_deleted_workloads, prompt_for_api_key=prompt_for_api_key_if_missing)
99
+
100
+ def load_from_saved_credentials(self, fqdn: str, include_deleted_workloads=False, prompt_for_api_key=False,
101
+ list_of_objects_to_load: Optional[List[str]] = None):
102
+ separator_pos = fqdn.find(':')
103
+ port = 8443
104
+
105
+ if separator_pos > 0:
106
+ port = fqdn[separator_pos + 1:]
107
+ fqdn = fqdn[0:separator_pos]
108
+
109
+ connector = pylo.APIConnector.create_from_credentials_in_file(fqdn)
110
+ if connector is None:
111
+ if not prompt_for_api_key:
112
+ raise pylo.PyloEx('Cannot find credentials for host {}'.format(fqdn))
113
+ print('Cannot find credentials for host "{}".\nPlease input an API user:'.format(fqdn), end='')
114
+ user = input()
115
+ password = getpass.getpass()
116
+ connector = pylo.APIConnector(fqdn, port, user, password, skip_ssl_cert_check=True, org_id=self.id)
117
+
118
+ self.load_from_api(connector, include_deleted_workloads=include_deleted_workloads,
119
+ list_of_objects_to_load=list_of_objects_to_load)
120
+
121
+ def load_from_json(self, data: PCEObjectsJsonStructure,
122
+ list_of_objects_to_load: Optional[List['pylo.ObjectTypes']] = None)\
123
+ -> None:
124
+ """
125
+ Load the organization from a JSON structure, mostly for developers use only
126
+ """
127
+ object_to_load = {}
128
+ if list_of_objects_to_load is not None:
129
+ all_types = pylo.APIConnector.get_all_object_types()
130
+ for object_type in list_of_objects_to_load:
131
+ if object_type not in all_types:
132
+ raise pylo.PyloEx("Unknown object type '{}'".format(object_type))
133
+ object_to_load[object_type] = True
134
+ else:
135
+ object_to_load = pylo.APIConnector.get_all_object_types()
136
+
137
+ if self.pce_version is None:
138
+ raise pylo.PyloEx('Organization has no "version" specified')
139
+
140
+ self.LabelStore.load_label_dimensions(data.get('label_dimensions'))
141
+
142
+ if 'labels' in object_to_load:
143
+ if 'labels' not in data:
144
+ raise Exception("'labels' was not found in json data")
145
+ self.LabelStore.load_labels_from_json(data['labels'])
146
+
147
+ if 'labelgroups' in object_to_load:
148
+ if 'labelgroups' not in data:
149
+ raise Exception("'labelgroups' was not found in json data")
150
+ self.LabelStore.load_label_groups_from_json(data['labelgroups'])
151
+
152
+ if 'iplists' in object_to_load:
153
+ if 'iplists' not in data:
154
+ raise Exception("'iplists' was not found in json data")
155
+ self.IPListStore.load_iplists_from_json(data['iplists'])
156
+
157
+ if 'services' in object_to_load:
158
+ if 'services' not in data:
159
+ raise Exception("'services' was not found in json data")
160
+ self.ServiceStore.load_services_from_json(data['services'])
161
+
162
+ if 'workloads' in object_to_load:
163
+ if 'workloads' not in data:
164
+ raise Exception("'workloads' was not found in json data")
165
+ self.WorkloadStore.load_workloads_from_json(data['workloads'])
166
+
167
+ if 'virtual_services' in object_to_load:
168
+ if 'virtual_services' not in data:
169
+ raise Exception("'virtual_services' was not found in json data")
170
+ self.VirtualServiceStore.load_virtualservices_from_json(data['virtual_services'])
171
+
172
+ if 'security_principals' in object_to_load:
173
+ if 'security_principals' not in data:
174
+ raise Exception("'security_principals' was not found in json data")
175
+ self.SecurityPrincipalStore.load_principals_from_json(data['security_principals'])
176
+
177
+ if 'rulesets' in object_to_load:
178
+ if 'rulesets' not in data:
179
+ raise Exception("'rulesets' was not found in json data")
180
+ self.RulesetStore.load_rulesets_from_json(data['rulesets'])
181
+
182
+ def load_from_api(self, con: pylo.APIConnector, include_deleted_workloads=False,
183
+ list_of_objects_to_load: Optional[List['pylo.ObjectTypes']] = None):
184
+ """
185
+ Load the organization from the API with the API Connector provided. Mostly intended for developers use only
186
+ :param con:
187
+ :param include_deleted_workloads:
188
+ :param list_of_objects_to_load:
189
+ :return:
190
+ """
191
+ self.pce_version = con.get_software_version()
192
+ self.connector = con
193
+ return self.load_from_json(self.get_config_from_api(con, include_deleted_workloads=include_deleted_workloads,
194
+ list_of_objects_to_load=list_of_objects_to_load))
195
+
196
+ @staticmethod
197
+ def create_fake_empty_config() -> PCEObjectsJsonStructure:
198
+ """
199
+ Create a fake empty config, mostly for developers use only
200
+ :return:
201
+ """
202
+ data = {}
203
+ for object_type in pylo.APIConnector.get_all_object_types().values():
204
+ data[object_type] = []
205
+ return data
206
+
207
+ def get_config_from_api(self, con: pylo.APIConnector, include_deleted_workloads=False,
208
+ list_of_objects_to_load: Optional[List[str]] = None) -> PCEObjectsJsonStructure:
209
+ """
210
+ Get the config/objects from the API using the API connector provided
211
+ :param con:
212
+ :param include_deleted_workloads:
213
+ :param list_of_objects_to_load:
214
+ :return:
215
+ """
216
+ self.connector = con
217
+ return con.get_pce_objects(include_deleted_workloads=include_deleted_workloads,
218
+ list_of_objects_to_load=list_of_objects_to_load)
219
+
220
+ def stats_to_str(self, padding='') -> str:
221
+ """ Dumps basic stats about the organization
222
+ :param padding: String to be added at the beginning of each line
223
+ Example:
224
+ - Version 21.5.33-3
225
+ - 539 Labels in total. Loc: 35 / Env: 13 / App: 368 / Role: 123
226
+ - Workloads: Managed: 5822 / Unmanaged: 1483 / Deleted: 0
227
+ - 0 IPlists in total.
228
+ - 0 RuleSets and 0 Rules.
229
+ """
230
+ stats = ""
231
+ stats += "{}- Version {}".format(padding, self.pce_version.generate_str_from_numbers()) + os.linesep
232
+
233
+ labels_str = ''
234
+ for dimension in self.LabelStore.label_types:
235
+ labels_str += " {}: {} /".format(dimension, self.LabelStore.count_labels(dimension))
236
+ # remove last ' /'
237
+ labels_str = labels_str[:-2]
238
+ stats += "{}- {} Labels in total. {}".\
239
+ format(padding,
240
+ self.LabelStore.count_labels(),
241
+ labels_str)
242
+
243
+ stats += os.linesep + "{}- Workloads: Managed: {} / Unmanaged: {} / Deleted: {}". \
244
+ format(padding,
245
+ self.WorkloadStore.count_managed_workloads(),
246
+ self.WorkloadStore.count_unmanaged_workloads(True),
247
+ self.WorkloadStore.count_deleted_workloads())
248
+
249
+ stats += os.linesep + "{}- {} IPlists in total.". \
250
+ format(padding,
251
+ self.IPListStore.count())
252
+
253
+ stats += os.linesep + "{}- {} RuleSets and {} Rules.". \
254
+ format(padding, self.RulesetStore.count_rulesets(), self.RulesetStore.count_rules())
255
+
256
+ return stats
257
+
258
+
illumio_pylo/Query.py ADDED
@@ -0,0 +1,331 @@
1
+ import illumio_pylo as pylo
2
+
3
+
4
+ def find_chars(text: str, start: int):
5
+ end = len(text)
6
+ return {"ending_parenthesis": text.find(")", start),
7
+ "opening_parenthesis": text.find(")", start),
8
+ "opening_squote": text.find("'", start),
9
+ }
10
+
11
+
12
+ def find_first_punctuation(text: str, start: int):
13
+ cursor = start
14
+ while cursor < len(text):
15
+ char = text[cursor]
16
+ if char == ')':
17
+ return {'notfound': False, 'position': cursor, 'character': ')'}
18
+ if char == '(':
19
+ return {'notfound': False, 'position': cursor, 'character': '('}
20
+ if char == "'":
21
+ return {'notfound': False, 'position': cursor, 'character': "'"}
22
+
23
+ cursor += 1
24
+
25
+ return {'notfound': True}
26
+
27
+
28
+ class get_block_response:
29
+ def __init__(self, length=None, operator=None, error=None):
30
+ self.length = length
31
+ self.operator = operator
32
+ self.error = error
33
+
34
+
35
+ def get_block_until_binary_ops_quotes_enabled(data: str):
36
+
37
+ detected_quote = None
38
+
39
+ for pos in range(len(data)):
40
+
41
+ cur_2let = data[pos:pos+2].lower()
42
+ cur_3let = data[pos:pos+3].lower()
43
+
44
+ # print("DEBUG {} {}".format(cur_2let, cur_3let))
45
+
46
+ if cur_2let == 'or':
47
+ if detected_quote is None:
48
+ return get_block_response(length=pos, operator='or')
49
+ elif cur_3let == 'and':
50
+ if detected_quote is None:
51
+ return get_block_response(length=pos, operator='and')
52
+
53
+ cur_char = data[pos]
54
+
55
+ if cur_char == "'":
56
+ if detected_quote is None:
57
+ detected_quote = cur_char
58
+ elif detected_quote == '"':
59
+ continue
60
+ else:
61
+ detected_quote = None
62
+
63
+ elif cur_char == '"':
64
+ if detected_quote is None:
65
+ detected_quote = cur_char
66
+ elif detected_quote == "'":
67
+ continue
68
+ else:
69
+ detected_quote = None
70
+
71
+ if detected_quote is None:
72
+ return get_block_response(length=len(data))
73
+
74
+ return get_block_response(error="some quotes {} were not closed in expression: {}".format(detected_quote, data))
75
+
76
+
77
+ class Query:
78
+ def __init__(self, level=0):
79
+ self.level = level
80
+ self.subQueries = [] # type: list[pylo.Query]
81
+ self.raw_value = None
82
+
83
+ def parse(self, data: str):
84
+ padding = ''.rjust(self.level*3)
85
+ data_len = len(data)
86
+ self.raw_value = data
87
+
88
+ cursor = 0
89
+ current_block_start = 0
90
+ parenthesis_opened = []
91
+ blocks = []
92
+ reached_end = False
93
+
94
+ print(padding + 'Level {} parsing string "{}"'.format(self.level , data))
95
+
96
+ while cursor < len(data):
97
+ find_punctuation = find_first_punctuation(data, cursor)
98
+ if find_punctuation['notfound']:
99
+ if len(parenthesis_opened) > 0:
100
+ raise pylo.PyloEx("Reached the end of string before closing parenthesis in block: {}".format(data[current_block_start-1:]))
101
+ blocks.append({'type': 'text', 'text': data[current_block_start:]})
102
+ reached_end = True
103
+ print(padding + "{}-{} REACHED END OF STRING".format(self.level, len(blocks)))
104
+ break
105
+
106
+ found_character = find_punctuation['character']
107
+ found_character_position = find_punctuation['position']
108
+
109
+ if found_character == "'":
110
+ find_next_quote = data.find("'", found_character_position+1)
111
+ if find_next_quote == -1:
112
+ raise pylo.PyloEx("Cannot find a matching closing quote from position {} in text: {}".format(found_character_position, data[current_block_start:]))
113
+ cursor = find_next_quote+1
114
+ if cursor >= len(data):
115
+ blocks.append({'type': 'text', 'text': data[current_block_start:]})
116
+ continue
117
+ elif found_character == ")":
118
+ if len(parenthesis_opened) < 1:
119
+ raise pylo.PyloEx("Cannot find a matching opening parenthesis at position #{} in text: {}".format(found_character_position, data[current_block_start:]))
120
+ elif len(parenthesis_opened) == 1:
121
+ parenthesis_opened.pop()
122
+ blocks.append({'type': 'sub', 'text': data[current_block_start:found_character_position]})
123
+ current_block_start = found_character_position + 1
124
+ cursor = current_block_start
125
+ print(padding + "{}-{} REACHED BLOCK-END C PARENTHESIS".format(self.level, len(blocks)))
126
+ continue
127
+ else:
128
+ parenthesis_opened.pop()
129
+ cursor = found_character_position + 1
130
+ print(padding + "{}-{} REACHED MID-BLOCK C PARENTHESIS".format(self.level, len(blocks)))
131
+ continue
132
+ elif found_character == "(":
133
+ parenthesis_opened.append(found_character_position)
134
+ print(padding + "{}-{} FOUND OPENING P".format(self.level, len(blocks)))
135
+
136
+ if (found_character_position == 0 or found_character_position > current_block_start) and len(parenthesis_opened) == 1:
137
+ if found_character_position != 0:
138
+ blocks.append({'type': 'text', 'text': data[current_block_start:found_character_position]})
139
+ current_block_start = found_character_position+1
140
+ cursor = current_block_start
141
+ print(padding + "{}-{} OPENING P WAS FIRST".format(self.level, len(blocks)))
142
+ continue
143
+ else:
144
+ print(padding + "{}-{} OPENING P WAS NOT FIRST".format(self.level, len(blocks)))
145
+
146
+ cursor = found_character_position + 1
147
+ continue
148
+
149
+ cursor += 1
150
+
151
+ if len(parenthesis_opened) > 0:
152
+ raise pylo.PyloEx("Reached the end of string before closing parenthesis in block: {}".format(
153
+ data[current_block_start-1:]))
154
+
155
+ print(padding + "* Query Level {} blocks:".format(self.level))
156
+ for block in blocks:
157
+ print(padding + "- {}: |{}|".format(block['type'], block['text']))
158
+
159
+ # clear empty blocks, they're just noise
160
+ cleared_blocks = []
161
+ for block in blocks:
162
+ if block['type'] == 'text':
163
+ block['text'] = block['text'].strip()
164
+ if len(block['text']) < 1:
165
+ continue
166
+ cleared_blocks.append(block)
167
+
168
+ # now building operator blocks
169
+ operator_blocks = []
170
+
171
+ for block_number in range(len(blocks)):
172
+ block = blocks[block_number]
173
+ if block['type'] == 'sub':
174
+ new_sub_query = Query(self.level+1)
175
+ new_sub_query.parse(block['text'])
176
+ self.subQueries.append(new_sub_query)
177
+ new_block = {'type': 'query', 'query': new_sub_query}
178
+ operator_blocks.append(new_block)
179
+ continue
180
+
181
+ text = block['text']
182
+ found_filter_name = False
183
+ found_filter_operator = False
184
+
185
+ filter_name = None
186
+ filter_operator = None
187
+ find_filter_in_collection = None
188
+
189
+ while True:
190
+ text_len = len(text)
191
+ if text_len < 1:
192
+ break
193
+ print(padding + "* Handling of text block '||{}||'".format(text))
194
+ first_word_end = text.find(' ')
195
+
196
+ if first_word_end < 0:
197
+ first_word = text
198
+ first_word_end = text_len
199
+ else:
200
+ first_word = text[0:first_word_end]
201
+ first_word_lower = first_word.lower()
202
+ print(padding + " - First word '{}'".format(first_word))
203
+
204
+ if first_word_lower == 'or' or first_word_lower == 'and':
205
+ if found_filter_name:
206
+ if not found_filter_operator:
207
+ raise pylo.PyloEx("Found binary operator '{}' while filter '{}' was found but no operator provided in expression '{}'".format(first_word, filter_name, block['text']))
208
+ if find_filter_in_collection.arguments is not None:
209
+ raise pylo.PyloEx(
210
+ "Found binary operator '{}' while filter '{}' with operator '{}' requires arguments in expression '{}'".format(
211
+ first_word, filter_name, filter_operator, block['text']))
212
+ new_block = {'type': 'filter', 'filter': find_filter_in_collection, 'raw_arguments': None}
213
+ operator_blocks.append(new_block)
214
+ found_filter_name = False
215
+ found_filter_operator = False
216
+
217
+ new_block = {'type': 'binary_op', 'value': first_word_lower}
218
+ operator_blocks.append(new_block)
219
+ elif found_filter_name and found_filter_operator:
220
+ block_info = get_block_until_binary_ops_quotes_enabled(text)
221
+ if block_info.error is not None:
222
+ raise pylo.PyloEx(block_info.error)
223
+
224
+ if block_info.length == 0:
225
+ new_block = {'type': 'filter', 'filter': find_filter_in_collection, 'raw_arguments': None}
226
+ operator_blocks.append(new_block)
227
+ print(padding+" - Found no argument (or empty)")
228
+ raise pylo.PyloEx("This should never happen")
229
+ else:
230
+ new_block = {'type': 'filter', 'filter': find_filter_in_collection, 'raw_arguments': text[0:block_info.length].strip()}
231
+ operator_blocks.append(new_block)
232
+ print(padding + " - Found argument ||{}|| stopped by {}".format(new_block['raw_arguments'], block_info.operator))
233
+
234
+ found_filter_name = False
235
+ found_filter_operator = False
236
+
237
+ first_word_end = block_info.length - 1
238
+
239
+ elif not found_filter_name and not found_filter_operator:
240
+ filter_name = first_word
241
+ found_filter_name = True
242
+ find_filter_in_collection = FilterCollections.workload_filters.get(filter_name)
243
+ if find_filter_in_collection is None:
244
+ raise pylo.PyloEx("Cannot find a filter named '{}' in expression '{}'".format(filter_name, block['text']))
245
+ elif found_filter_name and not found_filter_operator:
246
+ filter_operator = first_word
247
+ found_filter_operator = True
248
+ find_filter_in_collection = find_filter_in_collection.get(filter_operator)
249
+ if find_filter_in_collection is None:
250
+ raise pylo.PyloEx("Cannot find a filter operator '{}' for filter named '{}' in expression '{}'".format(filter_operator, filter_name, block['text']))
251
+
252
+ if first_word_end >= text_len:
253
+ break
254
+ text = text[first_word_end + 1:].strip()
255
+
256
+ # showing blocks to check how well we did
257
+ for block in operator_blocks:
258
+ print(block)
259
+
260
+ # todo: optimize/handle inverters
261
+
262
+ def execute_on_single_object(self, object):
263
+ if len(self.subQueries) == 1:
264
+ pass
265
+
266
+ return False
267
+
268
+
269
+ class Filter:
270
+ def __init__(self, name: str, func, arguments=None):
271
+ self.name = name
272
+ self.function = func
273
+ self.arguments = arguments
274
+
275
+
276
+ class WorkloadFilter(Filter):
277
+ def __init__(self, name: str, func, arguments = None):
278
+ Filter.__init__(self, name, func, arguments)
279
+
280
+ def do_filter(self, workload: pylo.Workload):
281
+ return self.function(workload)
282
+
283
+
284
+ class FilterContext:
285
+ def __init__(self, argument, math_op=None):
286
+ self.math_op = math_op
287
+ self.argument = argument
288
+
289
+
290
+ class FilterCollections:
291
+ workload_filters = {} # type: dict[str,WorkloadFilter]
292
+
293
+ @staticmethod
294
+ def add_workload_filter(name: str, operator: str, func, arguments=None):
295
+ new_filter = WorkloadFilter(name, func)
296
+ if FilterCollections.workload_filters.get(name) is None:
297
+ FilterCollections.workload_filters[name.lower()] = {}
298
+
299
+ if FilterCollections.workload_filters[name.lower()].get(operator) is not None:
300
+ raise pylo.PyloEx("Filter named '{}' with operator '{}' is already defined".format(name, operator))
301
+ FilterCollections.workload_filters[name.lower()][operator.lower()] = new_filter
302
+
303
+
304
+ def tmp_func(wkl: pylo.Workload, context: FilterContext):
305
+ return wkl.get_name() == context.argument
306
+
307
+
308
+ FilterCollections.add_workload_filter('name', 'matches', tmp_func, 'string')
309
+
310
+
311
+ def tmp_func(wkl: pylo.Workload, context: FilterContext):
312
+ if context.math_op == '>':
313
+ return wkl.count_references() > context.argument
314
+
315
+
316
+ FilterCollections.add_workload_filter('reference.count', '<>=!', tmp_func, 'int')
317
+
318
+
319
+ def tmp_func(wkl: pylo.Workload, context: FilterContext):
320
+ return context.argument in wkl.description
321
+
322
+
323
+ FilterCollections.add_workload_filter('description', 'contains', tmp_func, 'string')
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
@@ -0,0 +1,41 @@
1
+ import illumio_pylo as pylo
2
+ from illumio_pylo import log
3
+ from .Helpers import *
4
+
5
+
6
+ class ReferenceTracker:
7
+ def __init__(self):
8
+ self._references = {} # type: dict[Referencer, Referencer]
9
+
10
+ def add_reference(self, ref: 'pylo.Referencer'):
11
+ self._references[ref] = ref
12
+
13
+ def remove_reference(self, ref: 'pylo.Referencer'):
14
+ index = self._references.get(ref)
15
+ if index is None:
16
+ raise Exception('Tried to unreference an object which is not actually referenced')
17
+ self._references.pop(ref)
18
+
19
+ def count_references(self):
20
+ return len(self._references)
21
+
22
+ def get_references(self):
23
+ return self._references.values()
24
+
25
+ def get_references_filter_by_class(self, classes):
26
+ matches = []
27
+ for obj in self._references.values():
28
+ if type(obj) in classes:
29
+ matches.append(obj)
30
+ return matches
31
+
32
+
33
+ class Referencer:
34
+ def reference_name_changed(self):
35
+ raise Exception('not implemented')
36
+
37
+
38
+ class Pathable:
39
+ def __init__(self):
40
+ self.name = ''
41
+