2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-29 04:57:52 +00:00

[#3287] fix pylint warnings

- C0115: Missing class docstring (missing-class-docstring)
- C0123: Use isinstance() rather than type() for a typecheck. (unidiomatic-typecheck)
- C0201: Consider iterating the dictionary directly instead of calling .keys() (consider-iterating-dictionary)
- C0206: Consider iterating with .items() (consider-using-dict-items)
- C0411: standard import "..." should be placed before "..." (wrong-import-order)
- C0415: Import outside toplevel (...) (import-outside-toplevel)
- C1802: Do not use `len(SEQUENCE)` without comparison to determine if a sequence is empty (use-implicit-booleaness-not-len)
- E0001: Parsing failed: 'invalid syntax (<unknown>, line 2313)' (syntax-error)
- E0401: Unable to import '...' (import-error)
- E0602: Undefined variable 'l' (undefined-variable)
- R0205: Class 'VagrantEnv' inherits from object, can be safely removed from bases in python3 (useless-object-inheritance)
- E1101: Instance of 'NSECBASE' has no 'dump_fixedpart' member (no-member)
- E1123: Unexpected keyword argument 'capture' in method call (unexpected-keyword-arg)
- R0902: Too many instance attributes (too-many-instance-attributes)
- R0913: Too many arguments (too-many-arguments)
- R0916: Too many boolean expressions in if statement (6/5) (too-many-boolean-expressions)
- R1717: Consider using a dictionary comprehension (consider-using-dict-comprehension)
- R1722: Consider using 'sys.exit' instead (consider-using-sys-exit)
- R1732: Consider using 'with' for resource-allocating operations (consider-using-with)
- R1735: Consider using '{}' instead of a call to 'dict'. (use-dict-literal)
- W0102: Dangerous default value sys.argv[1:] (builtins.list) as argument (dangerous-default-value)
- W0102: Dangerous default value {} as argument (dangerous-default-value)
- W0106: Expression "[f.write('%02x' % x) for x in bin_address]" is assigned to nothing (expression-not-assigned)
- W0107: Unnecessary pass statement (unnecessary-pass)
- W0201: Attribute 'config' defined outside __init__ (attribute-defined-outside-init)
- W0404: Reimport '...' (imported line ...) (reimported)
- W0611: Unused import ... (unused-import)
- W0612: Unused variable '...' (unused-variable)
- W0613: Unused argument '...' (unused-argument)
- W0621: Redefining name '...' from outer scope (line 1471) (redefined-outer-name)
- W0622: Redefining built-in '...' (redefined-builtin)
- W0707: Consider explicitly re-raising using 'raise ... from ...' (raise-missing-from)
- W0718: Catching too general exception Exception (broad-exception-caught)
- W1202: Use lazy % formatting in logging functions (logging-format-interpolation)
- W1203: Use lazy % formatting in logging functions (logging-fstring-interpolation)
- W1308: Duplicate string formatting argument 'connection_type', consider passing as named argument (duplicate-string-formatting-argument)
- W1401: Anomalous backslash in string: '\/'. String constant might be missing an r prefix. (anomalous-backslash-in-string)
- W1406: The u prefix for strings is no longer necessary in Python >=3.0 (redundant-u-string-prefix)
- W1514: Using open without explicitly specifying an encoding (unspecified-encoding)
- W4901: Deprecated module 'optparse' (deprecated-module)
- W4904: Using deprecated class SafeConfigParser of module configparser (deprecated-class)
This commit is contained in:
Andrei Pavel 2024-06-07 11:43:43 +03:00
parent 8e37580e59
commit 9c35a4db68
No known key found for this signature in database
GPG Key ID: D4E804481939CB21
12 changed files with 290 additions and 303 deletions

View File

@ -33,7 +33,7 @@ def read_input_files(files):
if name.startswith('_'): if name.startswith('_'):
print("Skipping %s (starts with underscore)" % f) print("Skipping %s (starts with underscore)" % f)
continue continue
with open(f) as fp: with open(f, encoding='utf-8') as fp:
print("Processing %s" % f) print("Processing %s" % f)
# use OrderedDict to preserve order of fields in cmd-syntax # use OrderedDict to preserve order of fields in cmd-syntax
try: try:
@ -42,7 +42,7 @@ def read_input_files(files):
print(f'\nError while processing {f}: {e}\n\n') print(f'\nError while processing {f}: {e}\n\n')
raise raise
if name != descr['name']: if name != descr['name']:
exit("Expected name == descr['name'], but name is {name} and descr['name'] is {descr['name']}") raise ValueError("Expected name == descr['name']. Name is {name} and descr['name'] is {descr['name']}.")
apis[name] = descr apis[name] = descr
@ -196,7 +196,7 @@ def generate(in_files, out_file):
rst = generate_rst(apis) rst = generate_rst(apis)
if out_file: if out_file:
with open(out_file, 'w') as f: with open(out_file, 'w', encoding='utf-8') as f:
f.write(rst) f.write(rst)
print('Wrote generated RST content to: %s' % out_file) print('Wrote generated RST content to: %s' % out_file)
else: else:

View File

@ -6,31 +6,35 @@
# full list see the documentation: # full list see the documentation:
# http://www.sphinx-doc.org/en/master/config # http://www.sphinx-doc.org/en/master/config
import os
import sys
from shutil import copyfile
# -- Path setup -------------------------------------------------------------- # -- Path setup --------------------------------------------------------------
# to avoid sphinx.errors.SphinxParallelError: RecursionError: maximum recursion depth exceeded while pickling an object
sys.setrecursionlimit(5000)
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
# SRC_DIR = os.path.abspath(os.path.dirname(__file__))
import os sys.path.append(SRC_DIR)
# import sys
# sys.path.insert(0, os.path.abspath('.'))
# to avoid sphinx.errors.SphinxParallelError: RecursionError: maximum recursion depth exceeded while pickling an object import api2doc # noqa # pylint: disable=wrong-import-position
import sys import mes2doc # noqa # pylint: disable=wrong-import-position
sys.setrecursionlimit(5000)
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'Kea' project = 'Kea'
copyright = '2019-2024, Internet Systems Consortium' copyright = '2019-2024, Internet Systems Consortium' # pylint: disable=redefined-builtin
author = 'Internet Systems Consortium' author = 'Internet Systems Consortium'
# get current kea version # get current kea version
config_ac_path = '../../configure.ac' config_ac_path = '../../configure.ac'
changelog_path = '../../ChangeLog' changelog_path = '../../ChangeLog'
release = 'UNRELEASED' release = 'UNRELEASED'
with open(config_ac_path) as f: with open(config_ac_path, encoding='utf-8') as f:
for line in f.readlines(): for line in f.readlines():
if line.startswith('AC_INIT(kea'): if line.startswith('AC_INIT(kea'):
parts = line.split(',') parts = line.split(',')
@ -39,7 +43,7 @@ with open(config_ac_path) as f:
# that this is the final release. # that this is the final release.
dash_parts = release.split('-') dash_parts = release.split('-')
candidate_release = dash_parts[0] candidate_release = dash_parts[0]
with open(changelog_path) as changelog_file: with open(changelog_path, encoding='utf-8') as changelog_file:
first_line = changelog_file.readline() first_line = changelog_file.readline()
if candidate_release in first_line and "released" in first_line: if candidate_release in first_line and "released" in first_line:
release = candidate_release release = candidate_release
@ -251,23 +255,15 @@ rst_prolog = """
# Do generation of api.rst and kea-messages.rst here in conf.py instead of Makefile.am # Do generation of api.rst and kea-messages.rst here in conf.py instead of Makefile.am
# so they are available on ReadTheDocs as there makefiles are not used for building docs. # so they are available on ReadTheDocs as there makefiles are not used for building docs.
def run_generate_docs(_): def run_generate_docs(_):
import os with open(os.path.join(SRC_DIR, 'api-files.txt'), encoding='utf-8') as af:
import sys
src_dir = os.path.abspath(os.path.dirname(__file__))
print(src_dir)
sys.path.append(src_dir)
import api2doc
with open(os.path.join(src_dir, 'api-files.txt')) as af:
api_files = af.read().split() api_files = af.read().split()
api_files = [os.path.abspath(os.path.join(src_dir, '../..', af)) for af in api_files] api_files = [os.path.abspath(os.path.join(SRC_DIR, '../..', af)) for af in api_files]
api2doc.generate(api_files, os.path.join(src_dir, 'api.rst')) api2doc.generate(api_files, os.path.join(SRC_DIR, 'api.rst'))
import mes2doc with open(os.path.join(SRC_DIR, 'mes-files.txt'), encoding='utf-8') as mf:
with open(os.path.join(src_dir, 'mes-files.txt')) as mf:
mes_files = mf.read().split() mes_files = mf.read().split()
mes_files = [os.path.abspath(os.path.join(src_dir, '../..', mf)) for mf in mes_files] mes_files = [os.path.abspath(os.path.join(SRC_DIR, '../..', mf)) for mf in mes_files]
mes2doc.generate(mes_files, os.path.join(src_dir, 'kea-messages.rst')) mes2doc.generate(mes_files, os.path.join(SRC_DIR, 'kea-messages.rst'))
# Sphinx has some limitations. It can't import files from outside its directory, which # Sphinx has some limitations. It can't import files from outside its directory, which
# in our case is src/sphinx. On the other hand, we need to have platforms.rst file # in our case is src/sphinx. On the other hand, we need to have platforms.rst file
@ -292,10 +288,9 @@ def run_generate_docs(_):
['../examples/template-ha-mt-tls/kea-dhcp4-2.conf', 'template-ha-mt-tls-dhcp4-2.conf'] ['../examples/template-ha-mt-tls/kea-dhcp4-2.conf', 'template-ha-mt-tls-dhcp4-2.conf']
] ]
from shutil import copyfile
for [a, b] in FILES_TO_COPY: for [a, b] in FILES_TO_COPY:
src = os.path.join(src_dir, a) src = os.path.join(SRC_DIR, a)
dst = os.path.join(src_dir, 'arm', b) dst = os.path.join(SRC_DIR, 'arm', b)
print("Copying %s to %s" % (src, dst)) print("Copying %s to %s" % (src, dst))
copyfile(src, dst) copyfile(src, dst)

View File

@ -36,9 +36,8 @@ def parse_args():
def read_input_files(files): def read_input_files(files):
messages = {} messages = {}
for f in files: for f in files:
with open(f) as fp: with open(f, encoding='utf-8') as fp:
print("Processing %s" % f) print("Processing %s" % f)
namespace = None
msg_descr = None msg_descr = None
msg_id = None msg_id = None
msg_text = None msg_text = None
@ -119,7 +118,7 @@ def generate(in_files, out_file):
rst = generate_rst(messages) rst = generate_rst(messages)
if out_file: if out_file:
with open(out_file, 'w') as f: with open(out_file, 'w', encoding='utf-8') as f:
f.write(rst) f.write(rst)
print('Wrote generated RST content to: %s' % out_file) print('Wrote generated RST content to: %s' % out_file)
else: else:

315
hammer.py
View File

@ -6,6 +6,8 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this # License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
# pylint: disable=broad-exception-caught
"""Hammer - Kea development environment management tool.""" """Hammer - Kea development environment management tool."""
from __future__ import print_function from __future__ import print_function
@ -117,10 +119,9 @@ SYSTEMS = {
'3.19': True, '3.19': True,
'3.20': True, '3.20': True,
}, },
'arch': {} 'arch': {},
} }
# pylint: disable=C0326
IMAGE_TEMPLATES = { IMAGE_TEMPLATES = {
# fedora # fedora
'fedora-27-lxc': {'bare': 'lxc-fedora-27', 'kea': 'godfryd/kea-fedora-27'}, 'fedora-27-lxc': {'bare': 'lxc-fedora-27', 'kea': 'godfryd/kea-fedora-27'},
@ -276,52 +277,37 @@ def get_system_revision():
system = platform.system() system = platform.system()
if system == 'Linux': if system == 'Linux':
system, revision = None, None system, revision = None, None
try: if not os.path.exists('/etc/os-release'):
system, revision, _ = platform.dist() # pylint: disable=deprecated-method raise UnexpectedError('/etc/os-release does not exist. Cannot determine system or its revision.')
if system == 'debian': vals = {}
revision = revision.split('.')[0] with open('/etc/os-release', encoding='utf-8') as f:
elif system == 'redhat': for line in f.readlines():
system = 'rhel' if '=' in line:
revision = revision[0] key, val = line.split('=', 1)
elif system == 'rocky': vals[key.strip()] = val.strip().replace('"', '')
revision = revision[0]
elif system == 'centos':
revision = revision[0]
if not system or not revision:
raise Exception('fallback to /etc/os-release')
except Exception:
if os.path.exists('/etc/os-release'):
vals = {}
with open('/etc/os-release') as f:
for line in f.readlines():
if '=' in line:
key, val = line.split('=', 1)
vals[key.strip()] = val.strip().replace('"', '')
for i in ['ID', 'ID_LIKE']: for i in ['ID', 'ID_LIKE']:
if i in vals: if i in vals:
system_candidates = vals[i].strip('"').split() system_candidates = vals[i].strip('"').split()
for system_candidate in system_candidates: for system_candidate in system_candidates:
if system_candidate in SYSTEMS: if system_candidate in SYSTEMS:
system = system_candidate system = system_candidate
break
else:
continue
break break
if system is None: else:
raise Exception('cannot determine system') continue
break
if system is None:
raise UnexpectedError('cannot determine system')
for i in ['VERSION_ID', 'BUILD_ID']: for i in ['VERSION_ID', 'BUILD_ID']:
if i in vals: if i in vals:
revision = vals[i] revision = vals[i]
break break
if revision is None: if revision is None:
raise Exception('cannot determine revision') raise UnexpectedError('cannot determine revision')
if system in ['alpine', 'rhel', 'rocky']: if system in ['alpine', 'rhel', 'rocky']:
revision = revision.rsplit('.', 1)[0] revision = revision.rsplit('.', 1)[0]
else:
raise Exception('cannot determine system or its revision')
elif system == 'FreeBSD': elif system == 'FreeBSD':
system = system.lower() system = system.lower()
revision = platform.release() revision = platform.release()
@ -336,7 +322,10 @@ def get_system_revision():
class ExecutionError(Exception): class ExecutionError(Exception):
"""Exception thrown when execution encountered an error.""" """Exception thrown when execution encountered an error."""
pass
class UnexpectedError(Exception):
"""Exception thrown when an unexpected error occurred that hammer does not know how to recover from."""
def execute(cmd, timeout=60, cwd=None, env=None, raise_error=True, dry_run=False, log_file_path=None, def execute(cmd, timeout=60, cwd=None, env=None, raise_error=True, dry_run=False, log_file_path=None,
@ -379,57 +368,43 @@ def execute(cmd, timeout=60, cwd=None, env=None, raise_error=True, dry_run=False
cmd = cmd.replace('sudo', 'sudo -E') cmd = cmd.replace('sudo', 'sudo -E')
if log_file_path: if log_file_path:
log_file = open(log_file_path, "wb") with open(log_file_path, "wb", encoding='utf-8') as file:
log_file = file.read()
for attempt in range(attempts): for attempt in range(attempts):
if interactive: if interactive:
# Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True identified, # Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True identified,
# security issue. # security issue.
p = subprocess.Popen(cmd, cwd=cwd, env=env, shell=True) # nosec B602 with subprocess.Popen(cmd, cwd=cwd, env=env, shell=True) as pipe: # nosec: B602
exitcode = p.wait() pipe.communicate()
exitcode = pipe.returncode
else: else:
# Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True identified, # Issue: [B602:subprocess_popen_with_shell_equals_true] subprocess call with shell=True identified,
# security issue. # security issue.
p = subprocess.Popen(cmd, cwd=cwd, env=env, shell=True, # nosec B602 with subprocess.Popen(cmd, cwd=cwd, env=env, shell=True, # nosec: B602
stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as pipe:
if timeout is not None:
if capture: pipe.wait(timeout)
output = '' try:
t0 = time.time() stdout, _ = pipe.communicate()
# Repeat until the process has stopped and there are no more lines except subprocess.TimeoutExpired as e:
# to read, or until the timeout is up. pipe.kill()
while True: stdout2, _ = pipe.communicate()
line = p.stdout.readline() stdout += stdout2
if line: raise ExecutionError(f'Execution timeout: {e}, cmd: {cmd}') from e
line_decoded = line.decode(encoding='ascii', errors='ignore').rstrip() + '\r' exitcode = pipe.returncode
if not quiet: if capture:
log.info(line_decoded) output = stdout.decode('utf-8')
if capture: if not quiet:
output += line_decoded print(stdout.decode('utf-8'))
if log_file_path: if log_file_path is not None:
log_file.write(line) log_file.write(stdout)
t1 = time.time()
if p.poll() is not None and not line or (timeout is not None and timeout < t1 - t0):
break
# If no exitcode yet, ie. process is still running then it means that timeout occurred.
# In such case terminate the process and raise an exception.
if p.poll() is None:
# kill using sudo to be able to kill other sudo commands
execute('sudo kill -s TERM %s' % p.pid)
time.sleep(5)
# if still running, kill harder
if p.poll() is None:
execute('sudo kill -s KILL %s' % p.pid)
msg = "Execution timeout, %d > %d seconds elapsed (start: %d, stop %d), cmd: '%s'"
msg = msg % (t1 - t0, timeout, t0, t1, cmd)
raise ExecutionError(msg)
exitcode = p.returncode
if exitcode == 0: if exitcode == 0:
break break
elif attempt < attempts - 1:
if attempt < attempts - 1:
txt = 'command failed, retry, attempt %d/%d' % (attempt, attempts) txt = 'command failed, retry, attempt %d/%d' % (attempt, attempts)
if log_file_path: if log_file_path:
txt_to_file = '\n\n[HAMMER] %s\n\n\n' % txt txt_to_file = '\n\n[HAMMER] %s\n\n\n' % txt
@ -468,7 +443,12 @@ def _prepare_installed_packages_cache_for_debs():
continue continue
status, name, version, arch, descr = m.groups() status, name, version, arch, descr = m.groups()
name = name.split(':')[0] name = name.split(':')[0]
pkg_cache[name] = dict(status=status, version=version, arch=arch, descr=descr) pkg_cache[name] = {
'status': status,
'version': version,
'arch': arch,
'descr': descr,
}
return pkg_cache return pkg_cache
@ -480,7 +460,7 @@ def _prepare_installed_packages_cache_for_rpms():
for line in out.splitlines(): for line in out.splitlines():
name = line.strip() name = line.strip()
pkg_cache[name] = dict(status='ii') pkg_cache[name] = {'status': 'ii'}
return pkg_cache return pkg_cache
@ -492,7 +472,7 @@ def _prepare_installed_packages_cache_for_alpine():
for line in out.splitlines(): for line in out.splitlines():
name = line.strip() name = line.strip()
pkg_cache[name] = dict(status='ii') pkg_cache[name] = {'status': 'ii'}
return pkg_cache return pkg_cache
@ -575,7 +555,7 @@ def get_image_template(key, variant):
return IMAGE_TEMPLATES[key][variant] return IMAGE_TEMPLATES[key][variant]
def _get_full_repo_url(repository_url, system, revision, pkg_version): def _get_full_repo_url(repository_url, system, revision):
if not repository_url: if not repository_url:
return None return None
repo_name = 'kea-%s-%s' % (system, revision) repo_name = 'kea-%s-%s' % (system, revision)
@ -584,7 +564,7 @@ def _get_full_repo_url(repository_url, system, revision, pkg_version):
return repo_url return repo_url
class VagrantEnv(object): class VagrantEnv():
"""Helper class that makes interacting with Vagrant easier. """Helper class that makes interacting with Vagrant easier.
It creates Vagrantfile according to specified system. It exposes basic Vagrant functions It creates Vagrantfile according to specified system. It exposes basic Vagrant functions
@ -619,7 +599,7 @@ class VagrantEnv(object):
self.python = None self.python = None
self.key = key = "%s-%s-%s" % (system, revision, provider) self.key = key = "%s-%s-%s" % (system, revision, provider)
self.image_tpl = image_tpl = get_image_template(key, image_template_variant) self.image_tpl = get_image_template(key, image_template_variant)
self.repo_dir = os.getcwd() self.repo_dir = os.getcwd()
sys_dir = "%s-%s" % (system, revision) sys_dir = "%s-%s" % (system, revision)
@ -670,7 +650,7 @@ class VagrantEnv(object):
box_version=box_version, box_version=box_version,
hostname=hostname) hostname=hostname)
with open(vagrantfile_path, "w") as f: with open(vagrantfile_path, "w", encoding='utf-8') as f:
f.write(vagrantfile) f.write(vagrantfile)
log.info('Prepared vagrant system %s in %s', self.name, self.vagrant_dir) log.info('Prepared vagrant system %s in %s', self.name, self.vagrant_dir)
@ -706,7 +686,7 @@ class VagrantEnv(object):
with urllib.request.urlopen(url) as response: # nosec B310 with urllib.request.urlopen(url) as response: # nosec B310
data = response.read() data = response.read()
except Exception as e: except Exception as e:
log.exception(f'ignored exception: {e}') log.exception('ignored exception: %s', e)
return {} return {}
data = json.loads(data) data = json.loads(data)
return data return data
@ -715,7 +695,7 @@ class VagrantEnv(object):
meta_file = os.path.join(self.vagrant_dir, '.vagrant/machines/default', self.provider, 'box_meta') meta_file = os.path.join(self.vagrant_dir, '.vagrant/machines/default', self.provider, 'box_meta')
if not os.path.exists(meta_file): if not os.path.exists(meta_file):
return {} return {}
with open(meta_file) as f: with open(meta_file, encoding='utf-8') as f:
data = f.read() data = f.read()
data = json.loads(data) data = json.loads(data)
return data return data
@ -751,7 +731,7 @@ class VagrantEnv(object):
_, out = execute("vagrant status", cwd=self.vagrant_dir, timeout=15, capture=True, quiet=True) _, out = execute("vagrant status", cwd=self.vagrant_dir, timeout=15, capture=True, quiet=True)
m = re.search(r'default\s+(.+)\(', out) m = re.search(r'default\s+(.+)\(', out)
if not m: if not m:
raise Exception('cannot get status in:\n%s' % out) raise UnexpectedError('cannot get status in:\n%s' % out)
return m.group(1).strip() return m.group(1).strip()
def bring_up_latest_box(self): def bring_up_latest_box(self):
@ -813,7 +793,7 @@ class VagrantEnv(object):
# correct files ownership # correct files ownership
execute('sudo chown `id -un`:`id -gn` *', cwd=lxc_box_dir) execute('sudo chown `id -un`:`id -gn` *', cwd=lxc_box_dir)
# and other metadata # and other metadata
with open(os.path.join(lxc_box_dir, 'metadata.json'), 'w') as f: with open(os.path.join(lxc_box_dir, 'metadata.json'), 'w', encoding='utf-8') as f:
now = datetime.datetime.now() now = datetime.datetime.now()
f.write('{\n') f.write('{\n')
f.write(' "provider": "lxc",\n') f.write(' "provider": "lxc",\n')
@ -906,7 +886,7 @@ class VagrantEnv(object):
execute('scp -F %s -r default:~/kea-pkg/* .' % ssh_cfg_path, cwd=pkgs_dir) execute('scp -F %s -r default:~/kea-pkg/* .' % ssh_cfg_path, cwd=pkgs_dir)
if upload: if upload:
repo_url = _get_full_repo_url(repository_url, self.system, self.revision, pkg_version) repo_url = _get_full_repo_url(repository_url, self.system, self.revision)
if repo_url is None: if repo_url is None:
raise ValueError('repo_url is None') raise ValueError('repo_url is None')
upload_cmd = 'curl -v --netrc -f' upload_cmd = 'curl -v --netrc -f'
@ -957,7 +937,7 @@ class VagrantEnv(object):
execute(cmd, cwd=self.vagrant_dir) execute(cmd, cwd=self.vagrant_dir)
results_file = os.path.join(self.vagrant_dir, 'unit-test-results.json') results_file = os.path.join(self.vagrant_dir, 'unit-test-results.json')
if os.path.exists(results_file): if os.path.exists(results_file):
with open(results_file) as f: with open(results_file, encoding='utf-8') as f:
txt = f.read() txt = f.read()
results = json.loads(txt) results = json.loads(txt)
total = results['grand_total'] total = results['grand_total']
@ -966,7 +946,7 @@ class VagrantEnv(object):
cmd = 'scp -F %s -r default:/home/vagrant/aggregated_tests.xml .' % ssh_cfg_path cmd = 'scp -F %s -r default:/home/vagrant/aggregated_tests.xml .' % ssh_cfg_path
execute(cmd, cwd=self.vagrant_dir) execute(cmd, cwd=self.vagrant_dir)
except Exception as e: except Exception as e:
log.exception(f'ignored issue with parsing unit test results: {e}') log.exception('ignored issue with parsing unit test results: %s', e)
return total, passed return total, passed
@ -987,7 +967,7 @@ class VagrantEnv(object):
execute('vagrant ssh-config > %s' % ssh_cfg_path, cwd=self.vagrant_dir) execute('vagrant ssh-config > %s' % ssh_cfg_path, cwd=self.vagrant_dir)
return ssh_cfg_path return ssh_cfg_path
def execute(self, cmd, timeout=None, raise_error=True, log_file_path=None, quiet=False, env=None, def execute(self, cmd, timeout=None, raise_error=True, log_file_path=None, quiet=False, env=None, capture=False,
attempts=1, sleep_time_after_attempt=None): attempts=1, sleep_time_after_attempt=None):
"""Execute provided command inside Vagrant system.""" """Execute provided command inside Vagrant system."""
if not env: if not env:
@ -996,7 +976,7 @@ class VagrantEnv(object):
return execute('vagrant ssh -c "%s"' % cmd, env=env, cwd=self.vagrant_dir, timeout=timeout, return execute('vagrant ssh -c "%s"' % cmd, env=env, cwd=self.vagrant_dir, timeout=timeout,
raise_error=raise_error, dry_run=self.dry_run, log_file_path=log_file_path, raise_error=raise_error, dry_run=self.dry_run, log_file_path=log_file_path,
quiet=quiet, check_times=self.check_times, quiet=quiet, check_times=self.check_times, capture=capture,
attempts=attempts, sleep_time_after_attempt=sleep_time_after_attempt) attempts=attempts, sleep_time_after_attempt=sleep_time_after_attempt)
def prepare_system(self): def prepare_system(self):
@ -1038,7 +1018,7 @@ class VagrantEnv(object):
exitcode = self.execute(cmd, raise_error=False) exitcode = self.execute(cmd, raise_error=False)
if exitcode != 0: if exitcode != 0:
env = os.environ.copy() env = os.environ.copy()
with open(os.path.expanduser('~/rhel-creds.txt')) as f: with open(os.path.expanduser('~/rhel-creds.txt'), encoding='utf-8') as f:
env['RHEL_USER'] = f.readline().strip() env['RHEL_USER'] = f.readline().strip()
env['RHEL_PASSWD'] = f.readline().strip() env['RHEL_PASSWD'] = f.readline().strip()
self.execute('sudo subscription-manager register --user $RHEL_USER --password "$RHEL_PASSWD"', env=env) self.execute('sudo subscription-manager register --user $RHEL_USER --password "$RHEL_PASSWD"', env=env)
@ -1114,7 +1094,7 @@ def _install_gtest_sources():
gtest_version = '1.14.0' gtest_version = '1.14.0'
gtest_path = f'/usr/src/googletest-release-{gtest_version}/googletest' gtest_path = f'/usr/src/googletest-release-{gtest_version}/googletest'
if os.path.exists(gtest_path): if os.path.exists(gtest_path):
log.info(f'gtest is already installed in {gtest_path}.') log.info('gtest is already installed in %s.', gtest_path)
return return
execute('mkdir -p ~/.hammer-tmp') execute('mkdir -p ~/.hammer-tmp')
@ -1134,7 +1114,7 @@ def _install_libyang_from_sources(ignore_errors=False):
libyang_header = f'{prefix}/include/libyang/version.h' libyang_header = f'{prefix}/include/libyang/version.h'
if (any(os.path.exists(i) for i in libyang_so_candidates) and os.path.exists(libyang_header) and if (any(os.path.exists(i) for i in libyang_so_candidates) and os.path.exists(libyang_header) and
execute(f"grep -F '#define LY_VERSION_MAJOR 2' '{libyang_header}'", raise_error=False) == 0): execute(f"grep -F '#define LY_VERSION_MAJOR 2' '{libyang_header}'", raise_error=False) == 0):
log.info(f'libyang is already installed at {libyang_header}.') log.info('libyang is already installed at %s.', libyang_header)
return return
version = 'v2.1.4' version = 'v2.1.4'
@ -1149,7 +1129,7 @@ def _install_libyang_from_sources(ignore_errors=False):
cwd='~/.hammer-tmp/libyang/build') cwd='~/.hammer-tmp/libyang/build')
execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/libyang/build') execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/libyang/build')
execute('sudo make install', cwd='~/.hammer-tmp/libyang/build') execute('sudo make install', cwd='~/.hammer-tmp/libyang/build')
system, revision = get_system_revision() system, _ = get_system_revision()
if system != 'alpine': if system != 'alpine':
execute('sudo ldconfig') execute('sudo ldconfig')
except Exception as e: except Exception as e:
@ -1167,7 +1147,7 @@ def _install_sysrepo_from_sources(ignore_errors=False):
sysrepo_header = f'{prefix}/include/sysrepo/version.h' sysrepo_header = f'{prefix}/include/sysrepo/version.h'
if (any(os.path.exists(i) for i in sysrepo_so_candidates) and os.path.exists(sysrepo_header) and if (any(os.path.exists(i) for i in sysrepo_so_candidates) and os.path.exists(sysrepo_header) and
execute(f"grep -F '#define SR_VERSION_MAJOR 7' '{sysrepo_header}'", raise_error=False) == 0): execute(f"grep -F '#define SR_VERSION_MAJOR 7' '{sysrepo_header}'", raise_error=False) == 0):
log.info(f'sysrepo is already installed at {sysrepo_header}.') log.info('sysrepo is already installed at %s.', sysrepo_header)
return return
version = 'v2.2.12' version = 'v2.2.12'
@ -1185,7 +1165,7 @@ def _install_sysrepo_from_sources(ignore_errors=False):
execute('cmake -DBUILD_TESTING=OFF -DREPO_PATH=/etc/sysrepo ..', cwd='~/.hammer-tmp/sysrepo/build') execute('cmake -DBUILD_TESTING=OFF -DREPO_PATH=/etc/sysrepo ..', cwd='~/.hammer-tmp/sysrepo/build')
execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/sysrepo/build') execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/sysrepo/build')
execute('sudo make install', cwd='~/.hammer-tmp/sysrepo/build') execute('sudo make install', cwd='~/.hammer-tmp/sysrepo/build')
system, revision = get_system_revision() system, _ = get_system_revision()
if system != 'alpine': if system != 'alpine':
execute('sudo ldconfig') execute('sudo ldconfig')
except Exception as e: except Exception as e:
@ -1203,7 +1183,7 @@ def _install_libyang_cpp_from_sources(ignore_errors=False):
libyang_cpp_pc = f'{prefix_lib}/pkgconfig/libyang-cpp.pc' libyang_cpp_pc = f'{prefix_lib}/pkgconfig/libyang-cpp.pc'
if (os.path.exists(libyang_cpp_so) and os.path.exists(libyang_cpp_pc) and if (os.path.exists(libyang_cpp_so) and os.path.exists(libyang_cpp_pc) and
execute(f"grep -F 'Version: 1.1.0' '{libyang_cpp_pc}'", raise_error=False) == 0): execute(f"grep -F 'Version: 1.1.0' '{libyang_cpp_pc}'", raise_error=False) == 0):
log.info(f'libyang-cpp is already installed at {libyang_cpp_so}.') log.info('libyang-cpp is already installed at %s.', libyang_cpp_so)
return return
version = 'ae7d649ea75da081725c119dd553b2ef3121a6f8' version = 'ae7d649ea75da081725c119dd553b2ef3121a6f8'
@ -1214,16 +1194,22 @@ def _install_libyang_cpp_from_sources(ignore_errors=False):
execute('git clone https://github.com/CESNET/libyang-cpp.git ~/.hammer-tmp/libyang-cpp') execute('git clone https://github.com/CESNET/libyang-cpp.git ~/.hammer-tmp/libyang-cpp')
execute(f'git checkout {version}', cwd='~/.hammer-tmp/libyang-cpp') execute(f'git checkout {version}', cwd='~/.hammer-tmp/libyang-cpp')
# New cpp compiler is more picky about missing headers. (ex. Fedora 40) # New cpp compiler is more picky about missing headers. (ex. Fedora 40)
return_code = execute('sudo grep "#include <algorithm>" ~/.hammer-tmp/libyang-cpp/src/Context.cpp', execute("""git apply <<EOF
raise_error=False) diff --git a/src/Context.cpp b/src/Context.cpp
if return_code == 1: index b2fe887..add11cc 100644
execute(r'sed -i "/#include <libyang\/libyang.h>/a #include <algorithm>" ' --- a/src/Context.cpp
'~/.hammer-tmp/libyang-cpp/src/Context.cpp') +++ b/src/Context.cpp
@@ -13,2 +13,3 @@
#include <libyang/libyang.h>
+#include <algorithm>
#include <span>
EOF
""", cwd='~/.hammer-tmp/libyang-cpp')
execute('mkdir ~/.hammer-tmp/libyang-cpp/build') execute('mkdir ~/.hammer-tmp/libyang-cpp/build')
execute('cmake -DBUILD_TESTING=OFF .. ', cwd='~/.hammer-tmp/libyang-cpp/build') execute('cmake -DBUILD_TESTING=OFF .. ', cwd='~/.hammer-tmp/libyang-cpp/build')
execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/libyang-cpp/build') execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/libyang-cpp/build')
execute('sudo make install', cwd='~/.hammer-tmp/libyang-cpp/build') execute('sudo make install', cwd='~/.hammer-tmp/libyang-cpp/build')
system, revision = get_system_revision() system, _ = get_system_revision()
if system != 'alpine': if system != 'alpine':
execute('sudo ldconfig') execute('sudo ldconfig')
except Exception as e: except Exception as e:
@ -1241,7 +1227,7 @@ def _install_sysrepo_cpp_from_sources(ignore_errors=False):
sysrepo_cpp_pc = f'{prefix_lib}/pkgconfig/sysrepo-cpp.pc' sysrepo_cpp_pc = f'{prefix_lib}/pkgconfig/sysrepo-cpp.pc'
if (os.path.exists(sysrepo_cpp_so) and os.path.exists(sysrepo_cpp_pc) and if (os.path.exists(sysrepo_cpp_so) and os.path.exists(sysrepo_cpp_pc) and
execute(f"grep -F 'Version: 1.1.0' '{sysrepo_cpp_pc}'", raise_error=False) == 0): execute(f"grep -F 'Version: 1.1.0' '{sysrepo_cpp_pc}'", raise_error=False) == 0):
log.info(f'sysrepo-cpp is already installed at {sysrepo_cpp_so}.') log.info('sysrepo-cpp is already installed at %s.', sysrepo_cpp_so)
return return
version = '02634174ffc60568301c3d9b9b7cf710cff6a586' version = '02634174ffc60568301c3d9b9b7cf710cff6a586'
@ -1255,7 +1241,7 @@ def _install_sysrepo_cpp_from_sources(ignore_errors=False):
execute('cmake -DBUILD_TESTING=OFF .. ', cwd='~/.hammer-tmp/sysrepo-cpp/build') execute('cmake -DBUILD_TESTING=OFF .. ', cwd='~/.hammer-tmp/sysrepo-cpp/build')
execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/sysrepo-cpp/build') execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/sysrepo-cpp/build')
execute('sudo make install', cwd='~/.hammer-tmp/sysrepo-cpp/build') execute('sudo make install', cwd='~/.hammer-tmp/sysrepo-cpp/build')
system, revision = get_system_revision() system, _ = get_system_revision()
if system != 'alpine': if system != 'alpine':
execute('sudo ldconfig') execute('sudo ldconfig')
except Exception as e: except Exception as e:
@ -1332,7 +1318,7 @@ def _configure_mysql(system, revision, features):
exit_code = execute('openssl rsa -in src/lib/asiolink/testutils/ca/kea-server.key ' exit_code = execute('openssl rsa -in src/lib/asiolink/testutils/ca/kea-server.key '
'-out src/lib/asiolink/testutils/ca/kea-server.key', raise_error=False) '-out src/lib/asiolink/testutils/ca/kea-server.key', raise_error=False)
if exit_code != 0: if exit_code != 0:
log.warning(f'openssl command failed with exit code {exit_code}, but continuing...') log.warning('openssl command failed with exit code %d, but continuing...', exit_code)
for file in [ for file in [
'./src/lib/asiolink/testutils/ca/kea-ca.crt', './src/lib/asiolink/testutils/ca/kea-ca.crt',
'./src/lib/asiolink/testutils/ca/kea-client.crt', './src/lib/asiolink/testutils/ca/kea-client.crt',
@ -1436,7 +1422,7 @@ ssl_key = {cert_dir}/kea-client.key
execute(cmd) execute(cmd)
if system == 'debian' and revision == '9': if system == 'debian' and revision == '9':
log.info('FIX FOR ISSUE kea#389: {} {}'.format(system, revision)) log.info('FIX FOR ISSUE kea#389: %s %s', system, revision)
cmd = "sh -c \"cat <<EOF | sudo mysql -u root\n" cmd = "sh -c \"cat <<EOF | sudo mysql -u root\n"
cmd += "use keatest;\n" cmd += "use keatest;\n"
cmd += "set global innodb_large_prefix=on;\n" cmd += "set global innodb_large_prefix=on;\n"
@ -1447,7 +1433,7 @@ ssl_key = {cert_dir}/kea-client.key
execute(cmd) execute(cmd)
def _enable_postgresql(system, revision): def _enable_postgresql(system):
if system == 'alpine': if system == 'alpine':
execute('sudo rc-update add postgresql') execute('sudo rc-update add postgresql')
elif system == 'freebsd': elif system == 'freebsd':
@ -1463,7 +1449,7 @@ def _enable_postgresql(system, revision):
execute('sudo systemctl enable postgresql.service') execute('sudo systemctl enable postgresql.service')
def _restart_postgresql(system, revision): def _restart_postgresql(system):
if system == 'freebsd': if system == 'freebsd':
# redirecting output from start script to /dev/null otherwise the postgresql rc.d script will hang # redirecting output from start script to /dev/null otherwise the postgresql rc.d script will hang
# calling restart instead of start allow hammer.py to pass even if postgresql is already installed # calling restart instead of start allow hammer.py to pass even if postgresql is already installed
@ -1481,12 +1467,12 @@ def _restart_postgresql(system, revision):
exit_code = execute('sudo systemctl restart postgresql.service', raise_error=False) exit_code = execute('sudo systemctl restart postgresql.service', raise_error=False)
if exit_code != 0: if exit_code != 0:
log.error('Command "sudo systemctl restart postgresql.service" failed. Here is the journal:') log.error('Command "sudo systemctl restart postgresql.service" failed. Here is the journal:')
_, output = execute('sudo journalctl -xu postgresql.service', raise_error=False) execute('sudo journalctl -xu postgresql.service', raise_error=False)
log.error('And here are the logs:') log.error('And here are the logs:')
_, output = execute("sudo -u postgres psql -A -t -c 'SELECT pg_current_logfile()'", _, output = execute("sudo -u postgres psql -A -t -c 'SELECT pg_current_logfile()'",
capture=True, quiet=True) capture=True, quiet=True)
logfile = os.path.basename(output.strip()) logfile = os.path.basename(output.strip())
_, output = execute(fr'sudo find /var -type f -name "{logfile}" -exec cat {{}} \;', raise_error=False) execute(fr'sudo find /var -type f -name "{logfile}" -exec cat {{}} \;', raise_error=False)
sys.exit(exit_code) sys.exit(exit_code)
@ -1495,11 +1481,12 @@ def _restart_postgresql(system, revision):
# and user both set to 'all'. This is to not affect authentication of # and user both set to 'all'. This is to not affect authentication of
# `postgres` user which should have a separate entry. # `postgres` user which should have a separate entry.
def _change_postgresql_auth_method(connection_type, auth_method, hba_file): def _change_postgresql_auth_method(connection_type, auth_method, hba_file):
execute(r"sudo sed -i.bak 's/^{}\(.*\)all\(.*\)all\(.*\) [a-z0-9]*$/{}\1all\2all\3 {}/g' '{}'".format( execute(fr"sudo sed -i.bak 's/^{connection_type}\(.*\)all\(.*\)all\(.*\) [a-z0-9]*$"
connection_type, connection_type, auth_method, hba_file), cwd='/tmp') fr"/{connection_type}\1all\2all\3 {auth_method}/g' '{hba_file}'",
cwd='/tmp')
def _configure_pgsql(system, revision, features): def _configure_pgsql(system, features):
""" Configure PostgreSQL DB """ """ Configure PostgreSQL DB """
if system == 'freebsd': if system == 'freebsd':
@ -1539,8 +1526,8 @@ def _configure_pgsql(system, revision, features):
# the initial start of the postgresql will create the 'postmaster.opts' file # the initial start of the postgresql will create the 'postmaster.opts' file
execute(f'sudo test ! -f {var_db_postgres_data}/postmaster.opts && sudo service postgresql onestart || true') execute(f'sudo test ! -f {var_db_postgres_data}/postmaster.opts && sudo service postgresql onestart || true')
_enable_postgresql(system, revision) _enable_postgresql(system)
_restart_postgresql(system, revision) _restart_postgresql(system)
# Change auth-method to 'md5' on all connections. # Change auth-method to 'md5' on all connections.
cmd = "sudo -u postgres psql -t -c 'SHOW hba_file' | xargs" cmd = "sudo -u postgres psql -t -c 'SHOW hba_file' | xargs"
@ -1560,7 +1547,7 @@ def _configure_pgsql(system, revision, features):
{} {}
' '{}'""".format(auth_header, postgres_auth_line, hba_file)) ' '{}'""".format(auth_header, postgres_auth_line, hba_file))
_restart_postgresql(system, revision) _restart_postgresql(system)
cmd = """sh -c \"cat <<EOF | sudo -u postgres psql postgres cmd = """sh -c \"cat <<EOF | sudo -u postgres psql postgres
DROP DATABASE IF EXISTS keatest; DROP DATABASE IF EXISTS keatest;
@ -1614,7 +1601,7 @@ def _get_package_version(package: str):
Returns the version available in the package manager's repository for the requested package. Returns the version available in the package manager's repository for the requested package.
:param package: the name of the package whose version is retrieved :param package: the name of the package whose version is retrieved
""" """
system, revision = get_system_revision() system, _ = get_system_revision()
if system == 'alpine': if system == 'alpine':
cmd = "apk search --exact {0} | sed 's/{0}-//g'" cmd = "apk search --exact {0} | sed 's/{0}-//g'"
elif system in ['debian', 'ubuntu']: elif system in ['debian', 'ubuntu']:
@ -1644,13 +1631,13 @@ def require_minimum_package_version(package: str, minimum: str):
if version < minimum: if version < minimum:
message = f"ERROR: {package} has version {version}, but must be >= {minimum}" message = f"ERROR: {package} has version {version}, but must be >= {minimum}"
log.error(message) log.error(message)
raise Exception(message) raise UnexpectedError(message)
def prepare_system_local(features, check_times, ignore_errors_for, just_configure): def prepare_system_local(features, check_times, ignore_errors_for, just_configure):
"""Prepare local system for Kea development based on requested features.""" """Prepare local system for Kea development based on requested features."""
system, revision = get_system_revision() system, revision = get_system_revision()
log.info(f'Preparing deps for {system} {revision}...') log.info('Preparing deps for %s %s...', system, revision)
if not just_configure: if not just_configure:
install_packages_local(system, revision, features, check_times, ignore_errors_for) install_packages_local(system, revision, features, check_times, ignore_errors_for)
@ -1659,7 +1646,7 @@ def prepare_system_local(features, check_times, ignore_errors_for, just_configur
_configure_mysql(system, revision, features) _configure_mysql(system, revision, features)
if 'pgsql' in features: if 'pgsql' in features:
_configure_pgsql(system, revision, features) _configure_pgsql(system, features)
log.info('Preparing deps completed successfully.') log.info('Preparing deps completed successfully.')
@ -2199,7 +2186,12 @@ def _build_binaries_and_run_ut(system, revision, features, tarball_path, env, ch
failures = int(root.get('failures')) failures = int(root.get('failures'))
disabled = int(root.get('disabled')) disabled = int(root.get('disabled'))
errors = int(root.get('errors')) errors = int(root.get('errors'))
results[fn] = dict(total=total, failures=failures, disabled=disabled, errors=errors) results[fn] = {
'total': total,
'failures': failures,
'disabled': disabled,
'errors': errors,
}
grand_total += total grand_total += total
grand_not_passed += failures + errors grand_not_passed += failures + errors
@ -2221,7 +2213,7 @@ def _build_binaries_and_run_ut(system, revision, features, tarball_path, env, ch
result = green(result) result = green(result)
log.info('Unit test results: %s', result) log.info('Unit test results: %s', result)
with open('unit-test-results.json', 'w') as f: with open('unit-test-results.json', 'w', encoding='utf-8') as f:
f.write(json.dumps(results)) f.write(json.dumps(results))
# store aggregated results in XML # store aggregated results in XML
@ -2255,7 +2247,7 @@ def _check_installed_rpm_or_debs(services_list):
def _build_rpm(system, revision, features, tarball_path, env, check_times, dry_run, def _build_rpm(system, revision, features, tarball_path, env, check_times, dry_run,
pkg_version, pkg_isc_version, repo_url): pkg_version, pkg_isc_version):
# unpack kea sources tarball # unpack kea sources tarball
_, arch = execute('arch', capture=True) _, arch = execute('arch', capture=True)
@ -2318,7 +2310,7 @@ def _build_rpm(system, revision, features, tarball_path, env, check_times, dry_r
def _build_deb(system, revision, features, tarball_path, env, check_times, dry_run, def _build_deb(system, revision, features, tarball_path, env, check_times, dry_run,
pkg_version, pkg_isc_version, repository_url, repo_url): pkg_version, pkg_isc_version, repo_url):
_, arch = execute('arch', capture=True) _, arch = execute('arch', capture=True)
if system == 'debian' and revision == '9': if system == 'debian' and revision == '9':
@ -2329,15 +2321,15 @@ def _build_deb(system, revision, features, tarball_path, env, check_times, dry_r
_, output = execute("curl -o /dev/null -s -w '%{{http_code}}' {}/dists/kea/Release 2>/dev/null".format(repo_url), _, output = execute("curl -o /dev/null -s -w '%{{http_code}}' {}/dists/kea/Release 2>/dev/null".format(repo_url),
capture=True) capture=True)
http_code = output.rstrip() http_code = output.rstrip()
release_file_exists = (http_code == '200') release_file_exists = http_code == '200'
if release_file_exists: if release_file_exists:
log.info(f'{repo_url}/dists/kea/Release exists.') log.info('%s/dists/kea/Release exists.', repo_url)
else: else:
repo_name = 'kea-%s-%s-%s' % (pkg_version.rsplit('.', 1)[0], system, revision) repo_name = 'kea-%s-%s-%s' % (pkg_version.rsplit('.', 1)[0], system, revision)
log.error(f'{repo_url}/dists/kea/Release does not exist. ' log.error('%s/dists/kea/Release does not exist. '
f'This is usually caused by no package existing in {repo_name}. ' 'This is usually caused by no package existing in %s. '
'You can solve this by uploading any package.' 'You can solve this by uploading any package.'
'Continuing, but the build will likely fail.') 'Continuing, but the build will likely fail.', repo_url, repo_name)
# try apt update for up to 10 times if there is an error # try apt update for up to 10 times if there is an error
for _ in range(10): for _ in range(10):
@ -2387,8 +2379,8 @@ def _build_deb(system, revision, features, tarball_path, env, check_times, dry_r
_check_installed_rpm_or_debs(services_list) _check_installed_rpm_or_debs(services_list)
def _build_alpine_apk(system, revision, features, tarball_path, env, check_times, dry_run, def _build_alpine_apk(revision, features, tarball_path, check_times, dry_run,
pkg_version, pkg_isc_version, repo_url): pkg_version, pkg_isc_version):
_, arch = execute('arch', capture=True) _, arch = execute('arch', capture=True)
# unpack tarball # unpack tarball
execute('sudo rm -rf kea-src packages', check_times=check_times, dry_run=dry_run) execute('sudo rm -rf kea-src packages', check_times=check_times, dry_run=dry_run)
@ -2444,21 +2436,21 @@ def _build_native_pkg(system, revision, features, tarball_path, env, check_times
# enable ccache if requested # enable ccache if requested
env = _prepare_ccache_if_needed(system, ccache_dir, env) env = _prepare_ccache_if_needed(system, ccache_dir, env)
repo_url = _get_full_repo_url(repository_url, system, revision, pkg_version) repo_url = _get_full_repo_url(repository_url, system, revision)
if repo_url is None: if repo_url is None:
raise ValueError('repo_url is None') raise ValueError('repo_url is None')
if system in ['fedora', 'centos', 'rhel', 'rocky']: if system in ['fedora', 'centos', 'rhel', 'rocky']:
_build_rpm(system, revision, features, tarball_path, env, check_times, dry_run, _build_rpm(system, revision, features, tarball_path, env, check_times, dry_run,
pkg_version, pkg_isc_version, repo_url) pkg_version, pkg_isc_version)
elif system in ['ubuntu', 'debian']: elif system in ['ubuntu', 'debian']:
_build_deb(system, revision, features, tarball_path, env, check_times, dry_run, _build_deb(system, revision, features, tarball_path, env, check_times, dry_run,
pkg_version, pkg_isc_version, repository_url, repo_url) pkg_version, pkg_isc_version, repo_url)
elif system in ['alpine']: elif system in ['alpine']:
_build_alpine_apk(system, revision, features, tarball_path, env, check_times, dry_run, _build_alpine_apk(revision, features, tarball_path, check_times, dry_run,
pkg_version, pkg_isc_version, repo_url) pkg_version, pkg_isc_version)
elif system in ['arch']: elif system in ['arch']:
pass pass
@ -2546,7 +2538,7 @@ def build_in_vagrant(provider, system, revision, features, leave_system, tarball
except ExecutionError as e: except ExecutionError as e:
error = e error = e
msg = ' - ' + red(str(e)) msg = ' - ' + red(str(e))
except Exception as e: # pylint: disable=broad-except except Exception as e:
log.exception('Building erred') log.exception('Building erred')
error = e error = e
msg = ' - ' + red(str(e)) msg = ' - ' + red(str(e))
@ -2817,9 +2809,9 @@ def parse_args():
def list_supported_systems(): def list_supported_systems():
"""List systems hammer can support (with supported providers).""" """List systems hammer can support (with supported providers)."""
for system in SYSTEMS: for system, revision in SYSTEMS.items():
print('%s:' % system) print(f'{system}:')
for release, supported in SYSTEMS[system].items(): for release, supported in revision.items():
if not supported: if not supported:
continue continue
providers = [] providers = []
@ -2828,7 +2820,7 @@ def list_supported_systems():
if k in IMAGE_TEMPLATES: if k in IMAGE_TEMPLATES:
providers.append(p) providers.append(p)
providers = ', '.join(providers) providers = ', '.join(providers)
print(' - %s: %s' % (release, providers)) print(f' - {release}: {providers}')
def list_created_systems(): def list_created_systems():
@ -2903,10 +2895,10 @@ def _get_features(args):
for i in args.with_randomly: for i in args.with_randomly:
if _coin_toss(): if _coin_toss():
features.add(i) features.add(i)
log.info(f'Feature enabled through coin toss: {i}') log.info('Feature enabled through coin toss: %s', i)
else: else:
features.discard(i) features.discard(i)
log.info(f'Feature disabled through coin toss: {i}') log.info('Feature disabled through coin toss: %s', i)
if hasattr(args, 'ccache_dir') and args.ccache_dir: if hasattr(args, 'ccache_dir') and args.ccache_dir:
features.add('ccache') features.add('ccache')
@ -2955,20 +2947,19 @@ def _print_summary(results, features):
def _check_system_revision(system, revision): def _check_system_revision(system, revision):
if revision == 'all': if revision == 'all':
return return
if system not in SYSTEMS.keys(): if system not in SYSTEMS:
msg = "hammer.py error: argument -s/--system: invalid choice: '%s' (choose from '%s')" msg = "hammer.py error: argument -s/--system: invalid choice: '%s' (choose from '%s')"
msg = msg % (revision, "', '".join(SYSTEMS.keys())) msg = msg % (revision, "', '".join(SYSTEMS.keys()))
log.error(msg) log.error(msg)
sys.exit(1) sys.exit(1)
revs = SYSTEMS[system].keys() if revision not in SYSTEMS[system]:
if revision not in revs:
msg = "hammer.py error: argument -r/--revision: invalid choice: '%s' (choose from '%s')" msg = "hammer.py error: argument -r/--revision: invalid choice: '%s' (choose from '%s')"
msg = msg % (revision, "', '".join(revs)) msg = msg % (revision, "', '".join(SYSTEMS[system].keys()))
log.error(msg) log.error(msg)
sys.exit(1) sys.exit(1)
if not SYSTEMS[system][revision]: if not SYSTEMS[system][revision]:
log.warning(f'{system} ${revision} is no longer officially supported. ' log.warning('%s %s is no longer officially supported. '
'The script will continue in a best-effort manner.') 'The script will continue in a best-effort manner.', system, revision)
def _prepare_ccache_dir(ccache_dir, system, revision): def _prepare_ccache_dir(ccache_dir, system, revision):
@ -3010,7 +3001,7 @@ def prepare_system_cmd(args):
def upload_to_repo(args, pkgs_dir): def upload_to_repo(args, pkgs_dir):
# NOTE: note the differences (if any) in system/revision vs args.system/revision # NOTE: note the differences (if any) in system/revision vs args.system/revision
system, revision = get_system_revision() system, revision = get_system_revision()
repo_url = _get_full_repo_url(args.repository_url, system, revision, args.pkg_version) repo_url = _get_full_repo_url(args.repository_url, system, revision)
if repo_url is None: if repo_url is None:
raise ValueError('repo_url is None') raise ValueError('repo_url is None')
upload_cmd = 'curl -v --netrc -f' upload_cmd = 'curl -v --netrc -f'
@ -3055,7 +3046,7 @@ def upload_to_repo(args, pkgs_dir):
log.info("Asset already exists in the repository. Skipping upload.") log.info("Asset already exists in the repository. Skipping upload.")
break break
elif exitcode != 0: elif exitcode != 0:
raise Exception('Upload failed: %s' % output) raise UnexpectedError('Upload failed: %s' % output)
else: else:
break break

View File

@ -47,10 +47,10 @@ def send_to_control_agent(params):
# Issue: [B310:blacklist] Audit url open for permitted schemes. # Issue: [B310:blacklist] Audit url open for permitted schemes.
# Allowing use of file:/ or custom schemes is often unexpected. # Allowing use of file:/ or custom schemes is often unexpected.
# Reason for nosec: url is checked to be http further above. # Reason for nosec: url is checked to be http further above.
resp = urllib.request.urlopen(req, context=ssl_ctx) # nosec B310 with urllib.request.urlopen(req, context=ssl_ctx) as resp: # nosec B310
# Now get the response details, put it in CAResponse and return it
result = CAResponse(resp.getcode(), resp.reason,
resp.read().decode("utf-8"))
# Now get the response details, put it in CAResponse and return it return result
result = CAResponse(resp.getcode(), resp.reason, return None
resp.read().decode("utf-8"))
return result

View File

@ -166,8 +166,8 @@ class CARequestUnitTest(unittest.TestCase):
user = 'libert\xe9' user = 'libert\xe9'
password = '\xe9galit\xe9' password = '\xe9galit\xe9'
else: else:
user = u'libert\xe9' user = 'libert\xe9'
password = u'\xe9galit\xe9' password = '\xe9galit\xe9'
buser = user.encode('utf-8') buser = user.encode('utf-8')
bpassword = password.encode('utf-8') bpassword = password.encode('utf-8')
secret = b':'.join((buser, bpassword)) secret = b':'.join((buser, bpassword))

View File

@ -28,7 +28,7 @@ if len(sys.argv) != 3:
preproc = re.compile('^#') preproc = re.compile('^#')
constant = re.compile('^([a-zA-Z].*?[a-zA-Z_0-9]+)\\s*=.*;') constant = re.compile('^([a-zA-Z].*?[a-zA-Z_0-9]+)\\s*=.*;')
with open(filename_in) as file_in, open(filename_out, "w") as file_out: with open(filename_in, encoding='utf-8') as file_in, open(filename_out, "w", encoding='utf-8') as file_out:
file_out.write("// This file is generated from " + filename_in + "\n" + file_out.write("// This file is generated from " + filename_in + "\n" +
"// by the const2hdr.py script.\n" + "// by the const2hdr.py script.\n" +
"// Do not edit, all changes will be lost.\n\n") "// Do not edit, all changes will be lost.\n\n")

View File

@ -316,14 +316,14 @@ What you are expected to do is as follows:
examples. examples.
""" """
import argparse
import base64
import configparser import configparser
import re import re
import time
import socket import socket
import sys import sys
import base64 import time
from datetime import datetime from datetime import datetime
from optparse import OptionParser
re_hex = re.compile(r'^0x[0-9a-fA-F]+') re_hex = re.compile(r'^0x[0-9a-fA-F]+')
re_decimal = re.compile(r'^\d+$') re_decimal = re.compile(r'^\d+$')
@ -334,11 +334,9 @@ dnssec_timefmt = '%Y%m%d%H%M%S'
dict_qr = {'query': 0, 'response': 1} dict_qr = {'query': 0, 'response': 1}
dict_opcode = {'query': 0, 'iquery': 1, 'status': 2, 'notify': 4, dict_opcode = {'query': 0, 'iquery': 1, 'status': 2, 'notify': 4,
'update': 5} 'update': 5}
rdict_opcode = dict([(dict_opcode[k], k.upper()) for k in dict_opcode.keys()])
dict_rcode = {'noerror': 0, 'formerr': 1, 'servfail': 2, 'nxdomain': 3, dict_rcode = {'noerror': 0, 'formerr': 1, 'servfail': 2, 'nxdomain': 3,
'notimp': 4, 'refused': 5, 'yxdomain': 6, 'yxrrset': 7, 'notimp': 4, 'refused': 5, 'yxdomain': 6, 'yxrrset': 7,
'nxrrset': 8, 'notauth': 9, 'notzone': 10} 'nxrrset': 8, 'notauth': 9, 'notzone': 10}
rdict_rcode = dict([(dict_rcode[k], k.upper()) for k in dict_rcode.keys()])
dict_rrtype = {'none': 0, 'a': 1, 'ns': 2, 'md': 3, 'mf': 4, 'cname': 5, dict_rrtype = {'none': 0, 'a': 1, 'ns': 2, 'md': 3, 'mf': 4, 'cname': 5,
'soa': 6, 'mb': 7, 'mg': 8, 'mr': 9, 'null': 10, 'soa': 6, 'mb': 7, 'mg': 8, 'mr': 9, 'null': 10,
'wks': 11, 'ptr': 12, 'hinfo': 13, 'minfo': 14, 'mx': 15, 'wks': 11, 'ptr': 12, 'hinfo': 13, 'minfo': 14, 'mx': 15,
@ -352,21 +350,25 @@ dict_rrtype = {'none': 0, 'a': 1, 'ns': 2, 'md': 3, 'mf': 4, 'cname': 5,
'spf': 99, 'unspec': 103, 'tkey': 249, 'tsig': 250, 'spf': 99, 'unspec': 103, 'tkey': 249, 'tsig': 250,
'dlv': 32769, 'ixfr': 251, 'axfr': 252, 'mailb': 253, 'dlv': 32769, 'ixfr': 251, 'axfr': 252, 'mailb': 253,
'maila': 254, 'any': 255, 'caa': 257} 'maila': 254, 'any': 255, 'caa': 257}
rdict_rrtype = dict([(dict_rrtype[k], k.upper()) for k in dict_rrtype.keys()])
dict_rrclass = {'in': 1, 'ch': 3, 'hs': 4, 'any': 255} dict_rrclass = {'in': 1, 'ch': 3, 'hs': 4, 'any': 255}
rdict_rrclass = dict([(dict_rrclass[k], k.upper()) for k in dict_rrclass.keys()]) dict_algorithm = {'rsamd5': 1, 'dh': 2, 'dsa': 3, 'ecc': 4, 'rsasha1': 5}
dict_algorithm = {'rsamd5': 1, 'dh': 2, 'dsa': 3, 'ecc': 4,
'rsasha1': 5}
dict_nsec3_algorithm = {'reserved': 0, 'sha1': 1} dict_nsec3_algorithm = {'reserved': 0, 'sha1': 1}
rdict_algorithm = dict([(dict_algorithm[k], k.upper()) for k in dict_algorithm.keys()])
rdict_nsec3_algorithm = dict([(dict_nsec3_algorithm[k], k.upper()) for k in dict_nsec3_algorithm.keys()]) rdict_opcode = {k.upper(): v for k, v in dict_opcode.items()}
rdict_rcode = {k.upper(): v for k, v in dict_rcode.items()}
rdict_rrtype = {k.upper(): v for k, v in dict_rrtype.items()}
rdict_rrclass = {k.upper(): v for k, v in dict_rrclass.items()}
rdict_algorithm = {k.upper(): v for k, v in dict_algorithm.items()}
rdict_nsec3_algorithm = {k.upper(): v for k, v in dict_nsec3_algorithm.items()}
header_xtables = {'qr': dict_qr, 'opcode': dict_opcode, header_xtables = {'qr': dict_qr, 'opcode': dict_opcode,
'rcode': dict_rcode} 'rcode': dict_rcode}
question_xtables = {'rrtype': dict_rrtype, 'rrclass': dict_rrclass} question_xtables = {'rrtype': dict_rrtype, 'rrclass': dict_rrclass}
def parse_value(value, xtable={}): def parse_value(value, xtable=None):
if xtable is None:
xtable = {}
if re.search(re_hex, value): if re.search(re_hex, value):
return int(value, 16) return int(value, 16)
if re.search(re_decimal, value): if re.search(re_decimal, value):
@ -380,9 +382,9 @@ def parse_value(value, xtable={}):
return value return value
def code_totext(code, dict): def code_totext(code, dictionary):
if code in dict.keys(): if code in dictionary:
return dict[code] + '(' + str(code) + ')' return dictionary[code] + '(' + str(code) + ')'
return str(code) return str(code)
@ -395,7 +397,7 @@ def encode_name(name, absolute=True):
for label in labels: for label in labels:
if len(label) > 4 and label[0:4] == 'ptr=': if len(label) > 4 and label[0:4] == 'ptr=':
# special meta-syntax for compression pointer # special meta-syntax for compression pointer
wire += '%04x' % (0xc000 | int(l[4:])) wire += '%04x' % (0xc000 | int(label[4:]))
break break
if absolute or len(label) > 0: if absolute or len(label) > 0:
wire += '%02x' % len(label) wire += '%02x' % len(label)
@ -405,15 +407,15 @@ def encode_name(name, absolute=True):
return wire return wire
def encode_string(name, len=None): def encode_string(name, length=None):
if type(name) is int and len is not None: if isinstance(name, int) and length is not None:
return '%0.*x' % (len * 2, name) return '%0.*x' % (length * 2, name)
return ''.join(['%02x' % ord(ch) for ch in name]) return ''.join(['%02x' % ord(ch) for ch in name])
def encode_bytes(name, len=None): def encode_bytes(name, length=None):
if type(name) is int and len is not None: if isinstance(name, int) and length is not None:
return '%0.*x' % (len * 2, name) return '%0.*x' % (length * 2, name)
return ''.join(['%02x' % ch for ch in name]) return ''.join(['%02x' % ch for ch in name])
@ -426,11 +428,13 @@ def count_namelabels(name):
return len(name.split('.')) return len(name.split('.'))
def get_config(config, section, configobj, xtables={}): def get_config(config, section, configobj, xtables=None):
if xtables is None:
xtables = {}
try: try:
for field in config.options(section): for field in config.options(section):
value = config.get(section, field) value = config.get(section, field)
if field in xtables.keys(): if field in xtables:
xtable = xtables[field] xtable = xtables[field]
else: else:
xtable = {} xtable = {}
@ -704,7 +708,8 @@ class AAAA(RR):
self.dump_header(f, self.rdlen) self.dump_header(f, self.rdlen)
f.write('# Address=%s\n' % (self.address)) f.write('# Address=%s\n' % (self.address))
bin_address = socket.inet_pton(socket.AF_INET6, self.address) bin_address = socket.inet_pton(socket.AF_INET6, self.address)
[f.write('%02x' % x) for x in bin_address] for x in bin_address:
f.write('%02x' % x)
f.write('\n') f.write('\n')
@ -959,6 +964,7 @@ class NSECBASE(RR):
block = 0 block = 0
maplen = None # default bitmap length, auto-calculate maplen = None # default bitmap length, auto-calculate
bitmap = '040000000003' # an arbitrarily chosen bitmap sample bitmap = '040000000003' # an arbitrarily chosen bitmap sample
nextname = None
def dump(self, f): def dump(self, f):
# first, construct the bitmap data # first, construct the bitmap data
@ -994,6 +1000,17 @@ class NSECBASE(RR):
f.write('%02x %02x %s\n' % f.write('%02x %02x %s\n' %
(block_list[i], maplen_list[i], bitmap_list[i])) (block_list[i], maplen_list[i], bitmap_list[i]))
def dump_fixedpart(self, f, bitmap_totallen):
name_wire = encode_name(self.nextname)
if self.rdlen is None:
# if rdlen needs to be calculated, it must be based on the bitmap
# length, because the configured maplen can be fake.
self.rdlen = int(len(name_wire) / 2) + bitmap_totallen
self.dump_header(f, self.rdlen)
f.write('# Next Name=%s (%d bytes)\n' % (self.nextname,
int(len(name_wire) / 2)))
f.write('%s\n' % name_wire)
class NSEC(NSECBASE): class NSEC(NSECBASE):
'''Implements rendering NSEC RDATA in the test data format. '''Implements rendering NSEC RDATA in the test data format.
@ -1007,17 +1024,6 @@ class NSEC(NSECBASE):
nextname = 'next.example.com' nextname = 'next.example.com'
def dump_fixedpart(self, f, bitmap_totallen):
name_wire = encode_name(self.nextname)
if self.rdlen is None:
# if rdlen needs to be calculated, it must be based on the bitmap
# length, because the configured maplen can be fake.
self.rdlen = int(len(name_wire) / 2) + bitmap_totallen
self.dump_header(f, self.rdlen)
f.write('# Next Name=%s (%d bytes)\n' % (self.nextname,
int(len(name_wire) / 2)))
f.write('%s\n' % name_wire)
class NSEC3PARAM(RR): class NSEC3PARAM(RR):
'''Implements rendering NSEC3PARAM RDATA in the test data format. '''Implements rendering NSEC3PARAM RDATA in the test data format.
@ -1146,9 +1152,9 @@ class RRSIG(RR):
self.rdlen = int(18 + len(name_wire) / 2 + len(str(sig_wire)) / 2) self.rdlen = int(18 + len(name_wire) / 2 + len(str(sig_wire)) / 2)
self.dump_header(f, self.rdlen) self.dump_header(f, self.rdlen)
if type(self.covered) is str: if isinstance(self.covered, str):
self.covered = dict_rrtype[self.covered.lower()] self.covered = dict_rrtype[self.covered.lower()]
if type(self.algorithm) is str: if isinstance(self.algorithm, str):
self.algorithm = dict_algorithm[self.algorithm.lower()] self.algorithm = dict_algorithm[self.algorithm.lower()]
if self.labels is None: if self.labels is None:
self.labels = count_namelabels(self.signer) self.labels = count_namelabels(self.signer)
@ -1345,7 +1351,7 @@ class TSIG(RR):
name_wire = encode_name(self.algorithm) name_wire = encode_name(self.algorithm)
mac_size = self.mac_size mac_size = self.mac_size
if mac_size is None: if mac_size is None:
if self.algorithm in self.dict_macsize.keys(): if self.algorithm in self.dict_macsize:
mac_size = self.dict_macsize[self.algorithm] mac_size = self.dict_macsize[self.algorithm]
else: else:
raise RuntimeError('TSIG Mac Size cannot be determined') raise RuntimeError('TSIG Mac Size cannot be determined')
@ -1426,7 +1432,7 @@ config_param = {'name': (Name, {}),
'header': (DNSHeader, header_xtables), 'header': (DNSHeader, header_xtables),
'question': (DNSQuestion, question_xtables), 'question': (DNSQuestion, question_xtables),
'edns': (EDNS, {})} 'edns': (EDNS, {})}
for rrtype in dict_rrtype.keys(): for rrtype in dict_rrtype:
# For any supported RR types add the tuple of (RR_CLASS, {}). # For any supported RR types add the tuple of (RR_CLASS, {}).
# We expect KeyError as not all the types are supported, and simply # We expect KeyError as not all the types are supported, and simply
# ignore them. # ignore them.
@ -1448,18 +1454,18 @@ def get_config_param(section):
usage = 'usage: %prog [options] input_file' usage = 'usage: %prog [options] input_file'
if __name__ == "__main__": def main():
parser = OptionParser(usage=usage) parser = argparse.ArgumentParser(usage=usage)
parser.add_option('-o', '--output', action='store', dest='output', parser.add_argument('-o', '--output', action='store', dest='output',
default=None, metavar='FILE', default=None, metavar='FILE',
help='output file name [default: prefix of input_file]') help='output file name [default: prefix of input_file]')
(options, args) = parser.parse_args() args = parser.parse_args()
if len(args) == 0: if len(args) == 0:
parser.error('input file is missing') parser.error('input file is missing')
configfile = args[0] configfile = args[0]
outputfile = options.output outputfile = args.output
if not outputfile: if not outputfile:
m = re.match(r'(.*)\.[^.]+$', configfile) m = re.match(r'(.*)\.[^.]+$', configfile)
if m: if m:
@ -1468,12 +1474,12 @@ if __name__ == "__main__":
raise ValueError('output file is not specified and input file is not in the form of "output_file.suffix"') raise ValueError('output file is not specified and input file is not in the form of "output_file.suffix"')
# DeprecationWarning: use ConfigParser directly # DeprecationWarning: use ConfigParser directly
config = configparser.SafeConfigParser() config = configparser.SafeConfigParser() # pylint: disable=deprecated-class
config.read(configfile) config.read(configfile)
output = open(outputfile, 'w') output = open(outputfile, 'w', encoding='utf-8') # pylint: disable=consider-using-with
print_header(output, configfile) print_header(outputfile, configfile)
# First try the 'custom' mode; if it fails assume the query mode. # First try the 'custom' mode; if it fails assume the query mode.
try: try:
@ -1488,3 +1494,7 @@ if __name__ == "__main__":
obj.dump(output) obj.dump(output)
output.close() output.close()
if __name__ == "__main__":
main()

View File

@ -305,9 +305,9 @@ def main(parameters):
# Only print if we have something to print to avoid a newline. # Only print if we have something to print to avoid a newline.
# Also don't clutter output with lines that doesn't cause CI failure if # Also don't clutter output with lines that doesn't cause CI failure if
# there are lines that cause CI failure. # there are lines that cause CI failure.
if len(output_for_latest): if len(output_for_latest) > 0:
print(output_for_latest) print(output_for_latest)
elif len(output_for_other_than_latest): elif len(output_for_other_than_latest) > 0:
print(output_for_other_than_latest) print(output_for_other_than_latest)
# Only report errors on the latest upgrade script. For all other upgrade # Only report errors on the latest upgrade script. For all other upgrade

View File

@ -15,7 +15,7 @@
# git pull # git pull
# git remote prune origin # git remote prune origin
# #
# This script requires python 2.7 or 3. # This script requires python 3.
# #
# I have limited experience in Python. If things are done in a strange or # I have limited experience in Python. If things are done in a strange or
# uncommon way, there are no obscure reasons to do it that way, just plain # uncommon way, there are no obscure reasons to do it that way, just plain
@ -23,9 +23,7 @@
# #
# tomek # tomek
import string import argparse
import sys
from optparse import OptionParser
# [B404:blacklist] Consider possible security implications associated with subprocess module. # [B404:blacklist] Consider possible security implications associated with subprocess module.
import subprocess # nosec B404 import subprocess # nosec B404
@ -157,39 +155,31 @@ def check_output(cmd):
return subprocess.check_output(cmd) # nosec B603 return subprocess.check_output(cmd) # nosec B603
def parse_args(args=sys.argv[1:], Parser=OptionParser): def parse_args():
parser = argparse.ArgumentParser(
description="This script prints out merged and/or unmerged branches of a GIT tree.",
usage="""%prog
Lists all obsolete (fully merged into master) branches.
""")
parser = Parser(description="This script prints out merged and/or unmerged" parser.add_argument("-c", "--csv", action="store_true",
" branches of a GIT tree.") default=False, help="generates CSV output")
parser.add_argument("-u", "--unmerged", action="store_true",
default=False, help="lists unmerged branches")
parser.add_argument("-m", "--skip-merged", action="store_true",
default=False, help="omits listing merged branches")
parser.add_argument("-s", "--stats", action="store_true",
default=False, help="prints also statistics")
parser.add_option("-c", "--csv", action="store_true", return parser.parse_args()
default=False, help="generates CSV output")
parser.add_option("-u", "--unmerged", action="store_true",
default=False, help="lists unmerged branches")
parser.add_option("-m", "--skip-merged", action="store_true",
default=False, help="omits listing merged branches")
parser.add_option("-s", "--stats", action="store_true",
default=False, help="prints also statistics")
(options, args) = parser.parse_args(args)
if args:
parser.print_help()
sys.exit(1)
return options
def main(): def main():
usage = """%prog args = parse_args()
Lists all obsolete (fully merged into master) branches. csv = args.csv
""" merged = not args.skip_merged
unmerged = args.unmerged
options = parse_args() stats = args.stats
csv = options.csv
merged = not options.skip_merged
unmerged = options.unmerged
stats = options.stats
if csv: if csv:
print("branch name,status,date,last commit(mail),last commit(name)") print("branch name,status,date,last commit(mail),last commit(name)")

View File

@ -1,14 +1,14 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
from termcolor import colored, cprint
from io import StringIO
import json import json
import os import os
import re import re
import sqlalchemy as db
from sqlalchemy.sql import select
import sys import sys
from io import StringIO
import sqlalchemy as db # pylint: disable=import-error
from termcolor import cprint # pylint: disable=import-error
def convert_to_db(entity_name, make_singular=True): def convert_to_db(entity_name, make_singular=True):
@ -86,6 +86,7 @@ class State:
class ConfigFile: class ConfigFile:
def __init__(self, filename): def __init__(self, filename):
self.config = {}
self.filename = filename self.filename = filename
def load(self): def load(self):
@ -93,7 +94,7 @@ class ConfigFile:
print('The all keys file %s does not exist.' % self.filename) print('The all keys file %s does not exist.' % self.filename)
sys.exit(1) sys.exit(1)
with open(self.filename) as f: with open(self.filename, encoding='utf-8') as f:
self.config = json.load(f) self.config = json.load(f)
f.close() f.close()
@ -232,17 +233,17 @@ def main():
sys.exit(1) sys.exit(1)
sanitized_contents = '' sanitized_contents = ''
f = open(args.all_keys_file) with open(args.all_keys_file, encoding='utf-8') as f:
for line in f: for line in f:
sanitized_line = line.strip() sanitized_line = line.strip()
if not sanitized_line: if not sanitized_line:
continue continue
if sanitized_line.find('//') != -1 or sanitized_line.find('#') != -1: if sanitized_line.find('//') != -1 or sanitized_line.find('#') != -1:
continue continue
sanitized_line = sanitized_line.replace(': .', ': 0.') sanitized_line = sanitized_line.replace(': .', ': 0.')
sanitized_contents = sanitized_contents + sanitized_line sanitized_contents = sanitized_contents + sanitized_line
f.close() f.close()

View File

@ -160,7 +160,8 @@ def process_file(filename):
Parameters: Parameters:
filename Name of the message file to process filename Name of the message file to process
""" """
lines = open(filename).read().splitlines() with open(filename, encoding='utf-8') as f:
lines = f.read().splitlines()
# Search for the first line starting with the percent character. Everything # Search for the first line starting with the percent character. Everything
# before it is considered the file header and is copied to the output with # before it is considered the file header and is copied to the output with