@cloudbeat/atlas-cli 0.1.0 → 0.3.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/README.md +6 -0
- package/dist/api.js +50 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -245,3 +245,9 @@ To avoid repeating yourself:
|
|
|
245
245
|
atlas config set project SKT
|
|
246
246
|
atlas config set url https://atlas.cloudbeat.io
|
|
247
247
|
```
|
|
248
|
+
|
|
249
|
+
## Compatibility
|
|
250
|
+
|
|
251
|
+
Works with any Atlas instance. From 0.3.0 the CLI talks to a new API path, and
|
|
252
|
+
falls back automatically when it finds an older instance that does not serve it —
|
|
253
|
+
so you do not need to match versions between the CLI and your Atlas server.
|
package/dist/api.js
CHANGED
|
@@ -8,6 +8,9 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const config_1 = require("./config");
|
|
10
10
|
const ui_1 = require("./ui");
|
|
11
|
+
/** Where the control-plane API lives today, and where it used to live. */
|
|
12
|
+
const API_PREFIX = '/app/api';
|
|
13
|
+
const LEGACY_API_PREFIX = '/admin/api';
|
|
11
14
|
// ── Client ───────────────────────────────────────────────────────────────────
|
|
12
15
|
class AtlasApi {
|
|
13
16
|
url;
|
|
@@ -22,6 +25,33 @@ class AtlasApi {
|
|
|
22
25
|
get baseUrl() {
|
|
23
26
|
return this.url;
|
|
24
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The control-plane API prefix.
|
|
30
|
+
*
|
|
31
|
+
* It moved from /admin/api to /app/api when the browser UI moved to /app.
|
|
32
|
+
* Servers keep serving the old prefix so CLIs released before that keep
|
|
33
|
+
* working; this handles the other direction — a current CLI pointed at an
|
|
34
|
+
* Atlas that predates the move. The first 404 under the new prefix switches
|
|
35
|
+
* to the old one for the life of the process, so there is no extra round
|
|
36
|
+
* trip against an up-to-date server.
|
|
37
|
+
*/
|
|
38
|
+
prefix = API_PREFIX;
|
|
39
|
+
prefixResolved = false;
|
|
40
|
+
get api() {
|
|
41
|
+
return this.prefix;
|
|
42
|
+
}
|
|
43
|
+
/** Fetch that retries once under the legacy prefix if the route is absent. */
|
|
44
|
+
async fetchApi(routePath, init) {
|
|
45
|
+
const res = await fetch(`${this.url}${routePath}`, init);
|
|
46
|
+
if (res.status === 404 && !this.prefixResolved && routePath.startsWith(API_PREFIX)) {
|
|
47
|
+
this.prefix = LEGACY_API_PREFIX;
|
|
48
|
+
this.prefixResolved = true;
|
|
49
|
+
const legacy = routePath.replace(API_PREFIX, LEGACY_API_PREFIX);
|
|
50
|
+
return { res: await fetch(`${this.url}${legacy}`, init), routePath: legacy };
|
|
51
|
+
}
|
|
52
|
+
this.prefixResolved = true;
|
|
53
|
+
return { res, routePath };
|
|
54
|
+
}
|
|
25
55
|
authHeaders() {
|
|
26
56
|
if (!this.token) {
|
|
27
57
|
throw new ui_1.CliError('Not authenticated. Run `atlas login`, or set ATLAS_TOKEN to an API token.', ui_1.EXIT.USAGE);
|
|
@@ -34,9 +64,9 @@ class AtlasApi {
|
|
|
34
64
|
const headers = auth ? this.authHeaders() : {};
|
|
35
65
|
if (opts.body !== undefined)
|
|
36
66
|
headers['Content-Type'] = 'application/json';
|
|
37
|
-
let
|
|
67
|
+
let attempt;
|
|
38
68
|
try {
|
|
39
|
-
|
|
69
|
+
attempt = await this.fetchApi(routePath, {
|
|
40
70
|
method,
|
|
41
71
|
headers,
|
|
42
72
|
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
@@ -45,7 +75,7 @@ class AtlasApi {
|
|
|
45
75
|
catch (err) {
|
|
46
76
|
throw new ui_1.CliError(`Cannot reach ${this.url}: ${err.message}`, ui_1.EXIT.ERROR);
|
|
47
77
|
}
|
|
48
|
-
return this.parse(method, routePath, res);
|
|
78
|
+
return this.parse(method, attempt.routePath, attempt.res);
|
|
49
79
|
}
|
|
50
80
|
async parse(method, routePath, res) {
|
|
51
81
|
const text = await res.text();
|
|
@@ -67,9 +97,11 @@ class AtlasApi {
|
|
|
67
97
|
}
|
|
68
98
|
// ── auth ──────────────────────────────────────────────────────────────────
|
|
69
99
|
async login(username, password) {
|
|
70
|
-
|
|
100
|
+
// Usually the very first call a CLI makes, so this is where the prefix
|
|
101
|
+
// normally gets resolved against an older server.
|
|
102
|
+
let attempt;
|
|
71
103
|
try {
|
|
72
|
-
|
|
104
|
+
attempt = await this.fetchApi(`${this.api}/auth/login`, {
|
|
73
105
|
method: 'POST',
|
|
74
106
|
headers: { 'Content-Type': 'application/json' },
|
|
75
107
|
body: JSON.stringify({ username, password }),
|
|
@@ -78,14 +110,14 @@ class AtlasApi {
|
|
|
78
110
|
catch (err) {
|
|
79
111
|
throw new ui_1.CliError(`Cannot reach ${this.url}: ${err.message}`, ui_1.EXIT.ERROR);
|
|
80
112
|
}
|
|
81
|
-
return this.parse('POST',
|
|
113
|
+
return this.parse('POST', attempt.routePath, attempt.res);
|
|
82
114
|
}
|
|
83
115
|
async me() {
|
|
84
|
-
return this.request('GET',
|
|
116
|
+
return this.request('GET', `${this.api}/auth/me`);
|
|
85
117
|
}
|
|
86
118
|
// ── projects & files ──────────────────────────────────────────────────────
|
|
87
119
|
async projects() {
|
|
88
|
-
return this.request('GET',
|
|
120
|
+
return this.request('GET', `${this.api}/projects`);
|
|
89
121
|
}
|
|
90
122
|
/** Projects are addressed by name on the CLI; the API wants the id. */
|
|
91
123
|
async projectByName(name) {
|
|
@@ -98,7 +130,7 @@ class AtlasApi {
|
|
|
98
130
|
return hit;
|
|
99
131
|
}
|
|
100
132
|
async files(projectId) {
|
|
101
|
-
return this.request('GET',
|
|
133
|
+
return this.request('GET', `${this.api}/projects/${projectId}/files`);
|
|
102
134
|
}
|
|
103
135
|
async uploadFiles(projectId, paths) {
|
|
104
136
|
const form = new FormData();
|
|
@@ -110,7 +142,7 @@ class AtlasApi {
|
|
|
110
142
|
}
|
|
111
143
|
let res;
|
|
112
144
|
try {
|
|
113
|
-
res = await fetch(`${this.url}
|
|
145
|
+
res = await fetch(`${this.url}${this.api}/projects/${projectId}/files`, {
|
|
114
146
|
method: 'POST',
|
|
115
147
|
headers: this.authHeaders(), // no Content-Type: fetch sets the multipart boundary
|
|
116
148
|
body: form,
|
|
@@ -119,33 +151,33 @@ class AtlasApi {
|
|
|
119
151
|
catch (err) {
|
|
120
152
|
throw new ui_1.CliError(`Upload failed: ${err.message}`, ui_1.EXIT.ERROR);
|
|
121
153
|
}
|
|
122
|
-
return this.parse('POST',
|
|
154
|
+
return this.parse('POST', `${this.api}/projects/${projectId}/files`, res);
|
|
123
155
|
}
|
|
124
156
|
async downloadFile(projectId, filename) {
|
|
125
|
-
const res = await fetch(`${this.url}
|
|
157
|
+
const res = await fetch(`${this.url}${this.api}/projects/${projectId}/files/${encodeURIComponent(filename)}/download`, { headers: this.authHeaders() });
|
|
126
158
|
if (!res.ok)
|
|
127
159
|
throw new ui_1.CliError(`Download failed: ${res.status} ${res.statusText}`, ui_1.EXIT.ERROR);
|
|
128
160
|
return Buffer.from(await res.arrayBuffer());
|
|
129
161
|
}
|
|
130
162
|
async deleteFile(projectId, filename) {
|
|
131
|
-
await this.request('DELETE',
|
|
163
|
+
await this.request('DELETE', `${this.api}/projects/${projectId}/files/${encodeURIComponent(filename)}`);
|
|
132
164
|
}
|
|
133
165
|
// ── fleet ─────────────────────────────────────────────────────────────────
|
|
134
166
|
async servers() {
|
|
135
|
-
return this.request('GET',
|
|
167
|
+
return this.request('GET', `${this.api}/servers`);
|
|
136
168
|
}
|
|
137
169
|
async clusters() {
|
|
138
|
-
return this.request('GET',
|
|
170
|
+
return this.request('GET', `${this.api}/clusters`);
|
|
139
171
|
}
|
|
140
172
|
// ── runs ──────────────────────────────────────────────────────────────────
|
|
141
173
|
async run(projectId, body) {
|
|
142
|
-
return this.request('POST',
|
|
174
|
+
return this.request('POST', `${this.api}/projects/${projectId}/run`, { body });
|
|
143
175
|
}
|
|
144
176
|
async queueEntry(queueId) {
|
|
145
|
-
return this.request('GET',
|
|
177
|
+
return this.request('GET', `${this.api}/tests/queue/${queueId}`);
|
|
146
178
|
}
|
|
147
179
|
async stop(testId) {
|
|
148
|
-
return this.request('POST',
|
|
180
|
+
return this.request('POST', `${this.api}/tests/${encodeURIComponent(testId)}/stop`, { body: {} });
|
|
149
181
|
}
|
|
150
182
|
// ── results (public dashboard API) ─────────────────────────────────────────
|
|
151
183
|
async summary(testId) {
|