illumio-pylo 0.3.1__py3-none-any.whl → 0.3.2__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.
@@ -2,7 +2,7 @@ import json
2
2
  import time
3
3
  import getpass
4
4
 
5
- from .CredentialsManager import is_api_key_encrypted, decrypt_api_key
5
+ from .CredentialsManager import is_api_key_encrypted, decrypt_api_key, CredentialProfile
6
6
  from .JsonPayloadTypes import LabelGroupObjectJsonStructure, LabelObjectCreationJsonStructure, \
7
7
  LabelObjectJsonStructure, LabelObjectUpdateJsonStructure, PCEObjectsJsonStructure, \
8
8
  LabelGroupObjectUpdateJsonStructure, IPListObjectCreationJsonStructure, IPListObjectJsonStructure, \
@@ -108,6 +108,12 @@ class APIConnector:
108
108
  def get_all_object_types():
109
109
  return all_object_types.copy()
110
110
 
111
+ @staticmethod
112
+ def create_from_credentials_object(credentials: CredentialProfile) -> Optional['APIConnector']:
113
+ return APIConnector(credentials.fqdn, credentials.port, credentials.api_user,
114
+ credentials.api_key, skip_ssl_cert_check=not credentials.verify_ssl,
115
+ org_id=credentials.org_id, name=credentials.name)
116
+
111
117
  @staticmethod
112
118
  def create_from_credentials_in_file(fqdn_or_profile_name: str, request_if_missing: bool = False,
113
119
  credential_file: Optional[str] = None) -> Optional['APIConnector']:
@@ -115,9 +121,7 @@ class APIConnector:
115
121
  credentials = pylo.get_credentials_from_file(fqdn_or_profile_name, credential_file)
116
122
 
117
123
  if credentials is not None:
118
- return APIConnector(credentials.fqdn, credentials.port, credentials.api_user,
119
- credentials.api_key, skip_ssl_cert_check=not credentials.verify_ssl,
120
- org_id=credentials.org_id, name=credentials.name)
124
+ return APIConnector.create_from_credentials_object(credentials)
121
125
 
122
126
  if not request_if_missing:
123
127
  return None
@@ -362,7 +366,7 @@ class APIConnector:
362
366
  else:
363
367
  raise pylo.PyloEx("Unsupported object type '{}'".format(object_type))
364
368
 
365
- def get_pce_objects(self, include_deleted_workloads=False, list_of_objects_to_load: Optional[List[str]] = None):
369
+ def get_pce_objects(self, include_deleted_workloads=False, list_of_objects_to_load: Optional[List[str]] = None, force_async_mode=False):
366
370
 
367
371
  objects_to_load = {}
368
372
  if list_of_objects_to_load is not None:
@@ -389,7 +393,7 @@ class APIConnector:
389
393
  errors = []
390
394
  thread_queue = Queue()
391
395
 
392
- def get_objects(q: Queue, thread_num: int):
396
+ def get_objects(q: Queue, thread_num: int, force_async_mode=False):
393
397
  while True:
394
398
  object_type, errors = q.get()
395
399
  try:
@@ -397,7 +401,7 @@ class APIConnector:
397
401
  q.task_done()
398
402
  continue
399
403
  if object_type == 'workloads':
400
- if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls:
404
+ if self.get_objects_count_by_type(object_type) > default_max_objects_for_sync_calls or force_async_mode:
401
405
  data['workloads'] = self.objects_workload_get(include_deleted=include_deleted_workloads)
402
406
  else:
403
407
  data['workloads'] = self.objects_workload_get(include_deleted=include_deleted_workloads, async_mode=False, max_results=default_max_objects_for_sync_calls)
@@ -456,7 +460,7 @@ class APIConnector:
456
460
  q.task_done()
457
461
 
458
462
  for i in range(threads_count):
459
- worker = Thread(target=get_objects, args=(thread_queue, i))
463
+ worker = Thread(target=get_objects, args=(thread_queue, i, force_async_mode,))
460
464
  worker.daemon = True
461
465
  worker.start()
462
466
 
@@ -97,7 +97,7 @@ def get_all_credentials_from_file(credential_file: str ) -> List[CredentialProfi
97
97
 
98
98
 
99
99
  def get_credentials_from_file(fqdn_or_profile_name: str = None,
100
- credential_file: str = None) -> CredentialProfile:
100
+ credential_file: str = None, fail_with_an_exception=True) -> Optional[CredentialProfile]:
101
101
 
102
102
  if fqdn_or_profile_name is None:
103
103
  log.debug("No fqdn_or_profile_name provided, profile_name=default will be used")
