@andrian.yablonskyy/thub-agent 1.1.3 → 1.1.4
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.
- package/README.md +45 -49
- package/package.json +2 -2
- package/src/cli.js +15 -67
- package/src/config.js +1 -10
package/README.md
CHANGED
|
@@ -9,12 +9,12 @@ See the [main TestHub repo](https://github.com/andrianyablonskyy/thub) for the f
|
|
|
9
9
|
```bash
|
|
10
10
|
npm i -g @andrian.yablonskyy/thub-agent
|
|
11
11
|
# or, one-off in CI:
|
|
12
|
-
npx -y @andrian.yablonskyy/thub-agent run --type sw --download-file "$IMAGE_URL" --
|
|
12
|
+
npx -y @andrian.yablonskyy/thub-agent run --type sw --download-file "$IMAGE_URL" --command ./ci/test.sh --wait
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
## Configuration
|
|
16
16
|
|
|
17
|
-
Read from flags (`--url`, `--key`), then environment (`THUB_URL`, `THUB_KEY
|
|
17
|
+
Read from flags (`--url`, `--key`), then environment (`THUB_URL`, `THUB_KEY`; the old names `--token`/`THUB_TOKEN` still work), then `~/.config/thub/agent.json`, then a bundled default. `url`/`token` are required by the time a command actually talks to the Coordinator; `group`/`user` are optional everywhere.
|
|
18
18
|
|
|
19
19
|
`npm install -g` creates `~/.config/thub/agent.json` for you (blank `url`/`token`, so nothing works until you set them) if it doesn't already exist — a re-install never overwrites it. Fill it in with `thub config set`:
|
|
20
20
|
|
|
@@ -24,7 +24,6 @@ thub config set key thk_... # your access key: from an admin (Users), or y
|
|
|
24
24
|
thub whoami # jane (user) <jane@example.com>
|
|
25
25
|
thub key show # its last characters, created, last used
|
|
26
26
|
thub key rotate # a new key; the old one stops at once (saved here if it came from here)
|
|
27
|
-
thub config set user "Your Name" # optional default --user
|
|
28
27
|
```
|
|
29
28
|
|
|
30
29
|
## Commands
|
|
@@ -50,13 +49,8 @@ Key options for `thub run`. **On the Client** names the environment variable the
|
|
|
50
49
|
| `--type hw\|sw` | Required resource type. | `JOB_TYPE` |
|
|
51
50
|
| `--label <l>` | Required labels (repeatable; a board is `--label board:<name>`): the job runs only on a runner that has all of them. Each 1–64 characters, no whitespace, commas or semicolons. | `JOB_LABEL`, `JOB_LABEL_<n>` |
|
|
52
51
|
| `--client <name\|id>` | Run on this specific Client (resource name or id) only; the job waits in that Client's queue even if other matching resources are idle. | `JOB_CLIENT` |
|
|
53
|
-
| `--
|
|
54
|
-
| `--command <string>` | **Required.** The task's entry point: a shell command the Client runs (`sh -c`) in the task's work directory — the `--git-repo` checkout, else an empty directory — after preparing its inputs. Its exit code is the verdict. On HW it flashes the board itself (the Client doesn't); it gets `THUB_DUT_STLINK`/`_UART`/`_USB`/`_HOST`/`_CONTAINER`, `THUB_DOWNLOAD_<n>`, `THUB_DOWNLOADS_DIR`, `THUB_GIT_COMMIT`, `THUB_SUITE`, `THUB_META_*`. | `JOB_COMMAND` |
|
|
52
|
+
| `--command <string>` | **Required.** The task's entry point: a shell command the Client runs (`sh -c`) in the job's work directory, after the downloads. It clones repositories and runs containers itself (see Git and Docker below), with credentials from `--env`. Exit code 0 = PASSED. | `JOB_COMMAND` | `JOB_COMMAND` |
|
|
55
53
|
| `--download-file <url>` | A file the Client downloads before running the command (repeatable, `http(s)`), into the job's `downloads/` directory. | `JOB_DOWNLOAD_FILE`, `JOB_DOWNLOAD_FILE_<n>` (URLs); `THUB_DOWNLOAD_<n>` (local paths) |
|
|
56
|
-
| `--docker-image <name>` | SW only: a Docker image the Client runs as the **DUT** (an emulator the tests talk to, at `$THUB_DUT_HOST` / `$THUB_DUT_CONTAINER`) — any SW Client runs it; without one, an SW job has no DUT container. It's not where `--command` runs: to run the tests in an image, see [Docker](#docker). | `JOB_DOCKER_IMAGE` |
|
|
57
|
-
| `--git-repo <url> [<branch>\|<tag>\|<commit>]` | A git repository the Client clones (default ref: the default branch); the command runs in the checkout. | `JOB_GIT_REPO_URL`, `JOB_GIT_BRANCH` |
|
|
58
|
-
| `--depth <n>` | With `--git-repo`: commits to fetch, default `1`; `0` = full history. | `JOB_GIT_DEPTH` |
|
|
59
|
-
| `--git-options <string>` | With `--git-repo`: extra git options placed between `git` and its subcommand on the Client, e.g. `'-c core.sshCommand="ssh -i ~/.ssh/lab_key -p 2222"'`. Shell-quoted (no shell run). Stored with the job, so reference key files rather than inlining secrets. | `JOB_GIT_OPTIONS` |
|
|
60
54
|
| `--env <vars>` | Environment variables for every command the Client runs for the job (git and `--command`): `NAME=value[,NAME=value]`, repeatable; `--env NAME` alone takes the value from your shell. Any names — none means anything to the Agent or the Client (only `THUB_*`, `JOB_*`, `GIT_TERMINAL_PROMPT`, `GIT_ALLOW_PROTOCOL` are refused). Every value is a secret: it reaches only the Client running the job; the Coordinator masks it and drops it when the job ends. | each `NAME` itself |
|
|
61
55
|
| `--suite <name>` | Passed to the command as `THUB_SUITE`. | `JOB_SUITE` (also `THUB_SUITE`) |
|
|
62
56
|
| `--arg <value>` | Extra argument for the command, as `"$@"` (repeatable). | `JOB_ARG`, `JOB_ARG_<n>` (and `"$@"`) |
|
|
@@ -85,35 +79,37 @@ Exit codes make the Agent usable as a CI step:
|
|
|
85
79
|
|
|
86
80
|
## Examples
|
|
87
81
|
|
|
88
|
-
**Run a task** — download the firmware,
|
|
82
|
+
**Run a task** — download the firmware, clone the tests at a tag, flash and test (HW):
|
|
89
83
|
|
|
90
84
|
```bash
|
|
85
|
+
export GH_TOKEN=… # read access to the tests repository
|
|
91
86
|
thub run --type hw --label board:nucleo-f401re \
|
|
92
|
-
--download-file "$IMAGE_URL" \
|
|
93
|
-
--git
|
|
94
|
-
|
|
87
|
+
--download-file "$IMAGE_URL" --env GH_TOKEN \
|
|
88
|
+
--command 'git clone --depth 1 --branch v1.4.0 "https://x-access-token:$GH_TOKEN@github.com/yourorg/firmware-tests.git" . &&
|
|
89
|
+
st-flash --serial "$THUB_DUT_STLINK" --reset write "$THUB_DOWNLOAD_1" 0x08000000 && ./ci/test.sh "$@"' \
|
|
95
90
|
--arg --junit --wait
|
|
96
91
|
```
|
|
97
92
|
|
|
98
|
-
**SW task against
|
|
93
|
+
**SW task against an emulator container of your own** (started and removed by the command):
|
|
99
94
|
|
|
100
95
|
```bash
|
|
101
|
-
thub run --type sw --
|
|
102
|
-
--
|
|
103
|
-
|
|
96
|
+
thub run --type sw --download-file "$IMAGE_URL" \
|
|
97
|
+
--command 'dut="thub-$THUB_JOB_ID" && trap "docker rm -f $dut >/dev/null" EXIT &&
|
|
98
|
+
docker run -d --name "$dut" -p 127.0.0.1:5555:5555 -v "$THUB_DOWNLOADS_DIR:/downloads:ro" registry.lab.local:5000/dut-emulator:2026.08 &&
|
|
99
|
+
make test DUT=127.0.0.1:5555' --wait
|
|
104
100
|
```
|
|
105
101
|
|
|
106
102
|
**Associate a CI/CD job id with the internal job id:**
|
|
107
103
|
|
|
108
104
|
```bash
|
|
109
|
-
thub run --type sw --download-file "$IMAGE_URL" --
|
|
105
|
+
thub run --type sw --download-file "$IMAGE_URL" --command ./ci/test.sh \
|
|
110
106
|
--meta ciJobId="$GITHUB_RUN_ID" --wait
|
|
111
107
|
thub status A-00123 --json | jq '.spec.meta.ciJobId'
|
|
112
108
|
```
|
|
113
109
|
|
|
114
110
|
**Pass extra metadata** — `--meta key=value` reaches the command as `THUB_META_<KEY>` (`ciJobId` → `THUB_META_CI_JOB_ID`).
|
|
115
111
|
|
|
116
|
-
**Dry-run the pipeline** — proves the Coordinator↔Client plumbing works without real hardware
|
|
112
|
+
**Dry-run the pipeline** — proves the Coordinator↔Client plumbing works without real hardware or reachable downloads:
|
|
117
113
|
|
|
118
114
|
```bash
|
|
119
115
|
thub run --type sw --download-file https://does-not-exist.invalid/app.bin \
|
|
@@ -122,20 +118,15 @@ thub run --type sw --download-file https://does-not-exist.invalid/app.bin \
|
|
|
122
118
|
|
|
123
119
|
**Run in a resource group:** there's no `--group` option. An admin or maintainer gives your user (or a CI token) one group on the dashboard, and every job it submits runs only on that group's resources; `thub whoami` shows it.
|
|
124
120
|
|
|
125
|
-
**
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
thub run --type hw --label board:nucleo-f401re \
|
|
129
|
-
--git-repo "$TESTS_REPO" --command ./ci/test.sh --user "Your Name" --wait
|
|
130
|
-
```
|
|
121
|
+
**Who submitted a job** comes from your key: your username (a CI token: its name), shown on the dashboard, in `thub jobs`, on the Client's console and as `JOB_USER`. There's no `--user`.
|
|
131
122
|
|
|
132
123
|
## Environment variables and secrets
|
|
133
124
|
|
|
134
|
-
`--env NAME=value[,NAME=value]` (repeatable) sets variables for
|
|
125
|
+
`--env NAME=value[,NAME=value]` (repeatable) sets variables for `--command`. It's how all data and secrets reach a job (a git token, a registry password, an API key). The names are yours; none means anything to the Agent or the Client. `--env NAME` alone takes the value from your own environment, so a secret stays off the command line and out of CI logs:
|
|
135
126
|
|
|
136
127
|
```bash
|
|
137
128
|
export API_TOKEN=…
|
|
138
|
-
thub run --type sw
|
|
129
|
+
thub run --type sw \
|
|
139
130
|
--env TARGET=staging --env API_TOKEN \
|
|
140
131
|
--command './run-tests.sh --target "$TARGET"' --wait
|
|
141
132
|
```
|
|
@@ -147,10 +138,24 @@ thub run --type sw --git-repo "$TESTS_REPO" \
|
|
|
147
138
|
- The Coordinator replaces the values with `***` once the job ends.
|
|
148
139
|
- A `--dry-run` shows every value as `***`, whatever its name.
|
|
149
140
|
|
|
150
|
-
Your command's output is the job's log, so don't print secrets. Everything else — `--command`, `--arg`, `--meta`, `--
|
|
141
|
+
Your command's output is the job's log, so don't print secrets. Everything else — `--command`, `--arg`, `--meta`, `--download-file` — is stored as is and visible, so never put secrets there.
|
|
142
|
+
|
|
143
|
+
## Git
|
|
144
|
+
|
|
145
|
+
The Client doesn't clone anything (and doesn't need git itself): the command does, into its work directory, with a token from `--env`:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
export GH_TOKEN=…
|
|
149
|
+
thub run --type sw --env GH_TOKEN,REF=main \
|
|
150
|
+
--command 'git clone --depth 1 --branch "$REF" "https://x-access-token:$GH_TOKEN@github.com/yourorg/tests.git" . && ./ci/test.sh' --wait
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
For SSH, pass the private key itself as `--env` (base64) and use it via `GIT_SSH_COMMAND` from a file in the job directory, which is deleted with the job (main README, §7.2).
|
|
151
154
|
|
|
152
155
|
## Docker
|
|
153
156
|
|
|
157
|
+
The Client doesn't pull images or start containers (and doesn't need Docker itself): the command does.
|
|
158
|
+
|
|
154
159
|
**Log in to a custom registry:** in `--command`, with credentials passed as `--env` under any names. Point `DOCKER_CONFIG` at the job's work directory first, so the login is deleted with the job rather than left in the Client user's `~/.docker` for later jobs:
|
|
155
160
|
|
|
156
161
|
```bash
|
|
@@ -163,21 +168,13 @@ thub run --type hw \
|
|
|
163
168
|
--wait
|
|
164
169
|
```
|
|
165
170
|
|
|
166
|
-
**
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
thub run --type sw --docker-image registry.lab.local:5000/dut-emulator:2026.08 \
|
|
170
|
-
--git-repo "$TESTS_REPO" --command './ci/test.sh --dut "$THUB_DUT_HOST"' --wait
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
**Run the command inside a container:** `--command` starts on the Client host in the work directory (`$THUB_WORK_DIR`, the `--git-repo` checkout). Start the container from it, mounting that directory:
|
|
171
|
+
**Run the command inside a container:** `--command` starts on the Client host in the work directory (`$THUB_WORK_DIR`). Clone into it, then start the container mounting it:
|
|
174
172
|
|
|
175
173
|
```bash
|
|
176
174
|
thub run --type sw \
|
|
177
|
-
--env DOCKER_REGISTRY=registry.lab.local:5000,DOCKER_USER=ci --env DOCKER_PASSWORD \
|
|
178
|
-
--git-
|
|
179
|
-
|
|
180
|
-
--command 'export DOCKER_CONFIG="$THUB_WORK_DIR/.docker" &&
|
|
175
|
+
--env DOCKER_REGISTRY=registry.lab.local:5000,DOCKER_USER=ci --env DOCKER_PASSWORD --env GH_TOKEN \
|
|
176
|
+
--command 'git clone --depth 1 "https://x-access-token:$GH_TOKEN@github.com/yourorg/web-ui-tests.git" . &&
|
|
177
|
+
export DOCKER_CONFIG="$THUB_WORK_DIR/.docker" &&
|
|
181
178
|
echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" --username "$DOCKER_USER" --password-stdin &&
|
|
182
179
|
docker run --rm -v "$THUB_WORK_DIR:/work" -w /work "$DOCKER_REGISTRY/python:3.14" ./run-tests.sh' \
|
|
183
180
|
--wait
|
|
@@ -199,21 +196,19 @@ thub run --type sw \
|
|
|
199
196
|
--wait
|
|
200
197
|
```
|
|
201
198
|
|
|
202
|
-
The Client host needs Docker and the Client's user in the `docker` group
|
|
199
|
+
The Client host needs Docker and the Client's user in the `docker` group for these (install Docker before the Client). Add `--dry-run` to see every command a job would run on the Client (every `--env` value shown as `***`) without running any. More in the main README, §7.2.
|
|
203
200
|
|
|
204
201
|
## Client environment variables
|
|
205
202
|
|
|
206
203
|
On the Client, the job's `--command` gets every option above as a `JOB_<NAME>` variable (the **On the Client** column). A variable whose option wasn't given is unset. A repeatable option gives `<NAME>` with all values plus `<NAME>_<n>` for each one. It also gets:
|
|
207
204
|
- its `--env` variables, under their own names;
|
|
208
|
-
- `THUB_JOB_ID`, `THUB_WORK_DIR` (where it runs), `
|
|
209
|
-
- the DUT's `THUB_DUT_UART[_<n>]`, `THUB_DUT_USB[_<n>]`, `THUB_DUT_STLINK[_<n>]` (HW)
|
|
205
|
+
- `THUB_JOB_ID`, `THUB_WORK_DIR` (where it runs), `THUB_DOWNLOADS_DIR`, `THUB_DOWNLOADS`, `THUB_DOWNLOAD_<n>` (local paths), `THUB_SUITE` and `THUB_META_<KEY>`;
|
|
206
|
+
- the DUT's `THUB_DUT_UART[_<n>]`, `THUB_DUT_USB[_<n>]`, `THUB_DUT_STLINK[_<n>]` (HW).
|
|
210
207
|
|
|
211
208
|
`THUB_*` and `JOB_*` names can't be set with `--env`. The full list is in the main README, §7.4.
|
|
212
209
|
|
|
213
210
|
```bash
|
|
214
|
-
thub run --type
|
|
215
|
-
--command 'git clone --depth "$JOB_GIT_DEPTH" ${JOB_GIT_BRANCH:+--branch "$JOB_GIT_BRANCH"} "$JOB_GIT_REPO_URL" src && cd src && ./run-tests.sh' \
|
|
216
|
-
--wait
|
|
211
|
+
thub run --type hw --label uart --suite smoke --command 'echo "suite $JOB_SUITE on $JOB_LABEL" && ./ci/test.sh' --wait
|
|
217
212
|
```
|
|
218
213
|
|
|
219
214
|
## Job status and PASS/FAIL
|
|
@@ -232,7 +227,7 @@ thub jobs --mine --state FAILED
|
|
|
232
227
|
**From a script:** `thub status <jobId> --json` prints the job once, with `state`, `exit_code`, `summary`, `message` and `resource.name`. It exits with the verdict code, or `5` while the job is still queued or running:
|
|
233
228
|
|
|
234
229
|
```bash
|
|
235
|
-
JOB=$(thub run --type sw --
|
|
230
|
+
JOB=$(thub run --type sw --command ./ci/test.sh --detach --json | jq -r .jobId)
|
|
236
231
|
while thub status "$JOB" --json > job.json; [ $? -eq 5 ]; do sleep 10; done
|
|
237
232
|
jq -r '"\(.state) exit=\(.exit_code) failed=\(.summary.failed // 0)"' job.json # PASSED exit=0 failed=0
|
|
238
233
|
```
|
|
@@ -250,8 +245,9 @@ test-sw:
|
|
|
250
245
|
- run: |
|
|
251
246
|
npx -y @andrian.yablonskyy/thub-agent run --type sw \
|
|
252
247
|
--download-file "${{ needs.build.outputs.image_url }}" \
|
|
253
|
-
--
|
|
254
|
-
--command
|
|
248
|
+
--env GH_TOKEN="${{ secrets.TESTS_READ_TOKEN }}",REPO="$GITHUB_REPOSITORY",SHA="$GITHUB_SHA" \
|
|
249
|
+
--command 'git init -q . && git fetch -q --depth 1 "https://x-access-token:$GH_TOKEN@github.com/$REPO.git" "$SHA" &&
|
|
250
|
+
git checkout -q FETCH_HEAD && ./ci/sw-tests.sh' --suite full --wait
|
|
255
251
|
```
|
|
256
252
|
|
|
257
253
|
If the GitHub job is canceled, the runner sends `SIGINT` to the Agent. In `--wait` mode (CI), that's treated as a cancel request (`POST /jobs/:id/cancel`) before exiting, so abandoned CI jobs don't hold hardware. In interactive mode, Ctrl-C only detaches.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@andrian.yablonskyy/thub-agent",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "TestHub Agent CLI — the single entry point CI/CD and developers use to submit and follow test jobs",
|
|
5
5
|
"bin": {
|
|
6
6
|
"thub": "./src/cli.js"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"lint:fix": "eslint . --fix"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@andrian.yablonskyy/thub-common": "^1.1.
|
|
15
|
+
"@andrian.yablonskyy/thub-common": "^1.1.3",
|
|
16
16
|
"commander": "^13.1.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
package/src/cli.js
CHANGED
|
@@ -18,10 +18,9 @@
|
|
|
18
18
|
const { Command, Option } = require('commander'),
|
|
19
19
|
{
|
|
20
20
|
ApiClient, EXIT_CODES, ACTIVE_JOB_STATES, exitCodeForJobState, PACKAGES, fetchLatestVersion, isNewer, isValidVersion, formatDateTime,
|
|
21
|
-
splitArgs,
|
|
22
21
|
parseEnvList
|
|
23
22
|
} = require('@andrian.yablonskyy/thub-common'),
|
|
24
|
-
{ resolveConnection,
|
|
23
|
+
{ resolveConnection, writeConfigFile, readConfigFile, CONFIG_PATH } = require('./config'),
|
|
25
24
|
{ parseDurationSec } = require('./duration'),
|
|
26
25
|
{ followJob } = require('./streaming'),
|
|
27
26
|
{ applyRequestedUpdate, installAgent, version } = require('./self-update');
|
|
@@ -57,8 +56,8 @@ function client(){
|
|
|
57
56
|
return new ApiClient({ baseUrl: url, token, userAgent: `thub-agent/${version}` });
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
// --command (mandatory), --download-file (repeatable)
|
|
61
|
-
//
|
|
59
|
+
// --command (mandatory), --download-file (repeatable) and --env -> the job
|
|
60
|
+
// spec's task fields. A git checkout or a container is the command's own job.
|
|
62
61
|
// Checked here too, so mistakes explain themselves before anything is sent.
|
|
63
62
|
function taskFromOptions(opts){
|
|
64
63
|
const downloads = opts.downloadFile.map((url) => {
|
|
@@ -69,38 +68,6 @@ function taskFromOptions(opts){
|
|
|
69
68
|
}),
|
|
70
69
|
task = { command: opts.command, args: opts.arg, suite: opts.suite, ...(downloads.length ? { downloads } : {}) };
|
|
71
70
|
|
|
72
|
-
if (opts.dockerImage){
|
|
73
|
-
if (opts.type !== 'sw'){
|
|
74
|
-
throw usageError('--docker-image only works for SW jobs (--type sw): an HW job runs on the physical board');
|
|
75
|
-
}
|
|
76
|
-
task.image = opts.dockerImage;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (opts.depth !== undefined && !opts.gitRepo){
|
|
80
|
-
throw usageError('--depth only applies to --git-repo');
|
|
81
|
-
}
|
|
82
|
-
if (opts.gitOptions !== undefined && !opts.gitRepo){
|
|
83
|
-
throw usageError('--git-options only applies to --git-repo');
|
|
84
|
-
}
|
|
85
|
-
if (opts.gitOptions){
|
|
86
|
-
try {
|
|
87
|
-
splitArgs(opts.gitOptions);
|
|
88
|
-
}
|
|
89
|
-
catch (err){
|
|
90
|
-
throw usageError(`--git-options: ${err.message}`);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
if (opts.gitRepo){
|
|
94
|
-
const [url, ref, ...extra] = opts.gitRepo;
|
|
95
|
-
if (extra.length){
|
|
96
|
-
throw usageError(`--git-repo takes a URL and at most one branch, tag or commit (got: ${opts.gitRepo.join(' ')})`);
|
|
97
|
-
}
|
|
98
|
-
const depth = opts.depth === undefined ? 1 : Number(opts.depth);
|
|
99
|
-
if (!Number.isInteger(depth) || depth < 0){
|
|
100
|
-
throw usageError(`--depth ${opts.depth}: must be a whole number (0 = full history)`);
|
|
101
|
-
}
|
|
102
|
-
task.git = { url, ...(ref ? { ref } : {}), depth, ...(opts.gitOptions ? { options: opts.gitOptions } : {}) };
|
|
103
|
-
}
|
|
104
71
|
let env;
|
|
105
72
|
try {
|
|
106
73
|
env = parseEnvList(opts.env);
|
|
@@ -160,15 +127,12 @@ program
|
|
|
160
127
|
'Run on this specific Client (resource name or id) only; the job waits in that Client\'s queue ' +
|
|
161
128
|
'even if other matching resources are idle.'
|
|
162
129
|
)
|
|
163
|
-
.option(
|
|
164
|
-
'--user <name>',
|
|
165
|
-
'Free-text job owner, shown on the Client and the dashboard to tell whose job is whose ' +
|
|
166
|
-
'— purely a label, not an identity. Overrides THUB_USER / config file.'
|
|
167
|
-
)
|
|
168
130
|
.requiredOption(
|
|
169
131
|
'--command <string>',
|
|
170
|
-
'The task\'s entry point: a shell command the Client runs (sh -c) in the
|
|
171
|
-
'
|
|
132
|
+
'The task\'s entry point: a shell command the Client runs (sh -c) in the job\'s work directory, after downloading ' +
|
|
133
|
+
'--download-file files. --arg values arrive as "$@". Anything else the job needs it does itself — e.g. ' +
|
|
134
|
+
'`git clone "https://x-access-token:$GH_TOKEN@github.com/org/tests.git" . && ./ci/test.sh` or ' +
|
|
135
|
+
'`docker run --rm "$IMAGE" ./run.sh` — with credentials passed by --env'
|
|
172
136
|
)
|
|
173
137
|
.option(
|
|
174
138
|
'--download-file <url>',
|
|
@@ -177,27 +141,11 @@ program
|
|
|
177
141
|
collectRepeatable,
|
|
178
142
|
[]
|
|
179
143
|
)
|
|
180
|
-
.option(
|
|
181
|
-
'--docker-image <name>',
|
|
182
|
-
'SW jobs only: a Docker image the Client runs as the job\'s DUT container, next to --command (e.g. registry.lab:5000/emu:1); ' +
|
|
183
|
-
'pulled from the registry it names, else Docker Hub. Without it, an SW job has no DUT container'
|
|
184
|
-
)
|
|
185
|
-
.option(
|
|
186
|
-
'--git-repo <url...>',
|
|
187
|
-
'A git repository the Client clones before running --command, then runs it there: <url> [<branch>|<tag>|<commit>] ' +
|
|
188
|
-
'(https://, ssh://, git:// or user@host:path; default ref: the default branch)'
|
|
189
|
-
)
|
|
190
|
-
.option('--depth <n>', 'With --git-repo: how many commits to fetch (default 1; 0 = full history)')
|
|
191
|
-
.option(
|
|
192
|
-
'--git-options <string>',
|
|
193
|
-
'With --git-repo: extra git options, placed between `git` and its subcommand on the Client (shell-quoted, no shell run), ' +
|
|
194
|
-
'e.g. \'-c core.sshCommand="ssh -i ~/.ssh/lab_key -p 2222"\'. Stored with the job — reference key files, don\'t inline secrets'
|
|
195
|
-
)
|
|
196
144
|
.option(
|
|
197
145
|
'--env <vars>',
|
|
198
|
-
'Environment variables for
|
|
146
|
+
'Environment variables for --command — how data and secrets (a git token, a registry password) reach the job: ' +
|
|
199
147
|
'NAME=value[,NAME=value] (repeatable; a value may contain commas); --env NAME alone takes its value from this shell. ' +
|
|
200
|
-
'Any names
|
|
148
|
+
'Any names except THUB_* and JOB_*. ' +
|
|
201
149
|
'Values reach only the Client running the job; the Coordinator masks them and drops them when the job ends',
|
|
202
150
|
collectRepeatable,
|
|
203
151
|
[]
|
|
@@ -228,15 +176,13 @@ program
|
|
|
228
176
|
c = client(),
|
|
229
177
|
labels = requiredLabels(opts),
|
|
230
178
|
meta = Object.fromEntries(opts.meta.map((kv) => kv.split(/=(.*)/s).slice(0, 2))),
|
|
231
|
-
user = resolveUser({ user: opts.user }),
|
|
232
179
|
|
|
233
180
|
spec = {
|
|
234
|
-
// No group: the Coordinator
|
|
181
|
+
// No group or user: the Coordinator takes both from this key (§13.1).
|
|
235
182
|
target: { type: opts.type, labels, ...(opts.client ? { client: opts.client } : {}) },
|
|
236
183
|
...task,
|
|
237
184
|
timeoutSec: parseDurationSec(opts.timeout),
|
|
238
185
|
...(opts.priority !== undefined ? { priority: opts.priority } : {}),
|
|
239
|
-
...(user ? { user } : {}),
|
|
240
186
|
...(Object.keys(meta).length ? { meta } : {}),
|
|
241
187
|
...(opts.dryRun ? { dryRun: true } : {})
|
|
242
188
|
},
|
|
@@ -385,15 +331,17 @@ function formatBytes(bytes){
|
|
|
385
331
|
const config = program.command('config').description('Manage local Agent configuration');
|
|
386
332
|
config
|
|
387
333
|
.command('set')
|
|
388
|
-
.argument('<name>', 'url | key
|
|
334
|
+
.argument('<name>', 'url | key')
|
|
389
335
|
.argument('<value>')
|
|
390
336
|
.action((name, value) => {
|
|
391
337
|
// `token` is the old name of `key`.
|
|
392
338
|
const setting = name === 'token' ? 'key' : name;
|
|
393
|
-
if (!['url', 'key'
|
|
339
|
+
if (!['url', 'key'].includes(setting)){
|
|
394
340
|
console.error(setting === 'group'
|
|
395
341
|
? 'Error: a job\'s group is set on the dashboard now (Users / CI tokens), not in the Agent'
|
|
396
|
-
:
|
|
342
|
+
: setting === 'user'
|
|
343
|
+
? 'Error: a job\'s user comes from your access key now (thub whoami shows it), not from the Agent'
|
|
344
|
+
: 'Error: the setting must be "url" or "key"');
|
|
397
345
|
process.exit(EXIT_CODES.USAGE);
|
|
398
346
|
}
|
|
399
347
|
const { token: _old, ...current } = readConfigFile();
|
package/src/config.js
CHANGED
|
@@ -65,13 +65,4 @@ function resolveConnection(flags = {}){
|
|
|
65
65
|
return { url, token, keySource };
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
// url/token. Purely a label (who submitted this job), not an
|
|
70
|
-
// identity: unset is fine, and nothing on the Coordinator side enforces
|
|
71
|
-
// or authenticates it.
|
|
72
|
-
function resolveUser(flags = {}){
|
|
73
|
-
const file = { ...readJsonFile(PACKAGE_DEFAULT_CONFIG_PATH), ...readConfigFile() };
|
|
74
|
-
return flags.user || process.env.THUB_USER || file.user || undefined;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
module.exports = { CONFIG_PATH, readConfigFile, writeConfigFile, resolveConnection, resolveUser };
|
|
68
|
+
module.exports = { CONFIG_PATH, readConfigFile, writeConfigFile, resolveConnection };
|