diff --git a/.travis.yml b/.travis.yml index bb606560..60968577 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ sudo: false env: - MYPYPATH="$TRAVIS_BUILD_DIR/.travis/mypy-stubs" install: - - pip3 install flake8 mypy + - pip3 install flake8-mutable flake8-builtins flake8-mypy script: - - ".travis/flake8.sh" - - mypy iocage + - flake8 --exclude=".*" --exclude=__init__.py --ignore=E203,W391 diff --git a/.travis/flake8.sh b/.travis/flake8.sh deleted file mode 100755 index 5fd1f826..00000000 --- a/.travis/flake8.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -# Run pep8 on all .py files in all subfolders - -tmpafter=$(mktemp) -find ./libiocage/ -name \*.py -exec flake8 --ignore=E203,W391 {} + | grep -v "./libiocage/__init__.py" > ${tmpafter} -num_errors_after=`cat ${tmpafter} | wc -l` -echo "Current Error Count: ${num_errors_after}" -echo "Current Errors:" -cat "${tmpafter}" - -# Get new tags from remote -git fetch --tags --quiet -# Get latest tag name -last_release=$(git describe --tags `git rev-list --tags --max-count=1`) - -echo "Comparing with last stable release: ${last_release}" -git checkout ${last_release} - -tmpbefore=$(mktemp) -find ./libiocage/ -name \*.py -exec flake8 --ignore=E203,W391 {} + | grep -v "./libiocage/__init__.py" > ${tmpbefore} -num_errors_before=`cat ${tmpbefore} | wc -l` -echo "${last_release}'s Error Count: ${num_errors_before}" - -# restore whichever branch we were on previously -git checkout - - -# The number may be lower then the last release, but that doesn't tell them that they're not higher than they should be. -num_errors_adjusted=$((num_errors_before-num_errors_after)) - -if [ ${num_errors_adjusted} != 0 ] && [ ${num_errors_adjusted} != ${num_errors_before} ]; then - echo "New Flake8 errors were introduced:" - diff -u ${tmpbefore} ${tmpafter} - exit 1 -fi diff --git a/.travis/mypy-stubs/libzfs.pyi b/.travis/mypy-stubs/libzfs.pyi index 97493fd2..a68f279b 100644 --- a/.travis/mypy-stubs/libzfs.pyi +++ b/.travis/mypy-stubs/libzfs.pyi @@ -3,7 +3,7 @@ # NOTE: This dynamically typed stub was automatically generated by stubgen. from datetime import date -from typing import Any +from typing import Any, List, Dict, Optional DatasetType = ... # type: Any DiffFileType = ... # type: Any @@ -54,21 +54,21 @@ class ZFS: datasets = ... # type: Any errno = ... # type: Any errstr = ... # type: Any - pools = ... # type: Any + pools: List[ZFSPool] = ... snapshots = ... # type: Any __pyx_vtable__ = ... # type: Any - def __init__(self, *args, **kwargs): ... + def __init__(self, *args, **kwargs) -> None: ... def create(self, *args, **kwargs): ... def describe_resume_token(self, *args, **kwargs): ... - def destroy(self, *args, **kwargs): ... + def destroy(self, name: str) -> None: ... def export_pool(self, *args, **kwargs): ... def find_import(self, *args, **kwargs): ... def generate_history_opts(self, *args, **kwargs): ... def get(self, *args, **kwargs): ... - def get_dataset(self, *args, **kwargs): ... - def get_dataset_by_path(self, *args, **kwargs): ... + def get_dataset(self, name: str) -> ZFSDataset: ... + def get_dataset_by_path(self, *args, **kwargs) -> ZFSDataset: ... def get_object(self, *args, **kwargs): ... - def get_snapshot(self, *args, **kwargs): ... + def get_snapshot(self, name: str) -> ZFSSnapshot: ... def history_vdevs_list(self, *args, **kwargs): ... def import_pool(self, *args, **kwargs): ... def receive(self, *args, **kwargs): ... @@ -88,23 +88,28 @@ class ZFSBookmark(ZFSObject): class ZFSDataset(ZFSObject): bookmarks = ... # type: Any - children = ... # type: Any + children: List[ZFSDataset] = ... children_recursive = ... # type: Any dependents = ... # type: Any - mountpoint = ... # type: Any - snapshots = ... # type: Any + mountpoint: str = ... + snapshots: List[ZFSSnapshot]= ... snapshots_recursive = ... # type: Any __pyx_vtable__ = ... # type: Any def __init__(self, *args, **kwargs): ... def destroy_snapshot(self, *args, **kwargs): ... def diff(self, *args, **kwargs): ... def get_send_progress(self, *args, **kwargs): ... - def mount(self, *args, **kwargs): ... - def mount_recursive(self, *args, **kwargs): ... + def mount(self, *args, **kwargs) -> None: ... + def mount_recursive(self, *args, **kwargs) -> None: ... def promote(self, *args, **kwargs): ... def receive(self, *args, **kwargs): ... def send(self, *args, **kwargs): ... - def snapshot(self, *args, **kwargs): ... + def snapshot( + self, + name: str, + fsopts: Optional[Dict[str, str]], + recursive: bool=False + ) -> None: ... def umount(self, *args, **kwargs): ... def umount_recursive(self, *args, **kwargs): ... def __getstate__(self): ... @@ -167,8 +172,8 @@ class ZFSPool: def __init__(self, *args, **kwargs): ... def attach_vdevs(self, *args, **kwargs): ... def clear(self, *args, **kwargs): ... - def create(self, *args, **kwargs): ... - def delete(self, *args, **kwargs): ... + def create(self, *args, **kwargs) -> None: ... + def delete(self, *args, **kwargs) -> None: ... def start_scrub(self, *args, **kwargs): ... def stop_scrub(self, *args, **kwargs): ... def upgrade(self, *args, **kwargs): ... @@ -193,14 +198,14 @@ class ZFSProperty: def __setstate_cython__(self, *args, **kwargs): ... class ZFSPropertyDict(dict): - def __init__(self, *args, **kwargs): ... - def get(self, *args, **kwargs): ... + def __init__(self, *args, **kwargs) -> None: ... + def get(self, *args, **kwargs) -> ZFSProperty: ... def has_key(self, *args, **kwargs): ... def items(self, *args, **kwargs): ... def iterkeys(self, *args, **kwargs): ... def itervalues(self, *args, **kwargs): ... def keys(self, *args, **kwargs): ... - def refresh(self, *args, **kwargs): ... + def refresh(self) -> None: ... def setdefault(self, *args, **kwargs): ... def update(self, *args, **kwargs): ... def values(self, *args, **kwargs): ... @@ -214,16 +219,16 @@ class ZFSPropertyDict(dict): class ZFSSnapshot(ZFSObject): holds = ... # type: Any - mountpoint = ... # type: Any + mountpoint: str = ... parent = ... # type: Any - snapshot_name = ... # type: Any - def __init__(self, *args, **kwargs): ... + snapshot_name: str = ... + def __init__(self, *args, **kwargs) -> None: ... def bookmark(self, *args, **kwargs): ... - def clone(self, *args, **kwargs): ... + def clone(self, name: str, opts: Optional[Dict[str, str]]) -> None: ... def delete(self, *args, **kwargs): ... def hold(self, *args, **kwargs): ... def release(self, *args, **kwargs): ... - def rollback(self, *args, **kwargs): ... + def rollback(self, force: bool=False) -> None: ... def send(self, *args, **kwargs): ... def __getstate__(self): ... def __reduce_cython__(self, *args, **kwargs): ... @@ -234,7 +239,7 @@ class ZFSUserProperty(ZFSProperty): rawvalue = ... # type: Any source = ... # type: Any value = ... # type: Any - def __init__(self, *args, **kwargs): ... + def __init__(self, *args, **kwargs) -> None: ... def __reduce_cython__(self, *args, **kwargs): ... def __setstate_cython__(self, *args, **kwargs): ... diff --git a/.travis/mypy-stubs/texttable.pyi b/.travis/mypy-stubs/texttable.pyi index ea6417e8..cc321e4c 100644 --- a/.travis/mypy-stubs/texttable.pyi +++ b/.travis/mypy-stubs/texttable.pyi @@ -2,7 +2,7 @@ # # NOTE: This dynamically typed stub was automatically generated by stubgen. -from typing import Any +from typing import Any, List class ArraySizeError(Exception): msg = ... # type: Any @@ -15,14 +15,18 @@ class Texttable: VLINES = ... # type: Any def __init__(self, max_width: int = ...) -> None: ... def reset(self): ... - def set_chars(self, array): ... + def set_chars(self, array: List): ... def set_deco(self, deco): ... - def set_cols_align(self, array): ... - def set_cols_valign(self, array): ... - def set_cols_dtype(self, array): ... - def set_cols_width(self, array): ... + def set_cols_align(self, array: List): ... + def set_cols_valign(self, array: List): ... + def set_cols_dtype(self, array: List): ... + def set_cols_width(self, array: List): ... def set_precision(self, width): ... - def header(self, array): ... - def add_row(self, array): ... - def add_rows(self, rows, header: bool = ...): ... - def draw(self): ... + def header(self, array: List): ... + def add_row(self, array: List): ... + def add_rows( + self, + rows: List[List[str]], + header: bool = ... + ): ... + def draw(self) -> str: ... diff --git a/.travis/mypy-stubs/ucl.pyi b/.travis/mypy-stubs/ucl.pyi index ff60ac57..7975c840 100644 --- a/.travis/mypy-stubs/ucl.pyi +++ b/.travis/mypy-stubs/ucl.pyi @@ -8,6 +8,6 @@ UCL_EMIT_JSON_COMPACT = ... # type: int UCL_EMIT_MSGPACK = ... # type: int UCL_EMIT_YAML = ... # type: int -def dump(*args, **kwargs): ... -def load(*args, **kwargs): ... +def dump(*args, **kwargs) -> str: ... +def load(*args, **kwargs) -> dict: ... def validate(*args, **kwargs): ... diff --git a/__main__.py b/__main__.py index 9a51e8bf..9f88a09a 100644 --- a/__main__.py +++ b/__main__.py @@ -22,40 +22,36 @@ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """The main CLI for ioc.""" -import locale import os -import re -import signal -import subprocess as su import sys -import click from iocage.cli import cli def main_safe(): - try: - main() - except BaseException as e: - return e + try: + main() + except BaseException as e: + return e def main(): - cli(prog_name="iocage") + cli(prog_name="iocage") if __name__ == "__main__": - coverdir = os.environ.get("IOCAGE_TRACE", None) - if coverdir is None: - main() - else: - import trace - tracer = trace.Trace( - ignoredirs=[sys.prefix, sys.exec_prefix], - trace=0, - count=1) - tracer.run("main_safe()") - r = tracer.results() - r.write_results(show_missing=True, coverdir=coverdir) - print(f"Iocage Trace written to: {coverdir}") + coverdir = os.environ.get("IOCAGE_TRACE", None) + if coverdir is None: + main() + else: + import trace + tracer = trace.Trace( + ignoredirs=[sys.prefix, sys.exec_prefix], + trace=0, + count=1 + ) + tracer.run("main_safe()") + r = tracer.results() + r.write_results(show_missing=True, coverdir=coverdir) + print(f"Iocage Trace written to: {coverdir}") diff --git a/iocage/__init__.py b/iocage/__init__.py index 5004aa79..e186f0f7 100644 --- a/iocage/__init__.py +++ b/iocage/__init__.py @@ -21,10 +21,10 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -from iocage.lib import errors, events -from iocage.lib.Host import Host -from iocage.lib.Jail import Jail -from iocage.lib.Jails import Jails -from iocage.lib.Logger import Logger -from iocage.lib.Release import Release -from iocage.lib.Releases import Releases +from iocage.lib import errors, events # noqa: F401 +from iocage.lib.Host import Host # noqa: F401 +from iocage.lib.Jail import Jail # noqa: F401 +from iocage.lib.Jails import Jails # noqa: F401 +from iocage.lib.Logger import Logger # noqa: F401 +from iocage.lib.Release import Release # noqa: F401 +from iocage.lib.Releases import Releases # noqa: F401 diff --git a/iocage/cli/create.py b/iocage/cli/create.py index 4a8cfd9a..fd6b1099 100644 --- a/iocage/cli/create.py +++ b/iocage/cli/create.py @@ -144,7 +144,7 @@ def cli(ctx, release, template, count, props, pkglist, basejail, basejail_type, errors = False for i in range(count): - jail = iocage.lib.Jail.Jail( + jail = iocage.lib.Jail.JailGenerator( jail_data, logger=logger, host=host, @@ -158,6 +158,7 @@ def cli(ctx, release, template, count, props, pkglist, basejail, basejail_type, msg = f"{jail.humanreadable_name} successfully created!{suffix}" logger.log(msg) except: + raise msg = f"{jail.humanreadable_name} could not be created!{suffix}" logger.warn(msg) diff --git a/iocage/cli/list.py b/iocage/cli/list.py index 229d5ce2..9c85c0d3 100644 --- a/iocage/cli/list.py +++ b/iocage/cli/list.py @@ -131,12 +131,12 @@ def _print_table( try: sort_index = columns.index(sort_key) except ValueError: - sort_index = None + sort_index = -1 for resource in resources: table_data.append(_lookup_resource_values(resource, columns)) - if sort_index is not None: + if sort_index > -1: table_data.sort(key=lambda x: x[sort_index]) if show_header: diff --git a/iocage/cli/set.py b/iocage/cli/set.py index 5aba60b4..a62b3e0e 100644 --- a/iocage/cli/set.py +++ b/iocage/cli/set.py @@ -41,10 +41,15 @@ @click.pass_context @click.argument("props", nargs=-1) @click.argument("jail", nargs=1, required=True) -def cli(ctx, props, jail): +def cli( + ctx: click.core.Context, + props: typing.Tuple[str, ...], + jail: str +) -> None: """Get a list of jails and print the property.""" - logger = ctx.parent.logger + parent: typing.Any = ctx.parent + logger: iocage.lib.Logger.Logger = parent.logger host = iocage.lib.Host.HostGenerator(logger=logger) # Defaults @@ -66,15 +71,15 @@ def cli(ctx, props, jail): updated_jail_count = 0 - for jail in ioc_jails: + for ioc_jail in ioc_jails: # type: iocage.lib.Jail.JailGenerator - updated_properties = set_properties(props, jail) + updated_properties = set_properties(props, ioc_jail) if len(updated_properties) == 0: - logger.screen(f"Jail '{jail.humanreadable_name}' unchanged") + logger.screen(f"Jail '{ioc_jail.humanreadable_name}' unchanged") else: logger.screen( - f"Jail '{jail.humanreadable_name}' updated: " + + f"Jail '{ioc_jail.humanreadable_name}' updated: " + ", ".join(updated_properties) ) @@ -88,7 +93,7 @@ def cli(ctx, props, jail): def set_properties( - properties: typing.List[str], + properties: typing.Iterable[str], target: 'iocage.lib.LaunchableResource.LaunchableResource' ) -> set: @@ -107,6 +112,7 @@ def set_properties( del target.config[key] updated_properties.add(key) except: + raise pass if len(updated_properties) > 0: @@ -115,5 +121,5 @@ def set_properties( return updated_properties -def _is_setter_property(property_string): - return "=" in property_string +def _is_setter_property(property_string: str) -> bool: + return ("=" in property_string) diff --git a/iocage/cli/start.py b/iocage/cli/start.py index 56081d05..5178eef1 100644 --- a/iocage/cli/start.py +++ b/iocage/cli/start.py @@ -98,7 +98,6 @@ def start_jails(jails, logger, print_function): except Exception: failed_jails.append(jail) - raise continue logger.log(f"{jail.humanreadable_name} running as JID {jail.jid}") diff --git a/iocage/lib/Config/File.py b/iocage/lib/Config/File.py index 0bbe5a8a..6f2f646e 100644 --- a/iocage/lib/Config/File.py +++ b/iocage/lib/Config/File.py @@ -26,7 +26,7 @@ class ConfigFile(iocage.lib.Config.Prototype.Prototype): - _file: str = None + _file: str def __init__( self, @@ -38,7 +38,9 @@ def __init__( self, logger=logger ) - self._file = file + + if file is not None: + self._file = file @property def file(self) -> str: diff --git a/iocage/lib/Config/Jail/BaseConfig.py b/iocage/lib/Config/Jail/BaseConfig.py index 5f580a66..dd92d608 100644 --- a/iocage/lib/Config/Jail/BaseConfig.py +++ b/iocage/lib/Config/Jail/BaseConfig.py @@ -72,7 +72,7 @@ class BaseConfig(dict): def __init__( self, - logger: 'iocage.lib.Logger.Logger'=None, + logger: 'iocage.lib.Logger.Logger'=None ) -> None: dict.__init__(self) @@ -215,7 +215,7 @@ def _set_priority(self, value: typing.Union[int, str], **kwargs): self.data["priority"] = str(value) # legacy support - def _get_tag(self) -> str: + def _get_tag(self) -> typing.Optional[str]: if self._has_legacy_tag is True: return self.data["tag"] @@ -396,7 +396,7 @@ def _skip_on_error(self, **kwargs): except AttributeError: return False - def __getitem_user(self, key): + def __getitem_user(self, key: str) -> typing.Any: # passthrough existing properties try: @@ -484,20 +484,27 @@ def set(self, key: str, value, **kwargs) -> bool: bool: True if the JailConfig was changed """ + hash_before: typing.Any + hash_after: typing.Any + + existed_before = key in self.user_data try: hash_before = str(self.__getitem_user(key)).__hash__() except Exception: hash_before = None - pass self.__setitem__(key, value, **kwargs) + exists_after = key in self.user_data + try: hash_after = str(self.__getitem_user(key)).__hash__() except Exception: hash_after = None - pass + + if existed_before != exists_after: + return True return (hash_before != hash_after) @@ -506,7 +513,7 @@ def user_data(self) -> dict: return self.data def __str__(self) -> str: - return iocage.lib.helpers.to_json(self.user_data) + return iocage.lib.helpers.to_json(self.data) def __dir__(self) -> list: diff --git a/iocage/lib/Config/Jail/Defaults.py b/iocage/lib/Config/Jail/Defaults.py index e43ac82c..730745db 100644 --- a/iocage/lib/Config/Jail/Defaults.py +++ b/iocage/lib/Config/Jail/Defaults.py @@ -26,11 +26,38 @@ import iocage.lib.Config.Jail.BaseConfig -class JailConfigDefaults(iocage.lib.Config.Jail.BaseConfig.BaseConfig): +class DefaultsUserData(dict): user_properties: set = set() - data: dict = { + def __init__(self, defaults: dict={}) -> None: + self.defaults = defaults + dict.__init__(self, defaults) + + def __setitem__(self, key: str, value: typing.Any) -> None: + dict.__setitem__(self, key, value) + self.user_properties.add(key) + + def __delitem__(self, key: str) -> None: + if key in self.defaults: + self[key] = self.defaults[key] + else: + del self[key] + self.user_properties.remove(key) + + @property + def exclusive_user_data(self) -> dict: + data = {} + for key in self.user_properties: + data[key] = self[key] + return data + + +class JailConfigDefaults(iocage.lib.Config.Jail.BaseConfig.BaseConfig): + + _user_data: DefaultsUserData + + DEFAULTS: dict = { "id": None, "release": None, "boot": False, @@ -86,24 +113,20 @@ class JailConfigDefaults(iocage.lib.Config.Jail.BaseConfig.BaseConfig): "jail_zfs": False } - def clear(self): - dict.clear(self) - dict.__init__(self, JailConfigDefaults.DEFAULTS) - - def __setitem__( + def __init__( self, - key: str, - value: typing.Any, - **kwargs - ): + logger: 'iocage.lib.Logger.Logger'=None + ) -> None: - out = super().__setitem__(key, value, **kwargs) - self.user_properties.add(key) - return out + self._user_data = DefaultsUserData( + defaults=self.DEFAULTS + ) + super().__init__(logger=logger) + + @property + def data(self) -> DefaultsUserData: + return self._user_data @property def user_data(self) -> dict: - data = {} - for prop in self.user_properties: - data[prop] = self.data[prop] - return data + return self._user_data.exclusive_user_data diff --git a/iocage/lib/Config/Jail/File/Fstab.py b/iocage/lib/Config/Jail/File/Fstab.py index 3b1f3971..8d57f5b0 100644 --- a/iocage/lib/Config/Jail/File/Fstab.py +++ b/iocage/lib/Config/Jail/File/Fstab.py @@ -93,7 +93,7 @@ def path(self) -> str: def parse_lines( self, - input: str, + input_text: str, ignore_auto_created: bool=True ) -> None: """ @@ -101,7 +101,7 @@ def parse_lines( Args: - input: + input_text: The text content of an existing fstab file ignore_auto_created: @@ -110,7 +110,10 @@ def parse_lines( set.clear(self) - for line in input.split("\n"): + line: str + comment: typing.Optional[str] + + for line in input_text.split("\n"): try: line, comment = line.split("#", maxsplit=1) @@ -200,7 +203,7 @@ def new_line( self, source, destination, - type="nullfs", + fs_type="nullfs", options="ro", dump="0", passnum="0", @@ -210,7 +213,7 @@ def new_line( line = FstabLine({ "source": source, "destination": destination, - "type": type, + "type": fs_type, "options": options, "dump": dump, "passnum": passnum, @@ -228,7 +231,7 @@ def add_line(self, line: FstabLine) -> None: def basejail_lines(self) -> typing.List[dict]: if self.release is None: - return None + return [] if self.jail.config["basejail_type"] != "nullfs": return [] @@ -263,9 +266,7 @@ def __str__(self) -> str: def __iter__(self): fstab_lines = list(set.__iter__(self)) - basejail_lines = self.basejail_lines - if basejail_lines is not None: - fstab_lines += self.basejail_lines + fstab_lines += self.basejail_lines return iter(fstab_lines) def __contains__(self, value: typing.Any) -> bool: diff --git a/iocage/lib/Config/Jail/File/RCConf.py b/iocage/lib/Config/Jail/File/RCConf.py index 6f55f963..3ec7256e 100644 --- a/iocage/lib/Config/Jail/File/RCConf.py +++ b/iocage/lib/Config/Jail/File/RCConf.py @@ -137,7 +137,7 @@ def _read_file( # Current data matches with file contents self._file_content_changed = False - def _read(self, silent=False): + def _read(self, silent=False) -> dict: data = ucl.load(open(self.path).read()) self.logger.spam(f"rc.conf was read from {self.path}") return data diff --git a/iocage/lib/Config/Jail/JailConfig.py b/iocage/lib/Config/Jail/JailConfig.py index 310e639d..2ffda485 100644 --- a/iocage/lib/Config/Jail/JailConfig.py +++ b/iocage/lib/Config/Jail/JailConfig.py @@ -34,13 +34,13 @@ class JailConfig(iocage.lib.Config.Jail.BaseConfig.BaseConfig): - legacy: bool = None + legacy: bool = False jail: 'iocage.lib.Jail.JailGenerator' = None data: dict = {} def __init__( self, - data: dict=None, + data: dict={}, jail: 'iocage.lib.Jail.JailGenerator'=None, new: bool=False, logger: 'iocage.lib.Logger.Logger'=None, diff --git a/iocage/lib/Config/Jail/Properties/Addresses.py b/iocage/lib/Config/Jail/Properties/Addresses.py index f36b1df4..10c10f78 100644 --- a/iocage/lib/Config/Jail/Properties/Addresses.py +++ b/iocage/lib/Config/Jail/Properties/Addresses.py @@ -28,29 +28,31 @@ class AddressSet(set): - config: 'iocage.lib.Config.Jail.JailConfig.JailConfig' # type: ignore + config: 'iocage.lib.Config.Jail.JailConfig.JailConfig' def __init__( self, - config=None, - property_name="ip4_address" - ): + config: typing.Optional['iocage.lib.JailConfig.JailConfig']=None, + property_name: str="ip4_address" + ) -> None: + + if config is not None: + self.config = config - self.config = config set.__init__(self) - object.__setattr__(self, 'property_name', property_name) + self.property_name = property_name - def add(self, value, notify=True): + def add(self, value, notify=True) -> None: set.add(self, value) if notify: self.__notify() - def remove(self, value, notify=True): + def remove(self, value, notify=True) -> None: set.remove(self, value) if notify: self.__notify() - def __notify(self): + def __notify(self) -> None: self.config.update_special_property(self.property_name) @@ -166,7 +168,10 @@ def __delitem__(self, key: str) -> None: self.__notify() def __notify(self) -> None: - self.config.update_special_property(self.property_name) + try: + self.config.update_special_property(self.property_name) + except: + pass def __empty_prop(self, key: str) -> AddressSet: prop = AddressSet(self.config, property_name=self.property_name) diff --git a/iocage/lib/Config/Jail/Properties/Interfaces.py b/iocage/lib/Config/Jail/Properties/Interfaces.py index 496b9fb8..7ef32a6d 100644 --- a/iocage/lib/Config/Jail/Properties/Interfaces.py +++ b/iocage/lib/Config/Jail/Properties/Interfaces.py @@ -30,21 +30,29 @@ class BridgeSet(set): - def __init__(self, config=None): - self.config = config + config: 'iocage.lib.JailConfig.JailConfig' + + def __init__( + self, + config: typing.Optional['iocage.lib.JailConfig.JailConfig']=None + ) -> None: + + if config is not None: + self.config = config + set.__init__(self) - def add(self, value, notify=True): + def add(self, value: str, notify: bool=True) -> None: set.add(self, value) if notify: - self.notify() + self.__notify() - def remove(self, value, notify=True): + def remove(self, value: str, notify: bool=True) -> None: set.remove(self, value) if notify: - self._notify() + self.__notify() - def _notify(self): + def __notify(self) -> None: try: self.config.update_special_property("interfaces") except: @@ -53,17 +61,19 @@ def _notify(self): class InterfaceProp(dict): - config: _ConfigType # type: ignore + config: 'iocage.lib.Config.Jail.JailConfig.JailConfig' property_name: str = "interfaces" def __init__( self, - config=None, + config: typing.Optional['iocage.lib.JailConfig.JailConfig']=None, **kwargs ) -> None: dict.__init__(self, {}) - self.config = config + + if config is not None: + self.config = config def set( self, @@ -89,7 +99,12 @@ def set( self.__notify() - def add(self, jail_if, bridges=None, notify=True): + def add( + self, + jail_if: str, + bridges: typing.Optional[typing.Union[str, typing.List[str]]]=None, + notify: bool=True + ) -> None: if bridges is None or bridges == [] or bridges == "": return @@ -117,17 +132,17 @@ def __setitem__(self, key, values): self.add(key, values) - def __delitem__(self, key): + def __delitem__(self, key) -> None: dict.__delitem__(self, key) self.__notify() - def __notify(self): + def __notify(self) -> None: try: self.config.update_special_property(self.property_name) except: pass - def __empty_prop(self, key): + def __empty_prop(self, key: str) -> BridgeSet: prop = BridgeSet(self.config) dict.__setitem__(self, key, prop) @@ -140,5 +155,5 @@ def to_string(self, value: dict) -> str: out.append(f"{jail_if}:{bridge_if}") return " ".join(out) - def __str__(self): + def __str__(self) -> str: return self.to_string(value=self) diff --git a/iocage/lib/Config/Type/ZFS.py b/iocage/lib/Config/Type/ZFS.py index c8370cc5..068519dc 100644 --- a/iocage/lib/Config/Type/ZFS.py +++ b/iocage/lib/Config/Type/ZFS.py @@ -31,12 +31,11 @@ ZFS_PROPERTY_PREFIX = "org.freebsd.iocage:" -def is_iocage_property(name): - +def is_iocage_property(name: str) -> bool: return name.startswith(ZFS_PROPERTY_PREFIX) -def get_iocage_property_name(zfs_property_name): +def get_iocage_property_name(zfs_property_name: str) -> str: if is_iocage_property(zfs_property_name) is False: raise iocage.lib.errors.NotAnIocageZFSProperty( @@ -56,7 +55,7 @@ def read(self) -> dict: except: return {} - def write(self, data: dict): + def write(self, data: dict) -> None: """ Writes changes to the config file """ @@ -130,7 +129,7 @@ def __init__( ) -> None: self._dataset = dataset - BaseConfigZFS.__init__(self, **kwargs) + iocage.lib.Config.Dataset.DatasetConfig.__init__(self, **kwargs) @property def dataset(self) -> libzfs.ZFSDataset: @@ -146,7 +145,7 @@ def __init__( ) -> None: self.resource = resource - BaseConfigZFS.__init__(self, **kwargs) + iocage.lib.Config.Dataset.DatasetConfig.__init__(self, **kwargs) @property def dataset(self) -> libzfs.ZFSDataset: diff --git a/iocage/lib/Datasets.py b/iocage/lib/Datasets.py index 2365cdf9..e8471e77 100644 --- a/iocage/lib/Datasets.py +++ b/iocage/lib/Datasets.py @@ -21,25 +21,41 @@ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. +import typing import libzfs import iocage.lib.errors import iocage.lib.helpers +# MyPy +import iocage.lib.Types + class Datasets: - ZFS_POOL_ACTIVE_PROPERTY = "org.freebsd.ioc:active" - def __init__(self, root=None, pool=None, zfs=None, logger=None): + ZFS_POOL_ACTIVE_PROPERTY: str = "org.freebsd.ioc:active" + + root: libzfs.ZFSDataset + zfs: 'iocage.lib.ZFS.ZFS' + logger: 'iocage.lib.Logger.Logger' + _datasets: typing.Dict[str, libzfs.ZFSDataset] = {} + + def __init__( + self, + root: typing.Optional[libzfs.ZFSDataset]=None, + pool: typing.Optional[libzfs.ZFSPool]=None, + zfs: 'iocage.lib.ZFS.ZFS'=None, + logger: 'iocage.lib.Logger.Logger'=None + ) -> None: + self.logger = iocage.lib.helpers.init_logger(self, logger) self.zfs = iocage.lib.helpers.init_zfs(self, zfs) - self._datasets = {} if isinstance(root, libzfs.ZFSDataset): self.root = root return - if isinstance(pool, libzfs.ZFSPool): + if (pool is not None) and isinstance(pool, libzfs.ZFSPool): self.root = self._get_or_create_dataset( "iocage", root_name=pool.name, @@ -55,32 +71,40 @@ def __init__(self, root=None, pool=None, zfs=None, logger=None): self.root = self.zfs.get_dataset(f"{active_pool.name}/iocage") @property - def active_pool(self): + def active_pool(self) -> typing.Optional[libzfs.ZFSPool]: for pool in self.zfs.pools: if self._is_pool_active(pool): return pool return None @property - def releases(self): + def releases(self) -> libzfs.ZFSDataset: return self._get_or_create_dataset("releases") @property - def base(self): + def base(self) -> libzfs.ZFSDataset: return self._get_or_create_dataset("base") @property - def jails(self): + def jails(self) -> libzfs.ZFSDataset: return self._get_or_create_dataset("jails") @property - def logs(self): + def logs(self) -> libzfs.ZFSDataset: return self._get_or_create_dataset("log") - def activate(self, mountpoint=None): + def activate( + self, + mountpoint=None + ) -> None: + self.activate_pool(self.root.pool, mountpoint) - def activate_pool(self, pool, mountpoint=None): + def activate_pool( + self, + pool: libzfs.ZFSPool, + mountpoint: typing.Optional[iocage.lib.Types.AbsolutePath]=None + ) -> None: if self._is_pool_active(pool): msg = f"ZFS pool '{pool.name}' is already active" @@ -98,43 +122,50 @@ def activate_pool(self, pool, mountpoint=None): self._activate_pool(pool) - root_dataset_args = { - "pool": pool - } - if mountpoint is not None: - root_dataset_args["mountpoint"] = mountpoint - - self.root = self._get_or_create_dataset( - "iocage", - **root_dataset_args - ) + self.root = self._get_or_create_dataset( + "iocage", + pool=pool, + mountpoint=mountpoint + ) + else: + self.root = self._get_or_create_dataset( + "iocage", + pool=pool + ) - def _is_pool_active(self, pool): + def _is_pool_active(self, pool: libzfs.ZFSPool) -> bool: return iocage.lib.helpers.parse_user_input(self._get_pool_property( pool, self.ZFS_POOL_ACTIVE_PROPERTY )) - def _get_pool_property(self, pool, prop): - try: - return pool.root_dataset.properties[prop].value - except (KeyError, ValueError): - return None + def _get_pool_property( + self, + pool: libzfs.ZFSPool, + prop: str + ) -> str: + + return pool.root_dataset.properties[prop].value + + def _get_dataset_property( + self, + dataset: libzfs.ZFSDataset, + prop: str + ) -> typing.Optional[str]: - def _get_dataset_property(self, dataset, prop): try: return dataset.properties[prop].value except: return None - def _activate_pool(self, pool): + def _activate_pool(self, pool: libzfs.ZFSPool) -> None: self._set_pool_activation(pool, True) - def _deactivate_pool(self, pool): + def _deactivate_pool(self, pool: libzfs.ZFSPool) -> None: self._set_pool_activation(pool, False) - def _set_pool_activation(self, pool, state): + def _set_pool_activation(self, pool: libzfs.ZFSPool, state: bool) -> None: value = "yes" if state is True else "no" self._set_zfs_property( pool.root_dataset, @@ -142,7 +173,13 @@ def _set_pool_activation(self, pool, state): value ) - def _set_zfs_property(self, dataset, name, value): + def _set_zfs_property( + self, + dataset: libzfs.ZFSDataset, + name: str, + value: str + ) -> None: + current_value = self._get_dataset_property(dataset, name) if current_value != value: self.logger.verbose( @@ -151,38 +188,45 @@ def _set_zfs_property(self, dataset, name, value): ) dataset.properties[name] = libzfs.ZFSUserProperty(value) - def _get_or_create_dataset(self, - name, - root_name=None, - pool=None, - mountpoint=None): + def _get_or_create_dataset( + self, + name: str, + root_name: str=None, + pool: typing.Optional[libzfs.ZFSPool]=None, + mountpoint: typing.Optional[iocage.lib.Types.AbsolutePath]=None + ) -> libzfs.ZFSDataset: if not iocage.lib.helpers.validate_name(name): raise NameError(f"Invalid 'name' for Dataset: {name}") try: - return self.datasets[name] + return self._datasets[name] except (AttributeError, KeyError): pass - if root_name is None: - root_name = self.root.name + if root_name is not None: + root_dataset_name = root_name + else: + root_dataset_name = self.root.name - if pool is None: - pool = self.root.pool + target_pool: libzfs.ZFSPool + if pool is not None: + target_pool = pool + else: + target_pool = self.root.pool - name = f"{root_name}/{name}" + dataset_name = f"{root_dataset_name}/{name}" try: - dataset = self.zfs.get_dataset(name) + dataset = self.zfs.get_dataset(dataset_name) except: - pool.create(name, {}) - dataset = self.zfs.get_dataset(name) + target_pool.create(dataset_name, {}) + dataset = self.zfs.get_dataset(dataset_name) if mountpoint is not None: mountpoint_property = libzfs.ZFSUserProperty(mountpoint) dataset.properties["mountpoint"] = mountpoint_property dataset.mount() - self._datasets[name] = dataset + self._datasets[dataset_name] = dataset return dataset diff --git a/iocage/lib/Filter.py b/iocage/lib/Filter.py index 0a439df2..827ca119 100644 --- a/iocage/lib/Filter.py +++ b/iocage/lib/Filter.py @@ -43,7 +43,7 @@ class Term(list): glob_characters = ["*", "+"] - def __init__(self, key, values=list()): + def __init__(self, key, values=list()) -> None: self.key = key if values is None: diff --git a/iocage/lib/Jail.py b/iocage/lib/Jail.py index c0efbdfa..501b698f 100644 --- a/iocage/lib/Jail.py +++ b/iocage/lib/Jail.py @@ -47,20 +47,21 @@ class JailResource(iocage.lib.LaunchableResource.LaunchableResource): - _jail: 'JailGenerator' = None - _fstab: 'iocage.lib.Config.Jail.File.Fstab.Fstab' = None + _jail: 'JailGenerator' + _fstab: 'iocage.lib.Config.Jail.File.Fstab.Fstab' def __init__( self, host: 'iocage.lib.Host.HostGenerator', - jail: 'JailGenerator'=None, + jail: typing.Optional['JailGenerator']=None, **kwargs ) -> None: self.__jails_dataset_name = host.datasets.jails.name self.host = iocage.lib.helpers.init_host(self, host) - self._jail = jail + if jail is not None: + self._jail = jail iocage.lib.LaunchableResource.LaunchableResource.__init__( self, @@ -76,25 +77,33 @@ def jail(self) -> 'JailGenerator': It can still be used linked to a foreign jail by passing jail as named attribute to the __init__ function """ - if self._jail is not None: + try: return self._jail + except AttributeError: + pass # is instance of Jail itself - if isinstance(self, JailGenerator): + if isinstance(self, iocage.lib.Jail.JailGenerator): return self raise Exception("This resource is not a jail or not linked to one") @property def fstab(self) -> 'iocage.lib.Config.Jail.File.Fstab.Fstab': - if self._fstab is None: - self._fstab = iocage.lib.Config.Jail.File.Fstab.Fstab( - jail=self.jail, - release=self.jail.release, - logger=self.logger, - host=self.jail.host - ) - return self._fstab + + try: + return self._fstab + except AttributeError: + pass + + fstab = iocage.lib.Config.Jail.File.Fstab.Fstab( + jail=self.jail, + release=self.jail.release, + logger=self.logger, + host=self.jail.host + ) + self._fstab = fstab + return fstab @property def dataset_name(self) -> str: @@ -109,6 +118,11 @@ def dataset_name(self) -> str: except: pass + try: + return self._dataset.name + except AttributeError: + pass + return self._dataset_name_from_jail_name @dataset_name.setter @@ -179,7 +193,7 @@ class JailGenerator(JailResource): _class_storage = iocage.lib.Storage.Storage - jail_state: dict = None + jail_state: dict = {} def __init__( self, @@ -218,6 +232,22 @@ def __init__( "id": self._resolve_name(data) } + JailResource.__init__( + self, + jail=self, + host=self.host, + logger=self.logger, + zfs=self.zfs, + **resource_args + ) + + if not new and (("id" not in data) or (data["id"] is None)): + try: + # try to get the Jail nane from it's dataset_name + data["id"] = self.dataset_name.split("/").pop() + except: + pass + self.config = iocage.lib.Config.Jail.JailConfig.JailConfig( data=data, host=self.host, @@ -232,15 +262,6 @@ def __init__( zfs=self.zfs ) - JailResource.__init__( - self, - jail=self, - host=self.host, - logger=self.logger, - zfs=self.zfs, - **resource_args - ) - if new is False: self.config.read(data=self.read_config()) if self.config["id"] is None: @@ -337,7 +358,7 @@ def _run_hook(self, hook_name: str): env=self.env ) - def _start_services(self): + def _start_services(self) -> None: command = self.config["exec_start"].strip().split() self.logger.debug(f"Running exec_start on {self.humanreadable_name}") self.exec(command) @@ -385,7 +406,7 @@ def stop( self.update_jail_state() - def destroy(self, force=False): + def destroy(self, force: bool=False) -> None: """ Destroy a Jail and it's datasets @@ -406,7 +427,7 @@ def destroy(self, force=False): self.storage.delete_dataset_recursive(self.dataset) - def rename(self, new_name: str): + def rename(self, new_name: str) -> None: """ Change the name of a jail """ @@ -430,9 +451,9 @@ def rename(self, new_name: str): self.config["id"] = current_id raise - def _force_stop(self): - - successful = True + def _force_stop( + self + ) -> typing.Generator['iocage.lib.events.IocageEvent', None, None]: events: typing.Any = iocage.lib.events jailDestroyEvent = events.JailDestroy(self) @@ -442,7 +463,6 @@ def _force_stop(self): try: self._run_hook("prestop") except: - successful = False self.logger.warn("pre-stop script failed") yield jailDestroyEvent.begin() @@ -451,7 +471,6 @@ def _force_stop(self): self.logger.debug(f"{self.humanreadable_name}: jail destroyed") yield jailDestroyEvent.end() except Exception as e: - successful = False yield jailDestroyEvent.skip() if self.config["vnet"]: @@ -461,7 +480,6 @@ def _force_stop(self): self.logger.debug(f"{self.humanreadable_name}: VNET stopped") yield jailNetworkTeardownEvent.end() except Exception as e: - successful = False yield jailNetworkTeardownEvent.skip() yield jailMountTeardownEvent.begin() @@ -470,18 +488,14 @@ def _force_stop(self): self.logger.debug(f"{self.humanreadable_name}: mounts destroyed") yield jailMountTeardownEvent.end() except Exception as e: - successful = False yield jailMountTeardownEvent.skip() try: self.update_jail_state() except Exception as e: - successful = False self.logger.warn(str(e)) - return successful - - def create(self, release_name): + def create(self, release_name: str) -> None: """ Create a Jail from a Release @@ -541,11 +555,11 @@ def create(self, release_name): self.config.data["release"] = release.name self.save() - def save(self): + def save(self) -> None: self.write_config(self.config.data) self._save_autoconfig() - def _save_autoconfig(self): + def _save_autoconfig(self) -> None: """ Saves auto-generated files """ @@ -561,7 +575,11 @@ def _update_fstab(self) -> None: self.fstab.update_and_save() - def exec(self, command, **kwargs): + def exec( + self, + command: typing.List[str], + **kwargs + ) -> typing.Tuple[subprocess.Popen, str, str]: """ Execute a command in a started jail @@ -580,7 +598,7 @@ def exec(self, command, **kwargs): **kwargs ) - def passthru(self, command): + def passthru(self, command: typing.List[str]): """ Execute a command in a started jail ans passthrough STDIN and STDOUT @@ -609,7 +627,7 @@ def exec_console(self): ["/usr/bin/login"] + self.config["login_flags"] ) - def _destroy_jail(self): + def _destroy_jail(self) -> None: command = ["jail", "-r"] command.append(self.identifier) @@ -621,7 +639,7 @@ def _destroy_jail(self): ) @property - def _dhcp_enabled(self): + def _dhcp_enabled(self) -> bool: """ True if any ip4_addr uses DHCP """ @@ -631,7 +649,7 @@ def _dhcp_enabled(self): return ("dhcp" in self.config["ip4_addr"].networks) @property - def devfs_ruleset(self): + def devfs_ruleset(self) -> iocage.lib.DevfsRules.DevfsRuleset: """ The number of the jails devfs ruleset @@ -669,7 +687,7 @@ def devfs_ruleset(self): ruleset_line_position = self.host.devfs.index(devfs_ruleset) return self.host.devfs[ruleset_line_position].number - def _launch_jail(self): + def _launch_jail(self) -> None: command = ["jail", "-c"] @@ -761,7 +779,7 @@ def _launch_jail(self): raise @property - def networks(self) -> list: + def networks(self) -> typing.List[iocage.lib.Network.Network]: networks = [] @@ -856,7 +874,11 @@ def _resource_limit_config_keys(self): "writeiops" ] - def _get_resource_limit(self, key: str) -> typing.Tuple[str, str]: + def _get_resource_limit( + self, + key: str + ) -> typing.Union[typing.Tuple[str, str], typing.Tuple[None, None]]: + try: if isinstance(self.config[key], str): return self._parse_resource_limit(self.config[key]) @@ -965,7 +987,7 @@ def update_jail_state(self) -> None: self.jail_state = json.loads(output)["jail-information"]["jail"][0] except: - self.jail_state = None + self.jail_state = {} def _teardown_mounts(self) -> None: @@ -1056,13 +1078,13 @@ def running(self) -> bool: return self.jid is not None @property - def jid(self) -> int: + def jid(self) -> typing.Optional[int]: """ The JID of a running jail or None if the jail is not running """ try: return int(self.jail_state["jid"]) - except (TypeError, AttributeError, KeyError): + except KeyError: pass try: @@ -1119,17 +1141,10 @@ def __getattribute__(self, key: str): except AttributeError: pass - try: - jail_state = object.__getattribute__(self, "jail_state") - except: - jail_state = None - raise + jail_state = self.jail_state - if jail_state is not None: - try: - return jail_state[key] - except: - pass + if (len(jail_state) > 0) and key in jail_state.keys(): + return jail_state[key] raise AttributeError(f"Jail property {key} not found") @@ -1146,8 +1161,18 @@ def __dir__(self): class Jail(JailGenerator): - def start(self, *args, **kwargs): + def start( # noqa: T484 + self, + *args, + **kwargs + ) -> typing.List['iocage.lib.events.IocageEvent']: + return list(JailGenerator.start(self, *args, **kwargs)) - def stop(self, *args, **kwargs): + def stop( # noqa: T484 + self, + *args, + **kwargs + ) -> typing.List['iocage.lib.events.IocageEvent']: + return list(JailGenerator.stop(self, *args, **kwargs)) diff --git a/iocage/lib/LaunchableResource.py b/iocage/lib/LaunchableResource.py index 4a0336d3..06727e5f 100644 --- a/iocage/lib/LaunchableResource.py +++ b/iocage/lib/LaunchableResource.py @@ -36,6 +36,9 @@ class LaunchableResource(iocage.lib.Resource.Resource): _rc_conf: 'iocage.lib.Config.Jail.File.RCConf.RCConf' = None config: 'iocage.lib.Config.Jail.JailConfig.JailConfig' = None + def __init__(self, *args, **kwargs) -> None: + iocage.lib.Resource.Resource.__init__(self, *args, **kwargs) + def create_resource(self) -> None: """ Creates the root dataset @@ -68,17 +71,6 @@ def dataset_name(self, value: str) -> None: "This needs to be implemented by the inheriting class" ) - @property - def dataset(self) -> libzfs.ZFSDataset: - if self._dataset is None: - self._dataset = self.zfs.get_dataset(self.dataset_name) - - return self._dataset - - @dataset.setter - def dataset(self, value: libzfs.ZFSDataset): - self._set_dataset(value) - @property def rc_conf(self) -> 'iocage.lib.Config.Jail.File.RCConf.RCConf': if self._rc_conf is None: diff --git a/iocage/lib/Network.py b/iocage/lib/Network.py index fd65ec2d..57eb5e07 100644 --- a/iocage/lib/Network.py +++ b/iocage/lib/Network.py @@ -32,8 +32,8 @@ class Network: def __init__(self, jail, nic="vnet0", - ipv4_addresses=[], - ipv6_addresses=[], + ipv4_addresses=None, + ipv6_addresses=None, mtu=1500, bridges=None, logger=None): @@ -52,8 +52,8 @@ def __init__(self, jail, self.jail = jail self.nic = nic self.mtu = mtu - self.ipv4_addresses = ipv4_addresses - self.ipv6_addresses = ipv6_addresses + self.ipv4_addresses = ipv4_addresses or [] + self.ipv6_addresses = ipv6_addresses or [] def setup(self): if self.vnet: diff --git a/iocage/lib/Release.py b/iocage/lib/Release.py index 71316ac1..2fda238e 100644 --- a/iocage/lib/Release.py +++ b/iocage/lib/Release.py @@ -28,7 +28,6 @@ import shutil import tarfile import urllib.request -import uuid from urllib.parse import urlparse import libzfs @@ -47,12 +46,12 @@ class ReleaseResource(iocage.lib.LaunchableResource.LaunchableResource): - _release: 'ReleaseGenerator' = None + _release: typing.Optional['ReleaseGenerator'] = None def __init__( self, host: 'iocage.lib.Host.HostGenerator', - release: 'ReleaseGenerator'=None, + release: typing.Optional['ReleaseGenerator']=None, **kwargs ) -> None: @@ -116,7 +115,7 @@ def base_dataset_name(self) -> str: return f"{self.__base_dataset_name}/{self.release.name}/root" @property - def file(self) -> str: + def file(self) -> typing.Optional[str]: return None @@ -130,7 +129,7 @@ class ReleaseGenerator(ReleaseResource): "sendmail_outbound": False } - name: str = None + name: str eol: bool = False logger: 'iocage.lib.Logger.Logger' @@ -141,7 +140,7 @@ class ReleaseGenerator(ReleaseResource): def __init__( self, - name: str=None, + name: str, host: 'iocage.lib.Host.HostGenerator'=None, zfs: 'iocage.lib.ZFS.ZFS'=None, logger: 'iocage.lib.Logger.Logger'=None, @@ -310,7 +309,7 @@ def _pad_release_name(self, release_name: str, digits: int=4) -> str: try: int(major_version_number) - padding = str("0" * (digits-len(str(major_version_number)))) + padding = str("0" * (digits - len(str(major_version_number)))) return padding + release_name except: return release_name @@ -385,7 +384,6 @@ def fetch(self, update=None, fetch_updates=None): yield releasePrepareStorageEvent.begin() # ToDo: allow to reach this for forced re-fetch - self._clean_dataset() self.create_resource() self._ensure_dataset_mounted() @@ -575,11 +573,14 @@ def update(self): jail = iocage.lib.Jail.JailGenerator( { - "uuid": str(uuid.uuid4()), "basejail": False, "allow_mount_nullfs": "1", "release": self.name, - "securelevel": "0" + "securelevel": "0", + "vnet": False, + "ip4_addr": None, + "ip6_addr": None, + "defaultrouter": None }, new=True, logger=self.logger, @@ -722,10 +723,10 @@ def _update_freebsd_jail(self, jail): os.makedirs(local_update_mountpoint) jail.fstab.new_line( - self.release_updates_dir, - local_update_mountpoint, - "nullfs", - "rw" + source=self.release_updates_dir, + destination=local_update_mountpoint, + fs_type="nullfs", + options="rw" ) jail.fstab.save() @@ -749,7 +750,7 @@ def _update_freebsd_jail(self, jail): ) self.logger.debug("Already up to date") else: - yield executeReleaseUpdateEvent.failed() + yield executeReleaseUpdateEvent.fail() raise iocage.lib.errors.ReleaseUpdateFailure( release_name=self.name, reason=( @@ -797,23 +798,6 @@ def _fetch_assets(self): urllib.request.urlretrieve(url, path) self.logger.verbose(f"{url} was saved to {path}") - def _clean_dataset(self): - - if not os.path.isdir(self.root_dir): - return - - root_dir_index = os.listdir(self.root_dir) - if not len(root_dir_index) > 0: - return - - self.logger.verbose( - f"Remove existing fragments from {self.root_dir}" - ) - for directory in root_dir_index: - asset_path = os.path.join(self.root_dir, directory) - self.logger.spam(f"Purging {asset_path}") - self._rmtree(asset_path) - def read_hashes(self): # yes, this can read HardenedBSD and FreeBSD hash files path = self.__get_hashfile_location() diff --git a/iocage/lib/Resource.py b/iocage/lib/Resource.py index ee3887b5..6f98cf4a 100644 --- a/iocage/lib/Resource.py +++ b/iocage/lib/Resource.py @@ -82,16 +82,16 @@ class Resource: DEFAULT_JSON_FILE = "config.json" DEFAULT_UCL_FILE = "config" - _dataset_name: typing.Optional[str] = None _config_type: typing.Optional[int] = None _config_file: typing.Optional[str] = None - _dataset: typing.Optional[libzfs.ZFSDataset] = None + _dataset: libzfs.ZFSDataset + _dataset_name: str def __init__( self, - dataset: libzfs.ZFSDataset=None, - dataset_name: str=None, - config_type: str="auto", # auto, json, zfs, ucl + dataset: typing.Optional[libzfs.ZFSDataset]=None, + dataset_name: typing.Optional[str]=None, + config_type: typing.Optional[str]="auto", # auto, json, zfs, ucl config_file: str=None, # 'config.json', 'config', etc logger: 'iocage.lib.Logger.Logger'=None, zfs: 'iocage.lib.ZFS.ZFS'=None @@ -140,11 +140,17 @@ def _assigned_dataset_name(self) -> str: """ Name of the jail's base ZFS dataset manually assigned to this resource """ - if self._dataset_name is not None: + try: return self._dataset_name - elif self._dataset is not None: + except AttributeError: + pass + + try: return self._dataset.name - raise + except AttributeError: + pass + + raise Exception("Could not determine dataset_name") @property def dataset_name(self) -> str: @@ -156,32 +162,26 @@ def dataset_name(self, value: str): @property def dataset(self) -> libzfs.ZFSDataset: - """ - The jail's base ZFS dataset - """ - if self._dataset_name is not None: - # sets self._dataset_name to None and memoize the dataset - self._set_dataset(self.zfs.get_dataset(self.dataset_name)) - - return self._dataset + try: + return self._dataset + except AttributeError: + dataset = self.zfs.get_dataset(self.dataset_name) + self._dataset = dataset + return dataset @dataset.setter def dataset(self, value: libzfs.ZFSDataset): self._set_dataset(value) - def _set_dataset(self, value) -> None: - self._dataset_name = None + def _set_dataset(self, value: libzfs.ZFSDataset) -> None: + try: + del self._dataset_name + except: + pass self._dataset = value - # @property - # def path(self): - # """ - # Mountpoint of the jail's base ZFS dataset - # """ - # return self.dataset.mountpoint - @property - def config_type(self) -> str: + def config_type(self) -> typing.Optional[str]: if self._config_type is None: return None elif self._config_type == self.CONFIG_TYPES.index("auto"): @@ -209,7 +209,7 @@ def _detect_config_type(self) -> int: return 0 @property - def config_file(self) -> str: + def config_file(self) -> typing.Optional[str]: """ Relative path of the resource config file """ @@ -345,7 +345,9 @@ def __init__( self.filters = filters - def __iter__(self): + def __iter__( + self + ) -> typing.Generator['iocage.lib.Resource.Resource', None, None]: for child_dataset in self.dataset.children: diff --git a/iocage/lib/ZFS.py b/iocage/lib/ZFS.py index fad21f01..7ef3e307 100644 --- a/iocage/lib/ZFS.py +++ b/iocage/lib/ZFS.py @@ -28,16 +28,6 @@ import iocage.lib.errors -def get_zfs( - logger: 'iocage.lib.Logger.Logger'=None, - history: bool=True, - history_prefix: str="" -): - zfs = ZFS(history=history, history_prefix=history_prefix) - zfs.logger = iocage.lib.helpers.init_logger(zfs, logger) - return zfs - - class ZFS(libzfs.ZFS): logger: iocage.lib.Logger.Logger = None @@ -77,3 +67,13 @@ def get_pool(self, name: str) -> libzfs.ZFSPool: pool_name=pool_name, logger=self.logger ) + + +def get_zfs( + logger: 'iocage.lib.Logger.Logger'=None, + history: bool=True, + history_prefix: str="" +) -> ZFS: + zfs = ZFS(history=history, history_prefix=history_prefix) + zfs.logger = iocage.lib.helpers.init_logger(zfs, logger) + return zfs diff --git a/iocage/lib/events.py b/iocage/lib/events.py index 1f495f2a..cf10de00 100644 --- a/iocage/lib/events.py +++ b/iocage/lib/events.py @@ -48,13 +48,13 @@ class IocageEvent: PENDING_COUNT: int = 0 - identifier: str = None - _started_at: float = None - _stopped_at: float = None + identifier: typing.Optional[str] + _started_at: float + _stopped_at: float _pending: bool = False skipped: bool = False done: bool = True - error: BaseException = None + error: typing.Optional[BaseException] = None def __init__(self, message=None, **kwargs) -> None: """ @@ -115,9 +115,11 @@ def pending(self, state: bool) -> None: return if new_state is True: - if self._started_at is not None: + try: + self._started_at raise iocage.lib.errors.EventAlreadyFinished(event=self) - self._started_at = float(timer()) + except AttributeError: + self._started_at = float(timer()) if new_state is False: self._stopped_at = float(timer()) @@ -126,9 +128,10 @@ def pending(self, state: bool) -> None: @property def duration(self) -> typing.Optional[float]: - if (self._started_at is None) or (self._stopped_at is None): + try: + return self._stopped_at - self._started_at + except AttributeError: return None - return self._stopped_at - self._started_at def _update_message(self, **kwargs) -> None: if "message" in kwargs: @@ -168,7 +171,7 @@ def fail(self, exception=True, **kwargs) -> 'IocageEvent': self.parent_count = IocageEvent.PENDING_COUNT return self - def __hash__(self): + def __hash__(self) -> typing.Any: identifier = "generic" if self.identifier is None else self.identifier return hash((self.type, identifier)) diff --git a/setup.cfg b/setup.cfg index 91164c11..621aa627 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,6 +3,11 @@ description-file = README.md [mypy] python_version = 3.6 +# override flake8-mypy defaults, since we provide (missing) types +ignore_missing_imports=False +disallow_untyped_calls=True +follow_imports=True +cache_dir=.mypy_cache [tool:pytest] addopts = -v -x -rs --ignore=setup.py --pep8 --cov-report term-missing --cov=iocage/lib iocage/lib iocage/tests