midpoint-cli 1.2.0__tar.gz → 1.3.0__tar.gz

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 (39) hide show
  1. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/PKG-INFO +2 -7
  2. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/README.md +1 -0
  3. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/midpoint_cli.egg-info/PKG-INFO +2 -7
  4. midpoint-cli-1.3.0/src/midpoint_cli/__init__.py +1 -0
  5. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/client/__init__.py +25 -9
  6. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/client/objects.py +68 -3
  7. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/task.py +30 -4
  8. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/user.py +22 -2
  9. midpoint_cli-1.2.0/src/midpoint_cli/__init__.py +0 -1
  10. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/LICENSE +0 -0
  11. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/midpoint_cli.egg-info/SOURCES.txt +0 -0
  12. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/midpoint_cli.egg-info/dependency_links.txt +0 -0
  13. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/midpoint_cli.egg-info/requires.txt +0 -0
  14. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/midpoint_cli.egg-info/top_level.txt +0 -0
  15. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/setup.cfg +0 -0
  16. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/setup.py +0 -0
  17. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint-cli +0 -0
  18. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/client/observer.py +0 -0
  19. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/client/patch.py +0 -0
  20. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/client/progress.py +0 -0
  21. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/client/session.py +0 -0
  22. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/__init__.py +0 -0
  23. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/base.py +0 -0
  24. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/complete.py +0 -0
  25. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/configuration.py +0 -0
  26. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/console.py +0 -0
  27. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/delete.py +0 -0
  28. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/get.py +0 -0
  29. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/org.py +0 -0
  30. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/put.py +0 -0
  31. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/resource.py +0 -0
  32. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/src/midpoint_cli/prompt/script.py +0 -0
  33. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/test/test_client_api.py +0 -0
  34. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/test/test_client_model.py +0 -0
  35. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/test/test_client_put.py +0 -0
  36. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/test/test_environment.py +0 -0
  37. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/test/test_parser.py +0 -0
  38. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/test/test_patch.py +0 -0
  39. {midpoint_cli-1.2.0 → midpoint-cli-1.3.0}/test/test_script.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: midpoint-cli
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: A command line client to Midpoint Identity Management system.
5
5
  Home-page: https://gitlab.com/alcibiade/midpoint-cli
6
6
  Author: Yannick Kirschhoffer
@@ -14,12 +14,6 @@ Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: clint>=0.5
18
- Requires-Dist: tabulate>=0.9
19
- Requires-Dist: unidecode>=1.3
20
- Requires-Dist: requests>=2.31
21
- Requires-Dist: urllib3>=2.0
22
- Requires-Dist: setuptools>=68.0
23
17
 
24
18
  ## Midpoint CLI
25
19
 
@@ -229,5 +223,6 @@ midpoint-1
229
223
 
230
224
  * Update revision in src/midpoint_cli/__init__.py
231
225
  * Commit and tag with corresponding version number
226
+ * Generate markdown documentation: downdoc README.adoc
232
227
  * Build distribution: python setup.py sdist
