ssm-cli 0.1.3.dev1__tar.gz → 0.1.3.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ssm_cli-0.1.3.dev1/ssm_cli.egg-info → ssm_cli-0.1.3.dev2}/PKG-INFO +1 -1
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/cli.py +17 -8
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/cli_args.py +5 -2
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/base.py +2 -1
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/list.py +1 -2
- ssm_cli-0.1.3.dev2/ssm_cli/commands/setup.py +85 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/shell.py +1 -3
- ssm_cli-0.1.3.dev2/ssm_cli/console.py +2 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/selectors/tui/__init__.py +1 -2
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2/ssm_cli.egg-info}/PKG-INFO +1 -1
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli.egg-info/SOURCES.txt +2 -4
- ssm_cli-0.1.3.dev1/ssm_cli/commands/setup/__init__.py +0 -28
- ssm_cli-0.1.3.dev1/ssm_cli/commands/setup/actions/base.py +0 -74
- ssm_cli-0.1.3.dev1/ssm_cli/commands/setup/actions/ssh.py +0 -21
- ssm_cli-0.1.3.dev1/ssm_cli/commands/setup/definition.py +0 -125
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/LICENCE +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/README.md +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/pyproject.toml +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/setup.cfg +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/__init__.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/__main__.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/aws.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/__init__.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/ssh_proxy/__init__.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/ssh_proxy/channels.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/ssh_proxy/forward.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/ssh_proxy/server.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/ssh_proxy/shell.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/commands/ssh_proxy/transport.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/config.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/instances.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/selectors/__init__.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/selectors/first.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/selectors/tui/posix.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/selectors/tui/win.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli/xdg.py +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli.egg-info/dependency_links.txt +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli.egg-info/entry_points.txt +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli.egg-info/requires.txt +0 -0
- {ssm_cli-0.1.3.dev1 → ssm_cli-0.1.3.dev2}/ssm_cli.egg-info/top_level.txt +0 -0
|
@@ -5,15 +5,10 @@ from rich_argparse import ArgumentDefaultsRichHelpFormatter
|
|
|
5
5
|
import confclasses
|
|
6
6
|
from ssm_cli.config import CONFIG
|
|
7
7
|
from ssm_cli.xdg import get_log_file, get_conf_file
|
|
8
|
-
from ssm_cli.commands import COMMANDS
|
|
8
|
+
from ssm_cli.commands import COMMANDS, BaseCommand
|
|
9
9
|
from ssm_cli.cli_args import CliArgumentParser, ARGS
|
|
10
10
|
from ssm_cli.aws import AWSAuthError
|
|
11
|
-
from
|
|
12
|
-
from rich.console import Console
|
|
13
|
-
|
|
14
|
-
# Setup rich
|
|
15
|
-
install()
|
|
16
|
-
console = Console()
|
|
11
|
+
from ssm_cli.console import console
|
|
17
12
|
|
|
18
13
|
# Setup logging
|
|
19
14
|
import logging
|
|
@@ -61,7 +56,7 @@ def cli(argv: list = None) -> int:
|
|
|
61
56
|
|
|
62
57
|
# Setup is a special case, we cannot load config if we dont have any.
|
|
63
58
|
if ARGS.command == "setup":
|
|
64
|
-
|
|
59
|
+
run_command()
|
|
65
60
|
return 0
|
|
66
61
|
|
|
67
62
|
try:
|
|
@@ -90,3 +85,17 @@ def cli(argv: list = None) -> int:
|
|
|
90
85
|
return 2
|
|
91
86
|
|
|
92
87
|
return 0
|
|
88
|
+
|
|
89
|
+
def run_command():
|
|
90
|
+
"""
|
|
91
|
+
Run a command, better exceptions and logging
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
COMMANDS[ARGS.command].run()
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Unhandled exception in {ARGS.command}")
|
|
97
|
+
console.print(f"Unhandled exception, check [link=file://{get_log_file()}]{get_log_file()}[/link] for more information", style="red")
|
|
98
|
+
console.print(f"Error: {e}", style="red bold")
|
|
99
|
+
logger.exception(e, stack_info=True, stacklevel=20)
|
|
100
|
+
return 1
|
|
101
|
+
return 0
|
|
@@ -2,6 +2,7 @@ import argparse
|
|
|
2
2
|
import sys
|
|
3
3
|
from confclasses import fields, is_confclass
|
|
4
4
|
from ssm_cli.config import CONFIG
|
|
5
|
+
import os
|
|
5
6
|
|
|
6
7
|
# The long term aim is to move this module into the config module and "bind" config to cli arguments
|
|
7
8
|
# This will need a rethink of where some of the arguments/config come from because right now they are in the commands modules
|
|
@@ -104,8 +105,10 @@ class VersionAction(argparse._VersionAction):
|
|
|
104
105
|
print("session-manager-plugin not found", file=sys.stderr)
|
|
105
106
|
parser.exit(1)
|
|
106
107
|
|
|
108
|
+
pkg_dir = os.path.join(os.path.dirname(__file__), "..")
|
|
109
|
+
pkg_dir = os.path.abspath(pkg_dir)
|
|
110
|
+
print(f"ssm-cli {version('ssm-cli')} from {pkg_dir}")
|
|
107
111
|
v = sys.version_info
|
|
108
|
-
print(f"ssm-cli {version('ssm-cli')}")
|
|
109
112
|
print(f"python {v.major}.{v.minor}.{v.micro}")
|
|
110
113
|
print(f"session-manager-plugin {results.stdout.strip()}")
|
|
111
|
-
parser.exit()
|
|
114
|
+
parser.exit()
|
|
@@ -2,10 +2,9 @@ from ssm_cli.instances import Instances
|
|
|
2
2
|
from ssm_cli.commands.base import BaseCommand
|
|
3
3
|
from ssm_cli.cli_args import ARGS
|
|
4
4
|
import logging
|
|
5
|
-
from rich.console import Console
|
|
6
5
|
from rich.table import Table
|
|
6
|
+
from ssm_cli.console import console
|
|
7
7
|
|
|
8
|
-
console = Console()
|
|
9
8
|
logger = logging.getLogger(__name__)
|
|
10
9
|
|
|
11
10
|
class ListCommand(BaseCommand):
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import paramiko
|
|
3
|
+
from ssm_cli.commands.base import BaseCommand
|
|
4
|
+
from ssm_cli.xdg import get_conf_root, get_conf_file, get_ssh_hostkey, get_log_file
|
|
5
|
+
from ssm_cli.cli_args import ARGS
|
|
6
|
+
from ssm_cli.config import CONFIG
|
|
7
|
+
from confclasses import from_dict, save
|
|
8
|
+
from ssm_cli.console import console
|
|
9
|
+
|
|
10
|
+
GREY = "grey50"
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class SetupCommand(BaseCommand):
|
|
16
|
+
HELP = "Setups up ssm-cli"
|
|
17
|
+
|
|
18
|
+
def add_arguments(parser):
|
|
19
|
+
parser.add_argument("--replace-config", action=argparse.BooleanOptionalAction, default=False, help="if we should replace existing config file")
|
|
20
|
+
parser.add_argument("--replace-hostkey", action=argparse.BooleanOptionalAction, default=False, help="if we should replace existing hostkey file (bare careful with this option)")
|
|
21
|
+
|
|
22
|
+
def run():
|
|
23
|
+
# Create the root config directory
|
|
24
|
+
root = get_conf_root(False)
|
|
25
|
+
logger.debug(f"Checking if {root} exists")
|
|
26
|
+
if root.exists():
|
|
27
|
+
logger.debug(f"{root} exists")
|
|
28
|
+
if not root.is_dir():
|
|
29
|
+
logger.error(f"{root} already exists and is not a directory. Manual cleanup is likely needed.")
|
|
30
|
+
console.print(f"{root} already exists and is not a directory. Manual cleanup is likely needed.", style="red bold")
|
|
31
|
+
return
|
|
32
|
+
console.print(f"{root} - skipping (already exists)", style=GREY)
|
|
33
|
+
else:
|
|
34
|
+
root.mkdir(511, True, True)
|
|
35
|
+
console.print(f"{root} created", style="green")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Create the config file
|
|
39
|
+
path = get_conf_file(False)
|
|
40
|
+
logger.debug(f"Checking if {path} exists")
|
|
41
|
+
create_config = False
|
|
42
|
+
if path.exists():
|
|
43
|
+
logger.debug(f"{path} exists")
|
|
44
|
+
if ARGS.replace_config:
|
|
45
|
+
logger.info(f"{path} exists and --replace-config was set, unlink {path}")
|
|
46
|
+
console.print(f"{path} removing", style="green")
|
|
47
|
+
path.unlink(True)
|
|
48
|
+
create_config = True
|
|
49
|
+
else:
|
|
50
|
+
logger.debug(f"{path} does not exist")
|
|
51
|
+
create_config = True
|
|
52
|
+
|
|
53
|
+
if create_config:
|
|
54
|
+
logger.info(f"{path} creating")
|
|
55
|
+
console.print(f"{path} creating", style="green")
|
|
56
|
+
from_dict(CONFIG, {})
|
|
57
|
+
|
|
58
|
+
CONFIG.group_tag_key = console.input(f"What tag to use to split up the instances \[{CONFIG.group_tag_key}]: ") or CONFIG.group_tag_key
|
|
59
|
+
console.print(f"Using '{CONFIG.group_tag_key}' as the group tag", style=GREY)
|
|
60
|
+
logger.info(f"Writing config to {path}")
|
|
61
|
+
|
|
62
|
+
with path.open("w+") as f:
|
|
63
|
+
save(CONFIG, f)
|
|
64
|
+
console.print(f"{path} created", style="green")
|
|
65
|
+
|
|
66
|
+
# Create the ssh hostkey
|
|
67
|
+
path = get_ssh_hostkey(False)
|
|
68
|
+
create_key = False
|
|
69
|
+
if path.exists():
|
|
70
|
+
logger.debug(f"{path} exists")
|
|
71
|
+
console.print(f"{path} skipping (already exists)")
|
|
72
|
+
if ARGS.replace_hostkey:
|
|
73
|
+
logger.info(f"{path} exists and --replace-hostkey was set, unlink {path}")
|
|
74
|
+
console.print(f"{path} removing", style="green")
|
|
75
|
+
path.unlink(True)
|
|
76
|
+
create_key = True
|
|
77
|
+
else:
|
|
78
|
+
logger.debug(f"{path} does not exist")
|
|
79
|
+
create_key = True
|
|
80
|
+
|
|
81
|
+
if create_key:
|
|
82
|
+
logger.info(f"{path} creating")
|
|
83
|
+
host_key = paramiko.RSAKey.generate(1024)
|
|
84
|
+
host_key.write_private_key_file(path)
|
|
85
|
+
console.print(f"{path} created")
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from ssm_cli.instances import Instances
|
|
2
2
|
from ssm_cli.commands.base import BaseCommand
|
|
3
3
|
from ssm_cli.cli_args import ARGS
|
|
4
|
+
from ssm_cli import console
|
|
4
5
|
|
|
5
6
|
import logging
|
|
6
7
|
logger = logging.getLogger(__name__)
|
|
7
8
|
|
|
8
|
-
from rich.console import Console
|
|
9
|
-
|
|
10
|
-
console = Console()
|
|
11
9
|
|
|
12
10
|
class ShellCommand(BaseCommand):
|
|
13
11
|
HELP = "Connects to instances"
|
|
@@ -2,6 +2,7 @@ from rich.console import Console
|
|
|
2
2
|
from rich.live import Live
|
|
3
3
|
from rich.table import Table
|
|
4
4
|
from rich.style import StyleType
|
|
5
|
+
from ssm_cli.console import console
|
|
5
6
|
|
|
6
7
|
import os
|
|
7
8
|
if os.name == "nt":
|
|
@@ -9,8 +10,6 @@ if os.name == "nt":
|
|
|
9
10
|
else:
|
|
10
11
|
from ssm_cli.selectors.tui.posix import getch, UP, DOWN, CTRL_C, ENTER
|
|
11
12
|
|
|
12
|
-
console = Console()
|
|
13
|
-
|
|
14
13
|
class TableWithArrows(Table):
|
|
15
14
|
def __init__(self, *args, **kwargs):
|
|
16
15
|
super().__init__(*args, **kwargs)
|
|
@@ -7,6 +7,7 @@ ssm_cli/aws.py
|
|
|
7
7
|
ssm_cli/cli.py
|
|
8
8
|
ssm_cli/cli_args.py
|
|
9
9
|
ssm_cli/config.py
|
|
10
|
+
ssm_cli/console.py
|
|
10
11
|
ssm_cli/instances.py
|
|
11
12
|
ssm_cli/xdg.py
|
|
12
13
|
ssm_cli.egg-info/PKG-INFO
|
|
@@ -18,11 +19,8 @@ ssm_cli.egg-info/top_level.txt
|
|
|
18
19
|
ssm_cli/commands/__init__.py
|
|
19
20
|
ssm_cli/commands/base.py
|
|
20
21
|
ssm_cli/commands/list.py
|
|
22
|
+
ssm_cli/commands/setup.py
|
|
21
23
|
ssm_cli/commands/shell.py
|
|
22
|
-
ssm_cli/commands/setup/__init__.py
|
|
23
|
-
ssm_cli/commands/setup/definition.py
|
|
24
|
-
ssm_cli/commands/setup/actions/base.py
|
|
25
|
-
ssm_cli/commands/setup/actions/ssh.py
|
|
26
24
|
ssm_cli/commands/ssh_proxy/__init__.py
|
|
27
25
|
ssm_cli/commands/ssh_proxy/channels.py
|
|
28
26
|
ssm_cli/commands/ssh_proxy/forward.py
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
from ssm_cli.commands.base import BaseCommand
|
|
3
|
-
from ssm_cli.commands.setup.definition import SetupDefinitions
|
|
4
|
-
from confclasses import load
|
|
5
|
-
|
|
6
|
-
import logging
|
|
7
|
-
logger = logging.getLogger(__name__)
|
|
8
|
-
|
|
9
|
-
class SetupCommand(BaseCommand):
|
|
10
|
-
HELP = "Setups up ssm-cli"
|
|
11
|
-
|
|
12
|
-
def add_arguments(parser):
|
|
13
|
-
parser.add_argument("--replace", action=argparse.BooleanOptionalAction, default=False, help="if we should replace existing")
|
|
14
|
-
parser.add_argument("--definitions", type=str, help="Path to the definitions file")
|
|
15
|
-
|
|
16
|
-
def run(args):
|
|
17
|
-
logger.info("running setup action")
|
|
18
|
-
|
|
19
|
-
definitions = SetupDefinitions()
|
|
20
|
-
if args.definitions:
|
|
21
|
-
with open(args.definitions) as f:
|
|
22
|
-
load(definitions, f)
|
|
23
|
-
logger.info(f"Loaded definitions from {args.definitions}")
|
|
24
|
-
else:
|
|
25
|
-
logger.info("Using default definitions")
|
|
26
|
-
load(definitions, "")
|
|
27
|
-
|
|
28
|
-
definitions.run()
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
from ssm_cli.commands.setup.definition import action
|
|
2
|
-
from ssm_cli.xdg import get_conf_root, get_conf_file
|
|
3
|
-
from ssm_cli.config import CONFIG
|
|
4
|
-
from confclasses import from_dict, save, load
|
|
5
|
-
import logging
|
|
6
|
-
from rich.text import Text
|
|
7
|
-
|
|
8
|
-
logger = logging.getLogger(__name__)
|
|
9
|
-
|
|
10
|
-
@action
|
|
11
|
-
class ConfDirAction:
|
|
12
|
-
action_name = "base.conf-dir"
|
|
13
|
-
|
|
14
|
-
def run(self):
|
|
15
|
-
root = get_conf_root(False)
|
|
16
|
-
yield Text(f"Checking for {root}", style="cyan")
|
|
17
|
-
if not root.exists():
|
|
18
|
-
yield Text(f"creating directory", style="green")
|
|
19
|
-
root.mkdir(511, True, True)
|
|
20
|
-
else:
|
|
21
|
-
if not root.is_dir():
|
|
22
|
-
yield Text(f"{root} already exists and is not a directory. Cleanup is likely needed.", style="red")
|
|
23
|
-
return False
|
|
24
|
-
yield Text(f"already exists", style="cyan")
|
|
25
|
-
|
|
26
|
-
return True
|
|
27
|
-
|
|
28
|
-
@action
|
|
29
|
-
class ConfFileAction:
|
|
30
|
-
action_name = "base.conf-file"
|
|
31
|
-
action_depends = ["base.conf-dir"]
|
|
32
|
-
|
|
33
|
-
group_tag_key: str
|
|
34
|
-
replace: bool = False
|
|
35
|
-
|
|
36
|
-
def pre(self):
|
|
37
|
-
path = get_conf_file(False)
|
|
38
|
-
if path.exists():
|
|
39
|
-
yield Text(f"Found existing config", style="cyan")
|
|
40
|
-
if not self.replace:
|
|
41
|
-
yield Text(f"Pulling in config from {path}", style="cyan")
|
|
42
|
-
with path.open('r') as file:
|
|
43
|
-
load(CONFIG, file)
|
|
44
|
-
else:
|
|
45
|
-
yield Text(f"Replacing existing config, so we will use defaults instead", style="cyan")
|
|
46
|
-
from_dict(CONFIG, {})
|
|
47
|
-
self.apply_config()
|
|
48
|
-
else:
|
|
49
|
-
yield Text(f"No existing config found, so we will use defaults", style="cyan")
|
|
50
|
-
from_dict(CONFIG, {})
|
|
51
|
-
self.apply_config()
|
|
52
|
-
|
|
53
|
-
return True
|
|
54
|
-
|
|
55
|
-
def apply_config(self):
|
|
56
|
-
if self.group_tag_key:
|
|
57
|
-
CONFIG.group_tag_key = self.group_tag_key
|
|
58
|
-
|
|
59
|
-
def run(self):
|
|
60
|
-
path = get_conf_file(False)
|
|
61
|
-
if path.exists() and not self.replace:
|
|
62
|
-
yield Text(f"{path} - skipping (already exists)", style="cyan")
|
|
63
|
-
return True
|
|
64
|
-
|
|
65
|
-
try:
|
|
66
|
-
with path.open("w+") as file:
|
|
67
|
-
save(CONFIG, file)
|
|
68
|
-
yield Text(f"{path} - created", style="cyan")
|
|
69
|
-
return True
|
|
70
|
-
except Exception as e:
|
|
71
|
-
logger.error(e)
|
|
72
|
-
path.unlink(True)
|
|
73
|
-
yield Text(f"{path} - failed", style="red")
|
|
74
|
-
return False
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from ssm_cli.commands.setup.definition import action
|
|
2
|
-
from ssm_cli.xdg import get_ssh_hostkey
|
|
3
|
-
import paramiko
|
|
4
|
-
from rich.text import Text
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
@action
|
|
8
|
-
class SshHostKeyGenAction:
|
|
9
|
-
action_name = "ssh.host-keygen"
|
|
10
|
-
action_depends = ["base.conf-dir"]
|
|
11
|
-
|
|
12
|
-
def run(self):
|
|
13
|
-
path = get_ssh_hostkey(False)
|
|
14
|
-
if path.exists():
|
|
15
|
-
yield Text(f"{path} - skipping (already exists)", style="cyan")
|
|
16
|
-
else:
|
|
17
|
-
host_key = paramiko.RSAKey.generate(1024)
|
|
18
|
-
host_key.write_private_key_file(path)
|
|
19
|
-
yield Text(f"{path} - created", style="cyan")
|
|
20
|
-
|
|
21
|
-
return True
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
from confclasses import confclass, unused, from_dict
|
|
3
|
-
import traceback
|
|
4
|
-
import logging
|
|
5
|
-
from confclasses.exceptions import ConfclassesMissingValueError
|
|
6
|
-
from rich.console import Console, Group
|
|
7
|
-
from rich.panel import Panel
|
|
8
|
-
from rich.text import Text
|
|
9
|
-
from rich.tree import Tree
|
|
10
|
-
from rich.prompt import Prompt
|
|
11
|
-
from rich.live import Live
|
|
12
|
-
|
|
13
|
-
console = Console()
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
# 2 sets of defaults in this file, inside each action and in the SetupDefinitions default values, as sometimes it makes sense to have different defaults
|
|
17
|
-
# - The default in the action classes are for when the action is defined in a definitions file but no value is provided for that attribute
|
|
18
|
-
# - The default in the SetupDefinitions is for when the definitions file is not provided
|
|
19
|
-
|
|
20
|
-
# decorator to register the actions, saves having to do it manually. Lets also add confclasses here, saves space
|
|
21
|
-
actions = {}
|
|
22
|
-
modules = set()
|
|
23
|
-
def action(cls=None):
|
|
24
|
-
def wrap(cls):
|
|
25
|
-
cls = confclass(cls)
|
|
26
|
-
actions[cls.action_name] = cls
|
|
27
|
-
modules.add(cls.__module__)
|
|
28
|
-
return cls
|
|
29
|
-
return wrap(cls) if cls else wrap
|
|
30
|
-
|
|
31
|
-
@confclass
|
|
32
|
-
class SetupDefinition:
|
|
33
|
-
action: str
|
|
34
|
-
|
|
35
|
-
@confclass
|
|
36
|
-
class SetupDefinitions:
|
|
37
|
-
definitions: List[SetupDefinition] = [
|
|
38
|
-
{"action": "base.conf-dir"},
|
|
39
|
-
{"action": "base.conf-file", "merge": True},
|
|
40
|
-
{"action": "ssh.host-keygen"},
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
def __post_init__(self):
|
|
44
|
-
self.execution_plan = []
|
|
45
|
-
|
|
46
|
-
def get_action_cls(self, name: str):
|
|
47
|
-
module = name.split(".")[0]
|
|
48
|
-
if module not in modules:
|
|
49
|
-
__import__(f"{__package__}.actions.{module}", fromlist=[module])
|
|
50
|
-
modules.add(module)
|
|
51
|
-
|
|
52
|
-
return actions[name]
|
|
53
|
-
|
|
54
|
-
def add_definition_to_plan(self, name: str, tree: Tree, indent=1):
|
|
55
|
-
if name in self.execution_plan:
|
|
56
|
-
return
|
|
57
|
-
|
|
58
|
-
action_cls = self.get_action_cls(name)
|
|
59
|
-
|
|
60
|
-
for dep in getattr(action_cls, "action_depends", []):
|
|
61
|
-
self.add_definition_to_plan(dep, tree=tree.add(f"{dep}"), indent=indent+1)
|
|
62
|
-
|
|
63
|
-
self.execution_plan.append(name)
|
|
64
|
-
|
|
65
|
-
def make_action(self, name: str, args: dict):
|
|
66
|
-
try:
|
|
67
|
-
action = self.get_action_cls(name)()
|
|
68
|
-
from_dict(action, args)
|
|
69
|
-
return action
|
|
70
|
-
except ConfclassesMissingValueError as e:
|
|
71
|
-
if len(e.missing) > 2:
|
|
72
|
-
raise Exception(f"Nested missing values are not supported") from e
|
|
73
|
-
missing_key = e.missing[1]
|
|
74
|
-
value = Prompt.ask(f"[yellow]{name} requires user input for '{missing_key}'[/yellow]")
|
|
75
|
-
args[missing_key] = value
|
|
76
|
-
return self.make_action(name, args)
|
|
77
|
-
|
|
78
|
-
def panel_wrapper(self, title: str, func: callable):
|
|
79
|
-
log = []
|
|
80
|
-
panel = Panel(Text("Pending..."), title=title, style="cyan")
|
|
81
|
-
with Live(panel, console=console, refresh_per_second=4) as live:
|
|
82
|
-
gen = func()
|
|
83
|
-
try:
|
|
84
|
-
while True:
|
|
85
|
-
log.append(next(gen))
|
|
86
|
-
panel.renderable = Group(*log)
|
|
87
|
-
live.update(panel)
|
|
88
|
-
except StopIteration as e:
|
|
89
|
-
success = e.value # <- final return value
|
|
90
|
-
panel.title += "Success" if success else "Failed"
|
|
91
|
-
panel.style = "bold green" if success else "bold red"
|
|
92
|
-
live.update(panel)
|
|
93
|
-
return success
|
|
94
|
-
|
|
95
|
-
def run(self):
|
|
96
|
-
action_args = {}
|
|
97
|
-
dependency = Tree("Setup Actions", style="cyan")
|
|
98
|
-
panel = Panel(dependency, title="Building Dependency Tree for Setup")
|
|
99
|
-
for definition in self.definitions:
|
|
100
|
-
item = dependency.add(f"{definition.action}")
|
|
101
|
-
self.add_definition_to_plan(definition.action, tree=item)
|
|
102
|
-
action_args[definition.action] = unused(definition)
|
|
103
|
-
console.print(panel)
|
|
104
|
-
|
|
105
|
-
execution_plan = []
|
|
106
|
-
text = Text()
|
|
107
|
-
panel = Panel(text, title="Execution Order")
|
|
108
|
-
for idx, action_name in enumerate(self.execution_plan):
|
|
109
|
-
text.append(f"\n[{idx}] {action_name}", style="cyan")
|
|
110
|
-
action = self.make_action(action_name, action_args.get(action_name, {}))
|
|
111
|
-
execution_plan.append(action)
|
|
112
|
-
if hasattr(action, "pre"):
|
|
113
|
-
if not self.panel_wrapper(
|
|
114
|
-
title=f"Pre {action.action_name}",
|
|
115
|
-
func=action.pre
|
|
116
|
-
):
|
|
117
|
-
break
|
|
118
|
-
console.print(panel)
|
|
119
|
-
|
|
120
|
-
for action in execution_plan:
|
|
121
|
-
if not self.panel_wrapper(
|
|
122
|
-
title=action.action_name,
|
|
123
|
-
func=action.run
|
|
124
|
-
):
|
|
125
|
-
break
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|