diff --git a/test/.coveragerc b/.coveragerc similarity index 88% rename from test/.coveragerc rename to .coveragerc index 4a280d0..5e7d51a 100644 --- a/test/.coveragerc +++ b/.coveragerc @@ -17,11 +17,10 @@ exclude_lines = if 0: if __name__ == .__main__.: source = - ../bin/ - ../certcheck/ + ./certcheck/ [html] -directory = output_coverage_html +directory = test/output_coverage_html [paths] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9022dbd --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.pyc +/.coverage +/build* +certcheck.egg-info/* +debian/files diff --git a/README.md b/README.md index e73e833..9ebaf8c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,182 @@ -Copyright (c) 2013 Spotify AB -Certcheck - Simple certificate checker -Documentation for this project should be found in the doc directory. +# _Certchecker_ -Currently this is a dump of an internal tool; YMMV trying to get this to work -outside Spotify. +_Certchecker is a certificate expiration check capable of scanning GIT repos +and sending data on expiring/expired certificates back to the monitoring system +(currently only Riemann)._ + +## Project Setup + +In order to run certchecker you need to following dependencies installed: +- Bernhard - Riemann client library (https://github.com/banjiewen/bernhard) +- Google's protobuf library +- yaml bindings for python (http://pyyaml.org/) +- Dulwich - python implementation of GIT (https://www.samba.org/~jelmer/dulwich/docs/) +- ssh command in your PATH +- argparse library + +You can also use debian packaging rules from debian/ directory to build a deb +package. + +## Usage + +### Configuration + +Actions taken by the script are determined by its command line and the +configuration file. The command line has a build-in help system: + +``` +usage: certcheck [-h] [--version] -c CONFIG_FILE [-v] [-s] [-d] + +Simple certificate expiration check + +optional arguments: + -h, --help show this help message and exit + --version show program's version number and exit + -c CONFIG_FILE, --config-file CONFIG_FILE + Location of the configuration file + -v, --verbose Provide extra logging messages. + -s, --std-err Log to stderr instead of syslog + -d, --dont-send Do not send data to Riemann [use for debugging] + +Author: vespian a t wp.pl +``` + +The configuration file is a plain YAML document. It's syntax is as follows: + +``` +--- +lockfile: /tmp/certcheck.lock +warn_treshold: 30 +critical_treshold: 15 +riemann_hosts: + static: + - 192.168.122.16:5555:udp + - 192.168.122.16:5555:tcp + by_srv: + - _riemann._tcp + - _riemann._udp +riemann_tags: + - production + - class::certcheck +repo_host: git.example.net +repo_port: 22 +repo_url: /example-repo +repo_masterbranch: refs/heads/production +repo_localdir: /tmp/certcheck-temprepo +repo_user: certcheck +repo_pubkey: ./certcheck_id_rsa + # format - dict, hash as a key, and value as a comment + # sha1sum ./certificate_to_be_ignored +ignored_certs: + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: "some VPN key" + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: "some unused certificate" +``` + +### Operation + +The script connects to the $repo_user@$repo_host:$repo_port via SSH and clones +repository $repo_url to a *bare* repository in "$repo_tmpdir/repository". If +the repository already exists, it is only updated with newest referances. Only +$repo_masterbranch branch is pulled in along with all the objects it points to, +topic branches are not downloaded. + +The connection is established using the $repo_pubkey pubkey, and the $repo_user +itself should have very limited privileges. + +Next, the repository is scanned in search of files ending with one of the +certcheck:CERTIFICATE_EXTENSIONS extensions. Currently all possible +certificate extensions are listed but only ['pem', 'crt', 'cer'] are currently +supported (see certcheck:get_cert_expiration method). For the remaing ones +only a warning is issued. + +For each certificate found a sha1sum is computed, and if the result is found in +$ignored_certs hash, then the certificate is ignored even if it expires/exp- +ired. + +If the number of days till the certificate expires is less than $critical_tresh +(by default 15) - a "critical" partial status is generated, if it less than +$warn_tresh but more than $critical_tresh - a "warning" partial status is gene- +rated. Unsuported certificate yields an 'unknown' state and expired ones of +course the 'critical'. + +All the 'partial status' updates are agregated and each message can only ele- +vate up the final status of the metric send to Riemann. Currently, the hierar- +chy is as follows: + + (lowest)ok->warn->critical->unknown(highest) + +script errors, exceptions and unexcpected conditions result in imidiate elevation +to 'unknown' status and sending the metric to monitoring system ASAP if only +possible. + +IP addresses/ports of the Riemann instances can be defined in two ways: + * statically, by providing a list of riemann instances in $riemann_servers + var. The format of the list entry is hostname:port:proto. 'proto' can be one + of 'udp' or 'tcp'. + * by providing a SRV record, i.e. '_riemann._udp'. All the values + (host, port) will be resolved automatically. Protocol is chosen basing on + the SRV entry itself. + +The final metric is send to *all* Riemann instances with TTL equal to +certcheck:DATA_TTL == 25 hours. + +### Maintenance + +In order to not to let the "$repo_tmpdir/repository" repository grow endlessly +a 'git gc' command should be executed once a day by i.e. a cronjob. It should +repack all the packs and remove dangling objects. +Please see the doc/USAGE.md file for details. + +## Contributing + +All patches are welcome ! Please use Github issue tracking and/or create a pull +request. + +### Testing + +Currenlty the unittest python library is used to perform all the testing. In +test/ directory you can find: +- modules/ - modules used by unittests +- moduletests/ - the unittests themselves +- fabric/ - sample input files and test certificates temporary directories +- output_coverage_html/ - coverage tests results in a form of an html webpage + +Unittests can be started either by using *nosetest* command: + +``` +certcheck/ (master✗) # nosetests +[20:33:02] +...... +---------------------------------------------------------------------- +Ran 6 tests in 0.449s + +OK +``` + +or by issuing the *run_tests.py* command: + +``` +certcheck/ (master✗) # run_tests.py +[20:33:04] +Created test certificate expired_3_days.pem +Created test certificate expire_6_days.pem +Created test certificate expire_21_days.pem +Created test certificate expire_41_days.pem +Created test certificate expire_41_days.der +...... +---------------------------------------------------------------------- +Ran 6 tests in 0.362s + +OK +``` + +The difference is that the *run_tests.py* takes care of generating coverage +reports for you. + +All the dependencies required for performing the unittests are decribed in debian +packaging scripts and are as follows: +- unittests2 +- coverage +- python-mock +- openssl command in the PATH +, plus all the dependencies mentioned in 'Project Setup' section. diff --git a/bin/__init__.py b/bin/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/bin/certcheck b/bin/certcheck new file mode 100755 index 0000000..707f8c7 --- /dev/null +++ b/bin/certcheck @@ -0,0 +1,23 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2013 Spotify AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +import certcheck + + +if __name__ == '__main__': + args_dict = certcheck.parse_command_line() + + certcheck.main(**args_dict) diff --git a/bin/certcheck.py b/certcheck/__init__.py similarity index 90% rename from bin/certcheck.py rename to certcheck/__init__.py index 2a988c8..5e51f47 100755 --- a/bin/certcheck.py +++ b/certcheck/__init__.py @@ -1,4 +1,19 @@ #!/usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2013 Spotify AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + #Make it a bit more like python3: from __future__ import division @@ -45,6 +60,11 @@ class RecoverableException(Exception): class PubkeySSHGitClient(SSHGitClient): + """ + Simple class used to add pubkey authentication to the SSHGitClient class. + In the base class it is not supported, and using password authentication + for a script is insecure. + """ def __init__(self, host, pubkey, port=None, username=None, *args, **kwargs): self.host = host self.port = port @@ -81,6 +101,17 @@ def _connect(self, cmd, path): class LocalMirrorRepo(Repo): def lookup_files(self, determine_wants, root_sha=None, repo_path=''): + """ + Search the repo for files described by the determine_wants + function. The function itself operates on the file paths in a repo and + must return True for objects of interest. + + The search is done recursively, with each iteration scanning just one + repo directory. In case a directory is found the root_sha and repo_path + parameters are provided for a next iteration of the function. + + The result is a list of the filenames accumulated by all iterations. + """ file_list = [] if root_sha is None: commit = self.get_object(self.head()) @@ -113,6 +144,10 @@ def lookup_files(self, determine_wants, root_sha=None, repo_path=''): class CertStore(object): + """ + Provides local clone of a remote repo plus some extra functionality to + ease extracting of the certificates from the repository + """ _remote = None _local = None @@ -146,6 +181,11 @@ def wants_master_only(refs): @classmethod def lookup_certs(cls, cert_suffixes): + """ + Find all the certificates in the repository. The classification is made + by checking whether file suffix can be found in th list of certificate + suffixes found in cert_suffixes parameter. + """ if cls._local is None: raise RecoverableException("Local repo mirror has not been " + "initialized yet") @@ -162,7 +202,9 @@ def wants_all_certs(path): class ScriptConfiguration(object): - + """ + Simple file configuration class basing on the YAML format + """ _config = dict() @classmethod @@ -204,6 +246,10 @@ class ScriptStatus(object): @classmethod def _send_data(cls, event): + """ + Send script status to all Riemann servers using all the protocols that + were configured. + """ for riemann_connection in cls._riemann_connections: logging.info('Sending event {0}, '.format(str(event)) + 'using Riemann conn {0}:{1}'.format( @@ -225,6 +271,9 @@ def _send_data(cls, event): @classmethod def _name2ip(cls, name): + """ + Resolve a dns name. In case it is already an IP - just return it. + """ if re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', name): #IP entry: return name @@ -240,6 +289,10 @@ def _name2ip(cls, name): @classmethod def _resolve_srv_hosts(cls, name): + """ + Find Riemann servers by resolving SRV record, provide some sanity + checks as well. + """ result = [] logging.debug("Resolving " + name) if name.find('._udp') > 0: @@ -270,6 +323,10 @@ def _resolve_srv_hosts(cls, name): @classmethod def _resolve_static_entry(cls, name): + """ + Find Riemann servers by resolving plain A record, provide some sanity + checks as well. + """ entry = namedtuple("RiemannHost", ['host', 'port', 'proto']) try: a, b, c = name.split(":") @@ -413,7 +470,7 @@ def update(cls, exit_status, exit_message): class ScriptLock(object): - #python lockfile is brain-damaged, we have to write our own class :/ + #python lockfile isn't usefull, we have to write our own class _fh = None _file_path = None @@ -451,13 +508,13 @@ def release(cls): def parse_command_line(): parser = argparse.ArgumentParser( - description='Simple certificate expiration check', - epilog="Author: prozlach@spotify.com", + description='Certificate checking tool', + epilog="Author: vespian a t wp.pl", add_help=True,) parser.add_argument( '--version', action='version', - version='1.0') + version='0.3.0') parser.add_argument( "-c", "--config-file", action='store', @@ -488,6 +545,11 @@ def parse_command_line(): def get_cert_expiration(certificate, ignored_certs): + """ + Extract the certificate expiration date for a certificate blob. Handle + ignored certificates by comparing shasum of the blob with entries in the + ignored_certs list + """ if certificate.path[-3:] in ['pem', 'crt', 'cer']: try: #Many bad things can happen here, but still - we can recover! :) @@ -654,8 +716,3 @@ def main(config_file, std_err=False, verbose=True, dont_send=False): msg = "Exception occured: {0}".format(e.__class__.__name__) logging.exception(msg) sys.exit(1) - -if __name__ == '__main__': - args_dict = parse_command_line() - - main(**args_dict) diff --git a/debian/.gitignore b/debian/.gitignore new file mode 100644 index 0000000..6f373e7 --- /dev/null +++ b/debian/.gitignore @@ -0,0 +1,2 @@ +certcheck* + diff --git a/debian/changelog b/debian/changelog index 0a6c4a3..1298e41 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +certcheck (0.3.0) stable; urgency=low + + * Documentation refactoring + * Make unittests nosetest compatible + * splitting script into modules + + -- Vespian Mon, 18 Sep 2013 14:33:43 +0000 + certcheck (0.2.0) stable; urgency=low * Git integration. diff --git a/debian/control b/debian/control index f0f82ad..be17909 100644 --- a/debian/control +++ b/debian/control @@ -1,8 +1,8 @@ Source: certcheck -Section: non-free/net +Section: utils Priority: extra -Maintainer: Pawel Rozlach -Build-Depends: python (>= 2.6.6-3~), debhelper (>= 8), +Maintainer: Vespian +Build-Depends: python (>= 2.6.6-3~), debhelper (>= 8), python-dnspython, python-coverage, openssl, python-openssl, python-bernhard, python-argparse, python-protobuf, python-unittest2, python-yaml, python-dulwich (>= 0.8), openssh-client @@ -13,5 +13,6 @@ Package: certcheck Version: 0.2.0 Architecture: any Depends: ${python:Depends}, python-openssl, python-bernhard, python-argparse, - python-protobuf, python-yaml, python-dulwich (>= 0.8), openssh-client + python-protobuf, python-yaml, python-dulwich (>= 0.8), openssh-client, + python-dnspython Description: Simple certificate check diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..68e26fd --- /dev/null +++ b/debian/copyright @@ -0,0 +1,8 @@ +Format-Specification: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Files: * +Copyright: 2012-2013 Spotify AB +License: Apache-2.0 + On Debian systems, the full text of the Apache 2.0 license can be found in the + file `/usr/share/common-licenses/Apache-2.0'. + diff --git a/doc/README.md b/doc/README.md deleted file mode 100644 index 421e489..0000000 --- a/doc/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# _Certchecker_ - -_Certchecker is a certificate expiration check capable of scanning GIT repos -and sending data on expiring/expired certificates back to the monitoring system -(currently only Riemann)._ - -## Project Setup - -In order to run certchecker you need to following dependencies installed: -- Bernhard - Riemann client library (https://github.com/banjiewen/bernhard) -- Google's protobuf library -- yaml bindings for python (http://pyyaml.org/) -- Dulwich - python implementation of GIT (https://www.samba.org/~jelmer/dulwich/docs/) -- ssh command in your PATH -- argparse library - -You can also use debian packaging rules from debian/ directory to build a deb -package. - -## Testing - -Currenlty the unittest python library is used to perform all the testing. In -test/ directory you can find: -- modules/ - modules used by unittests -- moduletests/ - the unittests themselves -- fabric/ - sample input files and test certificates temporary directories -- output_coverage_html/ - coverage tests results in a form of an html webpage -- test.py - script to start all the unittests - -All the dependencies required for performing the unittests are decribed in debian -packaging scripts and are as follows: -- unittests2 -- coverage -- python-mock -- openssl command in the PATH - -Plus all the dependencies mentioned in 'Project Setup' section. - -## Usage - -Please see the doc/USAGE.md file for details. - -## Contributing - -All patches are welcome ! Please use Github issue tracking and/or create a pull -request. - -## License - -FIXME - Put the licence here diff --git a/doc/USAGE.md b/doc/USAGE.md deleted file mode 100644 index 6e5c809..0000000 --- a/doc/USAGE.md +++ /dev/null @@ -1,103 +0,0 @@ -## Configuration - -Actions taken by the script are determined by its command line and the -configuration file. The command line has a build-in help system: - - -usage: certcheck.py [-h] [--version] -c CONFIG_FILE [-v] [-s] [-d] - -Simple certificate expiration check - -optional arguments: - -h, --help show this help message and exit - --version show program's version number and exit - -c CONFIG_FILE, --config-file CONFIG_FILE - Location of the configuration file - -v, --verbose Provide extra logging messages. - -s, --std-err Log to stderr instead of syslog - -d, --dont-send Do not send data to Riemann [use for debugging] - -Author: pawel.rozlach a t gmail.com - -The configuration file is a plain YAML document. It's syntax is as follows: - ---- -lockfile: /tmp/certcheck.lock -warn_treshold: 30 -critical_treshold: 15 -riemann_hosts: - static: - - 192.168.122.16:5555:udp - - 192.168.122.16:5555:tcp - by_srv: - - _riemann._tcp - - _riemann._udp -riemann_tags: - - production - - class::certcheck -repo_host: git.example.net -repo_port: 22 -repo_url: /example-repo -repo_masterbranch: refs/heads/production -repo_localdir: /tmp/certcheck-temprepo -repo_user: certcheck -repo_pubkey: ./certcheck_id_rsa -# format - dict, hash as a key, and value as a comment -# sha1sum ./certificate_to_be_ignored -ignored_certs: - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: "some VPN key" - bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: "some unused certificate" - -## Operation - -The script connects to the $repo_user@$repo_host:$repo_port via SSH and clones -repository $repo_url to a *bare* repository in "$repo_tmpdir/repository". If -the repository already exists, it is only updated with newest referances. Only -$repo_masterbranch branch is pulled in along with all the objects it points to, -topic branches are not downloaded. - -The connection is established using the $repo_pubkey pubkey, and the $repo_user -itself should have very limited privileges. - -Next, the repository is scanned in search of files ending with one of the -certcheck.py:CERTIFICATE_EXTENSIONS extensions. Currently all possible -certificate extensions are listed but only ['pem', 'crt', 'cer'] are currently -supported (see certcheck.py:get_cert_expiration method). For the remaing ones -only a warning is issued. - -For each certificate found a sha1sum is computed, and if the result is found in -$ignored_certs hash, then the certificate is ignored even if it expires/exp- -ired. - -If the number of days till the certificate expires is less than $critical_tresh -(by default 15) - a "critical" partial status is generated, if it less than -$warn_tresh but more than $critical_tresh - a "warning" partial status is gene- -rated. Unsuported certificate yields an 'unknown' state and expired ones of -course the 'critical'. - -All the 'partial status' updates are agregated and each message can only ele- -vate up the final status of the metric send to Riemann. Currently, the hierar- -chy is as follows: - - (lowest)ok->warn->critical->unknown(highest) - -script errors, exceptions and unexcpected conditions result in imidiate elevation -to 'unknown' status and sending the metric to Riemann ASAP if only possible. - -IP addresses/ports of the Riemann instances can be defined in two ways: - * statically, by providing a list of riemann instances in $riemann_servers - var. The format of the list entry is hostname:port:proto. 'proto' can be one - of 'udp' or 'tcp'. - * by providing a SRV record, i.e. '_riemann._udp'. All the values - (host, port) will be resolved automatically. Protocol is chosen basing on - the SRV entry itself. - -The final metric is send to *all* Riemann instances with TTL equal to -certcheck.py:DATA_TTL == 25 hours. - - -=== Maintenance - -In order to not to let the "$repo_tmpdir/repository" repository grow endlessly -a 'git gc' command should be executed once a day by i.e. a cronjob. It should -repack all the packs and remove dangling objects. diff --git a/test/test.py b/run_tests.py similarity index 68% rename from test/test.py rename to run_tests.py index 6fc65aa..8b4d90e 100755 --- a/test/test.py +++ b/run_tests.py @@ -5,6 +5,8 @@ from __future__ import print_function import coverage +import os +import shutil import sys import unittest @@ -20,13 +22,22 @@ def main(): print("Tests were not tested on Python 3.X, use at your own risk") sys.exit(1) + #Cleanup old html report: + for root, dirs, files in os.walk('test/output_coverage_html/'): + for f in files: + if f == '.gitignore' or f == '.empty_dir': + continue + os.unlink(os.path.join(root, f)) + for d in dirs: + shutil.rmtree(os.path.join(root, d)) + #Perform coverage analisys: cov = coverage.coverage() cov.start() #Discover the test and execute them: loader = unittest.TestLoader() - tests = loader.discover('.') + tests = loader.discover('./test/') testRunner = unittest.runner.TextTestRunner(descriptions=True, verbosity=1) testRunner.run(tests) cov.stop() diff --git a/setup.py b/setup.py index d454f9f..98583a8 100755 --- a/setup.py +++ b/setup.py @@ -1,11 +1,28 @@ #! /usr/bin/env python +# -*- coding: utf-8 -*- +# Copyright (c) 2013 Spotify AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + from setuptools import setup setup(name='certcheck', - version='1', - author=u'Pawel Rozlach', - author_email='prozlach@spotify.com', - description='Simplified certificate check', + version='0.3.0', + author='Vespian', + author_email='vespian a t wp.pl', + license='ASF2.0', + url='https://github.com/vespian/certcheck', + description='Certificate checking tool', packages=['certcheck'], - scripts=['bin/certcheck.py']) + scripts=['bin/certcheck']) diff --git a/test/modules/file_paths.py b/test/modules/file_paths.py index b6f0a9a..719b316 100644 --- a/test/modules/file_paths.py +++ b/test/modules/file_paths.py @@ -1,4 +1,19 @@ #!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# Copyright (c) 2013 Spotify AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + import os.path as op diff --git a/test/moduletests/certcheck/test_certcheck.py b/test/moduletests/certcheck/test_certcheck.py index 005b25a..b6072c8 100644 --- a/test/moduletests/certcheck/test_certcheck.py +++ b/test/moduletests/certcheck/test_certcheck.py @@ -1,4 +1,19 @@ #!/usr/bin/python -tt +# -*- coding: utf-8 -*- +# Copyright (c) 2013 Spotify AB +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + #Make it a bit more like python3: from __future__ import absolute_import @@ -24,7 +39,6 @@ #To perform local imports first we need to fix PYTHONPATH: pwd = os.path.abspath(os.path.dirname(__file__)) -sys.path.append(os.path.abspath(pwd + '/../../../bin/')) sys.path.append(os.path.abspath(pwd + '/../../modules/')) #Local imports: @@ -301,7 +315,7 @@ def test_command_line_parsing(self, SysExitMock): old_args = sys.argv #General parsing: - sys.argv = ['./certcheck.py', '-v', '-s', '-d', '-c', './certcheck.json'] + sys.argv = ['./certcheck', '-v', '-s', '-d', '-c', './certcheck.json'] parsed_cmdline = certcheck.parse_command_line() self.assertEqual(parsed_cmdline, {'std_err': True, 'config_file': './certcheck.json', @@ -310,14 +324,14 @@ def test_command_line_parsing(self, SysExitMock): }) #Config file should be a mandatory argument: - sys.argv = ['./certcheck.py', ] + sys.argv = ['./certcheck', ] # Suppres warnings from argparse with mock.patch('sys.stderr'): parsed_cmdline = certcheck.parse_command_line() SysExitMock.assert_called_once_with(2) #Test default values: - sys.argv = ['./certcheck.py', '-c', './certcheck.json'] + sys.argv = ['./certcheck', '-c', './certcheck.json'] parsed_cmdline = certcheck.parse_command_line() self.assertEqual(parsed_cmdline, {'std_err': False, 'config_file': './certcheck.json',