233
228
  * Upload distribution to PyPI: twine upload dist/*
@@ -206,5 +206,6 @@ midpoint-1
206
206
 
207
207
  * Update revision in src/midpoint_cli/__init__.py
208
208
  * Commit and tag with corresponding version number
209
+ * Generate markdown documentation: downdoc README.adoc
209
210
  * Build distribution: python setup.py sdist
210
211
  * Upload distribution to PyPI: twine upload dist/*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: midpoint-cli
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: A command line client to Midpoint Identity Management system.
5
5
  Home-page: https://gitlab.com/alcibiade/midpoint-cli
6
6
  Author: Yannick Kirschhoffer
@@ -14,12 +14,6 @@ Classifier: License :: OSI Approved :: MIT License
14
14
  Classifier: Programming Language :: Python :: 3
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: clint>=0.5
18
- Requires-Dist: tabulate>=0.9
19
- Requires-Dist: unidecode>=1.3
20
- Requires-Dist: requests>=2.31
21
- Requires-Dist: urllib3>=2.0
22
- Requires-Dist: setuptools>=68.0
23
17
 
24
18
  ## Midpoint CLI
25
19
 
@@ -229,5 +223,6 @@ midpoint-1
229
223
 
230
224
  * Update revision in src/midpoint_cli/__init__.py
231
225
  * Commit and tag with corresponding version number
226
+ * Generate markdown documentation: downdoc README.adoc
232
227
  * Build distribution: python setup.py sdist
233
228
  * Upload distribution to PyPI: twine upload dist/*
@@ -0,0 +1 @@
1
+ __version__ = '1.3.0'
@@ -147,6 +147,18 @@ class MidpointClient:
147
147
  if api_client is not None:
148
148
  self.api_client = api_client
149
149
 
150
+ def __get_object(self, midpoint_type: str, oid: str) -> Element:
151
+ response = self.api_client.get_element(midpoint_type, oid)
152
+ tree = ElementTree.fromstring(response)
153
+
154
+ status = tree.find('c:status', namespaces)
155
+
156
+ if status is not None and status.text == 'fatal_error':
157
+ message = tree.find('c:partialResults/c:message', namespaces).text
158
+ raise MidpointServerError(message)
159
+
160
+ return tree
161
+
150
162
  def __get_collection(self, mp_class: str, local_class: Type) -> MidpointObjectList:
151
163
  tree = self.api_client.get_elements(mp_class)
152
164
  return MidpointObjectList([local_class(entity) for entity in tree])
@@ -160,6 +172,11 @@ class MidpointClient:
160
172
  def get_connectors(self) -> MidpointObjectList:
161
173
  return self.__get_collection('connector', MidpointConnector)
162
174
 
175
+ def get_user(self, oid: str) -> Optional[MidpointUser]:
176
+ user_root = self.__get_object('user', oid)
177
+ user = MidpointUser(user_root)
178
+ return user
179
+
163
180
  def get_users(self) -> MidpointObjectList:
164
181
  return self.__get_collection('user', MidpointUser)
165
182
 
@@ -171,18 +188,17 @@ class MidpointClient:
171
188
  selected_orgs = self._filter(queryterms, orgs)
172
189
  return selected_orgs
173
190
 
174
- def task_action(self, task_oid: str, task_action: str) -> None:
191
+ def task_action(self, task_oid: str, task_action: str) -> Optional[MidpointTask]:
175
192
  self.api_client.execute_action('task', task_oid, task_action)
176
193
 
177
194
  if task_action == 'run':
178
195
  return self.task_wait(task_oid)
179
196
 
180
- def task_wait(self, task_oid: str) -> None:
197
+ def task_wait(self, task_oid: str) -> MidpointTask:
181
198
  with AsciiProgressMonitor() as progress:
182
199
  while True:
183
200
  time.sleep(2)
184
- task_xml = self.api_client.get_element('task', task_oid)
185
- task_root = ElementTree.fromstring(task_xml)
201
+ task_root = self.__get_object('task', task_oid)
186
202
  task = MidpointTask(task_root)
187
203
 
188
204
  progress.update(int(task['Progress'] or '0'))
@@ -194,7 +210,7 @@ class MidpointClient:
194
210
  if rstatus != 'success':
195
211
  raise TaskExecutionFailure('Failed execution of task ' + task_oid + ' with status ' + rstatus)
196
212
 
197
- break
213
+ return task
198
214
 
199
215
  def test_resource(self, resource_oid: str) -> None:
200
216
  response = self.api_client.execute_action('resource', resource_oid, 'test')
@@ -202,11 +218,11 @@ class MidpointClient:
202
218
  status = tree.find('c:status', namespaces).text
203
219
  return status
204
220
 
205
- def get_xml(self, type: str, oid: str) -> Optional[str]:
206
- return self.api_client.get_element(type, oid)
221
+ def get_xml(self, midpoint_type: str, oid: str) -> Optional[str]:
222
+ return self.api_client.get_element(midpoint_type, oid)
207
223
 
208
224
  def put_xml(self, xml_file: str, patch_file: str = None, patch_write: bool = False) -> Tuple[str, str]:
209
225
  return self.api_client.put_element(xml_file, patch_file, patch_write)
210
226
 
211
- def delete(self, type: str, oid: str) -> str:
212
- return self.api_client.delete(type, oid)
227
+ def delete(self, midpoint_type: str, oid: str) -> str:
228
+ return self.api_client.delete(midpoint_type, oid)
@@ -1,9 +1,12 @@
1
+ import itertools
1
2
  import re
2
3
  from collections import OrderedDict
4
+ from textwrap import dedent
3
5
  from typing import Optional, List
4
6
  from xml.etree import ElementTree
5
7
  from xml.etree.ElementTree import Element
6
8
 
9
+ import tabulate
7
10
  from unidecode import unidecode
8
11
 
9
12
  namespaces = {
@@ -76,6 +79,57 @@ class MidpointTask(MidpointObject):
76
79
  total = xml_entity.find('c:expectedTotal', namespaces)
77
80
  self['Expected Total'] = total.text if total is not None else ''
78
81
 
82
+ # Collect execution statistics
83
+
84
+ self._transitions = []
85
+ self._actions = []
86
+
87
+ statistics = xml_entity.find('c:activityState/c:activity/c:statistics', namespaces)
88
+
89
+ if statistics is not None:
90
+ synchronization = statistics.find('c:synchronization', namespaces)
91
+
92
+ for transition in synchronization or []:
93
+ state_start = transition.find('c:onSynchronizationStart', namespaces).text
94
+ state_end = transition.find('c:onSynchronizationEnd', namespaces).text
95
+ count = int(transition.find('c:counter/c:count', namespaces).text)
96
+
97
+ self._transitions.append((state_start, state_end, count))
98
+
99
+ actions_executed = statistics.find('c:actionsExecuted', namespaces)
100
+
101
+ for object_actions_entry in actions_executed or []:
102
+ object_type = object_actions_entry.find('c:objectType', namespaces).text
103
+ operation = object_actions_entry.find('c:operation', namespaces).text
104
+ count_success = int(object_actions_entry.find('c:totalSuccessCount', namespaces).text)
105
+ count_failure = int(object_actions_entry.find('c:totalFailureCount', namespaces).text)
106
+
107
+ self._actions.append((object_type, operation, count_success, count_failure))
108
+
109
+ def get_transitions(self):
110
+ return self._transitions
111
+
112
+ def get_actions(self):
113
+ return self._actions
114
+
115
+ def get_full_description(self):
116
+ text = dedent("""\
117
+ {}
118
+
119
+ Transitions
120
+ {}
121
+
122
+ Actions
123
+ {}
124
+ """).format(tabulate.tabulate(self.items()),
125
+ tabulate.tabulate(self.get_transitions(),
126
+ headers=['From State', 'To State', 'Count']),
127
+ tabulate.tabulate(self.get_actions(),
128
+ headers=['Type', 'Action', 'Count Success', 'Count Failure'])
129
+ )
130
+
131
+ return text
132
+
79
133
 
80
134
  class MidpointResource(MidpointObject):
81
135
 
@@ -99,6 +153,10 @@ class MidpointConnector(MidpointObject):
99
153
 
100
154
  class MidpointUser(MidpointObject):
101
155
 
156
+ @staticmethod
157
+ def strip_namespace(tag: str) -> str:
158
+ return re.sub(r'{.*}', '', tag)
159
+
102
160
  def __init__(self, xml_entity: Element):
103
161
  super().__init__()
104
162
  self['OID'] = xml_entity.attrib['oid']
@@ -110,11 +168,18 @@ class MidpointUser(MidpointObject):
110
168
  self['Email'] = optional_text(xml_entity.find('c:emailAddress', namespaces))
111
169
  self['OU'] = optional_text(xml_entity.find('c:organizationalUnit', namespaces))
112
170
 
171
+ activation = xml_entity.find('c:activation', namespaces)
113
172
  extfields = xml_entity.find('c:extension', namespaces)
114
173
 
115
- if extfields is not None:
116
- for extfield in extfields:
117
- self[re.sub(r'{.*}', '', extfield.tag)] = extfield.text
174
+ self._all_attributes = []
175
+
176
+ for child in itertools.chain(xml_entity, activation, extfields or []):
177
+ # Check if the child has no children and contains non-empty text
178
+ if len(child) == 0 and (child.text is not None and child.text.strip()):
179
+ self._all_attributes.append((self.strip_namespace(child.tag), child.text))
180
+
181
+ def get_all_attributes(self):
182
+ return self._all_attributes
118
183
 
119
184
 
120
185
  class MidpointOrganization(MidpointObject):
@@ -4,7 +4,7 @@ from argparse import ArgumentParser, RawTextHelpFormatter
4
4
  import tabulate
5
5
 
6
6
  # Task command wrapper parser
7
- from midpoint_cli.client import TaskExecutionFailure
7
+ from midpoint_cli.client import TaskExecutionFailure, MidpointTask
8
8
  from midpoint_cli.prompt.base import PromptBase
9
9
 
10
10
  task_parser = ArgumentParser(
@@ -40,6 +40,15 @@ task_wait_parser = ArgumentParser(
40
40
  )
41
41
  task_wait_parser.add_argument('task', help='Task to wait for. Can be an OID or a task name.', nargs='*')
42
42
 
43
+ # Task GET parser
44
+
45
+ task_get_parser = ArgumentParser(
46
+ formatter_class=RawTextHelpFormatter,
47
+ prog='task get',
48
+ description='Get data about a task.',
49
+ )
50
+ task_get_parser.add_argument('task', help='Task to wait for. Can be an OID or a task name.', nargs='*')
51
+
43
52
 
44
53
  class TaskClientPrompt(PromptBase):
45
54
 
@@ -51,6 +60,19 @@ class TaskClientPrompt(PromptBase):
51
60
  if ns.command == 'ls':
52
61
  tasks = self.client.get_tasks()
53
62
  print(tabulate.tabulate(tasks, headers='keys'))
63
+ elif ns.command == 'get':
64
+ tasks = self.client.get_tasks()
65
+ get_ns = task_wait_parser.parse_args(task_args[1:])
66
+
67
+ tasks_to_get = get_ns.task
68
+
69
+ for task_id in tasks_to_get:
70
+ task_obj: MidpointTask = tasks.find_object(task_id)
71
+ if task_obj is None:
72
+ print(f'Task reference not found: {task_id}')
73
+ else:
74
+ print(task_obj.get_full_description())
75
+
54
76
  elif ns.command == 'wait':
55
77
  tasks = self.client.get_tasks()
56
78
  wait_ns = task_wait_parser.parse_args(task_args[1:])
@@ -63,9 +85,9 @@ class TaskClientPrompt(PromptBase):
63
85
  for task_id in tasks_to_wait:
64
86
  task_obj = tasks.find_object(task_id)
65
87
  if task_obj is None:
66
- print('Task reference not found:', task_id)
88
+ print(f'Task reference not found: {task_id}')
67
89
  else:
68
- print('Waiting for task', task_obj.get_oid(), '/', task_obj.get_name())
90
+ print(f'Waiting for task {task_obj.get_oid()} / {task_obj.get_name()}')
69
91
  self.client.task_wait(task_obj.get_oid())
70
92
 
71
93
  elif ns.command in ['run', 'resume', 'suspend']:
@@ -85,11 +107,15 @@ class TaskClientPrompt(PromptBase):
85
107
 
86
108
  print('Task', task_obj.get_name(), '-', ns.command)
87
109
  try:
88
- self.client.task_action(task_obj.get_oid(), ns.command)
110
+ task_result = self.client.task_action(task_obj.get_oid(), ns.command)
111
+
112
+ if task_result is not None:
113
+ print(task_result.get_full_description())
89
114
  except TaskExecutionFailure as e:
90
115
  self.error_code = 1
91
116
  self.error_message = e.message
92
117
  break
118
+
93
119
  else:
94
120
  self.error_code = 1
95
121
  self.error_message = f'Unknown command {ns.command}'
@@ -3,6 +3,7 @@ from argparse import ArgumentParser, RawTextHelpFormatter
3
3
 
4
4
  import tabulate
5
5
 
6
+ from midpoint_cli.client import MidpointUser, MidpointObjectList, MidpointServerError
6
7
  # User command wrapper parser
7
8
  from midpoint_cli.prompt.base import PromptBase
8
9
 
@@ -23,10 +24,17 @@ user_parser.add_argument('arg', help='Optional command arguments.', nargs='*')
23
24
  user_search_parser = ArgumentParser(
24
25
  formatter_class=RawTextHelpFormatter,
25
26
  prog='user search',
26
- description='Search for a user.',
27
+ description='Search for users by substring.',
27
28
  )
28
29
  user_search_parser.add_argument('searchquery', help='A string fragment found in the user data.', nargs='+')
29
30
 
31
+ user_get_parser = ArgumentParser(
32
+ formatter_class=RawTextHelpFormatter,
33
+ prog='user get',
34
+ description='Search for users by OID.',
35
+ )
36
+ user_get_parser.add_argument('oid', help='An OID or name value.')
37
+
30
38
 
31
39
  class UserClientPrompt(PromptBase):
32
40
 
@@ -42,6 +50,14 @@ class UserClientPrompt(PromptBase):
42
50
  search_ns = user_search_parser.parse_args(user_args[1:])
43
51
  users = self.client.get_users().filter(search_ns.searchquery)
44
52
  self.print_users(users)
53
+ elif ns.command == 'get':
54
+ get_ns = user_get_parser.parse_args(user_args[1:])
55
+ try:
56
+ user = self.client.get_user(get_ns.oid)
57
+ self.print_user(user)
58
+ except MidpointServerError as e:
59
+ self.error_code = 1
60
+ self.error_message = str(e)
45
61
  else:
46
62
  self.error_code = 1
47
63
  self.error_message = f'Unknown command {ns.command}'
@@ -50,7 +66,11 @@ class UserClientPrompt(PromptBase):
50
66
  pass
51
67
 
52
68
  @staticmethod
53
- def print_users(users):
69
+ def print_user(user: MidpointUser):
70
+ print(tabulate.tabulate(user.get_all_attributes()))
71
+
72
+ @staticmethod
73
+ def print_users(users: MidpointObjectList):
54
74
  print(tabulate.tabulate(users, headers='keys'))
55
75
  print()
56
76
  print(f'Total: {len(users)} users')
@@ -1 +0,0 @@
1
- __version__ = '1.2.0'
File without changes
File without changes
File without changes