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.
- illumio_pylo/API/APIConnector.py +1308 -0
- illumio_pylo/API/AuditLog.py +42 -0
- illumio_pylo/API/ClusterHealth.py +136 -0
- illumio_pylo/API/CredentialsManager.py +286 -0
- illumio_pylo/API/Explorer.py +1077 -0
- illumio_pylo/API/JsonPayloadTypes.py +240 -0
- illumio_pylo/API/RuleSearchQuery.py +128 -0
- illumio_pylo/API/__init__.py +0 -0
- illumio_pylo/AgentStore.py +139 -0
- illumio_pylo/Exception.py +44 -0
- illumio_pylo/Helpers/__init__.py +3 -0
- illumio_pylo/Helpers/exports.py +508 -0
- illumio_pylo/Helpers/functions.py +166 -0
- illumio_pylo/IPList.py +135 -0
- illumio_pylo/IPMap.py +285 -0
- illumio_pylo/Label.py +25 -0
- illumio_pylo/LabelCommon.py +48 -0
- illumio_pylo/LabelGroup.py +68 -0
- illumio_pylo/LabelStore.py +403 -0
- illumio_pylo/LabeledObject.py +25 -0
- illumio_pylo/Organization.py +258 -0
- illumio_pylo/Query.py +331 -0
- illumio_pylo/ReferenceTracker.py +41 -0
- illumio_pylo/Rule.py +671 -0
- illumio_pylo/Ruleset.py +306 -0
- illumio_pylo/RulesetStore.py +101 -0
- illumio_pylo/SecurityPrincipal.py +62 -0
- illumio_pylo/Service.py +256 -0
- illumio_pylo/SoftwareVersion.py +125 -0
- illumio_pylo/VirtualService.py +17 -0
- illumio_pylo/VirtualServiceStore.py +75 -0
- illumio_pylo/Workload.py +506 -0
- illumio_pylo/WorkloadStore.py +289 -0
- illumio_pylo/__init__.py +82 -0
- illumio_pylo/cli/NativeParsers.py +96 -0
- illumio_pylo/cli/__init__.py +134 -0
- illumio_pylo/cli/__main__.py +10 -0
- illumio_pylo/cli/commands/__init__.py +32 -0
- illumio_pylo/cli/commands/credential_manager.py +168 -0
- illumio_pylo/cli/commands/iplist_import_from_file.py +185 -0
- illumio_pylo/cli/commands/misc.py +7 -0
- illumio_pylo/cli/commands/ruleset_export.py +129 -0
- illumio_pylo/cli/commands/update_pce_objects_cache.py +44 -0
- illumio_pylo/cli/commands/ven_duplicate_remover.py +366 -0
- illumio_pylo/cli/commands/ven_idle_to_visibility.py +287 -0
- illumio_pylo/cli/commands/ven_upgrader.py +226 -0
- illumio_pylo/cli/commands/workload_export.py +251 -0
- illumio_pylo/cli/commands/workload_import.py +423 -0
- illumio_pylo/cli/commands/workload_relabeler.py +510 -0
- illumio_pylo/cli/commands/workload_reset_names_to_null.py +83 -0
- illumio_pylo/cli/commands/workload_used_in_rule_finder.py +80 -0
- illumio_pylo/docs/Doxygen +1757 -0
- illumio_pylo/tmp.py +104 -0
- illumio_pylo/utilities/__init__.py +0 -0
- illumio_pylo/utilities/cli.py +10 -0
- illumio_pylo/utilities/credentials.example.json +20 -0
- illumio_pylo/utilities/explorer_report_exporter.py +86 -0
- illumio_pylo/utilities/health_monitoring.py +102 -0
- illumio_pylo/utilities/iplist_analyzer.py +148 -0
- illumio_pylo/utilities/iplists_stats_duplicates_unused_finder.py +75 -0
- illumio_pylo/utilities/resources/iplists-import-example.csv +3 -0
- illumio_pylo/utilities/resources/iplists-import-example.xlsx +0 -0
- illumio_pylo/utilities/resources/workload-exporter-filter-example.csv +3 -0
- illumio_pylo/utilities/resources/workloads-import-example.csv +2 -0
- illumio_pylo/utilities/resources/workloads-import-example.xlsx +0 -0
- illumio_pylo/utilities/ven_compatibility_report_export.py +240 -0
- illumio_pylo/utilities/ven_idle_to_illumination.py +344 -0
- illumio_pylo/utilities/ven_reassign_pce.py +183 -0
- illumio_pylo-0.2.5.dist-info/LICENSE +176 -0
- illumio_pylo-0.2.5.dist-info/METADATA +197 -0
- illumio_pylo-0.2.5.dist-info/RECORD +73 -0
- illumio_pylo-0.2.5.dist-info/WHEEL +5 -0
- 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
|
+
|