sentry-devenv 1.19.0__tar.gz → 1.21.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.
Files changed (83) hide show
  1. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/PKG-INFO +15 -6
  2. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/README.md +14 -5
  3. sentry_devenv-1.19.0/devenv/checks/credsStore.py → sentry_devenv-1.21.0/devenv/checks/dockerConfig.py +11 -1
  4. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/archive.py +53 -44
  5. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/colima.py +1 -1
  6. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/docker.py +2 -2
  7. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/gcloud.py +1 -1
  8. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/limactl.py +1 -1
  9. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/node.py +2 -2
  10. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/tenv.py +1 -1
  11. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/venv.py +1 -1
  12. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/main.py +1 -1
  13. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/pyproject.toml +1 -1
  14. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/sentry_devenv.egg-info/PKG-INFO +15 -6
  15. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/sentry_devenv.egg-info/SOURCES.txt +2 -2
  16. sentry_devenv-1.19.0/tests/checks/test_credStore.py → sentry_devenv-1.21.0/tests/checks/test_dockerConfig.py +12 -6
  17. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/lib/test_archive.py +59 -2
  18. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/ci/integration/repo/devenv/sync.py +0 -0
  19. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/__init__.py +0 -0
  20. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/__main__.py +0 -0
  21. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/bootstrap.py +0 -0
  22. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/checks/__init__.py +0 -0
  23. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/checks/dockerDesktop.py +0 -0
  24. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/checks/limaDns.py +0 -0
  25. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/checks/test.py +0 -0
  26. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/colima.py +0 -0
  27. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/constants.py +0 -0
  28. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/doctor.py +0 -0
  29. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/fetch.py +0 -0
  30. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/__init__.py +0 -0
  31. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/brew.py +0 -0
  32. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/config.py +0 -0
  33. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/context.py +0 -0
  34. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/direnv.py +0 -0
  35. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/fs.py +0 -0
  36. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/github.py +0 -0
  37. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/modules.py +0 -0
  38. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/proc.py +0 -0
  39. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/repository.py +0 -0
  40. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib/rosetta.py +0 -0
  41. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib_check/__init__.py +0 -0
  42. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib_check/brew.py +0 -0
  43. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/lib_check/types.py +0 -0
  44. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/pin_gha.py +0 -0
  45. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/py.typed +0 -0
  46. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/pythons.py +0 -0
  47. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/sync.py +0 -0
  48. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/devenv/update.py +0 -0
  49. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/sentry_devenv.egg-info/dependency_links.txt +0 -0
  50. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/sentry_devenv.egg-info/entry_points.txt +0 -0
  51. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/sentry_devenv.egg-info/requires.txt +0 -0
  52. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/sentry_devenv.egg-info/top_level.txt +0 -0
  53. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/setup.cfg +0 -0
  54. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/__init__.py +0 -0
  55. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/checks/__init__.py +0 -0
  56. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/conftest.py +0 -0
  57. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/__init__.py +0 -0
  58. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/bad_check.py +0 -0
  59. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/bad_fix.py +0 -0
  60. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/broken_check.py +0 -0
  61. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/broken_fix.py +0 -0
  62. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/failing_check.py +0 -0
  63. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/failing_check_with_msg.py +0 -0
  64. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/no_check.py +0 -0
  65. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/no_name.py +0 -0
  66. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/no_tags.py +0 -0
  67. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/devenv/checks/passing_check.py +0 -0
  68. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/test_attempt_fix.py +0 -0
  69. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/test_filter_failing_checks.py +0 -0
  70. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/test_load_checks.py +0 -0
  71. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/test_prompt_for_fix.py +0 -0
  72. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/doctor/test_run_checks.py +0 -0
  73. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/lib/test_brew.py +0 -0
  74. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/lib/test_direnv.py +0 -0
  75. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/lib/test_fs.py +0 -0
  76. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/lib/test_github.py +0 -0
  77. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/lib/test_proc.py +0 -0
  78. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/lib/test_repository.py +0 -0
  79. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/lib/test_venv.py +0 -0
  80. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/test_main.py +0 -0
  81. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/test_pythons.py +0 -0
  82. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/test_sync.py +0 -0
  83. {sentry_devenv-1.19.0 → sentry_devenv-1.21.0}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentry_devenv
3
- Version: 1.19.0
3
+ Version: 1.21.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
@@ -47,7 +47,6 @@ To update this installation, run `devenv update`.
47
47
 
