Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/commands/regions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApiClient } from '../api.js'
import { info, printJson } from '../util.js'

// insta regions — list the regions a postgres/compute service can be created in.
export async function regionsList(opts: { json?: boolean } = {}): Promise<void> {
const api = await ApiClient.load()
const { regions } = await api.request('GET', '/regions')
if (opts.json) return printJson(regions)
for (const r of regions) info(`${r.slug} ${r.label}`)
}
6 changes: 4 additions & 2 deletions src/commands/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function resolveComputeServiceId(services: Array<{ id: string; type: stri

// ---- commands ----

export type ServicesAddOpts = { branch?: string; public?: boolean; image?: string; port?: string }
export type ServicesAddOpts = { branch?: string; public?: boolean; image?: string; port?: string; region?: string }

// Map service-add options to the platform POST body. Pure, so it's unit-tested without a network
// mock (mirrors deployRequestBody in deploy.ts). Validation (which options are valid for which
Expand All @@ -54,12 +54,14 @@ export function servicesAddRequestBody(type: string, name: string, branch: strin
return {
type, name, ...(branch ? { branch } : {}), public: !!opts.public,
...(opts.image ? { image: opts.image } : {}), ...(opts.port ? { port: Number(opts.port) } : {}),
...(opts.region ? { region: opts.region } : {}),
}
}

export async function servicesAdd(type: string, name: string, opts: ServicesAddOpts = {}): Promise<void> {
assertType(type)
if (opts.public && type !== 'storage') throw new Error('--public is only valid for storage services')
if (opts.region && type === 'storage') throw new Error('--region is not valid for storage services')
if (opts.image && type !== 'compute') throw new Error('--image is only valid for compute services')
if (opts.port && type !== 'compute') throw new Error('--port is only valid for compute services')
const api = await ApiClient.load()
Expand All @@ -70,7 +72,7 @@ export async function servicesAdd(type: string, name: string, opts: ServicesAddO
const svc = res.body.service
const access = svc.type === 'storage' ? ` [${svc.public ? 'public' : 'private'}]` : ''
const img = svc.image ? ` running ${svc.image}${svc.port ? `:${svc.port}` : ''}` : ''
info(`added ${type} service ${name} on ${branch ?? 'default'} (${svc.id})${access}${img}${svc.domain ? ` — ${svc.domain}` : ''}`)
info(`added ${type} service ${name} on ${branch ?? 'default'} (${svc.id})${access}${svc.region ? ` ${svc.region}` : ''}${img}${svc.domain ? ` — ${svc.domain}` : ''}`)
renderNextActions(res.body.nextActions)
}

Expand Down
5 changes: 5 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import * as org from './commands/org.js'
import * as project from './commands/project.js'
import * as branch from './commands/branch.js'
import * as services from './commands/services.js'
import * as regions from './commands/regions.js'
import * as secretsCmd from './commands/secrets.js'
import { deploy } from './commands/deploy.js'
import * as computeCmd from './commands/compute.js'
Expand Down Expand Up @@ -104,6 +105,7 @@ br.command('merge <source>').description('Merge a branch service set into anothe
const svc = program.command('services').alias('svc').description('Manage project services (postgres|storage|compute)')
svc.command('add <type> <name>').description('Provision a service on demand (assigns a default domain for postgres/compute)')
.option('--branch <branch>', 'target branch (default: current)')
.option('--region <region>', 'region for postgres/compute, e.g. us-east (see `insta regions`)')
.option('--public', 'storage only: serve the bucket with anonymous public-read (default private)')
.option('--image <url>', 'compute only: run this container image at creation')
.option('--port <n>', 'compute only: port the image listens on (default 8080)')
Expand Down Expand Up @@ -161,6 +163,9 @@ compute.command('status [service]').description("Show a compute service's desire
// ---- manifest ----
program.command('manifest').description('Print an agent-legible view of the project environments').option('--json').action(guard((o) => manifest(o)))

// ---- regions ----
program.command('regions').description('List regions available for postgres/compute services').option('--json').action(guard((o) => regions.regionsList(o)))

// ---- observability ----
program.command('metrics <target> [group]').description('Service metrics (target: db|compute)')
.option('--branch <b>').option('--from <unix>').option('--to <unix>').option('--step <s>').option('--json')
Expand Down
7 changes: 7 additions & 0 deletions test/services.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ describe('servicesAddRequestBody', () => {
it('carries --public through unchanged', () => {
expect(servicesAddRequestBody('storage', 'bkt', 'main', { public: true })).toMatchObject({ public: true })
})
it('sends region when passed, omits it when absent', () => {
expect(servicesAddRequestBody('postgres', 'db', 'main', { region: 'us-east' })).toMatchObject({ region: 'us-east' })
expect(servicesAddRequestBody('postgres', 'db', 'main', {})).not.toHaveProperty('region')
})
})

describe('servicesAdd validation (throws before any network/config access)', () => {
Expand All @@ -107,6 +111,9 @@ describe('servicesAdd validation (throws before any network/config access)', ()
it('rejects --public for a non-storage type', async () => {
await expect(servicesAdd('compute', 'api', { public: true })).rejects.toThrow(/--public is only valid for storage services/)
})
it('rejects --region for a storage type', async () => {
await expect(servicesAdd('storage', 'bkt', { region: 'us-east' })).rejects.toThrow(/--region is not valid for storage services/)
})
it('rejects an unknown service type before any option checks', async () => {
await expect(servicesAdd('lambda', 'x', {})).rejects.toThrow(/postgres\|storage\|compute/)
})
Expand Down
Loading