borescope 1.0.0__tar.gz → 1.0.2__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 (149) hide show
  1. {borescope-1.0.0 → borescope-1.0.2}/PKG-INFO +19 -3
  2. {borescope-1.0.0 → borescope-1.0.2}/README.md +18 -2
  3. {borescope-1.0.0 → borescope-1.0.2}/pyproject.toml +1 -1
  4. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/cli.py +8 -0
  5. borescope-1.0.2/src/borescope/snap.py +62 -0
  6. borescope-1.0.2/tests/test_snap.py +90 -0
  7. {borescope-1.0.0 → borescope-1.0.2}/.gitignore +0 -0
  8. {borescope-1.0.0 → borescope-1.0.2}/LICENSE +0 -0
  9. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/__init__.py +0 -0
  10. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/__main__.py +0 -0
  11. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/discovery.py +0 -0
  12. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/errors.py +0 -0
  13. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/juju.py +0 -0
  14. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/__init__.py +0 -0
  15. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/commands/__init__.py +0 -0
  16. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/commands/_args.py +0 -0
  17. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/commands/base.py +0 -0
  18. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/commands/basic.py +0 -0
  19. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/commands/execcmd.py +0 -0
  20. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/commands/filesystem.py +0 -0
  21. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/commands/pebble.py +0 -0
  22. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/completion.py +0 -0
  23. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/context.py +0 -0
  24. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/history.py +0 -0
  25. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/output.py +0 -0
  26. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/parser.py +0 -0
  27. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/pathutils.py +0 -0
  28. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/repl.py +0 -0
  29. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/sanitise.py +0 -0
  30. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/shell/theme.py +0 -0
  31. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/snapshot.py +0 -0
  32. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/transport/__init__.py +0 -0
  33. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/transport/cli_transport.py +0 -0
  34. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/transport/relay.py +0 -0
  35. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/transport/runner.py +0 -0
  36. {borescope-1.0.0 → borescope-1.0.2}/src/borescope/transport/socket_transport.py +0 -0
  37. {borescope-1.0.0 → borescope-1.0.2}/tests/charms/bareshell-test/.gitignore +0 -0
  38. {borescope-1.0.0 → borescope-1.0.2}/tests/charms/bareshell-test/Makefile +0 -0
  39. {borescope-1.0.0 → borescope-1.0.2}/tests/charms/bareshell-test/README.md +0 -0
  40. {borescope-1.0.0 → borescope-1.0.2}/tests/charms/bareshell-test/charmcraft.yaml +0 -0
  41. {borescope-1.0.0 → borescope-1.0.2}/tests/charms/bareshell-test/requirements.txt +0 -0
  42. {borescope-1.0.0 → borescope-1.0.2}/tests/charms/bareshell-test/src/charm.py +0 -0
  43. {borescope-1.0.0 → borescope-1.0.2}/tests/charms/bareshell-test/workload-src/go.mod +0 -0
  44. {borescope-1.0.0 → borescope-1.0.2}/tests/charms/bareshell-test/workload-src/main.go +0 -0
  45. {borescope-1.0.0 → borescope-1.0.2}/tests/cloud-config.yaml +0 -0
  46. {borescope-1.0.0 → borescope-1.0.2}/tests/conftest.py +0 -0
  47. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/README.md +0 -0
  48. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/corpus/mixed_quoting +0 -0
  49. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/corpus/pipe_command +0 -0
  50. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/corpus/pipe_with_flags +0 -0
  51. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/corpus/simple_command +0 -0
  52. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/corpus/single_quoted_arg +0 -0
  53. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/corpus/tilde_expansion +0 -0
  54. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/corpus/variable_expansion +0 -0
  55. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/corpus/whitespace_only +0 -0
  56. {borescope-1.0.0 → borescope-1.0.2}/tests/fuzz/fuzz_parser.py +0 -0
  57. {borescope-1.0.0 → borescope-1.0.2}/tests/integration/__init__.py +0 -0
  58. {borescope-1.0.0 → borescope-1.0.2}/tests/integration/conftest.py +0 -0
  59. {borescope-1.0.0 → borescope-1.0.2}/tests/integration/test_socket.py +0 -0
  60. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cat-missing-file-errors/task.yaml +0 -0
  61. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cat-multiple-files-concatenated/task.yaml +0 -0
  62. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cat-no-args-reads-stdin/task.yaml +0 -0
  63. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cat-single-file/task.yaml +0 -0
  64. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cd-absolute-path/task.yaml +0 -0
  65. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cd-into-file-errors/task.yaml +0 -0
  66. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cd-no-args-goes-home/task.yaml +0 -0
  67. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cd-relative-path/task.yaml +0 -0
  68. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cp-file-to-existing-dir/task.yaml +0 -0
  69. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cp-file-to-file/task.yaml +0 -0
  70. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/cp-missing-source-errors/task.yaml +0 -0
  71. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/double-quoted-tilde-is-literal/task.yaml +0 -0
  72. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/double-quotes-expand-var/task.yaml +0 -0
  73. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/echo-empty-string-arg-divergence/task.yaml +0 -0
  74. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/echo-multiple-args/task.yaml +0 -0
  75. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/echo-no-args-divergence/task.yaml +0 -0
  76. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/env-lists-tracked-variables/task.yaml +0 -0
  77. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/exit-default-zero/task.yaml +0 -0
  78. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/exit-non-numeric-arg-divergence/task.yaml +0 -0
  79. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/exit-with-code/task.yaml +0 -0
  80. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/expand-no-expansion-in-single-quotes/task.yaml +0 -0
  81. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/expand-tilde-alone/task.yaml +0 -0
  82. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/expand-tilde-not-at-word-start/task.yaml +0 -0
  83. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/expand-tilde-prefix/task.yaml +0 -0
  84. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/expand-unknown-var-empty/task.yaml +0 -0
  85. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/expand-var-braces/task.yaml +0 -0
  86. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/expand-var-dollar/task.yaml +0 -0
  87. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/find-by-name-pattern/task.yaml +0 -0
  88. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/find-by-type-directory/task.yaml +0 -0
  89. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/find-by-type-file/task.yaml +0 -0
  90. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/grep-basic-match-exit-0/task.yaml +0 -0
  91. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/grep-case-insensitive-i/task.yaml +0 -0
  92. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/grep-count-c/task.yaml +0 -0
  93. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/grep-invalid-pattern-divergence/task.yaml +0 -0
  94. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/grep-invert-v/task.yaml +0 -0
  95. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/grep-line-numbers-n/task.yaml +0 -0
  96. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/grep-multiple-files-prefixed/task.yaml +0 -0
  97. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/grep-no-match-exit-1/task.yaml +0 -0
  98. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/head-default-10-lines/task.yaml +0 -0
  99. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/head-missing-file-errors/task.yaml +0 -0
  100. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/head-n-flag/task.yaml +0 -0
  101. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/lib.sh +0 -0
  102. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/ls-a-shows-hidden/task.yaml +0 -0
  103. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/ls-default-no-hidden/task.yaml +0 -0
  104. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/ls-multiple-operands-headered/task.yaml +0 -0
  105. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/mixed-quoting-within-word/task.yaml +0 -0
  106. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/mkdir-create/task.yaml +0 -0
  107. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/mkdir-existing-errors/task.yaml +0 -0
  108. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/mkdir-p-creates-parents/task.yaml +0 -0
  109. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/mkdir-p-existing-ok/task.yaml +0 -0
  110. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/mv-into-existing-dir/task.yaml +0 -0
  111. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/mv-rename/task.yaml +0 -0
  112. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/pipe-empty-stage-rejected/task.yaml +0 -0
  113. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/pipe-quoted-bar-is-literal/task.yaml +0 -0
  114. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/pipe-two-stage/task.yaml +0 -0
  115. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/pwd-default-root/task.yaml +0 -0
  116. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/quoted-operator-literal-arg/task.yaml +0 -0
  117. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/rm-existing-file/task.yaml +0 -0
  118. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/rm-missing-with-f-ok/task.yaml +0 -0
  119. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/rm-missing-without-f-errors/task.yaml +0 -0
  120. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/rm-non-empty-dir-without-r-errors/task.yaml +0 -0
  121. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/rm-recursive/task.yaml +0 -0
  122. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/sequencing-semicolon-divergence/task.yaml +0 -0
  123. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/tail-default-10-lines/task.yaml +0 -0
  124. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/tail-n-flag/task.yaml +0 -0
  125. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/tokenize-double-quotes/task.yaml +0 -0
  126. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/touch-creates-new-file/task.yaml +0 -0
  127. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/touch-existing-updates-mtime-divergence/task.yaml +0 -0
  128. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/unbalanced-quote-is-error/task.yaml +0 -0
  129. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/unsupported-and-divergence/task.yaml +0 -0
  130. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/unsupported-multi-pipe-divergence/task.yaml +0 -0
  131. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/unsupported-or-divergence/task.yaml +0 -0
  132. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/unsupported-redirect-append-divergence/task.yaml +0 -0
  133. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/unsupported-redirect-in-divergence/task.yaml +0 -0
  134. {borescope-1.0.0 → borescope-1.0.2}/tests/spread/unsupported-redirect-out-divergence/task.yaml +0 -0
  135. {borescope-1.0.0 → borescope-1.0.2}/tests/test_args.py +0 -0
  136. {borescope-1.0.0 → borescope-1.0.2}/tests/test_cli.py +0 -0
  137. {borescope-1.0.0 → borescope-1.0.2}/tests/test_commands.py +0 -0
  138. {borescope-1.0.0 → borescope-1.0.2}/tests/test_completion.py +0 -0
  139. {borescope-1.0.0 → borescope-1.0.2}/tests/test_discovery.py +0 -0
  140. {borescope-1.0.0 → borescope-1.0.2}/tests/test_juju.py +0 -0
  141. {borescope-1.0.0 → borescope-1.0.2}/tests/test_parser.py +0 -0
  142. {borescope-1.0.0 → borescope-1.0.2}/tests/test_pathutils.py +0 -0
  143. {borescope-1.0.0 → borescope-1.0.2}/tests/test_pebble_commands.py +0 -0
  144. {borescope-1.0.0 → borescope-1.0.2}/tests/test_registry.py +0 -0
  145. {borescope-1.0.0 → borescope-1.0.2}/tests/test_relay.py +0 -0
  146. {borescope-1.0.0 → borescope-1.0.2}/tests/test_repl.py +0 -0
  147. {borescope-1.0.0 → borescope-1.0.2}/tests/test_runner.py +0 -0
  148. {borescope-1.0.0 → borescope-1.0.2}/tests/test_sanitise.py +0 -0
  149. {borescope-1.0.0 → borescope-1.0.2}/tests/test_snapshot.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: borescope
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: A natural shell for debugging Juju Kubernetes workload containers via Pebble
5
5
  Project-URL: Homepage, https://github.com/tonyandrewmeyer/borescope
