@zhuoyuezs/ml-platform 0.2.1 → 0.2.2-alpha.0
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/DEVELOPMENT.md +23 -5
- package/README.md +6 -3
- package/checksums.json +12 -12
- package/package.json +9 -3
- package/release.json +6 -6
- package/runtime/business-client/package-lock.json +35 -0
- package/runtime/business-client/package.json +7 -2
- package/runtime/business-client/src/catalog.js +471 -183
- package/runtime/business-client/src/cli.js +886 -622
- package/runtime/business-client/src/config.js +48 -41
- package/runtime/business-client/src/http.js +294 -224
- package/scripts/lib.js +1326 -1019
- package/scripts/main.js +138 -125
- package/scripts/verify-release-package.js +15 -19
|
@@ -1,679 +1,943 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
2
|
"use strict";
|
|
4
|
-
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
4
|
const fs = require("fs");
|
|
6
5
|
const path = require("path");
|
|
7
6
|
const { configPath, expandHome, loadApiUrl, normalizeApiUrl, saveApiUrl } = require("./config");
|
|
8
7
|
const { PlatformApiClient } = require("./http");
|
|
9
8
|
const { applyCatalog, loadCatalog, normalizeDataset, normalizeRegistryDataset } = require("./catalog");
|
|
10
9
|
const { version: CLIENT_VERSION } = require("../package.json");
|
|
11
|
-
|
|
12
10
|
const LIFECYCLE_COLLECTIONS = new Set(["training-runs", "training-jobs", "model-artifacts", "evaluation-configs", "evaluation-runs", "evaluation-jobs", "metric-definitions", "executable-packages", "model-packages"]);
|
|
13
|
-
|
|
14
11
|
const BUSINESS_COMMANDS = new Set([
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
12
|
+
...[...LIFECYCLE_COLLECTIONS].map((name) => `list-${name}`),
|
|
13
|
+
"get-model-runtime-release", "get-training-run", "get-model-artifact", "get-executable-package",
|
|
14
|
+
"validate-training-job", "validate-model-package",
|
|
15
|
+
"version", "create-project", "list-projects", "get-project", "delete-project",
|
|
16
|
+
"configure", "show-config", "health", "list-parameters", "list-operators", "list-features",
|
|
17
|
+
"list-feature-sets", "list-datasets", "get-dataset", "list-jobs", "resolve-manifest",
|
|
18
|
+
"resolve-dataset", "build-dataset", "build-registered-dataset",
|
|
19
|
+
"get-parameter", "get-feature", "get-feature-set", "get-operator",
|
|
20
|
+
"delete-parameter", "delete-feature", "delete-feature-set",
|
|
21
|
+
"restore-parameter", "restore-feature", "restore-feature-set",
|
|
22
|
+
"get-parameter-lifecycle", "get-feature-lifecycle", "get-feature-set-lifecycle",
|
|
23
|
+
"list-parameter-references", "list-feature-references", "list-feature-set-references",
|
|
24
|
+
"list-parameter-versions", "list-feature-versions", "list-feature-set-versions", "list-operator-versions",
|
|
25
|
+
"add-parameter", "add-feature", "add-feature-set", "add-operator", "publish-operator",
|
|
26
|
+
"add-dataset", "update-dataset", "delete-dataset", "list-dataset-artifacts",
|
|
27
|
+
"register-operator", "show-operator-specs", "delete-operator", "run-operator",
|
|
28
|
+
"get-job", "wait-job", "retry-job", "cancel-job", "get-dataset-artifact", "download-dataset-artifact", "fetch-inference-data", "fetch-inference-context", "predict-model", "apply",
|
|
29
|
+
"list-system-models", "get-system-model", "list-trainer-definitions", "get-trainer-definition", "register-training-run", "validate-training-run", "submit-training-job", "get-training-job", "retry-training-job", "cancel-training-job",
|
|
30
|
+
"register-evaluation-config", "get-evaluation-config", "register-metric-definition", "get-metric-definition",
|
|
31
|
+
"register-executable-package", "verify-executable-package", "publish-executable-package",
|
|
32
|
+
"validate-evaluation-run", "submit-evaluation", "get-evaluation-run", "get-evaluation-job", "retry-evaluation-job", "cancel-evaluation-job", "list-evaluation-attempts",
|
|
33
|
+
"get-evaluation-result", "get-evaluation-result-metrics", "get-evaluation-result-artifacts", "summarize-evaluation-run", "get-evaluation-summary",
|
|
34
|
+
"create-model-package", "get-model-package", "cancel-model-package", "validate-model-artifact", "register-model-artifact",
|
|
35
|
+
"list-deployment-targets", "validate-deployment", "create-deployment", "list-deployments",
|
|
36
|
+
"get-deployment", "wait-deployment", "reconcile-deployment", "approve-deployment",
|
|
37
|
+
"promote-deployment", "rollback-deployment", "suspend-deployment", "resume-deployment",
|
|
38
|
+
"undeploy-deployment", "get-deployment-events", "predict-deployment",
|
|
42
39
|
]);
|
|
43
|
-
|
|
44
40
|
const COMMAND_USAGE = {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
41
|
+
...Object.fromEntries([...LIFECYCLE_COLLECTIONS].map((name) => [`list-${name}`, `list-${name} [--limit N] [--offset N] [-q QUERY]`])),
|
|
42
|
+
"get-model-runtime-release": "get-model-runtime-release",
|
|
43
|
+
"get-training-run": "get-training-run RUN_ID",
|
|
44
|
+
"get-model-artifact": "get-model-artifact ARTIFACT_ID",
|
|
45
|
+
"get-executable-package": "get-executable-package NAME VERSION",
|
|
46
|
+
"validate-training-job": "validate-training-job REQUEST_JSON",
|
|
47
|
+
"validate-model-package": "validate-model-package REQUEST_JSON",
|
|
48
|
+
version: "version",
|
|
49
|
+
configure: "configure --api-url URL",
|
|
50
|
+
"show-config": "show-config",
|
|
51
|
+
health: "health",
|
|
52
|
+
"create-project": "create-project NAME [--display-name TEXT] [--description TEXT] [--owner OWNER]",
|
|
53
|
+
"list-projects": "list-projects",
|
|
54
|
+
"get-project": "get-project NAME",
|
|
55
|
+
"delete-project": "delete-project NAME",
|
|
56
|
+
"list-parameters": "list-parameters [--project PROJECT] [--name NAME] [--version VERSION] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
57
|
+
"list-operators": "list-operators [--project PROJECT] [--name NAME] [--version VERSION] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
58
|
+
"list-features": "list-features [--project PROJECT] [--name NAME] [--version VERSION] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
59
|
+
"list-feature-sets": "list-feature-sets [--project PROJECT] [--name NAME] [--version VERSION] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
60
|
+
"list-datasets": "list-datasets [--project PROJECT] [--dataset-id ID] [--dataset-version VERSION] [-q QUERY] [--limit N] [--offset N] [--include-deleted] [--all]",
|
|
61
|
+
"get-parameter": "get-parameter NAME VERSION [--project PROJECT]",
|
|
62
|
+
"get-feature": "get-feature NAME VERSION [--project PROJECT]",
|
|
63
|
+
"get-feature-set": "get-feature-set NAME VERSION [--project PROJECT]",
|
|
64
|
+
"get-operator": "get-operator NAME VERSION [--project PROJECT]",
|
|
65
|
+
"delete-parameter": "delete-parameter NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
66
|
+
"delete-feature": "delete-feature NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
67
|
+
"delete-feature-set": "delete-feature-set NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
68
|
+
"restore-parameter": "restore-parameter NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
69
|
+
"restore-feature": "restore-feature NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
70
|
+
"restore-feature-set": "restore-feature-set NAME VERSION [--project PROJECT] [--actor ACTOR] [--reason TEXT] [--idempotency-key KEY]",
|
|
71
|
+
"get-parameter-lifecycle": "get-parameter-lifecycle NAME VERSION [--project PROJECT]",
|
|
72
|
+
"get-feature-lifecycle": "get-feature-lifecycle NAME VERSION [--project PROJECT]",
|
|
73
|
+
"get-feature-set-lifecycle": "get-feature-set-lifecycle NAME VERSION [--project PROJECT]",
|
|
74
|
+
"list-parameter-references": "list-parameter-references NAME VERSION [--project PROJECT] [--direct]",
|
|
75
|
+
"list-feature-references": "list-feature-references NAME VERSION [--project PROJECT] [--direct]",
|
|
76
|
+
"list-feature-set-references": "list-feature-set-references NAME VERSION [--project PROJECT] [--direct]",
|
|
77
|
+
"list-parameter-versions": "list-parameter-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
78
|
+
"list-feature-versions": "list-feature-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
79
|
+
"list-feature-set-versions": "list-feature-set-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
80
|
+
"list-operator-versions": "list-operator-versions NAME [--project PROJECT] [--limit N] [--offset N] [--all]",
|
|
81
|
+
"add-parameter": "add-parameter SPEC_JSON",
|
|
82
|
+
"add-feature": "add-feature SPEC_JSON",
|
|
83
|
+
"add-feature-set": "add-feature-set SPEC_JSON",
|
|
84
|
+
"add-operator": "add-operator SPEC_JSON",
|
|
85
|
+
"publish-operator": "publish-operator SPEC_JSON --package PATH",
|
|
86
|
+
"add-dataset": "add-dataset MANIFEST_JSON [--force]",
|
|
87
|
+
"update-dataset": "update-dataset MANIFEST_JSON [--force]",
|
|
88
|
+
"delete-dataset": "delete-dataset DATASET_ID DATASET_VERSION [--project PROJECT]",
|
|
89
|
+
"list-dataset-artifacts": "list-dataset-artifacts [--project PROJECT] [--dataset-id ID] [--dataset-version VERSION] [--include-deleted] [--limit N] [--offset N]",
|
|
90
|
+
"get-dataset": "get-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--include-deleted]",
|
|
91
|
+
"list-jobs": "list-jobs [filters] [-q QUERY] [--limit N] [--offset N] [--all]",
|
|
92
|
+
"resolve-manifest": "resolve-manifest MANIFEST [--out PATH]",
|
|
93
|
+
"resolve-dataset": "resolve-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--out PATH]",
|
|
94
|
+
"build-dataset": "build-dataset MANIFEST [--engine chronon] [--source-mode direct] [--upload-chronon-metadata] [--partition-duration DURATION] [--max-parallelism N] [--no-resume] [--wait] [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
|
|
95
|
+
"build-registered-dataset": "build-registered-dataset DATASET_ID DATASET_VERSION [--project PROJECT] [--engine chronon] [--source-mode direct] [--upload-chronon-metadata] [--partition-duration DURATION] [--max-parallelism N] [--no-resume] [--wait] [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
|
|
96
|
+
"get-job": "get-job JOB_ID",
|
|
97
|
+
"wait-job": "wait-job JOB_ID [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
|
|
98
|
+
"retry-job": "retry-job JOB_ID",
|
|
99
|
+
"cancel-job": "cancel-job JOB_ID",
|
|
100
|
+
"register-operator": "register-operator SPEC_JSON",
|
|
101
|
+
"show-operator-specs": "show-operator-specs [--project PROJECT] [-q QUERY] [--limit N] [--offset N]",
|
|
102
|
+
"delete-operator": "delete-operator NAME VERSION [--project PROJECT]",
|
|
103
|
+
"run-operator": "run-operator NAME VERSION [--project PROJECT] [--config JSON]",
|
|
104
|
+
"get-dataset-artifact": "get-dataset-artifact DATASET_ID MANIFEST_HASH [--project PROJECT]",
|
|
105
|
+
"download-dataset-artifact": "download-dataset-artifact DATASET_ID MANIFEST_HASH [--project PROJECT] --out-dir PATH [--file PATH] [--force]",
|
|
106
|
+
"fetch-inference-data": "fetch-inference-data MANIFEST --cutoff-time TIMESTAMP [--max-workers N] [--max-source-lag-hours HOURS] [--no-validate-freshness] [--allow-missing]",
|
|
107
|
+
"fetch-inference-context": "fetch-inference-context REQUEST_JSON",
|
|
108
|
+
"predict-model": "predict-model REQUEST_JSON --serving-url URL",
|
|
109
|
+
"list-system-models": "list-system-models",
|
|
110
|
+
"get-system-model": "get-system-model NAME VERSION [--project PROJECT]",
|
|
111
|
+
"list-trainer-definitions": "list-trainer-definitions",
|
|
112
|
+
"get-trainer-definition": "get-trainer-definition NAME VERSION [--project PROJECT]",
|
|
113
|
+
"register-training-run": "register-training-run RUN_JSON",
|
|
114
|
+
"validate-training-run": "validate-training-run RUN_JSON",
|
|
115
|
+
"submit-training-job": "submit-training-job REQUEST_JSON",
|
|
116
|
+
"get-training-job": "get-training-job JOB_ID",
|
|
117
|
+
"retry-training-job": "retry-training-job JOB_ID",
|
|
118
|
+
"cancel-training-job": "cancel-training-job JOB_ID",
|
|
119
|
+
"register-evaluation-config": "register-evaluation-config CONFIG_JSON",
|
|
120
|
+
"get-evaluation-config": "get-evaluation-config NAME VERSION",
|
|
121
|
+
"register-metric-definition": "register-metric-definition DEFINITION_JSON",
|
|
122
|
+
"get-metric-definition": "get-metric-definition NAME VERSION",
|
|
123
|
+
"register-executable-package": "register-executable-package SPEC_JSON",
|
|
124
|
+
"verify-executable-package": "verify-executable-package NAME VERSION",
|
|
125
|
+
"publish-executable-package": "publish-executable-package NAME VERSION",
|
|
126
|
+
"validate-evaluation-run": "validate-evaluation-run REQUEST_JSON",
|
|
127
|
+
"submit-evaluation": "submit-evaluation REQUEST_JSON",
|
|
128
|
+
"get-evaluation-run": "get-evaluation-run RUN_ID",
|
|
129
|
+
"get-evaluation-job": "get-evaluation-job JOB_ID",
|
|
130
|
+
"retry-evaluation-job": "retry-evaluation-job JOB_ID",
|
|
131
|
+
"cancel-evaluation-job": "cancel-evaluation-job JOB_ID",
|
|
132
|
+
"list-evaluation-attempts": "list-evaluation-attempts JOB_ID",
|
|
133
|
+
"get-evaluation-result": "get-evaluation-result RESULT_ID",
|
|
134
|
+
"get-evaluation-result-metrics": "get-evaluation-result-metrics RESULT_ID",
|
|
135
|
+
"get-evaluation-result-artifacts": "get-evaluation-result-artifacts RESULT_ID",
|
|
136
|
+
"summarize-evaluation-run": "summarize-evaluation-run RUN_ID",
|
|
137
|
+
"get-evaluation-summary": "get-evaluation-summary SUMMARY_ID",
|
|
138
|
+
"create-model-package": "create-model-package REQUEST_JSON",
|
|
139
|
+
"get-model-package": "get-model-package PACKAGE_ID [--package-version VERSION]",
|
|
140
|
+
"cancel-model-package": "cancel-model-package PACKAGE_ID [--package-version VERSION]",
|
|
141
|
+
"validate-model-artifact": "validate-model-artifact ARTIFACT_ID",
|
|
142
|
+
"register-model-artifact": "register-model-artifact ARTIFACT_ID --evaluation-id EVALUATION_ID",
|
|
143
|
+
"list-deployment-targets": "list-deployment-targets",
|
|
144
|
+
"validate-deployment": "validate-deployment SPEC_JSON --evaluation-id ID",
|
|
145
|
+
"create-deployment": "create-deployment SPEC_JSON --evaluation-id ID",
|
|
146
|
+
"list-deployments": "list-deployments [--service SERVICE] [--environment ENV] [--package-id ID] [--package-version VERSION] [--model-artifact-id ID] [--state STATE] [-q QUERY] [--limit N] [--offset N]",
|
|
147
|
+
"get-deployment": "get-deployment DEPLOYMENT_ID",
|
|
148
|
+
"wait-deployment": "wait-deployment DEPLOYMENT_ID [--poll-interval SECONDS] [--wait-timeout-seconds SECONDS]",
|
|
149
|
+
"reconcile-deployment": "reconcile-deployment DEPLOYMENT_ID",
|
|
150
|
+
"approve-deployment": "approve-deployment DEPLOYMENT_ID --revision N --approver NAME --decision approved|rejected [--reason TEXT]",
|
|
151
|
+
"promote-deployment": "promote-deployment DEPLOYMENT_ID --stage canary|production --actor NAME --reason TEXT [--approval-id ID]",
|
|
152
|
+
"rollback-deployment": "rollback-deployment DEPLOYMENT_ID --actor NAME --reason TEXT [--to-revision N] [--to-deployment-id ID]",
|
|
153
|
+
"suspend-deployment": "suspend-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
|
|
154
|
+
"resume-deployment": "resume-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
|
|
155
|
+
"undeploy-deployment": "undeploy-deployment DEPLOYMENT_ID --actor NAME --reason TEXT",
|
|
156
|
+
"get-deployment-events": "get-deployment-events DEPLOYMENT_ID [--limit N] [--offset N]",
|
|
157
|
+
"predict-deployment": "predict-deployment DEPLOYMENT_ID REQUEST_JSON",
|
|
158
|
+
apply: "apply CATALOG_DIRECTORY [--dry-run] [--project PROJECT]",
|
|
163
159
|
};
|
|
164
|
-
|
|
165
160
|
function usage(command) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
161
|
+
const prefix = "ml-platform [--profile server] [--api-url URL] [--request-timeout-seconds SECONDS]";
|
|
162
|
+
if (command)
|
|
163
|
+
return `usage: ${prefix} ${COMMAND_USAGE[command]}\n`;
|
|
164
|
+
return `usage: ${prefix} COMMAND [ARGS]\n\ncommands:\n ${[...BUSINESS_COMMANDS].join("\n ")}\n`;
|
|
169
165
|
}
|
|
170
|
-
|
|
171
166
|
function parse(args) {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
167
|
+
const timeoutValue = process.env.ML_PLATFORM_API_TIMEOUT_SECONDS
|
|
168
|
+
|| process.env.DATA_PLATFORM_DEMO_API_TIMEOUT_SECONDS
|
|
169
|
+
|| 30;
|
|
170
|
+
const options = { profile: "server", timeout: finiteNumber(timeoutValue, "--request-timeout-seconds") };
|
|
171
|
+
let index = 0;
|
|
172
|
+
while (index < args.length && args[index].startsWith("--")) {
|
|
173
|
+
const flag = args[index++];
|
|
174
|
+
if (!["--profile", "--api-url", "--request-timeout-seconds"].includes(flag))
|
|
175
|
+
throw new Error(`unknown global argument: ${flag}`);
|
|
176
|
+
if (index >= args.length)
|
|
177
|
+
throw new Error(`${flag} requires a value`);
|
|
178
|
+
if (flag === "--profile")
|
|
179
|
+
options.profile = args[index++];
|
|
180
|
+
else if (flag === "--api-url")
|
|
181
|
+
options.apiUrl = args[index++];
|
|
182
|
+
else
|
|
183
|
+
options.timeout = finiteNumber(args[index++], "--request-timeout-seconds");
|
|
184
|
+
}
|
|
185
|
+
if (options.profile !== "server")
|
|
186
|
+
throw new Error("--profile must be server");
|
|
187
|
+
if (!(options.timeout > 0))
|
|
188
|
+
throw new Error("--request-timeout-seconds must be positive");
|
|
189
|
+
options.command = args[index++];
|
|
190
|
+
options.rest = args.slice(index);
|
|
191
|
+
options.apiUrl ??= process.env.ML_PLATFORM_API_URL || process.env.DATA_PLATFORM_DEMO_API_URL || loadApiUrl();
|
|
192
|
+
return options;
|
|
191
193
|
}
|
|
192
|
-
|
|
193
194
|
function take(rest, flag, fallback = undefined) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
195
|
+
const index = rest.indexOf(flag);
|
|
196
|
+
if (index < 0)
|
|
197
|
+
return fallback;
|
|
198
|
+
if (index + 1 >= rest.length)
|
|
199
|
+
throw new Error(`${flag} requires a value`);
|
|
200
|
+
const value = rest[index + 1];
|
|
201
|
+
rest.splice(index, 2);
|
|
202
|
+
return value;
|
|
200
203
|
}
|
|
201
|
-
|
|
202
204
|
function boolean(rest, flag) {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
const index = rest.indexOf(flag);
|
|
206
|
+
if (index < 0)
|
|
207
|
+
return false;
|
|
208
|
+
rest.splice(index, 1);
|
|
209
|
+
return true;
|
|
207
210
|
}
|
|
208
|
-
|
|
209
211
|
function finiteNumber(value, label) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
if (typeof value === "string" && value.trim() === "")
|
|
213
|
+
throw new Error(`${label} must be numeric`);
|
|
214
|
+
const parsed = Number(value);
|
|
215
|
+
if (!Number.isFinite(parsed))
|
|
216
|
+
throw new Error(`${label} must be numeric`);
|
|
217
|
+
return parsed;
|
|
214
218
|
}
|
|
215
|
-
|
|
216
219
|
function integer(value, label) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
+
const parsed = finiteNumber(value, label);
|
|
221
|
+
if (!Number.isInteger(parsed))
|
|
222
|
+
throw new Error(`${label} must be an integer`);
|
|
223
|
+
return parsed;
|
|
220
224
|
}
|
|
221
|
-
|
|
222
225
|
function resolvedPath(value) { return path.resolve(expandHome(value)); }
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
function positional(rest, label) { const value = rest.shift(); if (!value || value.startsWith("--"))
|
|
227
|
+
throw new Error(`${label} is required`); return value; }
|
|
226
228
|
function client(options) {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
229
|
+
if (options.profile !== "server")
|
|
230
|
+
throw new Error("the business client supports only --profile server");
|
|
231
|
+
if (!options.apiUrl)
|
|
232
|
+
throw new Error("server profile requires --api-url, ML_PLATFORM_API_URL, DATA_PLATFORM_DEMO_API_URL, or a saved configure target");
|
|
233
|
+
return new PlatformApiClient(options.apiUrl, options.timeout);
|
|
230
234
|
}
|
|
231
|
-
|
|
232
235
|
function readObject(file) {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
+
const payload = JSON.parse(fs.readFileSync(resolvedPath(file), "utf8"));
|
|
237
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
238
|
+
throw new Error(`JSON file must contain an object: ${file}`);
|
|
239
|
+
return payload;
|
|
236
240
|
}
|
|
237
|
-
|
|
238
241
|
function readDataset(file) { return normalizeDataset(readObject(file)); }
|
|
239
242
|
function readSpec(file) { return readObject(file); }
|
|
240
|
-
|
|
241
243
|
function datasetEndpoint(project, datasetId, datasetVersion) {
|
|
242
|
-
|
|
244
|
+
return `/datasets/${encodeURIComponent(project)}/${encodeURIComponent(datasetId)}/${encodeURIComponent(datasetVersion)}`;
|
|
243
245
|
}
|
|
244
|
-
|
|
245
246
|
function lifecycleResource(command) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
if (command.includes("feature-set"))
|
|
248
|
+
return "feature-sets";
|
|
249
|
+
if (command.includes("parameter"))
|
|
250
|
+
return "parameters";
|
|
251
|
+
if (command.includes("feature"))
|
|
252
|
+
return "features";
|
|
253
|
+
throw new Error(`unsupported lifecycle command: ${command}`);
|
|
250
254
|
}
|
|
251
|
-
|
|
252
255
|
function lifecycleEndpoint(resource, project, name, version, suffix = "") {
|
|
253
|
-
|
|
256
|
+
return `/registry/${resource}/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}${suffix}`;
|
|
254
257
|
}
|
|
255
|
-
|
|
256
258
|
async function registeredDataset(api, project, datasetId, datasetVersion, includeDeleted = false) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
259
|
+
const payload = await api.get(datasetEndpoint(project, datasetId, datasetVersion), { include_deleted: includeDeleted });
|
|
260
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
261
|
+
throw new Error("registered dataset response must be a JSON object");
|
|
262
|
+
return payload;
|
|
260
263
|
}
|
|
261
|
-
|
|
262
264
|
function buildOptions(rest) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
265
|
+
const engine = take(rest, "--engine", "chronon");
|
|
266
|
+
if (engine !== "chronon")
|
|
267
|
+
throw new Error("--engine must be chronon");
|
|
268
|
+
const sourceMode = take(rest, "--source-mode");
|
|
269
|
+
if (sourceMode !== undefined && sourceMode !== "direct")
|
|
270
|
+
throw new Error("--source-mode must be direct");
|
|
271
|
+
const maxParallelism = integer(take(rest, "--max-parallelism", 1), "--max-parallelism");
|
|
272
|
+
if (maxParallelism < 1)
|
|
273
|
+
throw new Error("--max-parallelism must be at least 1");
|
|
274
|
+
const wait = boolean(rest, "--wait");
|
|
275
|
+
const pollInterval = finiteNumber(take(rest, "--poll-interval", 2), "--poll-interval");
|
|
276
|
+
if (!(pollInterval > 0))
|
|
277
|
+
throw new Error("--poll-interval must be positive");
|
|
278
|
+
const rawTimeout = take(rest, "--wait-timeout-seconds");
|
|
279
|
+
const waitTimeout = rawTimeout === undefined ? undefined : finiteNumber(rawTimeout, "--wait-timeout-seconds");
|
|
280
|
+
if (waitTimeout !== undefined && !(waitTimeout > 0))
|
|
281
|
+
throw new Error("--wait-timeout-seconds must be positive");
|
|
282
|
+
return {
|
|
283
|
+
engine,
|
|
284
|
+
sourceMode,
|
|
285
|
+
upload: boolean(rest, "--upload-chronon-metadata"),
|
|
286
|
+
partitionDuration: take(rest, "--partition-duration"),
|
|
287
|
+
maxParallelism,
|
|
288
|
+
resume: !boolean(rest, "--no-resume"),
|
|
289
|
+
wait,
|
|
290
|
+
pollInterval,
|
|
291
|
+
waitTimeout,
|
|
292
|
+
};
|
|
286
293
|
}
|
|
287
|
-
|
|
288
294
|
async function submitBuild(api, manifest, build) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
let result = await api.post("/datasets/build", manifest, { engine: build.engine, source_mode: build.sourceMode, upload_chronon_metadata: build.upload, partition_duration: build.partitionDuration, max_parallelism: build.maxParallelism, resume: build.resume });
|
|
296
|
+
let exitCode = 0;
|
|
297
|
+
if (build.wait) {
|
|
298
|
+
if (!result || typeof result !== "object" || !result.job_id)
|
|
299
|
+
throw new Error("server build did not return a job_id to wait for");
|
|
300
|
+
result = await waitForJob(api, String(result.job_id), build.pollInterval, build.waitTimeout);
|
|
301
|
+
if (["failed", "cancelled"].includes(result.status))
|
|
302
|
+
exitCode = 1;
|
|
303
|
+
}
|
|
304
|
+
return { result, exitCode };
|
|
297
305
|
}
|
|
298
|
-
|
|
299
306
|
async function waitForJob(api, jobId, pollInterval, timeoutSeconds) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
307
|
+
if (!(pollInterval > 0))
|
|
308
|
+
throw new Error("job poll interval must be positive");
|
|
309
|
+
if (timeoutSeconds !== undefined && !(timeoutSeconds > 0))
|
|
310
|
+
throw new Error("job wait timeout must be positive");
|
|
311
|
+
const deadline = timeoutSeconds === undefined ? Infinity : Date.now() + timeoutSeconds * 1000;
|
|
312
|
+
let previous;
|
|
313
|
+
while (true) {
|
|
314
|
+
const payload = await api.get(`/jobs/${encodeURIComponent(jobId)}`);
|
|
315
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
316
|
+
throw new Error("job status response must be a JSON object");
|
|
317
|
+
const status = String(payload.status ?? "unknown");
|
|
318
|
+
if (status !== previous) {
|
|
319
|
+
process.stderr.write(`job ${jobId}: ${status}\n`);
|
|
320
|
+
previous = status;
|
|
321
|
+
}
|
|
322
|
+
if (["succeeded", "failed", "cancelled"].includes(status))
|
|
323
|
+
return payload;
|
|
324
|
+
if (Date.now() >= deadline)
|
|
325
|
+
throw new Error(`timed out waiting for job ${jobId}`);
|
|
326
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
|
|
327
|
+
}
|
|
313
328
|
}
|
|
314
|
-
|
|
315
329
|
function observedDeploymentState(payload) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
330
|
+
const state = payload?.observed_state?.observed_state ?? payload?.observed_state;
|
|
331
|
+
if (typeof state !== "string")
|
|
332
|
+
throw new Error("deployment status response has no observed state");
|
|
333
|
+
return state;
|
|
319
334
|
}
|
|
320
|
-
|
|
321
335
|
async function waitForDeployment(api, deploymentId, pollInterval, timeoutSeconds) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
336
|
+
if (!(pollInterval > 0))
|
|
337
|
+
throw new Error("deployment poll interval must be positive");
|
|
338
|
+
if (timeoutSeconds !== undefined && !(timeoutSeconds > 0))
|
|
339
|
+
throw new Error("deployment wait timeout must be positive");
|
|
340
|
+
const deadline = timeoutSeconds === undefined ? Infinity : Date.now() + timeoutSeconds * 1000;
|
|
341
|
+
let previous;
|
|
342
|
+
while (true) {
|
|
343
|
+
const payload = await api.get(`/deployments/${encodeURIComponent(deploymentId)}`);
|
|
344
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload))
|
|
345
|
+
throw new Error("deployment status response must be a JSON object");
|
|
346
|
+
const state = observedDeploymentState(payload);
|
|
347
|
+
if (state !== previous) {
|
|
348
|
+
process.stderr.write(`deployment ${deploymentId}: ${state}\n`);
|
|
349
|
+
previous = state;
|
|
350
|
+
}
|
|
351
|
+
if (["READY", "FAILED", "DEGRADED", "SUPERSEDED", "SUSPENDED", "UNDEPLOYED", "ROLLED_BACK"].includes(state))
|
|
352
|
+
return payload;
|
|
353
|
+
if (Date.now() >= deadline)
|
|
354
|
+
throw new Error(`timed out waiting for deployment ${deploymentId}`);
|
|
355
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval * 1000));
|
|
356
|
+
}
|
|
335
357
|
}
|
|
336
|
-
|
|
337
358
|
async function runBusinessCli(argv) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
359
|
+
if (argv.length === 0)
|
|
360
|
+
throw new Error("a business command is required");
|
|
361
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
362
|
+
const command = argv.find((item) => BUSINESS_COMMANDS.has(item));
|
|
363
|
+
if (command || argv[0] === "--help" || argv[0] === "-h") {
|
|
364
|
+
process.stdout.write(usage(command));
|
|
365
|
+
return 0;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const options = parse(argv);
|
|
369
|
+
const rest = [...options.rest];
|
|
370
|
+
let result;
|
|
371
|
+
let exitCode = 0;
|
|
372
|
+
if (options.command === "version")
|
|
373
|
+
result = CLIENT_VERSION;
|
|
374
|
+
else if (options.command === "configure") {
|
|
375
|
+
const apiUrl = take(rest, "--api-url");
|
|
376
|
+
if (!apiUrl)
|
|
377
|
+
throw new Error("--api-url is required");
|
|
378
|
+
const file = saveApiUrl(apiUrl);
|
|
379
|
+
result = { status: "configured", config_path: file, profile: "server", api_url: normalizeApiUrl(apiUrl) };
|
|
380
|
+
}
|
|
381
|
+
else if (options.command === "show-config") {
|
|
382
|
+
const file = configPath();
|
|
383
|
+
result = { config_path: file, exists: fs.existsSync(file), stored: { profile: "server", api_url: loadApiUrl() }, effective: { profile: options.profile, api_url: options.apiUrl, request_timeout_seconds: options.timeout } };
|
|
384
|
+
}
|
|
385
|
+
else if (options.command === "health")
|
|
386
|
+
result = await client(options).get("/health");
|
|
387
|
+
else if (options.command === "create-project") {
|
|
388
|
+
const name = positional(rest, "project name");
|
|
389
|
+
result = await client(options).post("/projects", { schema_version: "ml_data_platform.project/v1", name, display_name: take(rest, "--display-name", name), description: take(rest, "--description", ""), owner: take(rest, "--owner", "demo") });
|
|
390
|
+
}
|
|
391
|
+
else if (options.command === "list-projects")
|
|
392
|
+
result = await client(options).get("/projects");
|
|
393
|
+
else if (options.command === "get-project")
|
|
394
|
+
result = await client(options).get(`/projects/${encodeURIComponent(positional(rest, "project name"))}`);
|
|
395
|
+
else if (options.command === "delete-project")
|
|
396
|
+
result = await client(options).delete(`/projects/${encodeURIComponent(positional(rest, "project name"))}`);
|
|
397
|
+
else if (["add-parameter", "add-feature", "add-feature-set", "add-operator", "register-operator"].includes(options.command)) {
|
|
398
|
+
const spec = readSpec(positional(rest, "spec JSON"));
|
|
399
|
+
const endpoint = { "add-parameter": "/parameters", "add-feature": "/features", "add-feature-set": "/feature-sets", "add-operator": "/operators", "register-operator": "/operators" }[options.command];
|
|
400
|
+
result = await client(options).post(endpoint, spec);
|
|
401
|
+
}
|
|
402
|
+
else if (options.command === "publish-operator") {
|
|
403
|
+
const specPath = positional(rest, "OperatorSpec JSON");
|
|
404
|
+
const packagePath = take(rest, "--package");
|
|
405
|
+
if (!packagePath)
|
|
406
|
+
throw new Error("--package is required");
|
|
407
|
+
const spec = readSpec(specPath);
|
|
408
|
+
const api = client(options);
|
|
409
|
+
const project = spec.project || "default";
|
|
410
|
+
const upload = await api.uploadOperatorPackage(project, spec.name, spec.version, packagePath);
|
|
411
|
+
if (!upload || typeof upload.code_artifact !== "object")
|
|
412
|
+
throw new Error("operator package upload response has no code_artifact");
|
|
413
|
+
const published = { ...spec, code_hash: upload.code_artifact.sha256, package_uri: upload.code_artifact.uri, code_artifact: upload.code_artifact };
|
|
414
|
+
result = { status: "published", profile: "server", path: null, operator: published, registration: await api.post("/operators", published) };
|
|
415
|
+
}
|
|
416
|
+
else if (options.command === "get-dataset") {
|
|
417
|
+
const datasetId = positional(rest, "dataset id");
|
|
418
|
+
const datasetVersion = positional(rest, "dataset version");
|
|
419
|
+
const project = take(rest, "--project", "default");
|
|
420
|
+
result = await registeredDataset(client(options), project, datasetId, datasetVersion, boolean(rest, "--include-deleted"));
|
|
421
|
+
}
|
|
422
|
+
else if (["get-parameter", "get-feature", "get-feature-set", "get-operator"].includes(options.command)) {
|
|
423
|
+
const resource = options.command.slice(4);
|
|
424
|
+
const endpoint = resource === "feature-set" ? "feature-sets" : `${resource}s`;
|
|
425
|
+
const name = positional(rest, `${resource} name`);
|
|
426
|
+
const version = positional(rest, `${resource} version`);
|
|
427
|
+
const project = take(rest, "--project", "default");
|
|
428
|
+
result = await client(options).get(`/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
429
|
+
}
|
|
430
|
+
else if (/^delete-(parameter|feature|feature-set)$/.test(options.command)) {
|
|
431
|
+
const name = positional(rest, "resource name");
|
|
432
|
+
const version = positional(rest, "resource version");
|
|
433
|
+
const project = take(rest, "--project", "default");
|
|
434
|
+
result = await client(options).delete(lifecycleEndpoint(lifecycleResource(options.command), project, name, version), {
|
|
435
|
+
actor: take(rest, "--actor"), reason: take(rest, "--reason"), idempotency_key: take(rest, "--idempotency-key"),
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
else if (/^restore-(parameter|feature|feature-set)$/.test(options.command)) {
|
|
439
|
+
const name = positional(rest, "resource name");
|
|
440
|
+
const version = positional(rest, "resource version");
|
|
441
|
+
const project = take(rest, "--project", "default");
|
|
442
|
+
result = await client(options).post(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/restore"), {
|
|
443
|
+
actor: take(rest, "--actor") || "anonymous", reason: take(rest, "--reason", ""), idempotency_key: take(rest, "--idempotency-key"),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
else if (/^get-(parameter|feature|feature-set)-lifecycle$/.test(options.command)) {
|
|
447
|
+
const name = positional(rest, "resource name");
|
|
448
|
+
const version = positional(rest, "resource version");
|
|
449
|
+
const project = take(rest, "--project", "default");
|
|
450
|
+
result = await client(options).get(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/lifecycle"));
|
|
451
|
+
}
|
|
452
|
+
else if (/^list-(parameter|feature|feature-set)-references$/.test(options.command)) {
|
|
453
|
+
const name = positional(rest, "resource name");
|
|
454
|
+
const version = positional(rest, "resource version");
|
|
455
|
+
const project = take(rest, "--project", "default");
|
|
456
|
+
result = await client(options).get(lifecycleEndpoint(lifecycleResource(options.command), project, name, version, "/references"), { transitive: !boolean(rest, "--direct") });
|
|
457
|
+
}
|
|
458
|
+
else if (["list-parameter-versions", "list-feature-versions", "list-feature-set-versions", "list-operator-versions"].includes(options.command)) {
|
|
459
|
+
const resource = options.command.replace(/^list-/, "").replace(/-versions$/, "");
|
|
460
|
+
const endpoint = resource === "feature-set" ? "feature-sets" : `${resource}s`;
|
|
461
|
+
const name = positional(rest, `${resource} name`);
|
|
462
|
+
const project = take(rest, "--project", "default");
|
|
463
|
+
const allPages = boolean(rest, "--all");
|
|
464
|
+
const rawLimit = take(rest, "--limit");
|
|
465
|
+
const rawOffset = take(rest, "--offset");
|
|
466
|
+
if (allPages && (rawLimit !== undefined || rawOffset !== undefined))
|
|
467
|
+
throw new Error("--all cannot be combined with --limit or --offset");
|
|
468
|
+
if (allPages)
|
|
469
|
+
result = await fetchAll(client(options), `/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}`);
|
|
470
|
+
else {
|
|
471
|
+
const limit = Number(rawLimit ?? 50);
|
|
472
|
+
const offset = Number(rawOffset ?? 0);
|
|
473
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 500)
|
|
474
|
+
throw new Error("limit must be between 1 and 500");
|
|
475
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
476
|
+
throw new Error("offset must be >= 0");
|
|
477
|
+
result = await client(options).get(`/${endpoint}/${encodeURIComponent(project)}/${encodeURIComponent(name)}`, { limit, offset });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
else if (["list-parameters", "list-operators", "list-features", "list-feature-sets", "list-datasets"].includes(options.command)) {
|
|
481
|
+
const endpoints = { "list-parameters": "/parameters", "list-operators": "/operators", "list-features": "/features", "list-feature-sets": "/feature-sets", "list-datasets": "/datasets" };
|
|
482
|
+
const project = take(rest, "--project");
|
|
483
|
+
const name = take(rest, "--name");
|
|
484
|
+
const version = take(rest, "--version");
|
|
485
|
+
const query = take(rest, "--q", take(rest, "-q"));
|
|
486
|
+
const includeDeleted = options.command === "list-datasets" && boolean(rest, "--include-deleted");
|
|
487
|
+
const rawLimit = take(rest, "--limit");
|
|
488
|
+
const rawOffset = take(rest, "--offset");
|
|
489
|
+
const allPages = boolean(rest, "--all");
|
|
490
|
+
if (allPages && (rawLimit !== undefined || rawOffset !== undefined))
|
|
491
|
+
throw new Error("--all cannot be combined with --limit or --offset");
|
|
492
|
+
if (allPages)
|
|
493
|
+
result = await fetchAll(client(options), endpoints[options.command], { q: query, project, name, version, include_deleted: includeDeleted });
|
|
494
|
+
else {
|
|
495
|
+
const limit = Number(rawLimit ?? 50);
|
|
496
|
+
const offset = Number(rawOffset ?? 0);
|
|
497
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 500)
|
|
498
|
+
throw new Error("limit must be between 1 and 500");
|
|
499
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
500
|
+
throw new Error("offset must be >= 0");
|
|
501
|
+
result = await client(options).get(endpoints[options.command], { q: query, limit, offset, project, name, version, include_deleted: includeDeleted });
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else if (options.command === "list-jobs") {
|
|
505
|
+
const query = take(rest, "--q", take(rest, "-q"));
|
|
506
|
+
const rawLimit = take(rest, "--limit");
|
|
507
|
+
const rawOffset = take(rest, "--offset");
|
|
508
|
+
const filters = {};
|
|
509
|
+
for (const field of ["project", "dataset-id", "dataset-version", "manifest-hash", "status", "parent-job-id", "created-from", "created-to", "updated-from", "updated-to"])
|
|
510
|
+
filters[field.replaceAll("-", "_")] = take(rest, `--${field}`);
|
|
511
|
+
const allPages = boolean(rest, "--all");
|
|
512
|
+
if (allPages && (rawLimit !== undefined || rawOffset !== undefined))
|
|
513
|
+
throw new Error("--all cannot be combined with --limit or --offset");
|
|
514
|
+
if (allPages)
|
|
515
|
+
result = await fetchAll(client(options), "/jobs", { q: query, ...filters });
|
|
516
|
+
else {
|
|
517
|
+
const limit = Number(rawLimit ?? 50);
|
|
518
|
+
const offset = Number(rawOffset ?? 0);
|
|
519
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 500)
|
|
520
|
+
throw new Error("limit must be between 1 and 500");
|
|
521
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
522
|
+
throw new Error("offset must be >= 0");
|
|
523
|
+
result = await client(options).get("/jobs", { q: query, limit, offset, ...filters });
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
else if (options.command === "resolve-manifest") {
|
|
527
|
+
const manifest = positional(rest, "manifest");
|
|
528
|
+
result = await client(options).post("/datasets/resolve", readDataset(manifest));
|
|
529
|
+
const out = take(rest, "--out");
|
|
530
|
+
if (out) {
|
|
531
|
+
const target = resolvedPath(out);
|
|
532
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
533
|
+
fs.writeFileSync(target, `${JSON.stringify(result, null, 2)}\n`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
else if (options.command === "resolve-dataset") {
|
|
537
|
+
const datasetId = positional(rest, "dataset id");
|
|
538
|
+
const datasetVersion = positional(rest, "dataset version");
|
|
539
|
+
const project = take(rest, "--project", "default");
|
|
540
|
+
result = await client(options).post(`/datasets/${encodeURIComponent(project)}/${encodeURIComponent(datasetId)}/${encodeURIComponent(datasetVersion)}/resolve`);
|
|
541
|
+
const out = take(rest, "--out");
|
|
542
|
+
if (out) {
|
|
543
|
+
const target = resolvedPath(out);
|
|
544
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
545
|
+
fs.writeFileSync(target, `${JSON.stringify(result, null, 2)}\n`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
else if (options.command === "build-dataset") {
|
|
549
|
+
const manifest = positional(rest, "manifest");
|
|
550
|
+
const build = buildOptions(rest);
|
|
551
|
+
const api = client(options);
|
|
552
|
+
({ result, exitCode } = await submitBuild(api, readDataset(manifest), build));
|
|
553
|
+
}
|
|
554
|
+
else if (options.command === "build-registered-dataset") {
|
|
555
|
+
const datasetId = positional(rest, "dataset id");
|
|
556
|
+
const datasetVersion = positional(rest, "dataset version");
|
|
557
|
+
const project = take(rest, "--project", "default");
|
|
558
|
+
const build = buildOptions(rest);
|
|
559
|
+
const api = client(options);
|
|
560
|
+
const manifest = normalizeRegistryDataset(await registeredDataset(api, project, datasetId, datasetVersion));
|
|
561
|
+
({ result, exitCode } = await submitBuild(api, manifest, build));
|
|
562
|
+
}
|
|
563
|
+
else if (["add-dataset", "update-dataset"].includes(options.command)) {
|
|
564
|
+
const manifest = readDataset(positional(rest, "manifest JSON"));
|
|
565
|
+
const force = boolean(rest, "--force");
|
|
566
|
+
const project = manifest.project || "default";
|
|
567
|
+
const params = force ? { force: true } : undefined;
|
|
568
|
+
if (options.command === "add-dataset")
|
|
569
|
+
result = await client(options).post("/datasets", manifest, params);
|
|
570
|
+
else
|
|
571
|
+
result = await client(options).put(`/datasets/${encodeURIComponent(project)}/${encodeURIComponent(manifest.dataset_id)}/${encodeURIComponent(manifest.dataset_version)}`, manifest, params);
|
|
572
|
+
}
|
|
573
|
+
else if (options.command === "delete-dataset") {
|
|
574
|
+
const id = positional(rest, "dataset id");
|
|
575
|
+
const version = positional(rest, "dataset version");
|
|
576
|
+
const project = take(rest, "--project", "default");
|
|
577
|
+
result = await client(options).delete(`/datasets/${encodeURIComponent(project)}/${encodeURIComponent(id)}/${encodeURIComponent(version)}`);
|
|
578
|
+
}
|
|
579
|
+
else if (options.command === "list-dataset-artifacts") {
|
|
580
|
+
const project = take(rest, "--project");
|
|
581
|
+
const datasetId = take(rest, "--dataset-id");
|
|
582
|
+
const datasetVersion = take(rest, "--dataset-version");
|
|
583
|
+
const includeDeleted = boolean(rest, "--include-deleted");
|
|
584
|
+
result = await listCollection(client(options), "/dataset-artifacts", { project, dataset_id: datasetId, dataset_version: datasetVersion, include_deleted: includeDeleted }, rest);
|
|
585
|
+
}
|
|
586
|
+
else if (options.command === "show-operator-specs") {
|
|
587
|
+
const project = take(rest, "--project");
|
|
588
|
+
const query = take(rest, "--q", take(rest, "-q"));
|
|
589
|
+
result = await listCollection(client(options), "/operators", { project, q: query }, rest);
|
|
590
|
+
}
|
|
591
|
+
else if (options.command === "delete-operator") {
|
|
592
|
+
const name = positional(rest, "operator name");
|
|
593
|
+
const version = positional(rest, "operator version");
|
|
594
|
+
const project = take(rest, "--project", "default");
|
|
595
|
+
result = await client(options).delete(`/operators/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
596
|
+
}
|
|
597
|
+
else if (options.command === "run-operator") {
|
|
598
|
+
const name = positional(rest, "operator name");
|
|
599
|
+
const version = positional(rest, "operator version");
|
|
600
|
+
const project = take(rest, "--project", "default");
|
|
601
|
+
const rawConfig = take(rest, "--config", "{}");
|
|
602
|
+
let config;
|
|
603
|
+
try {
|
|
604
|
+
config = JSON.parse(rawConfig);
|
|
605
|
+
}
|
|
606
|
+
catch (_) {
|
|
607
|
+
throw new Error("--config must be valid JSON");
|
|
608
|
+
}
|
|
609
|
+
result = await client(options).post(`/operators/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}/run`, config);
|
|
610
|
+
}
|
|
611
|
+
else if (options.command === "get-job")
|
|
612
|
+
result = await client(options).get(`/jobs/${encodeURIComponent(positional(rest, "job id"))}`);
|
|
613
|
+
else if (options.command === "wait-job") {
|
|
614
|
+
const jobId = positional(rest, "job id");
|
|
615
|
+
const poll = finiteNumber(take(rest, "--poll-interval", 2), "--poll-interval");
|
|
616
|
+
const rawTimeout = take(rest, "--wait-timeout-seconds");
|
|
617
|
+
result = await waitForJob(client(options), jobId, poll, rawTimeout === undefined ? undefined : finiteNumber(rawTimeout, "--wait-timeout-seconds"));
|
|
618
|
+
if (["failed", "cancelled"].includes(result.status))
|
|
619
|
+
exitCode = 1;
|
|
620
|
+
}
|
|
621
|
+
else if (options.command === "cancel-job") {
|
|
622
|
+
result = await client(options).post(`/jobs/${encodeURIComponent(positional(rest, "job id"))}/cancel`);
|
|
623
|
+
}
|
|
624
|
+
else if (options.command === "retry-job") {
|
|
625
|
+
result = await client(options).post(`/jobs/${encodeURIComponent(positional(rest, "job id"))}/retry`);
|
|
626
|
+
}
|
|
627
|
+
else if (options.command === "get-dataset-artifact") {
|
|
628
|
+
const dataset = positional(rest, "dataset id");
|
|
629
|
+
const hash = positional(rest, "manifest hash");
|
|
630
|
+
const project = take(rest, "--project", "default");
|
|
631
|
+
result = await client(options).get(`/dataset-artifacts/${encodeURIComponent(project)}/${encodeURIComponent(dataset)}/${encodeURIComponent(hash)}`);
|
|
632
|
+
}
|
|
633
|
+
else if (options.command === "download-dataset-artifact") {
|
|
634
|
+
const dataset = positional(rest, "dataset id");
|
|
635
|
+
const hash = positional(rest, "manifest hash");
|
|
636
|
+
const project = take(rest, "--project", "default");
|
|
637
|
+
const outDir = take(rest, "--out-dir");
|
|
638
|
+
if (!outDir)
|
|
639
|
+
throw new Error("--out-dir is required");
|
|
640
|
+
const files = [];
|
|
641
|
+
while (rest.includes("--file"))
|
|
642
|
+
files.push(take(rest, "--file"));
|
|
643
|
+
result = files.length
|
|
644
|
+
? await client(options).downloadDatasetArtifactFiles(project, dataset, hash, files, resolvedPath(outDir), boolean(rest, "--force"))
|
|
645
|
+
: await client(options).downloadDatasetArtifact(project, dataset, hash, resolvedPath(outDir), boolean(rest, "--force"));
|
|
646
|
+
}
|
|
647
|
+
else if (options.command === "fetch-inference-data") {
|
|
648
|
+
const manifest = positional(rest, "manifest");
|
|
649
|
+
const cutoffTime = take(rest, "--cutoff-time");
|
|
650
|
+
if (!cutoffTime)
|
|
651
|
+
throw new Error("--cutoff-time is required");
|
|
652
|
+
const realtime = {};
|
|
653
|
+
const maxWorkers = take(rest, "--max-workers");
|
|
654
|
+
const maxLag = take(rest, "--max-source-lag-hours");
|
|
655
|
+
if (maxWorkers !== undefined) {
|
|
656
|
+
realtime.max_workers = integer(maxWorkers, "--max-workers");
|
|
657
|
+
if (realtime.max_workers < 1)
|
|
658
|
+
throw new Error("--max-workers must be at least 1");
|
|
659
|
+
}
|
|
660
|
+
if (maxLag !== undefined) {
|
|
661
|
+
realtime.max_source_lag_hours = finiteNumber(maxLag, "--max-source-lag-hours");
|
|
662
|
+
if (realtime.max_source_lag_hours < 0)
|
|
663
|
+
throw new Error("--max-source-lag-hours must be non-negative");
|
|
664
|
+
}
|
|
665
|
+
if (boolean(rest, "--no-validate-freshness"))
|
|
666
|
+
realtime.validate_freshness = false;
|
|
667
|
+
if (boolean(rest, "--allow-missing"))
|
|
668
|
+
realtime.allow_missing = true;
|
|
669
|
+
const request = { manifest: readDataset(manifest), cutoff_time: cutoffTime };
|
|
670
|
+
if (Object.keys(realtime).length)
|
|
671
|
+
request.realtime_fetch = realtime;
|
|
672
|
+
result = await client(options).post("/inference-data/fetch", request);
|
|
673
|
+
}
|
|
674
|
+
else if (options.command === "fetch-inference-context") {
|
|
675
|
+
result = await client(options).post("/inference-data/context", readSpec(positional(rest, "request JSON")));
|
|
676
|
+
}
|
|
677
|
+
else if (options.command === "predict-model") {
|
|
678
|
+
const request = readSpec(positional(rest, "request JSON"));
|
|
679
|
+
const servingUrl = take(rest, "--serving-url");
|
|
680
|
+
if (!servingUrl)
|
|
681
|
+
throw new Error("--serving-url is required");
|
|
682
|
+
result = await new PlatformApiClient(servingUrl, options.timeout).post("/v1/inference", request);
|
|
683
|
+
}
|
|
684
|
+
else if (options.command === "list-deployment-targets") {
|
|
685
|
+
result = await client(options).get("/deployment-targets");
|
|
686
|
+
}
|
|
687
|
+
else if (["validate-deployment", "create-deployment"].includes(options.command)) {
|
|
688
|
+
const deployment = readSpec(positional(rest, "deployment spec JSON"));
|
|
689
|
+
const evaluationId = take(rest, "--evaluation-id");
|
|
690
|
+
if (!evaluationId)
|
|
691
|
+
throw new Error("--evaluation-id is required");
|
|
692
|
+
const endpoint = options.command === "validate-deployment" ? "/deployments/validate" : "/deployments";
|
|
693
|
+
result = await client(options).post(endpoint, { deployment, evaluation_id: evaluationId });
|
|
694
|
+
}
|
|
695
|
+
else if (options.command === "list-deployments") {
|
|
696
|
+
const params = {
|
|
697
|
+
service: take(rest, "--service"),
|
|
698
|
+
environment: take(rest, "--environment"),
|
|
699
|
+
package_id: take(rest, "--package-id"),
|
|
700
|
+
package_version: take(rest, "--package-version"),
|
|
701
|
+
model_artifact_id: take(rest, "--model-artifact-id"),
|
|
702
|
+
state: take(rest, "--state"),
|
|
703
|
+
q: take(rest, "--q", take(rest, "-q")),
|
|
704
|
+
};
|
|
705
|
+
boolean(rest, "--all");
|
|
706
|
+
result = await listCollection(client(options), "/deployments", params, rest);
|
|
707
|
+
}
|
|
708
|
+
else if (options.command === "get-deployment") {
|
|
709
|
+
const deploymentId = positional(rest, "deployment id");
|
|
710
|
+
result = await client(options).get(`/deployments/${encodeURIComponent(deploymentId)}`);
|
|
711
|
+
}
|
|
712
|
+
else if (options.command === "wait-deployment") {
|
|
713
|
+
const deploymentId = positional(rest, "deployment id");
|
|
714
|
+
const poll = finiteNumber(take(rest, "--poll-interval", 2), "--poll-interval");
|
|
715
|
+
const rawTimeout = take(rest, "--wait-timeout-seconds");
|
|
716
|
+
result = await waitForDeployment(client(options), deploymentId, poll, rawTimeout === undefined ? undefined : finiteNumber(rawTimeout, "--wait-timeout-seconds"));
|
|
717
|
+
if (observedDeploymentState(result) !== "READY")
|
|
718
|
+
exitCode = 1;
|
|
719
|
+
}
|
|
720
|
+
else if (options.command === "reconcile-deployment") {
|
|
721
|
+
const deploymentId = positional(rest, "deployment id");
|
|
722
|
+
result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/reconcile`);
|
|
723
|
+
}
|
|
724
|
+
else if (options.command === "approve-deployment") {
|
|
725
|
+
const deploymentId = positional(rest, "deployment id");
|
|
726
|
+
const rawRevision = take(rest, "--revision");
|
|
727
|
+
const approver = take(rest, "--approver");
|
|
728
|
+
if (rawRevision === undefined)
|
|
729
|
+
throw new Error("--revision is required");
|
|
730
|
+
if (!approver)
|
|
731
|
+
throw new Error("--approver is required");
|
|
732
|
+
const decision = take(rest, "--decision", "approved");
|
|
733
|
+
if (!["approved", "rejected"].includes(decision))
|
|
734
|
+
throw new Error("--decision must be approved or rejected");
|
|
735
|
+
result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/approvals`, {
|
|
736
|
+
revision: integer(rawRevision, "--revision"), stage: "production", decision, approver,
|
|
737
|
+
reason: take(rest, "--reason", ""),
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
else if (options.command === "promote-deployment") {
|
|
741
|
+
const deploymentId = positional(rest, "deployment id");
|
|
742
|
+
const stage = take(rest, "--stage");
|
|
743
|
+
const actor = take(rest, "--actor");
|
|
744
|
+
const reason = take(rest, "--reason");
|
|
745
|
+
if (!["canary", "production"].includes(stage))
|
|
746
|
+
throw new Error("--stage must be canary or production");
|
|
747
|
+
if (!actor)
|
|
748
|
+
throw new Error("--actor is required");
|
|
749
|
+
if (!reason)
|
|
750
|
+
throw new Error("--reason is required");
|
|
751
|
+
result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/promote`, {
|
|
752
|
+
stage, actor, reason, approval_id: take(rest, "--approval-id"),
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
else if (options.command === "rollback-deployment") {
|
|
756
|
+
const deploymentId = positional(rest, "deployment id");
|
|
757
|
+
const actor = take(rest, "--actor");
|
|
758
|
+
const reason = take(rest, "--reason");
|
|
759
|
+
if (!actor)
|
|
760
|
+
throw new Error("--actor is required");
|
|
761
|
+
if (!reason)
|
|
762
|
+
throw new Error("--reason is required");
|
|
763
|
+
const rawRevision = take(rest, "--to-revision");
|
|
764
|
+
result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/rollback`, {
|
|
765
|
+
actor, reason,
|
|
766
|
+
to_revision: rawRevision === undefined ? undefined : integer(rawRevision, "--to-revision"),
|
|
767
|
+
to_deployment_id: take(rest, "--to-deployment-id"),
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
else if (["suspend-deployment", "resume-deployment", "undeploy-deployment"].includes(options.command)) {
|
|
771
|
+
const deploymentId = positional(rest, "deployment id");
|
|
772
|
+
const actor = take(rest, "--actor");
|
|
773
|
+
const reason = take(rest, "--reason");
|
|
774
|
+
if (!actor)
|
|
775
|
+
throw new Error("--actor is required");
|
|
776
|
+
if (!reason)
|
|
777
|
+
throw new Error("--reason is required");
|
|
778
|
+
const action = options.command.split("-")[0];
|
|
779
|
+
result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/${action}`, { actor, reason });
|
|
780
|
+
}
|
|
781
|
+
else if (options.command === "get-deployment-events") {
|
|
782
|
+
const deploymentId = positional(rest, "deployment id");
|
|
783
|
+
result = await listCollection(client(options), `/deployments/${encodeURIComponent(deploymentId)}/events`, {}, rest);
|
|
784
|
+
}
|
|
785
|
+
else if (options.command === "predict-deployment") {
|
|
786
|
+
const deploymentId = positional(rest, "deployment id");
|
|
787
|
+
const request = readSpec(positional(rest, "request JSON"));
|
|
788
|
+
result = await client(options).post(`/deployments/${encodeURIComponent(deploymentId)}/predict`, request);
|
|
789
|
+
}
|
|
790
|
+
else if (options.command === "get-model-runtime-release") {
|
|
791
|
+
result = await client(options).get("/model-runtime-release");
|
|
792
|
+
}
|
|
793
|
+
else if (options.command.startsWith("list-") && LIFECYCLE_COLLECTIONS.has(options.command.slice(5))) {
|
|
794
|
+
const params = {};
|
|
795
|
+
const limit = take(rest, "--limit");
|
|
796
|
+
const offset = take(rest, "--offset");
|
|
797
|
+
const query = take(rest, "-q");
|
|
798
|
+
if (limit !== undefined) {
|
|
799
|
+
params.limit = integer(limit, "--limit");
|
|
800
|
+
if (params.limit < 1 || params.limit > 500)
|
|
801
|
+
throw new Error("--limit must be between 1 and 500");
|
|
802
|
+
}
|
|
803
|
+
if (offset !== undefined) {
|
|
804
|
+
params.offset = integer(offset, "--offset");
|
|
805
|
+
if (params.offset < 0)
|
|
806
|
+
throw new Error("--offset must be non-negative");
|
|
807
|
+
}
|
|
808
|
+
if (query !== undefined)
|
|
809
|
+
params.q = query;
|
|
810
|
+
result = await client(options).get(`/${options.command.slice(5)}`, params);
|
|
811
|
+
}
|
|
812
|
+
else if (options.command === "get-training-run" || options.command === "get-model-artifact") {
|
|
813
|
+
const endpoint = options.command === "get-training-run" ? "training-runs" : "model-artifacts";
|
|
814
|
+
result = await client(options).get(`/${endpoint}/${encodeURIComponent(positional(rest, "resource id"))}`);
|
|
815
|
+
}
|
|
816
|
+
else if (options.command === "get-executable-package") {
|
|
817
|
+
const name = positional(rest, "package name");
|
|
818
|
+
const version = positional(rest, "package version");
|
|
819
|
+
result = await client(options).get(`/executable-packages/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
820
|
+
}
|
|
821
|
+
else if (options.command === "list-system-models") {
|
|
822
|
+
result = await client(options).get("/system-models");
|
|
823
|
+
}
|
|
824
|
+
else if (options.command === "get-system-model") {
|
|
825
|
+
const name = positional(rest, "system model name");
|
|
826
|
+
const version = positional(rest, "system model version");
|
|
827
|
+
const project = take(rest, "--project", "default");
|
|
828
|
+
result = await client(options).get(`/system-models/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
829
|
+
}
|
|
830
|
+
else if (options.command === "list-trainer-definitions") {
|
|
831
|
+
result = await client(options).get("/trainer-definitions");
|
|
832
|
+
}
|
|
833
|
+
else if (options.command === "get-trainer-definition") {
|
|
834
|
+
const name = positional(rest, "trainer name");
|
|
835
|
+
const version = positional(rest, "trainer version");
|
|
836
|
+
const project = take(rest, "--project", "default");
|
|
837
|
+
result = await client(options).get(`/trainer-definitions/${encodeURIComponent(project)}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
838
|
+
}
|
|
839
|
+
else if ({
|
|
840
|
+
"register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate",
|
|
841
|
+
"validate-training-job": "/training-jobs/validate", "validate-model-package": "/model-packages/validate", "submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions",
|
|
842
|
+
"register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "submit-evaluation": "/evaluation-runs",
|
|
843
|
+
"create-model-package": "/model-packages",
|
|
844
|
+
}[options.command]) {
|
|
845
|
+
const endpoint = { "register-training-run": "/training-runs", "validate-training-run": "/training-runs/validate", "validate-training-job": "/training-jobs/validate", "validate-model-package": "/model-packages/validate", "submit-training-job": "/training-jobs", "register-evaluation-config": "/evaluation-configs", "register-metric-definition": "/metric-definitions", "register-executable-package": "/executable-packages", "validate-evaluation-run": "/evaluation-runs/validate", "submit-evaluation": "/evaluation-runs", "create-model-package": "/model-packages" }[options.command];
|
|
846
|
+
result = await client(options).post(endpoint, readSpec(positional(rest, "request JSON")));
|
|
847
|
+
}
|
|
848
|
+
else if (/^(get|retry|cancel)-training-job$/.test(options.command)) {
|
|
849
|
+
const jobId = positional(rest, "training job id");
|
|
850
|
+
const endpoint = `/training-jobs/${encodeURIComponent(jobId)}`;
|
|
851
|
+
result = options.command === "get-training-job" ? await client(options).get(endpoint) : await client(options).post(`${endpoint}/${options.command.split("-")[0]}`);
|
|
852
|
+
}
|
|
853
|
+
else if (/^(get|retry|cancel)-evaluation-job$/.test(options.command) || options.command === "list-evaluation-attempts") {
|
|
854
|
+
const jobId = positional(rest, "evaluation job id");
|
|
855
|
+
const endpoint = `/evaluation-jobs/${encodeURIComponent(jobId)}`;
|
|
856
|
+
if (options.command === "get-evaluation-job")
|
|
857
|
+
result = await client(options).get(endpoint);
|
|
858
|
+
else if (options.command === "list-evaluation-attempts")
|
|
859
|
+
result = await client(options).get(`${endpoint}/attempts`);
|
|
860
|
+
else
|
|
861
|
+
result = await client(options).post(`${endpoint}/${options.command.split("-")[0]}`);
|
|
862
|
+
}
|
|
863
|
+
else if (options.command === "get-evaluation-run")
|
|
864
|
+
result = await client(options).get(`/evaluation-runs/${encodeURIComponent(positional(rest, "evaluation run id"))}`);
|
|
865
|
+
else if (options.command === "summarize-evaluation-run")
|
|
866
|
+
result = await client(options).post(`/evaluation-runs/${encodeURIComponent(positional(rest, "evaluation run id"))}/summarize`);
|
|
867
|
+
else if (options.command === "get-evaluation-summary")
|
|
868
|
+
result = await client(options).get(`/evaluation-summaries/${encodeURIComponent(positional(rest, "evaluation summary id"))}`);
|
|
869
|
+
else if (/^get-evaluation-result(?:-metrics|-artifacts)?$/.test(options.command)) {
|
|
870
|
+
const resultId = positional(rest, "evaluation result id");
|
|
871
|
+
const suffix = options.command.endsWith("-metrics") ? "/metrics" : options.command.endsWith("-artifacts") ? "/artifacts" : "";
|
|
872
|
+
result = await client(options).get(`/evaluation-results/${encodeURIComponent(resultId)}${suffix}`);
|
|
873
|
+
}
|
|
874
|
+
else if (/^(get-evaluation-config|get-metric-definition)$/.test(options.command)) {
|
|
875
|
+
const name = positional(rest, "resource name");
|
|
876
|
+
const version = positional(rest, "resource version");
|
|
877
|
+
const base = options.command === "get-evaluation-config" ? "evaluation-configs" : "metric-definitions";
|
|
878
|
+
result = await client(options).get(`/${base}/${encodeURIComponent(name)}/${encodeURIComponent(version)}`);
|
|
879
|
+
}
|
|
880
|
+
else if (/^(verify|publish)-executable-package$/.test(options.command)) {
|
|
881
|
+
const name = positional(rest, "package name");
|
|
882
|
+
const version = positional(rest, "package version");
|
|
883
|
+
const action = options.command.split("-")[0];
|
|
884
|
+
result = await client(options).post(`/executable-packages/${encodeURIComponent(name)}/${encodeURIComponent(version)}/${action}`);
|
|
885
|
+
}
|
|
886
|
+
else if (options.command === "validate-model-artifact")
|
|
887
|
+
result = await client(options).get(`/model-artifacts/${encodeURIComponent(positional(rest, "model artifact id"))}/validate`);
|
|
888
|
+
else if (options.command === "register-model-artifact") {
|
|
889
|
+
const artifactId = positional(rest, "model artifact id");
|
|
890
|
+
const evaluationId = take(rest, "--evaluation-id");
|
|
891
|
+
if (!evaluationId)
|
|
892
|
+
throw new Error("--evaluation-id is required");
|
|
893
|
+
result = await client(options).post(`/models/${encodeURIComponent(artifactId)}/register`, { evaluation_id: evaluationId });
|
|
894
|
+
}
|
|
895
|
+
else if (/^(get|cancel)-model-package$/.test(options.command)) {
|
|
896
|
+
const packageId = positional(rest, "model package id");
|
|
897
|
+
const params = { package_version: take(rest, "--package-version") };
|
|
898
|
+
const endpoint = `/model-packages/${encodeURIComponent(packageId)}`;
|
|
899
|
+
result = options.command === "get-model-package" ? await client(options).get(endpoint, params) : await client(options).post(`${endpoint}/cancel`, undefined, params);
|
|
900
|
+
}
|
|
901
|
+
else if (options.command === "apply") {
|
|
902
|
+
const directory = positional(rest, "catalog directory");
|
|
903
|
+
const dryRun = boolean(rest, "--dry-run");
|
|
904
|
+
const targetProject = take(rest, "--project");
|
|
905
|
+
result = await applyCatalog(loadCatalog(resolvedPath(directory)), client(options), dryRun, targetProject);
|
|
906
|
+
}
|
|
907
|
+
else
|
|
908
|
+
throw new Error(`unknown business command: ${options.command}`);
|
|
909
|
+
if (rest.length)
|
|
910
|
+
throw new Error(`unknown arguments: ${rest.join(" ")}`);
|
|
911
|
+
process.stdout.write(typeof result === "string" ? `${result}\n` : `${JSON.stringify(result, null, 2)}\n`);
|
|
912
|
+
return exitCode;
|
|
659
913
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
914
|
+
async function fetchAll(api, endpoint, params = {}) { const items = []; for (let offset = 0;; offset += 500) {
|
|
915
|
+
const page = await api.get(endpoint, { ...params, limit: 500, offset });
|
|
916
|
+
if (Array.isArray(page))
|
|
917
|
+
return page;
|
|
918
|
+
if (!page || !Array.isArray(page.items))
|
|
919
|
+
throw new Error(`${endpoint} returned an invalid paginated response`);
|
|
920
|
+
items.push(...page.items);
|
|
921
|
+
if (page.total == null || items.length >= page.total || page.items.length < 500)
|
|
922
|
+
return items;
|
|
923
|
+
} }
|
|
663
924
|
async function listCollection(api, endpoint, params, rest) {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
925
|
+
const rawLimit = take(rest, "--limit");
|
|
926
|
+
const rawOffset = take(rest, "--offset");
|
|
927
|
+
if (rawLimit === undefined && rawOffset === undefined)
|
|
928
|
+
return fetchAll(api, endpoint, params);
|
|
929
|
+
const limit = Number(rawLimit ?? 50);
|
|
930
|
+
const offset = Number(rawOffset ?? 0);
|
|
931
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 500)
|
|
932
|
+
throw new Error("limit must be between 1 and 500");
|
|
933
|
+
if (!Number.isInteger(offset) || offset < 0)
|
|
934
|
+
throw new Error("offset must be >= 0");
|
|
935
|
+
return api.get(endpoint, { ...params, limit, offset });
|
|
670
936
|
}
|
|
671
|
-
|
|
672
937
|
module.exports = { BUSINESS_COMMANDS, fetchAll, listCollection, parse, runBusinessCli, usage, waitForDeployment, waitForJob };
|
|
673
|
-
|
|
674
938
|
if (require.main === module) {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
939
|
+
runBusinessCli(process.argv.slice(2)).then((code) => { process.exitCode = code; }).catch((error) => {
|
|
940
|
+
process.stderr.write(`error: ${error.message}\n`);
|
|
941
|
+
process.exitCode = 2;
|
|
942
|
+
});
|
|
679
943
|
}
|