illumio-pylo 0.3.11__py3-none-any.whl → 0.3.13__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. illumio_pylo/API/APIConnector.py +138 -106
  2. illumio_pylo/API/CredentialsManager.py +168 -3
  3. illumio_pylo/API/Explorer.py +619 -14
  4. illumio_pylo/API/JsonPayloadTypes.py +64 -4
  5. illumio_pylo/FilterQuery.py +892 -0
  6. illumio_pylo/Helpers/exports.py +1 -1
  7. illumio_pylo/LabelCommon.py +13 -3
  8. illumio_pylo/LabelDimension.py +109 -0
  9. illumio_pylo/LabelStore.py +97 -38
  10. illumio_pylo/WorkloadStore.py +58 -0
  11. illumio_pylo/__init__.py +9 -3
  12. illumio_pylo/cli/__init__.py +5 -2
  13. illumio_pylo/cli/commands/__init__.py +1 -0
  14. illumio_pylo/cli/commands/credential_manager.py +555 -4
  15. illumio_pylo/cli/commands/label_delete_unused.py +0 -3
  16. illumio_pylo/cli/commands/traffic_export.py +358 -0
  17. illumio_pylo/cli/commands/ui/credential_manager_ui/app.js +638 -0
  18. illumio_pylo/cli/commands/ui/credential_manager_ui/index.html +217 -0
  19. illumio_pylo/cli/commands/ui/credential_manager_ui/styles.css +581 -0
  20. illumio_pylo/cli/commands/update_pce_objects_cache.py +1 -2
  21. illumio_pylo/cli/commands/ven_duplicate_remover.py +79 -59
  22. illumio_pylo/cli/commands/workload_export.py +29 -0
  23. illumio_pylo/utilities/cli.py +4 -1
  24. illumio_pylo/utilities/health_monitoring.py +5 -1
  25. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/METADATA +2 -1
  26. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/RECORD +29 -24
  27. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/WHEEL +1 -1
  28. illumio_pylo/Query.py +0 -331
  29. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/licenses/LICENSE +0 -0
  30. {illumio_pylo-0.3.11.dist-info → illumio_pylo-0.3.13.dist-info}/top_level.txt +0 -0
@@ -9,7 +9,7 @@ import illumio_pylo as pylo
9
9
 
10
10
 
11
11
  class ExcelHeader:
12
- def __init__(self, name: str, nice_name: Optional[str] = None, max_width: Optional[int] = None, wrap_text: Optional[bool] = None, is_url: [bool] = False, url_text: str = 'Link'):
12
+ def __init__(self, name: str, nice_name: Optional[str] = None, max_width: Optional[int] = None, wrap_text: Optional[bool] = None, is_url: bool = False, url_text: str = 'Link'):
13
13
  self.name = name
14
14
  self.nice_name:str = nice_name if nice_name is not None else name
15
15
  self.max_width = max_width
@@ -1,17 +1,27 @@
1
- from typing import Union
1
+ from typing import TYPE_CHECKING
2
2
  from .Exception import PyloEx
3
3
  from .LabelStore import LabelStore
4
4
 
5
+ if TYPE_CHECKING:
6
+ from .LabelDimension import LabelDimension
7
+
5
8
 
6
9
  class LabelCommon:
7
10
 
8
11
  __slots__ = ['owner', 'name', 'href', 'type']
9
12
 
10
- def __init__(self, name: str, href: str, label_type: str, owner: LabelStore):
13
+ def __init__(self, name: str, href: str, label_type: str, owner: LabelStore) -> None:
11
14
  self.owner: LabelStore = owner
12
15
  self.name: str = name
13
16
  self.href: str = href
14
- self.type = label_type
17
+ self.type: str = label_type
18
+
19
+ def get_dimension(self) -> 'LabelDimension':
20
+ """Get the LabelDimension object for this label's type from the owning LabelStore."""
21
+ dimension: 'LabelDimension | None' = self.owner.get_dimension(self.type)
22
+ if dimension is None:
23
+ raise PyloEx(f"Dimension '{self.type}' not found in LabelStore")
24
+ return dimension
15
25
 
16
26
  def is_label(self) -> bool:
17
27
  raise PyloEx("not implemented")
