Compare commits

...

2 commits

Author SHA1 Message Date
Benjamin Renard
b1953a5b51 Add CI for testing and publishing (gitea version & debian package)
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2022-05-01 03:12:04 +02:00
Benjamin Renard
d33df5c23d Fix pylint/flake8 warnings 2022-05-01 02:39:51 +02:00
12 changed files with 327 additions and 81 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
*~ *~
dist/

71
.woodpecker.yml Normal file
View file

@ -0,0 +1,71 @@
clone:
git:
image: woodpeckerci/plugin-git
tags: true
pipeline:
test-pylint:
group: test
image: debian
commands:
- DEBIAN_FRONTEND=noninteractive apt-get -qq update < /dev/null > /dev/null
- DEBIAN_FRONTEND=noninteractive apt-get -qq -y install --no-install-recommends python3-ldap pylint3 < /dev/null > /dev/null
- pylint3 gitdch
test-flake8:
group: test
image: pipelinecomponents/flake8
commands:
- flake8 gitdch
build:
image: brenard/debian-python-deb
when:
event: tag
commands:
- echo "$GPG_KEY"|base64 -d|gpg --import
- ./build.sh --quiet
secrets: [ maintainer_name, maintainer_email, gpg_key, debian_codename ]
publish-dryrun:
group: publish
image: alpine
when:
event: tag
commands:
- ls dist/* dist/check-syncrepl-extended-*/check_syncrepl_extended
publish-gitea:
group: publish
image: plugins/gitea-release
when:
event: tag
settings:
api_key:
from_secret: gitea_token
base_url: https://gitea.zionetrix.net
files:
- dist/check-syncrepl-extended-*/check_syncrepl_extended
- dist/*.deb
checksum:
- md5
- sha512
publish-apt:
group: publish
image: brenard/curl
when:
event: tag
commands:
- curl -u $APT_CREDS -X POST -F file=@$( ls dist/check-syncrepl-extended_*_all.deb ) $APT_API_URL/files/gitdch
- curl -u $APT_CREDS -X POST -F file=@$( ls dist/check-syncrepl-extended_*.buildinfo ) $APT_API_URL/files/gitdch
- curl -u $APT_CREDS -X POST -F file=@$( ls dist/check-syncrepl-extended_*.changes ) $APT_API_URL/files/gitdch
- curl -u $APT_CREDS -X POST -F file=@$( ls dist/check-syncrepl-extended_*.dsc ) $APT_API_URL/files/gitdch
- curl -u $APT_CREDS -X POST -F file=@$( ls dist/check-syncrepl-extended_*.tar.gz ) $APT_API_URL/files/gitdch
- curl -u $APT_CREDS -X POST $APT_API_URL/repos/$APT_REPO_NAME/include/gitdch
- APT_SNAP_NAME=$(date +%s)_$APT_REPO_NAME
- >
curl -u $APT_CREDS -X POST -H 'Content-Type: application/json' --data "{\"Name\":\"$APT_SNAP_NAME\"}" $APT_API_URL/repos/$APT_REPO_NAME/snapshots
- >
curl -u $APT_CREDS -X PUT -H 'Content-Type: application/json' --data "{\"Snapshots\": [{\"Component\": \"main\", \"Name\": \"$APT_SNAP_NAME\"}]}" $APT_API_URL/publish/:./$APT_REPO_NAME
secrets: [ apt_api_url, apt_creds, apt_repo_name ]

61
build.sh Executable file
View file

@ -0,0 +1,61 @@
#!/bin/bash
QUIET_ARG=""
[ "$1" == "--quiet" ] && QUIET_ARG="--quiet"
# Enter source directory
cd $( dirname $0 )
echo "Clean previous build..."
rm -fr dist
echo "Detect version using git describe..."
VERSION="$( git describe --tags|sed 's/^[^0-9]*//' )"
echo "Create building environemt..."
BDIR=dist/check-syncrepl-extended-$VERSION
mkdir -p $BDIR
[ -z "$QUIET_ARG" ] && RSYNC_ARG="-v" || RSYNC_ARG=""
rsync -a $RSYNC_ARG debian/ $BDIR/debian/
cp check_syncrepl_extended $BDIR/
echo "Set VERSION=$VERSION in gitdch using sed..."
sed -i "s/^VERSION *=.*$/VERSION = '$VERSION'/" $BDIR/check_syncrepl_extended
if [ -z "$DEBIAN_CODENAME" ]
then
echo "Retreive debian codename using lsb_release..."
DEBIAN_CODENAME=$( lsb_release -c -s )
else
echo "Use debian codename from environment ($DEBIAN_CODENAME)"
fi
echo "Generate debian changelog using gitdch..."
GITDCH_ARGS=('--verbose')
[ -n "$QUIET_ARG" ] && GITDCH_ARGS=('--warning')
if [ -n "$MAINTAINER_NAME" ]
then
echo "Use maintainer name from environment ($MAINTAINER_NAME)"
GITDCH_ARGS+=("--maintainer-name" "${MAINTAINER_NAME}")
fi
if [ -n "$MAINTAINER_EMAIL" ]
then
echo "Use maintainer email from environment ($MAINTAINER_EMAIL)"
GITDCH_ARGS+=("--maintainer-email" "$MAINTAINER_EMAIL")
fi
gitdch \
--package-name check-syncrepl-extended \
--version "${VERSION}" \
--code-name $DEBIAN_CODENAME \
--output $BDIR/debian/changelog \
"${GITDCH_ARGS[@]}"
if [ -n "$MAINTAINER_NAME" -a -n "$MAINTAINER_EMAIL" ]
then
echo "Set Maintainer field in debian control file ($MAINTAINER_NAME <$MAINTAINER_EMAIL>)..."
sed -i "s/^Maintainer: .*$/Maintainer: $MAINTAINER_NAME <$MAINTAINER_EMAIL>/" $BDIR/debian/control
fi
echo "Build debian package..."
cd $BDIR
dpkg-buildpackage

