|
|
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import errno
|
|
|
|
|
import fcntl
|
|
|
|
|
import hashlib
|
|
|
|
|
import inspect
|
|
|
|
|
import os
|
|
|
|
|
import pkgutil
|
|
|
|
@ -31,7 +30,6 @@ from .encoding import (
|
|
|
|
|
|
|
|
|
|
from .io import (
|
|
|
|
|
open_binary_file,
|
|
|
|
|
read_binary_file,
|
|
|
|
|
read_text_file,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@ -163,13 +161,11 @@ def exclude_none_values(data): # type: (t.Dict[TKey, t.Optional[TValue]]) -> t.
|
|
|
|
|
return dict((key, value) for key, value in data.items() if value is not None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_executable(executable, cwd=None, path=None, required=True):
|
|
|
|
|
def find_executable(executable, cwd=None, path=None, required=True): # type: (str, t.Optional[str], t.Optional[str], t.Union[bool, str]) -> t.Optional[str]
|
|
|
|
|
"""
|
|
|
|
|
:type executable: str
|
|
|
|
|
:type cwd: str
|
|
|
|
|
:type path: str
|
|
|
|
|
:type required: bool | str
|
|
|
|
|
:rtype: str | None
|
|
|
|
|
Find the specified executable and return the full path, or None if it could not be found.
|
|
|
|
|
If required is True an exception will be raised if the executable is not found.
|
|
|
|
|
If required is set to 'warning' then a warning will be shown if the executable is not found.
|
|
|
|
|
"""
|
|
|
|
|
match = None
|
|
|
|
|
real_cwd = os.getcwd()
|
|
|
|
@ -215,12 +211,11 @@ def find_executable(executable, cwd=None, path=None, required=True):
|
|
|
|
|
return match
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def find_python(version, path=None, required=True):
|
|
|
|
|
def find_python(version, path=None, required=True): # type: (str, t.Optional[str], bool) -> t.Optional[str]
|
|
|
|
|
"""
|
|
|
|
|
:type version: str
|
|
|
|
|
:type path: str | None
|
|
|
|
|
:type required: bool
|
|
|
|
|
:rtype: str
|
|
|
|
|
Find and return the full path to the specified Python version.
|
|
|
|
|
If required, an exception will be raised not found.
|
|
|
|
|
If not required, None will be returned if not found.
|
|
|
|
|
"""
|
|
|
|
|
version_info = str_to_version(version)
|
|
|
|
|
|
|
|
|
@ -415,10 +410,8 @@ def pass_vars(required, optional):
|
|
|
|
|
return env
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def remove_tree(path):
|
|
|
|
|
"""
|
|
|
|
|
:type path: str
|
|
|
|
|
"""
|
|
|
|
|
def remove_tree(path): # type: (str) -> None
|
|
|
|
|
"""Remove the specified directory, siliently continuing if the directory does not exist."""
|
|
|
|
|
try:
|
|
|
|
|
shutil.rmtree(to_bytes(path))
|
|
|
|
|
except OSError as ex:
|
|
|
|
@ -426,11 +419,8 @@ def remove_tree(path):
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_binary_file(path):
|
|
|
|
|
"""
|
|
|
|
|
:type path: str
|
|
|
|
|
:rtype: bool
|
|
|
|
|
"""
|
|
|
|
|
def is_binary_file(path): # type: (str) -> bool
|
|
|
|
|
"""Return True if the specified file is a binary file, otherwise return False."""
|
|
|
|
|
assume_text = {
|
|
|
|
|
'.cfg',
|
|
|
|
|
'.conf',
|
|
|
|
@ -491,10 +481,8 @@ def generate_name(length=8): # type: (int) -> str
|
|
|
|
|
return ''.join(random.choice(string.ascii_letters + string.digits) for _idx in range(length))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def generate_password():
|
|
|
|
|
"""Generate a random password.
|
|
|
|
|
:rtype: str
|
|
|
|
|
"""
|
|
|
|
|
def generate_password(): # type: () -> str
|
|
|
|
|
"""Generate and return random password."""
|
|
|
|
|
chars = [
|
|
|
|
|
string.ascii_letters,
|
|
|
|
|
string.digits,
|
|
|
|
@ -542,13 +530,11 @@ class Display:
|
|
|
|
|
if os.isatty(0):
|
|
|
|
|
self.rows, self.columns = unpack('HHHH', fcntl.ioctl(0, TIOCGWINSZ, pack('HHHH', 0, 0, 0, 0)))[:2]
|
|
|
|
|
|
|
|
|
|
def __warning(self, message):
|
|
|
|
|
"""
|
|
|
|
|
:type message: str
|
|
|
|
|
"""
|
|
|
|
|
def __warning(self, message): # type: (str) -> None
|
|
|
|
|
"""Internal implementation for displaying a warning message."""
|
|
|
|
|
self.print_message('WARNING: %s' % message, color=self.purple, fd=sys.stderr)
|
|
|
|
|
|
|
|
|
|
def review_warnings(self):
|
|
|
|
|
def review_warnings(self): # type: () -> None
|
|
|
|
|
"""Review all warnings which previously occurred."""
|
|
|
|
|
if not self.warnings:
|
|
|
|
|
return
|
|
|
|
@ -558,12 +544,8 @@ class Display:
|
|
|
|
|
for warning in self.warnings:
|
|
|
|
|
self.__warning(warning)
|
|
|
|
|
|
|
|
|
|
def warning(self, message, unique=False, verbosity=0):
|
|
|
|
|
"""
|
|
|
|
|
:type message: str
|
|
|
|
|
:type unique: bool
|
|
|
|
|
:type verbosity: int
|
|
|
|
|
"""
|
|
|
|
|
def warning(self, message, unique=False, verbosity=0): # type: (str, bool, int) -> None
|
|
|
|
|
"""Display a warning level message."""
|
|
|
|
|
if verbosity > self.verbosity:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
@ -576,24 +558,16 @@ class Display:
|
|
|
|
|
self.__warning(message)
|
|
|
|
|
self.warnings.append(message)
|
|
|
|
|
|
|
|
|
|
def notice(self, message):
|
|
|
|
|
"""
|
|
|
|
|
:type message: str
|
|
|
|
|
"""
|
|
|
|
|
def notice(self, message): # type: (str) -> None
|
|
|
|
|
"""Display a notice level message."""
|
|
|
|
|
self.print_message('NOTICE: %s' % message, color=self.purple, fd=sys.stderr)
|
|
|
|
|
|
|
|
|
|
def error(self, message):
|
|
|
|
|
"""
|
|
|
|
|
:type message: str
|
|
|
|
|
"""
|
|
|
|
|
def error(self, message): # type: (str) -> None
|
|
|
|
|
"""Display an error level message."""
|
|
|
|
|
self.print_message('ERROR: %s' % message, color=self.red, fd=sys.stderr)
|
|
|
|
|
|
|
|
|
|
def info(self, message, verbosity=0, truncate=False):
|
|
|
|
|
"""
|
|
|
|
|
:type message: str
|
|
|
|
|
:type verbosity: int
|
|
|
|
|
:type truncate: bool
|
|
|
|
|
"""
|
|
|
|
|
def info(self, message, verbosity=0, truncate=False): # type: (str, int, bool) -> None
|
|
|
|
|
"""Display an info level message."""
|
|
|
|
|
if self.verbosity >= verbosity:
|
|
|
|
|
color = self.verbosity_colors.get(verbosity, self.yellow)
|
|
|
|
|
self.print_message(message, color=color, fd=sys.stderr if self.info_stderr else sys.stdout, truncate=truncate)
|
|
|
|
@ -674,10 +648,7 @@ class SubprocessError(ApplicationError):
|
|
|
|
|
|
|
|
|
|
class MissingEnvironmentVariable(ApplicationError):
|
|
|
|
|
"""Error caused by missing environment variable."""
|
|
|
|
|
def __init__(self, name):
|
|
|
|
|
"""
|
|
|
|
|
:type name: str
|
|
|
|
|
"""
|
|
|
|
|
def __init__(self, name): # type: (str) -> None
|
|
|
|
|
super().__init__('Missing environment variable: %s' % name)
|
|
|
|
|
|
|
|
|
|
self.name = name
|
|
|
|
@ -694,12 +665,8 @@ def retry(func, ex_type=SubprocessError, sleep=10, attempts=10):
|
|
|
|
|
return func()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_to_list_of_dict(pattern, value):
|
|
|
|
|
"""
|
|
|
|
|
:type pattern: str
|
|
|
|
|
:type value: str
|
|
|
|
|
:return: list[dict[str, str]]
|
|
|
|
|
"""
|
|
|
|
|
def parse_to_list_of_dict(pattern, value): # type: (str, str) -> t.List[t.Dict[str, str]]
|
|
|
|
|
"""Parse lines from the given value using the specified pattern and return the extracted list of key/value pair dictionaries."""
|
|
|
|
|
matched = []
|
|
|
|
|
unmatched = []
|
|
|
|
|
|
|
|
|
@ -833,21 +800,6 @@ def sanitize_host_name(name):
|
|
|
|
|
return re.sub('[^A-Za-z0-9]+', '-', name)[:63].strip('-')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_hash(path):
|
|
|
|
|
"""
|
|
|
|
|
:type path: str
|
|
|
|
|
:rtype: str | None
|
|
|
|
|
"""
|
|
|
|
|
if not os.path.exists(path):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
file_hash = hashlib.sha256()
|
|
|
|
|
|
|
|
|
|
file_hash.update(read_binary_file(path))
|
|
|
|
|
|
|
|
|
|
return file_hash.hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@cache
|
|
|
|
|
def get_host_ip():
|
|
|
|
|
"""Return the host's IP address."""
|
|
|
|
|