@@ -0,0 +1,109 @@
1
+ from typing import Dict, Optional, Set, TYPE_CHECKING
2
+
3
+ if TYPE_CHECKING:
4
+ from .API.JsonPayloadTypes import LabelDimensionObjectStructure
5
+
6
+ # Module constants for dimension keys
7
+ DIMENSION_KEY_ROLE: str = 'role'
8
+ DIMENSION_KEY_APP: str = 'app'
9
+ DIMENSION_KEY_ENV: str = 'env'
10
+ DIMENSION_KEY_LOC: str = 'loc'
11
+
12
+ # Set of built-in dimension keys
13
+ BUILTIN_DIMENSION_KEYS: Set[str] = {DIMENSION_KEY_ROLE, DIMENSION_KEY_APP, DIMENSION_KEY_ENV, DIMENSION_KEY_LOC}
14
+
15
+
16
+ class LabelDimension:
17
+ """
18
+ Represents a label dimension (type) in the Illumio PCE.
19
+
20
+ Label dimensions define the categories of labels (e.g., role, app, env, loc).
21
+ Built-in dimensions are the standard four dimensions, while custom dimensions
22
+ can be created by the user.
23
+ """
24
+
25
+ __slots__ = ['key', 'display_name', 'href', 'created_at', 'created_by', 'updated_at', 'updated_by', 'raw_json']
26
+
27
+ def __init__(self, key: str, display_name: str, href: Optional[str] = None,
28
+ created_at: Optional[str] = None, created_by: Optional[str] = None,
29
+ updated_at: Optional[str] = None, updated_by: Optional[str] = None,
30
+ raw_json: Optional['LabelDimensionObjectStructure'] = None) -> None:
31
+ self.key: str = key
32
+ self.display_name: str = display_name
33
+ self.href: Optional[str] = href
34
+ self.created_at: Optional[str] = created_at
35
+ self.created_by: Optional[str] = created_by
36
+ self.updated_at: Optional[str] = updated_at
37
+ self.updated_by: Optional[str] = updated_by
38
+ self.raw_json: Optional['LabelDimensionObjectStructure'] = raw_json
39
+
40
+ @staticmethod
41
+ def from_json(json_data: 'LabelDimensionObjectStructure') -> 'LabelDimension':
42
+ """
43
+ Factory method to create a LabelDimension from JSON data returned by the API.
44
+
45
+ :param json_data: The JSON structure from the API
46
+ :return: A new LabelDimension instance
47
+ """
48
+ created_by: Optional[str] = None
49
+ if json_data.get('created_by') is not None:
50
+ created_by = json_data['created_by'].get('href')
51
+
52
+ updated_by: Optional[str] = None
53
+ if json_data.get('updated_by') is not None:
54
+ updated_by = json_data['updated_by'].get('href')
55
+
56
+ return LabelDimension(
57
+ key=json_data['key'],
58
+ display_name=json_data['display_name'],
59
+ href=json_data.get('href'),
60
+ created_at=json_data.get('created_at'),
61
+ created_by=created_by,
62
+ updated_at=json_data.get('updated_at'),
63
+ updated_by=updated_by,
64
+ raw_json=json_data
65
+ )
66
+
67
+ @staticmethod
68
+ def create_builtin_dimensions() -> Dict[str, 'LabelDimension']:
69
+ """
70
+ Create the four built-in label dimensions with default display names.
71
+
72
+ :return: A dictionary mapping dimension keys to LabelDimension objects
73
+ """
74
+ return {
75
+ DIMENSION_KEY_ROLE: LabelDimension(key=DIMENSION_KEY_ROLE, display_name='Role'),
76
+ DIMENSION_KEY_APP: LabelDimension(key=DIMENSION_KEY_APP, display_name='Application'),
77
+ DIMENSION_KEY_ENV: LabelDimension(key=DIMENSION_KEY_ENV, display_name='Environment'),
78
+ DIMENSION_KEY_LOC: LabelDimension(key=DIMENSION_KEY_LOC, display_name='Location'),
79
+ }
80
+
81
+ def is_role(self) -> bool:
82
+ """Check if this dimension is the Role dimension."""
83
+ return self.key == DIMENSION_KEY_ROLE
84
+
85
+ def is_application(self) -> bool:
86
+ """Check if this dimension is the Application dimension."""
87
+ return self.key == DIMENSION_KEY_APP
88
+
89
+ def is_environment(self) -> bool:
90
+ """Check if this dimension is the Environment dimension."""
91
+ return self.key == DIMENSION_KEY_ENV
92
+
93
+ def is_location(self) -> bool:
94
+ """Check if this dimension is the Location dimension."""
95
+ return self.key == DIMENSION_KEY_LOC
96
+
97
+ def is_builtin(self) -> bool:
98
+ """Check if this dimension is one of the four built-in dimensions."""
99
+ return self.key in BUILTIN_DIMENSION_KEYS
100
+
101
+ def is_custom(self) -> bool:
102
+ """Check if this dimension is a custom (non-built-in) dimension."""
103
+ return self.key not in BUILTIN_DIMENSION_KEYS
104
+
105
+ def __repr__(self) -> str:
106
+ return f"LabelDimension(key='{self.key}', display_name='{self.display_name}')"
107
+
108
+ def __str__(self) -> str:
109
+ return f"{self.display_name} ({self.key})"
@@ -1,15 +1,20 @@
1
1
  from hashlib import md5
