@andrian.yablonskyy/thub-agent 1.0.21 → 1.0.23
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/package.json +1 -1
- package/src/cli.js +82 -7
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -54,6 +54,61 @@ function client(){
|
|
|
54
54
|
return new ApiClient({ baseUrl: url, token, userAgent: `thub-agent/${version}` });
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// `--image`: an http(s) URL is a firmware file to download; anything else
|
|
58
|
+
// is taken as a Docker image reference, which only an SW job can run. Caught
|
|
59
|
+
// here so `--type hw --image alpine` explains itself instead of failing
|
|
60
|
+
// the Coordinator's schema check.
|
|
61
|
+
function firmwareFromImage(opts){
|
|
62
|
+
if (/^https?:\/\//i.test(opts.image)){
|
|
63
|
+
return { url: opts.image, ...(opts.sha256 ? { sha256: opts.sha256 } : {}) };
|
|
64
|
+
}
|
|
65
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(opts.image)){
|
|
66
|
+
throw usageError(`--image ${opts.image}: only http(s) URLs can be downloaded as firmware`);
|
|
67
|
+
}
|
|
68
|
+
if (opts.type !== 'sw'){
|
|
69
|
+
throw usageError(
|
|
70
|
+
`--image ${opts.image} looks like a Docker image, which only an SW job can run (--type sw). ` +
|
|
71
|
+
'An HW job flashes a firmware file: pass its http(s) URL.'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (opts.sha256){
|
|
75
|
+
throw usageError('--sha256 applies to a firmware URL only; pin a Docker image by digest instead (image@sha256:...)');
|
|
76
|
+
}
|
|
77
|
+
return { image: opts.image };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --tests (archive URL) or --tests-git (+ at most one of branch/tag/commit).
|
|
81
|
+
function testsFromOptions(opts){
|
|
82
|
+
const refs = ['branch', 'tag', 'commit'].filter((k) => opts[`tests${k[0].toUpperCase()}${k.slice(1)}`]);
|
|
83
|
+
if (opts.tests && opts.testsGit){
|
|
84
|
+
throw usageError('give either --tests (an archive URL) or --tests-git (a repository), not both');
|
|
85
|
+
}
|
|
86
|
+
if (!opts.tests && !opts.testsGit){
|
|
87
|
+
throw usageError('missing test sources: --tests <archive-url> or --tests-git <repo> [--tests-branch|--tests-tag|--tests-commit]');
|
|
88
|
+
}
|
|
89
|
+
if (opts.tests){
|
|
90
|
+
if (refs.length){
|
|
91
|
+
throw usageError(`--tests-${refs[0]} only applies to --tests-git`);
|
|
92
|
+
}
|
|
93
|
+
return { url: opts.tests };
|
|
94
|
+
}
|
|
95
|
+
if (refs.length > 1){
|
|
96
|
+
throw usageError(`give at most one of --tests-branch, --tests-tag, --tests-commit (got ${refs.map((r) => `--tests-${r}`).join(', ')})`);
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
git: {
|
|
100
|
+
url: opts.testsGit,
|
|
101
|
+
...(opts.testsBranch ? { branch: opts.testsBranch } : {}),
|
|
102
|
+
...(opts.testsTag ? { tag: opts.testsTag } : {}),
|
|
103
|
+
...(opts.testsCommit ? { commit: opts.testsCommit } : {})
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function usageError(message){
|
|
109
|
+
return Object.assign(new Error(message), { status: EXIT_CODES.USAGE });
|
|
110
|
+
}
|
|
111
|
+
|
|
57
112
|
function fail(err){
|
|
58
113
|
console.error(`Error: ${err.message}`);
|
|
59
114
|
process.exit(err.status && Number.isInteger(err.status) && err.status < 100 ? err.status : EXIT_CODES.USAGE);
|
|
@@ -80,11 +135,29 @@ program
|
|
|
80
135
|
'Free-text job owner, shown on the Client and the dashboard to tell whose job is whose ' +
|
|
81
136
|
'— purely a label, not an identity. Overrides THUB_USER / config file.'
|
|
82
137
|
)
|
|
83
|
-
.requiredOption(
|
|
84
|
-
|
|
85
|
-
|
|
138
|
+
.requiredOption(
|
|
139
|
+
'--image <url|docker-image>',
|
|
140
|
+
'What the DUT runs: a firmware file URL (http/https, e.g. Artifactory) the Client downloads — HW and SW jobs — ' +
|
|
141
|
+
'or, for --type sw only, a Docker image to run as the DUT (e.g. alpine, alpine:3.20, registry.lab:5000/emu:1), ' +
|
|
142
|
+
'pulled from the Client\'s registry or Docker Hub; the Client must allow it (sw.allowJobImages)'
|
|
143
|
+
)
|
|
144
|
+
.option('--sha256 <hex>', 'Expected sha256 of a firmware --image URL; the Client verifies it before flashing/running')
|
|
145
|
+
.option(
|
|
146
|
+
'--tests <url>',
|
|
147
|
+
'Test sources as an archive URL (tar, tar.gz/.tgz/.bz2/.xz, or zip) the Client downloads and unpacks. ' +
|
|
148
|
+
'Give this or --tests-git'
|
|
149
|
+
)
|
|
150
|
+
.option('--tests-git <repo>', 'Test sources as a git repository (https://, ssh://, git:// or user@host:path) the Client fetches')
|
|
151
|
+
.option('--tests-branch <name>', 'With --tests-git: the branch to check out (default: the repository\'s default branch)')
|
|
152
|
+
.option('--tests-tag <name>', 'With --tests-git: the tag to check out')
|
|
153
|
+
.option('--tests-commit <sha>', 'With --tests-git: the commit to check out (7-40 hex digits)')
|
|
154
|
+
.option(
|
|
155
|
+
'--run <command>',
|
|
156
|
+
'Shell command that starts the tests, run in the test sources on the Client (--arg values arrive as "$@"). ' +
|
|
157
|
+
'Default: the sources\' own run-tests.sh. The Client must allow it (allowJobCommands)'
|
|
158
|
+
)
|
|
86
159
|
.option('--suite <name>', 'Test suite name', 'default')
|
|
87
|
-
.option('--arg <value>', 'Extra argument passed through to run-tests.sh on the Client (repeatable)', collectRepeatable, [])
|
|
160
|
+
.option('--arg <value>', 'Extra argument passed through to run-tests.sh / --run on the Client (repeatable)', collectRepeatable, [])
|
|
88
161
|
.option('--timeout <duration>', 'e.g. 30m, 1h', '30m')
|
|
89
162
|
.option('--priority <n>', 'Priority 0-100', (v) => Number(v))
|
|
90
163
|
.option('--wait', 'Do not detach on job end; exit with the verdict code (used in CI)', false)
|
|
@@ -105,7 +178,9 @@ program
|
|
|
105
178
|
)
|
|
106
179
|
.action(async (opts) => {
|
|
107
180
|
try {
|
|
108
|
-
const
|
|
181
|
+
const firmware = firmwareFromImage(opts),
|
|
182
|
+
testSources = testsFromOptions(opts),
|
|
183
|
+
c = client(),
|
|
109
184
|
labels = [...(opts.board ? [`board:${opts.board}`] : []), ...opts.label],
|
|
110
185
|
meta = Object.fromEntries(opts.meta.map((kv) => kv.split(/=(.*)/s).slice(0, 2))),
|
|
111
186
|
group = resolveGroup({ group: opts.group }),
|
|
@@ -113,8 +188,8 @@ program
|
|
|
113
188
|
|
|
114
189
|
spec = {
|
|
115
190
|
target: { type: opts.type, labels, ...(group ? { group } : {}), ...(opts.client ? { client: opts.client } : {}) },
|
|
116
|
-
firmware
|
|
117
|
-
tests: {
|
|
191
|
+
firmware,
|
|
192
|
+
tests: { ...testSources, ...(opts.run ? { command: opts.run } : {}), suite: opts.suite, args: opts.arg },
|
|
118
193
|
timeoutSec: parseDurationSec(opts.timeout),
|
|
119
194
|
...(opts.priority !== undefined ? { priority: opts.priority } : {}),
|
|
120
195
|
...(user ? { user } : {}),
|