@andrian.yablonskyy/thub-common 1.1.1 → 1.1.3
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/job-spec.schema.js +11 -41
- package/src/split-args.js +1 -1
- package/src/validate-job-spec.js +18 -30
package/package.json
CHANGED
package/src/job-spec.schema.js
CHANGED
|
@@ -13,23 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
'use strict';
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
const DOCKER_IMAGE_PATTERN =
|
|
19
|
-
'^[A-Za-z0-9][A-Za-z0-9.-]*(:[0-9]+)?(/[a-z0-9]+((\\.|_|__|-+)[a-z0-9]+)*)*(:[A-Za-z0-9_][A-Za-z0-9_.-]{0,127})?(@sha256:[a-f0-9]{64})?$',
|
|
20
|
-
|
|
21
|
-
// A git ref to check out — a branch, a tag or a commit (hex) — as git
|
|
22
|
-
// allows them: no leading "-" (never taken for an option) or "/", no "..",
|
|
23
|
-
// no spaces or the characters git forbids, not ending in "/", ".lock", ".".
|
|
24
|
-
GIT_REF_PATTERN = '^(?![-/])(?!.*\\.\\.)(?!.*//)(?!.*(/|\\.lock|\\.)$)[A-Za-z0-9._/+@-]+$',
|
|
25
|
-
|
|
26
|
-
// An environment variable name a job may set (`--env NAME=value`).
|
|
27
|
-
ENV_NAME_PATTERN = '^[A-Za-z_][A-Za-z0-9_]*$',
|
|
16
|
+
// An environment variable name a job may set (`--env NAME=value`).
|
|
17
|
+
const ENV_NAME_PATTERN = '^[A-Za-z_][A-Za-z0-9_]*$',
|
|
28
18
|
|
|
29
19
|
// Matches the job spec shape documented in README.md §4.3. A task is:
|
|
30
|
-
// optionally files to download and
|
|
31
|
-
//
|
|
32
|
-
//
|
|
20
|
+
// optionally files to download, and — always — the shell command that is
|
|
21
|
+
// its entry point, run on the Client in the job's work directory. Anything
|
|
22
|
+
// else it needs (a git checkout, a Docker container) the command does
|
|
23
|
+
// itself, with credentials passed in `env`.
|
|
33
24
|
jobSpecSchema = {
|
|
34
25
|
$id: 'https://thub.example.com/schemas/job-spec.json',
|
|
35
26
|
type: 'object',
|
|
@@ -48,7 +39,8 @@ const DOCKER_IMAGE_PATTERN =
|
|
|
48
39
|
default: []
|
|
49
40
|
},
|
|
50
41
|
// Constrains scheduling to resources that are members of this
|
|
51
|
-
// group (§13.1
|
|
42
|
+
// group (§13.1): filled in by the Coordinator from the submitting
|
|
43
|
+
// agent's group (set on the dashboard; an Agent's own is replaced) — a third targeting
|
|
52
44
|
// dimension alongside type/labels. Omitted: any matching resource
|
|
53
45
|
// in any (or no) group is eligible, same as before groups existed.
|
|
54
46
|
group: { type: 'string', minLength: 1 },
|
|
@@ -79,30 +71,8 @@ const DOCKER_IMAGE_PATTERN =
|
|
|
79
71
|
},
|
|
80
72
|
default: []
|
|
81
73
|
},
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
image: { type: 'string', maxLength: 255, pattern: DOCKER_IMAGE_PATTERN },
|
|
85
|
-
// A repository the Client clones before running the command — at `ref`
|
|
86
|
-
// (branch, tag or commit; default: the default branch), `depth` commits
|
|
87
|
-
// deep (0 = full history). `--git-repo <url> [ref] [--depth <n>]`.
|
|
88
|
-
git: {
|
|
89
|
-
type: 'object',
|
|
90
|
-
additionalProperties: false,
|
|
91
|
-
required: ['url'],
|
|
92
|
-
properties: {
|
|
93
|
-
url: { type: 'string', minLength: 1, maxLength: 2048 },
|
|
94
|
-
ref: { type: 'string', maxLength: 255, pattern: GIT_REF_PATTERN },
|
|
95
|
-
depth: { type: 'integer', minimum: 0, maximum: 100000, default: 1 },
|
|
96
|
-
// Extra git options (`--git-options`), shell-quoted, inserted
|
|
97
|
-
// between `git` and its subcommand on the Client — e.g.
|
|
98
|
-
// -c core.sshCommand="ssh -i ~/.ssh/lab_key -p 2222". Stored with
|
|
99
|
-
// the job and visible like the rest of it: reference key files on
|
|
100
|
-
// the Client rather than putting secrets here.
|
|
101
|
-
options: { type: 'string', minLength: 1, maxLength: 1024 }
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
// Environment variables (`--env NAME=value`) the Client sets for every
|
|
105
|
-
// command it runs for the job — git and the job's command. No name
|
|
74
|
+
// Environment variables (`--env NAME=value`) the Client sets for the
|
|
75
|
+
// job's command — tokens for a git clone or docker login, say. No name
|
|
106
76
|
// means anything special. Values are secrets as far as the Coordinator
|
|
107
77
|
// is concerned: masked in the Agent API and dropped from its database
|
|
108
78
|
// once the job ends.
|
|
@@ -129,4 +99,4 @@ const DOCKER_IMAGE_PATTERN =
|
|
|
129
99
|
}
|
|
130
100
|
};
|
|
131
101
|
|
|
132
|
-
module.exports = { jobSpecSchema,
|
|
102
|
+
module.exports = { jobSpecSchema, ENV_NAME_PATTERN };
|
package/src/split-args.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file packages/shared/src/split-args.js
|
|
3
3
|
* @description Splits a command-line options string into arguments the way a POSIX shell would quote them —
|
|
4
|
-
* without running a shell (for
|
|
4
|
+
* without running a shell (kept for callers of thub-common; TestHub itself no longer uses it)
|
|
5
5
|
*
|
|
6
6
|
* @author Andrian Yablonskyy
|
|
7
7
|
* @copyright Copyright (c) 2026 Andrian Yablonskyy. All rights reserved.
|
package/src/validate-job-spec.js
CHANGED
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
const Ajv = require('ajv'),
|
|
17
17
|
addFormats = require('ajv-formats'),
|
|
18
|
-
{ jobSpecSchema } = require('./job-spec.schema')
|
|
19
|
-
{ splitArgs } = require('./split-args');
|
|
18
|
+
{ jobSpecSchema } = require('./job-spec.schema');
|
|
20
19
|
|
|
21
20
|
const ajv = new Ajv({ useDefaults: true, allErrors: true, strict: false });
|
|
22
21
|
addFormats(ajv);
|
|
@@ -27,7 +26,7 @@ const validateFn = ajv.compile(jobSpecSchema);
|
|
|
27
26
|
* Returns { valid, spec, errors }.
|
|
28
27
|
*/
|
|
29
28
|
function validateJobSpec(spec){
|
|
30
|
-
const legacy = legacyShapeError(spec);
|
|
29
|
+
const legacy = legacyShapeError(spec) || removedFieldsError(spec);
|
|
31
30
|
if (legacy){
|
|
32
31
|
return { valid: false, spec: JSON.parse(JSON.stringify(spec ?? {})), errors: [legacy] };
|
|
33
32
|
}
|
|
@@ -44,42 +43,31 @@ function validateJobSpec(spec){
|
|
|
44
43
|
function legacyShapeError(spec){
|
|
45
44
|
if (spec && typeof spec === 'object' && ('firmware' in spec || 'tests' in spec) && !('command' in spec)){
|
|
46
45
|
return '/ this job spec is from an older Agent (firmware/tests fields) — update the Agent (thub self-update) ' +
|
|
47
|
-
'and use --command
|
|
46
|
+
'and use --command and --download-file';
|
|
48
47
|
}
|
|
49
48
|
return null;
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
51
|
+
// `git` (--git-repo/--depth/--git-options) and `image` (--docker-image) are
|
|
52
|
+
// gone: the Client no longer clones or runs containers itself. Said plainly
|
|
53
|
+
// rather than as unknown properties.
|
|
54
|
+
function removedFieldsError(spec){
|
|
55
|
+
const removed = ['git', 'image'].filter((k) => spec && typeof spec === 'object' && spec[k] !== undefined);
|
|
56
|
+
if (!removed.length){
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
return `/ ${removed.join(', ')}: --git-repo, --git-options, --depth and --docker-image are no longer supported — ` +
|
|
60
|
+
'clone the repository or run docker in --command, passing credentials with --env (update the Agent: thub self-update)';
|
|
61
|
+
}
|
|
60
62
|
|
|
61
63
|
// Rules across fields, spelled out here rather than as schema if/then,
|
|
62
64
|
// whose errors ("must match a schema in then") say little.
|
|
63
65
|
function crossFieldErrors(spec){
|
|
64
|
-
const errors = []
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
if (spec.git && !GIT_URL.test(spec.git.url || '')){
|
|
69
|
-
errors.push('/git/url must be an https://, http://, ssh:// or git:// URL, or user@host:path');
|
|
70
|
-
}
|
|
71
|
-
const envNames = Object.keys(spec.env && typeof spec.env === 'object' ? spec.env : {}),
|
|
72
|
-
reserved = envNames.filter((n) => /^(THUB|JOB)_/.test(n) || RESERVED_ENV.includes(n));
|
|
66
|
+
const errors = [],
|
|
67
|
+
envNames = Object.keys(spec.env && typeof spec.env === 'object' ? spec.env : {}),
|
|
68
|
+
reserved = envNames.filter((n) => /^(THUB|JOB)_/.test(n));
|
|
73
69
|
if (reserved.length){
|
|
74
|
-
errors.push(`/env ${reserved.join(', ')}: set by the Client itself (THUB_*, JOB_
|
|
75
|
-
}
|
|
76
|
-
if (spec.git?.options){
|
|
77
|
-
try {
|
|
78
|
-
splitArgs(spec.git.options);
|
|
79
|
-
}
|
|
80
|
-
catch (err){
|
|
81
|
-
errors.push(`/git/options can't be split into arguments: ${err.message}`);
|
|
82
|
-
}
|
|
70
|
+
errors.push(`/env ${reserved.join(', ')}: set by the Client itself (THUB_*, JOB_*) — use other names`);
|
|
83
71
|
}
|
|
84
72
|
return errors;
|
|
85
73
|
}
|