@andrian.yablonskyy/thub-agent 1.1.3 → 1.1.5

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 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" --git-repo "$TESTS_REPO" --command ./ci/test.sh --wait
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`, `THUB_USER`; 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.
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,15 +49,10 @@ 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
- | `--user <name>` | Free-text job owner — a label, not an identity. Falls back to `THUB_USER` / `thub config set user <name>`. | `JOB_USER` |
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
- | `--suite <name>` | Passed to the command as `THUB_SUITE`. | `JOB_SUITE` (also `THUB_SUITE`) |
55
+ | `--suite <name>` | Test suite name for the command. | `JOB_SUITE` |
62
56
  | `--arg <value>` | Extra argument for the command, as `"$@"` (repeatable). | `JOB_ARG`, `JOB_ARG_<n>` (and `"$@"`) |
63
57
  | `--timeout <dur>` | e.g. `30m`, default `30m`. | `JOB_TIMEOUT` (seconds) |
64
58
  | `--priority <n>` | 0–100; CI defaults to 50, CLI to 60 so a developer is not starved by a busy pipeline. | `JOB_PRIORITY` |
@@ -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, check out the tests at a tag, flash and test (HW):
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-repo https://github.com/yourorg/firmware-tests.git v1.4.0 \
94
- --command 'st-flash --serial "$THUB_DUT_STLINK" --reset write "$THUB_DOWNLOAD_1" 0x08000000 && ./ci/test.sh "$@"' \
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" src && cd src &&
89
+ st-flash --reset write "$THUB_DOWNLOAD_1" 0x08000000 && ./ci/test.sh "$@"' \
95
90
  --arg --junit --wait
96
91
  ```
97
92
 
98
- **SW task against a DUT emulator image of your own** (the command runs on the Client host and talks to the emulator):
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 --docker-image registry.lab.local:5000/dut-emulator:2026.08 \
102
- --git-repo git@github.com:yourorg/firmware-tests.git main --depth 20 \
103
- --command 'make test DUT="$THUB_DUT_HOST"' --wait
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" --git-repo "$TESTS_REPO" --command ./ci/test.sh \
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, a real emulator image, or reachable downloads:
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
- **Label a job with its owner** — purely informational, shows up on the dashboard, in `thub jobs`, and on the Client's own console:
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 every command the Client runs for the job: git and `--command`. 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:
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 --git-repo "$TESTS_REPO" \
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,73 +138,82 @@ 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`, `--git-options` — is stored as is and visible, so never put secrets there.
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" src && cd src && ./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
157
162
  export DOCKER_PASSWORD=…
158
163
  thub run --type hw \
159
164
  --env DOCKER_REGISTRY=registry.lab.local:5000,DOCKER_USER=ci --env DOCKER_PASSWORD \
160
- --command 'export DOCKER_CONFIG="$THUB_WORK_DIR/.docker" &&
161
- echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" --username "$DOCKER_USER" --password-stdin &&
165
+ --command 'echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" --username "$DOCKER_USER" --password-stdin &&
162
166
  docker pull "$DOCKER_REGISTRY/team/test-runner:1.4"' \
163
167
  --wait
164
168
  ```
165
169
 
166
- **Pull an image as the DUT:** `--docker-image` is pulled by the Client before the command runs, as its service user. For a private registry, log in once on the Client host as that user: `sudo -u thub docker login registry.lab.local:5000`.
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:
170
+ **Run the command inside a container:** `--command` starts on the Client host in the job's directory (`$THUB_WORK_DIR`). Clone into `src/`, then start the container mounting only `src/` (the directory also holds `.docker/`, the job's registry login):
174
171
 
175
172
  ```bash
176
173
  thub run --type sw \
177
- --env DOCKER_REGISTRY=registry.lab.local:5000,DOCKER_USER=ci --env DOCKER_PASSWORD \
178
- --git-repo git@bitbucket.org:yourorg/web-ui-tests.git main \
179
- --git-options '-c core.sshCommand="ssh -i /home/thub/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new"' \
180
- --command 'export DOCKER_CONFIG="$THUB_WORK_DIR/.docker" &&
174
+ --env DOCKER_REGISTRY=registry.lab.local:5000,DOCKER_USER=ci --env DOCKER_PASSWORD --env GH_TOKEN \
175
+ --command 'git clone --depth 1 "https://x-access-token:$GH_TOKEN@github.com/yourorg/web-ui-tests.git" src && cd src &&
181
176
  echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" --username "$DOCKER_USER" --password-stdin &&
182
- docker run --rm -v "$THUB_WORK_DIR:/work" -w /work "$DOCKER_REGISTRY/python:3.14" ./run-tests.sh' \
177
+ docker run --rm --user "$(id -u):$(id -g)" -v "$THUB_WORK_DIR/src:/work" -w /work "$DOCKER_REGISTRY/python:3.14" ./run-tests.sh' \
183
178
  --wait
184
179
  ```
