@andrian.yablonskyy/thub-agent 1.0.20 → 1.0.22
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 +42 -8
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
'use strict';
|
|
17
17
|
|
|
18
18
|
const { Command } = require('commander'),
|
|
19
|
-
{
|
|
20
|
-
|
|
19
|
+
{
|
|
20
|
+
ApiClient, EXIT_CODES, ACTIVE_JOB_STATES, exitCodeForJobState, PACKAGES, fetchLatestVersion, isNewer, isValidVersion, formatDateTime
|
|
21
|
+
} = require('@andrian.yablonskyy/thub-common'),
|
|
21
22
|
{ resolveConnection, resolveGroup, resolveUser, writeConfigFile, readConfigFile } = require('./config'),
|
|
22
23
|
{ parseDurationSec } = require('./duration'),
|
|
23
24
|
{ followJob } = require('./streaming'),
|
|
@@ -53,6 +54,33 @@ function client(){
|
|
|
53
54
|
return new ApiClient({ baseUrl: url, token, userAgent: `thub-agent/${version}` });
|
|
54
55
|
}
|
|
55
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
|
+
function usageError(message){
|
|
81
|
+
return Object.assign(new Error(message), { status: EXIT_CODES.USAGE });
|
|
82
|
+
}
|
|
83
|
+
|
|
56
84
|
function fail(err){
|
|
57
85
|
console.error(`Error: ${err.message}`);
|
|
58
86
|
process.exit(err.status && Number.isInteger(err.status) && err.status < 100 ? err.status : EXIT_CODES.USAGE);
|
|
@@ -79,8 +107,13 @@ program
|
|
|
79
107
|
'Free-text job owner, shown on the Client and the dashboard to tell whose job is whose ' +
|
|
80
108
|
'— purely a label, not an identity. Overrides THUB_USER / config file.'
|
|
81
109
|
)
|
|
82
|
-
.requiredOption(
|
|
83
|
-
|
|
110
|
+
.requiredOption(
|
|
111
|
+
'--image <url|docker-image>',
|
|
112
|
+
'What the DUT runs: a firmware file URL (http/https, e.g. Artifactory) the Client downloads — HW and SW jobs — ' +
|
|
113
|
+
'or, for --type sw only, a Docker image to run as the DUT (e.g. alpine, alpine:3.20, registry.lab:5000/emu:1), ' +
|
|
114
|
+
'pulled from the Client\'s registry or Docker Hub; the Client must allow it (sw.allowJobImages)'
|
|
115
|
+
)
|
|
116
|
+
.option('--sha256 <hex>', 'Expected sha256 of a firmware --image URL; the Client verifies it before flashing/running')
|
|
84
117
|
.requiredOption('--tests <url>', 'Test package URL in Artifactory')
|
|
85
118
|
.option('--suite <name>', 'Test suite name', 'default')
|
|
86
119
|
.option('--arg <value>', 'Extra argument passed through to run-tests.sh on the Client (repeatable)', collectRepeatable, [])
|
|
@@ -104,7 +137,8 @@ program
|
|
|
104
137
|
)
|
|
105
138
|
.action(async (opts) => {
|
|
106
139
|
try {
|
|
107
|
-
const
|
|
140
|
+
const firmware = firmwareFromImage(opts),
|
|
141
|
+
c = client(),
|
|
108
142
|
labels = [...(opts.board ? [`board:${opts.board}`] : []), ...opts.label],
|
|
109
143
|
meta = Object.fromEntries(opts.meta.map((kv) => kv.split(/=(.*)/s).slice(0, 2))),
|
|
110
144
|
group = resolveGroup({ group: opts.group }),
|
|
@@ -112,7 +146,7 @@ program
|
|
|
112
146
|
|
|
113
147
|
spec = {
|
|
114
148
|
target: { type: opts.type, labels, ...(group ? { group } : {}), ...(opts.client ? { client: opts.client } : {}) },
|
|
115
|
-
firmware
|
|
149
|
+
firmware,
|
|
116
150
|
tests: { url: opts.tests, suite: opts.suite, args: opts.arg },
|
|
117
151
|
timeoutSec: parseDurationSec(opts.timeout),
|
|
118
152
|
...(opts.priority !== undefined ? { priority: opts.priority } : {}),
|
|
@@ -156,7 +190,7 @@ program
|
|
|
156
190
|
}
|
|
157
191
|
|
|
158
192
|
console.log(`Job ${job.id} — ${job.state}${job.resource ? ' on ' + job.resource.name : ''}`);
|
|
159
|
-
console.log(`Created: ${job.created_at}`);
|
|
193
|
+
console.log(`Created: ${formatDateTime(job.created_at)}`);
|
|
160
194
|
|
|
161
195
|
if (ACTIVE_JOB_STATES.has(job.state)){
|
|
162
196
|
const code = await followJob(c, jobId, { fromSeq: 0, waitMode: false });
|
|
@@ -230,7 +264,7 @@ program
|
|
|
230
264
|
['SOURCE', (j) => j.source],
|
|
231
265
|
['USER', (j) => j.spec.user || '-'],
|
|
232
266
|
['STATE', (j) => j.state],
|
|
233
|
-
['CREATED', (j) => j.created_at]
|
|
267
|
+
['CREATED', (j) => formatDateTime(j.created_at)]
|
|
234
268
|
]);
|
|
235
269
|
}
|
|
236
270
|
catch (err){
|