runcloud 0.2.188 → 0.2.190
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/dist/commands/dogfood.js +87 -34
- package/dist/config.js +3 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/runcloud-0.2.190.tgz +0 -0
- package/runcloud-0.2.188.tgz +0 -0
package/dist/commands/dogfood.js
CHANGED
|
@@ -4,7 +4,7 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { createInterface } from 'node:readline';
|
|
6
6
|
import { promisify } from 'node:util';
|
|
7
|
-
import { ApiClient, friendlyApiError } from '../api.js';
|
|
7
|
+
import { ApiClient, ApiError, friendlyApiError } from '../api.js';
|
|
8
8
|
import { requireCredentials } from '../config.js';
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
10
|
export const DOGFOOD_SECRET_GROUP = 'newly-dogfood';
|
|
@@ -171,26 +171,57 @@ async function projectOrgId(client, projectId) {
|
|
|
171
171
|
}
|
|
172
172
|
export async function pushDogfoodCreds(client, projectId, entries) {
|
|
173
173
|
const orgId = await projectOrgId(client, projectId);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
174
|
+
const path = `/orgs/${encodeURIComponent(orgId)}/secret-groups/${DOGFOOD_SECRET_GROUP}`;
|
|
175
|
+
if (Object.keys(entries).length === 0) {
|
|
176
|
+
try {
|
|
177
|
+
await client.delete(path);
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
if (!(err instanceof ApiError && err.status === 404))
|
|
181
|
+
throw err;
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
await client.put(path, { entries, exposeToSandbox: true, force: true });
|
|
186
|
+
}
|
|
187
|
+
export async function fetchServerCredentials(client) {
|
|
188
|
+
try {
|
|
189
|
+
const status = await client.get('/dogfood');
|
|
190
|
+
const provided = status?.serverCredentials;
|
|
191
|
+
return { github: provided?.github === true, gcp: provided?.gcp === true };
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
if (err instanceof ApiError && err.status === 404)
|
|
195
|
+
return { github: false, gcp: false };
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
export function credentialsToSupply(server, opts) {
|
|
200
|
+
return { github: Boolean(opts.githubTokenFile) || !server.github, gcp: opts.gcp && !server.gcp };
|
|
179
201
|
}
|
|
180
202
|
async function credsOnce(opts) {
|
|
181
203
|
const { client, apiUrl } = api();
|
|
182
204
|
const projectId = projectIdFrom(opts.project, apiUrl);
|
|
205
|
+
const supply = credentialsToSupply(await fetchServerCredentials(client), opts);
|
|
206
|
+
if (!supply.github && !supply.gcp) {
|
|
207
|
+
await pushDogfoodCreds(client, projectId, {});
|
|
208
|
+
console.log(`${apiUrl} mints the sessions' GitHub and GCP tokens itself; cleared any stored ones.`);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
183
211
|
const entries = {};
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
212
|
+
if (supply.github) {
|
|
213
|
+
const github = await readGithubToken({ tokenFile: opts.githubTokenFile });
|
|
214
|
+
entries.GH_TOKEN = github.token;
|
|
215
|
+
if (github.source === 'gh-cli') {
|
|
216
|
+
console.log('GitHub: using your gh CLI token (all of its scopes). A fine-grained token is safer: --github-token-file.');
|
|
217
|
+
}
|
|
188
218
|
}
|
|
189
|
-
if (
|
|
219
|
+
if (supply.gcp)
|
|
190
220
|
entries.GOOGLE_OAUTH_ACCESS_TOKEN = await mintDogfoodGcpToken();
|
|
191
221
|
await pushDogfoodCreds(client, projectId, entries);
|
|
192
222
|
console.log(`${new Date().toISOString()} credentials pushed to project ${projectId}: ${Object.keys(entries).join(', ')}` +
|
|
193
|
-
(
|
|
223
|
+
(supply.gcp ? ' (GCP token valid ~1h)' : ''));
|
|
224
|
+
return supply.gcp;
|
|
194
225
|
}
|
|
195
226
|
export function dogfoodSessionBody(opts) {
|
|
196
227
|
const body = {
|
|
@@ -341,25 +372,40 @@ export function registerDogfood(program) {
|
|
|
341
372
|
.action((opts) => run(async () => {
|
|
342
373
|
const { client, apiUrl } = api();
|
|
343
374
|
console.log(`control plane: ${apiUrl}`);
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
console.log(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
375
|
+
const supply = credentialsToSupply(await fetchServerCredentials(client), { githubTokenFile: opts.githubTokenFile, gcp: true });
|
|
376
|
+
const entries = {};
|
|
377
|
+
if (supply.gcp) {
|
|
378
|
+
console.log('1/4 GCP: minting a dogfood-agent token by impersonation...');
|
|
379
|
+
const gcpToken = await mintDogfoodGcpToken();
|
|
380
|
+
entries.GOOGLE_OAUTH_ACCESS_TOKEN = gcpToken;
|
|
381
|
+
console.log(' ok');
|
|
382
|
+
console.log('2/4 SOPS: dev must decrypt, prod must be refused...');
|
|
383
|
+
const root = await repoRoot();
|
|
384
|
+
if (root) {
|
|
385
|
+
const result = await verifySopsBoundary(root, gcpToken);
|
|
386
|
+
console.log(result.detail.replace(/^/gm, ' '));
|
|
387
|
+
if (!result.prodRefused)
|
|
388
|
+
throw new Error('the dogfood token can decrypt prod — stop and report this');
|
|
389
|
+
if (!result.dev)
|
|
390
|
+
throw new Error('the dogfood token cannot decrypt dev; see the error above');
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
console.log(' skipped: run setup from a monorepo checkout to verify (needs secrets/*.yaml and sops >= 3.10)');
|
|
394
|
+
}
|
|
356
395
|
}
|
|
357
396
|
else {
|
|
358
|
-
console.log(
|
|
397
|
+
console.log("1/4 GCP: the control plane mints the dogfood agent's token itself (dev sops key only)");
|
|
398
|
+
console.log("2/4 SOPS: nothing to prove from here; the agent account's IAM is the boundary");
|
|
399
|
+
}
|
|
400
|
+
if (supply.github) {
|
|
401
|
+
console.log('3/4 GitHub: reading a token...');
|
|
402
|
+
const github = await readGithubToken({ tokenFile: opts.githubTokenFile });
|
|
403
|
+
entries.GH_TOKEN = github.token;
|
|
404
|
+
console.log(` ok (${github.source})`);
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
console.log('3/4 GitHub: the control plane gives sessions a Newly GitHub App token (the dogfood repo only)');
|
|
359
408
|
}
|
|
360
|
-
console.log('3/4 GitHub: reading a token...');
|
|
361
|
-
const github = await readGithubToken({ tokenFile: opts.githubTokenFile });
|
|
362
|
-
console.log(` ok (${github.source})`);
|
|
363
409
|
console.log('4/4 Project...');
|
|
364
410
|
let projectId = opts.project ?? (readState()?.apiUrl === apiUrl ? readState()?.projectId : undefined);
|
|
365
411
|
if (!projectId) {
|
|
@@ -377,10 +423,17 @@ export function registerDogfood(program) {
|
|
|
377
423
|
console.log(` using ${projectId}`);
|
|
378
424
|
}
|
|
379
425
|
writeState({ projectId, apiUrl });
|
|
380
|
-
await pushDogfoodCreds(client, projectId,
|
|
381
|
-
console.log(
|
|
382
|
-
|
|
383
|
-
|
|
426
|
+
await pushDogfoodCreds(client, projectId, entries);
|
|
427
|
+
console.log(Object.keys(entries).length > 0
|
|
428
|
+
? ' credentials stored as the project\'s "newly-dogfood" secret group (dogfood sessions only)'
|
|
429
|
+
: ' no credentials stored: the control plane supplies them (any earlier ones were removed)');
|
|
430
|
+
if (supply.gcp) {
|
|
431
|
+
console.log('\nNext: `newly dogfood creds --watch` in a spare terminal (keeps the GCP token fresh),');
|
|
432
|
+
console.log('then `newly dogfood start "<what to do>"`.');
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
console.log('\nNext: `newly dogfood start "<what to do>"`.');
|
|
436
|
+
}
|
|
384
437
|
}));
|
|
385
438
|
dogfood
|
|
386
439
|
.command('creds')
|
|
@@ -390,8 +443,8 @@ export function registerDogfood(program) {
|
|
|
390
443
|
.option('--no-gcp', 'do not mint a GCP token (sessions then cannot use sops)')
|
|
391
444
|
.option('--watch', `keep refreshing every ${CREDS_REFRESH_MS / 60_000} minutes`, false)
|
|
392
445
|
.action((opts) => run(async () => {
|
|
393
|
-
await credsOnce(opts);
|
|
394
|
-
if (!opts.watch)
|
|
446
|
+
const mintedGcp = await credsOnce(opts);
|
|
447
|
+
if (!opts.watch || !mintedGcp)
|
|
395
448
|
return;
|
|
396
449
|
for (;;) {
|
|
397
450
|
await new Promise((resolve) => setTimeout(resolve, CREDS_REFRESH_MS));
|
package/dist/config.js
CHANGED
|
@@ -31,6 +31,9 @@ export function credentialsDisplayPath() {
|
|
|
31
31
|
return path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
32
32
|
}
|
|
33
33
|
export function loginCommandName() {
|
|
34
|
+
const override = process.env.NEWLY_LOGIN_COMMAND?.trim();
|
|
35
|
+
if (override)
|
|
36
|
+
return override;
|
|
34
37
|
return isRunCloud() ? 'runcloud login' : 'newly login';
|
|
35
38
|
}
|
|
36
39
|
export function normalizeApiUrl(url) {
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = '0.2.
|
|
1
|
+
export const CLI_VERSION = '0.2.190';
|
package/package.json
CHANGED
|
Binary file
|
package/runcloud-0.2.188.tgz
DELETED
|
Binary file
|