modernisation for python 3.11
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
# This file contains type hints that can be prepended to Nix test scripts so they can be type
|
# This file contains type hints that can be prepended to Nix test scripts so they can be type
|
||||||
# checked.
|
# checked.
|
||||||
|
|
||||||
from typing import Callable, List
|
from collections.abc import Callable
|
||||||
|
|
||||||
from test_driver import Machine
|
from test_driver import Machine
|
||||||
|
|
||||||
start_all: Callable[[], None]
|
start_all: Callable[[], None]
|
||||||
machines: List[Machine]
|
machines: list[Machine]
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import os
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Any, Callable, Dict, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def prepare_machine_root(machinename: str, root: Path) -> None:
|
def prepare_machine_root(machinename: str, root: Path) -> None:
|
||||||
@@ -88,7 +89,7 @@ class Machine:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
raise RuntimeError(f"Failed to parse child process id {childs[0]}")
|
raise RuntimeError(f"Failed to parse child process id {childs[0]}")
|
||||||
|
|
||||||
def get_unit_info(self, unit: str) -> Dict[str, str]:
|
def get_unit_info(self, unit: str) -> dict[str, str]:
|
||||||
proc = self.systemctl(f'--no-pager show "{unit}"')
|
proc = self.systemctl(f'--no-pager show "{unit}"')
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
@@ -98,7 +99,7 @@ class Machine:
|
|||||||
|
|
||||||
line_pattern = re.compile(r"^([^=]+)=(.*)$")
|
line_pattern = re.compile(r"^([^=]+)=(.*)$")
|
||||||
|
|
||||||
def tuple_from_line(line: str) -> Tuple[str, str]:
|
def tuple_from_line(line: str) -> tuple[str, str]:
|
||||||
match = line_pattern.match(line)
|
match = line_pattern.match(line)
|
||||||
assert match is not None
|
assert match is not None
|
||||||
return match[1], match[2]
|
return match[1], match[2]
|
||||||
@@ -114,7 +115,7 @@ class Machine:
|
|||||||
command: str,
|
command: str,
|
||||||
check_return: bool = True,
|
check_return: bool = True,
|
||||||
check_output: bool = True,
|
check_output: bool = True,
|
||||||
timeout: Optional[int] = 900,
|
timeout: int | None = 900,
|
||||||
) -> subprocess.CompletedProcess:
|
) -> subprocess.CompletedProcess:
|
||||||
"""
|
"""
|
||||||
Execute a shell command, returning a list `(status, stdout)`.
|
Execute a shell command, returning a list `(status, stdout)`.
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from collections.abc import Sequence
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any, Optional, Sequence
|
from typing import Any
|
||||||
|
|
||||||
from . import config, flakes, machines, secrets, vms, webui
|
from . import config, flakes, machines, secrets, vms, webui
|
||||||
from .custom_logger import setup_logging
|
from .custom_logger import setup_logging
|
||||||
@@ -12,7 +13,7 @@ from .ssh import cli as ssh_cli
|
|||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
argcomplete: Optional[ModuleType] = None
|
argcomplete: ModuleType | None = None
|
||||||
try:
|
try:
|
||||||
import argcomplete # type: ignore[no-redef]
|
import argcomplete # type: ignore[no-redef]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -28,7 +29,7 @@ class AppendOptionAction(argparse.Action):
|
|||||||
parser: argparse.ArgumentParser,
|
parser: argparse.ArgumentParser,
|
||||||
namespace: argparse.Namespace,
|
namespace: argparse.Namespace,
|
||||||
values: str | Sequence[str] | None,
|
values: str | Sequence[str] | None,
|
||||||
option_string: Optional[str] = None,
|
option_string: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
lst = getattr(namespace, self.dest)
|
lst = getattr(namespace, self.dest)
|
||||||
lst.append("--option")
|
lst.append("--option")
|
||||||
@@ -37,7 +38,7 @@ class AppendOptionAction(argparse.Action):
|
|||||||
lst.append(values[1])
|
lst.append(values[1])
|
||||||
|
|
||||||
|
|
||||||
def create_parser(prog: Optional[str] = None) -> argparse.ArgumentParser:
|
def create_parser(prog: str | None = None) -> argparse.ArgumentParser:
|
||||||
parser = argparse.ArgumentParser(prog=prog, description="cLAN tool")
|
parser = argparse.ArgumentParser(prog=prog, description="cLAN tool")
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import shlex
|
import shlex
|
||||||
|
from collections.abc import Callable, Coroutine
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Coroutine, Dict, NamedTuple, Optional
|
from typing import Any, NamedTuple
|
||||||
|
|
||||||
from .custom_logger import get_caller
|
from .custom_logger import get_caller
|
||||||
from .errors import ClanError
|
from .errors import ClanError
|
||||||
@@ -13,10 +14,10 @@ log = logging.getLogger(__name__)
|
|||||||
class CmdOut(NamedTuple):
|
class CmdOut(NamedTuple):
|
||||||
stdout: str
|
stdout: str
|
||||||
stderr: str
|
stderr: str
|
||||||
cwd: Optional[Path] = None
|
cwd: Path | None = None
|
||||||
|
|
||||||
|
|
||||||
async def run(cmd: list[str], cwd: Optional[Path] = None) -> CmdOut:
|
async def run(cmd: list[str], cwd: Path | None = None) -> CmdOut:
|
||||||
cwd_res = None
|
cwd_res = None
|
||||||
if cwd is not None:
|
if cwd is not None:
|
||||||
if not cwd.exists():
|
if not cwd.exists():
|
||||||
@@ -52,7 +53,7 @@ stdout:
|
|||||||
|
|
||||||
|
|
||||||
def runforcli(
|
def runforcli(
|
||||||
func: Callable[..., Coroutine[Any, Any, Dict[str, CmdOut]]], *args: Any
|
func: Callable[..., Coroutine[Any, Any, dict[str, CmdOut]]], *args: Any
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
res = asyncio.run(func(*args))
|
res = asyncio.run(func(*args))
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from clan_cli.nix import nix_eval
|
from clan_cli.nix import nix_eval
|
||||||
|
|
||||||
|
|
||||||
def get_clan_module_names(
|
def get_clan_module_names(
|
||||||
flake_dir: Path,
|
flake_dir: Path,
|
||||||
) -> tuple[list[str], Optional[str]]:
|
) -> tuple[list[str], str | None]:
|
||||||
"""
|
"""
|
||||||
Get the list of clan modules from the clan-core flake input
|
Get the list of clan modules from the clan-core flake input
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import shlex
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, Tuple, get_origin
|
from typing import Any, get_origin
|
||||||
|
|
||||||
from clan_cli.dirs import machine_settings_file
|
from clan_cli.dirs import machine_settings_file
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
@@ -34,7 +34,7 @@ def map_type(type: str) -> Any:
|
|||||||
return str
|
return str
|
||||||
elif type.startswith("null or "):
|
elif type.startswith("null or "):
|
||||||
subtype = type.removeprefix("null or ")
|
subtype = type.removeprefix("null or ")
|
||||||
return Optional[map_type(subtype)]
|
return map_type(subtype) | None
|
||||||
elif type.startswith("attribute set of"):
|
elif type.startswith("attribute set of"):
|
||||||
subtype = type.removeprefix("attribute set of ")
|
subtype = type.removeprefix("attribute set of ")
|
||||||
return dict[str, map_type(subtype)] # type: ignore
|
return dict[str, map_type(subtype)] # type: ignore
|
||||||
@@ -196,8 +196,8 @@ def get_or_set_option(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def find_option(
|
def find_option(
|
||||||
option: str, value: Any, options: dict, option_description: Optional[str] = None
|
option: str, value: Any, options: dict, option_description: str | None = None
|
||||||
) -> Tuple[str, Any]:
|
) -> tuple[str, Any]:
|
||||||
"""
|
"""
|
||||||
The option path specified by the user doesn't have to match exactly to an
|
The option path specified by the user doesn't have to match exactly to an
|
||||||
entry in the options.json file. Examples
|
entry in the options.json file. Examples
|
||||||
@@ -307,7 +307,7 @@ def set_option(
|
|||||||
|
|
||||||
# takes a (sub)parser and configures it
|
# takes a (sub)parser and configures it
|
||||||
def register_parser(
|
def register_parser(
|
||||||
parser: Optional[argparse.ArgumentParser],
|
parser: argparse.ArgumentParser | None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if parser is None:
|
if parser is None:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
@@ -361,7 +361,7 @@ def register_parser(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Optional[list[str]] = None) -> None:
|
def main(argv: list[str] | None = None) -> None:
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from clan_cli.dirs import machine_settings_file, nixpkgs_source, specific_machine_dir
|
from clan_cli.dirs import machine_settings_file, nixpkgs_source, specific_machine_dir
|
||||||
from clan_cli.errors import ClanError, ClanHttpError
|
from clan_cli.errors import ClanError, ClanHttpError
|
||||||
@@ -15,8 +14,8 @@ from clan_cli.nix import nix_eval
|
|||||||
def verify_machine_config(
|
def verify_machine_config(
|
||||||
flake_dir: Path,
|
flake_dir: Path,
|
||||||
machine_name: str,
|
machine_name: str,
|
||||||
config: Optional[dict] = None,
|
config: dict | None = None,
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
Verify that the machine evaluates successfully
|
Verify that the machine evaluates successfully
|
||||||
Returns a tuple of (success, error_message)
|
Returns a tuple of (success, error_message)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, Type, Union
|
from typing import Any
|
||||||
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..nix import nix_eval
|
from ..nix import nix_eval
|
||||||
@@ -19,7 +19,7 @@ type_map: dict[str, type] = {
|
|||||||
|
|
||||||
|
|
||||||
def schema_from_module_file(
|
def schema_from_module_file(
|
||||||
file: Union[str, Path] = f"{script_dir}/jsonschema/example-schema.json",
|
file: str | Path = f"{script_dir}/jsonschema/example-schema.json",
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
absolute_path = Path(file).absolute()
|
absolute_path = Path(file).absolute()
|
||||||
# define a nix expression that loads the given module file using lib.evalModules
|
# define a nix expression that loads the given module file using lib.evalModules
|
||||||
@@ -36,7 +36,7 @@ def schema_from_module_file(
|
|||||||
return json.loads(proc.stdout)
|
return json.loads(proc.stdout)
|
||||||
|
|
||||||
|
|
||||||
def subtype_from_schema(schema: dict[str, Any]) -> Type:
|
def subtype_from_schema(schema: dict[str, Any]) -> type:
|
||||||
if schema["type"] == "object":
|
if schema["type"] == "object":
|
||||||
if "additionalProperties" in schema:
|
if "additionalProperties" in schema:
|
||||||
sub_type = subtype_from_schema(schema["additionalProperties"])
|
sub_type = subtype_from_schema(schema["additionalProperties"])
|
||||||
@@ -57,8 +57,8 @@ def subtype_from_schema(schema: dict[str, Any]) -> Type:
|
|||||||
def type_from_schema_path(
|
def type_from_schema_path(
|
||||||
schema: dict[str, Any],
|
schema: dict[str, Any],
|
||||||
path: list[str],
|
path: list[str],
|
||||||
full_path: Optional[list[str]] = None,
|
full_path: list[str] | None = None,
|
||||||
) -> Type:
|
) -> type:
|
||||||
if full_path is None:
|
if full_path is None:
|
||||||
full_path = path
|
full_path = path
|
||||||
if len(path) == 0:
|
if len(path) == 0:
|
||||||
@@ -76,8 +76,8 @@ def type_from_schema_path(
|
|||||||
raise ClanError(f"Unknown type for path {path}")
|
raise ClanError(f"Unknown type for path {path}")
|
||||||
|
|
||||||
|
|
||||||
def options_types_from_schema(schema: dict[str, Any]) -> dict[str, Type]:
|
def options_types_from_schema(schema: dict[str, Any]) -> dict[str, type]:
|
||||||
result: dict[str, Type] = {}
|
result: dict[str, type] = {}
|
||||||
for name, value in schema.get("properties", {}).items():
|
for name, value in schema.get("properties", {}).items():
|
||||||
assert isinstance(value, dict)
|
assert isinstance(value, dict)
|
||||||
type_ = value["type"]
|
type_ = value["type"]
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from clan_cli.dirs import nixpkgs_source
|
from clan_cli.dirs import nixpkgs_source
|
||||||
from clan_cli.errors import ClanError, ClanHttpError
|
from clan_cli.errors import ClanError, ClanHttpError
|
||||||
@@ -14,7 +13,7 @@ from clan_cli.nix import nix_eval
|
|||||||
def machine_schema(
|
def machine_schema(
|
||||||
flake_dir: Path,
|
flake_dir: Path,
|
||||||
config: dict,
|
config: dict,
|
||||||
clan_imports: Optional[list[str]] = None,
|
clan_imports: list[str] | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
# use nix eval to lib.evalModules .#nixosConfigurations.<machine_name>.options.clan
|
# use nix eval to lib.evalModules .#nixosConfigurations.<machine_name>.options.clan
|
||||||
with NamedTemporaryFile(mode="w", dir=flake_dir) as clan_machine_settings_file:
|
with NamedTemporaryFile(mode="w", dir=flake_dir) as clan_machine_settings_file:
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable
|
from typing import Any
|
||||||
|
|
||||||
grey = "\x1b[38;20m"
|
grey = "\x1b[38;20m"
|
||||||
yellow = "\x1b[33;20m"
|
yellow = "\x1b[33;20m"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
from collections.abc import Callable
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any, Callable
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class FakeDeal:
|
class FakeDeal:
|
||||||
|
|||||||
@@ -6,15 +6,16 @@ import stat
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
import ipdb
|
import ipdb
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def command_exec(cmd: List[str], work_dir: Path, env: Dict[str, str]) -> None:
|
def command_exec(cmd: list[str], work_dir: Path, env: dict[str, str]) -> None:
|
||||||
subprocess.run(cmd, check=True, env=env, cwd=work_dir.resolve())
|
subprocess.run(cmd, check=True, env=env, cwd=work_dir.resolve())
|
||||||
|
|
||||||
|
|
||||||
@@ -32,8 +33,8 @@ def block_for_input() -> None:
|
|||||||
|
|
||||||
def breakpoint_container(
|
def breakpoint_container(
|
||||||
work_dir: Path,
|
work_dir: Path,
|
||||||
env: Optional[Dict[str, str]] = None,
|
env: dict[str, str] | None = None,
|
||||||
cmd: Optional[List[str]] = None,
|
cmd: list[str] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if env is None:
|
if env is None:
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
@@ -52,8 +53,8 @@ def breakpoint_container(
|
|||||||
|
|
||||||
def breakpoint_shell(
|
def breakpoint_shell(
|
||||||
work_dir: Path = Path(os.getcwd()),
|
work_dir: Path = Path(os.getcwd()),
|
||||||
env: Optional[Dict[str, str]] = None,
|
env: dict[str, str] | None = None,
|
||||||
cmd: Optional[List[str]] = None,
|
cmd: list[str] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if env is None:
|
if env is None:
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
@@ -91,7 +92,7 @@ def spawn_process(func: Callable, **kwargs: Any) -> mp.Process:
|
|||||||
return proc
|
return proc
|
||||||
|
|
||||||
|
|
||||||
def dump_env(env: Dict[str, str], loc: Path) -> None:
|
def dump_env(env: dict[str, str], loc: Path) -> None:
|
||||||
cenv = env.copy()
|
cenv = env.copy()
|
||||||
log.info("Dumping environment variables to %s", loc)
|
log.info("Dumping environment variables to %s", loc)
|
||||||
with open(loc, "w") as f:
|
with open(loc, "w") as f:
|
||||||
|
|||||||
@@ -2,20 +2,19 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_clan_flake_toplevel() -> Optional[Path]:
|
def get_clan_flake_toplevel() -> Path | None:
|
||||||
return find_toplevel([".clan-flake", ".git", ".hg", ".svn", "flake.nix"])
|
return find_toplevel([".clan-flake", ".git", ".hg", ".svn", "flake.nix"])
|
||||||
|
|
||||||
|
|
||||||
def find_git_repo_root() -> Optional[Path]:
|
def find_git_repo_root() -> Path | None:
|
||||||
return find_toplevel([".git"])
|
return find_toplevel([".git"])
|
||||||
|
|
||||||
|
|
||||||
def find_toplevel(top_level_files: list[str]) -> Optional[Path]:
|
def find_toplevel(top_level_files: list[str]) -> Path | None:
|
||||||
"""Returns the path to the toplevel of the clan flake"""
|
"""Returns the path to the toplevel of the clan flake"""
|
||||||
for project_file in top_level_files:
|
for project_file in top_level_files:
|
||||||
initial_path = Path(os.getcwd())
|
initial_path = Path(os.getcwd())
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
# !/usr/bin/env python3
|
# !/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
from clan_cli.dirs import user_history_file
|
from clan_cli.dirs import user_history_file
|
||||||
|
|
||||||
from ..async_cmd import CmdOut, runforcli
|
from ..async_cmd import CmdOut, runforcli
|
||||||
|
|
||||||
|
|
||||||
async def add_flake(path: Path) -> Dict[str, CmdOut]:
|
async def add_flake(path: Path) -> dict[str, CmdOut]:
|
||||||
user_history_file().parent.mkdir(parents=True, exist_ok=True)
|
user_history_file().parent.mkdir(parents=True, exist_ok=True)
|
||||||
# append line to history file
|
# append line to history file
|
||||||
# TODO: Make this atomic
|
# TODO: Make this atomic
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# !/usr/bin/env python3
|
# !/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
from ..async_cmd import CmdOut, run, runforcli
|
from ..async_cmd import CmdOut, run, runforcli
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
@@ -10,7 +9,7 @@ from ..nix import nix_command, nix_shell
|
|||||||
DEFAULT_URL: str = "git+https://git.clan.lol/clan/clan-core?new-clan"
|
DEFAULT_URL: str = "git+https://git.clan.lol/clan/clan-core?new-clan"
|
||||||
|
|
||||||
|
|
||||||
async def create_flake(directory: Path, url: str) -> Dict[str, CmdOut]:
|
async def create_flake(directory: Path, url: str) -> dict[str, CmdOut]:
|
||||||
if not directory.exists():
|
if not directory.exists():
|
||||||
directory.mkdir()
|
directory.mkdir()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
# from clan_cli.dirs import find_git_repo_root
|
# from clan_cli.dirs import find_git_repo_root
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
@@ -12,7 +11,7 @@ from clan_cli.nix import nix_shell
|
|||||||
def commit_file(
|
def commit_file(
|
||||||
file_path: Path,
|
file_path: Path,
|
||||||
repo_dir: Path,
|
repo_dir: Path,
|
||||||
commit_message: Optional[str] = None,
|
commit_message: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
# check that the file is in the git repository and exists
|
# check that the file is in the git repository and exists
|
||||||
if not Path(file_path).resolve().is_relative_to(repo_dir.resolve()):
|
if not Path(file_path).resolve().is_relative_to(repo_dir.resolve()):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import os
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from ..nix import nix_build, nix_config, nix_eval
|
from ..nix import nix_build, nix_config, nix_eval
|
||||||
from ..ssh import Host, parse_deployment_address
|
from ..ssh import Host, parse_deployment_address
|
||||||
@@ -31,7 +30,7 @@ class Machine:
|
|||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
flake_dir: Path,
|
flake_dir: Path,
|
||||||
machine_data: Optional[dict] = None,
|
machine_data: dict | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Creates a Machine
|
Creates a Machine
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import json
|
|||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from collections.abc import Iterator
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import IO, Iterator
|
from typing import IO
|
||||||
|
|
||||||
from ..dirs import user_config_dir
|
from ..dirs import user_config_dir
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from collections.abc import Callable
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from .sops import get_public_key
|
from .sops import get_public_key
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
from collections.abc import Callable, Iterator
|
||||||
from contextlib import ExitStack, contextmanager
|
from contextlib import ExitStack, contextmanager
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -18,16 +19,9 @@ from threading import Thread
|
|||||||
from typing import (
|
from typing import (
|
||||||
IO,
|
IO,
|
||||||
Any,
|
Any,
|
||||||
Callable,
|
|
||||||
Dict,
|
|
||||||
Generic,
|
Generic,
|
||||||
Iterator,
|
|
||||||
List,
|
|
||||||
Literal,
|
Literal,
|
||||||
Optional,
|
|
||||||
Tuple,
|
|
||||||
TypeVar,
|
TypeVar,
|
||||||
Union,
|
|
||||||
overload,
|
overload,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +42,7 @@ class CommandFormatter(logging.Formatter):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
"%(prefix_color)s[%(command_prefix)s]%(color_reset)s %(color)s%(message)s%(color_reset)s"
|
"%(prefix_color)s[%(command_prefix)s]%(color_reset)s %(color)s%(message)s%(color_reset)s"
|
||||||
)
|
)
|
||||||
self.hostnames: List[str] = []
|
self.hostnames: list[str] = []
|
||||||
self.hostname_color_offset = 1 # first host shouldn't get agressive red
|
self.hostname_color_offset = 1 # first host shouldn't get agressive red
|
||||||
|
|
||||||
def format(self, record: logging.LogRecord) -> str:
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
@@ -80,7 +74,7 @@ class CommandFormatter(logging.Formatter):
|
|||||||
return 31 + (index + self.hostname_color_offset) % 7
|
return 31 + (index + self.hostname_color_offset) % 7
|
||||||
|
|
||||||
|
|
||||||
def setup_loggers() -> Tuple[logging.Logger, logging.Logger]:
|
def setup_loggers() -> tuple[logging.Logger, logging.Logger]:
|
||||||
# If we use the default logger here (logging.error etc) or a logger called
|
# If we use the default logger here (logging.error etc) or a logger called
|
||||||
# "deploykit", then cmdlog messages are also posted on the default logger.
|
# "deploykit", then cmdlog messages are also posted on the default logger.
|
||||||
# To avoid this message duplication, we set up a main and command logger
|
# To avoid this message duplication, we set up a main and command logger
|
||||||
@@ -115,7 +109,7 @@ error = kitlog.error
|
|||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _pipe() -> Iterator[Tuple[IO[str], IO[str]]]:
|
def _pipe() -> Iterator[tuple[IO[str], IO[str]]]:
|
||||||
(pipe_r, pipe_w) = os.pipe()
|
(pipe_r, pipe_w) = os.pipe()
|
||||||
read_end = os.fdopen(pipe_r, "r")
|
read_end = os.fdopen(pipe_r, "r")
|
||||||
write_end = os.fdopen(pipe_w, "w")
|
write_end = os.fdopen(pipe_w, "w")
|
||||||
@@ -130,7 +124,7 @@ def _pipe() -> Iterator[Tuple[IO[str], IO[str]]]:
|
|||||||
write_end.close()
|
write_end.close()
|
||||||
|
|
||||||
|
|
||||||
FILE = Union[None, int]
|
FILE = None | int
|
||||||
|
|
||||||
# Seconds until a message is printed when _run produces no output.
|
# Seconds until a message is printed when _run produces no output.
|
||||||
NO_OUTPUT_TIMEOUT = 20
|
NO_OUTPUT_TIMEOUT = 20
|
||||||
@@ -149,13 +143,13 @@ class Host:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
host: str,
|
host: str,
|
||||||
user: Optional[str] = None,
|
user: str | None = None,
|
||||||
port: Optional[int] = None,
|
port: int | None = None,
|
||||||
key: Optional[str] = None,
|
key: str | None = None,
|
||||||
forward_agent: bool = False,
|
forward_agent: bool = False,
|
||||||
command_prefix: Optional[str] = None,
|
command_prefix: str | None = None,
|
||||||
host_key_check: HostKeyCheck = HostKeyCheck.STRICT,
|
host_key_check: HostKeyCheck = HostKeyCheck.STRICT,
|
||||||
meta: Dict[str, Any] = {},
|
meta: dict[str, Any] = {},
|
||||||
verbose_ssh: bool = False,
|
verbose_ssh: bool = False,
|
||||||
ssh_options: dict[str, str] = {},
|
ssh_options: dict[str, str] = {},
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -186,12 +180,12 @@ class Host:
|
|||||||
def _prefix_output(
|
def _prefix_output(
|
||||||
self,
|
self,
|
||||||
displayed_cmd: str,
|
displayed_cmd: str,
|
||||||
print_std_fd: Optional[IO[str]],
|
print_std_fd: IO[str] | None,
|
||||||
print_err_fd: Optional[IO[str]],
|
print_err_fd: IO[str] | None,
|
||||||
stdout: Optional[IO[str]],
|
stdout: IO[str] | None,
|
||||||
stderr: Optional[IO[str]],
|
stderr: IO[str] | None,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
) -> Tuple[str, str]:
|
) -> tuple[str, str]:
|
||||||
rlist = []
|
rlist = []
|
||||||
if print_std_fd is not None:
|
if print_std_fd is not None:
|
||||||
rlist.append(print_std_fd)
|
rlist.append(print_std_fd)
|
||||||
@@ -215,7 +209,7 @@ class Host:
|
|||||||
|
|
||||||
def print_from(
|
def print_from(
|
||||||
print_fd: IO[str], print_buf: str, is_err: bool = False
|
print_fd: IO[str], print_buf: str, is_err: bool = False
|
||||||
) -> Tuple[float, str]:
|
) -> tuple[float, str]:
|
||||||
read = os.read(print_fd.fileno(), 4096)
|
read = os.read(print_fd.fileno(), 4096)
|
||||||
if len(read) == 0:
|
if len(read) == 0:
|
||||||
rlist.remove(print_fd)
|
rlist.remove(print_fd)
|
||||||
@@ -256,7 +250,7 @@ class Host:
|
|||||||
extra=dict(command_prefix=self.command_prefix),
|
extra=dict(command_prefix=self.command_prefix),
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_fd(fd: Optional[IO[Any]]) -> str:
|
def handle_fd(fd: IO[Any] | None) -> str:
|
||||||
if fd and fd in r:
|
if fd and fd in r:
|
||||||
read = os.read(fd.fileno(), 4096)
|
read = os.read(fd.fileno(), 4096)
|
||||||
if len(read) == 0:
|
if len(read) == 0:
|
||||||
@@ -274,13 +268,13 @@ class Host:
|
|||||||
|
|
||||||
def _run(
|
def _run(
|
||||||
self,
|
self,
|
||||||
cmd: List[str],
|
cmd: list[str],
|
||||||
displayed_cmd: str,
|
displayed_cmd: str,
|
||||||
shell: bool,
|
shell: bool,
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
) -> subprocess.CompletedProcess[str]:
|
) -> subprocess.CompletedProcess[str]:
|
||||||
@@ -362,11 +356,11 @@ class Host:
|
|||||||
|
|
||||||
def run_local(
|
def run_local(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, List[str]],
|
cmd: str | list[str],
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
) -> subprocess.CompletedProcess[str]:
|
) -> subprocess.CompletedProcess[str]:
|
||||||
@@ -404,12 +398,12 @@ class Host:
|
|||||||
|
|
||||||
def run(
|
def run(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, List[str]],
|
cmd: str | list[str],
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
become_root: bool = False,
|
become_root: bool = False,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
verbose_ssh: bool = False,
|
verbose_ssh: bool = False,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
@@ -475,7 +469,7 @@ class Host:
|
|||||||
def ssh_cmd(
|
def ssh_cmd(
|
||||||
self,
|
self,
|
||||||
verbose_ssh: bool = False,
|
verbose_ssh: bool = False,
|
||||||
) -> List:
|
) -> list:
|
||||||
if self.user is not None:
|
if self.user is not None:
|
||||||
ssh_target = f"{self.user}@{self.host}"
|
ssh_target = f"{self.user}@{self.host}"
|
||||||
else:
|
else:
|
||||||
@@ -505,12 +499,12 @@ T = TypeVar("T")
|
|||||||
|
|
||||||
|
|
||||||
class HostResult(Generic[T]):
|
class HostResult(Generic[T]):
|
||||||
def __init__(self, host: Host, result: Union[T, Exception]) -> None:
|
def __init__(self, host: Host, result: T | Exception) -> None:
|
||||||
self.host = host
|
self.host = host
|
||||||
self._result = result
|
self._result = result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def error(self) -> Optional[Exception]:
|
def error(self) -> Exception | None:
|
||||||
"""
|
"""
|
||||||
Returns an error if the command failed
|
Returns an error if the command failed
|
||||||
"""
|
"""
|
||||||
@@ -528,13 +522,13 @@ class HostResult(Generic[T]):
|
|||||||
return self._result
|
return self._result
|
||||||
|
|
||||||
|
|
||||||
Results = List[HostResult[subprocess.CompletedProcess[str]]]
|
Results = list[HostResult[subprocess.CompletedProcess[str]]]
|
||||||
|
|
||||||
|
|
||||||
def _worker(
|
def _worker(
|
||||||
func: Callable[[Host], T],
|
func: Callable[[Host], T],
|
||||||
host: Host,
|
host: Host,
|
||||||
results: List[HostResult[T]],
|
results: list[HostResult[T]],
|
||||||
idx: int,
|
idx: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
@@ -545,18 +539,18 @@ def _worker(
|
|||||||
|
|
||||||
|
|
||||||
class HostGroup:
|
class HostGroup:
|
||||||
def __init__(self, hosts: List[Host]) -> None:
|
def __init__(self, hosts: list[Host]) -> None:
|
||||||
self.hosts = hosts
|
self.hosts = hosts
|
||||||
|
|
||||||
def _run_local(
|
def _run_local(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, List[str]],
|
cmd: str | list[str],
|
||||||
host: Host,
|
host: Host,
|
||||||
results: Results,
|
results: Results,
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
verbose_ssh: bool = False,
|
verbose_ssh: bool = False,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
@@ -578,13 +572,13 @@ class HostGroup:
|
|||||||
|
|
||||||
def _run_remote(
|
def _run_remote(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, List[str]],
|
cmd: str | list[str],
|
||||||
host: Host,
|
host: Host,
|
||||||
results: Results,
|
results: Results,
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
verbose_ssh: bool = False,
|
verbose_ssh: bool = False,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
@@ -605,7 +599,7 @@ class HostGroup:
|
|||||||
kitlog.exception(e)
|
kitlog.exception(e)
|
||||||
results.append(HostResult(host, e))
|
results.append(HostResult(host, e))
|
||||||
|
|
||||||
def _reraise_errors(self, results: List[HostResult[Any]]) -> None:
|
def _reraise_errors(self, results: list[HostResult[Any]]) -> None:
|
||||||
errors = 0
|
errors = 0
|
||||||
for result in results:
|
for result in results:
|
||||||
e = result.error
|
e = result.error
|
||||||
@@ -622,12 +616,12 @@ class HostGroup:
|
|||||||
|
|
||||||
def _run(
|
def _run(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, List[str]],
|
cmd: str | list[str],
|
||||||
local: bool = False,
|
local: bool = False,
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
verbose_ssh: bool = False,
|
verbose_ssh: bool = False,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
@@ -664,11 +658,11 @@ class HostGroup:
|
|||||||
|
|
||||||
def run(
|
def run(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, List[str]],
|
cmd: str | list[str],
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
verbose_ssh: bool = False,
|
verbose_ssh: bool = False,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
@@ -696,11 +690,11 @@ class HostGroup:
|
|||||||
|
|
||||||
def run_local(
|
def run_local(
|
||||||
self,
|
self,
|
||||||
cmd: Union[str, List[str]],
|
cmd: str | list[str],
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
timeout: float = math.inf,
|
timeout: float = math.inf,
|
||||||
) -> Results:
|
) -> Results:
|
||||||
@@ -728,14 +722,14 @@ class HostGroup:
|
|||||||
|
|
||||||
def run_function(
|
def run_function(
|
||||||
self, func: Callable[[Host], T], check: bool = True
|
self, func: Callable[[Host], T], check: bool = True
|
||||||
) -> List[HostResult[T]]:
|
) -> list[HostResult[T]]:
|
||||||
"""
|
"""
|
||||||
Function to run for each host in the group in parallel
|
Function to run for each host in the group in parallel
|
||||||
|
|
||||||
@func the function to call
|
@func the function to call
|
||||||
"""
|
"""
|
||||||
threads = []
|
threads = []
|
||||||
results: List[HostResult[T]] = [
|
results: list[HostResult[T]] = [
|
||||||
HostResult(h, Exception(f"No result set for thread {i}"))
|
HostResult(h, Exception(f"No result set for thread {i}"))
|
||||||
for (i, h) in enumerate(self.hosts)
|
for (i, h) in enumerate(self.hosts)
|
||||||
]
|
]
|
||||||
@@ -764,14 +758,14 @@ def parse_deployment_address(
|
|||||||
machine_name: str, host: str, meta: dict[str, Any] = {}
|
machine_name: str, host: str, meta: dict[str, Any] = {}
|
||||||
) -> Host:
|
) -> Host:
|
||||||
parts = host.split("@")
|
parts = host.split("@")
|
||||||
user: Optional[str] = None
|
user: str | None = None
|
||||||
if len(parts) > 1:
|
if len(parts) > 1:
|
||||||
user = parts[0]
|
user = parts[0]
|
||||||
hostname = parts[1]
|
hostname = parts[1]
|
||||||
else:
|
else:
|
||||||
hostname = parts[0]
|
hostname = parts[0]
|
||||||
maybe_options = hostname.split("?")
|
maybe_options = hostname.split("?")
|
||||||
options: Dict[str, str] = {}
|
options: dict[str, str] = {}
|
||||||
if len(maybe_options) > 1:
|
if len(maybe_options) > 1:
|
||||||
hostname = maybe_options[0]
|
hostname = maybe_options[0]
|
||||||
for option in maybe_options[1].split("&"):
|
for option in maybe_options[1].split("&"):
|
||||||
@@ -796,12 +790,12 @@ def parse_deployment_address(
|
|||||||
|
|
||||||
@overload
|
@overload
|
||||||
def run(
|
def run(
|
||||||
cmd: Union[List[str], str],
|
cmd: list[str] | str,
|
||||||
text: Literal[True] = ...,
|
text: Literal[True] = ...,
|
||||||
stdout: FILE = ...,
|
stdout: FILE = ...,
|
||||||
stderr: FILE = ...,
|
stderr: FILE = ...,
|
||||||
extra_env: Dict[str, str] = ...,
|
extra_env: dict[str, str] = ...,
|
||||||
cwd: Union[None, str, Path] = ...,
|
cwd: None | str | Path = ...,
|
||||||
check: bool = ...,
|
check: bool = ...,
|
||||||
) -> subprocess.CompletedProcess[str]:
|
) -> subprocess.CompletedProcess[str]:
|
||||||
...
|
...
|
||||||
@@ -809,24 +803,24 @@ def run(
|
|||||||
|
|
||||||
@overload
|
@overload
|
||||||
def run(
|
def run(
|
||||||
cmd: Union[List[str], str],
|
cmd: list[str] | str,
|
||||||
text: Literal[False],
|
text: Literal[False],
|
||||||
stdout: FILE = ...,
|
stdout: FILE = ...,
|
||||||
stderr: FILE = ...,
|
stderr: FILE = ...,
|
||||||
extra_env: Dict[str, str] = ...,
|
extra_env: dict[str, str] = ...,
|
||||||
cwd: Union[None, str, Path] = ...,
|
cwd: None | str | Path = ...,
|
||||||
check: bool = ...,
|
check: bool = ...,
|
||||||
) -> subprocess.CompletedProcess[bytes]:
|
) -> subprocess.CompletedProcess[bytes]:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
cmd: Union[List[str], str],
|
cmd: list[str] | str,
|
||||||
text: bool = True,
|
text: bool = True,
|
||||||
stdout: FILE = None,
|
stdout: FILE = None,
|
||||||
stderr: FILE = None,
|
stderr: FILE = None,
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
cwd: Union[None, str, Path] = None,
|
cwd: None | str | Path = None,
|
||||||
check: bool = True,
|
check: bool = True,
|
||||||
) -> subprocess.CompletedProcess[Any]:
|
) -> subprocess.CompletedProcess[Any]:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
|
|
||||||
@@ -9,7 +8,7 @@ from ..nix import nix_shell
|
|||||||
def ssh(
|
def ssh(
|
||||||
host: str,
|
host: str,
|
||||||
user: str = "root",
|
user: str = "root",
|
||||||
password: Optional[str] = None,
|
password: str | None = None,
|
||||||
ssh_args: list[str] = [],
|
ssh_args: list[str] = [],
|
||||||
) -> None:
|
) -> None:
|
||||||
packages = ["tor", "openssh"]
|
packages = ["tor", "openssh"]
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
from collections.abc import Iterator
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Iterator, Optional, Type, TypeVar
|
from typing import Any, TypeVar
|
||||||
from uuid import UUID, uuid4
|
from uuid import UUID, uuid4
|
||||||
|
|
||||||
from .custom_logger import ThreadFormatter, get_caller
|
from .custom_logger import ThreadFormatter, get_caller
|
||||||
@@ -36,8 +37,8 @@ class Command:
|
|||||||
def run(
|
def run(
|
||||||
self,
|
self,
|
||||||
cmd: list[str],
|
cmd: list[str],
|
||||||
env: Optional[dict[str, str]] = None,
|
env: dict[str, str] | None = None,
|
||||||
cwd: Optional[Path] = None,
|
cwd: Path | None = None,
|
||||||
name: str = "command",
|
name: str = "command",
|
||||||
) -> None:
|
) -> None:
|
||||||
self.running = True
|
self.running = True
|
||||||
@@ -188,7 +189,7 @@ T = TypeVar("T", bound="BaseTask")
|
|||||||
|
|
||||||
|
|
||||||
@deal.raises(ClanError)
|
@deal.raises(ClanError)
|
||||||
def create_task(task_type: Type[T], *args: Any) -> T:
|
def create_task(task_type: type[T], *args: Any) -> T:
|
||||||
global POOL
|
global POOL
|
||||||
|
|
||||||
# check if task_type is a callable
|
# check if task_type is a callable
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import sys
|
import sys
|
||||||
from typing import IO, Any, Callable
|
from collections.abc import Callable
|
||||||
|
from typing import IO, Any
|
||||||
|
|
||||||
|
|
||||||
def is_interactive() -> bool:
|
def is_interactive() -> bool:
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import shlex
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Condition, Thread
|
from threading import Condition, Thread
|
||||||
from typing import Iterator
|
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from ..nix import nix_build, nix_config, nix_eval, nix_shell
|
from ..nix import nix_build, nix_config, nix_eval, nix_shell
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from typing import Callable, NoReturn, Optional
|
from collections.abc import Callable
|
||||||
|
from typing import NoReturn
|
||||||
|
|
||||||
start_server: Optional[Callable] = None
|
start_server: Callable | None = None
|
||||||
ServerImportError: Optional[ImportError] = None
|
ServerImportError: ImportError | None = None
|
||||||
try:
|
try:
|
||||||
from .server import start_server
|
from .server import start_server
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Extra, Field
|
from pydantic import BaseModel, Extra, Field
|
||||||
|
|
||||||
@@ -71,9 +70,9 @@ class FlakeListResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class FlakeCreateResponse(BaseModel):
|
class FlakeCreateResponse(BaseModel):
|
||||||
cmd_out: Dict[str, CmdOut]
|
cmd_out: dict[str, CmdOut]
|
||||||
|
|
||||||
|
|
||||||
class FlakeResponse(BaseModel):
|
class FlakeResponse(BaseModel):
|
||||||
content: str
|
content: str
|
||||||
actions: List[FlakeAction]
|
actions: list[FlakeAction]
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
from collections.abc import Iterator
|
||||||
from contextlib import ExitStack, contextmanager
|
from contextlib import ExitStack, contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Iterator
|
|
||||||
|
|
||||||
# XXX: can we dynamically load this using nix develop?
|
# XXX: can we dynamically load this using nix develop?
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class Tags(Enum):
|
class Tags(Enum):
|
||||||
@@ -13,7 +13,7 @@ class Tags(Enum):
|
|||||||
return self.value
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
tags_metadata: List[Dict[str, Any]] = [
|
tags_metadata: list[dict[str, Any]] = [
|
||||||
{
|
{
|
||||||
"name": str(Tags.flake),
|
"name": str(Tags.flake),
|
||||||
"description": "Operations on a flake.",
|
"description": "Operations on a flake.",
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import IO, Any, Dict, Iterator, List, Optional, Union
|
from typing import IO, Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
_FILE = Union[None, int, IO[Any]]
|
_FILE = None | int | IO[Any]
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
class Command:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.processes: List[subprocess.Popen[str]] = []
|
self.processes: list[subprocess.Popen[str]] = []
|
||||||
|
|
||||||
def run(
|
def run(
|
||||||
self,
|
self,
|
||||||
command: List[str],
|
command: list[str],
|
||||||
extra_env: Dict[str, str] = {},
|
extra_env: dict[str, str] = {},
|
||||||
stdin: _FILE = None,
|
stdin: _FILE = None,
|
||||||
stdout: _FILE = None,
|
stdout: _FILE = None,
|
||||||
stderr: _FILE = None,
|
stderr: _FILE = None,
|
||||||
workdir: Optional[Path] = None,
|
workdir: Path | None = None,
|
||||||
) -> subprocess.Popen[str]:
|
) -> subprocess.Popen[str]:
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
env.update(extra_env)
|
env.update(extra_env)
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator, NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import socket
|
import socket
|
||||||
from typing import Callable
|
from collections.abc import Callable
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import shutil
|
|||||||
import string
|
import string
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from sys import platform
|
from sys import platform
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import TYPE_CHECKING, Iterator
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from collections.abc import Iterator
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from cli import Cli
|
from cli import Cli
|
||||||
@@ -206,18 +206,18 @@ def test_map_type() -> None:
|
|||||||
assert config.map_type("boolean") == bool
|
assert config.map_type("boolean") == bool
|
||||||
assert config.map_type("attribute set of string") == dict[str, str]
|
assert config.map_type("attribute set of string") == dict[str, str]
|
||||||
assert config.map_type("attribute set of integer") == dict[str, int]
|
assert config.map_type("attribute set of integer") == dict[str, int]
|
||||||
assert config.map_type("null or string") == Optional[str]
|
assert config.map_type("null or string") == str | None
|
||||||
|
|
||||||
|
|
||||||
# test the cast function with simple types
|
# test the cast function with simple types
|
||||||
def test_cast() -> None:
|
def test_cast() -> None:
|
||||||
assert config.cast(value=["true"], type=bool, opt_description="foo-option") is True
|
assert config.cast(value=["true"], type=bool, opt_description="foo-option") is True
|
||||||
assert (
|
assert (
|
||||||
config.cast(value=["null"], type=Optional[str], opt_description="foo-option")
|
config.cast(value=["null"], type=str | None, opt_description="foo-option")
|
||||||
is None
|
is None
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
config.cast(value=["bar"], type=Optional[str], opt_description="foo-option")
|
config.cast(value=["bar"], type=str | None, opt_description="foo-option")
|
||||||
== "bar"
|
== "bar"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from collections.abc import Iterator
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from typing import TYPE_CHECKING, Iterator
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from cli import Cli
|
from cli import Cli
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_subprocess.fake_process
|
import pytest_subprocess.fake_process
|
||||||
@@ -28,7 +27,7 @@ def test_ssh_no_pass(
|
|||||||
user = "user"
|
user = "user"
|
||||||
if os.environ.get("IN_NIX_SANDBOX"):
|
if os.environ.get("IN_NIX_SANDBOX"):
|
||||||
monkeypatch.delenv("IN_NIX_SANDBOX")
|
monkeypatch.delenv("IN_NIX_SANDBOX")
|
||||||
cmd: list[Union[str, utils.Any]] = [
|
cmd: list[str | utils.Any] = [
|
||||||
"nix",
|
"nix",
|
||||||
fp.any(),
|
fp.any(),
|
||||||
"shell",
|
"shell",
|
||||||
@@ -58,7 +57,7 @@ def test_ssh_with_pass(
|
|||||||
user = "user"
|
user = "user"
|
||||||
if os.environ.get("IN_NIX_SANDBOX"):
|
if os.environ.get("IN_NIX_SANDBOX"):
|
||||||
monkeypatch.delenv("IN_NIX_SANDBOX")
|
monkeypatch.delenv("IN_NIX_SANDBOX")
|
||||||
cmd: list[Union[str, utils.Any]] = [
|
cmd: list[str | utils.Any] = [
|
||||||
"nix",
|
"nix",
|
||||||
fp.any(),
|
fp.any(),
|
||||||
"shell",
|
"shell",
|
||||||
@@ -79,7 +78,7 @@ def test_ssh_with_pass(
|
|||||||
|
|
||||||
|
|
||||||
def test_qrcode_scan(fp: pytest_subprocess.fake_process.FakeProcess) -> None:
|
def test_qrcode_scan(fp: pytest_subprocess.fake_process.FakeProcess) -> None:
|
||||||
cmd: list[Union[str, utils.Any]] = [fp.any()]
|
cmd: list[str | utils.Any] = [fp.any()]
|
||||||
fp.register(cmd, stdout="https://test.test")
|
fp.register(cmd, stdout="https://test.test")
|
||||||
result = cli.qrcode_scan("test.png")
|
result = cli.qrcode_scan("test.png")
|
||||||
assert result == "https://test.test"
|
assert result == "https://test.test"
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
from typing import Callable, Optional
|
from collections.abc import Callable
|
||||||
|
|
||||||
start_app: Optional[Callable] = None
|
start_app: Callable | None = None
|
||||||
|
|
||||||
from .app import start_app
|
from .app import start_app
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user