2
2
  import random
3
- from typing import Set
3
+ from typing import Set, Union
4
4
  # Pylo imports
5
5
  from illumio_pylo import log
6
6
  from .API.JsonPayloadTypes import LabelObjectJsonStructure, LabelGroupObjectJsonStructure, LabelDimensionObjectStructure
7
7
  from .Helpers import *
8
+ from .LabelDimension import (
9
+ LabelDimension,
10
+ DIMENSION_KEY_ROLE, DIMENSION_KEY_APP, DIMENSION_KEY_ENV, DIMENSION_KEY_LOC
11
+ )
8
12
 
9
- label_type_loc = 'loc'
10
- label_type_env = 'env'
11
- label_type_app = 'app'
12
- label_type_role = 'role'
13
+ # Backward compatibility aliases
14
+ label_type_loc: str = DIMENSION_KEY_LOC
15
+ label_type_env: str = DIMENSION_KEY_ENV
16
+ label_type_app: str = DIMENSION_KEY_APP
17
+ label_type_role: str = DIMENSION_KEY_ROLE
13
18
 
14
19
 
15
20
  class LabelStore:
@@ -47,32 +52,78 @@ class LabelStore:
47
52
  result.append(label)
48
53
  return result
49
54
 
50
- __slots__ = ['owner', '_items_by_href', 'label_types', 'label_types_as_set', 'label_resolution_cache']
55
+ __slots__ = ['owner', '_items_by_href', '_dimensions', '_dimensions_dict', '_label_types_cache', '_label_types_as_set_cache', 'label_resolution_cache']
51
56
 
52
- def __init__(self, owner: 'pylo.Organization'):
57
+ def __init__(self, owner: 'pylo.Organization') -> None:
53
58
  self.owner: "pylo.Organization" = owner
54
59
  self._items_by_href: Dict[str, Union[pylo.Label, pylo.LabelGroup]] = {}
55
- self.label_types: List[str] = []
56
- self.label_types_as_set: Set[str] = set()
60
+ self._dimensions: List[LabelDimension] = []
61
+ self._dimensions_dict: Dict[str, LabelDimension] = {}
62
+ self._label_types_cache: Optional[List[str]] = None
63
+ self._label_types_as_set_cache: Optional[Set[str]] = None
64
+
65
+ self.label_resolution_cache: Optional[Dict[str, List[pylo.Workload]]] = None
66
+
67
+ @property
68
+ def label_types(self) -> List[str]:
69
+ """Backward-compatible property returning list of label type keys (strings)."""
70
+ if self._label_types_cache is None:
71
+ self._label_types_cache = [dim.key for dim in self._dimensions]
72
+ return self._label_types_cache
73
+
74
+ @property
75
+ def label_types_as_set(self) -> Set[str]:
76
+ """Backward-compatible property returning set of label type keys (strings)."""
77
+ if self._label_types_as_set_cache is None:
78
+ self._label_types_as_set_cache = set(dim.key for dim in self._dimensions)
79
+ return self._label_types_as_set_cache
80
+
81
+ @property
82
+ def dimensions(self) -> List[LabelDimension]:
83
+ """Return the list of LabelDimension objects, preserving order."""
84
+ return self._dimensions
85
+
86
+ def get_dimension(self, key: str) -> Optional[LabelDimension]:
87
+ """Get a LabelDimension by its key."""
88
+ return self._dimensions_dict.get(key)
89
+
90
+ def _add_dimension(self, dimension: Union[str, LabelDimension]) -> None:
91
+ """Add a dimension to the store. Accepts either a string key or a LabelDimension object."""
92
+ if isinstance(dimension, str):
93
+ # Create a basic LabelDimension from string key for backward compatibility
94
+ if dimension in self._dimensions_dict:
95
+ return
96
+ # Create built-in dimensions with proper display names
97
+ builtin_dims: Dict[str, LabelDimension] = LabelDimension.create_builtin_dimensions()
98
+ if dimension in builtin_dims:
99
+ dim_obj: LabelDimension = builtin_dims[dimension]
100
+ else:
101
+ # Custom dimension from string - use key as display name
102
+ dim_obj = LabelDimension(key=dimension, display_name=dimension)
103
+ else:
104
+ dim_obj = dimension
105
+ if dim_obj.key in self._dimensions_dict:
106
+ return
57
107
 
