datagrok-tools 6.0.8 → 6.1.1
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/.devcontainer/entrypoint.sh +0 -0
- package/bin/commands/config.js +42 -6
- package/bin/commands/docker-gen.js +16 -0
- package/bin/commands/help.js +30 -15
- package/bin/commands/publish.js +275 -15
- package/bin/commands/test.js +545 -12
- package/bin/grok.js +2 -0
- package/bin/utils/python-celery-gen.js +247 -0
- package/bin/utils/test-utils.js +39 -17
- package/bin/utils/utils.js +1 -1
- package/config-template.yaml +2 -0
- package/package.json +1 -1
|
File without changes
|
package/bin/commands/config.js
CHANGED
|
@@ -19,6 +19,15 @@ const confTemplate = _jsYaml.default.load(_fs.default.readFileSync(confTemplateD
|
|
|
19
19
|
}));
|
|
20
20
|
const grokDir = _path.default.join(_os.default.homedir(), '.grok');
|
|
21
21
|
const confPath = _path.default.join(grokDir, 'config.yaml');
|
|
22
|
+
function defaultRegistry(url) {
|
|
23
|
+
try {
|
|
24
|
+
const hostname = new URL(url).hostname;
|
|
25
|
+
if (hostname === 'localhost' || /^[\d.]+$/.test(hostname) || hostname === '::1') return '';
|
|
26
|
+
return `registry.${hostname}`;
|
|
27
|
+
} catch (e) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
22
31
|
function validateKey(key) {
|
|
23
32
|
if (!key || /^([A-Za-z\d-])+$/.test(key)) return true;else return 'Developer key may only include letters, numbers, or hyphens';
|
|
24
33
|
}
|
|
@@ -33,7 +42,7 @@ function generateKeyQ(server, url) {
|
|
|
33
42
|
if (server.startsWith('local')) question.message = `Developer key for ${origin} (press ENTER to skip):`;
|
|
34
43
|
return question;
|
|
35
44
|
}
|
|
36
|
-
async function addNewServer(config) {
|
|
45
|
+
async function addNewServer(config, askRegistry = false) {
|
|
37
46
|
while (true) {
|
|
38
47
|
const addServer = (await _inquirer.default.prompt({
|
|
39
48
|
name: 'add-server',
|
|
@@ -53,17 +62,29 @@ async function addNewServer(config) {
|
|
|
53
62
|
message: 'Enter a URL:'
|
|
54
63
|
}))['server-url'];
|
|
55
64
|
const key = (await _inquirer.default.prompt(generateKeyQ(name, url)))[name];
|
|
56
|
-
|
|
65
|
+
const entry = {
|
|
57
66
|
url,
|
|
58
67
|
key
|
|
59
68
|
};
|
|
69
|
+
if (askRegistry) {
|
|
70
|
+
const regDefault = defaultRegistry(url);
|
|
71
|
+
const reg = (await _inquirer.default.prompt({
|
|
72
|
+
name: 'registry',
|
|
73
|
+
type: 'input',
|
|
74
|
+
message: `Docker registry for ${name}:`,
|
|
75
|
+
default: regDefault || undefined
|
|
76
|
+
}))['registry'] || '';
|
|
77
|
+
if (reg) entry.registry = reg;
|
|
78
|
+
}
|
|
79
|
+
config.servers[name] = entry;
|
|
60
80
|
} else break;
|
|
61
81
|
}
|
|
62
82
|
}
|
|
63
83
|
function config(args) {
|
|
64
84
|
const nOptions = Object.keys(args).length - 1;
|
|
65
|
-
const
|
|
66
|
-
const
|
|
85
|
+
const askRegistry = args.registry != null;
|
|
86
|
+
const interactiveMode = args['_'].length === 1 && (nOptions < 1 || nOptions === 1 && (args.reset || askRegistry) || nOptions === 2 && args.reset && askRegistry);
|
|
87
|
+
const hasAddServerCommand = args['_'].length === 2 && args['_'][1] === 'add' && args.server && args.key && args.k && args.alias && nOptions >= 4 && nOptions <= 6;
|
|
67
88
|
if (!interactiveMode && !hasAddServerCommand) return false;
|
|
68
89
|
if (!_fs.default.existsSync(grokDir)) _fs.default.mkdirSync(grokDir);
|
|
69
90
|
if (!_fs.default.existsSync(confPath) || args.reset) _fs.default.writeFileSync(confPath, _jsYaml.default.dump(confTemplate));
|
|
@@ -77,10 +98,15 @@ function config(args) {
|
|
|
77
98
|
color.error('URL parsing error. Please, provide a valid server URL.');
|
|
78
99
|
return false;
|
|
79
100
|
}
|
|
80
|
-
|
|
101
|
+
const server = {
|
|
81
102
|
url: args.server,
|
|
82
103
|
key: args.key
|
|
83
104
|
};
|
|
105
|
+
if (args.registry != null) {
|
|
106
|
+
const registry = typeof args.registry === 'string' ? args.registry : defaultRegistry(args.server);
|
|
107
|
+
if (registry) server.registry = registry;
|
|
108
|
+
}
|
|
109
|
+
config.servers[args.alias] = server;
|
|
84
110
|
color.success(`Successfully added the server to ${confPath}.`);
|
|
85
111
|
console.log(`Use this command to deploy packages: grok publish ${args.alias}`);
|
|
86
112
|
if (args.default) config.default = args.alias;
|
|
@@ -110,8 +136,18 @@ function config(args) {
|
|
|
110
136
|
question.default = config['servers'][server]['key'];
|
|
111
137
|
const devKey = await _inquirer.default.prompt(question);
|
|
112
138
|
config['servers'][server]['key'] = devKey[server];
|
|
139
|
+
if (askRegistry) {
|
|
140
|
+
const regDefault = config['servers'][server]['registry'] || defaultRegistry(url);
|
|
141
|
+
const reg = (await _inquirer.default.prompt({
|
|
142
|
+
name: 'registry',
|
|
143
|
+
type: 'input',
|
|
144
|
+
message: `Docker registry for ${server}:`,
|
|
145
|
+
default: regDefault || undefined
|
|
146
|
+
}))['registry'] || '';
|
|
147
|
+
if (reg) config['servers'][server]['registry'] = reg;else delete config['servers'][server]['registry'];
|
|
148
|
+
}
|
|
113
149
|
}
|
|
114
|
-
await addNewServer(config);
|
|
150
|
+
await addNewServer(config, askRegistry);
|
|
115
151
|
const defaultServer = await _inquirer.default.prompt({
|
|
116
152
|
name: 'default-server',
|
|
117
153
|
type: 'input',
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.dockerGen = dockerGen;
|
|
7
|
+
var _pythonCeleryGen = require("../utils/python-celery-gen");
|
|
8
|
+
var color = _interopRequireWildcard(require("../utils/color-utils"));
|
|
9
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
10
|
+
async function dockerGen(args) {
|
|
11
|
+
color.setVerbose(args.verbose || args.v || false);
|
|
12
|
+
const packageDir = process.cwd();
|
|
13
|
+
const generated = (0, _pythonCeleryGen.generateCeleryArtifacts)(packageDir);
|
|
14
|
+
if (generated) color.success('Generated Celery Docker artifacts');else console.log('No python/ directory with annotated functions found');
|
|
15
|
+
return true;
|
|
16
|
+
}
|
package/bin/commands/help.js
CHANGED
|
@@ -10,19 +10,20 @@ Usage: grok <command>
|
|
|
10
10
|
Datagrok's package management tool
|
|
11
11
|
|
|
12
12
|
Commands:
|
|
13
|
-
add
|
|
14
|
-
api
|
|
15
|
-
build
|
|
16
|
-
check
|
|
17
|
-
claude
|
|
18
|
-
config
|
|
19
|
-
create
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
add Add an object template
|
|
14
|
+
api Create wrapper functions
|
|
15
|
+
build Build a package or multiple packages
|
|
16
|
+
check Check package content (function signatures, etc.)
|
|
17
|
+
claude Launch Claude Code in a Datagrok dev container
|
|
18
|
+
config Create and manage config files
|
|
19
|
+
create Create a package
|
|
20
|
+
docker-gen Generate Celery Docker artifacts from Python functions
|
|
21
|
+
init Modify a package template
|
|
22
|
+
link Link \`datagrok-api\` and libraries for local development
|
|
23
|
+
publish Upload a package
|
|
24
|
+
test Run package tests
|
|
25
|
+
testall Run packages tests
|
|
26
|
+
migrate Migrate legacy tags to meta.role
|
|
26
27
|
|
|
27
28
|
To get help on a particular command, use:
|
|
28
29
|
grok <command> --help
|
|
@@ -122,13 +123,14 @@ Usage: grok config
|
|
|
122
123
|
Create or update a configuration file
|
|
123
124
|
|
|
124
125
|
Options:
|
|
125
|
-
[--reset] [--server] [--alias] [-k | --key]
|
|
126
|
+
[--reset] [--server] [--alias] [-k | --key] [--registry]
|
|
126
127
|
|
|
127
128
|
--reset Restore the default config file template
|
|
128
129
|
--server Use to add a server to the config (\`grok config add --alias alias --server url --key key\`)
|
|
129
130
|
--alias Use in conjunction with the \`server\` option to set the server name
|
|
130
131
|
--key Use in conjunction with the \`server\` option to set the developer key
|
|
131
132
|
--default Use in conjunction with the \`server\` option to set the added server as default
|
|
133
|
+
--registry Docker registry URL (default: registry.{server hostname})
|
|
132
134
|
`;
|
|
133
135
|
const HELP_CREATE = `
|
|
134
136
|
Usage: grok create [name]
|
|
@@ -156,13 +158,14 @@ Uploads a package
|
|
|
156
158
|
Checks for errors before publishing — the package won't be published if there are any.
|
|
157
159
|
|
|
158
160
|
Options:
|
|
159
|
-
[--all] [--refresh] [--link] [--build] [--release] [--skip-check] [-v | --verbose]
|
|
161
|
+
[--all] [--refresh] [--link] [--build] [--release] [--rebuild-docker] [--skip-check] [-v | --verbose]
|
|
160
162
|
|
|
161
163
|
--all Publish all available packages (run in packages directory)
|
|
162
164
|
--refresh Publish all available already loaded packages (run in packages directory)
|
|
163
165
|
--link Link the package to local packages
|
|
164
166
|
--build Builds the package
|
|
165
167
|
--release Publish package as release version
|
|
168
|
+
--rebuild-docker Force rebuild Docker images locally before pushing to registry
|
|
166
169
|
--skip-check Skip check stage
|
|
167
170
|
--verbose Print detailed output
|
|
168
171
|
|
|
@@ -266,6 +269,17 @@ Options:
|
|
|
266
269
|
--verbose Prints detailed information about linked packages
|
|
267
270
|
--all Links all available packages(run in packages directory)
|
|
268
271
|
`;
|
|
272
|
+
const HELP_DOCKER_GEN = `
|
|
273
|
+
Usage: grok docker-gen
|
|
274
|
+
|
|
275
|
+
Generate Celery Docker artifacts from annotated Python functions in the python/ directory.
|
|
276
|
+
Produces Dockerfile, tasks.yaml, and Celery entry point in dockerfiles/<name>/.
|
|
277
|
+
|
|
278
|
+
Options:
|
|
279
|
+
[-v | --verbose]
|
|
280
|
+
|
|
281
|
+
--verbose Print detailed output
|
|
282
|
+
`;
|
|
269
283
|
const HELP_MIGRATE = `
|
|
270
284
|
Usage: grok migrate
|
|
271
285
|
|
|
@@ -314,6 +328,7 @@ const help = exports.help = {
|
|
|
314
328
|
claude: HELP_CLAUDE,
|
|
315
329
|
config: HELP_CONFIG,
|
|
316
330
|
create: HELP_CREATE,
|
|
331
|
+
'docker-gen': HELP_DOCKER_GEN,
|
|
317
332
|
init: HELP_INIT,
|
|
318
333
|
link: HELP_LINK,
|
|
319
334
|
publish: HELP_PUBLISH,
|
package/bin/commands/publish.js
CHANGED
|
@@ -17,13 +17,15 @@ var _testUtils = require("../utils/test-utils");
|
|
|
17
17
|
var utils = _interopRequireWildcard(require("../utils/utils"));
|
|
18
18
|
var color = _interopRequireWildcard(require("../utils/color-utils"));
|
|
19
19
|
var _check = require("./check");
|
|
20
|
+
var _pythonCeleryGen = require("../utils/python-celery-gen");
|
|
20
21
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
21
22
|
// @ts-ignore
|
|
22
23
|
|
|
23
24
|
// @ts-ignore
|
|
24
25
|
|
|
25
26
|
const {
|
|
26
|
-
exec
|
|
27
|
+
exec,
|
|
28
|
+
execSync
|
|
27
29
|
} = require('child_process');
|
|
28
30
|
const grokDir = _path.default.join(_os.default.homedir(), '.grok');
|
|
29
31
|
const confPath = _path.default.join(grokDir, 'config.yaml');
|
|
@@ -35,22 +37,263 @@ let curDir = process.cwd();
|
|
|
35
37
|
const packDir = _path.default.join(curDir, 'package.json');
|
|
36
38
|
const packageFiles = ['src/package.ts', 'src/detectors.ts', 'src/package.js', 'src/detectors.js', 'src/package-test.ts', 'src/package-test.js', 'package.js', 'detectors.js'];
|
|
37
39
|
let config;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
+
const BLEEDING_EDGE_TAG = 'bleeding-edge';
|
|
41
|
+
function discoverDockerfiles(packageName, version, debug) {
|
|
42
|
+
const dockerfilesDir = _path.default.join(curDir, 'dockerfiles');
|
|
43
|
+
if (!_fs.default.existsSync(dockerfilesDir)) return [];
|
|
44
|
+
const results = [];
|
|
45
|
+
const entries = _fs.default.readdirSync(dockerfilesDir, {
|
|
46
|
+
withFileTypes: true
|
|
47
|
+
});
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (!entry.isDirectory()) continue;
|
|
50
|
+
const dockerfilePath = _path.default.join(dockerfilesDir, entry.name, 'Dockerfile');
|
|
51
|
+
if (!_fs.default.existsSync(dockerfilePath)) continue;
|
|
52
|
+
const cleanName = utils.removeScope(packageName).replace(/-/g, '').toLowerCase();
|
|
53
|
+
const imageName = `${cleanName}-${entry.name.toLowerCase()}`;
|
|
54
|
+
const imageTag = debug ? version : `${version}.X`;
|
|
55
|
+
results.push({
|
|
56
|
+
imageName,
|
|
57
|
+
imageTag,
|
|
58
|
+
dirName: entry.name,
|
|
59
|
+
fullLocalName: `${imageName}:${imageTag}`
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return results;
|
|
63
|
+
}
|
|
64
|
+
function dockerCommand(args) {
|
|
65
|
+
return execSync(`docker ${args}`, {
|
|
66
|
+
encoding: 'utf-8',
|
|
67
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
68
|
+
}).trim();
|
|
69
|
+
}
|
|
70
|
+
function localImageExists(fullName) {
|
|
71
|
+
try {
|
|
72
|
+
const output = dockerCommand(`images --format "{{.Repository}}:{{.Tag}}"`);
|
|
73
|
+
return output.split('\n').some(line => line.trim() === fullName);
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function dockerLogin(registry, devKey) {
|
|
79
|
+
try {
|
|
80
|
+
dockerCommand(`login ${registry} -u ${devKey} -p ${devKey}`);
|
|
81
|
+
return true;
|
|
82
|
+
} catch (e) {
|
|
83
|
+
color.warn(`Docker login to ${registry} failed: ${e.message || e}`);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function dockerTag(source, target) {
|
|
88
|
+
try {
|
|
89
|
+
dockerCommand(`tag ${source} ${target}`);
|
|
90
|
+
return true;
|
|
91
|
+
} catch (e) {
|
|
92
|
+
color.error(`Failed to tag ${source} as ${target}: ${e.message || e}`);
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function dockerPush(image) {
|
|
97
|
+
try {
|
|
98
|
+
dockerCommand(`push ${image}`);
|
|
99
|
+
return true;
|
|
100
|
+
} catch (e) {
|
|
101
|
+
color.error(`Failed to push ${image}: ${e.message || e}`);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function dockerBuild(imageName, dockerfilePath, context) {
|
|
106
|
+
try {
|
|
107
|
+
color.log(`Building Docker image ${imageName}...`);
|
|
108
|
+
execSync(`docker build -t ${imageName} -f ${dockerfilePath} ${context}`, {
|
|
109
|
+
encoding: 'utf-8',
|
|
110
|
+
stdio: 'inherit'
|
|
111
|
+
});
|
|
112
|
+
return true;
|
|
113
|
+
} catch (e) {
|
|
114
|
+
color.error(`Failed to build ${imageName}: ${e.message || e}`);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function getUserLogin(host, devKey) {
|
|
119
|
+
let loginResp;
|
|
120
|
+
try {
|
|
121
|
+
loginResp = await (0, _nodeFetch.default)(`${host}/users/login/dev/${devKey}`, {
|
|
122
|
+
method: 'POST'
|
|
123
|
+
});
|
|
124
|
+
} catch (e) {
|
|
125
|
+
color.warn(`Cannot reach server ${host}: ${e.message || e}`);
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (loginResp.status !== 200) return null;
|
|
129
|
+
const loginData = await loginResp.json();
|
|
130
|
+
const token = loginData.token;
|
|
131
|
+
try {
|
|
132
|
+
const userResp = await (0, _nodeFetch.default)(`${host}/users/current`, {
|
|
133
|
+
headers: {
|
|
134
|
+
'Authorization': token
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
if (userResp.status !== 200) return null;
|
|
138
|
+
const userData = await userResp.json();
|
|
139
|
+
return {
|
|
140
|
+
login: userData.login,
|
|
141
|
+
token
|
|
142
|
+
};
|
|
143
|
+
} catch (e) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function resolveLatestCompatible(host, devKey, dockerName) {
|
|
148
|
+
const userInfo = await getUserLogin(host, devKey);
|
|
149
|
+
if (!userInfo) return {
|
|
150
|
+
found: null,
|
|
151
|
+
serverError: `Authentication failed. Check your developer key.`
|
|
152
|
+
};
|
|
153
|
+
try {
|
|
154
|
+
const url = `${host}/docker/images/${encodeURIComponent(dockerName)}/latest-compatible`;
|
|
155
|
+
const resp = await (0, _nodeFetch.default)(url, {
|
|
156
|
+
headers: {
|
|
157
|
+
'Authorization': userInfo.token
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
if (resp.status === 200) return {
|
|
161
|
+
found: await resp.json(),
|
|
162
|
+
serverError: null
|
|
163
|
+
};
|
|
164
|
+
if (resp.status === 404) return {
|
|
165
|
+
found: null,
|
|
166
|
+
serverError: null
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
found: null,
|
|
170
|
+
serverError: `Unexpected response (HTTP ${resp.status}) from latest-compatible endpoint`
|
|
171
|
+
};
|
|
172
|
+
} catch (e) {
|
|
173
|
+
return {
|
|
174
|
+
found: null,
|
|
175
|
+
serverError: `Failed to query latest-compatible: ${e.message || e}`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function processDockerImages(packageName, version, registry, devKey, host, rebuildDocker, zip, localTimestamps, debug) {
|
|
180
|
+
const dockerImages = discoverDockerfiles(packageName, version, debug);
|
|
181
|
+
if (dockerImages.length === 0) return;
|
|
182
|
+
color.log(`Found ${dockerImages.length} Dockerfile(s)`);
|
|
183
|
+
if (registry) dockerLogin(registry, devKey);
|
|
184
|
+
for (const img of dockerImages) {
|
|
185
|
+
let result;
|
|
186
|
+
if (rebuildDocker) {
|
|
187
|
+
const dockerfileDir = _path.default.join('dockerfiles', img.dirName);
|
|
188
|
+
const dockerfilePath = _path.default.join(dockerfileDir, 'Dockerfile');
|
|
189
|
+
if (dockerBuild(img.fullLocalName, dockerfilePath, dockerfileDir)) {
|
|
190
|
+
result = pushImage(img, registry);
|
|
191
|
+
color.success(`Built and tagged ${img.fullLocalName}`);
|
|
192
|
+
} else {
|
|
193
|
+
result = await fallbackImage(img, host, devKey, registry);
|
|
194
|
+
}
|
|
195
|
+
} else if (localImageExists(img.fullLocalName)) {
|
|
196
|
+
result = pushImage(img, registry);
|
|
197
|
+
color.success(`Found local image ${img.fullLocalName}`);
|
|
198
|
+
} else {
|
|
199
|
+
color.warn(`Local image not found. Expected: ${img.fullLocalName}`);
|
|
200
|
+
color.log(` Build it with: docker build -t ${img.fullLocalName} -f dockerfiles/${img.dirName}/Dockerfile dockerfiles/${img.dirName}`);
|
|
201
|
+
const fallback = await fallbackImage(img, host, devKey, registry);
|
|
202
|
+
if (fallback.serverError) {
|
|
203
|
+
color.error(`Cannot resolve fallback: ${fallback.serverError}`);
|
|
204
|
+
result = {
|
|
205
|
+
image: null,
|
|
206
|
+
fallback: true,
|
|
207
|
+
requestedVersion: img.imageTag
|
|
208
|
+
};
|
|
209
|
+
} else if (fallback.image) {
|
|
210
|
+
result = fallback;
|
|
211
|
+
color.warn(`Falling back to ${fallback.image}`);
|
|
212
|
+
} else {
|
|
213
|
+
// No fallback and no local image — must build
|
|
214
|
+
color.warn(`No fallback available. Building ${img.fullLocalName}...`);
|
|
215
|
+
const dockerfileDir = _path.default.join('dockerfiles', img.dirName);
|
|
216
|
+
const dockerfilePath = _path.default.join(dockerfileDir, 'Dockerfile');
|
|
217
|
+
if (dockerBuild(img.fullLocalName, dockerfilePath, dockerfileDir)) {
|
|
218
|
+
result = pushImage(img, registry);
|
|
219
|
+
color.success(`Built and tagged ${img.fullLocalName}`);
|
|
220
|
+
} else {
|
|
221
|
+
result = {
|
|
222
|
+
image: null,
|
|
223
|
+
fallback: true,
|
|
224
|
+
requestedVersion: img.imageTag
|
|
225
|
+
};
|
|
226
|
+
color.error(`Failed to build ${img.fullLocalName}. No container will be available.`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const imageJsonPath = _path.default.join('dockerfiles', img.dirName, 'image.json');
|
|
231
|
+
zip.append(JSON.stringify(result, null, 2), {
|
|
232
|
+
name: imageJsonPath
|
|
233
|
+
});
|
|
234
|
+
localTimestamps[imageJsonPath.replace(/\\/g, '/')] = new Date().toUTCString();
|
|
235
|
+
color.log(`Added ${imageJsonPath}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function pushImage(img, registry) {
|
|
239
|
+
const canonicalImage = `datagrok/${img.fullLocalName}`;
|
|
240
|
+
if (registry) {
|
|
241
|
+
const remoteTag = `${registry}/${canonicalImage}`;
|
|
242
|
+
dockerTag(img.fullLocalName, remoteTag);
|
|
243
|
+
if (dockerPush(remoteTag)) return {
|
|
244
|
+
image: canonicalImage,
|
|
245
|
+
fallback: false
|
|
246
|
+
};
|
|
247
|
+
color.warn(`Push failed, image tagged locally only: ${remoteTag}`);
|
|
248
|
+
return {
|
|
249
|
+
image: canonicalImage,
|
|
250
|
+
fallback: false
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
color.warn('No registry configured. Image tagged locally only.');
|
|
254
|
+
return {
|
|
255
|
+
image: canonicalImage,
|
|
256
|
+
fallback: false
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
async function fallbackImage(img, host, devKey, registry) {
|
|
260
|
+
const {
|
|
261
|
+
found,
|
|
262
|
+
serverError
|
|
263
|
+
} = await resolveLatestCompatible(host, devKey, img.imageName);
|
|
264
|
+
if (serverError) return {
|
|
265
|
+
image: null,
|
|
266
|
+
fallback: true,
|
|
267
|
+
requestedVersion: img.imageTag,
|
|
268
|
+
serverError
|
|
269
|
+
};
|
|
270
|
+
if (found) return {
|
|
271
|
+
image: found.image,
|
|
272
|
+
fallback: true,
|
|
273
|
+
requestedVersion: img.imageTag
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
image: null,
|
|
277
|
+
fallback: true,
|
|
278
|
+
requestedVersion: img.imageTag
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
async function processPackage(debug, rebuild, host, devKey, packageName, dropDb, suffix, hostAlias, registry, rebuildDocker) {
|
|
282
|
+
// Validate server connectivity and dev key
|
|
40
283
|
let timestamps = {};
|
|
41
284
|
let url = `${host}/packages/dev/${devKey}/${packageName}`;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return 1;
|
|
48
|
-
}
|
|
49
|
-
} catch (error) {
|
|
50
|
-
if (utils.isConnectivityError(error)) color.error(`Server is possibly offline: ${host}`);
|
|
51
|
-
if (color.isVerbose()) console.error(error);
|
|
285
|
+
try {
|
|
286
|
+
const checkResp = await (0, _nodeFetch.default)(url + '/timestamps');
|
|
287
|
+
const checkData = await checkResp.json();
|
|
288
|
+
if (checkData['#type'] === 'ApiError') {
|
|
289
|
+
color.error(checkData.message);
|
|
52
290
|
return 1;
|
|
53
291
|
}
|
|
292
|
+
if (debug) timestamps = checkData;
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (utils.isConnectivityError(error)) color.error(`Server is possibly offline: ${host}`);
|
|
295
|
+
if (color.isVerbose()) console.error(error);
|
|
296
|
+
return 1;
|
|
54
297
|
}
|
|
55
298
|
const zip = (0, _archiverPromise.default)('zip', {
|
|
56
299
|
store: false
|
|
@@ -147,6 +390,17 @@ async function processPackage(debug, rebuild, host, devKey, packageName, dropDb,
|
|
|
147
390
|
errs.forEach(e => color.error(e));
|
|
148
391
|
return 1;
|
|
149
392
|
}
|
|
393
|
+
|
|
394
|
+
// Generate Celery Docker artifacts from python/ if present
|
|
395
|
+
(0, _pythonCeleryGen.generateCeleryArtifacts)(curDir);
|
|
396
|
+
|
|
397
|
+
// Process Docker images and inject image.json into the ZIP
|
|
398
|
+
let dockerVersion = json.version;
|
|
399
|
+
if (debug) {
|
|
400
|
+
const userInfo = await getUserLogin(host, devKey);
|
|
401
|
+
if (userInfo) dockerVersion = userInfo.login;
|
|
402
|
+
}
|
|
403
|
+
await processDockerImages(packageName, dockerVersion, registry, devKey, host, rebuildDocker ?? false, zip, localTimestamps, debug);
|
|
150
404
|
zip.append(JSON.stringify(localTimestamps), {
|
|
151
405
|
name: 'timestamps.json'
|
|
152
406
|
});
|
|
@@ -245,16 +499,22 @@ async function publishPackage(args) {
|
|
|
245
499
|
if (nArgs === 2) host = args['_'][1];
|
|
246
500
|
let key = '';
|
|
247
501
|
let url = '';
|
|
502
|
+
let registry;
|
|
248
503
|
|
|
249
504
|
// The host can be passed either as a URL or an alias
|
|
250
505
|
try {
|
|
251
506
|
url = new URL(host).href;
|
|
252
507
|
if (url.endsWith('/')) url = url.slice(0, -1);
|
|
253
|
-
if (url in urls)
|
|
508
|
+
if (url in urls) {
|
|
509
|
+
const alias = urls[url];
|
|
510
|
+
key = config['servers'][alias]['key'];
|
|
511
|
+
registry = config['servers'][alias]['registry'];
|
|
512
|
+
}
|
|
254
513
|
} catch (error) {
|
|
255
514
|
if (!(host in config.servers)) return color.error(`Unknown server alias. Please add it to ${confPath}`);
|
|
256
515
|
url = config['servers'][host]['url'];
|
|
257
516
|
key = config['servers'][host]['key'];
|
|
517
|
+
registry = config['servers'][host]['registry'];
|
|
258
518
|
}
|
|
259
519
|
|
|
260
520
|
// Update the developer key
|
|
@@ -285,7 +545,7 @@ async function publishPackage(args) {
|
|
|
285
545
|
if (!args.suffix && stdout) args.suffix = stdout.toString().substring(0, 8);
|
|
286
546
|
});
|
|
287
547
|
await utils.delay(100);
|
|
288
|
-
code = await processPackage(!args.release, Boolean(args.rebuild), url, key, packageName, args.dropDb ?? false, args.suffix, host);
|
|
548
|
+
code = await processPackage(!args.release, Boolean(args.rebuild), url, key, packageName, args.dropDb ?? false, args.suffix, host, registry, args['rebuild-docker']);
|
|
289
549
|
} catch (error) {
|
|
290
550
|
console.error(error);
|
|
291
551
|
code = 1;
|