sentry-devenv 1.18.0__py3-none-any.whl → 1.20.0__py3-none-any.whl

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.
@@ -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 = "credsStore"
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?"
devenv/checks/limaDns.py CHANGED
@@ -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 = "limaDns"
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, "colima isn't up"
22
+ return False, "Colima isn't running."
23
23
 
24
24
  try:
25
25
  proc.run(
devenv/doctor.py CHANGED
@@ -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"\t⏭️ Skipped {check.name}".expandtabs(4))
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"\t✅ check: {check.name}".expandtabs(4))
165
+ print(f" ✅ check: {check.name}")
166
166
  continue
167
- print(f"\t❌ check: {check.name}{msg}".expandtabs(4))
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"\t\tDo you want to attempt to fix {check.name}? (Y/n): ".expandtabs(4)
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"\t❌ {check.name}".expandtabs(4))
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"\t\t✅ fix: {check.name}".expandtabs(4))
240
+ print(f"✅ fix: {check.name}")
241
241
  else:
242
- print(f"\t\t❌ fix: {check.name}{msg}".expandtabs(4))
242
+ print(f"❌ fix: {check.name}{msg}")
243
243
  else:
244
- print(f"\t\t⏭️ Skipping {check.name}".expandtabs(4))
244
+ print(f" ⏭️ Skipping {check.name}")
245
245
  skip.append(check)
246
246
 
247
247
  print("\nChecking that fixes worked as expected...")
