Skip to content

Commit 7e99513

Browse files
authored
Better permission errors for update, compose (#8)
A check is done preemptively to guess if the user has the necessary permissions to do these operations and provides guidance if necessary. This prevents the CLI from crashing later on with unhelpful stack traces. Fixes #2.
1 parent d45857d commit 7e99513

4 files changed

Lines changed: 71 additions & 31 deletions

File tree

brainframe/cli/commands/compose.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,21 +8,6 @@
88

99
@command("compose")
1010
def compose():
11-
# Check first if the user has permission to interact with Docker
12-
if not os_utils.is_root():
13-
if not os_utils.currently_in_group("docker"):
14-
error_message = i18n.t("compose.docker-bad-permissions") + "\n"
15-
16-
if os_utils.added_to_group("docker"):
17-
# The user is in the group, they just need to restart
18-
error_message += i18n.t("compose.restart-for-group-access")
19-
else:
20-
# The user is not in the group, so they need to either add
21-
# themselves or use sudo
22-
error_message += i18n.t("compose.retry-with-sudo-or-group")
23-
24-
print_utils.fail(error_message)
25-
2611
install_path = env_vars.install_path.get()
2712
docker_compose.assert_installed(install_path)
2813
docker_compose.run(install_path, sys.argv[2:])

brainframe/cli/docker_compose.py

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import subprocess
22
from pathlib import Path
3-
from typing import List
3+
from typing import List, Tuple, cast, TextIO
44
import yaml
5+
import i18n
6+
import os
57

68
from . import os_utils, print_utils, env_vars
79

@@ -14,7 +16,7 @@
1416
)
1517

1618

17-
def assert_installed(install_path: Path):
19+
def assert_installed(install_path: Path) -> None:
1820
compose_path = install_path / "docker-compose.yml"
1921

2022
if not compose_path.is_file():
@@ -24,7 +26,9 @@ def assert_installed(install_path: Path):
2426
)
2527

2628

27-
def run(install_path: Path, commands: List[str]):
29+
def run(install_path: Path, commands: List[str]) -> None:
30+
_assert_has_docker_permissions()
31+
2832
compose_path = install_path / "docker-compose.yml"
2933

3034
full_command = ["docker-compose", "--file", str(compose_path)]
@@ -42,7 +46,9 @@ def run(install_path: Path, commands: List[str]):
4246
os_utils.run(full_command + commands)
4347

4448

45-
def download(target: Path, version="latest"):
49+
def download(target: Path, version: str = "latest") -> None:
50+
_assert_has_write_permissions(target.parent)
51+
4652
subdomain, auth_flags, version = check_download_version(version=version)
4753

4854
url = BRAINFRAME_DOCKER_COMPOSE_URL.format(
@@ -58,7 +64,9 @@ def download(target: Path, version="latest"):
5864
os_utils.give_brainframe_group_rw_access([target])
5965

6066

61-
def check_download_version(version="latest"):
67+
def check_download_version(
68+
version: str = "latest",
69+
) -> Tuple[str, List[str], str]:
6270
subdomain = ""
6371
auth_flags = []
6472

@@ -87,14 +95,57 @@ def check_download_version(version="latest"):
8795
stdout=subprocess.PIPE,
8896
encoding="utf-8",
8997
)
90-
version = result.stdout.readline().strip()
98+
# stdout is a file-like object opened in text mode when the encoding
99+
# argument is "utf-8"
100+
stdout = cast(TextIO, result.stdout)
101+
version = stdout.readline().strip()
91102

92103
return subdomain, auth_flags, version
93104

94105

95-
def check_existing_version(install_path: Path):
106+
def check_existing_version(install_path: Path) -> str:
96107
compose_path = install_path / "docker-compose.yml"
97108
compose = yaml.load(compose_path.read_text(), Loader=yaml.SafeLoader)
98109
version = compose["services"]["core"]["image"].split(":")[-1]
99110
version = "v" + version
100111
return version
112+
113+
114+
def _assert_has_docker_permissions() -> None:
115+
"""Fails if the user does not have permissions to interact with Docker"""
116+
if not (os_utils.is_root() or os_utils.currently_in_group("docker")):
117+
error_message = (
118+
i18n.t("general.docker-bad-permissions")
119+
+ "\n"
120+
+ _group_recommendation_message("docker")
121+
)
122+
123+
print_utils.fail(error_message)
124+
125+
126+
def _assert_has_write_permissions(path: Path) -> None:
127+
"""Fails if the user does not have write access to the given path."""
128+
if os.access(path, os.W_OK):
129+
return
130+
131+
error_message = i18n.t("general.file-bad-write-permissions", path=path)
132+
error_message += "\n"
133+
134+
if path.stat().st_gid == os_utils.BRAINFRAME_GROUP_ID:
135+
error_message += " " + _group_recommendation_message("brainframe")
136+
else:
137+
error_message += " " + i18n.t(
138+
"general.unexpected-group-for-file", path=path, group="brainframe"
139+
)
140+
141+
print_utils.fail(error_message)
142+
143+
144+
def _group_recommendation_message(group: str) -> str:
145+
if os_utils.added_to_group("brainframe"):
146+
# The user is in the group, they just need to restart
147+
return i18n.t("general.restart-for-group-access", group=group)
148+
else:
149+
# The user is not in the group, so they need to either add
150+
# themselves or use sudo
151+
return i18n.t("general.retry-as-root-or-group", group=group)

brainframe/cli/translations/compose.en.yml

Lines changed: 0 additions & 8 deletions
This file was deleted.

brainframe/cli/translations/general.en.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,19 @@ en:
1212
to a custom location, ensure that the %{install_env_var} environment variable
1313
has been set."
1414
mkdir-permission-denied: "Permission denied while making directory
15-
%{directory}, trying with sudo."
15+
%{directory}, trying as root."
1616
staging-missing-credentials: "The %{username_env_var} and %{password_env_var}
1717
variables must be set to access staging."
1818
user-not-root: "This command must be run as root!"
19+
docker-bad-permissions: "Your user does not have sufficient privileges to
20+
use Docker."
21+
restart-for-group-access: "Your user has been added to the \"%{group}\"
22+
group but you must restart to apply this change. Alternatively, you can try
23+
again as root."
24+
retry-as-root-or-group: "Please try again as root or consider adding your
25+
user to the \"%{group}\" group."
26+
file-bad-write-permissions: "Your user does not have write access to
27+
\"%{path}\"."
28+
unexpected-group-for-file: "File \"%{path}\" is not owned by the
29+
\"%{group}\" group, but probably should be. Please add this file to the group
30+
or try again as root."

0 commit comments

Comments
 (0)