insta 0.0.21 → 0.0.22
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/regions.js +12 -0
- package/dist/commands/secrets.js +55 -7
- package/dist/commands/services.js +67 -7
- package/dist/index.js +16 -2
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ApiClient } from '../api.js';
|
|
2
|
+
import { info, printJson } from '../util.js';
|
|
3
|
+
// insta regions — list the regions a postgres/compute service can be created in.
|
|
4
|
+
export async function regionsList(opts = {}) {
|
|
5
|
+
const api = await ApiClient.load();
|
|
6
|
+
const { regions } = await api.request('GET', '/regions');
|
|
7
|
+
if (opts.json)
|
|
8
|
+
return printJson(regions);
|
|
9
|
+
for (const r of regions)
|
|
10
|
+
info(`${r.slug} ${r.label}`);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=regions.js.map
|
package/dist/commands/secrets.js
CHANGED
|
@@ -28,15 +28,61 @@ export async function secrets(opts) {
|
|
|
28
28
|
info(` .gitignore += ${out} (credentials must never be committed)`);
|
|
29
29
|
info(' tip: `insta run -- <cmd>` injects these per-run with nothing written to disk');
|
|
30
30
|
}
|
|
31
|
+
// Render one branch's service-grouped secrets, then its unbound (branch-level) secrets.
|
|
32
|
+
function renderBranch(b) {
|
|
33
|
+
for (const s of b.services)
|
|
34
|
+
if (s.secrets.length) {
|
|
35
|
+
info(` ${s.type}/${s.name}`);
|
|
36
|
+
for (const n of s.secrets)
|
|
37
|
+
info(` ${n}`);
|
|
38
|
+
}
|
|
39
|
+
if (b.unbound.length) {
|
|
40
|
+
info(' (branch-level)');
|
|
41
|
+
for (const n of b.unbound)
|
|
42
|
+
info(` ${n}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Show the full binding tree: project-wide, then every branch grouped by service.
|
|
46
|
+
export async function secretsTree(opts) {
|
|
47
|
+
const api = await ApiClient.load();
|
|
48
|
+
const p = await requireProject();
|
|
49
|
+
const res = await api.rawRequest('GET', `/projects/${p.projectId}/secrets/tree`);
|
|
50
|
+
if (handleApproval(res))
|
|
51
|
+
return;
|
|
52
|
+
const tree = res.body;
|
|
53
|
+
if (opts.json)
|
|
54
|
+
return printJson(tree);
|
|
55
|
+
if (tree.projectWide.length) {
|
|
56
|
+
info('(project-wide)');
|
|
57
|
+
for (const n of tree.projectWide)
|
|
58
|
+
info(` ${n}`);
|
|
59
|
+
}
|
|
60
|
+
for (const b of tree.branches) {
|
|
61
|
+
info(`${b.name}${b.isDefault ? ' *' : ''}`);
|
|
62
|
+
renderBranch(b);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// List secret names for the current (or given) branch, grouped by service.
|
|
31
66
|
export async function secretsList(opts) {
|
|
32
67
|
const api = await ApiClient.load();
|
|
33
68
|
const p = await requireProject();
|
|
34
69
|
const branch = opts.branch ?? p.branch;
|
|
35
|
-
const res = await api.rawRequest('GET', `/projects/${p.projectId}/secrets
|
|
70
|
+
const res = await api.rawRequest('GET', `/projects/${p.projectId}/secrets/tree`);
|
|
36
71
|
if (handleApproval(res))
|
|
37
72
|
return;
|
|
38
|
-
|
|
39
|
-
|
|
73
|
+
const tree = res.body;
|
|
74
|
+
const b = tree.branches.find((x) => x.name === branch);
|
|
75
|
+
if (opts.json)
|
|
76
|
+
return printJson({ projectWide: tree.projectWide, branch: b });
|
|
77
|
+
if (tree.projectWide.length) {
|
|
78
|
+
info('(project-wide)');
|
|
79
|
+
for (const n of tree.projectWide)
|
|
80
|
+
info(` ${n}`);
|
|
81
|
+
}
|
|
82
|
+
if (b) {
|
|
83
|
+
info(`${b.name}`);
|
|
84
|
+
renderBranch(b);
|
|
85
|
+
}
|
|
40
86
|
}
|
|
41
87
|
async function readStdin() {
|
|
42
88
|
let data = '';
|
|
@@ -44,19 +90,21 @@ async function readStdin() {
|
|
|
44
90
|
data += chunk;
|
|
45
91
|
return data.trim();
|
|
46
92
|
}
|
|
47
|
-
// Set a user secret. Project-wide by default; --branch scopes it to one branch.
|
|
48
|
-
//
|
|
93
|
+
// Set a user secret. Project-wide by default; --branch scopes it to one branch. --service binds
|
|
94
|
+
// it to a branch service instead, which implies the current branch (binding requires one). Value
|
|
95
|
+
// comes from the argument, or stdin when omitted (keeps secret values out of shell history).
|
|
49
96
|
export async function secretsSet(name, value, opts) {
|
|
50
97
|
const api = await ApiClient.load();
|
|
51
98
|
const p = await requireProject();
|
|
52
99
|
const v = value ?? (await readStdin());
|
|
53
100
|
if (!v)
|
|
54
101
|
die('value is required (pass as an argument or on stdin)');
|
|
55
|
-
const
|
|
102
|
+
const branch = opts.service ? (opts.branch ?? p.branch) : opts.branch;
|
|
103
|
+
const payload = { value: v, ...(branch ? { branch } : {}), ...(opts.service ? { service: opts.service } : {}) };
|
|
56
104
|
const res = await api.rawRequest('PUT', `/projects/${p.projectId}/secrets/${encodeURIComponent(name)}`, payload);
|
|
57
105
|
if (handleApproval(res))
|
|
58
106
|
return;
|
|
59
|
-
info(`set ${name}
|
|
107
|
+
info(`set ${name}${opts.service ? ` → ${opts.service}` : ''} (${branch ? `branch ${branch}` : 'project-wide'})`);
|
|
60
108
|
}
|
|
61
109
|
export async function secretsUnset(name, opts) {
|
|
62
110
|
const api = await ApiClient.load();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { ApiClient, requireProject } from '../api.js';
|
|
3
3
|
import { info, printJson, handleApproval, renderNextActions } from '../util.js';
|
|
4
4
|
export const SERVICE_TYPES = ['postgres', 'storage', 'compute'];
|
|
5
|
+
const SERVICE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,38}$/;
|
|
5
6
|
export function q(branch) {
|
|
6
7
|
return branch ? `?branch=${encodeURIComponent(branch)}` : '';
|
|
7
8
|
}
|
|
@@ -11,6 +12,10 @@ export function assertType(type, allowed = SERVICE_TYPES) {
|
|
|
11
12
|
if (!allowed.includes(type))
|
|
12
13
|
throw new Error(`type must be ${allowed.join('|')}`);
|
|
13
14
|
}
|
|
15
|
+
export function assertServiceName(name) {
|
|
16
|
+
if (!SERVICE_NAME_RE.test(name))
|
|
17
|
+
throw new Error('service name must be lower-kebab (a-z, 0-9, -)');
|
|
18
|
+
}
|
|
14
19
|
// Parse a positive-integer machine count.
|
|
15
20
|
export function parseCount(raw) {
|
|
16
21
|
const n = Number(raw);
|
|
@@ -40,22 +45,46 @@ export function resolveComputeServiceId(services, name) {
|
|
|
40
45
|
throw new Error(`multiple compute services — specify one: ${compute.map((s) => s.name).join(', ')}`);
|
|
41
46
|
return compute[0].id;
|
|
42
47
|
}
|
|
43
|
-
//
|
|
48
|
+
// Map service-add options to the platform POST body. Pure, so it's unit-tested without a network
|
|
49
|
+
// mock (mirrors deployRequestBody in deploy.ts). Validation (which options are valid for which
|
|
50
|
+
// type) stays in servicesAdd, ahead of any network/config access.
|
|
51
|
+
export function servicesAddRequestBody(type, name, branch, opts) {
|
|
52
|
+
return {
|
|
53
|
+
type, name, ...(branch ? { branch } : {}), public: !!opts.public,
|
|
54
|
+
...(opts.image ? { image: opts.image } : {}), ...(opts.port ? { port: Number(opts.port) } : {}),
|
|
55
|
+
...(opts.region ? { region: opts.region } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
44
58
|
export async function servicesAdd(type, name, opts = {}) {
|
|
45
59
|
assertType(type);
|
|
46
60
|
if (opts.public && type !== 'storage')
|
|
47
61
|
throw new Error('--public is only valid for storage services');
|
|
62
|
+
if (opts.region && type === 'storage')
|
|
63
|
+
throw new Error('--region is not valid for storage services');
|
|
64
|
+
if (opts.image && type !== 'compute')
|
|
65
|
+
throw new Error('--image is only valid for compute services');
|
|
66
|
+
if (opts.port && type !== 'compute')
|
|
67
|
+
throw new Error('--port is only valid for compute services');
|
|
48
68
|
const api = await ApiClient.load();
|
|
49
69
|
const p = await requireProject();
|
|
50
70
|
const branch = opts.branch ?? p.branch;
|
|
51
|
-
const res = await api.rawRequest('POST', `/projects/${p.projectId}/services`,
|
|
71
|
+
const res = await api.rawRequest('POST', `/projects/${p.projectId}/services`, servicesAddRequestBody(type, name, branch, opts));
|
|
52
72
|
if (handleApproval(res))
|
|
53
73
|
return;
|
|
54
74
|
const svc = res.body.service;
|
|
55
75
|
const access = svc.type === 'storage' ? ` [${svc.public ? 'public' : 'private'}]` : '';
|
|
56
|
-
|
|
76
|
+
const img = svc.image ? ` running ${svc.image}${svc.port ? `:${svc.port}` : ''}` : '';
|
|
77
|
+
info(`added ${type} service ${name} on ${branch ?? 'default'} (${svc.id})${access}${svc.region ? ` ${svc.region}` : ''}${img}${svc.domain ? ` — ${svc.domain}` : ''}`);
|
|
57
78
|
renderNextActions(res.body.nextActions);
|
|
58
79
|
}
|
|
80
|
+
// Render one `services list` row. Pure, so it's unit-tested without a network mock (mirrors
|
|
81
|
+
// billingLines in billing.ts). Compute rows show the running image when the platform reports one.
|
|
82
|
+
export function serviceListLine(s) {
|
|
83
|
+
const extra = s.type === 'compute'
|
|
84
|
+
? ` x${s.machine_count}${s.image ? ` running ${s.image}${s.port ? `:${s.port}` : ''}` : ''}`
|
|
85
|
+
: s.type === 'storage' ? ` ${s.public ? 'public' : 'private'}` : '';
|
|
86
|
+
return `${s.type}/${s.name} [${s.status}]${extra}${s.domain ? ` ${s.domain}` : ''} ${s.id}`;
|
|
87
|
+
}
|
|
59
88
|
export async function servicesList(opts) {
|
|
60
89
|
const api = await ApiClient.load();
|
|
61
90
|
const p = await requireProject();
|
|
@@ -65,10 +94,8 @@ export async function servicesList(opts) {
|
|
|
65
94
|
return printJson(services);
|
|
66
95
|
if (!services.length)
|
|
67
96
|
return info(`(no services on ${branch ?? 'default'} — add one with \`insta services add <postgres|storage|compute> <name>\`)`);
|
|
68
|
-
for (const s of services)
|
|
69
|
-
|
|
70
|
-
info(`${s.type}/${s.name} [${s.status}]${extra}${s.domain ? ` ${s.domain}` : ''} ${s.id}`);
|
|
71
|
-
}
|
|
97
|
+
for (const s of services)
|
|
98
|
+
info(serviceListLine(s));
|
|
72
99
|
}
|
|
73
100
|
export async function servicesRemove(type, name, opts = {}) {
|
|
74
101
|
assertType(type);
|
|
@@ -82,6 +109,21 @@ export async function servicesRemove(type, name, opts = {}) {
|
|
|
82
109
|
return;
|
|
83
110
|
info(`removed ${type} service ${name} from ${branch ?? 'default'}`);
|
|
84
111
|
}
|
|
112
|
+
export async function servicesRename(type, name, newName, opts = {}) {
|
|
113
|
+
assertType(type);
|
|
114
|
+
assertServiceName(newName);
|
|
115
|
+
const api = await ApiClient.load();
|
|
116
|
+
const p = await requireProject();
|
|
117
|
+
const branch = opts.branch ?? p.branch;
|
|
118
|
+
const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(branch)}`);
|
|
119
|
+
const id = resolveServiceId(services, type, name);
|
|
120
|
+
const res = await api.rawRequest('POST', `/projects/${p.projectId}/services/${id}/rename`, { name: newName });
|
|
121
|
+
if (handleApproval(res))
|
|
122
|
+
return;
|
|
123
|
+
if (opts.json)
|
|
124
|
+
return printJson(res.body.service);
|
|
125
|
+
info(`renamed ${type} service ${name} to ${newName}`);
|
|
126
|
+
}
|
|
85
127
|
// Validate a bucket access-mode argument.
|
|
86
128
|
export function parseAccess(raw) {
|
|
87
129
|
if (raw === 'public')
|
|
@@ -134,4 +176,22 @@ export async function servicesUpgrade(type, name, spec, _opts) {
|
|
|
134
176
|
return printJson(res.body.service);
|
|
135
177
|
info(`upgraded ${type} ${name} to ${spec}`);
|
|
136
178
|
}
|
|
179
|
+
// insta services secrets <type> <name> — the secret names bound to a service.
|
|
180
|
+
export async function servicesSecrets(type, name, opts = {}) {
|
|
181
|
+
assertType(type);
|
|
182
|
+
const api = await ApiClient.load();
|
|
183
|
+
const p = await requireProject();
|
|
184
|
+
const { services } = await api.request('GET', `/projects/${p.projectId}/services${q(opts.branch ?? p.branch)}`);
|
|
185
|
+
const id = resolveServiceId(services, type, name);
|
|
186
|
+
const res = await api.rawRequest('GET', `/projects/${p.projectId}/services/${id}/secrets`);
|
|
187
|
+
if (handleApproval(res))
|
|
188
|
+
return;
|
|
189
|
+
const { secrets } = res.body;
|
|
190
|
+
if (opts.json)
|
|
191
|
+
return printJson(secrets);
|
|
192
|
+
if (!secrets.length)
|
|
193
|
+
return info(`(no secrets bound to ${type}/${name})`);
|
|
194
|
+
for (const n of secrets)
|
|
195
|
+
info(n);
|
|
196
|
+
}
|
|
137
197
|
//# sourceMappingURL=services.js.map
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import * as org from './commands/org.js';
|
|
|
11
11
|
import * as project from './commands/project.js';
|
|
12
12
|
import * as branch from './commands/branch.js';
|
|
13
13
|
import * as services from './commands/services.js';
|
|
14
|
+
import * as regions from './commands/regions.js';
|
|
14
15
|
import * as secretsCmd from './commands/secrets.js';
|
|
15
16
|
import { deploy } from './commands/deploy.js';
|
|
16
17
|
import * as computeCmd from './commands/compute.js';
|
|
@@ -97,28 +98,39 @@ br.command('merge <source>').description('Merge a branch service set into anothe
|
|
|
97
98
|
const svc = program.command('services').alias('svc').description('Manage project services (postgres|storage|compute)');
|
|
98
99
|
svc.command('add <type> <name>').description('Provision a service on demand (assigns a default domain for postgres/compute)')
|
|
99
100
|
.option('--branch <branch>', 'target branch (default: current)')
|
|
101
|
+
.option('--region <region>', 'region for postgres/compute, e.g. us-east (see `insta regions`)')
|
|
100
102
|
.option('--public', 'storage only: serve the bucket with anonymous public-read (default private)')
|
|
103
|
+
.option('--image <url>', 'compute only: run this container image at creation')
|
|
104
|
+
.option('--port <n>', 'compute only: port the image listens on (default 8080)')
|
|
101
105
|
.action(guard((type, name, o) => services.servicesAdd(type, name, o)));
|
|
102
106
|
svc.command('list').option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
103
107
|
.action(guard((o) => services.servicesList(o)));
|
|
104
108
|
svc.command('remove <type> <name>').description('Remove a service and destroy its resources')
|
|
105
109
|
.option('--branch <branch>', 'branch (default: current)')
|
|
106
110
|
.action(guard((type, name, o) => services.servicesRemove(type, name, o)));
|
|
111
|
+
svc.command('rename <type> <name> <new-name>').description('Rename a service and re-key its managed secret names')
|
|
112
|
+
.option('--json').option('--branch <branch>', 'branch (default: current)')
|
|
113
|
+
.action(guard((type, name, newName, o) => services.servicesRename(type, name, newName, o)));
|
|
107
114
|
svc.command('set-access <type> <name> <access>').description('Set a storage service bucket access mode (access: public|private)')
|
|
108
115
|
.option('--json').action(guard((type, name, access, o) => services.servicesSetAccess(type, name, access, o)));
|
|
109
116
|
svc.command('scale <type> <name> <number> [region]').description('Set a compute service machine count (paid plans only)')
|
|
110
117
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((type, name, number, region, o) => services.servicesScale(type, name, number, region, o)));
|
|
111
118
|
svc.command('upgrade <type> <name> <spec>').description('Change a compute/postgres service spec (paid plans only)')
|
|
112
119
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((type, name, spec, o) => services.servicesUpgrade(type, name, spec, o)));
|
|
120
|
+
svc.command('secrets <type> <name>').description("List a service's secret names")
|
|
121
|
+
.option('--branch <b>').option('--json').action(guard((type, name, o) => services.servicesSecrets(type, name, o)));
|
|
113
122
|
// ---- secrets (seam) ----
|
|
114
123
|
const sec = program.command('secrets').description('Fetch the credential bundle (secret seam) into .env')
|
|
115
124
|
.option('--branch <branch>').option('-o, --output <file>', 'output file (default .env)').option('--print', 'print instead of writing').option('--json')
|
|
116
125
|
.action(guard((o) => secretsCmd.secrets(o)));
|
|
117
|
-
sec.command('list').description('List secret names
|
|
126
|
+
sec.command('list').description('List secret names, grouped by service').option('--branch <branch>').option('--json').action(guard((o) => secretsCmd.secretsList(o)));
|
|
118
127
|
sec.command('set <name> [value]').description('Set a user secret (project-wide; value from stdin if omitted)')
|
|
119
|
-
.option('--branch <branch>', 'scope to one branch').
|
|
128
|
+
.option('--branch <branch>', 'scope to one branch').option('--service <type/name>', 'bind to a branch service (implies current branch)')
|
|
129
|
+
.action(guard((n, v, o) => secretsCmd.secretsSet(n, v, o)));
|
|
120
130
|
sec.command('unset <name>').description('Remove a user secret')
|
|
121
131
|
.option('--branch <branch>', 'scope to one branch').action(guard((n, o) => secretsCmd.secretsUnset(n, o)));
|
|
132
|
+
sec.command('tree').description('Show secrets as project → branch → service → secrets').option('--json')
|
|
133
|
+
.action(guard((o) => secretsCmd.secretsTree(o)));
|
|
122
134
|
// ---- deploy ----
|
|
123
135
|
program.command('deploy [dir]').description('Deploy a source directory (built remotely on Fly) or a prebuilt --image to a branch compute group')
|
|
124
136
|
.option('--image <url>', 'prebuilt container image to deploy (instead of a source dir)').option('--branch <b>').option('--group <g>').option('--port <p>')
|
|
@@ -142,6 +154,8 @@ compute.command('status [service]').description("Show a compute service's desire
|
|
|
142
154
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeStatus(service, o)));
|
|
143
155
|
// ---- manifest ----
|
|
144
156
|
program.command('manifest').description('Print an agent-legible view of the project environments').option('--json').action(guard((o) => manifest(o)));
|
|
157
|
+
// ---- regions ----
|
|
158
|
+
program.command('regions').description('List regions available for postgres/compute services').option('--json').action(guard((o) => regions.regionsList(o)));
|
|
145
159
|
// ---- observability ----
|
|
146
160
|
program.command('metrics <target> [group]').description('Service metrics (target: db|compute)')
|
|
147
161
|
.option('--branch <b>').option('--from <unix>').option('--to <unix>').option('--step <s>').option('--json')
|