mirror of
https://github.com/Cian-H/I-Form_Server_Node_Deployer.git
synced 2025-12-28 00:52:02 +00:00
Better restructure, and improved debug
This commit is contained in:
24
src/debug.py
24
src/debug.py
@@ -1,24 +0,0 @@
|
||||
from functools import wraps
|
||||
import inspect
|
||||
from typing import Callable
|
||||
|
||||
import typer # type: ignore
|
||||
|
||||
import config
|
||||
|
||||
|
||||
def debug_guard(f: Callable) -> Callable:
|
||||
if not config.DEBUG:
|
||||
return f
|
||||
try:
|
||||
import snoop # type: ignore
|
||||
except ImportError:
|
||||
typer.echo("Debug mode requires the snoop package")
|
||||
raise typer.Exit(1)
|
||||
else:
|
||||
snoop.install(
|
||||
snoop="ss",
|
||||
)
|
||||
|
||||
typer.echo(f"Debug mode enabled: {inspect.stack()[1].filename}")
|
||||
wraps(f)(ss)(f) # noqa: F821 #* ss is installed in debug_mode
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/usr/bin/env poetry run python
|
||||
|
||||
from autoignition import json_to_img
|
||||
import config
|
||||
from create_disk import create_ignition_disk
|
||||
from create_img import create_img
|
||||
import typer
|
||||
|
||||
|
||||
app = typer.Typer(
|
||||
help="A tool for creating ignition images for automated deployment to a swarm"
|
||||
)
|
||||
|
||||
app.command()(create_img)
|
||||
app.command()(create_ignition_disk)
|
||||
app.command()(json_to_img)
|
||||
|
||||
if __name__ == "__main__":
|
||||
config.update_config("cli")
|
||||
app()
|
||||
0
src/node_deployer/__init__.py
Normal file
0
src/node_deployer/__init__.py
Normal file
31
src/node_deployer/__main__.py
Normal file
31
src/node_deployer/__main__.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
def main() -> None:
|
||||
from . import config
|
||||
config.update_config("cli")
|
||||
from .node_deployer import app
|
||||
app()
|
||||
|
||||
def debug():
|
||||
from . import config
|
||||
config.update_config("debug")
|
||||
from .node_deployer import app
|
||||
|
||||
# Below, we set the default value of the debug flag
|
||||
# for the base function of each command to True
|
||||
def unwrap(f):
|
||||
if hasattr(f, "__wrapped__"):
|
||||
return unwrap(f.__wrapped__)
|
||||
else:
|
||||
return f
|
||||
|
||||
for c in app.registered_commands:
|
||||
f = unwrap(c.callback)
|
||||
defaults = list(f.__defaults__)
|
||||
defaults[-1] = True
|
||||
f.__defaults__ = tuple(defaults)
|
||||
|
||||
app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -5,16 +5,17 @@ import tarfile
|
||||
import time
|
||||
from typing import Annotated
|
||||
|
||||
from cli import cli_spinner
|
||||
import config
|
||||
from debug import debug_guard
|
||||
import git
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
import typer
|
||||
from utils import ensure_build_dir
|
||||
|
||||
from . import config
|
||||
from .cli import cli_spinner
|
||||
from .debug import debug_guard
|
||||
from .utils import ensure_build_dir
|
||||
|
||||
|
||||
def create_driver():
|
||||
@@ -114,14 +115,39 @@ def build_fuelignition():
|
||||
@cli_spinner(description="Converting json to img", total=None)
|
||||
@ensure_build_dir
|
||||
def json_to_img(
|
||||
json_path: Path = Annotated[
|
||||
json_path: Annotated[
|
||||
Path,
|
||||
typer.Option(help="The fuel-ignition json for configuring the disk image", prompt=True),
|
||||
],
|
||||
img_path: Path = Annotated[
|
||||
typer.Option(
|
||||
"--json-path", "-i",
|
||||
help="The fuel-ignition json for configuring the disk image",
|
||||
prompt=True,
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
),
|
||||
] = Path("fuelignition.json"),
|
||||
img_path: Annotated[
|
||||
Path,
|
||||
typer.Option(help="The file to output the disk image to", prompt=True),
|
||||
],
|
||||
typer.Option(
|
||||
"--img-path", "-o",
|
||||
help="The file to output the disk image to",
|
||||
prompt=True,
|
||||
dir_okay=False,
|
||||
writable=True,
|
||||
readable=False,
|
||||
),
|
||||
] = Path("ignition.img"),
|
||||
debug: Annotated[
|
||||
bool,
|
||||
typer.Option(
|
||||
"--debug",
|
||||
help="Enable debug mode",
|
||||
is_eager=True,
|
||||
is_flag=True,
|
||||
flag_value=True,
|
||||
expose_value=config.DEBUG,
|
||||
hidden=not config.DEBUG,
|
||||
)
|
||||
] = False,
|
||||
) -> None:
|
||||
"""Takes a fuel-ignition json file and produces an ignition disk image file"""
|
||||
selenium_container = None
|
||||
@@ -4,8 +4,8 @@ from typing import Callable
|
||||
|
||||
from rich.progress import Progress, SpinnerColumn, TextColumn
|
||||
|
||||
import config
|
||||
from utils import Singleton
|
||||
from . import config
|
||||
from .utils import Singleton
|
||||
|
||||
|
||||
class SingletonProgress(Progress, metaclass=Singleton):
|
||||
@@ -1,7 +1,8 @@
|
||||
# flake8: noqa: F821
|
||||
# type: ignore
|
||||
#* This file sets a number of config constants by modifying its own globals
|
||||
#* As a result, F821 is disabled as the intereter cannot be trusted to know
|
||||
#* when F821 should be raised.
|
||||
#* As a result, F821 and typing is disabled as the interpreter cannot be
|
||||
#* trusted to know when F821 or UndefinedVeriable errors should be raised.
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
@@ -10,7 +11,8 @@ import tomllib
|
||||
|
||||
|
||||
CLIENT = docker.from_env(version="auto")
|
||||
PROJECT_ROOT = Path(__file__).parent.parent.absolute()
|
||||
MAX_PORT: int = 65535
|
||||
PROJECT_ROOT: Path = Path(__file__).parent.parent.parent.absolute()
|
||||
|
||||
type ConfigLabel = str | list[str]
|
||||
|
||||
@@ -63,8 +65,9 @@ def update_config(config_label: ConfigLabel = "default") -> None:
|
||||
apply_config(get_config(config_label))
|
||||
|
||||
|
||||
def init() -> None:
|
||||
globals().update(get_config())
|
||||
def init(config) -> None:
|
||||
globals().update(get_config(config))
|
||||
update_config()
|
||||
|
||||
init()
|
||||
|
||||
init(config="default")
|
||||
@@ -1,14 +1,18 @@
|
||||
from fnmatch import fnmatch
|
||||
import ipaddress
|
||||
from typing import Annotated
|
||||
|
||||
from docker.types import Mount
|
||||
import typer
|
||||
|
||||
from cli import cli_spinner
|
||||
import config
|
||||
from create_img import create_img
|
||||
from debug import debug_guard
|
||||
from docker.types import Mount
|
||||
from utils import ensure_build_dir
|
||||
from . import config
|
||||
from .cli import cli_spinner
|
||||
from .create_img import create_img
|
||||
from .debug import debug_guard
|
||||
from .utils import ensure_build_dir
|
||||
|
||||
|
||||
type IPAddress = ipaddress.IPv4Address | ipaddress.IPv6Address
|
||||
|
||||
|
||||
def filter_validation_response(response: str) -> str:
|
||||
@@ -66,35 +70,86 @@ def write_disk(disk: str) -> None:
|
||||
@cli_spinner(description="Creating ignition initialisation disk", total=None)
|
||||
@ensure_build_dir
|
||||
def create_ignition_disk(
|
||||
disk: str = Annotated[str, typer.Option(help="Path to the disk to write to", prompt=True)],
|
||||
hostname: str = Annotated[str, typer.Option(help="Hostname for the new node", prompt=True)],
|
||||
password: str = Annotated[
|
||||
disk: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
"--disk",
|
||||
"-d",
|
||||
help="Path to the disk to write to",
|
||||
prompt=True,
|
||||
),
|
||||
] = None,
|
||||
hostname: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
"--hostname",
|
||||
"-h",
|
||||
help="Hostname for the new node",
|
||||
prompt=True,
|
||||
),
|
||||
] = "node",
|
||||
password: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
"--password",
|
||||
"-p",
|
||||
help="Password for the root user on the new node",
|
||||
prompt=True,
|
||||
confirmation_prompt=True,
|
||||
hide_input=True,
|
||||
),
|
||||
],
|
||||
switch_ip_address: str = Annotated[
|
||||
str, typer.Option(help="IP address of the switch to connect to", prompt=True)
|
||||
],
|
||||
switch_port: int = Annotated[
|
||||
int, typer.Option(help="Port on the switch to connect to", prompt=True)
|
||||
],
|
||||
swarm_token: str = Annotated[
|
||||
str, typer.Option(help="Swarm token for connecting to the swarm", prompt=True)
|
||||
],
|
||||
] = None,
|
||||
switch_ip: Annotated[
|
||||
IPAddress,
|
||||
typer.Option(
|
||||
"--switch-ip",
|
||||
"-ip",
|
||||
help="IP address of the switch to connect to",
|
||||
prompt=True,
|
||||
parser=ipaddress.ip_address,
|
||||
),
|
||||
] = None,
|
||||
switch_port: Annotated[
|
||||
int,
|
||||
typer.Option(
|
||||
"--switch-port",
|
||||
"-sp",
|
||||
help="Port on the switch to connect to",
|
||||
prompt=True,
|
||||
min=1,
|
||||
max=config.MAX_PORT,
|
||||
),
|
||||
] = 4789,
|
||||
swarm_token: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
"--swarm-token",
|
||||
"-t",
|
||||
help="Swarm token for connecting to the swarm",
|
||||
prompt=True,
|
||||
),
|
||||
] = None,
|
||||
debug: Annotated[
|
||||
bool,
|
||||
typer.Option(
|
||||
"--debug",
|
||||
help="Enable debug mode",
|
||||
is_eager=True,
|
||||
is_flag=True,
|
||||
flag_value=True,
|
||||
hidden=not config.DEBUG,
|
||||
)
|
||||
] = False,
|
||||
) -> None:
|
||||
"""Writes an ignition image to the specified disk for easy deployment of new nodes to the swarm""" # noqa
|
||||
create_img(
|
||||
hostname,
|
||||
password,
|
||||
switch_ip_address,
|
||||
switch_port,
|
||||
swarm_token,
|
||||
config.BUILD_DIR / "ignition.img",
|
||||
hostname = hostname,
|
||||
password = password,
|
||||
switch_ip = switch_ip,
|
||||
switch_port = switch_port,
|
||||
swarm_token = swarm_token,
|
||||
img_path = config.BUILD_DIR / "ignition.img",
|
||||
debug = debug,
|
||||
)
|
||||
valid, response = validate()
|
||||
if not valid:
|
||||
@@ -3,19 +3,20 @@ import json
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from autoignition import json_to_img
|
||||
from cli import cli_spinner
|
||||
import config
|
||||
from debug import debug_guard
|
||||
import typer
|
||||
from utils import ensure_build_dir
|
||||
|
||||
from . import config
|
||||
from .autoignition import json_to_img
|
||||
from .cli import cli_spinner
|
||||
from .debug import debug_guard
|
||||
from .utils import ensure_build_dir
|
||||
|
||||
|
||||
MAX_PORT: int = 65535
|
||||
type IPAddress = ipaddress.IPv4Address | ipaddress.IPv6Address
|
||||
|
||||
|
||||
def load_template() -> dict:
|
||||
with open(config.SRC_DIR / "templates" / "fuelignition.json", "r") as f:
|
||||
with open(config.SRC_DIR / "templates/fuelignition.json", "r") as f:
|
||||
out = json.load(f)
|
||||
return out
|
||||
|
||||
@@ -31,8 +32,8 @@ def apply_ignition_settings(
|
||||
ignition_config["login"]["users"][0]["passwd"] = password
|
||||
|
||||
# Add files that will define a service to ensure that the node joins the swarm
|
||||
with open(config.SRC_DIR / "templates" / "join_swarm.sh", "r") as f1, open(
|
||||
config.SRC_DIR / "templates" / "join_swarm.service", "r"
|
||||
with open(config.SRC_DIR / "templates/join_swarm.sh", "r") as f1, open(
|
||||
config.SRC_DIR / "templates/join_swarm.service", "r"
|
||||
) as f2:
|
||||
swarm_script, swarm_service = f1.read(), f2.read()
|
||||
|
||||
@@ -72,47 +73,82 @@ def apply_ignition_settings(
|
||||
@cli_spinner(description="Creating ignition image", total=None)
|
||||
@ensure_build_dir
|
||||
def create_img(
|
||||
hostname: str = Annotated[str, typer.Option(help="Hostname for the new node", prompt=True)],
|
||||
password: str = Annotated[
|
||||
hostname: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
"--hostname",
|
||||
"-h",
|
||||
help="Hostname for the new node",
|
||||
prompt=True,
|
||||
),
|
||||
] = "node",
|
||||
password: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
"--password",
|
||||
"-p",
|
||||
help="Password for the root user on the new node",
|
||||
prompt=True,
|
||||
confirmation_prompt=True,
|
||||
hide_input=True,
|
||||
),
|
||||
],
|
||||
switch_ip_address: str = Annotated[
|
||||
str, typer.Option(help="IP address of the switch to connect to", prompt=True)
|
||||
],
|
||||
switch_port: int = Annotated[
|
||||
int, typer.Option(
|
||||
] = None,
|
||||
switch_ip: Annotated[
|
||||
IPAddress,
|
||||
typer.Option(
|
||||
"--switch-ip",
|
||||
"-ip",
|
||||
help="IP address of the switch to connect to",
|
||||
prompt=True,
|
||||
parser=ipaddress.ip_address,
|
||||
),
|
||||
] = None,
|
||||
switch_port: Annotated[
|
||||
int,
|
||||
typer.Option(
|
||||
"--switch-port",
|
||||
"-sp",
|
||||
help="Port on the switch to connect to",
|
||||
prompt=True,
|
||||
min=1,
|
||||
max=MAX_PORT,
|
||||
)
|
||||
],
|
||||
swarm_token: str = Annotated[
|
||||
str, typer.Option(help="Swarm token for connecting to the swarm", prompt=True)
|
||||
],
|
||||
img_path: Path = Annotated[
|
||||
max=config.MAX_PORT,
|
||||
),
|
||||
] = 4789,
|
||||
swarm_token: Annotated[
|
||||
str,
|
||||
typer.Option(
|
||||
"--swarm-token",
|
||||
"-t",
|
||||
help="Swarm token for connecting to the swarm",
|
||||
prompt=True,
|
||||
),
|
||||
] = None,
|
||||
img_path: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
help="Path to the JSON file to be converted to an img",
|
||||
default=Path.cwd() / "ignition.img",
|
||||
"--img-path",
|
||||
"-o",
|
||||
help="Path to which the ignition image should be written",
|
||||
dir_okay=False,
|
||||
),
|
||||
],
|
||||
] = Path("ignition.img"),
|
||||
debug: Annotated[
|
||||
bool,
|
||||
typer.Option(
|
||||
"--debug",
|
||||
help="Enable debug mode",
|
||||
is_eager=True,
|
||||
is_flag=True,
|
||||
flag_value=True,
|
||||
hidden=not config.DEBUG,
|
||||
),
|
||||
] = False,
|
||||
) -> None:
|
||||
"""Creates an ignition image for deploying a new node to the swarm"""
|
||||
switch_ip_address = ipaddress.ip_address(switch_ip_address)
|
||||
if switch_port > MAX_PORT:
|
||||
raise ValueError(f"Port must be less than {MAX_PORT}")
|
||||
|
||||
# get swarm configuration as JSON
|
||||
swarm_config = json.dumps(
|
||||
{
|
||||
"SWITCH_IP_ADDRESS": str(switch_ip_address),
|
||||
"SWITCH_IP_ADDRESS": str(switch_ip),
|
||||
"SWITCH_PORT": switch_port,
|
||||
"SWARM_TOKEN": swarm_token,
|
||||
}
|
||||
@@ -132,7 +168,11 @@ def create_img(
|
||||
json.dump(ignition_config, f, indent=4)
|
||||
|
||||
# convert ignition configuration to image
|
||||
json_to_img(config.BUILD_DIR / "fuelignition.json", img_path)
|
||||
json_to_img(
|
||||
json_path=config.BUILD_DIR / "fuelignition.json",
|
||||
img_path=img_path,
|
||||
debug=debug,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
39
src/node_deployer/debug.py
Normal file
39
src/node_deployer/debug.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from functools import wraps
|
||||
import inspect
|
||||
from typing import Callable
|
||||
|
||||
import typer
|
||||
|
||||
from . import config
|
||||
|
||||
|
||||
# def merge(f1: Callable) -> Callable:
|
||||
# https://docs.python.org/3/library/functools.html#functools.update_wrapper
|
||||
# wraps, but it combines the signatures of the two functions
|
||||
# This will allow us to add/remove the `debug` arg depending on config context
|
||||
|
||||
|
||||
def debug_guard(f: Callable) -> Callable:
|
||||
if not config.DEBUG:
|
||||
return f
|
||||
try:
|
||||
import snoop # type: ignore
|
||||
except ImportError:
|
||||
typer.echo("Debug mode requires the dev group to be installed")
|
||||
raise typer.Exit(1)
|
||||
else:
|
||||
snoop.install(**config.snoop["install"])
|
||||
|
||||
@wraps(f)
|
||||
def debug_mode(
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> Callable:
|
||||
typer.echo(f"Debug mode enabled: {inspect.stack()[1].filename}")
|
||||
if kwargs.get("debug", False):
|
||||
# Snoop depth is set to compensate for wrapper stack frames
|
||||
return snoop.snoop(**config.snoop["snoop"])(f)(*args, **kwargs) # noqa: F821 #* ss is installed in debug_mode
|
||||
else:
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return debug_mode
|
||||
24
src/node_deployer/node_deployer.py
Executable file
24
src/node_deployer/node_deployer.py
Executable file
@@ -0,0 +1,24 @@
|
||||
import typer
|
||||
|
||||
from . import config
|
||||
from .autoignition import json_to_img
|
||||
from .create_disk import create_ignition_disk
|
||||
from .create_img import create_img
|
||||
|
||||
|
||||
cmd_params = {
|
||||
"no_args_is_help": True,
|
||||
}
|
||||
|
||||
app = typer.Typer(
|
||||
help="A tool for creating ignition images for automated deployment to a swarm",
|
||||
**cmd_params,
|
||||
)
|
||||
|
||||
app.command(**cmd_params)(create_img)
|
||||
app.command(**cmd_params)(create_ignition_disk)
|
||||
app.command(**cmd_params)(json_to_img)
|
||||
|
||||
if __name__ == "__main__":
|
||||
config.update_config("cli")
|
||||
app()
|
||||
@@ -2,7 +2,7 @@ from functools import wraps
|
||||
from pathlib import Path
|
||||
from typing import Callable
|
||||
|
||||
import config
|
||||
from . import config
|
||||
|
||||
|
||||
def ensure_build_dir(f: Callable) -> Callable:
|
||||
Reference in New Issue
Block a user