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