sentry-devenv 1.18.0__tar.gz → 1.20.0__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.
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/PKG-INFO +1 -1
- sentry_devenv-1.18.0/devenv/checks/credsStore.py → sentry_devenv-1.20.0/devenv/checks/dockerConfig.py +11 -1
- sentry_devenv-1.20.0/devenv/checks/dockerDesktop.py +47 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/checks/limaDns.py +2 -2
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/doctor.py +9 -9
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/archive.py +53 -44
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/colima.py +1 -1
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/docker.py +2 -2
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/gcloud.py +1 -1
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/limactl.py +1 -1
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/node.py +2 -2
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/tenv.py +1 -1
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/venv.py +1 -1
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/pyproject.toml +1 -1
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/sentry_devenv.egg-info/PKG-INFO +1 -1
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/sentry_devenv.egg-info/SOURCES.txt +3 -2
- sentry_devenv-1.18.0/tests/checks/test_credStore.py → sentry_devenv-1.20.0/tests/checks/test_dockerConfig.py +12 -6
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/test_run_checks.py +1 -1
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/lib/test_archive.py +59 -2
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/README.md +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/ci/integration/repo/devenv/sync.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/__init__.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/__main__.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/bootstrap.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/checks/__init__.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/checks/test.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/colima.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/constants.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/fetch.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/__init__.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/brew.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/config.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/context.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/direnv.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/fs.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/github.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/modules.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/proc.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/repository.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib/rosetta.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib_check/__init__.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib_check/brew.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/lib_check/types.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/main.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/pin_gha.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/py.typed +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/pythons.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/sync.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/devenv/update.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/sentry_devenv.egg-info/dependency_links.txt +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/sentry_devenv.egg-info/entry_points.txt +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/sentry_devenv.egg-info/requires.txt +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/sentry_devenv.egg-info/top_level.txt +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/setup.cfg +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/__init__.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/checks/__init__.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/conftest.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/__init__.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/bad_check.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/bad_fix.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/broken_check.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/broken_fix.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/failing_check.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/failing_check_with_msg.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/no_check.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/no_name.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/no_tags.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/passing_check.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/test_attempt_fix.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/test_filter_failing_checks.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/test_load_checks.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/test_prompt_for_fix.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/lib/test_brew.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/lib/test_direnv.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/lib/test_fs.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/lib/test_github.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/lib/test_proc.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/lib/test_repository.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/lib/test_venv.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/test_main.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/test_pythons.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/test_sync.py +0 -0
- {sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sentry_devenv
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.20.0
|
|
4
4
|
Summary: Utilities for setting up a Sentry development environment
|
|
5
5
|
Author-email: Joshua Li <joshua.li@sentry.io>, Ian Woodard <ian.woodard@sentry.io>, Buck Evan <buck.evan@sentry.io>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -8,7 +8,7 @@ from devenv.lib_check.types import checker
|
|
|
8
8
|
from devenv.lib_check.types import fixer
|
|
9
9
|
|
|
10
10
|
tags: set[str] = {"builtin"}
|
|
11
|
-
name = "
|
|
11
|
+
name = "correct docker configuration"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@checker
|
|
@@ -23,6 +23,15 @@ def check() -> tuple[bool, str]:
|
|
|
23
23
|
if store and not shutil.which(f"docker-credential-{store}"):
|
|
24
24
|
return False, "credsStore requires nonexistent binary"
|
|
25
25
|
|
|
26
|
+
# When docker-buildx is installed via brew, brew adds cliPluginsExtraDirs
|
|
27
|
+
# which takes precedence over the default plugin path we rely on.
|
|
28
|
+
# This ensures the devenv-managed global docker cli uses the default plugin path.
|
|
29
|
+
if config.get("cliPluginsExtraDirs"):
|
|
30
|
+
return (
|
|
31
|
+
False,
|
|
32
|
+
"cliPluginsExtraDirs exists, which overshadows the default plugin path",
|
|
33
|
+
)
|
|
34
|
+
|
|
26
35
|
return True, ""
|
|
27
36
|
|
|
28
37
|
|
|
@@ -33,6 +42,7 @@ def fix() -> tuple[bool, str]:
|
|
|
33
42
|
config = json.load(f)
|
|
34
43
|
|
|
35
44
|
config.pop("credsStore", None)
|
|
45
|
+
config.pop("cliPluginsExtraDirs", None)
|
|
36
46
|
|
|
37
47
|
with open(os.path.expanduser("~/.docker/config.json"), "w") as f:
|
|
38
48
|
json.dump(config, f)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from devenv.lib import proc
|
|
6
|
+
from devenv.lib_check.types import checker
|
|
7
|
+
from devenv.lib_check.types import fixer
|
|
8
|
+
|
|
9
|
+
tags: set[str] = {"builtin"}
|
|
10
|
+
name = "docker desktop shouldn't be running"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def docker_desktop_is_running() -> bool:
|
|
14
|
+
procs = proc.run(("/bin/ps", "-Ac", "-o", "comm"), stdout=True)
|
|
15
|
+
return "Docker Desktop" in procs.split("\n")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@checker
|
|
19
|
+
def check() -> tuple[bool, str]:
|
|
20
|
+
if docker_desktop_is_running():
|
|
21
|
+
return (
|
|
22
|
+
False,
|
|
23
|
+
"Docker Desktop is running. We don't support it, and it conflicts with colima.",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return True, ""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@fixer
|
|
30
|
+
def fix() -> tuple[bool, str]:
|
|
31
|
+
# regular pkill won't stop the Docker Desktop UI,
|
|
32
|
+
# it'll just spin. the proper way to terminate it
|
|
33
|
+
# without SIGKILL is to use osascript.
|
|
34
|
+
print("Attempting to stop Docker Desktop.")
|
|
35
|
+
try:
|
|
36
|
+
proc.run(("osascript", "-e", 'quit app "Docker Desktop"'))
|
|
37
|
+
except RuntimeError as e:
|
|
38
|
+
return False, f"failed to quit Docker Desktop:\n{e}\n"
|
|
39
|
+
|
|
40
|
+
# osascript doesn't wait to make sure it finishes quitting
|
|
41
|
+
# so let's block for up to 5 secs
|
|
42
|
+
for _ in range(10):
|
|
43
|
+
time.sleep(0.5)
|
|
44
|
+
if not docker_desktop_is_running():
|
|
45
|
+
return True, ""
|
|
46
|
+
|
|
47
|
+
return False, "Docker Desktop is taking too long to quit... try again?"
|
|
@@ -8,7 +8,7 @@ from devenv.lib_check.types import checker
|
|
|
8
8
|
from devenv.lib_check.types import fixer
|
|
9
9
|
|
|
10
10
|
tags: set[str] = {"builtin"}
|
|
11
|
-
name = "
|
|
11
|
+
name = "colima's DNS isn't working"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@checker
|
|
@@ -19,7 +19,7 @@ def check() -> tuple[bool, str]:
|
|
|
19
19
|
|
|
20
20
|
status = colima.check()
|
|
21
21
|
if status != colima.ColimaStatus.UP:
|
|
22
|
-
return False, "
|
|
22
|
+
return False, "Colima isn't running."
|
|
23
23
|
|
|
24
24
|
try:
|
|
25
25
|
proc.run(
|
|
@@ -143,7 +143,7 @@ def run_checks(
|
|
|
143
143
|
results: dict[Check, tuple[bool, str]] = {}
|
|
144
144
|
for check in checks:
|
|
145
145
|
if check in skip:
|
|
146
|
-
print(f"
|
|
146
|
+
print(f" ⏭️ Skipped {check.name}")
|
|
147
147
|
continue
|
|
148
148
|
futures[check] = executor.submit(check.check)
|
|
149
149
|
for check, future in futures.items():
|
|
@@ -162,9 +162,9 @@ def filter_failing_checks(
|
|
|
162
162
|
for check, result in results.items():
|
|
163
163
|
ok, msg = result
|
|
164
164
|
if ok:
|
|
165
|
-
print(f"
|
|
165
|
+
print(f" ✅ check: {check.name}")
|
|
166
166
|
continue
|
|
167
|
-
print(f"
|
|
167
|
+
print(f" ❌ check: {check.name}\n {msg}")
|
|
168
168
|
failing_checks.append(check)
|
|
169
169
|
return failing_checks
|
|
170
170
|
|
|
@@ -172,7 +172,7 @@ def filter_failing_checks(
|
|
|
172
172
|
def prompt_for_fix(check: Check) -> bool:
|
|
173
173
|
"""Prompt the user to attempt a fix."""
|
|
174
174
|
return input(
|
|
175
|
-
f"
|
|
175
|
+
f" Do you want to attempt to fix {check.name}? (Y/n): "
|
|
176
176
|
).lower() in {"y", "yes", ""}
|
|
177
177
|
|
|
178
178
|
|
|
@@ -229,19 +229,19 @@ def main(context: Context, argv: Sequence[str] | None = None) -> int:
|
|
|
229
229
|
if args.check_only:
|
|
230
230
|
return 1
|
|
231
231
|
|
|
232
|
-
print("\nThe following problems have been identified:")
|
|
233
232
|
skip: list[Check] = []
|
|
233
|
+
print("\nLet's go through the failures one by one.")
|
|
234
234
|
for check in failing_checks:
|
|
235
|
-
print(f"
|
|
235
|
+
print(f"❌ {check.name}")
|
|
236
236
|
# Prompt for fixes one by one, so the user can decide to skip a fix.
|
|
237
237
|
if prompt_for_fix(check):
|
|
238
238
|
ok, msg = attempt_fix(check, executor)
|
|
239
239
|
if ok:
|
|
240
|
-
print(f"
|
|
240
|
+
print(f"✅ fix: {check.name}")
|
|
241
241
|
else:
|
|
242
|
-
print(f"
|
|
242
|
+
print(f"❌ fix: {check.name}{msg}")
|
|
243
243
|
else:
|
|
244
|
-
print(f"
|
|
244
|
+
print(f" ⏭️ Skipping {check.name}")
|
|
245
245
|
skip.append(check)
|
|
246
246
|
|
|
247
247
|
print("\nChecking that fixes worked as expected...")
|
|
@@ -38,51 +38,60 @@ def download(
|
|
|
38
38
|
dest = f"{cache_root}/{sha256}"
|
|
39
39
|
os.makedirs(cache_root, exist_ok=True)
|
|
40
40
|
|
|
41
|
-
if
|
|
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
|
-
|
|
41
|
+
if os.path.islink(dest):
|
|
42
|
+
# there are cases where dest can be an existing symlink
|
|
43
|
+
# (docker desktop starts and puts symlinks into ~/.docker/cli-plugins)
|
|
44
|
+
# such symlinks should be removed otherwise callers to download
|
|
45
|
+
# usually try to chmod after and end up with PermissionError
|
|
46
|
+
os.remove(dest)
|
|
47
|
+
|
|
48
|
+
if os.path.exists(dest):
|
|
49
|
+
return dest
|
|
50
|
+
|
|
51
|
+
headers = {}
|
|
52
|
+
if url.startswith("https://ghcr.io/v2/homebrew"):
|
|
53
|
+
# downloading homebrew blobs requires auth
|
|
54
|
+
# you can get an anonymous token from https://ghcr.io/token?service=ghcr.io&scope=repository%3Ahomebrew/core/go%3Apull
|
|
55
|
+
# but there's also a special shortcut token QQ==
|
|
56
|
+
# https://github.com/Homebrew/brew/blob/2184406bd8444e4de2626f5b0c749d4d08cb1aed/Library/Homebrew/brew.sh#L993
|
|
57
|
+
headers["Authorization"] = "bearer QQ=="
|
|
58
|
+
|
|
59
|
+
req = urllib.request.Request(url, headers=headers)
|
|
60
|
+
|
|
61
|
+
retry_sleep = 1.0
|
|
62
|
+
while retries >= 0:
|
|
63
|
+
try:
|
|
64
|
+
resp = urllib.request.urlopen(req)
|
|
65
|
+
break
|
|
66
|
+
except HTTPError as e:
|
|
67
|
+
if retries == 0:
|
|
68
|
+
raise RuntimeError(f"Error getting {url}: {e}")
|
|
69
|
+
print(f"Error getting {url} ({retries} retries left): {e}")
|
|
70
|
+
|
|
71
|
+
time.sleep(retry_sleep)
|
|
72
|
+
retries -= 1
|
|
73
|
+
retry_sleep *= retry_exp
|
|
74
|
+
|
|
75
|
+
dest_dir = os.path.dirname(dest)
|
|
76
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
77
|
+
|
|
78
|
+
with tempfile.NamedTemporaryFile(delete=False, dir=dest_dir) as tmpf:
|
|
79
|
+
shutil.copyfileobj(resp, tmpf)
|
|
80
|
+
tmpf.seek(0)
|
|
81
|
+
checksum = hashlib.sha256()
|
|
82
|
+
buf = tmpf.read(4096)
|
|
83
|
+
while buf:
|
|
84
|
+
checksum.update(buf)
|
|
73
85
|
buf = tmpf.read(4096)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
atomic_replace(tmpf.name, dest)
|
|
86
|
+
|
|
87
|
+
if not secrets.compare_digest(checksum.hexdigest(), sha256):
|
|
88
|
+
raise RuntimeError(
|
|
89
|
+
f"checksum mismatch for {url}:\n"
|
|
90
|
+
f"- got: {checksum.hexdigest()}\n"
|
|
91
|
+
f"- expected: {sha256}\n"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
atomic_replace(tmpf.name, dest)
|
|
86
95
|
|
|
87
96
|
return dest
|
|
88
97
|
|
|
@@ -78,7 +78,7 @@ def install_global() -> None:
|
|
|
78
78
|
installed_version = stdout.strip().split()[-1]
|
|
79
79
|
if version == installed_version:
|
|
80
80
|
return
|
|
81
|
-
print(f"installed colima {installed_version} is
|
|
81
|
+
print(f"installed colima {installed_version} is unexpected!")
|
|
82
82
|
|
|
83
83
|
print(f"installing colima {version}...")
|
|
84
84
|
uninstall(binroot)
|
|
@@ -101,7 +101,7 @@ def _check_buildx(binroot: str, expected_version: str) -> bool:
|
|
|
101
101
|
return True
|
|
102
102
|
|
|
103
103
|
print(
|
|
104
|
-
f"installed docker-buildx {installed_version} is
|
|
104
|
+
f"installed docker-buildx {installed_version} is unexpected! expected: {expected_version}"
|
|
105
105
|
)
|
|
106
106
|
return False
|
|
107
107
|
|
|
@@ -133,7 +133,7 @@ def install_global() -> None:
|
|
|
133
133
|
stdout = proc.run((f"{binroot}/docker", "--version"), stdout=True)
|
|
134
134
|
installed_version = stdout.strip().split()[2][:-1]
|
|
135
135
|
if version != installed_version:
|
|
136
|
-
print(f"installed docker {installed_version} is
|
|
136
|
+
print(f"installed docker {installed_version} is unexpected!")
|
|
137
137
|
print(f"installing docker (cli, not desktop) {version}...")
|
|
138
138
|
uninstall(binroot)
|
|
139
139
|
_install(
|
|
@@ -82,7 +82,7 @@ def install(version: str, url: str, sha256: str, reporoot: str) -> None:
|
|
|
82
82
|
installed_version = f.read().strip()
|
|
83
83
|
if version == installed_version:
|
|
84
84
|
return
|
|
85
|
-
print(f"installed gcloud {installed_version} is
|
|
85
|
+
print(f"installed gcloud {installed_version} is unexpected!")
|
|
86
86
|
|
|
87
87
|
print(f"installing gcloud {version}...")
|
|
88
88
|
uninstall(binroot)
|
|
@@ -91,7 +91,7 @@ def install_global() -> None:
|
|
|
91
91
|
installed_version = stdout.strip().split()[-1]
|
|
92
92
|
if version == installed_version:
|
|
93
93
|
return
|
|
94
|
-
print(f"installed limactl {installed_version} is
|
|
94
|
+
print(f"installed limactl {installed_version} is unexpected!")
|
|
95
95
|
|
|
96
96
|
uninstall(binroot)
|
|
97
97
|
_install(cfg[SYSTEM_MACHINE], cfg[f"{SYSTEM_MACHINE}_sha256"], binroot)
|
|
@@ -67,7 +67,7 @@ def installed(version: str, binroot: str) -> bool:
|
|
|
67
67
|
if version == installed_version:
|
|
68
68
|
return True
|
|
69
69
|
|
|
70
|
-
print(f"installed node {installed_version} is
|
|
70
|
+
print(f"installed node {installed_version} is unexpected!")
|
|
71
71
|
return False
|
|
72
72
|
|
|
73
73
|
|
|
@@ -118,7 +118,7 @@ def installed_yarn(version: str, binroot: str) -> bool:
|
|
|
118
118
|
if version == installed_version:
|
|
119
119
|
return True
|
|
120
120
|
|
|
121
|
-
print(f"installed yarn {installed_version} is
|
|
121
|
+
print(f"installed yarn {installed_version} is unexpected!")
|
|
122
122
|
return False
|
|
123
123
|
|
|
124
124
|
|
|
@@ -88,7 +88,7 @@ def install(version: str, url: str, sha256: str, reporoot: str) -> None:
|
|
|
88
88
|
installed_version = _version(binpath)
|
|
89
89
|
if version == installed_version:
|
|
90
90
|
return
|
|
91
|
-
print(f"installed tenv {installed_version} is
|
|
91
|
+
print(f"installed tenv {installed_version} is unexpected!")
|
|
92
92
|
|
|
93
93
|
print(f"installing tenv {version}...")
|
|
94
94
|
uninstall(binroot)
|
|
@@ -106,7 +106,7 @@ def ensure(venv: str, python_version: str, url: str, sha256: str) -> None:
|
|
|
106
106
|
return
|
|
107
107
|
|
|
108
108
|
print(
|
|
109
|
-
f"virtualenv doesn't exist or is using an
|
|
109
|
+
f"virtualenv doesn't exist or is using an unexpected python, recreating at {venv}..."
|
|
110
110
|
)
|
|
111
111
|
if os.path.exists(venv):
|
|
112
112
|
shutil.rmtree(venv)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sentry_devenv
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.20.0
|
|
4
4
|
Summary: Utilities for setting up a Sentry development environment
|
|
5
5
|
Author-email: Joshua Li <joshua.li@sentry.io>, Ian Woodard <ian.woodard@sentry.io>, Buck Evan <buck.evan@sentry.io>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -16,7 +16,8 @@ devenv/pythons.py
|
|
|
16
16
|
devenv/sync.py
|
|
17
17
|
devenv/update.py
|
|
18
18
|
devenv/checks/__init__.py
|
|
19
|
-
devenv/checks/
|
|
19
|
+
devenv/checks/dockerConfig.py
|
|
20
|
+
devenv/checks/dockerDesktop.py
|
|
20
21
|
devenv/checks/limaDns.py
|
|
21
22
|
devenv/checks/test.py
|
|
22
23
|
devenv/lib/__init__.py
|
|
@@ -54,7 +55,7 @@ tests/test_pythons.py
|
|
|
54
55
|
tests/test_sync.py
|
|
55
56
|
tests/utils.py
|
|
56
57
|
tests/checks/__init__.py
|
|
57
|
-
tests/checks/
|
|
58
|
+
tests/checks/test_dockerConfig.py
|
|
58
59
|
tests/doctor/__init__.py
|
|
59
60
|
tests/doctor/test_attempt_fix.py
|
|
60
61
|
tests/doctor/test_filter_failing_checks.py
|
|
@@ -8,7 +8,7 @@ from unittest import mock
|
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
10
|
|
|
11
|
-
from devenv.checks import
|
|
11
|
+
from devenv.checks import dockerConfig
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@pytest.fixture
|
|
@@ -25,26 +25,32 @@ def fake_config(tmp_path: pathlib.Path) -> Generator[pathlib.Path]:
|
|
|
25
25
|
|
|
26
26
|
def test_no_credsStore_ok(fake_config: pathlib.Path) -> None:
|
|
27
27
|
fake_config.write_text("{}")
|
|
28
|
-
assert
|
|
28
|
+
assert dockerConfig.check() == (True, "")
|
|
29
29
|
|
|
30
30
|
|
|
31
31
|
def test_binary_ok(fake_config: pathlib.Path) -> None:
|
|
32
32
|
fake_config.write_text('{"credsStore": "example"}')
|
|
33
33
|
with mock.patch.object(shutil, "which", return_value="/fake/exe"):
|
|
34
|
-
assert
|
|
34
|
+
assert dockerConfig.check() == (True, "")
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@pytest.mark.parametrize("name", ("desktop", "osxkeychain"))
|
|
38
38
|
def test_binary_missing(fake_config: pathlib.Path, name: str) -> None:
|
|
39
39
|
fake_config.write_text(f'{{"credsStore": "{name}"}}')
|
|
40
40
|
with mock.patch.object(shutil, "which", return_value=None):
|
|
41
|
-
assert
|
|
41
|
+
assert dockerConfig.check() == (
|
|
42
42
|
False,
|
|
43
43
|
"credsStore requires nonexistent binary",
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def
|
|
47
|
+
def test_fix_credsStore(fake_config: pathlib.Path) -> None:
|
|
48
48
|
fake_config.write_text('{"credsStore": "bad"}')
|
|
49
|
-
assert
|
|
49
|
+
assert dockerConfig.fix() == (True, "")
|
|
50
|
+
assert fake_config.read_text() == "{}"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_fix_cliPluginsExtraDirs(fake_config: pathlib.Path) -> None:
|
|
54
|
+
fake_config.write_text('{"cliPluginsExtraDirs": ["foo/"]}')
|
|
55
|
+
assert dockerConfig.fix() == (True, "")
|
|
50
56
|
assert fake_config.read_text() == "{}"
|
|
@@ -51,7 +51,7 @@ def test_run_checks_skip(capsys: pytest.CaptureFixture[str]) -> None:
|
|
|
51
51
|
[first_check, second_check], ThreadPoolExecutor(), skip=[second_check]
|
|
52
52
|
) == {first_check: (True, "")}
|
|
53
53
|
captured = capsys.readouterr()
|
|
54
|
-
assert captured.out == "
|
|
54
|
+
assert captured.out == " ⏭️ Skipped failing check\n"
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def test_run_checks_multiple_failing_checks() -> None:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import io
|
|
4
|
+
import os
|
|
4
5
|
import pathlib
|
|
5
6
|
import tarfile
|
|
6
7
|
import time
|
|
@@ -169,6 +170,12 @@ def test_download(tmp_path: pathlib.Path, mock_sleep: mock.MagicMock) -> None:
|
|
|
169
170
|
)
|
|
170
171
|
|
|
171
172
|
dest = f"{tmp_path}/a"
|
|
173
|
+
|
|
174
|
+
# if dest is already a valid symlink it should be paved over
|
|
175
|
+
with open(f"{tmp_path}/hi", "wb"):
|
|
176
|
+
pass
|
|
177
|
+
os.symlink(f"{tmp_path}/hi", dest)
|
|
178
|
+
|
|
172
179
|
with mock.patch.object(
|
|
173
180
|
urllib.request,
|
|
174
181
|
"urlopen",
|
|
@@ -186,7 +193,22 @@ def test_download(tmp_path: pathlib.Path, mock_sleep: mock.MagicMock) -> None:
|
|
|
186
193
|
with open(dest, "rb") as f:
|
|
187
194
|
assert f.read() == data
|
|
188
195
|
|
|
189
|
-
|
|
196
|
+
|
|
197
|
+
def test_download_exceeded_retries(
|
|
198
|
+
tmp_path: pathlib.Path, mock_sleep: mock.MagicMock
|
|
199
|
+
) -> None:
|
|
200
|
+
data_sha256 = (
|
|
201
|
+
"b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
err = urllib.error.HTTPError(
|
|
205
|
+
"https://example.com/foo",
|
|
206
|
+
503,
|
|
207
|
+
"Service Unavailable",
|
|
208
|
+
"", # type: ignore
|
|
209
|
+
io.BytesIO(b""),
|
|
210
|
+
)
|
|
211
|
+
dest = f"{tmp_path}/a"
|
|
190
212
|
with pytest.raises(RuntimeError) as excinfo:
|
|
191
213
|
with mock.patch.object(
|
|
192
214
|
urllib.request,
|
|
@@ -202,7 +224,42 @@ def test_download(tmp_path: pathlib.Path, mock_sleep: mock.MagicMock) -> None:
|
|
|
202
224
|
== "Error getting https://example.com/foo: HTTP Error 503: Service Unavailable"
|
|
203
225
|
)
|
|
204
226
|
|
|
205
|
-
|
|
227
|
+
|
|
228
|
+
def test_download_dest_is_broken_symlink(
|
|
229
|
+
tmp_path: pathlib.Path, mock_sleep: mock.MagicMock
|
|
230
|
+
) -> None:
|
|
231
|
+
data = b"foo\n"
|
|
232
|
+
data_sha256 = (
|
|
233
|
+
"b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
dest = f"{tmp_path}/a"
|
|
237
|
+
|
|
238
|
+
# if dest is already a dead symlink it should be paved over as well
|
|
239
|
+
os.symlink(f"{tmp_path}/does-not-exist", dest)
|
|
240
|
+
|
|
241
|
+
with mock.patch.object(
|
|
242
|
+
urllib.request,
|
|
243
|
+
"urlopen",
|
|
244
|
+
autospec=True,
|
|
245
|
+
side_effect=(io.BytesIO(data),),
|
|
246
|
+
):
|
|
247
|
+
archive.download("https://example.com/foo", data_sha256, dest)
|
|
248
|
+
|
|
249
|
+
with open(dest, "rb") as f:
|
|
250
|
+
assert f.read() == data
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def test_download_wrong_sha(
|
|
254
|
+
tmp_path: pathlib.Path, mock_sleep: mock.MagicMock
|
|
255
|
+
) -> None:
|
|
256
|
+
data = b"foo\n"
|
|
257
|
+
data_sha256 = (
|
|
258
|
+
"b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
dest = f"{tmp_path}/a"
|
|
262
|
+
|
|
206
263
|
with pytest.raises(RuntimeError) as excinfo:
|
|
207
264
|
with mock.patch.object(
|
|
208
265
|
urllib.request,
|
|
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
|
|
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
|
{sentry_devenv-1.18.0 → sentry_devenv-1.20.0}/tests/doctor/devenv/checks/failing_check_with_msg.py
RENAMED
|
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
|