brinqz-agent 0.2.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.
- brinqz_agent-0.2.0/MANIFEST.in +10 -0
- brinqz_agent-0.2.0/PKG-INFO +199 -0
- brinqz_agent-0.2.0/README.md +167 -0
- brinqz_agent-0.2.0/pyproject.toml +60 -0
- brinqz_agent-0.2.0/setup.cfg +4 -0
- brinqz_agent-0.2.0/src/brinqz_agent/__init__.py +6 -0
- brinqz_agent-0.2.0/src/brinqz_agent/__main__.py +4 -0
- brinqz_agent-0.2.0/src/brinqz_agent/artifacts.py +173 -0
- brinqz_agent-0.2.0/src/brinqz_agent/background.py +100 -0
- brinqz_agent-0.2.0/src/brinqz_agent/cli.py +560 -0
- brinqz_agent-0.2.0/src/brinqz_agent/cloud.py +910 -0
- brinqz_agent-0.2.0/src/brinqz_agent/compatibility.py +24 -0
- brinqz_agent-0.2.0/src/brinqz_agent/config.py +170 -0
- brinqz_agent-0.2.0/src/brinqz_agent/containment.py +621 -0
- brinqz_agent-0.2.0/src/brinqz_agent/execution.py +244 -0
- brinqz_agent-0.2.0/src/brinqz_agent/execution_lifecycle.py +456 -0
- brinqz_agent-0.2.0/src/brinqz_agent/execution_state.py +206 -0
- brinqz_agent-0.2.0/src/brinqz_agent/identity.py +59 -0
- brinqz_agent-0.2.0/src/brinqz_agent/lifecycle.py +265 -0
- brinqz_agent-0.2.0/src/brinqz_agent/listeners/__init__.py +1 -0
- brinqz_agent-0.2.0/src/brinqz_agent/listeners/brinqz_robot_listener.py +101 -0
- brinqz_agent-0.2.0/src/brinqz_agent/live_events.py +441 -0
- brinqz_agent-0.2.0/src/brinqz_agent/managed_runtime.py +833 -0
- brinqz_agent-0.2.0/src/brinqz_agent/pabot.py +34 -0
- brinqz_agent-0.2.0/src/brinqz_agent/packages.py +180 -0
- brinqz_agent-0.2.0/src/brinqz_agent/pairing.py +135 -0
- brinqz_agent-0.2.0/src/brinqz_agent/persistence.py +46 -0
- brinqz_agent-0.2.0/src/brinqz_agent/platforms.py +123 -0
- brinqz_agent-0.2.0/src/brinqz_agent/poll_health.py +83 -0
- brinqz_agent-0.2.0/src/brinqz_agent/preparation.py +284 -0
- brinqz_agent-0.2.0/src/brinqz_agent/robot.py +822 -0
- brinqz_agent-0.2.0/src/brinqz_agent/runtime.py +578 -0
- brinqz_agent-0.2.0/src/brinqz_agent/service.py +329 -0
- brinqz_agent-0.2.0/src/brinqz_agent/setup_ui.py +401 -0
- brinqz_agent-0.2.0/src/brinqz_agent/setup_workflow.py +658 -0
- brinqz_agent-0.2.0/src/brinqz_agent/startup.py +402 -0
- brinqz_agent-0.2.0/src/brinqz_agent/version.py +3 -0
- brinqz_agent-0.2.0/src/brinqz_agent.egg-info/PKG-INFO +199 -0
- brinqz_agent-0.2.0/src/brinqz_agent.egg-info/SOURCES.txt +41 -0
- brinqz_agent-0.2.0/src/brinqz_agent.egg-info/dependency_links.txt +1 -0
- brinqz_agent-0.2.0/src/brinqz_agent.egg-info/entry_points.txt +2 -0
- brinqz_agent-0.2.0/src/brinqz_agent.egg-info/requires.txt +5 -0
- brinqz_agent-0.2.0/src/brinqz_agent.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brinqz-agent
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Secure local execution agent for Brinqz Cloud
|
|
5
|
+
Author: Brinqz
|
|
6
|
+
Maintainer: Brinqz
|
|
7
|
+
License-Expression: LicenseRef-Proprietary
|
|
8
|
+
Project-URL: Homepage, https://brinqz.com
|
|
9
|
+
Project-URL: Documentation, https://github.com/ronnieparalejas/brinqz-agent#readme
|
|
10
|
+
Project-URL: Source, https://github.com/ronnieparalejas/brinqz-agent
|
|
11
|
+
Project-URL: Issues, https://github.com/ronnieparalejas/brinqz-agent/issues
|
|
12
|
+
Project-URL: Support, https://github.com/ronnieparalejas/brinqz-agent/issues
|
|
13
|
+
Project-URL: Brinqz Cloud, https://app.brinqz.com
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Software Development :: Testing
|
|
26
|
+
Requires-Python: <4,>=3.11
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: build>=1.2.2; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest>=8.3; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=6.1; extra == "dev"
|
|
32
|
+
|
|
33
|
+
# Brinqz Agent
|
|
34
|
+
|
|
35
|
+
Brinqz Agent is the local command-line agent that runs approved Robot Framework
|
|
36
|
+
test executions on your machine. Brinqz Cloud is its control plane: the Agent
|
|
37
|
+
pairs with Cloud, receives execution work, runs it through the configured local
|
|
38
|
+
test runtime, and returns approved results and artifacts.
|
|
39
|
+
|
|
40
|
+
Brinqz Agent is currently a **Public Preview / Alpha** release. It supports
|
|
41
|
+
Windows, macOS, and Linux and requires Python 3.11 or newer (Python 4 is not
|
|
42
|
+
supported).
|
|
43
|
+
|
|
44
|
+
## Install and set up
|
|
45
|
+
|
|
46
|
+
Install or upgrade the Agent, then follow its guided setup:
|
|
47
|
+
|
|
48
|
+
```console
|
|
49
|
+
python -m pip install --upgrade brinqz-agent
|
|
50
|
+
brinqz-agent setup
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
For an isolated command-line installation, you can use
|
|
54
|
+
[pipx](https://pipx.pypa.io/):
|
|
55
|
+
|
|
56
|
+
```console
|
|
57
|
+
pipx install brinqz-agent
|
|
58
|
+
brinqz-agent setup
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The default control-plane URL is `https://app.brinqz.com`. For local
|
|
62
|
+
development only, you can select a `.test` Cloud origin:
|
|
63
|
+
|
|
64
|
+
```console
|
|
65
|
+
brinqz-agent setup --cloud-url http://app.brinqz.test
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Setup guides you through Cloud pairing and capability choices, prepares an
|
|
69
|
+
isolated Brinqz-managed execution runtime, registers per-user automatic startup,
|
|
70
|
+
starts the Agent, and verifies Cloud health. Robot Framework is always enabled.
|
|
71
|
+
Browser testing is recommended and defaults to Chromium; Firefox and WebKit are
|
|
72
|
+
optional. Parallel execution with Pabot is optional.
|
|
73
|
+
|
|
74
|
+
It is safe to rerun `brinqz-agent setup`. A healthy matching runtime is reused.
|
|
75
|
+
If repair or an upgrade is needed, setup builds and validates a replacement at
|
|
76
|
+
its final path before selecting it, and retains the previous working runtime
|
|
77
|
+
until activation succeeds.
|
|
78
|
+
|
|
79
|
+
## Setup choices and managed deployment
|
|
80
|
+
|
|
81
|
+
Normal users only need the two commands above. Managed deployments and CI can
|
|
82
|
+
inspect advanced options with `brinqz-agent setup --help`. Common examples are:
|
|
83
|
+
|
|
84
|
+
```console
|
|
85
|
+
# Recommended Chromium and Pabot capabilities
|
|
86
|
+
brinqz-agent setup --yes --recommended
|
|
87
|
+
|
|
88
|
+
# Explicit browsers and parallel execution
|
|
89
|
+
brinqz-agent setup --yes --browsers chromium,firefox --with-pabot
|
|
90
|
+
|
|
91
|
+
# Robot-only runtime without background registration
|
|
92
|
+
brinqz-agent setup --yes --robot-only --no-startup
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use `--cloud-url` to select a different approved Cloud origin. Rerun setup with
|
|
96
|
+
`--browsers` or `--with-pabot` to change capabilities. Setup never installs
|
|
97
|
+
project credentials or project secrets.
|
|
98
|
+
|
|
99
|
+
## Execution runtime
|
|
100
|
+
|
|
101
|
+
The Agent package environment and execution environment remain separate. Setup
|
|
102
|
+
creates a dedicated virtual environment under the user-scoped Agent data
|
|
103
|
+
directory and never installs Robot Framework, Browser Library, browsers, or
|
|
104
|
+
Pabot globally. It does not change the global `PATH` and does not require
|
|
105
|
+
administrator or root privileges.
|
|
106
|
+
|
|
107
|
+
Browser setup prefers the official BrowserBatteries distribution, which avoids
|
|
108
|
+
a separate Node.js installation on supported systems. On an unsupported system,
|
|
109
|
+
setup can use an already-installed supported Node.js LTS runtime; it never
|
|
110
|
+
silently installs Node.js or operating-system packages. If Linux browser system
|
|
111
|
+
dependencies are missing, setup reports the requirement without automatically
|
|
112
|
+
using `sudo`.
|
|
113
|
+
|
|
114
|
+
Use the following command to inspect the Agent configuration, selected runtime,
|
|
115
|
+
and required local capabilities:
|
|
116
|
+
|
|
117
|
+
```console
|
|
118
|
+
brinqz-agent doctor
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Advanced users can keep a compatible external runtime with `brinqz-agent
|
|
122
|
+
runtime use <python-path>`. Setup preserves a valid manually selected runtime;
|
|
123
|
+
use `--managed-runtime` when deliberately switching back to Brinqz management.
|
|
124
|
+
|
|
125
|
+
## Linux display behavior
|
|
126
|
+
|
|
127
|
+
Headless Robot and Browser setup works without `DISPLAY`. Headed browser
|
|
128
|
+
execution requires a real logged-in graphical session and a valid `DISPLAY` or
|
|
129
|
+
Wayland environment inherited by the Agent. The Agent does not fabricate display
|
|
130
|
+
variables. A headed request without a display fails with a concise diagnostic.
|
|
131
|
+
Some distributions require browser system libraries installed by an
|
|
132
|
+
administrator; setup explains those requirements and does not elevate itself.
|
|
133
|
+
|
|
134
|
+
## Background lifecycle
|
|
135
|
+
|
|
136
|
+
The background registration is per-user: Task Scheduler on Windows, a
|
|
137
|
+
LaunchAgent on macOS, or a systemd user service on Linux.
|
|
138
|
+
|
|
139
|
+
```console
|
|
140
|
+
brinqz-agent install
|
|
141
|
+
brinqz-agent status
|
|
142
|
+
brinqz-agent start
|
|
143
|
+
brinqz-agent stop
|
|
144
|
+
brinqz-agent restart
|
|
145
|
+
brinqz-agent uninstall
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`uninstall` removes background startup registration but preserves pairing,
|
|
149
|
+
managed runtimes, runtime selection, reports, and execution history. Remove the
|
|
150
|
+
user-scoped Brinqz Agent data directory separately only when you deliberately
|
|
151
|
+
want to delete those preserved files. `brinqz-agent run` is
|
|
152
|
+
available as a foreground diagnostic mode.
|
|
153
|
+
|
|
154
|
+
Managed data locations are:
|
|
155
|
+
|
|
156
|
+
- Windows: `%LOCALAPPDATA%\Brinqz Agent` (runtime under `runtime`)
|
|
157
|
+
- macOS: `~/Library/Application Support/Brinqz Agent`
|
|
158
|
+
- Linux: `${XDG_DATA_HOME:-~/.local/share}/brinqz-agent`
|
|
159
|
+
|
|
160
|
+
## Security model
|
|
161
|
+
|
|
162
|
+
The Agent initiates outbound communication to Brinqz Cloud and does not open
|
|
163
|
+
inbound ports. Pairing establishes a local machine identity through Cloud's
|
|
164
|
+
browser-approval flow, and approved machine credentials are stored in the
|
|
165
|
+
user-scoped Agent configuration.
|
|
166
|
+
|
|
167
|
+
Project credentials are not entered or configured through Agent setup. An
|
|
168
|
+
execution that requires an unavailable local secret is refused before process
|
|
169
|
+
launch. The Agent executes prepared Robot Framework packages through a fixed
|
|
170
|
+
Robot or Pabot invocation; it does not provide arbitrary remote shell
|
|
171
|
+
execution.
|
|
172
|
+
|
|
173
|
+
Downloaded packages are authenticated, size and checksum verified, and safely
|
|
174
|
+
extracted. Execution runs in a contained process boundary, and only approved
|
|
175
|
+
report artifacts are returned to Cloud.
|
|
176
|
+
|
|
177
|
+
## Public Preview limitations
|
|
178
|
+
|
|
179
|
+
This Alpha release is command-line only. It does not install Python, Node.js,
|
|
180
|
+
Linux operating-system browser dependencies, or project secrets. It does not
|
|
181
|
+
include a GUI or tray application, a native installer, self-update, or
|
|
182
|
+
Runner-local project-secret storage. Interfaces and operational guidance may
|
|
183
|
+
change before a stable release.
|
|
184
|
+
|
|
185
|
+
## Support
|
|
186
|
+
|
|
187
|
+
Report problems and request support through the
|
|
188
|
+
[Brinqz Agent issue tracker](https://github.com/ronnieparalejas/brinqz-agent/issues).
|
|
189
|
+
|
|
190
|
+
## Development
|
|
191
|
+
|
|
192
|
+
```console
|
|
193
|
+
python -m venv .venv
|
|
194
|
+
# Activate .venv using your shell, then:
|
|
195
|
+
python -m pip install -e ".[dev]"
|
|
196
|
+
python -m pytest
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The repository contains additional manual validation plans for maintainers.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# Brinqz Agent
|
|
2
|
+
|
|
3
|
+
Brinqz Agent is the local command-line agent that runs approved Robot Framework
|
|
4
|
+
test executions on your machine. Brinqz Cloud is its control plane: the Agent
|
|
5
|
+
pairs with Cloud, receives execution work, runs it through the configured local
|
|
6
|
+
test runtime, and returns approved results and artifacts.
|
|
7
|
+
|
|
8
|
+
Brinqz Agent is currently a **Public Preview / Alpha** release. It supports
|
|
9
|
+
Windows, macOS, and Linux and requires Python 3.11 or newer (Python 4 is not
|
|
10
|
+
supported).
|
|
11
|
+
|
|
12
|
+
## Install and set up
|
|
13
|
+
|
|
14
|
+
Install or upgrade the Agent, then follow its guided setup:
|
|
15
|
+
|
|
16
|
+
```console
|
|
17
|
+
python -m pip install --upgrade brinqz-agent
|
|
18
|
+
brinqz-agent setup
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For an isolated command-line installation, you can use
|
|
22
|
+
[pipx](https://pipx.pypa.io/):
|
|
23
|
+
|
|
24
|
+
```console
|
|
25
|
+
pipx install brinqz-agent
|
|
26
|
+
brinqz-agent setup
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The default control-plane URL is `https://app.brinqz.com`. For local
|
|
30
|
+
development only, you can select a `.test` Cloud origin:
|
|
31
|
+
|
|
32
|
+
```console
|
|
33
|
+
brinqz-agent setup --cloud-url http://app.brinqz.test
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Setup guides you through Cloud pairing and capability choices, prepares an
|
|
37
|
+
isolated Brinqz-managed execution runtime, registers per-user automatic startup,
|
|
38
|
+
starts the Agent, and verifies Cloud health. Robot Framework is always enabled.
|
|
39
|
+
Browser testing is recommended and defaults to Chromium; Firefox and WebKit are
|
|
40
|
+
optional. Parallel execution with Pabot is optional.
|
|
41
|
+
|
|
42
|
+
It is safe to rerun `brinqz-agent setup`. A healthy matching runtime is reused.
|
|
43
|
+
If repair or an upgrade is needed, setup builds and validates a replacement at
|
|
44
|
+
its final path before selecting it, and retains the previous working runtime
|
|
45
|
+
until activation succeeds.
|
|
46
|
+
|
|
47
|
+
## Setup choices and managed deployment
|
|
48
|
+
|
|
49
|
+
Normal users only need the two commands above. Managed deployments and CI can
|
|
50
|
+
inspect advanced options with `brinqz-agent setup --help`. Common examples are:
|
|
51
|
+
|
|
52
|
+
```console
|
|
53
|
+
# Recommended Chromium and Pabot capabilities
|
|
54
|
+
brinqz-agent setup --yes --recommended
|
|
55
|
+
|
|
56
|
+
# Explicit browsers and parallel execution
|
|
57
|
+
brinqz-agent setup --yes --browsers chromium,firefox --with-pabot
|
|
58
|
+
|
|
59
|
+
# Robot-only runtime without background registration
|
|
60
|
+
brinqz-agent setup --yes --robot-only --no-startup
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Use `--cloud-url` to select a different approved Cloud origin. Rerun setup with
|
|
64
|
+
`--browsers` or `--with-pabot` to change capabilities. Setup never installs
|
|
65
|
+
project credentials or project secrets.
|
|
66
|
+
|
|
67
|
+
## Execution runtime
|
|
68
|
+
|
|
69
|
+
The Agent package environment and execution environment remain separate. Setup
|
|
70
|
+
creates a dedicated virtual environment under the user-scoped Agent data
|
|
71
|
+
directory and never installs Robot Framework, Browser Library, browsers, or
|
|
72
|
+
Pabot globally. It does not change the global `PATH` and does not require
|
|
73
|
+
administrator or root privileges.
|
|
74
|
+
|
|
75
|
+
Browser setup prefers the official BrowserBatteries distribution, which avoids
|
|
76
|
+
a separate Node.js installation on supported systems. On an unsupported system,
|
|
77
|
+
setup can use an already-installed supported Node.js LTS runtime; it never
|
|
78
|
+
silently installs Node.js or operating-system packages. If Linux browser system
|
|
79
|
+
dependencies are missing, setup reports the requirement without automatically
|
|
80
|
+
using `sudo`.
|
|
81
|
+
|
|
82
|
+
Use the following command to inspect the Agent configuration, selected runtime,
|
|
83
|
+
and required local capabilities:
|
|
84
|
+
|
|
85
|
+
```console
|
|
86
|
+
brinqz-agent doctor
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Advanced users can keep a compatible external runtime with `brinqz-agent
|
|
90
|
+
runtime use <python-path>`. Setup preserves a valid manually selected runtime;
|
|
91
|
+
use `--managed-runtime` when deliberately switching back to Brinqz management.
|
|
92
|
+
|
|
93
|
+
## Linux display behavior
|
|
94
|
+
|
|
95
|
+
Headless Robot and Browser setup works without `DISPLAY`. Headed browser
|
|
96
|
+
execution requires a real logged-in graphical session and a valid `DISPLAY` or
|
|
97
|
+
Wayland environment inherited by the Agent. The Agent does not fabricate display
|
|
98
|
+
variables. A headed request without a display fails with a concise diagnostic.
|
|
99
|
+
Some distributions require browser system libraries installed by an
|
|
100
|
+
administrator; setup explains those requirements and does not elevate itself.
|
|
101
|
+
|
|
102
|
+
## Background lifecycle
|
|
103
|
+
|
|
104
|
+
The background registration is per-user: Task Scheduler on Windows, a
|
|
105
|
+
LaunchAgent on macOS, or a systemd user service on Linux.
|
|
106
|
+
|
|
107
|
+
```console
|
|
108
|
+
brinqz-agent install
|
|
109
|
+
brinqz-agent status
|
|
110
|
+
brinqz-agent start
|
|
111
|
+
brinqz-agent stop
|
|
112
|
+
brinqz-agent restart
|
|
113
|
+
brinqz-agent uninstall
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`uninstall` removes background startup registration but preserves pairing,
|
|
117
|
+
managed runtimes, runtime selection, reports, and execution history. Remove the
|
|
118
|
+
user-scoped Brinqz Agent data directory separately only when you deliberately
|
|
119
|
+
want to delete those preserved files. `brinqz-agent run` is
|
|
120
|
+
available as a foreground diagnostic mode.
|
|
121
|
+
|
|
122
|
+
Managed data locations are:
|
|
123
|
+
|
|
124
|
+
- Windows: `%LOCALAPPDATA%\Brinqz Agent` (runtime under `runtime`)
|
|
125
|
+
- macOS: `~/Library/Application Support/Brinqz Agent`
|
|
126
|
+
- Linux: `${XDG_DATA_HOME:-~/.local/share}/brinqz-agent`
|
|
127
|
+
|
|
128
|
+
## Security model
|
|
129
|
+
|
|
130
|
+
The Agent initiates outbound communication to Brinqz Cloud and does not open
|
|
131
|
+
inbound ports. Pairing establishes a local machine identity through Cloud's
|
|
132
|
+
browser-approval flow, and approved machine credentials are stored in the
|
|
133
|
+
user-scoped Agent configuration.
|
|
134
|
+
|
|
135
|
+
Project credentials are not entered or configured through Agent setup. An
|
|
136
|
+
execution that requires an unavailable local secret is refused before process
|
|
137
|
+
launch. The Agent executes prepared Robot Framework packages through a fixed
|
|
138
|
+
Robot or Pabot invocation; it does not provide arbitrary remote shell
|
|
139
|
+
execution.
|
|
140
|
+
|
|
141
|
+
Downloaded packages are authenticated, size and checksum verified, and safely
|
|
142
|
+
extracted. Execution runs in a contained process boundary, and only approved
|
|
143
|
+
report artifacts are returned to Cloud.
|
|
144
|
+
|
|
145
|
+
## Public Preview limitations
|
|
146
|
+
|
|
147
|
+
This Alpha release is command-line only. It does not install Python, Node.js,
|
|
148
|
+
Linux operating-system browser dependencies, or project secrets. It does not
|
|
149
|
+
include a GUI or tray application, a native installer, self-update, or
|
|
150
|
+
Runner-local project-secret storage. Interfaces and operational guidance may
|
|
151
|
+
change before a stable release.
|
|
152
|
+
|
|
153
|
+
## Support
|
|
154
|
+
|
|
155
|
+
Report problems and request support through the
|
|
156
|
+
[Brinqz Agent issue tracker](https://github.com/ronnieparalejas/brinqz-agent/issues).
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```console
|
|
161
|
+
python -m venv .venv
|
|
162
|
+
# Activate .venv using your shell, then:
|
|
163
|
+
python -m pip install -e ".[dev]"
|
|
164
|
+
python -m pytest
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The repository contains additional manual validation plans for maintainers.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=75"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "brinqz-agent"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Secure local execution agent for Brinqz Cloud"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11,<4"
|
|
11
|
+
license = "LicenseRef-Proprietary"
|
|
12
|
+
authors = [{ name = "Brinqz" }]
|
|
13
|
+
maintainers = [{ name = "Brinqz" }]
|
|
14
|
+
dependencies = []
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: MacOS",
|
|
20
|
+
"Operating System :: Microsoft :: Windows",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Topic :: Software Development :: Testing",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://brinqz.com"
|
|
32
|
+
Documentation = "https://github.com/ronnieparalejas/brinqz-agent#readme"
|
|
33
|
+
Source = "https://github.com/ronnieparalejas/brinqz-agent"
|
|
34
|
+
Issues = "https://github.com/ronnieparalejas/brinqz-agent/issues"
|
|
35
|
+
Support = "https://github.com/ronnieparalejas/brinqz-agent/issues"
|
|
36
|
+
"Brinqz Cloud" = "https://app.brinqz.com"
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
dev = [
|
|
40
|
+
"build>=1.2.2",
|
|
41
|
+
"pytest>=8.3",
|
|
42
|
+
"twine>=6.1",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[project.scripts]
|
|
46
|
+
brinqz-agent = "brinqz_agent.cli:main"
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.packages.find]
|
|
49
|
+
where = ["src"]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.dynamic]
|
|
52
|
+
version = { attr = "brinqz_agent.version.__version__" }
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.package-data]
|
|
55
|
+
brinqz_agent = ["listeners/brinqz_robot_listener.py"]
|
|
56
|
+
|
|
57
|
+
[tool.pytest.ini_options]
|
|
58
|
+
addopts = "-ra"
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Allow-listed Robot result collection for the existing Cloud contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from xml.etree import ElementTree
|
|
7
|
+
import hashlib
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
from brinqz_agent.cloud import ArtifactUpload
|
|
13
|
+
|
|
14
|
+
MAX_SCREENSHOTS = 25
|
|
15
|
+
MAX_SCREENSHOT_BYTES = 10 * 1024 * 1024
|
|
16
|
+
MAX_OUTPUT_XML_BYTES = 16 * 1024 * 1024
|
|
17
|
+
MAX_REPORT_SUMMARY_BYTES = 2 * 1024 * 1024
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ArtifactCollectionError(RuntimeError):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def collect_cloud_artifacts(output_directory: Path) -> tuple[ArtifactUpload, ...]:
|
|
25
|
+
if not output_directory.is_dir() or output_directory.is_symlink():
|
|
26
|
+
return ()
|
|
27
|
+
root = output_directory.resolve()
|
|
28
|
+
artifacts = list(_screenshots(root))
|
|
29
|
+
output_xml = root / "output.xml"
|
|
30
|
+
if _safe_regular_file(output_xml, root):
|
|
31
|
+
size = output_xml.stat().st_size
|
|
32
|
+
if 0 < size <= MAX_OUTPUT_XML_BYTES:
|
|
33
|
+
sanitized = _sanitized_output_xml(output_xml)
|
|
34
|
+
artifacts.append(
|
|
35
|
+
_artifact(
|
|
36
|
+
sanitized,
|
|
37
|
+
"artifact-robot_output-output-xml",
|
|
38
|
+
"robot_output",
|
|
39
|
+
"output.xml",
|
|
40
|
+
"application/xml",
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
return tuple(artifacts)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def report_summary_path(output_directory: Path) -> Path | None:
|
|
47
|
+
if not output_directory.is_dir() or output_directory.is_symlink():
|
|
48
|
+
return None
|
|
49
|
+
root = output_directory.resolve()
|
|
50
|
+
candidate = root / "report-summary.json"
|
|
51
|
+
if not _safe_regular_file(candidate, root):
|
|
52
|
+
return None
|
|
53
|
+
size = candidate.stat().st_size
|
|
54
|
+
if size <= 0 or size > MAX_REPORT_SUMMARY_BYTES:
|
|
55
|
+
return None
|
|
56
|
+
try:
|
|
57
|
+
payload = json.loads(candidate.read_text(encoding="utf-8"))
|
|
58
|
+
except (OSError, UnicodeError, json.JSONDecodeError):
|
|
59
|
+
return None
|
|
60
|
+
return candidate if isinstance(payload, dict) else None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _screenshots(root: Path):
|
|
64
|
+
screenshot_root = root / "browser" / "screenshot"
|
|
65
|
+
if not screenshot_root.is_dir() or screenshot_root.is_symlink():
|
|
66
|
+
return ()
|
|
67
|
+
found: list[ArtifactUpload] = []
|
|
68
|
+
for candidate in sorted(screenshot_root.rglob("*")):
|
|
69
|
+
if len(found) >= MAX_SCREENSHOTS:
|
|
70
|
+
break
|
|
71
|
+
if not _safe_regular_file(candidate, root):
|
|
72
|
+
continue
|
|
73
|
+
size = candidate.stat().st_size
|
|
74
|
+
content_type = _screenshot_type(candidate)
|
|
75
|
+
if not content_type or size <= 0 or size > MAX_SCREENSHOT_BYTES:
|
|
76
|
+
continue
|
|
77
|
+
if not _screenshot_magic(candidate, content_type):
|
|
78
|
+
continue
|
|
79
|
+
relative = candidate.resolve().relative_to(root).as_posix()
|
|
80
|
+
if not relative.startswith("browser/screenshot/"):
|
|
81
|
+
continue
|
|
82
|
+
found.append(
|
|
83
|
+
_artifact(
|
|
84
|
+
candidate,
|
|
85
|
+
_stable_artifact_id(relative, "screenshot"),
|
|
86
|
+
"screenshot",
|
|
87
|
+
relative,
|
|
88
|
+
content_type,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
return tuple(found)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _safe_regular_file(path: Path, root: Path) -> bool:
|
|
95
|
+
try:
|
|
96
|
+
if not path.is_file() or path.is_symlink():
|
|
97
|
+
return False
|
|
98
|
+
resolved = path.resolve()
|
|
99
|
+
return os.path.commonpath((str(root), str(resolved))) == str(root)
|
|
100
|
+
except (OSError, ValueError):
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _sanitized_output_xml(source: Path) -> Path:
|
|
105
|
+
try:
|
|
106
|
+
content = source.read_text(encoding="utf-8")
|
|
107
|
+
if re.search(r"(?i)<!DOCTYPE|<!ENTITY", content):
|
|
108
|
+
raise ArtifactCollectionError("Robot output.xml contains a forbidden declaration.")
|
|
109
|
+
root = ElementTree.fromstring(content)
|
|
110
|
+
except (OSError, UnicodeError, ElementTree.ParseError) as error:
|
|
111
|
+
raise ArtifactCollectionError("Robot output.xml is invalid.") from error
|
|
112
|
+
if root.tag != "robot":
|
|
113
|
+
raise ArtifactCollectionError("Robot output.xml has an unexpected root element.")
|
|
114
|
+
sanitized = re.sub(
|
|
115
|
+
r"(?i)\b(password|token|secret|api[_-]?key|login_password)\b(\s*[:=]\s*)([^\s<]+)",
|
|
116
|
+
r"\1\2[redacted]",
|
|
117
|
+
content,
|
|
118
|
+
)
|
|
119
|
+
destination = source.with_name(".brinqz-cloud-output.xml")
|
|
120
|
+
destination.write_text(sanitized, encoding="utf-8")
|
|
121
|
+
return destination
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _artifact(
|
|
125
|
+
path: Path,
|
|
126
|
+
artifact_id: str,
|
|
127
|
+
artifact_type: str,
|
|
128
|
+
relative_path: str,
|
|
129
|
+
content_type: str,
|
|
130
|
+
) -> ArtifactUpload:
|
|
131
|
+
size = path.stat().st_size
|
|
132
|
+
digest = hashlib.sha256()
|
|
133
|
+
with path.open("rb") as stream:
|
|
134
|
+
while chunk := stream.read(128 * 1024):
|
|
135
|
+
digest.update(chunk)
|
|
136
|
+
return ArtifactUpload(
|
|
137
|
+
artifact_id,
|
|
138
|
+
artifact_type,
|
|
139
|
+
relative_path,
|
|
140
|
+
content_type,
|
|
141
|
+
size,
|
|
142
|
+
digest.hexdigest(),
|
|
143
|
+
path,
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _screenshot_type(path: Path) -> str | None:
|
|
148
|
+
return {
|
|
149
|
+
".png": "image/png",
|
|
150
|
+
".jpg": "image/jpeg",
|
|
151
|
+
".jpeg": "image/jpeg",
|
|
152
|
+
".webp": "image/webp",
|
|
153
|
+
}.get(path.suffix.lower())
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _screenshot_magic(path: Path, content_type: str) -> bool:
|
|
157
|
+
with path.open("rb") as stream:
|
|
158
|
+
header = stream.read(12)
|
|
159
|
+
if content_type == "image/png":
|
|
160
|
+
return header.startswith(b"\x89PNG\r\n\x1a\n")
|
|
161
|
+
if content_type == "image/jpeg":
|
|
162
|
+
return header.startswith(b"\xff\xd8\xff")
|
|
163
|
+
return len(header) >= 12 and header[:4] == b"RIFF" and header[8:12] == b"WEBP"
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _stable_artifact_id(relative_path: str, kind: str) -> str:
|
|
167
|
+
value = f"artifact-{kind}-"
|
|
168
|
+
for character in relative_path:
|
|
169
|
+
if character.isascii() and character.isalnum():
|
|
170
|
+
value += character.lower()
|
|
171
|
+
elif character in "/.-_" and not value.endswith("-"):
|
|
172
|
+
value += "-"
|
|
173
|
+
return value.rstrip("-")
|