View file

@ -43,15 +43,19 @@ import getpass
import ldap import ldap
from ldap import LDAPError # pylint: disable=no-name-in-module from ldap import LDAPError # pylint: disable=no-name-in-module
from ldap.controls import SimplePagedResultsControl from ldap.controls import SimplePagedResultsControl
import ldap.modlist as modlist from ldap import modlist
VERSION = '0.0'
TOUCH_VALUE = '%%TOUCH%%' TOUCH_VALUE = '%%TOUCH%%'
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description=("Script to check LDAP syncrepl replication state between "+ description=(
"two servers."), "Script to check LDAP syncrepl replication state between "
epilog=("Author: Benjamin Renard <brenard@easter-eggs.com>, "+ "two servers."),
"Source: https://gogs.zionetrix.net/bn8/check_syncrepl_extended") epilog=(
'Author: Benjamin Renard <brenard@easter-eggs.com>, '
f'Version: {VERSION}, '
'Source: https://gogs.zionetrix.net/bn8/check_syncrepl_extended')
) )
parser.add_argument( parser.add_argument(
@ -75,11 +79,12 @@ parser.add_argument(
dest="serverid", dest="serverid",
action="store", action="store",
type=int, type=int,
help=("Compare contextCSN of a specific master. Useful in MultiMaster "+ help=(
"setups where each master has a unique ID and a contextCSN for "+ "Compare contextCSN of a specific master. Useful in MultiMaster "
"each replicated master exists. A valid serverID is a integer "+ "setups where each master has a unique ID and a contextCSN for "
"value from 0 to 4095 (limited to 3 hex digits, example: '12' "+ "each replicated master exists. A valid serverID is a integer "
"compares the contextCSN matching '#00C#')"), "value from 0 to 4095 (limited to 3 hex digits, example: '12' "
"compares the contextCSN matching '#00C#')"),
default=False default=False
) )
@ -169,8 +174,9 @@ parser.add_argument(
"--only-check-contextCSN", "--only-check-contextCSN",
dest="onlycheckcontextcsn", dest="onlycheckcontextcsn",
action="store_true", action="store_true",
help=("Only check servers root contextCSN (objects check disabled, "+ help=(
"default : False)"), "Only check servers root contextCSN (objects check disabled, "
"default : False)"),
default=False default=False
) )
@ -196,11 +202,13 @@ parser.add_argument(
dest="touch", dest="touch",
action="store", action="store",
type=str, type=str,
help=("Touch attribute giving in parameter to force resync a this LDAP "+ help=(
"object from provider. A value '{}' will be add to this attribute "+ 'Touch attribute giving in parameter to force resync a this LDAP '
"and remove after. The user use to connect to the LDAP directory "+ f'object from provider. A value "{TOUCH_VALUE}" will be add to this '
"must have write permission on this attribute on each object." 'attribute and remove after. The user use to connect to the LDAP '
).format(TOUCH_VALUE), 'directory must have write permission on this attribute on each '
'object.'
),
default=None default=None
) )
@ -225,16 +233,18 @@ parser.add_argument(
dest="page_size", dest="page_size",
action="store", action="store",
type=int, type=int,
help=("Page size: if defined, paging control using LDAP v3 extended " + help=(
"control will be enabled."), "Page size: if defined, paging control using LDAP v3 extended "
"control will be enabled."),
default=None default=None
) )
options = parser.parse_args() options = parser.parse_args()
if options.nocheckcontextcsn and options.onlycheckcontextcsn: if options.nocheckcontextcsn and options.onlycheckcontextcsn:
parser.error("You can't use both --no-check-contextCSN and "+ parser.error(
"--only-check-contextCSN parameters and the same time") "You can't use both --no-check-contextCSN and "
"--only-check-contextCSN parameters and the same time")
if options.nagios: if options.nagios:
sys.exit(3) sys.exit(3)
sys.exit(1) sys.exit(1)
@ -251,9 +261,11 @@ if not options.basedn:
sys.exit(3) sys.exit(3)
sys.exit(1) sys.exit(1)
if not 0 <= options.serverid <= 4095: if not 0 <= options.serverid <= 4095:
parser.error("ServerID should be a integer value from 0 to 4095 "+ parser.error(
"(limited to 3 hexadecimal digits).") "ServerID should be a integer value from 0 to 4095 "
"(limited to 3 hexadecimal digits).")
if options.nagios: if options.nagios:
sys.exit(3) sys.exit(3)
sys.exit(1) sys.exit(1)
@ -282,7 +294,8 @@ elif options.quiet:
else: else:
logging.basicConfig(level=logging.INFO, format=FORMAT) logging.basicConfig(level=logging.INFO, format=FORMAT)
class LdapServer(object):
class LdapServer:
uri = "" uri = ""
dn = "" dn = ""
@ -302,7 +315,8 @@ class LdapServer(object):
if self.con == 0: if self.con == 0:
try: try:
con = ldap.initialize(self.uri) con = ldap.initialize(self.uri)
con.protocol_version = ldap.VERSION3 # pylint: disable=no-member # pylint: disable=no-member
con.protocol_version = ldap.VERSION3
if self.start_tls: if self.start_tls:
con.start_tls_s() con.start_tls_s()
if self.dn: if self.dn:
@ -316,18 +330,20 @@ class LdapServer(object):
def getContextCSN(self, basedn=False, serverid=False): def getContextCSN(self, basedn=False, serverid=False):
if not basedn: if not basedn:
basedn = self.dn basedn = self.dn
data = self.search(basedn, '(objectclass=*)', attrs=['contextCSN'], scope='base') data = self.search(
basedn, '(objectclass=*)', attrs=['contextCSN'], scope='base')
if data: if data:
contextCSNs = data[0][0][1]['contextCSN'] contextCSNs = data[0][0][1]['contextCSN']
logging.debug('Found contextCSNs %s', contextCSNs) logging.debug('Found contextCSNs %s', contextCSNs)
if serverid is False: if serverid is False:
return contextCSNs[0] return contextCSNs[0]
csnid = str(format(serverid, 'X')).zfill(3) csnid = str(format(serverid, 'X')).zfill(3)
sub = str.encode('#%s#' % csnid, encoding="ascii", errors="replace") sub = str.encode(f'#{csnid}#', encoding="ascii", errors="replace")
CSN = [s for s in contextCSNs if sub in s] CSN = [s for s in contextCSNs if sub in s]
if not CSN: if not CSN:
logging.error( logging.error(
"No contextCSN matching with ServerID %s (=%s) could be found.", "No contextCSN matching with ServerID %s (=%s) could be "
"found.",
serverid, sub serverid, sub
) )
return False return False
@ -342,11 +358,12 @@ class LdapServer(object):
return ldap.SCOPE_ONELEVEL # pylint: disable=no-member return ldap.SCOPE_ONELEVEL # pylint: disable=no-member
if scope == 'sub': if scope == 'sub':
return ldap.SCOPE_SUBTREE # pylint: disable=no-member return ldap.SCOPE_SUBTREE # pylint: disable=no-member
raise Exception("Unknown LDAP scope '%s'" % scope) raise Exception(f'Unknown LDAP scope "{scope}"')
def search(self, basedn, filterstr, attrs=None, scope=None): def search(self, basedn, filterstr, attrs=None, scope=None):
if self.page_size: if self.page_size:
return self.paged_search(basedn, filterstr, attrs=attrs, scope=scope) return self.paged_search(
basedn, filterstr, attrs=attrs, scope=scope)
res_id = self.con.search( res_id = self.con.search(
basedn, self.get_scope(scope if scope else 'sub'), basedn, self.get_scope(scope if scope else 'sub'),
filterstr, attrs if attrs else [] filterstr, attrs if attrs else []
@ -371,7 +388,8 @@ class LdapServer(object):
basedn, self.get_scope(scope if scope else 'sub'), basedn, self.get_scope(scope if scope else 'sub'),
filterstr, attrs if attrs else [], serverctrls=[pg_ctrl] filterstr, attrs if attrs else [], serverctrls=[pg_ctrl]
) )
res_type, res_data, res_id, serverctrls = self.con.result3(res_id) # pylint: disable=unused-variable # pylint: disable=unused-variable
res_type, res_data, res_id, serverctrls = self.con.result3(res_id)
for serverctrl in serverctrls: for serverctrl in serverctrls:
if serverctrl.controlType == SimplePagedResultsControl.controlType: if serverctrl.controlType == SimplePagedResultsControl.controlType:
pg_ctrl.cookie = serverctrl.cookie pg_ctrl.cookie = serverctrl.cookie
@ -382,7 +400,7 @@ class LdapServer(object):
def update_object(self, dn, old, new): def update_object(self, dn, old, new):
ldif = modlist.modifyModlist(old, new) ldif = modlist.modifyModlist(old, new)
if ldif == []: if not ldif:
return True return True
try: try:
logging.debug('Update object %s: %s', dn, ldif) logging.debug('Update object %s: %s', dn, ldif)
@ -419,7 +437,9 @@ class LdapServer(object):
dn, attr, old, new dn, attr, old, new
) )
if self.update_object(dn, old, new): if self.update_object(dn, old, new):
logging.info('Restore original value of attribute "%s" of object "%s"', attr, dn) logging.info(
'Restore original value of attribute "%s" of object "%s"',
attr, dn)
if options.removetouchvalue and TOUCH_VALUE in old[attr]: if options.removetouchvalue and TOUCH_VALUE in old[attr]:
old[attr].remove(TOUCH_VALUE) old[attr].remove(TOUCH_VALUE)
self.update_object(dn=dn, old=new, new=old) self.update_object(dn=dn, old=new, new=old)
@ -428,8 +448,11 @@ class LdapServer(object):
logging.error('Error touching object "%s"', dn, exc_info=True) logging.error('Error touching object "%s"', dn, exc_info=True)
return False return False
if options.nocheckcert: if options.nocheckcert:
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) # pylint: disable=no-member # pylint: disable=no-member
ldap.set_option(
ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
servers = [options.provider, options.consumer] servers = [options.provider, options.consumer]
@ -445,13 +468,14 @@ for srv in servers:
if not LdapServers[srv].connect(): if not LdapServers[srv].connect():
if options.nagios: if options.nagios:
print("UNKWNON - Failed to connect to %s" % srv) # pylint: disable=print-statement print(f'UNKWNON - Failed to connect to {srv}')
sys.exit(3) sys.exit(3)
else: else:
sys.exit(1) sys.exit(1)
if not options.nocheckcontextcsn: if not options.nocheckcontextcsn:
LdapServersCSN[srv] = LdapServers[srv].getContextCSN(options.basedn, options.serverid) LdapServersCSN[srv] = LdapServers[srv].getContextCSN(
options.basedn, options.serverid)
logging.info('ContextCSN of %s: %s', srv, LdapServersCSN[srv]) logging.info('ContextCSN of %s: %s', srv, LdapServersCSN[srv])
if not options.onlycheckcontextcsn: if not options.onlycheckcontextcsn:
@ -459,11 +483,15 @@ for srv in servers:
LdapObjects[srv] = {} LdapObjects[srv] = {}
if options.attrs: if options.attrs:
for obj in LdapServers[srv].search(options.basedn, options.filterstr, []): for obj in LdapServers[srv].search(
options.basedn, options.filterstr, []
):
logging.debug('Found on %s: %s', srv, obj[0][0]) logging.debug('Found on %s: %s', srv, obj[0][0])
LdapObjects[srv][obj[0][0]] = obj[0][1] LdapObjects[srv][obj[0][0]] = obj[0][1]
else: else:
for obj in LdapServers[srv].search(options.basedn, options.filterstr, ['entryCSN']): for obj in LdapServers[srv].search(
options.basedn, options.filterstr, ['entryCSN']
):
logging.debug( logging.debug(
'Found on %s: %s / %s', 'Found on %s: %s / %s',
srv, obj[0][0], obj[0][1]['entryCSN'][0] srv, obj[0][0], obj[0][1]['entryCSN'][0]
@ -482,33 +510,36 @@ if not options.onlycheckcontextcsn:
not_sync[srv] = [] not_sync[srv] = []
if options.attrs: if options.attrs:
logging.info("Check if objects a are synchronized (by comparing attributes's values)") logging.info(
"Check if objects a are synchronized (by comparing attributes's "
"values)")
else: else:
logging.info('Check if objets are synchronized (by comparing entryCSN)') logging.info(
'Check if objets are synchronized (by comparing entryCSN)')
for obj in LdapObjects[options.provider]: for obj in LdapObjects[options.provider]:
logging.debug('Check obj %s', obj) logging.debug('Check obj %s', obj)
for srv in LdapObjects: for srv_name, srv in LdapObjects.items():
if srv == options.provider: if srv_name == options.provider:
continue continue
if obj in LdapObjects[srv]: if obj in srv:
touch = False touch = False
if LdapObjects[options.provider][obj] != LdapObjects[srv][obj]: if LdapObjects[options.provider][obj] != srv[obj]:
if options.attrs: if options.attrs:
attrs_list = [] attrs_list = []
for attr in LdapObjects[options.provider][obj]: for attr in LdapObjects[options.provider][obj]:
if attr in excl_attrs: if attr in excl_attrs:
continue continue
if attr not in LdapObjects[srv][obj]: if attr not in srv[obj]:
attrs_list.append(attr) attrs_list.append(attr)
logging.debug( logging.debug(
"Obj %s not synchronized: %s not present on %s", "Obj %s not synchronized: %s not present on %s",
obj, ','.join(attrs_list), srv obj, ','.join(attrs_list), srv_name
) )
touch = True touch = True
else: else:
LdapObjects[srv][obj][attr].sort() srv[obj][attr].sort()
LdapObjects[options.provider][obj][attr].sort() LdapObjects[options.provider][obj][attr].sort()
if LdapObjects[srv][obj][attr] != LdapObjects[options.provider][obj][attr]: if srv[obj][attr] != LdapObjects[options.provider][obj][attr]:
attrs_list.append(attr) attrs_list.append(attr)
logging.debug( logging.debug(
"Obj %s not synchronized: %s not same value(s)", "Obj %s not synchronized: %s not same value(s)",
@ -516,26 +547,28 @@ if not options.onlycheckcontextcsn:
) )
touch = True touch = True
if attrs_list: if attrs_list:
not_sync[srv].append("%s (%s)" % (obj, ','.join(attrs_list))) not_sync[srv_name].append(f'{obj} ({",".join(attrs_list)})')
else: else:
logging.debug( logging.debug(
"Obj %s not synchronized: %s <-> %s", "Obj %s not synchronized: %s <-> %s",
obj, LdapObjects[options.provider][obj], LdapObjects[srv][obj] obj, LdapObjects[options.provider][obj], srv[obj]
) )
not_sync[srv].append(obj) not_sync[srv_name].append(obj)
if touch and options.touch: if touch and options.touch:
orig_value = [] orig_value = []
if options.touch in LdapObjects[options.provider][obj]: if options.touch in LdapObjects[options.provider][obj]:
orig_value = LdapObjects[options.provider][obj][options.touch] orig_value = LdapObjects[options.provider][obj][options.touch]
LdapServers[options.provider].touch_object(obj, options.touch, orig_value) LdapServers[options.provider].touch_object(
obj, options.touch, orig_value)
else: else:
logging.debug('Obj %s: not found on %s', obj, srv) logging.debug('Obj %s: not found on %s', obj, srv_name)
not_found[srv].append(obj) not_found[srv_name].append(obj)
if options.touch: if options.touch:
orig_value = [] orig_value = []
if options.touch in LdapObjects[options.provider][obj]: if options.touch in LdapObjects[options.provider][obj]:
orig_value = LdapObjects[options.provider][obj][options.touch] orig_value = LdapObjects[options.provider][obj][options.touch]
LdapServers[options.provider].touch_object(obj, options.touch, orig_value) LdapServers[options.provider].touch_object(
obj, options.touch, orig_value)
for obj in LdapObjects[options.consumer]: for obj in LdapObjects[options.consumer]:
logging.debug('Check obj %s of consumer', obj) logging.debug('Check obj %s of consumer', obj)
@ -551,54 +584,74 @@ if options.nagios:
if not LdapServersCSN[options.provider]: if not LdapServersCSN[options.provider]:
errors.append('ContextCSN of LDAP server provider could not be found') errors.append('ContextCSN of LDAP server provider could not be found')
else: else:
long_output.append('ContextCSN on LDAP server provider = %s' % LdapServersCSN[options.provider]) long_output.append(
for srv in LdapServersCSN: f'ContextCSN on LDAP server provider = {LdapServersCSN[options.provider]}')
if srv == options.provider: for srv_name, srv_csn in LdapServersCSN.items():
if srv_name == options.provider:
continue continue
if not LdapServersCSN[srv]: if not srv_csn:
errors.append('ContextCSN of %s not found' % srv) errors.append(f'ContextCSN of {srv_name} not found')
elif LdapServersCSN[srv] != LdapServersCSN[options.provider]: elif srv_csn != LdapServersCSN[options.provider]:
errors.append('ContextCSN of %s not the same of provider' % srv) errors.append(
long_output.append('ContextCSN on LDAP server %s = %s' % (srv, LdapServersCSN[srv])) f'ContextCSN of {srv_name} not the same of provider')
long_output.append(
f'ContextCSN on LDAP server {srv_name} = {srv_csn}')
if not options.onlycheckcontextcsn: if not options.onlycheckcontextcsn:
if not_found[options.consumer]: if not_found[options.consumer]:
errors.append("%s not found object(s) on consumer" % len(not_found[options.consumer])) errors.append(
long_output.append("Object(s) not found on server %s (consumer) :" % options.consumer) f'{len(not_found[options.consumer])} not found object(s) on '
'consumer')
long_output.append(
f'Object(s) not found on server {options.consumer} '
'(consumer):')
for obj in not_found[options.consumer]: for obj in not_found[options.consumer]:
long_output.append(" - %s" % obj) long_output.append(f' - {obj}')
if not_found[options.provider]: if not_found[options.provider]:
errors.append("%s not found object(s) on provider" % len(not_found[options.provider])) errors.append(
long_output.append("Object(s) not found on server %s (provider) :" % options.provider) f'{len(not_found[options.provider])} not found object(s) on '
'provider')
long_output.append(
f'Object(s) not found on server {options.provider} '
'(provider):')
for obj in not_found[options.provider]: for obj in not_found[options.provider]:
long_output.append(" - %s" % obj) long_output.append(f' - {obj}')
if not_sync[options.consumer]: if not_sync[options.consumer]:
errors.append("%s not synchronized object(s) on consumer" % len(not_sync[options.consumer])) errors.append(
long_output.append("Object(s) not synchronized on server %s (consumer) :" % options.consumer) f'{len(not_sync[options.consumer])} not synchronized object(s) '
'on consumer')
long_output.append(
f'Object(s) not synchronized on server {options.consumer} '
'(consumer):')
for obj in not_sync[options.consumer]: for obj in not_sync[options.consumer]:
long_output.append(" - %s" % obj) long_output.append(f' - {obj}')
if errors: if errors:
print("CRITICAL: " + ', '.join(errors) + "\n\n" + "\n".join(long_output)) # pylint: disable=print-statement print(f'CRITICAL: {", ".join(errors)}')
print('\n\n')
print("\n".join(long_output))
sys.exit(2) sys.exit(2)
else: else:
print('OK: consumer and provider are synchronized') # pylint: disable=print-statement print('OK: consumer and provider are synchronized')
sys.exit(0) sys.exit(0)
else: else:
noerror = True noerror = True
for srv in servers: for srv in servers:
if not options.nocheckcontextcsn: if not options.nocheckcontextcsn:
if not LdapServersCSN[options.provider]: if not LdapServersCSN[options.provider]:
logging.warning('ContextCSN of LDAP server provider could not be found') logging.warning(
'ContextCSN of LDAP server provider could not be found')
noerror = False noerror = False
else: else:
for srv in LdapServersCSN: for srv_name, srv_csn in LdapServersCSN.items():
if srv == options.provider: if srv_name == options.provider:
continue continue
if not LdapServersCSN[srv]: if not srv_csn:
logging.warning('ContextCSN of %s not found', srv) logging.warning('ContextCSN of %s not found', srv_name)
noerror = False noerror = False
elif LdapServersCSN[srv] != LdapServersCSN[options.provider]: elif srv_csn != LdapServersCSN[options.provider]:
logging.warning('ContextCSN of %s not the same of provider', srv) logging.warning(
'ContextCSN of %s not the same of provider',
srv_name)
noerror = False noerror = False
if not options.onlycheckcontextcsn: if not options.onlycheckcontextcsn:

1
debian/compat vendored Normal file
View file

@ -0,0 +1 @@
11

30
debian/control vendored Normal file
View file

@ -0,0 +1,30 @@
Source: check-syncrepl-extended
Section: admin
Priority: optional
Maintainer: Debian Zionetrix - check-syncrepl-extended <debian+check-syncrepl-extended@zionetrix.net>
Build-Depends: debhelper (>> 11.0.0)
Standards-Version: 3.9.6
Package: check-syncrepl-extended
Architecture: all
Depends: ${misc:Depends}, python3, python3-ldap
Description: Check LDAP syncrepl replication state between two servers
This script check LDAP syncrepl replication state between two servers. One
server is consider as provider and the other as consumer.
This script can check replication state with two method :
- by the fisrt, entryCSN of all entries of LDAP directory will be compare
between two servers
- by the second, all values of all atributes of all entries will be compare
between two servers.
In all case, contextCSN of servers will be compare and entries not present in
consumer or in provider will be notice. You can decide to disable contextCSN
verification by using argument --no-check-contextCSN.
This script is also able to "touch" LDAP object on provider to force
synchronisation of this object. This mechanism consist to add '%%TOUCH%%'
value to an attribute of this object and remove it just after. The touched
attribute is specify by parameter --touch. Of course, couple of DN and
password provided, must have write right on this attribute.
If your prefer, you can use --replace-touch parameter to replace value of
touched attribute instead of adding the touched value. Use-ful in case of
single-value attribute.
To use this script as an Icinga (or Nagios) plugin, use -n argument

20
debian/copyright vendored Normal file
View file

@ -0,0 +1,20 @@
This package was written by Benjamin Renard <brenard@zionetrix.net>.
Copyright (C) 2022 Benjamin Renard <brenard@zionetrix.net>
check-syncrepl-extended is licensed under the GNU general public license, version 3.
check-syncrepl-extended is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2, or (at your option) any later version.
check-syncrepl-extended is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
check-syncrepl-extended; see the file COPYING. If not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
On Debian systems, a copy of the GNU General Public License is available in
/usr/share/common-licenses/GPL-3 as part of the base-files package.

1
debian/dirs vendored Normal file
View file

@ -0,0 +1 @@
usr/lib/nagios/plugins

1
debian/install vendored Normal file
View file

@ -0,0 +1 @@
check_syncrepl_extended usr/lib/nagios/plugins

4
debian/rules vendored Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/make -f
#export DH_VERBOSE=1
%:
dh $@

1
debian/source/format vendored Normal file
View file

@ -0,0 +1 @@
1.0

2
setup.cfg Normal file
View file

@ -0,0 +1,2 @@
[flake8]
ignore = E501