diff --git a/.prospector.yaml b/.prospector.yaml index 4997a20..af742d7 100644 --- a/.prospector.yaml +++ b/.prospector.yaml @@ -1,3 +1,4 @@ +strictness: veryhigh test-warnings: true ignore-paths: - marmoset/virt/ diff --git a/marmoset.py b/marmoset.py index 56f5ff8..5f74f5b 100755 --- a/marmoset.py +++ b/marmoset.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +"""TODO: Docstring.""" # pylint: disable-msg=import-self import marmoset diff --git a/marmoset/__init__.py b/marmoset/__init__.py index b3136d3..a506b26 100644 --- a/marmoset/__init__.py +++ b/marmoset/__init__.py @@ -4,5 +4,6 @@ def run(config_file=None): + """Run the thing.""" cfg = config.load_config(config_file) cli.parse(cfg) diff --git a/marmoset/app.py b/marmoset/app.py index f4ed90f..18a5f1b 100644 --- a/marmoset/app.py +++ b/marmoset/app.py @@ -1,5 +1,5 @@ from . import config, webserver -config = config.load_config() +CONFIG = config.load_config() -app = webserver.app(config) +APP = webserver.app(config) diff --git a/marmoset/cli/installstatus_parser.py b/marmoset/cli/installstatus_parser.py index 2feeb92..ddf00e7 100644 --- a/marmoset/cli/installstatus_parser.py +++ b/marmoset/cli/installstatus_parser.py @@ -6,7 +6,7 @@ def add_to(parser, name, **kwargs): """ Update the CLI parser to support installimage status updates and stats. - supports a lot of information based on a UUID. For example a list of + Supports a lot of information based on a UUID. For example a list of all status updates a UUID got, the last update it got or some stats for it. """ command = parser.add_parser(name, **kwargs) diff --git a/marmoset/dhcp/dhcp_config.py b/marmoset/dhcp/dhcp_config.py index acc1344..1f21023 100644 --- a/marmoset/dhcp/dhcp_config.py +++ b/marmoset/dhcp/dhcp_config.py @@ -1,8 +1,10 @@ +"""TODO: module docstring.""" from marmoset import config as config_reader from marmoset import validation from .isc_dhcp_ldap_config import ISCDhcpLdapConfig -config = config_reader.load_config() + +CONFIG = config_reader.load_config() class DhcpConfig: @@ -27,13 +29,15 @@ def set_settings( ip_address=None, gateway=None, networkmask=None): + """TODO: docstring.""" # pylint: disable-msg=too-many-arguments self.mac = mac if gateway is not None or allow_none_value_for_not_required_parameter: self.gateway = gateway - if networkmask is not None or allow_none_value_for_not_required_parameter: + if networkmask is not None \ + or allow_none_value_for_not_required_parameter: self.networkmask = networkmask if validation.is_cidr(ip_address): @@ -42,28 +46,33 @@ def set_settings( if self.networkmask is None: self.networkmask = validation.get_nm_from_cidr(ip_address) - if self.gateway is None and config[ + if self.gateway is None and CONFIG[ 'DHCPConfig'].getboolean('force_gateway'): self.gateway = validation.get_gw_from_cidr(ip_address) else: self.ip_address = ip_address - self.dhcp_hostname = config['Common'].get('FQDN') + self.dhcp_hostname = CONFIG['Common'].get('FQDN') def add_additional_statement(self, key, value): + """TODO: docstring.""" self.additional_statements[key] = value def create_isc_ldap(self): + """TODO: docstring.""" isc_dhcp_config = ISCDhcpLdapConfig(self) isc_dhcp_config.save() def remove_by_ipv4(self): + """TODO: docstring.""" return ISCDhcpLdapConfig.remove_by_ipv4(self.ip_address) > 0 def remove_by_mac(self): + """TODO: docstring.""" return ISCDhcpLdapConfig.remove_by_mac(self.mac) > 0 def remove_all(self): + """TODO: docstring.""" ipv4_removed_count = ISCDhcpLdapConfig.remove_by_ipv4(self.ip_address) mac_removed_count = ISCDhcpLdapConfig.remove_by_mac(self.mac) @@ -71,20 +80,25 @@ def remove_all(self): @staticmethod def all(): + """TODO: docstring.""" return ISCDhcpLdapConfig.get_all_db_entries() @staticmethod def get_by_ip(ip_address): + """TODO: docstring.""" return ISCDhcpLdapConfig.get_by_ip(ip_address) @staticmethod def get_by_mac(mac): + """TODO: docstring.""" return ISCDhcpLdapConfig.get_by_mac(mac) @staticmethod def exists_ipv4(ip_address): + """TODO: docstring.""" return ISCDhcpLdapConfig.get_by_ip(ip_address) is not None @staticmethod def exists_mac(mac_address): + """TODO: docstring.""" return ISCDhcpLdapConfig.get_by_mac(mac_address) is not None diff --git a/marmoset/dhcp/isc_dhcp_ldap_config.py b/marmoset/dhcp/isc_dhcp_ldap_config.py index 11906c1..d70960c 100644 --- a/marmoset/dhcp/isc_dhcp_ldap_config.py +++ b/marmoset/dhcp/isc_dhcp_ldap_config.py @@ -9,7 +9,7 @@ from marmoset import config as config_reader from marmoset import validation -config = config_reader.load_config() +CONFIG = config_reader.load_config() class ISCDhcpLdapConfig: @@ -21,13 +21,13 @@ def __init__(self, dhcp_config): @staticmethod def __get_server_connection(): - server = Server(config['DHCPConfig'].get('ldap_server'), - port=int(config['DHCPConfig'].get('ldap_port')), + server = Server(CONFIG['DHCPConfig'].get('ldap_server'), + port=int(CONFIG['DHCPConfig'].get('ldap_port')), get_info=ALL) conn = Connection(server, - config['DHCPConfig'].get('ldap_bind_dn'), - config['DHCPConfig'].get('ldap_passwort'), + CONFIG['DHCPConfig'].get('ldap_bind_dn'), + CONFIG['DHCPConfig'].get('ldap_passwort'), auto_bind=True) return conn @@ -55,7 +55,7 @@ def save(self): self.dhcp_config.dhcp_hostname) } - conn.add("cn=%s,%s" % (self.dhcp_config.ip_address, config['DHCPConfig'].get('ldap_client_base_dn')), + conn.add("cn=%s,%s" % (self.dhcp_config.ip_address, CONFIG['DHCPConfig'].get('ldap_client_base_dn')), 'dhcpHost', entry_attributes) @@ -65,7 +65,7 @@ def get_all_db_entries(): conn = ISCDhcpLdapConfig.__get_server_connection() entry_generator = conn.extend.standard.paged_search( - search_base=config['DHCPConfig'].get('ldap_client_base_dn'), + search_base=CONFIG['DHCPConfig'].get('ldap_client_base_dn'), search_filter='(objectClass=dhcpHost)', search_scope=SUBTREE, attributes=['cn'], @@ -84,7 +84,7 @@ def __get_dn_by_ipv4(ip_address, multi=False): """Get a DN for a certain entry based on the provided ipv4 address.""" conn = ISCDhcpLdapConfig.__get_server_connection() conn.search( - search_base=config['DHCPConfig'].get('ldap_client_base_dn'), + search_base=CONFIG['DHCPConfig'].get('ldap_client_base_dn'), search_filter='(cn=%s)' % ip_address, search_scope=SUBTREE, @@ -114,7 +114,7 @@ def __get_dn_by_mac(mac_address, multi=False): """Get a DN for a certain entry based on the provided MAC address.""" conn = ISCDhcpLdapConfig.__get_server_connection() conn.search( - search_base=config['DHCPConfig'].get('ldap_client_base_dn'), + search_base=CONFIG['DHCPConfig'].get('ldap_client_base_dn'), search_filter='(dhcpHWAddress=ethernet %s)' % mac_address, search_scope=SUBTREE, @@ -178,7 +178,7 @@ def __get_dhcp_config(distinguished_name): dhcp_config = DhcpConfig(mac, ip_address, gateway, networkmask) - additional_statements = config['DHCPConfig'].get('additional_statements').split(',') + additional_statements = CONFIG['DHCPConfig'].get('additional_statements').split(',') for ldap_additional_statement in entries[ 0]['attributes']['dhcpStatements']: diff --git a/marmoset/imagecatalog/catalog.py b/marmoset/imagecatalog/catalog.py index 6934c71..e9aab18 100644 --- a/marmoset/imagecatalog/catalog.py +++ b/marmoset/imagecatalog/catalog.py @@ -1,7 +1,7 @@ """Module for listing all images.""" -from .image import Image import glob import os +from .image import Image class ImageCatalog: diff --git a/marmoset/imagecatalog/image.py b/marmoset/imagecatalog/image.py index 4161477..8293271 100644 --- a/marmoset/imagecatalog/image.py +++ b/marmoset/imagecatalog/image.py @@ -34,8 +34,8 @@ def size_mb(self): def metadata(self): metadata = {} try: - with open(self.metadata_path, "r") as f: - parsed_yaml = yaml.load(f) + with open(self.metadata_path, "r") as in_file: + parsed_yaml = yaml.load(in_file) except (yaml.scanner.ScannerError, yaml.YAMLError, OSError, IOError): parsed_yaml = {} finally: @@ -71,8 +71,8 @@ def metadata(self): def signature(self): signature_path = self.image_path + ".sig" try: - with open(signature_path, "r") as f: - signature = f.read() + with open(signature_path, "r") as in_file: + signature = in_file.read() except (IOError, OSError, UnicodeDecodeError): signature = None return signature diff --git a/marmoset/installimage/req_argument_parser.py b/marmoset/installimage/req_argument_parser.py index 48a7334..175f392 100644 --- a/marmoset/installimage/req_argument_parser.py +++ b/marmoset/installimage/req_argument_parser.py @@ -1,9 +1,11 @@ """Simple module to get access to data from an http request.""" +# pylint: disable=too-few-public-methods class ReqArgumentParser: """Simple class to get access to data from an http request.""" - def parse_args(self, req): + @staticmethod + def parse_args(req): """Parse arguments from a request.""" return req.form diff --git a/marmoset/installstatus/db_helper.py b/marmoset/installstatus/db_helper.py index bf2c189..606ef6d 100644 --- a/marmoset/installstatus/db_helper.py +++ b/marmoset/installstatus/db_helper.py @@ -1,10 +1,10 @@ """Module to provide a database wrapper for installimage status.""" -from marmoset import config as config_reader import sqlite3 +from marmoset import config as config_reader -config = config_reader.load_config() -DB = config['Installstatus'].get('SQLiteDB') +CONFIG = config_reader.load_config() +DB = CONFIG['Installstatus'].get('SQLiteDB') class DBHelper: diff --git a/marmoset/installstatus/installstatus.py b/marmoset/installstatus/installstatus.py index 269c20a..358b4ff 100644 --- a/marmoset/installstatus/installstatus.py +++ b/marmoset/installstatus/installstatus.py @@ -1,9 +1,20 @@ """Module to handle everything related to installimage status.""" +import datetime from marmoset import config as config_reader from .db_helper import DBHelper -import datetime -config = config_reader.load_config() +CONFIG = config_reader.load_config() + + +def convert_date(date_string): + """ + Convert the date string from database to datetime object. + + :param date_string: + :return: datetime + """ + date = datetime.datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S') + return date class InstallStatus: @@ -62,8 +73,8 @@ def get_stats(self): stats['end_date'] = status_history[-1]['date'] stats['latest_status_code'] = status_history[-1]['status_code'] stats['total_steps'] = status_history[-1]['total_steps'] - datetime_start = self.convert_date(stats['start_date']) - datetime_end = self.convert_date(stats['end_date']) + datetime_start = convert_date(stats['start_date']) + datetime_end = convert_date(stats['end_date']) duration = datetime_end - datetime_start duration = duration.total_seconds() stats['installation_duration'] = int(duration) @@ -81,13 +92,3 @@ def get_stats(self): stats['status_updates'] = history_count stats['uuid'] = self.uuid return stats - - def convert_date(self, date_string): - """ - Convert the date string from database to datetime object. - - :param date_string: - :return: datetime - """ - date = datetime.datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S') - return date diff --git a/marmoset/pxe/client_config.py b/marmoset/pxe/client_config.py index f06a39a..5429cf4 100644 --- a/marmoset/pxe/client_config.py +++ b/marmoset/pxe/client_config.py @@ -10,6 +10,24 @@ from .exceptions import Error +# pylint: disable=too-many-instance-attributes +def __expand_template(label, options=None): + """Return the config file content expanded with the given values.""" + if options is not None: + options = " ".join(options) + else: + options = '' + + template = ClientConfig.CFG_TEMPLATE + return template.substitute(label=label, + options=options) + + +def __mksalt(): + """Return a crypt style salt string.""" + return crypt.mksalt(crypt.METHOD_SHA512) + + class ClientConfig: """Class to handle PXE configs for clients.""" @@ -122,6 +140,7 @@ def get_label(self): option = re.match(r' *APPEND (\w+)', line) if option is not None: return option.group(1) + return None def get_script(self): """Parse the script option from the config file.""" @@ -130,6 +149,7 @@ def get_script(self): option = re.match(r' *APPEND.*script=(\S+)', line) if option is not None: return option.group(1) + return None def get_uuid(self): """Parse the uuid option from the config file.""" @@ -138,6 +158,7 @@ def get_uuid(self): option = re.match(r' *APPEND.*UUID=(\S+)', line) if option is not None: return option.group(1) + return None def get_ipv6_address(self): """Parse the ipv6 address option from the config file.""" @@ -161,6 +182,7 @@ def get_from_config(self, option_string): option = re.match(r' *APPEND.*%s=(\S+)' % option_string, line) if option is not None: return option.group(1) + return None def create(self, pxe_label): """Create the config file for this instance.""" @@ -185,7 +207,7 @@ def create(self, pxe_label): options.append("IP6GW=%s" % self.ipv6_gateway) options.append("IP6PRE=%s" % self.ipv6_prefix) - content = self.__expand_template(pxe_label.name, options) + content = __expand_template(pxe_label.name, options) self.__write_config_file(content) self.label = pxe_label.name @@ -243,17 +265,6 @@ def __make_file_mutable(self, path=None): raise Error(message='couldnt remove immutable flag for {path}'. format(path=path)) - def __expand_template(self, label, options=None): - """Return the config file content expanded with the given values.""" - if options is not None: - options = " ".join(options) - else: - options = '' - - template = ClientConfig.CFG_TEMPLATE - return template.substitute(label=label, - options=options) - def __mkpwhash(self): """ Return the hashed password. @@ -263,11 +274,7 @@ def __mkpwhash(self): if 'password' not in vars(self) or self.password in [None, '']: password = base64.b64encode(os.urandom(16), b'-_')[:16] self.password = password.decode('utf-8') - return crypt.crypt(self.password, self.__mksalt()) - - def __mksalt(self): - """Return a crypt style salt string.""" - return crypt.mksalt(crypt.METHOD_SHA512) + return crypt.crypt(self.password, __mksalt()) def cb_setpwhash(self): """Create a callback that adds a HASH= string to the command line.""" diff --git a/marmoset/webserver/dhcp.py b/marmoset/webserver/dhcp.py index 260b8ff..a279c40 100644 --- a/marmoset/webserver/dhcp.py +++ b/marmoset/webserver/dhcp.py @@ -6,9 +6,9 @@ from marmoset import dhcp from marmoset import validation -config = config_reader.load_config() +CONFIG = config_reader.load_config() -additional_statements_str = config['DHCPConfig'].get('additional_statements') +additional_statements_str = CONFIG['DHCPConfig'].get('additional_statements') additional_statements = additional_statements_str.split(',') parser = reqparse.RequestParser() @@ -32,7 +32,7 @@ def post(self): """Create a new PXE record.""" args = parser.parse_args() - if ((args.gateway is None and config['DHCPConfig'].getboolean( + if ((args.gateway is None and CONFIG['DHCPConfig'].getboolean( 'force_gateway')) or args.networkmask is None) and not validation.is_cidr(args.ip_address): return { 'message': 'missing parameter gateway and networkmask or give an ip address in CIDR notation'}, 406 diff --git a/marmoset/webserver/flask/auth.py b/marmoset/webserver/flask/auth.py index a584acd..e8a7e36 100644 --- a/marmoset/webserver/flask/auth.py +++ b/marmoset/webserver/flask/auth.py @@ -3,8 +3,8 @@ from flask import request, current_app from werkzeug.exceptions import Unauthorized -Username = 'admin' -Password = 'APgo1VANd6YPqP0ZaJ0OK9A7WHbXzFBqe6Nz8MU9rTxKv6gIZ26nIW1cfn4GbR36' +USERNAME = 'admin' +PASSWORD = 'APgo1VANd6YPqP0ZaJ0OK9A7WHbXzFBqe6Nz8MU9rTxKv6gIZ26nIW1cfn4GbR36' def required(f): @@ -32,7 +32,7 @@ def __check_auth(username, password): # nopep8 This function is called to check if a username / password combination is valid. """ - return username == Username and password == Password + return username == USERNAME and password == PASSWORD # we've to disable pep8 here because diff --git a/marmoset/webserver/installimage.py b/marmoset/webserver/installimage.py index 6002949..458a516 100644 --- a/marmoset/webserver/installimage.py +++ b/marmoset/webserver/installimage.py @@ -5,7 +5,7 @@ from ..installimage.installimage_config import InstallimageConfig from ..installimage.req_argument_parser import ReqArgumentParser -parser = ReqArgumentParser() +PARSER = ReqArgumentParser() class InstallimageCollection(Resource): @@ -29,7 +29,7 @@ def get(self, mac): def post(self, mac): """Create or updates a config based on the provided MAC.""" - args = parser.parse_args(request) + args = PARSER.parse_args(request) installimage_config = InstallimageConfig(mac) installimage_config.clear_variables() diff --git a/marmoset/webserver/pxe.py b/marmoset/webserver/pxe.py index 90d6481..d067c8f 100644 --- a/marmoset/webserver/pxe.py +++ b/marmoset/webserver/pxe.py @@ -4,28 +4,28 @@ from .. import pxe from marmoset import config as config_reader -config = config_reader.load_config() -immutable_pxe_config_support = config['PXEConfig'].get('ImmutableSupport') +CONFIG = config_reader.load_config() +IMMUTABLE_PXE_CONFIG_SUPPORT = CONFIG['PXEConfig'].get('ImmutableSupport') -parser = reqparse.RequestParser() -parser.add_argument('ip_address', type=str) -parser.add_argument('password', type=str, default=None) -parser.add_argument('script', type=str, default=None) -parser.add_argument('uuid', type=str, default=None) -parser.add_argument('ipv6_address', type=str, default=None) -parser.add_argument('ipv6_gateway', type=str, default=None) -parser.add_argument('ipv6_prefix', type=str, default=None) -parser.add_argument( +PARSER = reqparse.RequestParser() +PARSER.add_argument('ip_address', type=str) +PARSER.add_argument('password', type=str, default=None) +PARSER.add_argument('script', type=str, default=None) +PARSER.add_argument('uuid', type=str, default=None) +PARSER.add_argument('ipv6_address', type=str, default=None) +PARSER.add_argument('ipv6_gateway', type=str, default=None) +PARSER.add_argument('ipv6_prefix', type=str, default=None) +PARSER.add_argument( 'label', type=str, choices=pxe.Label.names(), default=pxe.Label.names()[0]) -if immutable_pxe_config_support == 'True': - persistent_choices = ['true', 'True', 'false', 'False'] +if IMMUTABLE_PXE_CONFIG_SUPPORT == 'True': + PERSISTENT_CHOICES = ['true', 'True', 'false', 'False'] else: - persistent_choices = ['false', 'False'] -parser.add_argument('persistent', choices=persistent_choices, + PERSISTENT_CHOICES = ['false', 'False'] +PARSER.add_argument('persistent', choices=PERSISTENT_CHOICES, type=str, default='false') @@ -45,7 +45,7 @@ def post(self): only when all of them are provided. Missing password or uuid will be auto generated by ClientConfig. """ - args = parser.parse_args() + args = PARSER.parse_args() persistent = args.persistent in ['true', 'True'] print(args) diff --git a/requirements.txt b/requirements.txt index 87f37b6..07d0a79 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,15 +1,15 @@ -astroid>=2.3.0 -aniso8601>=8.0.0 -Flask>=1.1.2 Flask-RESTful>=0.3.8 -itsdangerous>=1.1.0 +Flask>=1.1.2 Jinja2>=2.11.2 -ldap3>=2.7 MarkupSafe>=1.1.1 +Werkzeug>=1.0.1 +aniso8601>=8.0.0 +astroid>=2.3.0 +itsdangerous>=1.1.0 +ldap3>=2.7 +pyaml>=20.4.0 pyasn1>=0.4.8 python-dateutil>=2.8.1 pytz>=2020.1 six>=1.15.0 -Werkzeug>=1.0.1 wheel>=0.34.2 -pyaml>=20.4.0