borescope 0.1.0.dev1__tar.gz → 1.0.1__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.
- borescope-1.0.1/.gitignore +38 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/PKG-INFO +32 -5
- {borescope-0.1.0.dev1 → borescope-1.0.1}/README.md +29 -2
- {borescope-0.1.0.dev1 → borescope-1.0.1}/pyproject.toml +104 -17
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/__init__.py +6 -3
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/__main__.py +4 -1
- borescope-1.0.1/src/borescope/cli.py +164 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/discovery.py +66 -58
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/errors.py +4 -7
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/juju.py +13 -14
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/__init__.py +4 -1
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/commands/__init__.py +4 -1
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/commands/_args.py +18 -7
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/commands/base.py +25 -11
- borescope-1.0.1/src/borescope/shell/commands/basic.py +109 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/commands/execcmd.py +13 -12
- borescope-1.0.1/src/borescope/shell/commands/filesystem.py +503 -0
- borescope-1.0.1/src/borescope/shell/commands/pebble.py +489 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/completion.py +16 -10
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/context.py +9 -4
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/history.py +6 -4
- borescope-1.0.1/src/borescope/shell/output.py +35 -0
- borescope-1.0.1/src/borescope/shell/parser.py +223 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/pathutils.py +7 -4
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/repl.py +19 -20
- borescope-1.0.1/src/borescope/shell/sanitise.py +37 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/shell/theme.py +11 -7
- borescope-1.0.1/src/borescope/snapshot.py +102 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/transport/__init__.py +10 -13
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/transport/cli_transport.py +9 -6
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/transport/relay.py +8 -5
- borescope-1.0.1/src/borescope/transport/runner.py +241 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/src/borescope/transport/socket_transport.py +4 -1
- borescope-1.0.1/tests/charms/bareshell-test/.gitignore +5 -0
- borescope-1.0.1/tests/charms/bareshell-test/Makefile +33 -0
- borescope-1.0.1/tests/charms/bareshell-test/README.md +42 -0
- borescope-1.0.1/tests/charms/bareshell-test/charmcraft.yaml +30 -0
- borescope-1.0.1/tests/charms/bareshell-test/requirements.txt +1 -0
- borescope-1.0.1/tests/charms/bareshell-test/src/charm.py +123 -0
- borescope-1.0.1/tests/charms/bareshell-test/workload-src/go.mod +5 -0
- borescope-1.0.1/tests/charms/bareshell-test/workload-src/main.go +70 -0
- borescope-1.0.1/tests/cloud-config.yaml +10 -0
- borescope-1.0.1/tests/conftest.py +317 -0
- borescope-1.0.1/tests/fuzz/README.md +77 -0
- borescope-1.0.1/tests/fuzz/corpus/mixed_quoting +1 -0
- borescope-1.0.1/tests/fuzz/corpus/pipe_command +1 -0
- borescope-1.0.1/tests/fuzz/corpus/pipe_with_flags +1 -0
- borescope-1.0.1/tests/fuzz/corpus/simple_command +1 -0
- borescope-1.0.1/tests/fuzz/corpus/single_quoted_arg +1 -0
- borescope-1.0.1/tests/fuzz/corpus/tilde_expansion +1 -0
- borescope-1.0.1/tests/fuzz/corpus/variable_expansion +1 -0
- borescope-1.0.1/tests/fuzz/corpus/whitespace_only +1 -0
- borescope-1.0.1/tests/fuzz/fuzz_parser.py +60 -0
- borescope-1.0.1/tests/integration/__init__.py +3 -0
- {borescope-0.1.0.dev1 → borescope-1.0.1}/tests/integration/conftest.py +13 -10
- {borescope-0.1.0.dev1 → borescope-1.0.1}/tests/integration/test_socket.py +31 -30
- borescope-1.0.1/tests/spread/cat-missing-file-errors/task.yaml +23 -0
- borescope-1.0.1/tests/spread/cat-multiple-files-concatenated/task.yaml +28 -0
- borescope-1.0.1/tests/spread/cat-no-args-reads-stdin/task.yaml +18 -0
- borescope-1.0.1/tests/spread/cat-single-file/task.yaml +25 -0
- borescope-1.0.1/tests/spread/cd-absolute-path/task.yaml +16 -0
- borescope-1.0.1/tests/spread/cd-into-file-errors/task.yaml +25 -0
- borescope-1.0.1/tests/spread/cd-no-args-goes-home/task.yaml +20 -0
- borescope-1.0.1/tests/spread/cd-relative-path/task.yaml +18 -0
- borescope-1.0.1/tests/spread/cp-file-to-existing-dir/task.yaml +25 -0
- borescope-1.0.1/tests/spread/cp-file-to-file/task.yaml +23 -0
- borescope-1.0.1/tests/spread/cp-missing-source-errors/task.yaml +28 -0
- borescope-1.0.1/tests/spread/double-quoted-tilde-is-literal/task.yaml +21 -0
- borescope-1.0.1/tests/spread/double-quotes-expand-var/task.yaml +23 -0
- borescope-1.0.1/tests/spread/echo-empty-string-arg-divergence/task.yaml +22 -0
- borescope-1.0.1/tests/spread/echo-multiple-args/task.yaml +24 -0
- borescope-1.0.1/tests/spread/echo-no-args-divergence/task.yaml +25 -0
- borescope-1.0.1/tests/spread/env-lists-tracked-variables/task.yaml +23 -0
- borescope-1.0.1/tests/spread/exit-default-zero/task.yaml +27 -0
- borescope-1.0.1/tests/spread/exit-non-numeric-arg-divergence/task.yaml +25 -0
- borescope-1.0.1/tests/spread/exit-with-code/task.yaml +21 -0
- borescope-1.0.1/tests/spread/expand-no-expansion-in-single-quotes/task.yaml +20 -0
- borescope-1.0.1/tests/spread/expand-tilde-alone/task.yaml +19 -0
- borescope-1.0.1/tests/spread/expand-tilde-not-at-word-start/task.yaml +21 -0
- borescope-1.0.1/tests/spread/expand-tilde-prefix/task.yaml +17 -0
- borescope-1.0.1/tests/spread/expand-unknown-var-empty/task.yaml +23 -0
- borescope-1.0.1/tests/spread/expand-var-braces/task.yaml +19 -0
- borescope-1.0.1/tests/spread/expand-var-dollar/task.yaml +19 -0
- borescope-1.0.1/tests/spread/find-by-name-pattern/task.yaml +29 -0
- borescope-1.0.1/tests/spread/find-by-type-directory/task.yaml +25 -0
- borescope-1.0.1/tests/spread/find-by-type-file/task.yaml +25 -0
- borescope-1.0.1/tests/spread/grep-basic-match-exit-0/task.yaml +25 -0
- borescope-1.0.1/tests/spread/grep-case-insensitive-i/task.yaml +22 -0
- borescope-1.0.1/tests/spread/grep-count-c/task.yaml +22 -0
- borescope-1.0.1/tests/spread/grep-invalid-pattern-divergence/task.yaml +34 -0
- borescope-1.0.1/tests/spread/grep-invert-v/task.yaml +24 -0
- borescope-1.0.1/tests/spread/grep-line-numbers-n/task.yaml +24 -0
- borescope-1.0.1/tests/spread/grep-multiple-files-prefixed/task.yaml +26 -0
- borescope-1.0.1/tests/spread/grep-no-match-exit-1/task.yaml +27 -0
- borescope-1.0.1/tests/spread/head-default-10-lines/task.yaml +28 -0
- borescope-1.0.1/tests/spread/head-missing-file-errors/task.yaml +21 -0
- borescope-1.0.1/tests/spread/head-n-flag/task.yaml +25 -0
- borescope-1.0.1/tests/spread/lib.sh +81 -0
- borescope-1.0.1/tests/spread/ls-a-shows-hidden/task.yaml +25 -0
- borescope-1.0.1/tests/spread/ls-default-no-hidden/task.yaml +30 -0
- borescope-1.0.1/tests/spread/ls-multiple-operands-headered/task.yaml +30 -0
- borescope-1.0.1/tests/spread/mixed-quoting-within-word/task.yaml +23 -0
- borescope-1.0.1/tests/spread/mkdir-create/task.yaml +22 -0
- borescope-1.0.1/tests/spread/mkdir-existing-errors/task.yaml +30 -0
- borescope-1.0.1/tests/spread/mkdir-p-creates-parents/task.yaml +23 -0
- borescope-1.0.1/tests/spread/mkdir-p-existing-ok/task.yaml +24 -0
- borescope-1.0.1/tests/spread/mv-into-existing-dir/task.yaml +27 -0
- borescope-1.0.1/tests/spread/mv-rename/task.yaml +25 -0
- borescope-1.0.1/tests/spread/pipe-empty-stage-rejected/task.yaml +23 -0
- borescope-1.0.1/tests/spread/pipe-quoted-bar-is-literal/task.yaml +19 -0
- borescope-1.0.1/tests/spread/pipe-two-stage/task.yaml +24 -0
- borescope-1.0.1/tests/spread/pwd-default-root/task.yaml +20 -0
- borescope-1.0.1/tests/spread/quoted-operator-literal-arg/task.yaml +22 -0
- borescope-1.0.1/tests/spread/rm-existing-file/task.yaml +23 -0
- borescope-1.0.1/tests/spread/rm-missing-with-f-ok/task.yaml +36 -0
- borescope-1.0.1/tests/spread/rm-missing-without-f-errors/task.yaml +23 -0
- borescope-1.0.1/tests/spread/rm-non-empty-dir-without-r-errors/task.yaml +34 -0
- borescope-1.0.1/tests/spread/rm-recursive/task.yaml +23 -0
- borescope-1.0.1/tests/spread/sequencing-semicolon-divergence/task.yaml +26 -0
- borescope-1.0.1/tests/spread/tail-default-10-lines/task.yaml +24 -0
- borescope-1.0.1/tests/spread/tail-n-flag/task.yaml +26 -0
- borescope-1.0.1/tests/spread/tokenize-double-quotes/task.yaml +19 -0
- borescope-1.0.1/tests/spread/touch-creates-new-file/task.yaml +25 -0
- borescope-1.0.1/tests/spread/touch-existing-updates-mtime-divergence/task.yaml +36 -0
- borescope-1.0.1/tests/spread/unbalanced-quote-is-error/task.yaml +25 -0
- borescope-1.0.1/tests/spread/unsupported-and-divergence/task.yaml +25 -0
- borescope-1.0.1/tests/spread/unsupported-multi-pipe-divergence/task.yaml +24 -0
- borescope-1.0.1/tests/spread/unsupported-or-divergence/task.yaml +22 -0
- borescope-1.0.1/tests/spread/unsupported-redirect-append-divergence/task.yaml +23 -0
- borescope-1.0.1/tests/spread/unsupported-redirect-in-divergence/task.yaml +22 -0
- borescope-1.0.1/tests/spread/unsupported-redirect-out-divergence/task.yaml +26 -0
- borescope-1.0.1/tests/test_args.py +60 -0
- borescope-1.0.1/tests/test_cli.py +102 -0
- borescope-1.0.1/tests/test_commands.py +251 -0
- borescope-1.0.1/tests/test_completion.py +53 -0
- borescope-1.0.1/tests/test_discovery.py +196 -0
- borescope-1.0.1/tests/test_juju.py +95 -0
- borescope-1.0.1/tests/test_parser.py +104 -0
- borescope-1.0.1/tests/test_pathutils.py +33 -0
- borescope-1.0.1/tests/test_pebble_commands.py +243 -0
- borescope-1.0.1/tests/test_registry.py +68 -0
- borescope-1.0.1/tests/test_relay.py +31 -0
- borescope-1.0.1/tests/test_repl.py +67 -0
- borescope-1.0.1/tests/test_runner.py +227 -0
- borescope-1.0.1/tests/test_sanitise.py +26 -0
- borescope-1.0.1/tests/test_snapshot.py +75 -0
- borescope-0.1.0.dev1/.gitignore +0 -27
- borescope-0.1.0.dev1/src/borescope/cli.py +0 -144
- borescope-0.1.0.dev1/src/borescope/shell/commands/basic.py +0 -117
- borescope-0.1.0.dev1/src/borescope/shell/commands/filesystem.py +0 -494
- borescope-0.1.0.dev1/src/borescope/shell/commands/pebble.py +0 -388
- borescope-0.1.0.dev1/src/borescope/shell/parser.py +0 -91
- borescope-0.1.0.dev1/src/borescope/snapshot.py +0 -103
- borescope-0.1.0.dev1/src/borescope/transport/runner.py +0 -149
- borescope-0.1.0.dev1/tests/conftest.py +0 -166
- borescope-0.1.0.dev1/tests/integration/__init__.py +0 -0
- borescope-0.1.0.dev1/tests/test_args.py +0 -46
- borescope-0.1.0.dev1/tests/test_commands.py +0 -152
- borescope-0.1.0.dev1/tests/test_completion.py +0 -53
- borescope-0.1.0.dev1/tests/test_discovery.py +0 -186
- borescope-0.1.0.dev1/tests/test_parser.py +0 -58
- borescope-0.1.0.dev1/tests/test_pathutils.py +0 -30
- borescope-0.1.0.dev1/tests/test_registry.py +0 -65
- borescope-0.1.0.dev1/tests/test_relay.py +0 -28
- borescope-0.1.0.dev1/tests/test_repl.py +0 -46
- borescope-0.1.0.dev1/tests/test_runner.py +0 -92
- {borescope-0.1.0.dev1 → borescope-1.0.1}/LICENSE +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
__pycache__
|
|
2
|
+
/sandbox
|
|
3
|
+
.idea
|
|
4
|
+
*~
|
|
5
|
+
.venv
|
|
6
|
+
venv
|
|
7
|
+
.vscode
|
|
8
|
+
.coverage
|
|
9
|
+
coverage.xml
|
|
10
|
+
htmlcov
|
|
11
|
+
/.tox
|
|
12
|
+
.*.swp
|
|
13
|
+
.ruff_cache
|
|
14
|
+
.pytest_cache
|
|
15
|
+
|
|
16
|
+
# Tokens and settings for `act` to run GHA locally
|
|
17
|
+
.env
|
|
18
|
+
.envrc
|
|
19
|
+
.secrets
|
|
20
|
+
|
|
21
|
+
# Build artifacts
|
|
22
|
+
/cascade.egg-info
|
|
23
|
+
/dist
|
|
24
|
+
/build
|
|
25
|
+
|
|
26
|
+
# Agents
|
|
27
|
+
.claude/*local*
|
|
28
|
+
|
|
29
|
+
# Local docs-preview tooling writes this when viewing the page
|
|
30
|
+
/docs/localStorage.json
|
|
31
|
+
|
|
32
|
+
# Workshop tool's per-checkout runtime lock; not source.
|
|
33
|
+
/.workshop.lock
|
|
34
|
+
|
|
35
|
+
# libFuzzer-generated corpus entries (SHA1-named).
|
|
36
|
+
# The hand-written seeds use descriptive names and stay tracked; ignore the
|
|
37
|
+
# auto-discovered ones that accumulate during local fuzz runs.
|
|
38
|
+
tests/fuzz/corpus/[0-9a-f]*
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: borescope
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.0.1
|
|
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
|
|
@@ -9,7 +9,7 @@ Author-email: Tony Meyer <borescope@aotearoa.dev>
|
|
|
9
9
|
License-Expression: Apache-2.0
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: charm,debugging,juju,kubernetes,pebble,shell
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Intended Audience :: System Administrators
|
|
@@ -24,7 +24,7 @@ Classifier: Topic :: System :: Systems Administration
|
|
|
24
24
|
Classifier: Topic :: Utilities
|
|
25
25
|
Requires-Python: >=3.11
|
|
26
26
|
Requires-Dist: ops<4,>=2.0.0
|
|
27
|
-
Requires-Dist: pebble-shimmer>=1.0.
|
|
27
|
+
Requires-Dist: pebble-shimmer>=1.0.0
|
|
28
28
|
Requires-Dist: prompt-toolkit>=3.0
|
|
29
29
|
Requires-Dist: pyyaml>=6.0
|
|
30
30
|
Description-Content-Type: text/markdown
|
|
@@ -54,10 +54,23 @@ can't, it fails the same way.
|
|
|
54
54
|
|
|
55
55
|
## Install
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
From the [snap store](https://snapcraft.io/borescope):
|
|
58
58
|
|
|
59
59
|
```console
|
|
60
|
-
|
|
60
|
+
sudo snap install borescope
|
|
61
|
+
sudo snap connect borescope:dot-local-share-juju
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The `dot-local-share-juju` connection is required: the snap bundles its
|
|
65
|
+
own juju, but it needs access to your `~/.local/share/juju` (JUJU_DATA)
|
|
66
|
+
to know which controller and model you're talking to. The `ssh-keys`
|
|
67
|
+
plug auto-connects for `juju ssh`; if it doesn't, run
|
|
68
|
+
`sudo snap connect borescope:ssh-keys` as well.
|
|
69
|
+
|
|
70
|
+
Or from [PyPI](https://pypi.org/project/borescope/):
|
|
71
|
+
|
|
72
|
+
```console
|
|
73
|
+
uv tool install borescope # or: uvx borescope, pipx install borescope
|
|
61
74
|
```
|
|
62
75
|
|
|
63
76
|
## Usage
|
|
@@ -70,6 +83,20 @@ borescope <unit> --command "services" # one-shot, no REPL (for scripts)
|
|
|
70
83
|
borescope <unit> --snapshot # dump container state as JSON
|
|
71
84
|
```
|
|
72
85
|
|
|
86
|
+
## Documentation
|
|
87
|
+
|
|
88
|
+
Full documentation — a tutorial, how-to guides, and CLI/command reference — is
|
|
89
|
+
at **<https://tonyandrewmeyer.github.io/borescope/>**.
|
|
90
|
+
|
|
91
|
+
The docs are plain Markdown under [`docs/src/`](docs/src/), built into static
|
|
92
|
+
HTML with a small script (no docs framework). To build them locally:
|
|
93
|
+
|
|
94
|
+
```console
|
|
95
|
+
uv run python docs/src/_build.py # or: tox -e docs
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See [`docs/README.md`](docs/README.md) for the authoring rules.
|
|
99
|
+
|
|
73
100
|
## How it works
|
|
74
101
|
|
|
75
102
|
borescope is three thin, independently-testable layers:
|
|
@@ -23,10 +23,23 @@ can't, it fails the same way.
|
|
|
23
23
|
|
|
24
24
|
## Install
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
From the [snap store](https://snapcraft.io/borescope):
|
|
27
27
|
|
|
28
28
|
```console
|
|
29
|
-
|
|
29
|
+
sudo snap install borescope
|
|
30
|
+
sudo snap connect borescope:dot-local-share-juju
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The `dot-local-share-juju` connection is required: the snap bundles its
|
|
34
|
+
own juju, but it needs access to your `~/.local/share/juju` (JUJU_DATA)
|
|
35
|
+
to know which controller and model you're talking to. The `ssh-keys`
|
|
36
|
+
plug auto-connects for `juju ssh`; if it doesn't, run
|
|
37
|
+
`sudo snap connect borescope:ssh-keys` as well.
|
|
38
|
+
|
|
39
|
+
Or from [PyPI](https://pypi.org/project/borescope/):
|
|
40
|
+
|
|
41
|
+
```console
|
|
42
|
+
uv tool install borescope # or: uvx borescope, pipx install borescope
|
|
30
43
|
```
|
|
31
44
|
|
|
32
45
|
## Usage
|
|
@@ -39,6 +52,20 @@ borescope <unit> --command "services" # one-shot, no REPL (for scripts)
|
|
|
39
52
|
borescope <unit> --snapshot # dump container state as JSON
|
|
40
53
|
```
|
|
41
54
|
|
|
55
|
+
## Documentation
|
|
56
|
+
|
|
57
|
+
Full documentation — a tutorial, how-to guides, and CLI/command reference — is
|
|
58
|
+
at **<https://tonyandrewmeyer.github.io/borescope/>**.
|
|
59
|
+
|
|
60
|
+
The docs are plain Markdown under [`docs/src/`](docs/src/), built into static
|
|
61
|
+
HTML with a small script (no docs framework). To build them locally:
|
|
62
|
+
|
|
63
|
+
```console
|
|
64
|
+
uv run python docs/src/_build.py # or: tox -e docs
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
See [`docs/README.md`](docs/README.md) for the authoring rules.
|
|
68
|
+
|
|
42
69
|
## How it works
|
|
43
70
|
|
|
44
71
|
borescope is three thin, independently-testable layers:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "borescope"
|
|
3
|
-
version = "
|
|
3
|
+
version = "1.0.1"
|
|
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"
|
|
@@ -10,7 +10,7 @@ authors = [
|
|
|
10
10
|
]
|
|
11
11
|
keywords = ["juju", "pebble", "kubernetes", "charm", "shell", "debugging"]
|
|
12
12
|
classifiers = [
|
|
13
|
-
"Development Status ::
|
|
13
|
+
"Development Status :: 5 - Production/Stable",
|
|
14
14
|
"Environment :: Console",
|
|
15
15
|
"Intended Audience :: Developers",
|
|
16
16
|
"Intended Audience :: System Administrators",
|
|
@@ -26,21 +26,27 @@ classifiers = [
|
|
|
26
26
|
]
|
|
27
27
|
dependencies = [
|
|
28
28
|
"ops>=2.0.0,<4",
|
|
29
|
-
"pebble-shimmer>=1.0.
|
|
29
|
+
"pebble-shimmer>=1.0.0",
|
|
30
30
|
"prompt_toolkit>=3.0",
|
|
31
31
|
"PyYAML>=6.0",
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[dependency-groups]
|
|
35
35
|
dev = [
|
|
36
|
+
"atheris>=2.3.0",
|
|
36
37
|
"pytest>=7.0.0",
|
|
37
38
|
"pytest-cov>=4.0.0",
|
|
38
39
|
"pytest-mock>=3.10.0",
|
|
39
40
|
"ruff==0.15.14",
|
|
40
41
|
"ty==0.0.38",
|
|
41
|
-
"tox>=4.22.0",
|
|
42
42
|
"pre-commit>=3.0.0",
|
|
43
43
|
]
|
|
44
|
+
docs = [
|
|
45
|
+
"jinja2>=3.1",
|
|
46
|
+
"markdown-it-py>=3.0",
|
|
47
|
+
"mdit-py-plugins>=0.4",
|
|
48
|
+
"PyYAML>=6.0",
|
|
49
|
+
]
|
|
44
50
|
|
|
45
51
|
[project.scripts]
|
|
46
52
|
borescope = "borescope.cli:main"
|
|
@@ -67,29 +73,110 @@ include = [
|
|
|
67
73
|
|
|
68
74
|
# Ruff configuration
|
|
69
75
|
[tool.ruff]
|
|
70
|
-
line-length =
|
|
76
|
+
line-length = 99
|
|
71
77
|
target-version = "py311"
|
|
72
78
|
src = ["src", "tests"]
|
|
73
79
|
|
|
80
|
+
[tool.ruff.format]
|
|
81
|
+
quote-style = "single"
|
|
82
|
+
|
|
74
83
|
[tool.ruff.lint]
|
|
84
|
+
# CPY (flake8-copyright) currently requires preview mode.
|
|
85
|
+
preview = true
|
|
86
|
+
explicit-preview-rules = true
|
|
75
87
|
select = [
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
88
|
+
# Pyflakes
|
|
89
|
+
"F",
|
|
90
|
+
# Pycodestyle
|
|
91
|
+
"E",
|
|
92
|
+
"W",
|
|
93
|
+
# isort
|
|
94
|
+
"I001",
|
|
95
|
+
# pep8-naming
|
|
96
|
+
"N",
|
|
97
|
+
# flake8-builtins
|
|
98
|
+
"A",
|
|
99
|
+
# flake8-copyright
|
|
100
|
+
"CPY001",
|
|
101
|
+
# pyupgrade
|
|
102
|
+
"UP",
|
|
103
|
+
# flake8-2020
|
|
104
|
+
"YTT",
|
|
105
|
+
# flake8-bandit
|
|
106
|
+
"S",
|
|
107
|
+
# flake8-bugbear
|
|
108
|
+
"B",
|
|
109
|
+
# flake8-simplify
|
|
110
|
+
"SIM",
|
|
111
|
+
# Ruff-specific
|
|
112
|
+
"RUF",
|
|
113
|
+
# Perflint
|
|
114
|
+
"PERF",
|
|
115
|
+
# pydocstyle
|
|
116
|
+
"D",
|
|
117
|
+
# flake8-future-annotations
|
|
118
|
+
"FA",
|
|
119
|
+
# flake8-type-checking
|
|
120
|
+
"TC",
|
|
84
121
|
]
|
|
85
122
|
ignore = [
|
|
86
|
-
|
|
87
|
-
"
|
|
88
|
-
|
|
123
|
+
# Line too long: handled by the formatter.
|
|
124
|
+
"E501",
|
|
125
|
+
# Don't force imports into TYPE_CHECKING blocks.
|
|
126
|
+
"TC001",
|
|
127
|
+
"TC002",
|
|
128
|
+
"TC003",
|
|
129
|
+
# assert is fine.
|
|
130
|
+
"S101",
|
|
131
|
+
# Magic methods and __init__ don't need docstrings.
|
|
132
|
+
"D105",
|
|
133
|
+
"D107",
|
|
134
|
+
# subprocess call: check for execution of untrusted input. False-positive heavy.
|
|
135
|
+
"S603",
|
|
89
136
|
]
|
|
90
137
|
|
|
138
|
+
[tool.ruff.lint.pydocstyle]
|
|
139
|
+
convention = "google"
|
|
140
|
+
|
|
141
|
+
[tool.ruff.lint.flake8-builtins]
|
|
142
|
+
builtins-ignorelist = ["id", "min", "map", "range", "type", "input", "format", "exit", "help"]
|
|
143
|
+
|
|
144
|
+
[tool.ruff.lint.flake8-copyright]
|
|
145
|
+
notice-rgx = "(?i)Copyright \\d{4} Tony Meyer"
|
|
146
|
+
|
|
91
147
|
[tool.ruff.lint.per-file-ignores]
|
|
92
|
-
|
|
148
|
+
# Command classes are self-documenting via their ``name``/``summary``/``usage``
|
|
149
|
+
# attributes; a docstring would only restate ``summary``.
|
|
150
|
+
"src/borescope/shell/commands/*.py" = [
|
|
151
|
+
"D101",
|
|
152
|
+
"D102",
|
|
153
|
+
]
|
|
154
|
+
# Transport is a structural ``Protocol`` mirroring ``ops.pebble.Client``; the
|
|
155
|
+
# upstream API is the docstring.
|
|
156
|
+
"src/borescope/transport/__init__.py" = [
|
|
157
|
+
"D102",
|
|
158
|
+
]
|
|
159
|
+
"tests/*" = [
|
|
160
|
+
# Docstrings aren't required in tests.
|
|
161
|
+
"D",
|
|
162
|
+
# Hard-coded "secrets" are fine in tests.
|
|
163
|
+
"S105",
|
|
164
|
+
"S106",
|
|
165
|
+
]
|
|
166
|
+
"tests/**/*" = [
|
|
167
|
+
"D",
|
|
168
|
+
"S105",
|
|
169
|
+
"S106",
|
|
170
|
+
]
|
|
171
|
+
# The docs build script intentionally keeps two things ruff would flag:
|
|
172
|
+
"docs/src/_build.py" = [
|
|
173
|
+
# The entity-rewrite table maps literal "ambiguous" Unicode characters
|
|
174
|
+
# (en dash, curly quotes, ...) to HTML entities — they must stay as-is.
|
|
175
|
+
"RUF001",
|
|
176
|
+
# Jinja2 autoescape is deliberately off: the body is trusted, already-
|
|
177
|
+
# rendered HTML, and escaping it would double-encode the docs.
|
|
178
|
+
"S701",
|
|
179
|
+
]
|
|
93
180
|
|
|
94
181
|
[tool.ruff.lint.isort]
|
|
95
182
|
known-first-party = ["borescope"]
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
# Copyright 2026 Tony Meyer
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
1
4
|
"""borescope - a natural shell for debugging Juju Kubernetes workload containers."""
|
|
2
5
|
|
|
3
6
|
from importlib.metadata import PackageNotFoundError, version
|
|
4
7
|
|
|
5
8
|
try:
|
|
6
|
-
__version__ = version(
|
|
9
|
+
__version__ = version('borescope')
|
|
7
10
|
except PackageNotFoundError: # pragma: no cover - running from an uninstalled tree
|
|
8
|
-
__version__ =
|
|
11
|
+
__version__ = '0.0.0+unknown'
|
|
9
12
|
|
|
10
|
-
__all__ = [
|
|
13
|
+
__all__ = ['__version__']
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
# Copyright 2026 Tony Meyer
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
1
4
|
"""``python -m borescope`` entry point."""
|
|
2
5
|
|
|
3
6
|
from __future__ import annotations
|
|
@@ -6,5 +9,5 @@ import sys
|
|
|
6
9
|
|
|
7
10
|
from .cli import main
|
|
8
11
|
|
|
9
|
-
if __name__ ==
|
|
12
|
+
if __name__ == '__main__':
|
|
10
13
|
sys.exit(main())
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Copyright 2026 Tony Meyer
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""Command-line entry point for borescope.
|
|
5
|
+
|
|
6
|
+
Deliberately *out* of scope for v1, and worth marking explicitly: borescope has
|
|
7
|
+
no Canonical-spec verbosity ladder (`--quiet` / `--verbose` /
|
|
8
|
+
`--verbosity=debug|trace`). The tool's primary output is the REPL or a single
|
|
9
|
+
command's result, neither of which benefits from a five-level taxonomy, so we
|
|
10
|
+
stay minimal until a concrete need shows up.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
from . import __version__
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
22
|
+
"""Build the top-level ``argparse`` parser."""
|
|
23
|
+
parser = argparse.ArgumentParser(
|
|
24
|
+
prog='borescope',
|
|
25
|
+
description=('A natural shell for debugging Juju Kubernetes workload containers.'),
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument('unit', nargs='?', help="unit reference, for example 'myapp/0'")
|
|
28
|
+
parser.add_argument('--container', help='workload container name (default: first declared)')
|
|
29
|
+
# Canonical CLI guidance is "don't offer both short and long" for the same
|
|
30
|
+
# flag — `-m/--model` deliberately diverges to match `juju`'s own convention
|
|
31
|
+
# (`juju ssh -m <model> …`), which is what users muscle-memory their way to
|
|
32
|
+
# when reaching for borescope.
|
|
33
|
+
parser.add_argument('-m', '--model', help='Juju model (default: current)')
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
'--command',
|
|
36
|
+
help='run a single command and exit (no REPL)',
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
'--snapshot',
|
|
40
|
+
action='store_true',
|
|
41
|
+
help='dump container state as JSON and exit',
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
'--socket',
|
|
45
|
+
help='talk directly to a Pebble unix socket (skip juju)',
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
'--here',
|
|
49
|
+
action='store_true',
|
|
50
|
+
help=(
|
|
51
|
+
"run inside the charm container: auto-detect a workload's mounted "
|
|
52
|
+
'Pebble socket (use --container to pick when there are several)'
|
|
53
|
+
),
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument('--juju', default='juju', help='juju binary to invoke (default: juju)')
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
'--via',
|
|
58
|
+
choices=('ssh', 'exec'),
|
|
59
|
+
default='ssh',
|
|
60
|
+
help=(
|
|
61
|
+
"Juju relay for Mode B: 'ssh' (default, streaming) or 'exec' "
|
|
62
|
+
'(request/response — for sites where ssh is disabled)'
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
parser.add_argument('--version', action='version', version=f'borescope {__version__}')
|
|
66
|
+
return parser
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _build_target(args: argparse.Namespace):
|
|
70
|
+
from .discovery import Target, resolve_local_target, resolve_target, validate_container_name
|
|
71
|
+
|
|
72
|
+
if args.container is not None:
|
|
73
|
+
validate_container_name(args.container)
|
|
74
|
+
if args.here:
|
|
75
|
+
return resolve_local_target(container=args.container)
|
|
76
|
+
if args.socket:
|
|
77
|
+
unit = args.unit or 'local'
|
|
78
|
+
app = unit.split('/')[0]
|
|
79
|
+
return Target(
|
|
80
|
+
unit=unit,
|
|
81
|
+
app=app,
|
|
82
|
+
container=args.container,
|
|
83
|
+
model=args.model,
|
|
84
|
+
juju_binary=args.juju,
|
|
85
|
+
socket_path=args.socket,
|
|
86
|
+
)
|
|
87
|
+
return resolve_target(
|
|
88
|
+
args.unit,
|
|
89
|
+
container=args.container,
|
|
90
|
+
model=args.model,
|
|
91
|
+
juju_binary=args.juju,
|
|
92
|
+
via=args.via,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(argv: list[str] | None = None) -> int:
|
|
97
|
+
"""Run borescope from the command line and return the exit code."""
|
|
98
|
+
# Accept Canonical-style `help` / `version` subcommands as aliases for the
|
|
99
|
+
# Python-default `--help` / `--version`. Awkward to support both, but it
|
|
100
|
+
# means the tool matches the standard *and* what `argparse` users expect.
|
|
101
|
+
cli_args = sys.argv[1:] if argv is None else argv
|
|
102
|
+
if cli_args[:1] == ['help']:
|
|
103
|
+
build_parser().print_help()
|
|
104
|
+
return 0
|
|
105
|
+
if cli_args[:1] == ['version']:
|
|
106
|
+
print(f'borescope {__version__}')
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
args = build_parser().parse_args(argv)
|
|
110
|
+
if not args.unit and not args.socket and not args.here:
|
|
111
|
+
print(
|
|
112
|
+
"borescope: A unit reference is required (for example 'borescope myapp/0'), "
|
|
113
|
+
'or use --here when running inside a charm container.',
|
|
114
|
+
file=sys.stderr,
|
|
115
|
+
)
|
|
116
|
+
return 2
|
|
117
|
+
|
|
118
|
+
# Heavy imports happen only past argument parsing, keeping --help/--version fast.
|
|
119
|
+
from .errors import BorescopeError
|
|
120
|
+
from .shell import ShellContext
|
|
121
|
+
from .transport import open_transport
|
|
122
|
+
|
|
123
|
+
try:
|
|
124
|
+
target = _build_target(args)
|
|
125
|
+
transport = open_transport(
|
|
126
|
+
unit=target.unit,
|
|
127
|
+
container=target.container,
|
|
128
|
+
model=target.model,
|
|
129
|
+
juju_binary=target.juju_binary,
|
|
130
|
+
socket_path=target.socket_path,
|
|
131
|
+
via=target.via,
|
|
132
|
+
)
|
|
133
|
+
if args.snapshot:
|
|
134
|
+
from .snapshot import snapshot_json
|
|
135
|
+
|
|
136
|
+
print(snapshot_json(transport, target))
|
|
137
|
+
return 0
|
|
138
|
+
|
|
139
|
+
from .discovery import sanity_check
|
|
140
|
+
|
|
141
|
+
sanity_check(transport, target)
|
|
142
|
+
except BorescopeError as exc:
|
|
143
|
+
print(f'borescope: {exc}', file=sys.stderr)
|
|
144
|
+
return 1
|
|
145
|
+
|
|
146
|
+
from .shell import Shell
|
|
147
|
+
|
|
148
|
+
shell = Shell(ShellContext(transport=transport, target=target))
|
|
149
|
+
|
|
150
|
+
if args.command is not None:
|
|
151
|
+
return shell.execute_and_emit(args.command)
|
|
152
|
+
|
|
153
|
+
if not sys.stdin.isatty():
|
|
154
|
+
code = 0
|
|
155
|
+
for line in sys.stdin:
|
|
156
|
+
if line.strip():
|
|
157
|
+
code = shell.execute_and_emit(line.rstrip('\n'))
|
|
158
|
+
return code
|
|
159
|
+
|
|
160
|
+
return shell.loop()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
if __name__ == '__main__': # pragma: no cover
|
|
164
|
+
sys.exit(main())
|