185
180
 
186
- **Clone the repository inside the image, with parameters from `--env`:**
181
+ **Clone and test inside a container, with a deploy key and a registry login from `--env`** (the reference example for passing data and secrets to a job):
187
182
 
188
183
  ```bash
184
+ # In CI, from its secret store — never typed on the command line:
185
+ export THUB_KEY=… # a CI token (dashboard → CI tokens)
186
+ export DOCKER_PASSWORD=… # the registry password
187
+ export GIT_KEY="$(cat ~/.ssh/thub_deploy)" # a private deploy key with read access to the repository
188
+
189
189
  thub run --type sw \
190
- --env DOCKER_REGISTRY=registry.lab.local:5000,DOCKER_USER=ci --env DOCKER_PASSWORD \
191
- --env TEST_IMAGE=registry.lab.local:5000/team/test-runner:1.4 \
192
- --env REPO_URL=git@bitbucket.org:yourorg/web-ui-tests.git,REPO_REF=main \
193
- --env 'GIT_SSH_COMMAND=ssh -i /root/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new' \
194
- --command 'export DOCKER_CONFIG="$THUB_WORK_DIR/.docker" &&
195
- echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" --username "$DOCKER_USER" --password-stdin &&
196
- docker run --rm -e REPO_URL -e REPO_REF -e GIT_SSH_COMMAND \
197
- -v "$HOME/.ssh:/root/.ssh:ro" -v "$THUB_WORK_DIR/results:/results" \
198
- "$TEST_IMAGE" sh -c "git clone --depth 1 --branch \"\$REPO_REF\" \"\$REPO_URL\" /src && cd /src && ./run-tests.sh --junit /results"' \
190
+ --env DOCKER_REGISTRY=registry.lab:5000,DOCKER_USERNAME=ci-reader \
191
+ --env DOCKER_PASSWORD --env GIT_KEY \
192
+ --command 'echo "$DOCKER_PASSWORD" | docker login "$DOCKER_REGISTRY" --username "$DOCKER_USERNAME" --password-stdin &&
193
+ mkdir -p "$THUB_WORK_DIR/src" &&
194
+ docker run --rm -e GIT_KEY -e HOME=/tmp --user "$(id -u):$(id -g)" \
195
+ -v "$THUB_WORK_DIR/src:/work" -w /work --entrypoint sh alpine/git -c "
196
+ eval \$(ssh-agent -s) > /dev/null &&
197
+ printf \"%s\n\" \"\$GIT_KEY\" | ssh-add - &&
198
+ GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=accept-new\" git clone --depth 1 git@bitbucket.org:yourorg/web-ui-tests.git . &&
199
+ ./run-tests.sh"' \
199
200
  --wait
200
201
  ```
201
202
 