@@ -121,9 +121,12 @@ def get_credentials_from_file(fqdn_or_profile_name: str = None,
121
121
  if credential_profile.fqdn.lower() == fqdn_or_profile_name.lower():
122
122
  return credential_profile
123
123
 
124
- raise PyloEx("No profile found in credential file '{}' with fqdn: {}".
124
+ if fail_with_an_exception:
125
+ raise PyloEx("No profile found in credential file '{}' with fqdn: {}".
125
126
  format(credential_file, fqdn_or_profile_name))
126
127
 
128
+ return None
129
+
127
130
 
128
131
  def list_potential_credential_files() -> List[str]:
129
132
  """
@@ -204,9 +207,7 @@ def create_credential_in_default_file(data: CredentialFileEntry) -> str:
204
207
  return file_path
205
208
 
206
209
 
207
- def encrypt_api_key_with_paramiko_key(ssh_key: paramiko.AgentKey, api_key: str) -> str:
208
-
209
-
210
+ def encrypt_api_key_with_paramiko_ssh_key_fernet(ssh_key: paramiko.AgentKey, api_key: str) -> str:
210
211
  def encrypt(raw: str, key: bytes) -> bytes:
211
212
  """
212
213
 
@@ -220,7 +221,7 @@ def encrypt_api_key_with_paramiko_key(ssh_key: paramiko.AgentKey, api_key: str)
220
221
 
221
222
 
222
223
  # generate a random 128bit key
223
- session_key_to_sign = os.urandom(16)
224
+ session_key_to_sign = os.urandom(32)
224
225
 
225
226
  signed_message = ssh_key.sign_ssh_data(session_key_to_sign)
226
227
 
@@ -236,7 +237,7 @@ def encrypt_api_key_with_paramiko_key(ssh_key: paramiko.AgentKey, api_key: str)
236
237
  return api_key
237
238
 
238
239
 
239
- def decrypt_api_key_with_paramiko_key(encrypted_api_key_payload: str) -> str:
240
+ def decrypt_api_key_with_paramiko_ssh_key_fernet(encrypted_api_key_payload: str) -> str:
240
241
  def decrypt(token_b64_encoded: str, key: bytes):
241
242
  f = Fernet(base64.urlsafe_b64encode(key))
242
243
  return f.decrypt(token_b64_encoded).decode('utf-8')
@@ -277,7 +278,7 @@ def decrypt_api_key(encrypted_api_key_payload: str) -> str:
277
278
  if not encrypted_api_key_payload.startswith("$encrypted$:"):
278
279
  raise PyloEx("Invalid encrypted API key format")
279
280
  if encrypted_api_key_payload.startswith("$encrypted$:ssh-Fernet:"):
280
- return decrypt_api_key_with_paramiko_key(encrypted_api_key_payload)
281
+ return decrypt_api_key_with_paramiko_ssh_key_fernet(encrypted_api_key_payload)
281
282
 
282
283
  raise PyloEx("Unsupported encryption method: {}".format(encrypted_api_key_payload.split(":")[1]))
283
284
 
@@ -255,10 +255,9 @@ class ArraysToExcel:
255
255
  new_line.append('=HYPERLINK("{}", "{}")'.format(item,self._headers[item_index].url_text))
256
256
  else:
257
257
  new_line.append(item)
258
-
259
- length = find_length(new_line[item_index])
260
- if length > columns_max_width[item_index]:
261
- columns_max_width[item_index] = length
258
+ length = find_length(new_line[item_index])
259
+ if length > columns_max_width[item_index]:
260
+ columns_max_width[item_index] = length
262
261
 
263
262
 
264
263
  xls_data.append(new_line)
illumio_pylo/IPMap.py CHANGED
@@ -71,6 +71,15 @@ class IP4Map:
71
71
  self._entries.append(new_entry)
72
72
  self.sort_and_recalculate()
73
73
 
74
+ def add_another_map(self, another_map: 'IP4Map', skip_recalculation=False):
75
+ for entry in another_map._entries:
76
+ self._entries.append(entry)
77
+
78
+ if skip_recalculation:
79
+ return
80
+
81
+ self.sort_and_recalculate()
82
+
74
83
  def intersection(self, another_map: 'IP4Map'):
75
84
 
76
85
  inverted_map = IP4Map()
@@ -13,7 +13,7 @@ class LabeledObject:
13
13
  def get_labels_dict(self):
14
14
  return self._labels.copy()
15
15
 
16
- def get_labels(self):
16
+ def get_labels(self) -> Iterable['pylo.Label']:
17
17
  return self._labels.values()
18
18
 
19
19
  def set_label(self, label :'pylo.Label'):
@@ -135,7 +135,10 @@ class Organization:
135
135
  object_to_load = pylo.APIConnector.get_all_object_types()
136
136
 
137
137
  if self.pce_version is None:
138
- raise pylo.PyloEx('Organization has no "version" specified')
138
+ if 'pce_version' in data:
139
+ self.pce_version = pylo.SoftwareVersion(data['pce_version'])
140
+ else:
141
+ raise pylo.PyloEx('Organization has no "version" specified')
139
142
 
140
143
  self.LabelStore.load_label_dimensions(data.get('label_dimensions'))
141
144
 
illumio_pylo/Rule.py CHANGED
@@ -4,7 +4,7 @@ from typing import Optional, List, Union, Dict, Any, NewType
4
4
  import illumio_pylo as pylo
5
5
  from .API.JsonPayloadTypes import RuleServiceReferenceObjectJsonStructure, RuleDirectServiceReferenceObjectJsonStructure
6
6
  from illumio_pylo import Workload, Label, LabelGroup, Ruleset, Referencer, SecurityPrincipal, PyloEx, \
7
- Service, nice_json, string_list_to_text, find_connector_or_die, VirtualService, IPList
7
+ Service, nice_json, string_list_to_text, find_connector_or_die, VirtualService, IPList, PortMap
8
8
 
9
9
  RuleActorsAcceptableTypes = NewType('RuleActorsAcceptableTypes', Union[Workload, Label, LabelGroup, IPList, VirtualService])
10
10
 
@@ -250,6 +250,7 @@ class RuleServiceContainer(pylo.Referencer):
250
250
  self.owner = owner
251
251
  self._items: Dict[Service, Service] = {}
252
252
  self._direct_services: List[DirectServiceInRule] = []
253
+ self._cached_port_map: Optional[PortMap] = None
253
254
 
254
255
  def load_from_json(self, data_list: List[RuleServiceReferenceObjectJsonStructure|RuleDirectServiceReferenceObjectJsonStructure]):
255
256
  ss_store = self.owner.owner.owner.owner.ServiceStore # make it a local variable for fast lookups
@@ -295,6 +296,8 @@ class RuleServiceContainer(pylo.Referencer):
295
296
  :param service:
296
297
  :return: True if the service was removed, False if it was not found
297
298
  """
299
+ self._cached_port_map = None
300
+
298
301
  for i in range(0, len(self._direct_services)):
299
302
  if self._direct_services[i] is service:
300
303
  del(self._direct_services[i])
@@ -302,6 +305,8 @@ class RuleServiceContainer(pylo.Referencer):
302
305
  return False
303
306
 
304
307
  def add_direct_service(self, service: DirectServiceInRule) -> bool:
308
+ self._cached_port_map = None
309
+
305
310
  for member in self._direct_services:
306
311
  if service is member:
307
312
  return False
@@ -352,6 +357,27 @@ class RuleServiceContainer(pylo.Referencer):
352
357
 
353
358
  self.owner.raw_json.update(data)
354
359
 
360
+ def get_port_map(self) -> PortMap:
361
+ """
362
+ Get a PortMap object with all ports and protocols from all services in this container
363
+ :return:
364
+ """
365
+ if self._cached_port_map is not None:
366
+ return self._cached_port_map
367
+
368
+ result = PortMap()
369
+ for service in self._items.values():
370
+ for entry in service.entries:
371
+ result.add(entry.protocol, entry.port, entry.to_port, skip_recalculation=True)
372
+ for direct in self._direct_services:
373
+ result.add(direct.protocol, direct.port, direct.to_port, skip_recalculation=True)
374
+
375
+ result.merge_overlapping_maps()
376
+
377
+ self._cached_port_map = result
378
+
379
+ return result
380
+
355
381
 
356
382
  class RuleHostContainer(pylo.Referencer):
357
383
  def __init__(self, owner: 'pylo.Rule', name: str):
illumio_pylo/Ruleset.py CHANGED
@@ -101,29 +101,17 @@ class RulesetScopeEntry:
101
101
 
102
102
 
103
103
  def to_string(self, label_separator = '|', use_href=False):
104
- string = 'All' + label_separator
105
- if self.app_label is not None:
106
- if use_href:
107
- string = self.app_label.href + label_separator
108
- else:
109
- string = self.app_label.name + label_separator
110
-
111
- if self.env_label is None:
112
- string += 'All' + label_separator
113
- else:
114
- if use_href:
115
- string += self.env_label.href + label_separator
116
- else:
117
- string += self.env_label.name + label_separator
118
-
119
- if self.loc_label is None:
120
- string += 'All'
121
- else:
122
- if use_href:
123
- string += self.loc_label.href
104
+ string = ''
105
+ for label_type in self.owner.owner.owner.owner.LabelStore.label_types:
106
+ label = self._labels.get(label_type)
107
+ if len(string) > 0:
108
+ string += label_separator
109
+ if label is None:
110
+ string += 'All'
111
+ elif use_href:
112
+ string += label.href
124
113
  else:
125
- string += self.loc_label.name
126
-
114
+ string += label.name
127
115
  return string
128
116
 
129
117
  def is_all_all_all(self):
@@ -156,10 +144,6 @@ class RulesetScopeEntry:
156
144
 
157
145
  class Ruleset:
158
146
 
159
- name: str
160
- href: Optional[str]
161
- description: str
162
-
163
147
  def __init__(self, owner: 'pylo.RulesetStore'):
164
148
  self.owner: 'pylo.RulesetStore' = owner
165
149
  self.href: Optional[str] = None
@@ -169,6 +153,7 @@ class Ruleset:
169
153
  # must keep an ordered list of rules while the dict by href is there for quick searches
170
154
  self._rules_by_href: Dict[str, 'pylo.Rule'] = {}
171
155
  self._rules: List['pylo.Rule'] = []
156
+ self.disabled: bool = False
172
157
 
173
158
  @property
174
159
  def rules(self):
@@ -211,6 +196,9 @@ class Ruleset:
211
196
  raise pylo.PyloEx("Cannot find Ruleset href in JSON data: \n" + pylo.Helpers.nice_json(data))
212
197
  self.href = data['href']
213
198
 
199
+ if 'enabled' in data:
200
+ self.disabled = not data['enabled']
201
+
214
202
  scopes_json = data.get('scopes')
215
203
  if scopes_json is None:
216
204
  raise pylo.PyloEx("Cannot find Ruleset scope in JSON data: \n" + pylo.Helpers.nice_json(data))
@@ -287,7 +275,7 @@ class Ruleset:
287
275
  if pce_fqdn is None or pce_port is None:
288
276
  connector = pylo.find_connector_or_die(self)
289
277
  if pce_fqdn is None:
290
- pce_fqdn = connector.hostname
278
+ pce_fqdn = connector.fqdn
291
279
  if pce_port is None:
292
280
  pce_port = connector.port
293
281
 
illumio_pylo/Service.py CHANGED
@@ -7,10 +7,20 @@ from typing import *
7
7
 
8
8
  class PortMap:
9
9
  def __init__(self):
10
- self._tcp_map = []
11
- self._udp_map = []
10
+ self._tcp_map: List[List[2]] = [] # [start, end]
11
+ self._udp_map: List[List[2]] = [] # [start, end]
12
12
  self._protocol_map = {}
13
13
 
14
+ def copy(self) -> 'PortMap':
15
+ new_map = PortMap()
16
+ new_map._tcp_map = self._tcp_map.copy()
17
+ new_map._udp_map = self._udp_map.copy()
18
+ new_map._protocol_map = self._protocol_map.copy()
19
+ return new_map
20
+
21
+ def count(self) -> int:
22
+ return len(self._tcp_map) + len(self._udp_map) + len(self._protocol_map)
23
+
14
24
  def add(self, protocol, start_port: int, end_port: int = None, skip_recalculation=False):
15
25
 
16
26
  proto = None
@@ -31,67 +41,54 @@ class PortMap:
31
41
  return
32
42
 
33
43
  if start_port is None:
44
+ start_port = end_port
45
+
46
+ if end_port is None:
34
47
  end_port = start_port
35
48
 
36
- new_entry = [start_port, end_port]
49
+ if proto == 6:
50
+ self._tcp_map.append([start_port, end_port])
51
+ else:
52
+ self._udp_map.append([start_port, end_port])
37
53
 
38
54
  if not skip_recalculation:
39
55
  self.merge_overlapping_maps()
40
56
 
41
- def merge_overlapping_maps(self):
42
- self._sort_maps()
43
-
44
- new_map = []
45
-
46
- cur_entry = None
47
-
48
- for original_entry in self._tcp_map:
49
- if cur_entry is None:
50
- cur_entry = original_entry
51
- continue
52
-
53
- cur_start = cur_entry[0]
54
- cur_end = cur_entry[1]
55
- new_start = original_entry[0]
56
- new_end = original_entry[1]
57
+ def to_list_of_objects(self) -> List[Dict]:
58
+ result = []
59
+ for entry in self._tcp_map:
60
+ result.append({'proto': 6, 'port': entry[0], 'to_port': entry[1]})
57
61
 
58
- if new_start > cur_end + 1:
59
- new_map.append(cur_entry)
60
- continue
62
+ for entry in self._udp_map:
63
+ result.append({'proto': 17, 'port': entry[0], 'to_port': entry[1]})
61
64
 
62
- if new_end > cur_end:
63
- cur_entry[1] = new_end
65
+ for proto in self._protocol_map:
66
+ result.append({'proto': proto})
64
67
 
65
- if cur_entry is not None:
66
- self._tcp_map = []
67
- else:
68
- new_map.append(cur_entry)
69
- self._tcp_map = new_map
70
-
71
- new_map = []
72
-
73
- for original_entry in self._udp_map:
74
- if cur_entry is None:
75
- cur_entry = original_entry
76
- continue
77
-
78
- cur_start = cur_entry[0]
79
- cur_end = cur_entry[1]
80
- new_start = original_entry[0]
81
- new_end = original_entry[1]
82
-
83
- if new_start > cur_end + 1:
84
- new_map.append(cur_entry)
85
- continue
68
+ return result
86
69
 
87
- if new_end > cur_end:
88
- cur_entry[1] = new_end
70
+ def merge_overlapping_maps(self):
71
+ self._sort_maps()
89
72
 
90
- if cur_entry is not None:
91
- self._udp_map = []
92
- else:
93
- new_map.append(cur_entry)
94
- self._udp_map = new_map
73
+ def merge_maps(map_list):
74
+ new_list = []
75
+ current = None
76
+ for entry in map_list:
77
+ if current is None:
78
+ current = entry
79
+ continue
80
+
81
+ if entry[0] <= current[1] + 1:
82
+ current[1] = entry[1]
83
+ else:
84
+ new_list.append(current)
85
+ current = entry
86
+ if current is not None:
87
+ new_list.append(current)
88
+ return new_list
89
+
90
+ self._tcp_map = merge_maps(self._tcp_map)
91
+ self._udp_map = merge_maps(self._udp_map)
95
92
 
96
93
  def _sort_maps(self):
97
94
  def first_entry(my_list):
illumio_pylo/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.3.1"
1
+ __version__ = "0.3.2"
2
2
 
3
3
  import os
4
4
  import sys
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  from typing import Optional, Dict
3
-
3
+ import time
4
+ import datetime
4
5
  import sys
5
6
  import argparse
6
7
  from .NativeParsers import BaseParser
@@ -15,6 +16,8 @@ from illumio_pylo.cli import commands
15
16
 
16
17
  def run(forced_command_name: Optional[str] = None):
17
18
 
19
+ cli_start_time = datetime.datetime.now()
20
+
18
21
  def add_native_parser_to_argparse(parser: argparse.ArgumentParser, native_parsers: object):
19
22
  # 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
23
  for attr_name in dir(native_parsers):
@@ -33,9 +36,11 @@ def run(forced_command_name: Optional[str] = None):
33
36
  print(" * Native CLI arguments parsing...")
34
37
  attr.execute(args[attr.get_arg_name()], org, padding=' ')
35
38
 
36
- parser = argparse.ArgumentParser(description='TODO LATER')
39
+ parser = argparse.ArgumentParser(description='PYLO-CLI: Illumio API&More Command Line Interface')
37
40
  parser.add_argument('--pce', type=str, required=False,
38
41
  help='hostname of the PCE')
42
+ parser.add_argument('--force-async-mode', action='store_true',
43
+ help='Forces the command to run async API queries when required (large PCEs which timeout on specific queries)')
39
44
  parser.add_argument('--debug', action='store_true',
40
45
  help='Enables extra debugging output in Pylo framework')
41
46
  parser.add_argument('--use-cache', action='store_true',
@@ -89,14 +94,16 @@ def run(forced_command_name: Optional[str] = None):
89
94
 
90
95
  print("* Started Pylo CLI version {}".format(pylo.__version__))
91
96
 
97
+
92
98
  if not selected_command.credentials_manager_mode:
99
+ timer_start = time.perf_counter()
93
100
  # credential_profile_name is required for all commands except the credential manager
94
101
  if credential_profile_name is None:
95
102
  raise pylo.PyloEx("The --pce argument is required for this command")
96
103
  if settings_use_cache:
97
104
  print(" * Loading objects from cached PCE '{}' data... ".format(credential_profile_name), end="", flush=True)
98
105
  org = pylo.Organization.get_from_cache_file(credential_profile_name)
99
- print("OK!")
106
+ print("OK! (execution time: {:.2f} seconds)".format(time.perf_counter() - timer_start))
100
107
  connector = pylo.APIConnector.create_from_credentials_in_file(credential_profile_name, request_if_missing=False)
101
108
  if connector is not None:
102
109
  org.connector = connector
@@ -106,8 +113,9 @@ def run(forced_command_name: Optional[str] = None):
106
113
  print("OK!")
107
114
 
108
115
  print(" * Downloading PCE objects from API... ".format(credential_profile_name), end="", flush=True)
109
- config_data = connector.get_pce_objects(list_of_objects_to_load=selected_command.load_specific_objects_only)
110
- print("OK!")
116
+ config_data = connector.get_pce_objects(list_of_objects_to_load=selected_command.load_specific_objects_only, force_async_mode=args['force_async_mode'])
117
+ timer_download_finished = time.perf_counter()
118
+ print("OK! (execution time: {:.2f} seconds)".format(timer_download_finished - timer_start))
111
119
 
112
120
  org = pylo.Organization(1)
113
121
  org.connector = connector
@@ -116,7 +124,7 @@ def run(forced_command_name: Optional[str] = None):
116
124
  print(" * Loading objects from PCE '{}' via API... ".format(credential_profile_name), end="", flush=True)
117
125
  org.pce_version = connector.get_software_version()
118
126
  org.load_from_json(config_data, list_of_objects_to_load=selected_command.load_specific_objects_only)
119
- print("OK!")
127
+ print("OK! (execution time: {:.2f} seconds)".format(time.perf_counter() - timer_download_finished))
120
128
 
121
129
  print()
122
130
  if not selected_command.skip_pce_config_loading:
@@ -126,6 +134,7 @@ def run(forced_command_name: Optional[str] = None):
126
134
  print(flush=True)
127
135
 
128
136
  print("**** {} UTILITY ****".format(selected_command.name.upper()), flush=True)
137
+ command_execution_time_start = time.perf_counter()
129
138
  if selected_command.native_parsers is None:
130
139
  native_parsers = None
131
140
  else:
@@ -138,7 +147,11 @@ def run(forced_command_name: Optional[str] = None):
138
147
  commands.available_commands[selected_command.name].main(args, org=org, config_data=config_data, connector=connector, pce_cache_was_used=settings_use_cache)
139
148
 
140
149
  print()
150
+ cli_end_time = datetime.datetime.now()
141
151
  print("**** END OF {} UTILITY ****".format(selected_command.name.upper()))
152
+ print("Command Specific Execution time: {:.2f} seconds".format(time.perf_counter() - command_execution_time_start))
153
+ print("CLI started at {} and finished at {}".format(cli_start_time, cli_end_time))
154
+ print("CLI Total Execution time: {}".format(cli_end_time - cli_start_time))
142
155
  print()
143
156
 
144
157
 
@@ -1,3 +1,5 @@
1
+ import sys
2
+
1
3
  from prettytable import PrettyTable
2
4
  import argparse
3
5
  import os
@@ -7,7 +9,8 @@ import paramiko
7
9
  import illumio_pylo as pylo
8
10
  import click
9
11
  from illumio_pylo.API.CredentialsManager import get_all_credentials, create_credential_in_file, CredentialFileEntry, \
10
- create_credential_in_default_file, encrypt_api_key_with_paramiko_key, decrypt_api_key_with_paramiko_key
12
+ create_credential_in_default_file, encrypt_api_key_with_paramiko_ssh_key_fernet, decrypt_api_key_with_paramiko_ssh_key_fernet, \
13
+ get_credentials_from_file
11
14
 
12
15
  from illumio_pylo import log
13
16
  from . import Command
@@ -20,19 +23,24 @@ objects_load_filter = None
20
23
  def fill_parser(parser: argparse.ArgumentParser):
21
24
  sub_parser = parser.add_subparsers(dest='sub_command', required=True)
22
25
  list_parser = sub_parser.add_parser('list', help='List all credentials')
23
- create_parser = sub_parser.add_parser('create', help='Create a new credential')
24
26
 
25
- create_parser.add_argument('--name', required=True, type=str,
26
- help='Name of the credential')
27
- create_parser.add_argument('--fqdn', required=True, type=str,
27
+ test_parser = sub_parser.add_parser('test', help='Test a credential')
28
+ test_parser.add_argument('--name', required=False, type=str, default=None,
29
+ help='Name of the credential profile to test')
30
+
31
+
32
+ create_parser = sub_parser.add_parser('create', help='Create a new credential')
33
+ create_parser.add_argument('--name', required=False, type=str, default=None,
34
+ help='Name of the credential')
35
+ create_parser.add_argument('--fqdn', required=False, type=str, default=None,
28
36
  help='FQDN of the PCE')
29
- create_parser.add_argument('--port', required=True, type=int,
37
+ create_parser.add_argument('--port', required=False, type=int, default=None,
30
38
  help='Port of the PCE')
31
- create_parser.add_argument('--org', required=True, type=int,
39
+ create_parser.add_argument('--org', required=False, type=int, default=None,
32
40
  help='Organization ID')
33
- create_parser.add_argument('--api-user', required=True, type=str,
41
+ create_parser.add_argument('--api-user', required=False, type=str, default=None,
34
42
  help='API user')
35
- create_parser.add_argument('--verify-ssl', required=True, type=bool,
43
+ create_parser.add_argument('--verify-ssl', required=False, type=bool, default=None,
36
44
  help='Verify SSL')
37
45
 
38
46
 
@@ -54,14 +62,10 @@ def __main(args, **kwargs):
54
62
  )
55
63
 
56
64
  elif args['sub_command'] == 'create':
57
- print("Recap:")
58
- print("Name: {}".format(args['name']))
59
- print("FQDN: {}".format(args['fqdn']))
60
- print("Port: {}".format(args['port']))
61
- print("Org ID: {}".format(args['org']))
62
- print("API User: {}".format(args['api_user']))
63
- print("Verify SSL: {}".format(args['verify_ssl']))
64
- print()
65
+
66
+ wanted_name = args['name']
67
+ if wanted_name is None:
68
+ wanted_name = click.prompt('> Input a Profile Name (ie: prod-pce)', type=str)
65
69
 
66
70
  print("* Checking if a credential with the same name already exists...", flush=True, end="")
67
71
  credentials = get_all_credentials()
@@ -70,20 +74,52 @@ def __main(args, **kwargs):
70
74
  raise pylo.PyloEx("A credential named '{}' already exists".format(args['name']))
71
75
  print("OK!")
72
76
 
77
+ wanted_fqdn = args['fqdn']
78
+ if wanted_fqdn is None:
79
+ wanted_fqdn = click.prompt('> PCE FQDN (ie: pce1.mycompany.com)', type=str)
80
+
81
+ wanted_port = args['port']
82
+ if wanted_port is None:
83
+ wanted_port = click.prompt('> PCE Port (ie: 8443)', type=int)
84
+
85
+ wanted_org = args['org']
86
+ if wanted_org is None:
87
+ wanted_org = click.prompt('> Organization ID', type=int)
88
+
89
+ wanted_api_user = args['api_user']
90
+ if wanted_api_user is None:
91
+ wanted_api_user = click.prompt('> API User', type=str)
92
+
93
+ wanted_verify_ssl = args['verify_ssl']
94
+ if wanted_verify_ssl is None:
95
+ wanted_verify_ssl = click.prompt('> Verify SSL/TLS certificate? Y/N', type=bool)
96
+
97
+
98
+ print()
99
+ print("Recap:")
100
+ print("Name: {}".format(wanted_name))
101
+ print("FQDN: {}".format(wanted_fqdn))
102
+ print("Port: {}".format(wanted_port))
103
+ print("Org ID: {}".format(wanted_org))
104
+ print("API User: {}".format(wanted_api_user))
105
+ print("Verify SSL: {}".format(wanted_verify_ssl))
106
+ print()
107
+
108
+
73
109
  # prompt of API key from user input, single line, hidden
74
110
  api_key = click.prompt('> API Key', hide_input=True)
75
111
 
76
112
  credentials_data: CredentialFileEntry = {
77
- "name": args['name'],
78
- "fqdn": args['fqdn'],
79
- "port": args['port'],
80
- "org_id": args['org'],
81
- "api_user": args['api_user'],
82
- "verify_ssl": args['verify_ssl'],
113
+ "name": wanted_name,
114
+ "fqdn": wanted_fqdn,
115
+ "port": wanted_port,
116
+ "org_id": wanted_org,
117
+ "api_user": wanted_api_user,
118
+ "verify_ssl": wanted_verify_ssl,
83
119
  "api_key": api_key
84
120
  }
85
121
 
86
- encrypt_api_key = click.prompt('> Encrypt API? Y/N', type=bool)
122
+ encrypt_api_key = click.prompt('> Encrypt API (requires an SSH agent running and an RSA or Ed25519 key) ? Y/N', type=bool)
87
123
  if encrypt_api_key:
88
124
  print("Available keys (ECDSA NISTPXXX keys and a few others are not supported and will be filtered out):")
89
125
  ssh_keys = paramiko.Agent().get_keys()
@@ -100,10 +136,10 @@ def __main(args, **kwargs):
100
136
  selected_ssh_key.get_fingerprint().hex(),
101
137
  selected_ssh_key.comment))
102
138
  print(" * encrypting API key with selected key (you may be prompted by your SSH agent for confirmation or PIN code) ...", flush=True, end="")
103
- encrypted_api_key = encrypt_api_key_with_paramiko_key(ssh_key=selected_ssh_key, api_key=api_key)
139
+ encrypted_api_key = encrypt_api_key_with_paramiko_ssh_key_fernet(ssh_key=selected_ssh_key, api_key=api_key)
104
140
  print("OK!")
105
141
  print(" * trying to decrypt the encrypted API key...", flush=True, end="")
106
- decrypted_api_key = decrypt_api_key_with_paramiko_key(encrypted_api_key_payload=encrypted_api_key)
142
+ decrypted_api_key = decrypt_api_key_with_paramiko_ssh_key_fernet(encrypted_api_key_payload=encrypted_api_key)
107
143
  if decrypted_api_key != api_key:
108
144
  raise pylo.PyloEx("Decrypted API key does not match original API key")
109
145
  print("OK!")
@@ -122,6 +158,35 @@ def __main(args, **kwargs):
122
158
 
123
159
  print("OK! ({})".format(file_path))
124
160
 
161
+ elif args['sub_command'] == 'test':
162
+ print("* Profile Tester command")
163
+ wanted_name = args['name']
164
+ if wanted_name is None:
165
+ wanted_name = click.prompt('> Input a Profile Name to test (ie: prod-pce)', type=str)
166
+ found_profile = get_credentials_from_file(wanted_name, fail_with_an_exception=False)
167
+ if found_profile is None:
168
+ print("Cannot find a profile named '{}'".format(wanted_name))
169
+ print("Available profiles:")
170
+ credentials = get_all_credentials()
171
+ for credential in credentials:
172
+ print(" - {}".format(credential.name))
173
+ sys.exit(1)
174
+
175
+ print("Selected profile:")
176
+ print(" - Name: {}".format(found_profile.name))
177
+ print(" - FQDN: {}".format(found_profile.fqdn))
178
+ print(" - Port: {}".format(found_profile.port))
179
+ print(" - Org ID: {}".format(found_profile.org_id))
180
+ print(" - API User: {}".format(found_profile.api_user))
181
+ print(" - Verify SSL: {}".format(found_profile.verify_ssl))
182
+
183
+ print("* Testing credential...", flush=True, end="")
184
+ connector = pylo.APIConnector.create_from_credentials_object(found_profile)
185
+ connector.objects_label_dimension_get()
186
+ print("OK!")
187
+
188
+ else:
189
+ raise pylo.PyloEx("Unknown sub-command '{}'".format(args['sub_command']))
125
190
 
126
191
  command_object = Command(command_name, __main, fill_parser, credentials_manager_mode=True)
127
192
 
@@ -1,6 +1,6 @@
1
1
  import argparse
2
2
  import os
3
- from typing import Dict, List
3
+ from typing import Dict, List, Literal
4
4
 
5
5
  import illumio_pylo as pylo
6
6
  from illumio_pylo import ArraysToExcel, ExcelHeader
@@ -12,7 +12,7 @@ command_name = 'rule-export'
12
12
 
13
13
  def fill_parser(parser: argparse.ArgumentParser):
14
14
  parser.add_argument('--format', '-f', required=False, default='excel',choices=['csv', 'excel'], help='Output file format')
15
- parser.add_argument('--output', '-o', required=False, default='.', help='Directory where to save the output file')
15
+ parser.add_argument('--output-dir', '-o', required=False, default='output', help='Directory where to save the output file')
16
16
  parser.add_argument('--prefix-objects-with-type', nargs='?', const=True, default=False,
17
17
  help='Prefix objects with their type (e.g. "label:mylabel")')
18
18
  parser.add_argument('--object-types-as-section', action='store_true', default=False,
@@ -26,44 +26,26 @@ def fill_parser(parser: argparse.ArgumentParser):
26
26
 
27
27
 
28
28
 
29
- def __main(options: Dict, org: pylo.Organization, **kwargs):
30
- csv_report_headers = pylo.ExcelHeaderSet(
31
- [ ExcelHeader(name = 'ruleset', max_width = 40),
32
- ExcelHeader(name = 'scope', max_width = 50),
33
- ExcelHeader(name = 'type', max_width = 10),
34
- ExcelHeader(name = 'consumers', max_width = 80),
35
- ExcelHeader(name = 'providers', max_width = 80),
36
- ExcelHeader(name = 'services', max_width = 30),
37
- ExcelHeader(name = 'options', max_width = 40),
38
- ExcelHeader(name = 'ruleset_url', max_width = 40, wrap_text = False),
39
- ExcelHeader(name = 'ruleset_href', max_width = 30, wrap_text = False)
40
- ])
41
-
42
- setting_prefix_objects_with_type: bool|str = options['prefix_objects_with_type']
29
+ def __main(args: Dict, org: pylo.Organization, **kwargs):
30
+
31
+ setting_prefix_objects_with_type: bool|str = args['prefix_objects_with_type']
32
+ setting_object_types_as_section: bool = args['prefix_objects_with_type']
33
+ settings_output_file_format = args['format']
34
+ settings_output_dir = args['output_dir']
35
+
36
+
43
37
  if setting_prefix_objects_with_type is False:
44
38
  print(" * Prefix for object types are disabled")
45
39
  else:
46
40
  print(" * Prefix for object types are enabled")
47
41
 
48
- setting_object_types_as_section: bool = options['prefix_objects_with_type']
42
+
49
43
  if setting_object_types_as_section is False:
50
44
  print(" * Object types as section are disabled")
51
45
  else:
52
46
  print(" * Object types as section are enabled")
53
47
 
54
- output_file_format = options.get('format')
55
- if output_file_format == "excel":
56
- output_file_extension = ".xlsx"
57
- elif output_file_format == "csv":
58
- output_file_extension = ".csv"
59
- else:
60
- raise Exception("Unknown output file format: %s" % output_file_format)
61
-
62
- output_file_name = options.get('output') + os.sep + make_filename_with_timestamp('rule_export_') + output_file_extension
63
- output_file_name = os.path.abspath(output_file_name)
64
-
65
- csv_report = pylo.ArraysToExcel()
66
- sheet = csv_report.create_sheet('rulesets', csv_report_headers, force_all_wrap_text=True, multivalues_cell_delimiter=',')
48
+ csv_report, output_file_name, sheet = prepare_csv_report_object(settings_output_file_format, settings_output_dir)
67
49
 
68
50
  for ruleset in org.RulesetStore.rulesets:
69
51
  for rule in ruleset.rules_ordered_by_type:
@@ -103,23 +85,47 @@ def __main(options: Dict, org: pylo.Organization, **kwargs):
103
85
  'services': rule.services.members_to_str("\n"),
104
86
  'options': pylo.string_list_to_text(rule_options, "\n"),
105
87
  'ruleset_href': ruleset.href,
106
- 'ruleset_url': ruleset.get_ruleset_url()}
107
- if rule.is_extra_scope():
108
- data['type'] = 'extra'
109
- else:
110
- data['type'] = 'intra'
88
+ 'ruleset_url': ruleset.get_ruleset_url(),
89
+ 'type': 'intra' if rule.is_intra_scope() else 'extra'
90
+ }
91
+
111
92
  sheet.add_line_from_object(data)
112
93
 
113
- if output_file_format == "csv":
94
+ if settings_output_file_format == "csv":
114
95
  print(" * Writing export file '{}' ... ".format(output_file_name), end='', flush=True)
115
96
  sheet.write_to_csv(output_file_name)
116
97
  print("DONE")
117
- elif output_file_format == "excel":
98
+ elif settings_output_file_format == "excel":
118
99
  print(" * Writing export file '{}' ... ".format(output_file_name), end='', flush=True)
119
100
  csv_report.write_to_excel(output_file_name)
120
101
  print("DONE")
121
102
  else:
122
- raise pylo.PyloEx("Unknown format: '{}'".format(options['format']))
103
+ raise pylo.PyloEx("Unknown format: '{}'".format(args['format']))
104
+
105
+
106
+ def prepare_csv_report_object(output_file_format: Literal['excel', 'csv'], settings_output_dir: str):
107
+ if output_file_format == "excel":
108
+ output_file_extension = ".xlsx"
109
+ elif output_file_format == "csv":
110
+ output_file_extension = ".csv"
111
+ else:
112
+ raise Exception("Unknown output file format: %s" % output_file_format)
113
+ csv_report_headers = pylo.ExcelHeaderSet(
114
+ [ExcelHeader(name='ruleset', max_width=40),
115
+ ExcelHeader(name='scope', max_width=50),
116
+ ExcelHeader(name='type', max_width=10),
117
+ ExcelHeader(name='consumers', max_width=80),
118
+ ExcelHeader(name='providers', max_width=80),
119
+ ExcelHeader(name='services', max_width=30),
120
+ ExcelHeader(name='options', max_width=40),
121
+ ExcelHeader(name='ruleset_url', max_width=40, wrap_text=False),
122
+ ExcelHeader(name='ruleset_href', max_width=30, wrap_text=False)
123
+ ])
124
+ csv_report = ArraysToExcel()
125
+ sheet = csv_report.create_sheet('rulesets', csv_report_headers, force_all_wrap_text=True,
126
+ multivalues_cell_delimiter=',')
127
+ output_file_name = make_filename_with_timestamp('rule_export_', settings_output_dir) + output_file_extension
128
+ return csv_report, output_file_name, sheet
123
129
 
124
130
 
125
131
  command_object = Command(command_name, __main, fill_parser)
@@ -8,5 +8,9 @@ def make_filename_with_timestamp(prefix: str, output_directory: str = './') -> s
8
8
  if output_directory.startswith('.') or not output_directory.startswith('/') or not output_directory.startswith('\\'):
9
9
  output_directory = os.path.realpath(os.getcwd() + os.path.sep + output_directory)
10
10
 
11
+ # if the directory does not exist, we will create it and its parents if needed
12
+ if not os.path.exists(output_directory):
13
+ os.makedirs(output_directory)
14
+
11
15
  now = datetime.now()
12
16
  return output_directory + os.path.sep + prefix + now.strftime("%Y%m%d-%H%M%S")
@@ -11,14 +11,8 @@ objects_load_filter = ['workloads', 'labels']
11
11
 
12
12
 
13
13
  def fill_parser(parser: argparse.ArgumentParser):
14
- parser.add_argument('--filter-env-label', type=str, required=False, default=None,
15
- help='Filter agents by environment labels (separated by commas)')
16
- parser.add_argument('--filter-loc-label', type=str, required=False, default=None,
17
- help='Filter agents by location labels (separated by commas)')
18
- parser.add_argument('--filter-app-label', type=str, required=False, default=None,
19
- help='Filter agents by application labels (separated by commas)')
20
- parser.add_argument('--filter-role-label', type=str, required=False, default=None,
21
- help='Filter agents by role labels (separated by commas)')
14
+ parser.add_argument('--filter-label', '-fl', action='append',
15
+ help='Only look at workloads matching specified labels')
22
16
 
23
17
  parser.add_argument('--filter-ven-versions', nargs='+', type=str, required=False, default=None,
24
18
  help='Filter agents by versions (separated by spaces)')
@@ -67,58 +61,13 @@ def __main(args, org: pylo.Organization, **kwargs):
67
61
  target_version = pylo.SoftwareVersion(target_version_string)
68
62
 
69
63
  print(" * Parsing filters")
70
-
71
- env_label_list = {}
72
- if args['filter_env_label'] is not None:
73
- print(" * Environment Labels specified")
74
- for raw_label_name in args['filter_env_label'].split(','):
75
- print(" - label named '{}'".format(raw_label_name), end='', flush=True)
76
- label = org.LabelStore.find_label_by_name_and_type(raw_label_name, pylo.label_type_env)
77
- if label is None:
78
- print("NOT FOUND!")
79
- raise pylo.PyloEx("Cannot find label named '{}'".format(raw_label_name))
80
- else:
81
- print("found")
82
- env_label_list[label] = label
83
-
84
- loc_label_list = {}
85
- if args['filter_loc_label'] is not None:
86
- print(" * Location Labels specified")
87
- for raw_label_name in args['filter_loc_label'].split(','):
88
- print(" - label named '{}' ".format(raw_label_name), end='', flush=True)
89
- label = org.LabelStore.find_label_by_name_and_type(raw_label_name, pylo.label_type_loc)
90
- if label is None:
91
- print("NOT FOUND!")
92
- raise pylo.PyloEx("Cannot find label named '{}'".format(raw_label_name))
93
- else:
94
- print("found")
95
- loc_label_list[label] = label
96
-
97
- app_label_list = {}
98
- if args['filter_app_label'] is not None:
99
- print(" * Application Labels specified")
100
- for raw_label_name in args['filter_app_label'].split(','):
101
- print(" - label named '{}' ".format(raw_label_name), end='', flush=True)
102
- label = org.LabelStore.find_label_by_name_and_type(raw_label_name, pylo.label_type_app)
103
- if label is None:
104
- print("NOT FOUND!")
105
- raise pylo.PyloEx("Cannot find label named '{}'".format(raw_label_name))
106
- else:
107
- print("found")
108
- app_label_list[label] = label
109
-
110
- role_label_list = {}
111
- if args['filter_role_label'] is not None:
112
- print(" * Role Labels specified")
113
- for raw_label_name in args['filter_role_label']:
114
- print(" - label named '{}' ".format(raw_label_name), end='', flush=True)
115
- label = org.LabelStore.find_label_by_name_and_type(raw_label_name, pylo.label_type_role)
64
+ filter_labels: List[pylo.Label] = [] # the list of labels to filter the workloads against
65
+ if args['filter_label'] is not None:
66
+ for label_name in args['filter_label']:
67
+ label = org.LabelStore.find_label_by_name(label_name)
116
68
  if label is None:
117
- print("NOT FOUND!")
118
- raise pylo.PyloEx("Cannot find label named '{}'".format(raw_label_name))
119
- else:
120
- print("found")
121
- role_label_list[label] = label
69
+ raise pylo.PyloEx("Cannot find label '{}' in the PCE".format(label_name))
70
+ filter_labels.append(label)
122
71
 
123
72
  filter_versions = {}
124
73
  if args['filter_ven_versions'] is not None:
@@ -149,25 +98,8 @@ def __main(args, org: pylo.Organization, **kwargs):
149
98
  del agents[agent_href]
150
99
  continue
151
100
 
152
- if len(env_label_list) > 0 and (workload.env_label is None or workload.env_label not in env_label_list):
153
- pylo.log.debug(" - workload '{}' does not match env_label filters, it's out!".format(workload.get_name()))
154
- del agents[agent_href]
155
- continue
156
- if len(loc_label_list) > 0 and (workload.loc_label is None or workload.loc_label not in loc_label_list):
157
- pylo.log.debug(" - workload '{}' does not match loc_label filters, it's out!".format(workload.get_name()))
158
- del agents[agent_href]
159
- continue
160
- if len(app_label_list) > 0 and (workload.app_label is None or workload.app_label not in app_label_list):
161
- pylo.log.debug(" - workload '{}' does not match app_label filters, it's out!".format(workload.get_name()))
162
- del agents[agent_href]
163
- continue
164
- if len(role_label_list) > 0 and (workload.role_label is None or workload.role_label not in role_label_list):
165
- pylo.log.debug(" - workload '{}' does not match role_label filters, it's out!".format(workload.get_name()))
166
- del agents[agent_href]
167
- continue
168
-
169
- if agent.software_version.version_string not in filter_versions:
170
- pylo.log.debug(" - workload '{}' does not match the version filter, it's out!".format(workload.get_name()))
101
+ if workload.uses_all_labels(filter_labels) is False:
102
+ pylo.log.debug(" - workload '{}' is not matching the labels filter".format(workload.get_name()))
171
103
  del agents[agent_href]
172
104
  continue
173
105
 
@@ -80,9 +80,11 @@ def __main(args, org: pylo.Organization, **kwargs):
80
80
  for label_type in org.LabelStore.label_types:
81
81
  csv_report_headers.append(f'label_{label_type}')
82
82
 
83
- csv_report_headers.extend(['online', 'managed', 'status', 'agent.last_heartbeat',
84
- 'agent.sec_policy_sync_state', 'agent.sec_policy_applied_at',
85
- 'href', 'agent.href'])
83
+ csv_report_headers.extend([
84
+ 'online', 'managed', 'status', 'agent.last_heartbeat',
85
+ 'agent.sec_policy_sync_state', 'agent.sec_policy_applied_at',
86
+ ExcelHeader(name='link_to_pce', wrap_text=False, url_text='See in PCE', is_url=True),
87
+ 'href', 'agent.href'])
86
88
 
87
89
  for extra_column in extra_columns:
88
90
  csv_report_headers.append(extra_column.column_description().name)
@@ -110,7 +112,7 @@ def __main(args, org: pylo.Organization, **kwargs):
110
112
  for field in filter_data._detected_headers:
111
113
  csv_report_headers.append('_' + field)
112
114
 
113
- csv_report = pylo.ArraysToExcel()
115
+ csv_report = ArraysToExcel()
114
116
  csv_sheet = csv_report.create_sheet('workloads', csv_report_headers, force_all_wrap_text=True)
115
117
 
116
118
  all_workloads = org.WorkloadStore.itemsByHRef.copy()
@@ -132,6 +134,7 @@ def __main(args, org: pylo.Organization, **kwargs):
132
134
  'online': wkl.online,
133
135
  'managed': not wkl.unmanaged,
134
136
  'status': wkl.get_status_string(),
137
+ 'link_to_pce': wkl.href,
135
138
  }
136
139
  for label_type in org.LabelStore.label_types:
137
140
  new_row[f'label_{label_type}'] = wkl.get_label_name(label_type)
@@ -3,6 +3,8 @@ from dataclasses import dataclass
3
3
  import sys
4
4
  import argparse
5
5
 
6
+ import click
7
+
6
8
  import illumio_pylo as pylo
7
9
  from illumio_pylo import ArraysToExcel, ExcelHeaderSet, ExcelHeader
8
10
  from .utils.LabelCreation import generate_list_of_labels_to_create, create_labels
@@ -43,6 +45,12 @@ def fill_parser(parser: argparse.ArgumentParser):
43
45
  parser.add_argument('--batch-size', type=int, required=False, default=500,
44
46
  help='Number of Workloads to create per API call')
45
47
 
48
+ parser.add_argument('--proceed-with-creation', '-p', action='store_true',
49
+ help='If set, the script will proceed with the creation of the workloads')
50
+
51
+ parser.add_argument('--no-confirmation-required', '-n', action='store_true',
52
+ help='If set, the script will proceed with the creation of the workloads and labels without asking for confirmation')
53
+
46
54
 
47
55
 
48
56
  def __main(args, org: pylo.Organization, **kwargs):
@@ -54,6 +62,8 @@ def __main(args, org: pylo.Organization, **kwargs):
54
62
  settings_header_label_prefix: str = args['label_type_header_prefix']
55
63
  settings_ignore_all_sorts_collisions: bool = args['ignore_all_sorts_collisions']
56
64
  settings_ignore_empty_ip_entries: bool = args['ignore_empty_ip_entries']
65
+ settings_proceed_with_creation: bool = args['proceed_with_creation']
66
+ settings_no_confirmation_required: bool = args['no_confirmation_required']
57
67
  settings_output_dir: str = args['output_dir']
58
68
 
59
69
  batch_size = args['batch_size']
@@ -113,6 +123,11 @@ def __main(args, org: pylo.Organization, **kwargs):
113
123
  # <editor-fold desc="Missing Labels creation">
114
124
  if len(labels_to_be_created) > 0:
115
125
  print(" * {} Labels need to created before Workloads can be imported, listing:".format(len(labels_to_be_created)))
126
+ for label in labels_to_be_created:
127
+ print(" - Label: {} (type={})".format(label.name, label.type))
128
+ if not settings_no_confirmation_required:
129
+ click.confirm("Do you want to proceed with the creation of these labels?", abort=True)
130
+
116
131
  create_labels(labels_to_be_created, org)
117
132
  # </editor-fold>
118
133
 
@@ -134,21 +149,37 @@ def __main(args, org: pylo.Organization, **kwargs):
134
149
  print(" * No Workloads to create, all were ignored due to collisions or missing data.")
135
150
  # still want to save the CSV/Excel files in the end so don't exit
136
151
  else:
137
- print(" * Creating {} Unmanaged Workloads in batches of {}".format(umw_creator_manager.count_drafts(), batch_size))
138
- total_created_count = 0
139
- total_failed_count = 0
140
-
141
- results = umw_creator_manager.create_all_in_pce(amount_created_per_batch=batch_size, retrieve_workloads_after_creation=False)
142
- for result in results:
143
- if result.success:
144
- total_created_count += 1
145
- result.external_tracker_id['href'] = result.workload_href
152
+ if not settings_proceed_with_creation is True:
153
+ print(" * No workload will be created because the --proceed-with-creation/-p flag was not set. Yet report will be generated")
154
+ for object_to_create in csv_objects_to_create:
155
+ if '**not_created_reason**' not in object_to_create:
156
+ object_to_create['**not_created_reason**'] = '--proceed-with-creation/-p flag was not set'
157
+ else:
158
+ confirmed = settings_no_confirmation_required
159
+ print(" * Creating {} Unmanaged Workloads in batches of {}".format(umw_creator_manager.count_drafts(), batch_size))
160
+ if not settings_no_confirmation_required:
161
+ confirmed = click.confirm("Do you want to proceed with the creation of these workloads?")
162
+
163
+ if not confirmed:
164
+ print(" * No Workloads will be created, user aborted the operation")
165
+ for object_to_create in csv_objects_to_create:
166
+ if '**not_created_reason**' not in object_to_create:
167
+ object_to_create['**not_created_reason**'] = 'user aborted the operation'
146
168
  else:
147
- total_failed_count += 1
148
- result.external_tracker_id['**not_created_reason**'] = result.message
169
+ total_created_count = 0
170
+ total_failed_count = 0
171
+
172
+ results = umw_creator_manager.create_all_in_pce(amount_created_per_batch=batch_size, retrieve_workloads_after_creation=False)
173
+ for result in results:
174
+ if result.success:
175
+ total_created_count += 1
176
+ result.external_tracker_id['href'] = result.workload_href
177
+ else:
178
+ total_failed_count += 1
179
+ result.external_tracker_id['**not_created_reason**'] = result.message
149
180
 
150
- print(" * DONE - {} created with success, {} failures and {} ignored.".format(
151
- total_created_count, total_failed_count, ignored_objects_count))
181
+ print(" * DONE - {} created with success, {} failures and {} ignored.".format(
182
+ total_created_count, total_failed_count, ignored_objects_count))
152
183
  # </editor-fold>
153
184
 
154
185
  print()
illumio_pylo/tmp.py CHANGED
@@ -3,12 +3,11 @@ from datetime import datetime
3
3
 
4
4
  import illumio_pylo as pylo
5
5
 
6
- log = logging.getLogger('Pylo')
7
-
6
+ log = logging.getLogger('PYLO')
8
7
 
9
8
  def init_logger():
10
9
  console_logger = logging.StreamHandler()
11
- formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
10
+ formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s/%(filename)s:%(lineno)d - %(message)s')
12
11
  console_logger.setFormatter(formatter)
13
12
  log.addHandler(console_logger)
14
13
 
@@ -32,7 +31,12 @@ def get_logger():
32
31
  return log
33
32
 
34
33
 
35
- def find_connector_or_die(obj):
34
+ def find_connector_or_die(obj) -> 'pylo.APIConnector':
35
+ """
36
+ Find the APIConnector object in the object or its owner recursively. Will raise an exception if not found
37
+ :param obj:
38
+ :return:
39
+ """
36
40
  connector = obj.__dict__.get('connector') # type: pylo.APIConnector
37
41
  if connector is None:
38
42
  owner = obj.__dict__.get('owner')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: illumio_pylo
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Summary: A set of tools and library for working with Illumio PCE
5
5
  Home-page: https://github.com/cpainchaud/pylo
6
6
  Author: Christophe Painchaud
@@ -1,60 +1,60 @@
1
1
  illumio_pylo/AgentStore.py,sha256=O4dz9m0KM0TQPZvvEMoD_5pCkMvQ70YVDaWqbqqhR4k,5091
2
2
  illumio_pylo/Exception.py,sha256=3lxS-ANBaEvHAKhDb8UzNLj5IpQBmRHNs4YkHONmQjs,1033
3
3
  illumio_pylo/IPList.py,sha256=XljZM4AHjM59PoR0bPLoJDVjLFJGVjewF91GwXV29UY,4501
4
- illumio_pylo/IPMap.py,sha256=Jh7qpsp2G8faHU6hX6ahvpoJGJkmuLYyCEn1YVUl_2k,9980
4
+ illumio_pylo/IPMap.py,sha256=2xCU0BD6Zc5yWgemn6Q0jNgs0owzp7TCyh6sVRLMf5U,10232
5
5
  illumio_pylo/Label.py,sha256=sn6qNFo8Etdl0lUYRoSp_VC0mJymuFJpQ99x9vAwKSY,835
6
6
  illumio_pylo/LabelCommon.py,sha256=2HzljD2xJSBjf4Ywxk8JpBQQfbiZILCMV_Mj_EATIGk,1500
7
7
  illumio_pylo/LabelGroup.py,sha256=bVXvONHwR44GkF-9S19Fs2cnc4g8IW42VjyKi7QXmvQ,2624
8
8
  illumio_pylo/LabelStore.py,sha256=_feA5HdrfnOgcWStBToesu1S2ymHQKEKEG2Ygcadh9g,19809
9
- illumio_pylo/LabeledObject.py,sha256=R5FlDzmGzFpePCaEKMvBYuXcD6ejH9Hc985U2XVAfpU,1965
10
- illumio_pylo/Organization.py,sha256=Cz9bTBQKuMkjGDV21G4NdPm18SwMnpuzO8Z3UjKAY4U,12242
9
+ illumio_pylo/LabeledObject.py,sha256=zXIFcHtuvgxhu93htxiWtHoJ_mMWfSSdOBRNlEiXej0,1991
10
+ illumio_pylo/Organization.py,sha256=MeESA6HuU0HzzkIKbj1NW_wzkarq30ptsCiffD1lm2M,12379
11
11
  illumio_pylo/Query.py,sha256=lgLjst41ma3HMVkngvTjL7zSLjh80RoXYL5vS3PWO7Q,13330
12
12
  illumio_pylo/ReferenceTracker.py,sha256=HyY0NwZwOdAzSXJrs6i6dhB_kgn1tidZL5LFLLkPuSM,1070
13
- illumio_pylo/Rule.py,sha256=pHTe-7eLgJ-ucXHvv6XQWihQ8WPhNoxCzTJ9NvJHj44,25496
14
- illumio_pylo/Ruleset.py,sha256=f8HIS2LIQ-LRBYWooLur_AR0OwMOTVpMquYAjwQZdcM,11987
13
+ illumio_pylo/Rule.py,sha256=jPU0hj8BDv2t-keL3NCK6NEd-kYSRkjwtzdo1xWiC5E,26352
14
+ illumio_pylo/Ruleset.py,sha256=G-YrxHRovECDV0TVOLOJyRbMtlgSNmqiq4jMwR3175A,11711
15
15
  illumio_pylo/RulesetStore.py,sha256=wJLAKi32y-YiI8NLWWa_Ykrn54AQDcLz3rRlvTGUToI,3379
16
16
  illumio_pylo/SecurityPrincipal.py,sha256=8c7VYirJvByjW4r8Vz4NYHXlGYawOVE6YHDpizSIIO4,2280
17
- illumio_pylo/Service.py,sha256=AIvBd_2p_ZSwakZLW__i8uwBnT0LPSXSSI0NzRIQGlI,8012
17
+ illumio_pylo/Service.py,sha256=CKj5VHjkvy7YL18afHqgJl-Qole7s1NWoah3rWHm6Zw,8280
18
18
  illumio_pylo/SoftwareVersion.py,sha256=Wg8Mi7vcoL9JgFSRo3i5aiX_OkUIU3ahmgLUrzJ9JYw,4348
19
19
  illumio_pylo/VirtualService.py,sha256=KAKE4iWuNmOumGradR2vwZxZ_3iiZg4pBST5ixCZSng,561
20
20
  illumio_pylo/VirtualServiceStore.py,sha256=b6bcMixq0Vfv7WjQTQnFHGCv_Mld7sOfvGi4NtFAMQ4,3057
21
21
  illumio_pylo/Workload.py,sha256=OIrP2TVFpCjQdjMYPdHkZHcKRszZ2hPnj4uRhI6d8EI,19677
22
22
  illumio_pylo/WorkloadStore.py,sha256=08FKIk9fubsaHy_-m2a0Io5sO_V9jmTa40-L3vyemRc,10947
23
23
  illumio_pylo/WorkloadStoreSubClasses.py,sha256=tK7s-7s5wRf9SHv3T00EOzgv4gX8gPIM77KBubCzBuo,6092
24
- illumio_pylo/__init__.py,sha256=T4OgouskDV99jeXA-lZU43r0rN7fc3bQ4Lf1t7cvWEY,4192
25
- illumio_pylo/tmp.py,sha256=zC3QAFfjMsthnbdjDKhXTG13q5G197oU-i45sMSDOhU,2926
26
- illumio_pylo/API/APIConnector.py,sha256=ufuZ0cNx-hpjgDGvWOAuGm91d3ykoEE5FC_CCv4UCZI,59144
24
+ illumio_pylo/__init__.py,sha256=0cUO16-EHeOd6bZL4Aap5BngagWLcGsCZAnvhoVSxdg,4192
25
+ illumio_pylo/tmp.py,sha256=7PIn11aYfoxed7tjG_I1iZmiacn4TxZPihs7qD7f4DI,3123
26
+ illumio_pylo/API/APIConnector.py,sha256=PM-7sNPmBaZ8fLM5KGcLd7nXBha4Il6fFGAHntToSdk,59433
27
27
  illumio_pylo/API/AuditLog.py,sha256=p0mfjrE5S8qoJgA5LIP_XGFBP3iL86Nl6BQEmhMVrPA,1533
28
28
  illumio_pylo/API/ClusterHealth.py,sha256=GdMpVQrHUW4zLM-409GcPHM8H8b3LAppO37ZcUZyT_Q,5122
29
- illumio_pylo/API/CredentialsManager.py,sha256=8Z_R3AuVJk3Nhq1cwxHDBXp-z3GfIGOaNQeZBFpAw3c,10909
29
+ illumio_pylo/API/CredentialsManager.py,sha256=vPqekauA0twbcpbWx8GcGFX4DQlh0y0SWAE9M4HKBsE,11031
30
30
  illumio_pylo/API/Explorer.py,sha256=fFAIF-67_uuKgJOP0eZPPJrOGuYmFl33GK75AyMjgJU,47590
31
31
  illumio_pylo/API/JsonPayloadTypes.py,sha256=eUsK1ba8PLoV-VReRE6A3qar-N3xckXRZbY2ckSkmMk,7113
32
32
  illumio_pylo/API/RuleSearchQuery.py,sha256=O0-MsUXhwmywoO0G-GXnLq6kkVC9LgmxMZwqVKc_oJE,5325
33
33
  illumio_pylo/API/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
34
  illumio_pylo/Helpers/__init__.py,sha256=6E2eTZe-4qfPKGjRRQNtYsPrBhJSAjbdvv_DpniV_rY,49
35
- illumio_pylo/Helpers/exports.py,sha256=LJHtec-xbifHsJWF18exijSh3YLjkYW0f8s4hfn-a-0,21867
35
+ illumio_pylo/Helpers/exports.py,sha256=mW8zGDqSyAsAC7WjC-WX1DDhDS-DCJ37cF3raZ_C6xg,21890
36
36
  illumio_pylo/Helpers/functions.py,sha256=TjI_NmD3cm1Escp3h51SFEDgS1vT9dUa8PX9-e0siao,4680
37
37
  illumio_pylo/cli/NativeParsers.py,sha256=nzDL54EV0J-Iz0P9EkeiPl6DWQBSbCu-MpEPRad3j6c,4055
38
- illumio_pylo/cli/__init__.py,sha256=wa0GHWNyRin-HfmJI4b9RMbKv-vwF-9Dra6ZgCSCWcU,6651
38
+ illumio_pylo/cli/__init__.py,sha256=UuZ2KamgIpvfYE1FXilm_THHAvXimdUcvGb3Jrbjfhc,7737
39
39
  illumio_pylo/cli/__main__.py,sha256=ll1gK8k1YL_kPsImI7WVlw2sCyNyhocnuCqko6mGaYI,223
40
40
  illumio_pylo/cli/commands/__init__.py,sha256=yoVkXy-qBGiAAziWiayJdjcclx1WTayShXSPqHelcWA,1489
41
- illumio_pylo/cli/commands/credential_manager.py,sha256=oS7fM7HMmkaLoQZHOctpSZNeCgIqZpl9-y2C3OBt0Tk,6868
41
+ illumio_pylo/cli/commands/credential_manager.py,sha256=nChEuE7jewCzVNH2dXn3VspsfPmplZO-TjXLt07h7MA,9613
42
42
  illumio_pylo/cli/commands/iplist_analyzer.py,sha256=vOl6fNd0HrUKnKXtY1sj7jeyx2GtuDZENV7mF79O7yU,3714
43
43
  illumio_pylo/cli/commands/iplist_import_from_file.py,sha256=XvC-GlJyjA4dDPzfvGoBFtwelI9g_rQqGzgKgqMYcsI,8284
44
- illumio_pylo/cli/commands/ruleset_export.py,sha256=Sf1nlQHq28i8OYbf8osEDO8ndOlXPhsqHkb8I3241To,5700
44
+ illumio_pylo/cli/commands/ruleset_export.py,sha256=E64oYgGKI1Ry382DqV1gT4IlsLdd55QTqdInXlL9EPU,5896
45
45
  illumio_pylo/cli/commands/update_pce_objects_cache.py,sha256=kNBAmPIbJyw97FRcNREy059A5FLOGm_poKJYGdTWqr4,1256
46
46
  illumio_pylo/cli/commands/ven_compatibility_report_export.py,sha256=8ZpgGQfn5zmOslgeE1imwx4-gDHfukdgoXJGZUh9q3o,8120
47
47
  illumio_pylo/cli/commands/ven_duplicate_remover.py,sha256=sPq1E9LqJ0lku8c23zwfeJbCOblkG_3umve5-zV3DXU,19579
48
48
  illumio_pylo/cli/commands/ven_idle_to_visibility.py,sha256=OMcQSB82Pn50TS7OL1_TPOvvN9XFR3jUxVYYizUQinI,13387
49
- illumio_pylo/cli/commands/ven_upgrader.py,sha256=B07JbrEc8Gd5qgsu8JuYKMUagVEKBSuEXEDyFpO5n7c,11032
50
- illumio_pylo/cli/commands/workload_export.py,sha256=9jhvMby9RVwYYbiUB0PCu2YHPRrUIyJTLWxp_Bu0OLY,11323
51
- illumio_pylo/cli/commands/workload_import.py,sha256=PxviMVmjfe3t0aW05aX_k45YKnFxg1AhgiPRXF-Igaw,16969
49
+ illumio_pylo/cli/commands/ven_upgrader.py,sha256=4cLUA7HSpskJpPOtEkQh41lE4gMGJJiQiKGCd0CKYJk,7222
50
+ illumio_pylo/cli/commands/workload_export.py,sha256=EcQR8AacJVe7rOqYH-HFxyTchwHHQ9ZTc-ALSMt9gDY,11421
51
+ illumio_pylo/cli/commands/workload_import.py,sha256=EJPjue9jnPR4UyLrh67LlXovvpmfa9Lb4DAfy6exAFA,18939
52
52
  illumio_pylo/cli/commands/workload_reset_names_to_null.py,sha256=GaO2Th1rx8i7_7yqb9WkGpDz3sdbYNod5fABQnhogig,3383
53
53
  illumio_pylo/cli/commands/workload_update.py,sha256=hDiYEeOwGaSUkoG4NK7EvKbMnChoYJAGDmqRWDtLD9A,28484
54
54
  illumio_pylo/cli/commands/workload_used_in_rule_finder.py,sha256=2MO9jjYh-mkY6rkfvDyW0Ok6El_AqDWGXiFDYq5YgTk,3361
55
55
  illumio_pylo/cli/commands/utils/LabelCreation.py,sha256=qaU7BeP4et2GJxHwgbDNRoQLCA-xWp_y1nJ-m8ANUwA,4883
56
56
  illumio_pylo/cli/commands/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
57
- illumio_pylo/cli/commands/utils/misc.py,sha256=BUnRLo_RVtVc79e_AmVlReU1aY_Z4cYQIVUYtAW9HVY,594
57
+ illumio_pylo/cli/commands/utils/misc.py,sha256=iV5CEEccfQ08eO0wSpTOxS9-32_GXSekShic6auuGPE,761
58
58
  illumio_pylo/docs/Doxygen,sha256=AVvSIRYLHFWJ15YLGahhzhsW0ZUUFO5lVxd2-F3iWz8,74257
59
59
  illumio_pylo/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
60
  illumio_pylo/utilities/cli.py,sha256=7Wv7qhdc91-vsaxiu4j8k7LY7V9dcwWHnHV7PLpOBAI,320
@@ -65,8 +65,8 @@ illumio_pylo/utilities/resources/iplists-import-example.xlsx,sha256=VW-7CRr8NA2V
65
65
  illumio_pylo/utilities/resources/workload-exporter-filter-example.csv,sha256=cn5IA8AGEPjvS-EsPXA_GJ-LFsdF9t_44rSzFTCmAzE,36
66
66
  illumio_pylo/utilities/resources/workloads-import-example.csv,sha256=DEOGVikFjxQpMFFI0l0jb3hrxEEeZCpTGkmWkz6GUcY,91
67
67
  illumio_pylo/utilities/resources/workloads-import-example.xlsx,sha256=U8Ac2BZidA6NlvBFAVPHqeY5zmg3rjmIAXp5b3b1P5w,17101
68
- illumio_pylo-0.3.1.dist-info/LICENSE,sha256=WYmcYJG1QFgu1hfo7qrEkZ3Jhcz8NUWe6XUraZvlIFs,10172
69
- illumio_pylo-0.3.1.dist-info/METADATA,sha256=W31HBVec-NOUgqCD0SH3HvUs4Dp81L6vayukDDKv_aM,12224
70
- illumio_pylo-0.3.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
71
- illumio_pylo-0.3.1.dist-info/top_level.txt,sha256=c5cu_ZMuSuxjq48ih58Kc0Tr8-JdQtV8GrKJicvWNFE,13
72
- illumio_pylo-0.3.1.dist-info/RECORD,,
68
+ illumio_pylo-0.3.2.dist-info/LICENSE,sha256=WYmcYJG1QFgu1hfo7qrEkZ3Jhcz8NUWe6XUraZvlIFs,10172
69
+ illumio_pylo-0.3.2.dist-info/METADATA,sha256=TQ6C78x4N8G9pvl48e9MKRErO1CIjV17ETRUM1tN824,12224
70
+ illumio_pylo-0.3.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
71
+ illumio_pylo-0.3.2.dist-info/top_level.txt,sha256=c5cu_ZMuSuxjq48ih58Kc0Tr8-JdQtV8GrKJicvWNFE,13
72
+ illumio_pylo-0.3.2.dist-info/RECORD,,