devenv/lib/archive.py CHANGED
@@ -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 not os.path.exists(dest):
42
- headers = {}
43
- if url.startswith("https://ghcr.io/v2/homebrew"):
44
- # downloading homebrew blobs requires auth
45
- # you can get an anonymous token from https://ghcr.io/token?service=ghcr.io&scope=repository%3Ahomebrew/core/go%3Apull
46
- # but there's also a special shortcut token QQ==
47
- # https://github.com/Homebrew/brew/blob/2184406bd8444e4de2626f5b0c749d4d08cb1aed/Library/Homebrew/brew.sh#L993
48
- headers["Authorization"] = "bearer QQ=="
49
-
50
- req = urllib.request.Request(url, headers=headers)
51
-
52
- retry_sleep = 1.0
53
- while retries >= 0:
54
- try:
55
- resp = urllib.request.urlopen(req)
56
- break
57
- except HTTPError as e:
58
- if retries == 0:
59
- raise RuntimeError(f"Error getting {url}: {e}")
60
- print(f"Error getting {url} ({retries} retries left): {e}")
61
-
62
- time.sleep(retry_sleep)
63
- retries -= 1
64
- retry_sleep *= retry_exp
65
-
66
- dest_dir = os.path.dirname(dest)
67
- os.makedirs(dest_dir, exist_ok=True)
68
-
69
- with tempfile.NamedTemporaryFile(delete=False, dir=dest_dir) as tmpf:
70
- shutil.copyfileobj(resp, tmpf)
71
- tmpf.seek(0)
72
- checksum = hashlib.sha256()
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
- while buf:
75
- checksum.update(buf)
76
- buf = tmpf.read(4096)
77
-
78
- if not secrets.compare_digest(checksum.hexdigest(), sha256):
79
- raise RuntimeError(
80
- f"checksum mismatch for {url}:\n"
81
- f"- got: {checksum.hexdigest()}\n"
82
- f"- expected: {sha256}\n"
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
 
devenv/lib/colima.py CHANGED
@@ -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 outdated!")
81
+ print(f"installed colima {installed_version} is unexpected!")
82
82
 
83
83
  print(f"installing colima {version}...")
84
84
  uninstall(binroot)
devenv/lib/docker.py CHANGED
@@ -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 outdated! expected: {expected_version}"
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 outdated!")
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(
devenv/lib/gcloud.py CHANGED
@@ -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 outdated!")
85
+ print(f"installed gcloud {installed_version} is unexpected!")
86
86
 
87
87
  print(f"installing gcloud {version}...")
88
88
  uninstall(binroot)
devenv/lib/limactl.py CHANGED
@@ -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 outdated!")
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)
devenv/lib/node.py CHANGED
@@ -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 outdated!")
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 outdated!")
121
+ print(f"installed yarn {installed_version} is unexpected!")
122
122
  return False
123
123
 
124
124
 
devenv/lib/tenv.py CHANGED
@@ -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 outdated!")
91
+ print(f"installed tenv {installed_version} is unexpected!")
92
92
 
93
93
  print(f"installing tenv {version}...")
94
94
  uninstall(binroot)
devenv/lib/venv.py CHANGED
@@ -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 outdated python, recreating at {venv}..."
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.18.0
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
@@ -4,7 +4,7 @@ devenv/__main__.py,sha256=O8ROZOx3emX7hhzMD608Ud_Pny_PCy5RsWXwBLhf8Q8,122
4
4
  devenv/bootstrap.py,sha256=VSlS1giUv8fE0K5pZgIO-QBKg5PsaWxirF6G9OPUUhE,4047
5
5
  devenv/colima.py,sha256=Dk9t6eu2DEoqqkY8xLmgiB96r-6zi3aXcvON3P8h1JQ,1537
6
6
  devenv/constants.py,sha256=wl0sRfkNeJL-sML6MG5nS0zU-dwRo1mDPrhUSHHY1YQ,1498
7
- devenv/doctor.py,sha256=VZlcX1GgIEUGTT4PX0phyWSx1UfI3uQbXsANpKVDTj0,8782
7
+ devenv/doctor.py,sha256=tcwXrE2Y7Ued2gAXCXtfFkyXbG9DOi6jVobbq6DnuZs,8663
8
8
  devenv/fetch.py,sha256=MbztGFOzBR4KDPG0TVWcQsA4pGXk61zrQXvh-C9xHuk,3904
9
9
  devenv/main.py,sha256=JYV1GflML2DOrIhbZq_Vn89dFd0hqOKXxDVSd2GQRgY,3264
10
10
  devenv/pin_gha.py,sha256=t7A5CV1bnYRa4SAikuoJeodHuwV6moIePvhQ1Zdc1eE,2187
@@ -13,28 +13,29 @@ devenv/pythons.py,sha256=ZcjBrkfmQEkiMP217VyTG48cyteJdYG_hoQ6oFKx_CU,1252
13
13
  devenv/sync.py,sha256=B9bvAuI7K-4SqcfHD4_zwKpozAnHWCNOL-IhVOcc3Zg,1254
14
14
  devenv/update.py,sha256=CkshQUvmVVblXdjARgWQKXSe7pwMjSMrPR3gDEesX5c,2402
15
15
  devenv/checks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- devenv/checks/credsStore.py,sha256=eUwtoQ8btFsM8Ir-Vla98MEQFQ5o-OyFu--CSnZr3uY,1149
17
- devenv/checks/limaDns.py,sha256=yX0s63BR9OcX4TWtlywNlaS4NkrlFYj9W_vxX6NAFQg,1860
16
+ devenv/checks/dockerConfig.py,sha256=5VExtcarjizPhR7OSdyWhkvzS6zY-6qiefxlwkexQuk,1623
17
+ devenv/checks/dockerDesktop.py,sha256=ynt8nNQ2DDT2rJDGZ-g7xxc4HErHJV_iqBqYHQtvA7Q,1343
18
+ devenv/checks/limaDns.py,sha256=cHef2czdYYLvQqiM4bxiYtzYw4UVTIXJPwrGW2Kjpxo,1885
18
19
  devenv/checks/test.py,sha256=AvxoA2W5qTO2ZpoXmjScLiJxXD3Y_limYp5yTjGSZ9U,701
19
20
  devenv/lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
- devenv/lib/archive.py,sha256=LlVETRsf3J53yY_8sjL4P8plLNEMKoS9G48kfoVoseA,4503
21
+ devenv/lib/archive.py,sha256=75MjlDuwHVaazbNUr8vJmqTyl4Jz6KRd_fUcpwvPwec,4710
21
22
  devenv/lib/brew.py,sha256=_7recvYh-LnUJC0eKnYK7F8AzGRL5bi_Khv0f_mgrlM,1795
22
- devenv/lib/colima.py,sha256=8JSAGC2pXthY_h7-AOsW0xgN-kyZq7_fh63-hhYFCIE,7381
23
+ devenv/lib/colima.py,sha256=fVzM8jCN06HAkJd3dTz3CXXlpGoykkAe-Ln8RgYb2QU,7383
23
24
  devenv/lib/config.py,sha256=5eXYwM44AJ3w2VfVbCDqchYOL1zAjazNcirtM6kxxGw,3344
24
25
  devenv/lib/context.py,sha256=4EDImK9pvTp3FqeNigevC9lbJsgdDO9D1t2_Y4NyPlc,207
25
26
  devenv/lib/direnv.py,sha256=HDRUaIR0PMAQYiiTaw3gEQuh6Tdb7BUJmV7dBZdPztM,1325
26
- devenv/lib/docker.py,sha256=cOBe9TRzoVkJbYv0g8yqkuyDYyntTasagwBGSiAgijs,5684
27
+ devenv/lib/docker.py,sha256=d-jGdagfvxGpZtfoEG_m6IMcm8YvUEjJoHx0ACKNWKk,5688
27
28
  devenv/lib/fs.py,sha256=IBahQ9fB-o9cK9HipK_L0gso2OVinIDTxFWoeMte9ww,2764
28
- devenv/lib/gcloud.py,sha256=G8rhKC89TqWcFzIVsFd64d4ouD6M6s-TaMX5ZxtKypw,3550
29
+ devenv/lib/gcloud.py,sha256=pJ28_L0gUS1lZna-gIHeJPo9bEfSHVXh63CoRQGyAw8,3552
29
30
  devenv/lib/github.py,sha256=AMtcz6gTVKy7qvCP1zTUdHQlnSM32TVaji0aOXJ_F9A,3463
30
- devenv/lib/limactl.py,sha256=uGcRkXQACDvbwKWhgEqgoUSVZjX6vEUGOutJDEw3kNM,4254
31
+ devenv/lib/limactl.py,sha256=wMA_Q4DRPjmBPe0JSj1xPwYesFa9MT_vVSdaBAAKn5w,4256
31
32
  devenv/lib/modules.py,sha256=shoqgja0b-xzXr601Ryz0JOy3Vxbw53I3cRYezVt77g,859
32
- devenv/lib/node.py,sha256=0LobIBWRQMzBiTKgv-WCva0Q1ZK3sR2NXNmnSa0Pcow,4156
33
+ devenv/lib/node.py,sha256=sp5QbExF0H03UnfLUmFown42_NhyPWKw0H776W7987U,4160
33
34
  devenv/lib/proc.py,sha256=eU7DHIk_66wgkHRmdtKbBKZTa6q4tl9QJbrlnQOQCls,2743
34
35
  devenv/lib/repository.py,sha256=cDCgcZ6lw0bcWC6DrcDp3tz_MWObmgAyFq39TeU7YHY,1966
35
36
  devenv/lib/rosetta.py,sha256=Lm-tDFut86o6l-Y55d5kTacRHTwrv5MFEymQpy7Qihc,1170
36
- devenv/lib/tenv.py,sha256=9-I-7wvJNqanaMEs39bheOfikVz3wgTKtArkeCsOz8I,3028
37
- devenv/lib/venv.py,sha256=VbV2IA3HTk5WABBpMQJvlizdknEQVz1SsLxlEBDOLkc,3186
37
+ devenv/lib/tenv.py,sha256=EulO1xYh8wu6-QoDvQd3TVbaLvmztXqCV6ya5sBWZiw,3030
38
+ devenv/lib/venv.py,sha256=1HWY1uFXLP1YCGVX7Ik5R3tNilRCKrVXOZN42-k_5eQ,3188
38
39
  devenv/lib_check/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
40
  devenv/lib_check/brew.py,sha256=tId1IgItju-fulbmTXmmI2Oo_-q332rd1tUN5RSkTrs,544
40
41
  devenv/lib_check/types.py,sha256=HIvuU3q27Izgm77jH6QX6gKMvgfunx05X4OEsJoqjP8,271
@@ -45,13 +46,13 @@ tests/test_pythons.py,sha256=Nepff11shv18irF3EYzf3U6zsRMrpLeS8QfrGJ3bTME,723
45
46
  tests/test_sync.py,sha256=ihCYFxscPwbVY_cnFyq2fvYUBc_SyB6gsElz334fmaw,871
46
47
  tests/utils.py,sha256=Qr2RM5Vo-14mF8tPSJITiYuhGc6fU9rk-fChxW-lokk,291
47
48
  tests/checks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
- tests/checks/test_credStore.py,sha256=fvvKBx2vqbHDAfwxz77GPHO_gc8jawkqOpaDOob1dEs,1459
49
+ tests/checks/test_dockerConfig.py,sha256=wJQ1ADdG4HJCQyiTirUk5VsEAUfhlSDWCJfW_ylH5po,1702
49
50
  tests/doctor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
50
51
  tests/doctor/test_attempt_fix.py,sha256=cGUv7-xRKWCizC9cNNqiCp7aa95k3nHUXvRFFuLfJ34,1075
51
52
  tests/doctor/test_filter_failing_checks.py,sha256=5L5XdBSyeySwZtJeAR71lbktt3NxkJdUfncKBJPYdAc,1270
52
53
  tests/doctor/test_load_checks.py,sha256=C0uIdjftQZokZNYf003pNFfWuT9Gk1C7ALZzN_JnsWQ,2714
53
54
  tests/doctor/test_prompt_for_fix.py,sha256=dfFcdZnbkboB2-4vk1iyAHj7sCcsvM4AkBarnU8xBs8,1035
54
- tests/doctor/test_run_checks.py,sha256=2sJEvV3FsSa1HCwgVId-Tg8sn1GSR1tn5hcvO72n01U,2321
55
+ tests/doctor/test_run_checks.py,sha256=t03x6ICUPuceTcHUQKBMRTrr0VzRORv9_hZwcnm2v08,2320
55
56
  tests/doctor/devenv/checks/bad_check.py,sha256=uohSVSUMnZmj0eFXEwCOHBzCJN6cdjv2hm6_u9ZMEFw,336
56
57
  tests/doctor/devenv/checks/bad_fix.py,sha256=dQqUumOOw-YhnbweMXYKeYQFVh1CXZzGMDsO73Uv16Y,335
57
58
  tests/doctor/devenv/checks/broken_check.py,sha256=dutNCmaB6COeu8VPMcR_r5hw9Zud0lOXfQG1a5sg8-o,383
@@ -62,7 +63,7 @@ tests/doctor/devenv/checks/no_check.py,sha256=hcX2CNzB07U7r6KHbW71IvsW7UG-JsHcji
62
63
  tests/doctor/devenv/checks/no_name.py,sha256=txJhaXiJjY4CUPONTX9G6J6RttTCJ-BupDmZT1sKoAc,167
63
64
  tests/doctor/devenv/checks/no_tags.py,sha256=ATwlSsn5fe-OwNfgEDDa0HiBoOgkOGwkqIbQuDaghpk,161
64
65
  tests/doctor/devenv/checks/passing_check.py,sha256=F-gW4ZEqXVBL5akMaR3Hcf3yuihm3jfxTY80-EqsbOg,307
65
- tests/lib/test_archive.py,sha256=5FdCF8t64FewevL4P9PFojWS-a15z2I11TX0G1L_A-g,7944
66
+ tests/lib/test_archive.py,sha256=4yWoB0ZBpGLQByutPAOEVjUYSBlWdZppvRofYqX3pjk,9370
66
67
  tests/lib/test_brew.py,sha256=g2Sz-yg4-212qlsKg0vfHf604bQyRjUElsf3khvXrTQ,5711
67
68
  tests/lib/test_direnv.py,sha256=GKpEbsHgaRZWevYrsa0oOGQFCsyllCLX1vGdg3igvAc,1551
68
69
  tests/lib/test_fs.py,sha256=qhX9CQTTSht1f6ZJfmpsSFICTwPs--h_mLlS_1jnZQg,1670
@@ -70,8 +71,8 @@ tests/lib/test_github.py,sha256=IMEG2cmRaK_PMJprFE_ZMqPnZ0StwWqgznhhT_zVk3U,4526
70
71
  tests/lib/test_proc.py,sha256=XH6OnxKPSSm3HvDjYHApputMKwgOE8lYqDuK2vK0Z0U,2626
71
72
  tests/lib/test_repository.py,sha256=gUi1lkY7bha5WwZ5xcnENOlFYboVV8TFW1lCESvS0VA,787
72
73
  tests/lib/test_venv.py,sha256=wscU7Enp8fK6o_2HgIT4WwDnvHbKwaKvJhNyJUrDNqk,3262
73
- sentry_devenv-1.18.0.dist-info/METADATA,sha256=2yYNwF0Iz9QTxXw0dnucRef4B0hIpcil88QzTqs6uf4,13622
74
- sentry_devenv-1.18.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
75
- sentry_devenv-1.18.0.dist-info/entry_points.txt,sha256=StsIuNugcoEU8jsPy6H9ECjzoDzOVKXl1vUaYTIGbzM,44
76
- sentry_devenv-1.18.0.dist-info/top_level.txt,sha256=dOQExvIA0fj_EQjCrMTS7CCHNH7WZFHxEU0M7LlNcKQ,16
77
- sentry_devenv-1.18.0.dist-info/RECORD,,
74
+ sentry_devenv-1.20.0.dist-info/METADATA,sha256=xjMLoZcHZGALmvLVUfBz0bjJp7fW5cZgFFla_IpynCc,13622
75
+ sentry_devenv-1.20.0.dist-info/WHEEL,sha256=wXxTzcEDnjrTwFYjLPcsW_7_XihufBwmpiBeiXNBGEA,91
76
+ sentry_devenv-1.20.0.dist-info/entry_points.txt,sha256=StsIuNugcoEU8jsPy6H9ECjzoDzOVKXl1vUaYTIGbzM,44
77
+ sentry_devenv-1.20.0.dist-info/top_level.txt,sha256=dOQExvIA0fj_EQjCrMTS7CCHNH7WZFHxEU0M7LlNcKQ,16
78
+ sentry_devenv-1.20.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (78.1.0)
2
+ Generator: setuptools (80.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -8,7 +8,7 @@ from unittest import mock
8
8
 
9
9
  import pytest
10
10
 
11
- from devenv.checks import credsStore
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 credsStore.check() == (True, "")
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 credsStore.check() == (True, "")
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 credsStore.check() == (
41
+ assert dockerConfig.check() == (
42
42
  False,
43
43
  "credsStore requires nonexistent binary",
44
44
  )
45
45
 
46
46
 
47
- def test_fix(fake_config: pathlib.Path) -> None:
47
+ def test_fix_credsStore(fake_config: pathlib.Path) -> None:
48
48
  fake_config.write_text('{"credsStore": "bad"}')
49
- assert credsStore.fix() == (True, "")
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 == " ⏭️ Skipped failing check\n"
54
+ assert captured.out == " ⏭️ Skipped failing check\n"
55
55
 
56
56
 
57
57
  def test_run_checks_multiple_failing_checks() -> None:
tests/lib/test_archive.py CHANGED
@@ -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
- dest = f"{tmp_path}/b"
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
- dest = f"{tmp_path}/b"
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,