Skip to content

Commit c33939b

Browse files
committed
Added Docker functionality and the Manager
1 parent d856c9c commit c33939b

File tree

4 files changed

+140
-26
lines changed

4 files changed

+140
-26
lines changed

src/cli/builder.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from yaspin import yaspin # type: ignore
1212

1313
from ..core.copy_files import copy_files # type: ignore
14+
from ..core.docker import ComposeManager
1415
from ..core.manage_json import read_json, write_json
1516
from ..core.manage_templates import template_to_file
1617
from ..utils.cli import clear, confirm
@@ -102,8 +103,9 @@ def callback(
102103
path: Path = self.cwd.joinpath("data.json")
103104

104105
if not path.exists():
105-
print("Missing JSON file for services. Use 'create' first.")
106-
return
106+
exit(
107+
"ERROR: Missing JSON file for services. Use 'create' first."
108+
)
107109

108110
data: dicts = read_json(path) or {}
109111
compose: dicts = data.get("compose", {}) or {}
@@ -113,8 +115,7 @@ def callback(
113115
envs: list[dicts] = data.get("envs", []) or []
114116

115117
if not services:
116-
print("No services found. Use 'create' first.")
117-
return
118+
exit("ERROR: No services found. Use 'create' first.")
118119

119120
def find_index_by_name(name: str) -> int | None:
120121
for i, s in enumerate(services):
@@ -127,16 +128,15 @@ def find_index_by_name(name: str) -> int | None:
127128
if not target:
128129
names = [s.get("name") for s in services if s.get("name")]
129130
if not names:
130-
print("No services found.")
131-
return
131+
exit("ERROR: No services found.")
132+
132133
target = inquirer.select( # type: ignore
133134
message="Select a service to remove: ", choices=names
134135
).execute()
135136

136-
idx = find_index_by_name(target)
137+
idx = find_index_by_name(target) # type: ignore
137138
if idx is None:
138-
print(f"Service '{target}' not found.")
139-
return
139+
exit(f"ERROR: Service '{target}' not found.")
140140

141141
if confirm(msg=f"Remove service '{target}'"):
142142
services.pop(idx)
@@ -158,8 +158,8 @@ def find_index_by_name(name: str) -> int | None:
158158
if not confirm(
159159
msg=f"Service '{name}' already exists. Overwrite? "
160160
):
161-
print("Add cancelled.")
162-
return
161+
exit("ERROR: Add cancelled.")
162+
163163
services = [s for s in services if s.get("name") != name]
164164
envs = [e for e in envs if e.get("CONTAINER_NAME") != name]
165165

@@ -202,21 +202,20 @@ def build(self) -> Command:
202202
help = "Build the files for the containerization."
203203
options: list[Option] = []
204204

205-
def callback(
206-
service: str | None = None, add: bool = False, remove: bool = False
207-
) -> None:
205+
def callback() -> None:
208206
clear(0)
209207

210208
path: Path = self.cwd.joinpath("data.json")
211209

212210
if not path.exists():
213-
print("Missing JSON file for services. Use 'create' first.")
214-
return
211+
exit(
212+
"ERROR: Missing JSON file for services. Use 'create' first."
213+
)
215214

216215
data: dicts = read_json(path) or {}
217216

218217
if not data:
219-
print("JSON file is empty. Use 'create' first.")
218+
exit("ERROR: JSON file is empty. Use 'create' first.")
220219

221220
clear(0)
222221
self.__save_files(data, build=True)

src/cli/manager.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from InquirerPy.validator import EmptyInputValidator # type: ignore
77
from click import Command, Option
88

9+
from ..core.docker import ComposeManager
910
from ..utils.cli import clear, confirm # type: ignore
1011
from .custom_group import CustomGroup
1112

@@ -14,14 +15,15 @@ class Manager(CustomGroup):
1415

1516
def __init__(self) -> None:
1617
super().__init__()
18+
self.compose_manager = ComposeManager()
1719

1820
def backup(self) -> Command:
1921

2022
help = "Create a backup of the files inside the containers to their respective build directories."
2123
options = [Option()]
2224

2325
def callback() -> None:
24-
pass
26+
self.compose_manager.back_up(self.cwd)
2527

2628
return Command(
2729
name=inspect.currentframe().f_code.co_name, # type: ignore
@@ -30,13 +32,27 @@ def callback() -> None:
3032
params=options, # type: ignore
3133
)
3234

33-
def delete(self) -> Command:
35+
def up(self) -> Command:
36+
help = "Start up the container (first time start)."
37+
options = [Option(["--detached"], is_flag=True, default=False)]
3438

35-
help = "Delete entirely the files related with the containerization of the server/network."
36-
options = [Option()]
39+
def callback(detached: bool = False) -> None:
40+
self.compose_manager.up(detached)
3741

38-
def callback() -> None:
39-
pass
42+
return Command(
43+
name=inspect.currentframe().f_code.co_name, # type: ignore
44+
help=help,
45+
callback=callback,
46+
params=options, # type: ignore
47+
)
48+
49+
def down(self) -> Command:
50+
51+
help = "Delete the container."
52+
options = [Option(["--rm-volumes"], is_flag=True, default=True)]
53+
54+
def callback(rm_volumes: bool = True) -> None:
55+
self.compose_manager.down(rm_volumes)
4056

4157
return Command(
4258
name=inspect.currentframe().f_code.co_name, # type: ignore
@@ -51,7 +67,7 @@ def start(self) -> Command:
5167
options = [Option()]
5268

5369
def callback() -> None:
54-
pass
70+
self.compose_manager.start()
5571

5672
return Command(
5773
name=inspect.currentframe().f_code.co_name, # type: ignore
@@ -66,7 +82,7 @@ def stop(self) -> Command:
6682
options = [Option()]
6783

6884
def callback() -> None:
69-
pass
85+
self.compose_manager.stop()
7086

7187
return Command(
7288
name=inspect.currentframe().f_code.co_name, # type: ignore

src/core/copy_files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from importlib_resources import files # type: ignore
77

88

9-
def copy_files(path: Path, services: list[str] | Iterable[str]) -> None:
9+
def copy_files(path: Path, services: list[str]) -> None:
1010
# Resolve resource traversables
1111
docker_pkg = files("minecraft-docker-cli.assets.docker")
1212
dockerfile_res = docker_pkg.joinpath("Dockerfile")

src/core/docker.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
from __future__ import annotations
2+
3+
from pathlib import Path
4+
from subprocess import CompletedProcess, run
5+
from time import strftime
6+
from typing import Any
7+
8+
from yaspin import yaspin
9+
10+
from .manage_json import read_json
11+
12+
13+
class ComposeManager:
14+
15+
def __init__(self) -> None:
16+
self.composer_file = Path.cwd().joinpath("docker-compose.yml")
17+
if not self.composer_file.exists():
18+
exit(
19+
"ERROR: docker-compose.yml was not located in current directory."
20+
)
21+
22+
def __run(
23+
self, *args: str, capture_output: bool = False
24+
) -> CompletedProcess[str]:
25+
command = ["docker", "compose", "-f", str(self.composer_file), *args]
26+
result = run(command, text=True, capture_output=capture_output)
27+
if result.returncode != 0:
28+
print("ERROR:\n", result.stderr)
29+
else:
30+
print("Command run:\n", result.stdout)
31+
return result
32+
33+
@yaspin("Building Container...")
34+
def build(
35+
self, no_cache: bool = False, pull: bool = False
36+
) -> CompletedProcess[str]:
37+
args = ["build"]
38+
if no_cache:
39+
args.append("--no-cache")
40+
if pull:
41+
args.append("--pull")
42+
return self.__run(*args)
43+
44+
@yaspin("Stopping Services...")
45+
def stop(self) -> CompletedProcess[str]:
46+
return self.__run("stop")
47+
48+
@yaspin("Starting Services...")
49+
def start(self) -> CompletedProcess[str]:
50+
return self.__run("start")
51+
52+
@yaspin("Removing Container...")
53+
def down(self, remove_volumes: bool = False) -> CompletedProcess[str]:
54+
args = ["down"]
55+
if remove_volumes:
56+
args.append("-v")
57+
return self.__run(*args)
58+
59+
@yaspin("Putting Up Container...")
60+
def up(self, detached: bool = True) -> CompletedProcess[str]:
61+
args = ["up"]
62+
if detached:
63+
args.append("-d")
64+
return self.__run(*args)
65+
66+
@yaspin("Backing Up Container...")
67+
def back_up(self, cwd: Path = Path.cwd()) -> None:
68+
backup_path = cwd.joinpath(".backup")
69+
compose_json = cwd.joinpath("data.json")
70+
71+
backup_path.mkdir(exist_ok=True)
72+
data: dict[str, Any] = read_json(compose_json)
73+
services = data.get("composer", {}).get("services", []) or []
74+
names: list[str] = [svc.get("name") for svc in services if svc.get("name") is not None] # type: ignore
75+
for svc_name in names:
76+
container_path = svc_name
77+
tar_file = backup_path.joinpath(
78+
f"{svc_name}_{strftime("%d-%m-%Y_%H:%M:%S")}.tar.gz"
79+
)
80+
container_name = self.__get_container_name(svc_name)
81+
if container_name:
82+
run(
83+
[
84+
"docker",
85+
"compose",
86+
"cp",
87+
f"{container_name}:{container_path}",
88+
tar_file,
89+
],
90+
check=True,
91+
)
92+
93+
def __get_container_name(self, service_name: str) -> str | None:
94+
result = self.__run("ps", capture_output=True)
95+
lines = result.stdout.splitlines()
96+
for line in lines[2:]: # skip header
97+
if service_name in line:
98+
return line.split()[0]
99+
return None

0 commit comments

Comments
 (0)