Macrostrat application frame
This is a CLI toolset for managing data and services for a Dockerized
application. It was originally developed for the
Sparrow application, but it has been generalized for
use with other projects including Macrostrat, Mapboard
and other systems.
It is designed to work well with projects managed using Docker Compose, but may
eventually acquire features for managing Kubernetes-based applications.
Basic usage
app = Application(
"Mapboard",
restart_commands={"gateway": "caddy reload --config /etc/caddy/Caddyfile"},
log_modules=["mapboard.server"],
compose_files=[MAPBOARD_ROOT / "system" / "docker-compose.yaml"],
)
cli = app.control_command()
The cli
is a Typer application that can be used
to control the system, with abilities to start, stop, and restart services, with
extension points to add new functionality.
Application
Bases: ApplicationBase
Source code in app-frame/macrostrat/app_frame/core.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117 | class Application(ApplicationBase):
console: Console
_dotenv_cfg: bool | Path | list[Path]
_log_modules: list[str] = []
def __init__(
self,
name: str,
*,
command_name: Optional[str] = None,
project_prefix: Optional[str] = None,
restart_commands: dict[str, str] = {},
log_modules: Optional[str | list[str]] = None,
root_dir: Path | Callable[[Path], Path] = Path.cwd(),
compose_files: ComposeFilesDependency = [],
env: EnvironmentDependency = {},
load_dotenv: bool | Path | list[Path] = False,
):
self.name = name
self.command_name = command_name or name.lower()
self.project_prefix = project_prefix or name.lower().replace(" ", "_")
self.envvar_prefix = self.project_prefix.upper() + "_"
self.console = Console()
self.restart_commands = restart_commands
if isinstance(log_modules, str):
log_modules = [log_modules]
if log_modules is not None:
self._log_modules = log_modules
self._dotenv_cfg = load_dotenv
# Root dir and compose files can be specified using dependency injection.
if callable(root_dir):
root_dir = root_dir(Path.cwd())
self.root_dir = root_dir
if callable(compose_files):
compose_files = compose_files(self)
self.compose_files = compose_files
# Environment setup should possibly be postponed until within a command context.
self.setup_environment(env)
def replace_names(self, text: str) -> str:
text = text.replace(":app_name:", self.name)
return text.replace(":command_name:", self.name.lower())
def info(self, text, style=None):
self.console.print(self.replace_names(text), style=style)
def load_dotenv(self):
if isinstance(self._dotenv_cfg, list):
for path in self._dotenv_cfg:
load_dotenv(path)
elif isinstance(self._dotenv_cfg, Path):
load_dotenv(self._dotenv_cfg)
elif load_dotenv is True:
load_dotenv()
def setup_environment(self, env: EnvironmentDependency):
environ["DOCKER_SCAN_SUGGEST"] = "false"
#environ["DOCKER_BUILDKIT"] = "1"
# Set up environment for docker-compose
# We may need to move this to a context where it is only
# applied for docker-compose commands
environ["COMPOSE_PROJECT_NAME"] = self.project_prefix
compose_files = ":".join([str(f) for f in self.compose_files])
environ["COMPOSE_FILE"] = compose_files
# Additional user-specified environment variables
if callable(env):
env = env(self)
for k, v in env.items():
environ[k] = v
def setup_logs(self, verbose: bool = False):
if len(self._log_modules) == 0:
log.warning("No modules specified, not setting up logs")
return
if verbose:
setup_stderr_logs(*self._log_modules)
else:
# Disable all logging
# TODO: This is a hack, we shouldn't have to explicitly disable
# logging in the CLI. Perhaps there's somewhere that it's being
# enabled that we haven't chased down?
setup_stderr_logs("", level=logging.CRITICAL)
def control_command(self, *args, **kwargs):
from .control_command import ControlCommand
return ControlCommand(self, *args, **kwargs)
|