6 |
6 |
import ldap.sasl
|
7 |
7 |
|
8 |
8 |
from ldaptools import ldif_utils, paged, ldap_source
|
9 |
|
from ldaptools.synchronize import Synchronize
|
|
9 |
from ldaptools.synchronize import Synchronize, Create, Rename, Update, Delete
|
|
10 |
|
|
11 |
|
|
12 |
operation_types = {
|
|
13 |
'create': Create,
|
|
14 |
'rename': Rename,
|
|
15 |
'update': Update,
|
|
16 |
'delete': Delete,
|
|
17 |
}
|
10 |
18 |
|
11 |
19 |
|
12 |
20 |
def source_uri(value):
|
... | ... | |
35 |
43 |
return value
|
36 |
44 |
|
37 |
45 |
|
|
46 |
def max_changes_threshold(value):
|
|
47 |
if ':' in value:
|
|
48 |
try:
|
|
49 |
operation_type, threshold = value.strip().split(':')
|
|
50 |
except ValueError as exc:
|
|
51 |
raise argparse.ArgumentTypeError(
|
|
52 |
'Invalid operation threshold %s' % value) from exc
|
|
53 |
if operation_type not in operation_types:
|
|
54 |
raise argparse.ArgumentTypeError('Unknown %s operation type' % operation_type)
|
|
55 |
operation_type = operation_types[operation_type]
|
|
56 |
else:
|
|
57 |
operation_type = None
|
|
58 |
threshold = value
|
|
59 |
try:
|
|
60 |
threshold = int(threshold)
|
|
61 |
if threshold < 1:
|
|
62 |
raise ValueError
|
|
63 |
except ValueError as exc:
|
|
64 |
raise argparse.ArgumentTypeError(
|
|
65 |
'Invalid threshold %s, must be a positive integer' % threshold) from exc
|
|
66 |
return (operation_type, threshold)
|
|
67 |
|
|
68 |
|
38 |
69 |
def main(args=None):
|
39 |
70 |
parser = argparse.ArgumentParser(description='''\
|
40 |
71 |
Synchronize an LDIF file or a source LDAP directory to another directory
|
... | ... | |
84 |
115 |
parser.add_argument('--fake',
|
85 |
116 |
action='store_true',
|
86 |
117 |
help='compute synchronization actions but do not apply')
|
|
118 |
parser.add_argument(
|
|
119 |
'--max-changes', type=max_changes_threshold, action='append',
|
|
120 |
help=(
|
|
121 |
'maximum changes count: permit to inhibit synchronization '
|
|
122 |
'if too much changes are detected. You could specify a '
|
|
123 |
'threshold by operation type (%s), for instance, '
|
|
124 |
'"delete:10". To specify a global threshold, just specify '
|
|
125 |
'the change count. You could specify multiple threshold '
|
|
126 |
'by repeating this parameter.' % ', '.join(operation_types)
|
|
127 |
))
|
|
128 |
parser.add_argument(
|
|
129 |
'--force', action='store_true',
|
|
130 |
help='Force synchronization, even if a threshold is reached.')
|
87 |
131 |
parser.add_argument('--verbose',
|
88 |
132 |
action='store_true',
|
89 |
133 |
help='print all actions to stdout')
|
... | ... | |
147 |
191 |
print(' -', action)
|
148 |
192 |
if not synchronize.actions:
|
149 |
193 |
print('Nothing to do.')
|
|
194 |
|
|
195 |
if options.max_changes:
|
|
196 |
threshold_reached = False
|
|
197 |
for operation_type, threshold in options.max_changes:
|
|
198 |
count = len([
|
|
199 |
action for action in synchronize.actions
|
|
200 |
if not operation_type or isinstance(action, operation_type)
|
|
201 |
])
|
|
202 |
if count > threshold:
|
|
203 |
print(
|
|
204 |
'Max %soperation threshold reached (%d > %d)' % (
|
|
205 |
"%s " % operation_type.__name__.lower() if operation_type else '',
|
|
206 |
count, threshold))
|
|
207 |
threshold_reached = True
|
|
208 |
if threshold_reached:
|
|
209 |
if not options.force:
|
|
210 |
print('Block this synchronization. Run again with --force parameter to force.')
|
|
211 |
sys.exit(2)
|
|
212 |
print('Force mode, continue')
|
|
213 |
|
150 |
214 |
if not options.fake:
|
151 |
215 |
synchronize.apply_actions()
|
152 |
216 |
failed_actions = [action for action in synchronize.actions if action.errors]
|
153 |
|
-
|