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,289 @@
|
|
|
1
|
+
from illumio_pylo import log, IP4Map, PyloEx, Workload, nice_json, Label, LabelGroup
|
|
2
|
+
from .Helpers import *
|
|
3
|
+
from .Organization import Organization
|
|
4
|
+
from typing import Optional, List, Union, Set
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class WorkloadStore:
|
|
8
|
+
|
|
9
|
+
def __init__(self, owner: 'Organization'):
|
|
10
|
+
self.owner: Organization = owner
|
|
11
|
+
self.itemsByHRef: Dict[str, Workload] = {}
|
|
12
|
+
|
|
13
|
+
def load_workloads_from_json(self, json_list):
|
|
14
|
+
for json_item in json_list:
|
|
15
|
+
if 'name' not in json_item or 'href' not in json_item:
|
|
16
|
+
raise PyloEx("Cannot find 'value'/name or href for Workload in JSON:\n" + nice_json(json_item))
|
|
17
|
+
|
|
18
|
+
new_item_name = json_item['name']
|
|
19
|
+
new_item_href = json_item['href']
|
|
20
|
+
|
|
21
|
+
# Workload's name is None when it's provided by VEN through its hostname until it's manually overwritten
|
|
22
|
+
# (eventually) by someone. In such a case, you need to use hostname instead
|
|
23
|
+
if new_item_name is None:
|
|
24
|
+
if 'hostname' not in json_item:
|
|
25
|
+
raise PyloEx("Cannot find 'value'/hostname in JSON:\n" + nice_json(json_item))
|
|
26
|
+
new_item_name = json_item['hostname']
|
|
27
|
+
|
|
28
|
+
new_item = Workload(new_item_name, new_item_href, self)
|
|
29
|
+
new_item.load_from_json(json_item)
|
|
30
|
+
|
|
31
|
+
if new_item_href in self.itemsByHRef:
|
|
32
|
+
raise PyloEx("A Workload with href '%s' already exists in the table", new_item_href)
|
|
33
|
+
|
|
34
|
+
self.itemsByHRef[new_item_href] = new_item
|
|
35
|
+
|
|
36
|
+
log.debug("Found Workload '%s' with href '%s'", new_item_name, new_item_href)
|
|
37
|
+
|
|
38
|
+
def find_by_href_or_die(self, href: str) -> 'Workload':
|
|
39
|
+
"""
|
|
40
|
+
Find a Workload from its HREF, throw an Exception if not found
|
|
41
|
+
|
|
42
|
+
:param href: the HREF you are looking for
|
|
43
|
+
:return:
|
|
44
|
+
:raises:
|
|
45
|
+
PyloEx: if no Workload matching provided HREF
|
|
46
|
+
"""
|
|
47
|
+
find_object = self.itemsByHRef.get(href)
|
|
48
|
+
if find_object is None:
|
|
49
|
+
raise PyloEx("Workload with HREF '%s' was not found" % href)
|
|
50
|
+
|
|
51
|
+
return find_object
|
|
52
|
+
|
|
53
|
+
def find_by_href_or_create_tmp(self, href: str, tmp_wkl_name: str) -> 'Workload':
|
|
54
|
+
"""
|
|
55
|
+
Find a Workload from its HREF, creates a fake temporary one if not found. *Reserved for developers*
|
|
56
|
+
|
|
57
|
+
:param href: the HREF you are looking for
|
|
58
|
+
:return:
|
|
59
|
+
"""
|
|
60
|
+
find_object = self.itemsByHRef.get(href)
|
|
61
|
+
if find_object is not None:
|
|
62
|
+
return find_object
|
|
63
|
+
|
|
64
|
+
new_tmp_item = Workload(tmp_wkl_name, href, self)
|
|
65
|
+
new_tmp_item.deleted = True
|
|
66
|
+
new_tmp_item.temporary = True
|
|
67
|
+
|
|
68
|
+
self.itemsByHRef[href] = new_tmp_item
|
|
69
|
+
|
|
70
|
+
return new_tmp_item
|
|
71
|
+
|
|
72
|
+
def find_workloads_matching_label(self, label: 'Label') -> Dict[str, 'Workload']:
|
|
73
|
+
"""
|
|
74
|
+
Find all Workloads which are using a specific Label.
|
|
75
|
+
|
|
76
|
+
:param label: Label you want to match on
|
|
77
|
+
:return: a dictionary of all matching Workloads using their HREF as key
|
|
78
|
+
"""
|
|
79
|
+
result = {}
|
|
80
|
+
|
|
81
|
+
for href, workload in self.itemsByHRef.items():
|
|
82
|
+
if workload.is_using_label(label):
|
|
83
|
+
result[href] = workload
|
|
84
|
+
|
|
85
|
+
return result
|
|
86
|
+
|
|
87
|
+
def find_workloads_matching_all_labels(self, labels: Union[List[Label|LabelGroup], Dict[str,Label|LabelGroup]])\
|
|
88
|
+
-> Dict[str, 'Workload']:
|
|
89
|
+
"""
|
|
90
|
+
Find all Workloads which are using all the Labels from a specified list/dict. Note that labels will be ordered by type
|
|
91
|
+
and workloads will need to match 1 Label of each specified. LabelGroups will be expanded.
|
|
92
|
+
If one of the labels is None, it will be ignored
|
|
93
|
+
|
|
94
|
+
:param labels: list of Labels you want to match on
|
|
95
|
+
:return: a dictionary of all matching Workloads using their HREF as key
|
|
96
|
+
"""
|
|
97
|
+
unique_labels: Set[Union[Label, LabelGroup]] = set()
|
|
98
|
+
|
|
99
|
+
if isinstance(labels, list):
|
|
100
|
+
for label in labels:
|
|
101
|
+
if label is None:
|
|
102
|
+
pylo.log.debug("Label is None, skipping")
|
|
103
|
+
continue
|
|
104
|
+
if isinstance(label, LabelGroup):
|
|
105
|
+
unique_labels.update(label.expand_nested_to_dict_by_href().values())
|
|
106
|
+
else:
|
|
107
|
+
unique_labels.add(label)
|
|
108
|
+
else:
|
|
109
|
+
for label in labels.values():
|
|
110
|
+
if label is None:
|
|
111
|
+
pylo.log.warn("Label is None, skipping")
|
|
112
|
+
continue
|
|
113
|
+
if isinstance(label, LabelGroup):
|
|
114
|
+
unique_labels.update(label.expand_nested_to_dict_by_href().values())
|
|
115
|
+
else:
|
|
116
|
+
unique_labels.add(label)
|
|
117
|
+
|
|
118
|
+
labels_by_type = pylo.LabelStore.Utils.list_to_dict_by_type(unique_labels)
|
|
119
|
+
|
|
120
|
+
result = {}
|
|
121
|
+
|
|
122
|
+
for workload in self.itemsByHRef.values():
|
|
123
|
+
workload_is_a_match = True
|
|
124
|
+
for label_type, labels_to_find in labels_by_type.items():
|
|
125
|
+
workload_label = workload.get_label(label_type)
|
|
126
|
+
if workload_label is None or workload_label not in labels_to_find:
|
|
127
|
+
workload_is_a_match = False
|
|
128
|
+
break
|
|
129
|
+
if workload_is_a_match:
|
|
130
|
+
result[workload.href] = workload
|
|
131
|
+
|
|
132
|
+
return result
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def find_workload_matching_forced_name(self, name: str, case_sensitive: bool = True, strip_fqdn: bool = False) -> Optional[Workload]:
|
|
136
|
+
"""
|
|
137
|
+
Find a Workload based on its name (case-sensitive). Beware that if several are matching, only the first one will be returned
|
|
138
|
+
|
|
139
|
+
:param name: the name string you are looking for
|
|
140
|
+
:param case_sensitive: make it a case-sensitive search or not
|
|
141
|
+
:param strip_fqdn: remove the fqdn part of the hostname
|
|
142
|
+
:return: the Workload it found, None otherwise
|
|
143
|
+
"""
|
|
144
|
+
if not case_sensitive:
|
|
145
|
+
name = name.lower()
|
|
146
|
+
|
|
147
|
+
for workload in self.itemsByHRef.values():
|
|
148
|
+
wkl_name = workload.forced_name
|
|
149
|
+
if strip_fqdn:
|
|
150
|
+
wkl_name = Workload.static_name_stripped_fqdn(wkl_name)
|
|
151
|
+
if case_sensitive:
|
|
152
|
+
if wkl_name == name:
|
|
153
|
+
return workload
|
|
154
|
+
else:
|
|
155
|
+
if wkl_name.lower() == name:
|
|
156
|
+
return workload
|
|
157
|
+
|
|
158
|
+
return None
|
|
159
|
+
|
|
160
|
+
def find_workload_matching_hostname(self, name: str, case_sensitive: bool = True, strip_fqdn: bool = False, fall_back_to_name: bool = False) -> Optional[Workload]:
|
|
161
|
+
"""
|
|
162
|
+
Find a workload based on its hostname.Beware that if several are matching, only the first one will be returned
|
|
163
|
+
|
|
164
|
+
:param name: the name string you are looking for
|
|
165
|
+
:param case_sensitive: make it a case-sensitive search or not
|
|
166
|
+
:param strip_fqdn: remove the fqdn part of the hostname
|
|
167
|
+
:param fall_back_to_name: if True, will fall back to the forced name if no hostname is found
|
|
168
|
+
:return: the Workload it found, None otherwise
|
|
169
|
+
"""
|
|
170
|
+
if not case_sensitive:
|
|
171
|
+
name = name.lower()
|
|
172
|
+
|
|
173
|
+
for workload in self.itemsByHRef.values():
|
|
174
|
+
wkl_name = workload.hostname
|
|
175
|
+
if wkl_name is None:
|
|
176
|
+
if fall_back_to_name:
|
|
177
|
+
wkl_name = workload.forced_name
|
|
178
|
+
if wkl_name is None:
|
|
179
|
+
continue
|
|
180
|
+
else:
|
|
181
|
+
continue
|
|
182
|
+
if strip_fqdn:
|
|
183
|
+
wkl_name = Workload.static_name_stripped_fqdn(wkl_name)
|
|
184
|
+
if case_sensitive:
|
|
185
|
+
if wkl_name == name:
|
|
186
|
+
return workload
|
|
187
|
+
else:
|
|
188
|
+
if wkl_name.lower() == name:
|
|
189
|
+
return workload
|
|
190
|
+
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
def find_all_workloads_matching_hostname(self, name: str, case_sensitive: bool = True, strip_fqdn: bool = False, fall_back_to_name: bool = False) -> List[Workload]:
|
|
194
|
+
"""
|
|
195
|
+
Find all workloads based on their hostnames.
|
|
196
|
+
:param name: the name string you are looking for
|
|
197
|
+
:param case_sensitive: make it a case-sensitive search or not
|
|
198
|
+
:param strip_fqdn: remove the fqdn part of the hostname
|
|
199
|
+
:param fall_back_to_name: if True, will fall back to the forced name if no hostname is found
|
|
200
|
+
:return: list of matching Workloads
|
|
201
|
+
"""
|
|
202
|
+
result = []
|
|
203
|
+
|
|
204
|
+
if not case_sensitive:
|
|
205
|
+
name = name.lower()
|
|
206
|
+
|
|
207
|
+
for workload in self.itemsByHRef.values():
|
|
208
|
+
wkl_name = workload.hostname
|
|
209
|
+
if wkl_name is None:
|
|
210
|
+
if fall_back_to_name:
|
|
211
|
+
wkl_name = workload.forced_name
|
|
212
|
+
if wkl_name is None:
|
|
213
|
+
continue
|
|
214
|
+
else:
|
|
215
|
+
continue
|
|
216
|
+
if strip_fqdn:
|
|
217
|
+
wkl_name = Workload.static_name_stripped_fqdn(wkl_name)
|
|
218
|
+
if case_sensitive:
|
|
219
|
+
if wkl_name == name:
|
|
220
|
+
result.append(workload)
|
|
221
|
+
else:
|
|
222
|
+
if wkl_name.lower() == name:
|
|
223
|
+
result.append(workload)
|
|
224
|
+
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
def count_workloads(self) -> int:
|
|
228
|
+
return len(self.itemsByHRef)
|
|
229
|
+
|
|
230
|
+
def count_managed_workloads(self) -> int:
|
|
231
|
+
count = 0
|
|
232
|
+
|
|
233
|
+
for item in self.itemsByHRef.values():
|
|
234
|
+
if not item.unmanaged and not item.deleted:
|
|
235
|
+
count += 1
|
|
236
|
+
|
|
237
|
+
return count
|
|
238
|
+
|
|
239
|
+
def get_managed_workloads_list(self) -> List['Workload']:
|
|
240
|
+
"""
|
|
241
|
+
Get a list of all managed workloads
|
|
242
|
+
:return:
|
|
243
|
+
"""
|
|
244
|
+
results = []
|
|
245
|
+
for item in self.itemsByHRef.values():
|
|
246
|
+
if not item.unmanaged:
|
|
247
|
+
results.append(item)
|
|
248
|
+
|
|
249
|
+
return results
|
|
250
|
+
|
|
251
|
+
def get_managed_workloads_dict_by_href(self) -> Dict[str, 'Workload']:
|
|
252
|
+
"""
|
|
253
|
+
Get a dictionary of all managed workloads using their HREF as key
|
|
254
|
+
:return:
|
|
255
|
+
"""
|
|
256
|
+
results = {}
|
|
257
|
+
for item in self.itemsByHRef.values():
|
|
258
|
+
if not item.unmanaged:
|
|
259
|
+
results[item.href] = item
|
|
260
|
+
|
|
261
|
+
return results
|
|
262
|
+
|
|
263
|
+
def count_deleted_workloads(self) -> int:
|
|
264
|
+
count = 0
|
|
265
|
+
for item in self.itemsByHRef.values():
|
|
266
|
+
if item.deleted:
|
|
267
|
+
count += 1
|
|
268
|
+
#print(item.href)
|
|
269
|
+
|
|
270
|
+
return count
|
|
271
|
+
|
|
272
|
+
def count_unmanaged_workloads(self, if_not_deleted=False) -> int:
|
|
273
|
+
count = 0
|
|
274
|
+
|
|
275
|
+
for item in self.itemsByHRef.values():
|
|
276
|
+
if item.unmanaged and (not if_not_deleted or (if_not_deleted and not item.deleted)):
|
|
277
|
+
count += 1
|
|
278
|
+
|
|
279
|
+
return count
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def workloads(self) -> List['Workload']:
|
|
283
|
+
"""
|
|
284
|
+
Get a list of all workloads
|
|
285
|
+
:return:
|
|
286
|
+
"""
|
|
287
|
+
return list(self.itemsByHRef.values())
|
|
288
|
+
|
|
289
|
+
|
illumio_pylo/__init__.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from .tmp import *
|
|
6
|
+
from .Helpers import *
|
|
7
|
+
|
|
8
|
+
from .Exception import PyloEx, PyloApiEx, PyloApiTooManyRequestsEx, PyloApiUnexpectedSyntax, PyloObjectNotFound, PyloApiRequestForbiddenEx
|
|
9
|
+
from .SoftwareVersion import SoftwareVersion
|
|
10
|
+
from .IPMap import IP4Map
|
|
11
|
+
from .ReferenceTracker import ReferenceTracker, Referencer, Pathable
|
|
12
|
+
from .API.APIConnector import APIConnector, ObjectTypes
|
|
13
|
+
from .API.RuleSearchQuery import RuleSearchQuery, RuleSearchQueryResolvedResultSet
|
|
14
|
+
from .API.ClusterHealth import ClusterHealth
|
|
15
|
+
from .API.Explorer import ExplorerResultSetV1, RuleCoverageQueryManager, ExplorerFilterSetV1, ExplorerQuery
|
|
16
|
+
from .API.AuditLog import AuditLogQuery, AuditLogQueryResultSet, AuditLogFilterSet
|
|
17
|
+
from .API.CredentialsManager import get_credentials_from_file
|
|
18
|
+
from .LabelCommon import LabelCommon
|
|
19
|
+
from .Label import Label
|
|
20
|
+
from .LabelGroup import LabelGroup
|
|
21
|
+
from .LabelStore import LabelStore, label_type_app, label_type_env, label_type_loc, label_type_role
|
|
22
|
+
from .IPList import IPList, IPListStore
|
|
23
|
+
from .AgentStore import AgentStore, VENAgent
|
|
24
|
+
from .Workload import Workload, WorkloadInterface
|
|
25
|
+
from .WorkloadStore import WorkloadStore
|
|
26
|
+
from .VirtualService import VirtualService
|
|
27
|
+
from .VirtualServiceStore import VirtualServiceStore
|
|
28
|
+
from .Service import Service, ServiceStore, PortMap, ServiceEntry
|
|
29
|
+
from .Rule import Rule, RuleServiceContainer, RuleSecurityPrincipalContainer, DirectServiceInRule, RuleHostContainer, RuleActorsAcceptableTypes
|
|
30
|
+
from .Ruleset import Ruleset, RulesetScope, RulesetScopeEntry
|
|
31
|
+
from .RulesetStore import RulesetStore
|
|
32
|
+
from .SecurityPrincipal import SecurityPrincipal, SecurityPrincipalStore
|
|
33
|
+
from .Organization import Organization
|
|
34
|
+
from .Query import Query
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_organization(fqdn: str, port: int, api_user: str, api_key: str,
|
|
38
|
+
organization_id: int, verify_ssl: bool = True,
|
|
39
|
+
list_of_objects_to_load: Optional[List['pylo.ObjectTypes']] = None,
|
|
40
|
+
include_deleted_workloads: bool = False) -> Organization:
|
|
41
|
+
"""
|
|
42
|
+
Load an organization from the API with parameters provided as arguments.
|
|
43
|
+
"""
|
|
44
|
+
api = APIConnector(fqdn=fqdn, port=port, apiuser=api_user, apikey=api_key, org_id=organization_id,
|
|
45
|
+
skip_ssl_cert_check=not verify_ssl)
|
|
46
|
+
org = Organization(1)
|
|
47
|
+
org.load_from_api(api, include_deleted_workloads=include_deleted_workloads,
|
|
48
|
+
list_of_objects_to_load=list_of_objects_to_load)
|
|
49
|
+
|
|
50
|
+
return org
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_organization_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
|
+
return Organization.get_from_api_using_credential_file(fqdn_or_profile_name=fqdn_or_profile_name,
|
|
72
|
+
credential_file=credential_file,
|
|
73
|
+
list_of_objects_to_load=list_of_objects_to_load,
|
|
74
|
+
include_deleted_workloads=include_deleted_workloads,
|
|
75
|
+
callback_api_objects_downloaded=callback_api_objects_downloaded)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
ignoreWorkloadsWithSameName = True
|
|
80
|
+
|
|
81
|
+
objectNotFound = object()
|
|
82
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing import Optional, List, Dict
|
|
3
|
+
|
|
4
|
+
import illumio_pylo as pylo
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BaseParser:
|
|
8
|
+
_action: Optional[argparse.Action] = None
|
|
9
|
+
|
|
10
|
+
def fill_parser(self, parser: argparse.ArgumentParser):
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
|
|
13
|
+
def execute(self, args: str | int, org: 'pylo.Organization', padding: str = '') -> Optional[List['pylo.Label']]:
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
def get_arg_name(self) -> str:
|
|
17
|
+
return self._action.dest
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class LabelParser(BaseParser):
|
|
21
|
+
def __init__(self, action_name: str, action_short_name: Optional[str], label_type: Optional[str] = None, is_required: bool = True, allow_multiple: bool = False, help_text: Optional[str] = None):
|
|
22
|
+
self.action_name = action_name
|
|
23
|
+
self.action_short_name = action_short_name
|
|
24
|
+
self.label_type = label_type
|
|
25
|
+
self.is_required = is_required
|
|
26
|
+
self.allow_multiple = allow_multiple
|
|
27
|
+
self.results: Optional[List['pylo.Label']] = None
|
|
28
|
+
self.results_as_dict_by_href: Dict[str, 'pylo.Label'] = {}
|
|
29
|
+
self.help_text = help_text
|
|
30
|
+
|
|
31
|
+
def fill_parser(self, parser: argparse.ArgumentParser):
|
|
32
|
+
|
|
33
|
+
help_text = self.help_text
|
|
34
|
+
if help_text is None:
|
|
35
|
+
if self.allow_multiple:
|
|
36
|
+
if self.label_type is None:
|
|
37
|
+
help_text = f"Filter by label name. Multiple labels can be specified by separating them with a comma."
|
|
38
|
+
else:
|
|
39
|
+
help_text = f"Filter by label name of type '{self.label_type}'. Multiple labels can be specified by separating them with a comma."
|
|
40
|
+
else:
|
|
41
|
+
if self.label_type is None:
|
|
42
|
+
help_text = f"Filter by label name."
|
|
43
|
+
else:
|
|
44
|
+
help_text = f"Filter by label name of type '{self.label_type}'."
|
|
45
|
+
|
|
46
|
+
if self.action_short_name is None:
|
|
47
|
+
self._action = parser.add_argument(self.action_name, type=str,
|
|
48
|
+
required=self.is_required, help=help_text)
|
|
49
|
+
else:
|
|
50
|
+
self._action = parser.add_argument(self.action_short_name, self.action_name, type=str,
|
|
51
|
+
required=self.is_required, help=help_text)
|
|
52
|
+
|
|
53
|
+
def execute(self, args: str, org: 'pylo.Organization', padding: str = ''):
|
|
54
|
+
print(f"{padding}{self.action_name}:", end="")
|
|
55
|
+
if args is None:
|
|
56
|
+
print(" None")
|
|
57
|
+
return
|
|
58
|
+
|
|
59
|
+
if self.allow_multiple:
|
|
60
|
+
label_names = args.split(",")
|
|
61
|
+
else:
|
|
62
|
+
label_names = [args]
|
|
63
|
+
|
|
64
|
+
missing_labels: List[str] = []
|
|
65
|
+
|
|
66
|
+
label_objects = org.LabelStore.find_label_by_name(label_names, label_type=self.label_type, missing_labels_names=missing_labels)
|
|
67
|
+
if len(missing_labels) > 0:
|
|
68
|
+
raise pylo.PyloEx(f"Could not find labels: {pylo.string_list_to_text(missing_labels)}, please check their spelling, case and type.")
|
|
69
|
+
|
|
70
|
+
# just in case make sure it's a list of unique labels
|
|
71
|
+
self.results = list(set(label_objects))
|
|
72
|
+
self.results_as_dict_by_href = {label.href: label for label in self.results}
|
|
73
|
+
print(f" {pylo.string_list_to_text(self.results)}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def filter_workloads_matching_labels(self, workloads: List['pylo.Workload']) -> List['pylo.Workload']:
|
|
77
|
+
if self.results is None:
|
|
78
|
+
return workloads
|
|
79
|
+
|
|
80
|
+
# we must group Labels by their type first
|
|
81
|
+
labels_dict_by_type: Dict[str, List[pylo.Label]] = {}
|
|
82
|
+
for label in self.results:
|
|
83
|
+
if labels_dict_by_type.get(label.type) is None:
|
|
84
|
+
labels_dict_by_type[label.type] = [label]
|
|
85
|
+
else:
|
|
86
|
+
labels_dict_by_type[label.type].append(label)
|
|
87
|
+
|
|
88
|
+
# now we can filter the workloads, they must match at least one Label per type
|
|
89
|
+
filtered_workloads = []
|
|
90
|
+
for workload in workloads:
|
|
91
|
+
for label_type in labels_dict_by_type:
|
|
92
|
+
if workload.get_label(label_type) in labels_dict_by_type[label_type]:
|
|
93
|
+
filtered_workloads.append(workload)
|
|
94
|
+
|
|
95
|
+
return filtered_workloads
|
|
96
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Optional, Dict
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
import argparse
|
|
6
|
+
from .NativeParsers import BaseParser
|
|
7
|
+
|
|
8
|
+
# in case user wants to run this utility while having a version of pylo already installed
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
|
|
11
|
+
|
|
12
|
+
import illumio_pylo as pylo
|
|
13
|
+
from illumio_pylo.cli import commands
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run(forced_command_name: Optional[str] = None):
|
|
17
|
+
|
|
18
|
+
def add_native_parser_to_argparse(parser: argparse.ArgumentParser, native_parsers: object):
|
|
19
|
+
# each property of the native parser is an extension of BaseParser, we need to iterate over them and add them to the argparse parser
|
|
20
|
+
for attr_name in dir(native_parsers):
|
|
21
|
+
attr = getattr(native_parsers, attr_name)
|
|
22
|
+
if isinstance(attr, BaseParser):
|
|
23
|
+
attr.fill_parser(parser)
|
|
24
|
+
|
|
25
|
+
def execute_native_parsers(args: Dict, org: pylo.Organization, native_parsers: object):
|
|
26
|
+
first_native_parser_found = False
|
|
27
|
+
|
|
28
|
+
for attr_name in dir(native_parsers):
|
|
29
|
+
attr = getattr(native_parsers, attr_name)
|
|
30
|
+
if isinstance(attr, BaseParser):
|
|
31
|
+
if first_native_parser_found is False:
|
|
32
|
+
first_native_parser_found = True
|
|
33
|
+
print(" * Native CLI arguments parsing...")
|
|
34
|
+
attr.execute(args[attr.get_arg_name()], org, padding=' ')
|
|
35
|
+
|
|
36
|
+
parser = argparse.ArgumentParser(description='TODO LATER')
|
|
37
|
+
parser.add_argument('--pce', type=str, required=False,
|
|
38
|
+
help='hostname of the PCE')
|
|
39
|
+
parser.add_argument('--debug', action='store_true',
|
|
40
|
+
help='Enables extra debugging output in Pylo framework')
|
|
41
|
+
parser.add_argument('--use-cache', action='store_true',
|
|
42
|
+
help='For developers only')
|
|
43
|
+
|
|
44
|
+
selected_command = None
|
|
45
|
+
|
|
46
|
+
if forced_command_name is None:
|
|
47
|
+
sub_parsers = parser.add_subparsers(dest='command', required=True)
|
|
48
|
+
for command in commands.available_commands.values():
|
|
49
|
+
sub_parser = sub_parsers.add_parser(command.name, help='')
|
|
50
|
+
command.fill_parser(sub_parser)
|
|
51
|
+
if command.native_parsers is not None:
|
|
52
|
+
add_native_parser_to_argparse(sub_parser, command.native_parsers)
|
|
53
|
+
else:
|
|
54
|
+
for command in commands.available_commands.values():
|
|
55
|
+
if forced_command_name is not None and command.name != forced_command_name:
|
|
56
|
+
continue
|
|
57
|
+
command.fill_parser(parser)
|
|
58
|
+
if command.native_parsers is not None:
|
|
59
|
+
add_native_parser_to_argparse(parser, command.native_parsers)
|
|
60
|
+
selected_command = command
|
|
61
|
+
|
|
62
|
+
args = vars(parser.parse_args())
|
|
63
|
+
|
|
64
|
+
if args['debug']:
|
|
65
|
+
pylo.log_set_debug()
|
|
66
|
+
|
|
67
|
+
credential_profile_name = args['pce']
|
|
68
|
+
settings_use_cache = args['use_cache']
|
|
69
|
+
org: Optional[pylo.Organization] = None
|
|
70
|
+
|
|
71
|
+
# We are getting the command object associated to the command name if it was not already set (via forced_command_name)
|
|
72
|
+
if selected_command is None:
|
|
73
|
+
selected_command = commands.available_commands[args['command']]
|
|
74
|
+
if selected_command is None:
|
|
75
|
+
raise pylo.PyloEx("Cannot find command named '{}'".format(args['command']))
|
|
76
|
+
|
|
77
|
+
connector: Optional[pylo.APIConnector] = None
|
|
78
|
+
config_data = None
|
|
79
|
+
|
|
80
|
+
if not selected_command.credentials_manager_mode:
|
|
81
|
+
# credential_profile_name is required for all commands except the credential manager
|
|
82
|
+
if credential_profile_name is None:
|
|
83
|
+
raise pylo.PyloEx("The --pce argument is required for this command")
|
|
84
|
+
if settings_use_cache:
|
|
85
|
+
print(" * Loading objects from cached PCE '{}' data... ".format(credential_profile_name), end="", flush=True)
|
|
86
|
+
org = pylo.Organization.get_from_cache_file(credential_profile_name)
|
|
87
|
+
print("OK!")
|
|
88
|
+
connector = pylo.APIConnector.create_from_credentials_in_file(credential_profile_name, request_if_missing=False)
|
|
89
|
+
if connector is not None:
|
|
90
|
+
org.connector = connector
|
|
91
|
+
else:
|
|
92
|
+
print(" * Looking for PCE/profile '{}' credentials... ".format(credential_profile_name), end="", flush=True)
|
|
93
|
+
connector = pylo.APIConnector.create_from_credentials_in_file(credential_profile_name, request_if_missing=True)
|
|
94
|
+
print("OK!")
|
|
95
|
+
|
|
96
|
+
print(" * Downloading PCE objects from API... ".format(credential_profile_name), end="", flush=True)
|
|
97
|
+
config_data = connector.get_pce_objects(list_of_objects_to_load=selected_command.load_specific_objects_only)
|
|
98
|
+
print("OK!")
|
|
99
|
+
|
|
100
|
+
org = pylo.Organization(1)
|
|
101
|
+
org.connector = connector
|
|
102
|
+
|
|
103
|
+
if not selected_command.skip_pce_config_loading:
|
|
104
|
+
print(" * Loading objects from PCE '{}' via API... ".format(credential_profile_name), end="", flush=True)
|
|
105
|
+
org.pce_version = connector.get_software_version()
|
|
106
|
+
org.load_from_json(config_data, list_of_objects_to_load=selected_command.load_specific_objects_only)
|
|
107
|
+
print("OK!")
|
|
108
|
+
|
|
109
|
+
print()
|
|
110
|
+
if not selected_command.skip_pce_config_loading:
|
|
111
|
+
print(" * PCE statistics: ")
|
|
112
|
+
print(org.stats_to_str(padding=' '))
|
|
113
|
+
|
|
114
|
+
print(flush=True)
|
|
115
|
+
|
|
116
|
+
print("**** {} UTILITY ****".format(selected_command.name.upper()), flush=True)
|
|
117
|
+
if selected_command.native_parsers is None:
|
|
118
|
+
native_parsers = None
|
|
119
|
+
else:
|
|
120
|
+
native_parsers = selected_command.native_parsers
|
|
121
|
+
execute_native_parsers(args, org, native_parsers)
|
|
122
|
+
|
|
123
|
+
if native_parsers is not None:
|
|
124
|
+
commands.available_commands[selected_command.name].main(args, org=org, config_data=config_data, connector=connector, native_parsers=native_parsers, pce_cache_was_used=settings_use_cache)
|
|
125
|
+
else:
|
|
126
|
+
commands.available_commands[selected_command.name].main(args, org=org, config_data=config_data, connector=connector, pce_cache_was_used=settings_use_cache)
|
|
127
|
+
|
|
128
|
+
print()
|
|
129
|
+
print("**** END OF {} UTILITY ****".format(selected_command.name.upper()))
|
|
130
|
+
print()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
if __name__ == "__main__":
|
|
134
|
+
run()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from typing import Dict, Optional, List
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Command:
|
|
5
|
+
def __init__(self, name: str, main_func, parser_func, load_specific_objects_only: Optional[List[str]] = None,
|
|
6
|
+
skip_pce_config_loading: bool = False,
|
|
7
|
+
native_parsers_as_class: Optional = None,
|
|
8
|
+
credentials_manager_mode: bool = False):
|
|
9
|
+
self.name: str = name
|
|
10
|
+
self.main = main_func
|
|
11
|
+
self.fill_parser = parser_func
|
|
12
|
+
self.load_specific_objects_only: Optional[List[str]] = load_specific_objects_only
|
|
13
|
+
self.skip_pce_config_loading = skip_pce_config_loading
|
|
14
|
+
self.native_parsers = native_parsers_as_class
|
|
15
|
+
self.credentials_manager_mode = credentials_manager_mode
|
|
16
|
+
available_commands[name] = self
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
available_commands: Dict[str, Command] = {}
|
|
20
|
+
|
|
21
|
+
from .ruleset_export import command_object
|
|
22
|
+
from .workload_used_in_rule_finder import command_object
|
|
23
|
+
from .workload_relabeler import command_object
|
|
24
|
+
from .ven_duplicate_remover import command_object
|
|
25
|
+
from .workload_export import command_object
|
|
26
|
+
from .iplist_import_from_file import command_object
|
|
27
|
+
from .update_pce_objects_cache import command_object
|
|
28
|
+
from .ven_upgrader import command_object
|
|
29
|
+
from .workload_import import command_object
|
|
30
|
+
from .ven_idle_to_visibility import command_object
|
|
31
|
+
from .workload_reset_names_to_null import command_object
|
|
32
|
+
from .credential_manager import command_object
|