6
6
  Project-URL: Repository, https://github.com/tonyandrewmeyer/borescope
@@ -54,10 +54,26 @@ can't, it fails the same way.
54
54
 
55
55
  ## Install
56
56
 
57
- > v1 is under active development. For now, from a checkout:
57
+ From the [snap store](https://snapcraft.io/borescope):
58
58
 
59
59
  ```console
60
- uv tool install . # or: pipx install .
60
+ sudo snap install borescope
61
+ ```
62
+
63
+ A few things to know about the snap:
64
+
65
+ - It bundles its own juju (currently `juju/4/stable`), so it works
66
+ even without juju installed on the host.
67
+ - It reads your `~/.local/share/juju` (JUJU_DATA) read-only via the
68
+ auto-connecting `juju-client-observe` interface, then copies it into
69
+ a writable per-snap directory at startup. **Run `juju login` /
70
+ `juju switch` outside borescope** — changes made inside a borescope
71
+ session don't propagate back to the host JUJU_DATA.
72
+
73
+ Or from [PyPI](https://pypi.org/project/borescope/):
74
+
75
+ ```console
76
+ uv tool install borescope # or: uvx borescope, pipx install borescope
61
77
  ```
62
78
 
63
79
  ## Usage
@@ -23,10 +23,26 @@ can't, it fails the same way.
23
23
 
24
24
  ## Install
25
25
 
26
- > v1 is under active development. For now, from a checkout:
26
+ From the [snap store](https://snapcraft.io/borescope):
27
27
 
28
28
  ```console
29
- uv tool install . # or: pipx install .
29
+ sudo snap install borescope
30
+ ```
31
+
32
+ A few things to know about the snap:
33
+
34
+ - It bundles its own juju (currently `juju/4/stable`), so it works
35
+ even without juju installed on the host.
36
+ - It reads your `~/.local/share/juju` (JUJU_DATA) read-only via the
37
+ auto-connecting `juju-client-observe` interface, then copies it into
38
+ a writable per-snap directory at startup. **Run `juju login` /
39
+ `juju switch` outside borescope** — changes made inside a borescope
40
+ session don't propagate back to the host JUJU_DATA.
41
+
42
+ Or from [PyPI](https://pypi.org/project/borescope/):
43
+
44
+ ```console
45
+ uv tool install borescope # or: uvx borescope, pipx install borescope
30
46
  ```
31
47
 
32
48
  ## Usage
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "borescope"
3
- version = "1.0.0"
3
+ version = "1.0.2"
4
4
  description = "A natural shell for debugging Juju Kubernetes workload containers via Pebble"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -107,6 +107,14 @@ def main(argv: list[str] | None = None) -> int:
107
107
  return 0
108
108
 
109
109
  args = build_parser().parse_args(argv)
110
+
111
+ # When running as a snap, stage the host's JUJU_DATA into the snap's
112
+ # writable home so the bundled juju can refresh macaroons/cookies. No-op
113
+ # outside the snap. See borescope.snap for the trade-offs.
114
+ from .snap import stage_juju_data
115
+
116
+ stage_juju_data()
117
+
110
118
  if not args.unit and not args.socket and not args.here:
111
119
  print(
112
120
  "borescope: A unit reference is required (for example 'borescope myapp/0'), "
@@ -0,0 +1,62 @@
1
+ # Copyright 2026 Tony Meyer
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Snap-environment helpers.
5
+
6
+ Under strict confinement, the snap can only *read* the host's Juju
7
+ configuration directory via the ``juju-client-observe`` interface — it
8
+ cannot write to it. Borescope's bundled juju, however, refreshes
9
+ macaroons and cookies on the fly and will fail the moment it tries to
10
+ update ``cookies.yaml`` or ``accounts.yaml``.
11
+
12
+ Personal-files would grant write access but pushes the snap into manual
13
+ store review on every revision until an auto-connect declaration is
14
+ granted. To stay on auto-connecting interfaces, we instead stage a copy
15
+ of the host's ``~/.local/share/juju`` into the snap's writable
16
+ ``$SNAP_USER_COMMON/juju`` on every invocation, point ``JUJU_DATA`` at
17
+ the copy, and let the bundled juju write freely to it.
18
+
19
+ Trade-offs of staging:
20
+
21
+ * The copy is one-way (host → snap). Logins/model-switches you do
22
+ *inside* a borescope session never write back to the host, so they
23
+ vanish at the end of the session. Do ``juju login`` / ``juju
24
+ switch`` *outside* borescope.
25
+ * The copy happens at every borescope invocation, so host-side changes
26
+ are picked up on the next run. For typical use (debug a workload,
27
+ exit, move on) this is invisible.
28
+
29
+ This module is a no-op outside the snap, so unit tests and PyPI
30
+ installs are unaffected.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ import os
36
+ import pwd
37
+ import shutil
38
+
39
+
40
+ def stage_juju_data() -> None:
41
+ """If running inside the borescope snap, copy host JUJU_DATA into writable storage.
42
+
43
+ The snap's ``apps.borescope.environment`` block sets
44
+ ``JUJU_DATA=$SNAP_USER_COMMON/juju``; this function populates that
45
+ directory by copying ``~/.local/share/juju`` (read-only via
46
+ ``juju-client-observe``) into it on every invocation. The bundled
47
+ juju then reads and writes the copy freely.
48
+
49
+ No-op outside the snap, and no-op if the host has no JUJU_DATA
50
+ (the bundled juju will surface "no controllers registered" itself).
51
+ """
52
+ if os.environ.get('SNAP_NAME') != 'borescope':
53
+ return
54
+ snap_user_common = os.environ.get('SNAP_USER_COMMON')
55
+ if not snap_user_common:
56
+ return
57
+ real_home = pwd.getpwuid(os.getuid()).pw_dir
58
+ source = os.path.join(real_home, '.local', 'share', 'juju')
59
+ if not os.path.isdir(source):
60
+ return
61
+ target = os.path.join(snap_user_common, 'juju')
62
+ shutil.copytree(source, target, dirs_exist_ok=True, symlinks=True)
@@ -0,0 +1,90 @@
1
+ # Copyright 2026 Tony Meyer
2
+ # SPDX-License-Identifier: Apache-2.0
3
+
4
+ """Tests for the snap startup hook that stages JUJU_DATA."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import pwd
9
+
10
+ import pytest
11
+
12
+ from borescope.snap import stage_juju_data
13
+
14
+
15
+ @pytest.fixture
16
+ def in_snap(monkeypatch, tmp_path):
17
+ """Pretend we're running inside the borescope snap."""
18
+ real_home = tmp_path / 'home'
19
+ snap_common = tmp_path / 'snap-common'
20
+ source = real_home / '.local' / 'share' / 'juju'
21
+ target = snap_common / 'juju'
22
+
23
+ source.mkdir(parents=True)
24
+ snap_common.mkdir()
25
+ (source / 'controllers.yaml').write_text('controllers:\n k8s: {}\n')
26
+ (source / 'cookies.yaml').write_text('cookies: []\n')
27
+
28
+ monkeypatch.setenv('SNAP_NAME', 'borescope')
29
+ monkeypatch.setenv('SNAP_USER_COMMON', str(snap_common))
30
+ monkeypatch.setattr(
31
+ pwd,
32
+ 'getpwuid',
33
+ lambda _uid: pwd.struct_passwd(('u', 'x', 0, 0, '', str(real_home), '/bin/sh')),
34
+ )
35
+ return source, target
36
+
37
+
38
+ def test_noop_outside_snap(monkeypatch, tmp_path):
39
+ monkeypatch.delenv('SNAP_NAME', raising=False)
40
+ target = tmp_path / 'juju'
41
+ stage_juju_data()
42
+ assert not target.exists()
43
+
44
+
45
+ def test_noop_when_snap_name_is_something_else(monkeypatch):
46
+ monkeypatch.setenv('SNAP_NAME', 'jhack')
47
+ stage_juju_data() # must not raise
48
+
49
+
50
+ def test_stages_when_host_juju_data_exists(in_snap):
51
+ source, target = in_snap
52
+ stage_juju_data()
53
+ assert (target / 'controllers.yaml').read_text() == (source / 'controllers.yaml').read_text()
54
+ assert (target / 'cookies.yaml').read_text() == (source / 'cookies.yaml').read_text()
55
+
56
+
57
+ def test_noop_when_host_juju_data_missing(monkeypatch, tmp_path):
58
+ real_home = tmp_path / 'home'
59
+ snap_common = tmp_path / 'snap-common'
60
+ real_home.mkdir()
61
+ snap_common.mkdir()
62
+ monkeypatch.setenv('SNAP_NAME', 'borescope')
63
+ monkeypatch.setenv('SNAP_USER_COMMON', str(snap_common))
64
+ monkeypatch.setattr(
65
+ pwd,
66
+ 'getpwuid',
67
+ lambda _uid: pwd.struct_passwd(('u', 'x', 0, 0, '', str(real_home), '/bin/sh')),
68
+ )
69
+ stage_juju_data()
70
+ assert not (snap_common / 'juju').exists()
71
+
72
+
73
+ def test_subsequent_runs_pick_up_host_changes(in_snap):
74
+ source, target = in_snap
75
+ stage_juju_data()
76
+ assert (target / 'controllers.yaml').read_text().startswith('controllers:\n k8s')
77
+ (source / 'controllers.yaml').write_text('controllers:\n prod: {}\n')
78
+ stage_juju_data()
79
+ assert 'prod' in (target / 'controllers.yaml').read_text()
80
+
81
+
82
+ def test_inside_snap_writes_bundled_juju_cookies_to_copy(in_snap):
83
+ """Writes by the bundled juju into the snap copy must not leak back to host."""
84
+ source, target = in_snap
85
+ stage_juju_data()
86
+ (target / 'cookies.yaml').write_text('cookies:\n - inside-snap\n')
87
+ # Re-run: host source is unchanged, target gets overwritten with host data.
88
+ stage_juju_data()
89
+ assert (source / 'cookies.yaml').read_text() == 'cookies: []\n'
90
+ assert (target / 'cookies.yaml').read_text() == 'cookies: []\n'
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