diff --git a/nixops/backends/__init__.py b/nixops/backends/__init__.py index e2ef1c168..6e792f60a 100644 --- a/nixops/backends/__init__.py +++ b/nixops/backends/__init__.py @@ -3,23 +3,22 @@ import os import re import subprocess -from typing import Dict, Any, List, Optional +from typing import Dict, Any, List, Optional, Union, Set import nixops.util import nixops.resources import nixops.ssh_util +import xml.etree.ElementTree as ET class MachineDefinition(nixops.resources.ResourceDefinition): """Base class for NixOps machine definitions.""" - def __init__(self, xml, config={}): + def __init__(self, xml, config={}) -> None: nixops.resources.ResourceDefinition.__init__(self, xml, config) - self.encrypted_links_to = set( - [ - e.get("value") - for e in xml.findall("attrs/attr[@name='encryptedLinksTo']/list/string") - ] - ) + self.encrypted_links_to: Set[str] = { + e.get("value") + for e in xml.findall("attrs/attr[@name='encryptedLinksTo']/list/string") + } self.store_keys_on_machine = ( xml.find("attrs/attr[@name='storeKeysOnMachine']/bool").get("value") == "true" @@ -37,7 +36,7 @@ def __init__(self, xml, config={}): == "true" ) - def _extract_key_options(x): + def _extract_key_options(x: ET.Element) -> Dict[str, str]: opts = {} for (key, xmlType) in ( ("text", "string"), @@ -49,7 +48,9 @@ def _extract_key_options(x): ): elem = x.find("attrs/attr[@name='{0}']/{1}".format(key, xmlType)) if elem is not None: - opts[key] = elem.get("value") + value = elem.get("value") + if value is not None: + opts[key] = value return opts self.keys = { @@ -90,7 +91,7 @@ class MachineState(nixops.resources.ResourceState): # machine was created. state_version: Optional[str] = nixops.util.attr_property("stateVersion", None, str) - def __init__(self, depl, name: str, id: int): + def __init__(self, depl, name: str, id: int) -> None: nixops.resources.ResourceState.__init__(self, depl, name, id) self._ssh_pinged_this_time = False self.ssh = nixops.ssh_util.SSH(self.logger) @@ -104,11 +105,11 @@ def prefix_definition(self, attr): return attr @property - def started(self): + def started(self) -> bool: state = self.state return state == self.STARTING or state == self.UP - def set_common_state(self, defn): + def set_common_state(self, defn) -> None: self.store_keys_on_machine = defn.store_keys_on_machine self.keys = defn.keys self.ssh_port = defn.ssh_port @@ -116,15 +117,15 @@ def set_common_state(self, defn): if not self.has_fast_connection: self.ssh.enable_compression() - def stop(self): + def stop(self) -> None: """Stop this machine, if possible.""" self.warn("don't know how to stop machine ‘{0}’".format(self.name)) - def start(self): + def start(self) -> None: """Start this machine, if possible.""" pass - def get_load_avg(self): + def get_load_avg(self) -> Union[List[str], None]: """Get the load averages on the machine.""" try: res = ( @@ -141,13 +142,13 @@ def get_load_avg(self): # FIXME: Move this to ResourceState so that other kinds of # resources can be checked. - def check(self): + def check(self): # TODO -> CheckResult, but supertype ResourceState -> True """Check machine state.""" res = CheckResult() self._check(res) return res - def _check(self, res): + def _check(self, res): # TODO -> None but supertype ResourceState -> True avg = self.get_load_avg() if avg == None: if self.state == self.UP: @@ -199,7 +200,7 @@ def _check(self, res): continue res.failed_units.append(match.group(1)) - def restore(self, defn, backup_id, devices=[]): + def restore(self, defn, backup_id: Optional[str], devices: List[str] = []): """Restore persistent disks to a given backup, if possible.""" self.warn( "don't know how to restore disks from backup for machine ‘{0}’".format( @@ -223,7 +224,7 @@ def backup(self, defn, backup_id: str, devices: List[str] = []) -> None: "don't know how to make backup of disks for machine ‘{0}’".format(self.name) ) - def reboot(self, hard=False): + def reboot(self, hard: bool = False) -> None: """Reboot this machine.""" self.log("rebooting...") if self.state == self.RESCUE: @@ -237,7 +238,7 @@ def reboot(self, hard=False): self.state = self.STARTING self.ssh.reset() - def reboot_sync(self, hard=False): + def reboot_sync(self, hard: bool = False) -> None: """Reboot this machine and wait until it's up again.""" self.reboot(hard=hard) self.log_start("waiting for the machine to finish rebooting...") @@ -257,13 +258,13 @@ def reboot_sync(self, hard=False): self._ssh_pinged_this_time = True self.send_keys() - def reboot_rescue(self, hard=False): + def reboot_rescue(self, hard: bool = False) -> None: """ Reboot machine into rescue system and wait until it is active. """ self.warn("machine ‘{0}’ doesn't have a rescue" " system.".format(self.name)) - def send_keys(self): + def send_keys(self) -> None: if self.state == self.RESCUE: # Don't send keys when in RESCUE state, because we're most likely # bootstrapping plus we probably don't have /run mounted properly @@ -488,7 +489,7 @@ def get_console_output(self): class CheckResult(object): - def __init__(self): + def __init__(self) -> None: # Whether the resource exists. self.exists = None @@ -513,7 +514,7 @@ def __init__(self): self.load = None # Error messages. - self.messages = [] + self.messages: List[str] = [] # FIXME: add a check whether the active NixOS config on the # machine is correct. diff --git a/nixops/resources/__init__.py b/nixops/resources/__init__.py index 6af3215e6..04d096516 100644 --- a/nixops/resources/__init__.py +++ b/nixops/resources/__init__.py @@ -3,7 +3,7 @@ import re import nixops.util from threading import Event -from typing import List, Optional, Dict +from typing import List, Optional, Dict, Any from nixops.state import StateDict from nixops.diff import Diff, Handler @@ -73,7 +73,7 @@ def __init__(self, depl, name: str, id): self.logger = depl.logger.get_logger_for(name) self.logger.register_index(self.index) - def _set_attrs(self, attrs): + def _set_attrs(self, attrs: Dict[str, Any]) -> None: """Update machine attributes in the state file.""" with self.depl._db: c = self.depl._db.cursor() @@ -89,11 +89,11 @@ def _set_attrs(self, attrs): (self.id, n, v), ) - def _set_attr(self, name, value): + def _set_attr(self, name: str, value: Any) -> None: """Update one machine attribute in the state file.""" self._set_attrs({name: value}) - def _del_attr(self, name): + def _del_attr(self, name: str) -> None: """Delete a machine attribute from the state file.""" with self.depl._db: self.depl._db.execute( @@ -101,7 +101,7 @@ def _del_attr(self, name): (self.id, name), ) - def _get_attr(self, name, default=nixops.util.undefined): + def _get_attr(self, name: str, default=nixops.util.undefined) -> Any: """Get a machine attribute from the state file.""" with self.depl._db: c = self.depl._db.cursor() @@ -201,7 +201,9 @@ def create(self, defn, check, allow_reboot, allow_recreate): """Create or update the resource defined by ‘defn’.""" raise NotImplementedError("create") - def check(self): + def check( + self, + ): # TODO this return type is inconsistent with child class MachineState """ Reconcile the state file with the real world infrastructure state. This should not do any provisionning but just sync the state. diff --git a/nixops/util.py b/nixops/util.py index 78fe85fd3..53c64b2c0 100644 --- a/nixops/util.py +++ b/nixops/util.py @@ -16,7 +16,7 @@ import logging import atexit import re -from typing import Callable, List, Optional, Any, IO, Union, Mapping, TextIO +from typing import Callable, List, Optional, Any, IO, Union, Mapping, TextIO, Tuple # the following ansi_ imports are for backwards compatability. They # would belong fine in this util.py, but having them in util.py @@ -51,7 +51,7 @@ def check_wait( class CommandFailed(Exception): - def __init__(self, message: str, exitcode: int): + def __init__(self, message: str, exitcode: int) -> None: self.message = message self.exitcode = exitcode @@ -202,7 +202,7 @@ def generate_random_string(length=256) -> str: return base64.b64encode(s).decode() -def make_non_blocking(fd: IO[Any]): +def make_non_blocking(fd: IO[Any]) -> None: fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) @@ -243,7 +243,7 @@ def wait_for_tcp_port( timeout: int = -1, open: bool = True, callback: Optional[Callable[[], Any]] = None, -): +) -> bool: """Wait until the specified TCP port is open or closed.""" n = 0 while True: @@ -270,7 +270,7 @@ def _maybe_abspath(s: str) -> str: return os.path.abspath(s) -def abs_nix_path(x): +def abs_nix_path(x: str) -> str: xs = x.split("=", 1) if len(xs) == 1: return _maybe_abspath(x) @@ -319,7 +319,9 @@ def set(self, x: Any) -> None: return property(get, set) -def create_key_pair(key_name="NixOps auto-generated key", type="ed25519"): +def create_key_pair( + key_name="NixOps auto-generated key", type="ed25519" +) -> Tuple[str, str]: key_dir = tempfile.mkdtemp(prefix="nixops-key-tmp") res = subprocess.call( ["ssh-keygen", "-t", type, "-f", key_dir + "/key", "-N", "", "-C", key_name], @@ -338,30 +340,31 @@ def create_key_pair(key_name="NixOps auto-generated key", type="ed25519"): class SelfDeletingDir(str): - def __init__(self, s: str): + def __init__(self, s: str) -> None: str.__init__(s) atexit.register(self._delete) - def _delete(self): + def _delete(self) -> None: shutil.rmtree(self) class TeeStderr(StringIO): stderr: TextIO - def __init__(self): + def __init__(self) -> None: StringIO.__init__(self) self.stderr = sys.stderr self.logger = logging.getLogger("root") sys.stderr = self - def __del__(self): + def __del__(self) -> None: sys.stderr = self.stderr - def write(self, data): - self.stderr.write(data) + def write(self, data) -> int: + ret = self.stderr.write(data) for l in data.split("\n"): self.logger.warning(l) + return ret def fileno(self) -> int: return self.stderr.fileno() @@ -369,26 +372,27 @@ def fileno(self) -> int: def isatty(self) -> bool: return self.stderr.isatty() - def flush(self): + def flush(self) -> None: return self.stderr.flush() class TeeStdout(StringIO): stdout: TextIO - def __init__(self): + def __init__(self) -> None: StringIO.__init__(self) self.stdout = sys.stdout self.logger = logging.getLogger("root") sys.stdout = self - def __del__(self): + def __del__(self) -> None: sys.stdout = self.stdout - def write(self, data): - self.stdout.write(data) + def write(self, data) -> int: + ret = self.stdout.write(data) for l in data.split("\n"): self.logger.info(l) + return ret def fileno(self) -> int: return self.stdout.fileno() @@ -396,7 +400,7 @@ def fileno(self) -> int: def isatty(self) -> bool: return self.stdout.isatty() - def flush(self): + def flush(self) -> None: return self.stdout.flush() @@ -425,7 +429,7 @@ def enum(**enums): return type("Enum", (), enums) -def write_file(path: str, contents: str): +def write_file(path: str, contents: str) -> None: f = open(path, "w") f.write(contents) f.close()