58
- self.label_resolution_cache: Optional[Dict[str, Union[pylo.Label, pylo.LabelGroup]]] = None
59
-
60
- def _add_dimension(self, dimension: str):
61
- if dimension not in self.label_types_as_set:
62
- self.label_types_as_set.add(dimension)
63
- self.label_types.append(dimension)
108
+ self._dimensions.append(dim_obj)
109
+ self._dimensions_dict[dim_obj.key] = dim_obj
110
+ # Invalidate caches
111
+ self._label_types_cache = None
112
+ self._label_types_as_set_cache = None
64
113
 
65
- def load_label_dimensions(self, json_list: Optional[List[LabelDimensionObjectStructure]]):
114
+ def load_label_dimensions(self, json_list: Optional[List[LabelDimensionObjectStructure]]) -> None:
66
115
  if json_list is None or len(json_list) == 0:
67
- # add the default built-in label types
68
- self._add_dimension(label_type_role)
69
- self._add_dimension(label_type_app)
70
- self._add_dimension(label_type_env)
71
- self._add_dimension(label_type_loc)
116
+ # add the default built-in label types in the standard order
117
+ builtin_dims: Dict[str, LabelDimension] = LabelDimension.create_builtin_dimensions()
118
+ self._add_dimension(builtin_dims[DIMENSION_KEY_ROLE])
119
+ self._add_dimension(builtin_dims[DIMENSION_KEY_APP])
120
+ self._add_dimension(builtin_dims[DIMENSION_KEY_ENV])
121
+ self._add_dimension(builtin_dims[DIMENSION_KEY_LOC])
72
122
  return
73
123
 
74
- for dimension in json_list:
75
- self._add_dimension(dimension['key'])
124
+ for dimension_json in json_list:
125
+ dim_obj: LabelDimension = LabelDimension.from_json(dimension_json)
126
+ self._add_dimension(dim_obj)
76
127
 
77
128
  def load_labels_from_json(self, json_list: List[LabelObjectJsonStructure]):
78
129
  for json_label in json_list:
@@ -202,7 +253,7 @@ class LabelStore:
202
253
  allow_label: bool = True,
203
254
  raise_exception_if_not_found: bool = False) \
204
255
  -> Optional[Union['pylo.Label', 'pylo.LabelGroup', List[Union['pylo.Label', 'pylo.LabelGroup']]]]:
205
- """Find a label by its name. If case_sensitive is False, the search is case-insensitive.
256
+ """Find a label by its name. If case_sensitive is False, the search is case-insensitive and will return a list of matches
206
257
  If case_sensitive is False it will return a list of labels with the same name rather than a single object.
207
258
  If missing_labels_names is not None, it will be filled with the names of the labels not found.
208
259
  If raise_exception_if_not_found is True, an exception will be raised if a label is not found.
@@ -210,22 +261,30 @@ class LabelStore:
210
261
  If a label is not found, None will be returned in the list.