202
- The Client host needs Docker and the Client's user in the `docker` group; SW Clients have both. 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
+ Plain values go as `--env NAME=value`, secrets as `--env NAME` (taken from your environment, so never on the command line; multi-line values arrive intact). The inner script's `\$` is expanded in the container; `ssh-agent` keeps the key in memory only; `--entrypoint sh` because `alpine/git`'s entrypoint is `git`; `--user` keeps the clone deletable by the Client. Only `src/` is mounted, so the registry login (the Client's `DOCKER_CONFIG`, `$THUB_WORK_DIR/.docker`) stays out of the container. More in the main README, §7.2.
204
+
205
+ 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
206
 
204
207
  ## Client environment variables
205
208
 
206
209
  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
210
  - its `--env` variables, under their own names;
208
- - `THUB_JOB_ID`, `THUB_WORK_DIR` (where it runs), `THUB_GIT_COMMIT`, `THUB_DOWNLOADS_DIR`, `THUB_DOWNLOADS`, `THUB_DOWNLOAD_<n>` (local paths), `THUB_SUITE` and `THUB_META_<KEY>`;
209
- - the DUT's `THUB_DUT_UART[_<n>]`, `THUB_DUT_USB[_<n>]`, `THUB_DUT_STLINK[_<n>]` (HW), or `THUB_DUT_HOST` and `THUB_DUT_CONTAINER` (SW with `--docker-image`).
211
+ - `THUB_JOB_ID`, `THUB_WORK_DIR` (where it runs), `THUB_DOWNLOADS_DIR`, `THUB_DOWNLOADS`, `THUB_DOWNLOAD_<n>` (local paths) and `THUB_META_<KEY>`. HW devices are used by their `/dev/thub/dut<N>-uart|usb|stlink` paths; no device variables are passed.
210
212
 
211
213
  `THUB_*` and `JOB_*` names can't be set with `--env`. The full list is in the main README, §7.4.
212
214
 
213
215
  ```bash
214
- thub run --type sw --git-repo git@bitbucket.org:yourorg/tests.git main --depth 1 \
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
216
+ thub run --type hw --label uart --suite smoke --command 'echo "suite $JOB_SUITE on $JOB_LABEL" && ./ci/test.sh' --wait
217
217
  ```
218
218
 
219
219
  ## Job status and PASS/FAIL
@@ -232,7 +232,7 @@ thub jobs --mine --state FAILED
232
232
  **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
233
 
234
234
  ```bash
235
- JOB=$(thub run --type sw --git-repo "$TESTS_REPO" --command ./ci/test.sh --detach --json | jq -r .jobId)
235
+ JOB=$(thub run --type sw --command ./ci/test.sh --detach --json | jq -r .jobId)
236
236
  while thub status "$JOB" --json > job.json; [ $? -eq 5 ]; do sleep 10; done
237
237
  jq -r '"\(.state) exit=\(.exit_code) failed=\(.summary.failed // 0)"' job.json # PASSED exit=0 failed=0
238
238
  ```
@@ -250,8 +250,9 @@ test-sw:
250
250
  - run: |
251
251
  npx -y @andrian.yablonskyy/thub-agent run --type sw \
252
252
  --download-file "${{ needs.build.outputs.image_url }}" \
253
- --git-repo "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git" "$GITHUB_SHA" \
254
- --command ./ci/sw-tests.sh --suite full --wait
253
+ --env GH_TOKEN="${{ secrets.TESTS_READ_TOKEN }}",REPO="$GITHUB_REPOSITORY",SHA="$GITHUB_SHA" \
254
+ --command 'git init -q src && cd src && git fetch -q --depth 1 "https://x-access-token:$GH_TOKEN@github.com/$REPO.git" "$SHA" &&
255
+ git checkout -q FETCH_HEAD && ./ci/sw-tests.sh' --suite full --wait
255
256
  ```
256
257
 
257
258
  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",
3
+ "version": "1.1.5",
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.2",
15
+ "@andrian.yablonskyy/thub-common": "^1.1.4",
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, resolveUser, writeConfigFile, readConfigFile, CONFIG_PATH } = require('./config'),
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), --docker-image (SW
61
- // only) and --git-repo <url> [ref] --depth <n> -> the job spec's task fields.
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 task\'s work directory — the --git-repo ' +
171
- 'checkout if given — after downloading --download-file files. --arg values arrive as "$@"'
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" src && cd src && ./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,32 +141,16 @@ 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 every command the Client runs for the job (git, --command): ' +
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 (except THUB_*, JOB_*, GIT_TERMINAL_PROMPT, GIT_ALLOW_PROTOCOL). ' +
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
  []
204
152
  )
205
- .option('--suite <name>', 'Test suite name, passed to --command as THUB_SUITE', 'default')
153
+ .option('--suite <name>', 'Test suite name, passed to --command as JOB_SUITE', 'default')
206
154
  .option('--arg <value>', 'Extra argument for --command, as "$@" (repeatable)', collectRepeatable, [])
207
155
  .option('--timeout <duration>', 'e.g. 30m, 1h', '30m')
208
156
  .option('--priority <n>', 'Priority 0-100', (v) => Number(v))
@@ -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 uses this key's, set on the dashboard (§13.1).
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 | user')
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', 'user'].includes(setting)){
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
- : 'Error: the setting must be "url", "key", or "user"');
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
- // Optional default for `--user` (§4.3/§7.1) — flags > env > file, like
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 };