48
48
  This is intended for initial setup of a new machine.
49
49
 
50
-
51
50
  `devenv fetch [repository name]`
52
51
 
53
52
  Any repository on github in the form of `[org]/[reponame]`
@@ -71,7 +70,6 @@ In general, our library is designed to isolate, as much as possible, a repo's de
71
70
  For example, [gcloud](#gcloud) is installed to `[reporoot]/.devenv/bin/gcloud` (with the gcloud sdk at `[reporoot]/.devenv/bin/google-cloud-sdk`).
72
71
  An exception to this would be python virtualenvs, which was implemented before the idea of `[reporoot]/.devenv`.
73
72
 
74
-
75
73
  `devenv doctor`
76
74
 
77
75
  Use this to diagnose and fix common issues.
@@ -79,14 +77,25 @@ Use this to diagnose and fix common issues.
79
77
  Repo-specific checks and fixes can be defined in `[reporoot]/devenv/checks`.
80
78
  Otherwise we have "builtin" checks and fixes in `devenv.checks`.
81
79
 
80
+ `devenv update`
81
+
82
+ This updates the global devenv installation, and global tools.
83
+
84
+ If you're upgrading from a particularly old devenv, it won't have `update` so you need to:
85
+ `~/.local/share/sentry-devenv/venv/bin/pip install -U sentry-devenv`
86
+
82
87
 
83
88
  ## technical overview
84
89
 
85
90
  Everything devenv needs is in `~/.local/share/sentry-devenv`.
86
91
 
87
- - `~/.local/share/sentry-devenv/bin` contains `devenv` and `direnv`
88
- - we currently rely on a minimal [`[reporoot]/.envrc`](#direnv) to add `[reporoot]/.devenv/bin` to PATH
89
- - see [examples](#examples) for .envrc suggestions
92
+ - `~/.local/share/sentry-devenv/bin` contains:
93
+ - `devenv` itself
94
+ - `direnv`
95
+ - we currently rely on direnv and a minimal [`[reporoot]/.envrc`](#direnv) to add `[reporoot]/.devenv/bin` to PATH
96
+ - see [examples](#examples) for .envrc suggestions
97
+ - global tools: `docker` (cli), `colima`
98
+
90
99
 
91
100
  ### runtime
92
101
 
@@ -34,7 +34,6 @@ To update this installation, run `devenv update`.
34
34
 
35
35
  This is intended for initial setup of a new machine.
36
36
 
37
-
38
37
  `devenv fetch [repository name]`
39
38
 
40
39
  Any repository on github in the form of `[org]/[reponame]`
@@ -58,7 +57,6 @@ In general, our library is designed to isolate, as much as possible, a repo's de
58
57
  For example, [gcloud](#gcloud) is installed to `[reporoot]/.devenv/bin/gcloud` (with the gcloud sdk at `[reporoot]/.devenv/bin/google-cloud-sdk`).
59
58
  An exception to this would be python virtualenvs, which was implemented before the idea of `[reporoot]/.devenv`.
60
59
 
61
-
62
60
  `devenv doctor`
63
61
 
64
62
  Use this to diagnose and fix common issues.
@@ -66,14 +64,25 @@ Use this to diagnose and fix common issues.
66
64
  Repo-specific checks and fixes can be defined in `[reporoot]/devenv/checks`.
67
65
  Otherwise we have "builtin" checks and fixes in `devenv.checks`.
68
66
 
67
+ `devenv update`
68
+
69
+ This updates the global devenv installation, and global tools.
70
+
71
+ If you're upgrading from a particularly old devenv, it won't have `update` so you need to:
72
+ `~/.local/share/sentry-devenv/venv/bin/pip install -U sentry-devenv`
73
+
69
74
 
70
75
  ## technical overview
71
76
 
72
77
  Everything devenv needs is in `~/.local/share/sentry-devenv`.
73
78
 
74
- - `~/.local/share/sentry-devenv/bin` contains `devenv` and `direnv`
75
- - we currently rely on a minimal [`[reporoot]/.envrc`](#direnv) to add `[reporoot]/.devenv/bin` to PATH
76
- - see [examples](#examples) for .envrc suggestions
79
+ - `~/.local/share/sentry-devenv/bin` contains:
80
+ - `devenv` itself
81
+ - `direnv`
82
+ - we currently rely on direnv and a minimal [`[reporoot]/.envrc`](#direnv) to add `[reporoot]/.devenv/bin` to PATH
83
+ - see [examples](#examples) for .envrc suggestions
84
+ - global tools: `docker` (cli), `colima`
85
+
77
86
 
78
87
  ### runtime
79
88
 
@@ -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 fix"
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)
@@ -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
 
@@ -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)
@@ -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(
@@ -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)
@@ -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)
@@ -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
 
@@ -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)
@@ -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)
@@ -103,7 +103,7 @@ def main() -> ExitCode:
103
103
  # https://sentry.sentry.io/settings/projects/sentry-dev-env/keys/
104
104
  dsn="https://9bdb053cb8274ea69231834d1edeec4c@o1.ingest.sentry.io/5723503",
105
105
  # enable performance monitoring
106
- enable_tracing=True,
106
+ traces_sample_rate=1.0,
107
107
  )
108
108
 
109
109
  return devenv(sys.argv, f"{home}/.config/sentry-devenv/config.ini")
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sentry_devenv"
7
- version = "1.19.0"
7
+ version = "1.21.0"
8
8
  authors = [
9
9
  { name="Joshua Li", email="joshua.li@sentry.io" },
10
10
  { name="Ian Woodard", email="ian.woodard@sentry.io" },
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sentry_devenv
3
- Version: 1.19.0
3
+ Version: 1.21.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
@@ -47,7 +47,6 @@ To update this installation, run `devenv update`.
47
47
 
48
48
  This is intended for initial setup of a new machine.
49
49
 
50
-
51
50
  `devenv fetch [repository name]`
52
51
 
53
52
  Any repository on github in the form of `[org]/[reponame]`
@@ -71,7 +70,6 @@ In general, our library is designed to isolate, as much as possible, a repo's de
71
70
  For example, [gcloud](#gcloud) is installed to `[reporoot]/.devenv/bin/gcloud` (with the gcloud sdk at `[reporoot]/.devenv/bin/google-cloud-sdk`).
72
71
  An exception to this would be python virtualenvs, which was implemented before the idea of `[reporoot]/.devenv`.
73
72
 
74
-
75
73
  `devenv doctor`
76
74
 
77
75
  Use this to diagnose and fix common issues.
@@ -79,14 +77,25 @@ Use this to diagnose and fix common issues.
79
77
  Repo-specific checks and fixes can be defined in `[reporoot]/devenv/checks`.
80
78
  Otherwise we have "builtin" checks and fixes in `devenv.checks`.
81
79
 
80
+ `devenv update`
81
+
82
+ This updates the global devenv installation, and global tools.
83
+
84
+ If you're upgrading from a particularly old devenv, it won't have `update` so you need to:
85
+ `~/.local/share/sentry-devenv/venv/bin/pip install -U sentry-devenv`
86
+
82
87
 
83
88
  ## technical overview
84
89
 
85
90
  Everything devenv needs is in `~/.local/share/sentry-devenv`.
86
91
 
87
- - `~/.local/share/sentry-devenv/bin` contains `devenv` and `direnv`
88
- - we currently rely on a minimal [`[reporoot]/.envrc`](#direnv) to add `[reporoot]/.devenv/bin` to PATH
89
- - see [examples](#examples) for .envrc suggestions
92
+ - `~/.local/share/sentry-devenv/bin` contains:
93
+ - `devenv` itself
94
+ - `direnv`
95
+ - we currently rely on direnv and a minimal [`[reporoot]/.envrc`](#direnv) to add `[reporoot]/.devenv/bin` to PATH
96
+ - see [examples](#examples) for .envrc suggestions
97
+ - global tools: `docker` (cli), `colima`
98
+
90
99
 
91
100
  ### runtime
92
101
 
@@ -16,7 +16,7 @@ devenv/pythons.py
16
16
  devenv/sync.py
17
17
  devenv/update.py
18
18
  devenv/checks/__init__.py
19
- devenv/checks/credsStore.py
19
+ devenv/checks/dockerConfig.py
20
20
  devenv/checks/dockerDesktop.py
21
21
  devenv/checks/limaDns.py
22
22
  devenv/checks/test.py
@@ -55,7 +55,7 @@ tests/test_pythons.py
55
55
  tests/test_sync.py
56
56
  tests/utils.py
57
57
  tests/checks/__init__.py
58
- tests/checks/test_credStore.py
58
+ tests/checks/test_dockerConfig.py
59
59
  tests/doctor/__init__.py
60
60
  tests/doctor/test_attempt_fix.py
61
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 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() == "{}"
@@ -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,
File without changes