211
262
  """
212
263
  if not isinstance(name, list):
213
- if case_sensitive is False:
214
- return self.find_object_by_name([name], label_type=label_type, case_sensitive=case_sensitive,
215
- missing_labels_names=missing_labels_names,
216
- allow_label_group=allow_label_group,
217
- allow_label=allow_label,
218
- raise_exception_if_not_found=raise_exception_if_not_found)
219
- for label in self._items_by_href.values():
220
- if label_type is not None and label.type != label_type:
221
- continue
222
- if label.is_label() and allow_label: # ignore groups
223
- if case_sensitive:
264
+ if case_sensitive:
265
+ for label in self._items_by_href.values():
266
+ if label_type is not None and label.type != label_type:
267
+ continue
268
+ if label.is_label() and allow_label:
269
+ if label.name == name:
270
+ return label
271
+ elif label.is_group() and allow_label_group: # ignore labels
224
272
  if label.name == name:
225
273
  return label
226
- elif allow_label_group:
227
- if label.name.lower() == name.lower():
228
- return label
274
+ else:
275
+ matches = []
276
+ for label in self._items_by_href.values():
277
+ if label_type is not None and label.type != label_type:
278
+ continue
279
+ if label.is_label() and allow_label:
280
+ if label.name.lower() == name.lower():
281
+ matches.append(label)
282
+ elif label.is_group() and allow_label_group: # ignore labels
283
+ if label.name.lower() == name.lower():
284
+ matches.append(label)
285
+ if len(matches) > 0:
286
+ return matches
287
+
229
288
  if raise_exception_if_not_found:
230
289
  raise pylo.PyloEx("Label/group '%s' not found", name)
231
290
  if missing_labels_names is not None:
@@ -6,6 +6,7 @@ from .Organization import Organization
6
6
  from typing import Optional, List, Union, Set, Iterable
7
7
 
8
8
  from .WorkloadStoreSubClasses import UnmanagedWorkloadDraft, UnmanagedWorkloadDraftMultiCreatorManager
9
+ from .FilterQuery import FilterQuery, get_workload_filter_registry
9
10
 
10
11
 
11
12
  class WorkloadStore:
@@ -297,5 +298,62 @@ class WorkloadStore:
297
298
  def new_unmanaged_workload_multi_creator_manager(self) -> UnmanagedWorkloadDraftMultiCreatorManager:
298
299
  return UnmanagedWorkloadDraftMultiCreatorManager(self)
299
300
 
301
+ def find_workloads_matching_query(self, query: str, include_deleted: bool = False) -> List['Workload']:
302
+ """
303
+ Find all workloads matching a filter query expression.
304
+
305
+ Query syntax supports:
306
+ - Comparison operators: ==, !=, <, >, <=, >=
307
+ - String operators: contains, matches (regex)
308
+ - Logical operators: and, or, not
309
+ - Parentheses for grouping
310
+
311
+ Available fields:
312
+ - name, hostname, forced_name, href, description
313
+ - online, managed, unmanaged, deleted
314
+ - os_id, os_detail
315
+ - ip_address, ip (checks all interfaces)
316
+ - last_heartbeat, last_heartbeat_received
317
+ - agent.status, agent.mode, mode, agent.version
318
+ - label.<type> for any label type configured in the PCE (e.g., label.role, label.app, label.env, label.loc)
319
+ - Shorthand aliases for labels (e.g., role, app, env, loc)
320
+ - created_at
321
+ - reference_count
322
+
323
+ Examples:
324
+ "(name == 'SRV158' or name == 'SRV48889') and ip_address == '192.168.2.54'"
325
+ "last_heartbeat_received <= '2022-09-12' and online == true"
326
+ "hostname contains 'prod' and label.env == 'Production'"
327
+ "name matches 'SRV[0-9]+' and not deleted == true"
328
+
329
+ :param query: filter query expression
330
+ :param include_deleted: whether to include deleted workloads in the search
331
+ :return: list of matching Workload objects
332
+ """
333
+ # Pass owner (Organization) to get registry with all configured label types
334
+ registry = get_workload_filter_registry(self.owner)
335
+ filter_query = FilterQuery(registry)
336
+
337
+ # Get workloads to filter
338
+ if include_deleted:
339
+ workloads = list(self.itemsByHRef.values())
340
+ else:
341
+ workloads = [w for w in self.itemsByHRef.values() if not w.deleted]
342
+
343
+ return filter_query.execute(query, workloads)
344
+
345
+ def find_workloads_matching_query_as_dict(self, query: str, include_deleted: bool = False) -> Dict[str, 'Workload']:
346
+ """
347
+ Find all workloads matching a filter query expression, returned as a dict with HREF as key.
348
+
349
+ See find_workloads_matching_query() for query syntax and available fields.
350
+
351
+ :param query: filter query expression
352
+ :param include_deleted: whether to include deleted workloads in the search
353
+ :return: dict of matching Workload objects with HREF as key
354
+ """
355
+ matching = self.find_workloads_matching_query(query, include_deleted)
356
+ return {w.href: w for w in matching}
357
+
300
358
 
301
359
 
illumio_pylo/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.3.11"
1
+ __version__ = "0.3.13"
2
2
 
3
3
  from typing import Callable
4
4
 
@@ -12,9 +12,11 @@ from .ReferenceTracker import ReferenceTracker, Referencer, Pathable
12
12
  from .API.APIConnector import APIConnector, ObjectTypes
13
13
  from .API.RuleSearchQuery import RuleSearchQuery, RuleSearchQueryResolvedResultSet
14
14
  from .API.ClusterHealth import ClusterHealth
15
- from .API.Explorer import ExplorerResultSetV1, RuleCoverageQueryManager, ExplorerFilterSetV1, ExplorerQuery
15
+ from .API.Explorer import (ExplorerResultSetV1, ExplorerResultSetV2, RuleCoverageQueryManager, ExplorerFilterSetV1,
16
+ ExplorerFilterSetV2, ExplorerQuery, ExplorerQueryV2, ExplorerResultV2)
16
17
  from .API.AuditLog import AuditLogQuery, AuditLogQueryResultSet, AuditLogFilterSet
17
18
  from .API.CredentialsManager import get_credentials_from_file
19
+ from .LabelDimension import LabelDimension, DIMENSION_KEY_ROLE, DIMENSION_KEY_APP, DIMENSION_KEY_ENV, DIMENSION_KEY_LOC
18
20
  from .LabelCommon import LabelCommon
19
21
  from .Label import Label
20
22
  from .LabelGroup import LabelGroup
@@ -31,7 +33,11 @@ from .Ruleset import Ruleset, RulesetScope, RulesetScopeEntry
31
33
  from .RulesetStore import RulesetStore
32
34
  from .SecurityPrincipal import SecurityPrincipal, SecurityPrincipalStore
33
35
  from .Organization import Organization
34
- from .Query import Query
36
+ from .FilterQuery import (
37
+ FilterQuery, FilterRegistry, FilterField, ValueType,
38
+ WorkloadFilterRegistry, get_workload_filter_registry,
39
+ QueryLexer, QueryParser, QueryNode, AndNode, OrNode, NotNode, ConditionNode
40
+ )
35
41
 
36
42
 
37
43
  def get_organization(fqdn: str, port: int, api_user: str, api_key: str,
@@ -38,13 +38,15 @@ def run(forced_command_name: Optional[str] = None):
38
38
 
39
39
  parser = argparse.ArgumentParser(description='PYLO-CLI: Illumio API&More Command Line Interface')
40
40
  parser.add_argument('--pce', type=str, required=False,
41
- help='hostname of the PCE')
41
+ help='name of the CredentialProfile to use (as defined in the credentials file)')
42
42
  parser.add_argument('--force-async-mode', action='store_true',
43
43
  help='Forces the command to run async API queries when required (large PCEs which timeout on specific queries)')
44
44
  parser.add_argument('--debug', action='store_true',
45
45
  help='Enables extra debugging output in Pylo framework')
46
46
  parser.add_argument('--use-cache', action='store_true',
47
47
  help='For developers only')
48
+ parser.add_argument('--include-deleted-workloads', action='store_true',
49
+ help='Include deleted workloads when loading data from the PCE')
48
50
  parser.add_argument('--version', action='store_true', help='Prints the version of the Pylo CLI')
49
51
 
50
52
  selected_command = None
@@ -80,6 +82,7 @@ def run(forced_command_name: Optional[str] = None):
80
82
 
81
83
  credential_profile_name = args['pce']
82
84
  settings_use_cache = args['use_cache']
85
+ include_deleted_workloads = args['include_deleted_workloads']
83
86
  org: Optional[pylo.Organization] = None
84
87
 
85
88
  # We are getting the command object associated to the command name if it was not already set (via forced_command_name)
@@ -111,7 +114,7 @@ def run(forced_command_name: Optional[str] = None):
111
114
  print("OK!")
112
115
 
113
116
  print(" * Downloading PCE objects from API... ".format(credential_profile_name), end="", flush=True)
114
- 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
+ config_data = connector.get_pce_objects(list_of_objects_to_load=selected_command.load_specific_objects_only, force_async_mode=args['force_async_mode'], include_deleted_workloads=include_deleted_workloads)
115
118
  timer_download_finished = time.perf_counter()
116
119
  print("OK! (execution time: {:.2f} seconds)".format(timer_download_finished - timer_start))
117
120
 
@@ -33,3 +33,4 @@ from .credential_manager import command_object
33
33
  from .iplist_analyzer import command_object
34
34
  from .ven_compatibility_report_export import command_object
35
35
  from .label_delete_unused import command_object
36
+ from .traffic_export import command_object