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
5 changes: 4 additions & 1 deletion src/commands/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export function deployedTarget(r: { url?: string; endpointHost?: string | null;
if (endpoint === undefined) return `${r.endpointHost}:${port ?? '?'} (endpoint unchanged; \`insta service list\` shows its protocol)`
return (endpoint === 'tcp+udp' ? ['tcp', 'udp'] : [endpoint]).map((pr) => `${pr}://${r.endpointHost}:${port ?? '?'}`).join(', ')
}
return r.url || '(no URL: a worker)'
if (r.url) return r.url
// No URL is a worker (port 0) or a private-only service; a redeploy that passed no --port can be either.
if (port === '0') return '(no URL: a worker)'
return `(no public URL: ${port === undefined ? 'a worker or ' : ''}a private service — \`insta compute public-access <service>\` shows its private address)`
}

// What a source directory resolved to. flyctl and local-docker build an image locally and hand it
Expand Down
6 changes: 5 additions & 1 deletion src/deploy-archive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,11 @@ export async function deployArchive(
// The endpoint the service is on (instacloud-platform#681), so the address gets its scheme;
// an older platform omits it, and a value this CLI does not know is treated as absent.
const endpoint = endpointHost && isV6Endpoint(res.body?.endpoint) ? res.body.endpoint as Exclude<Endpoint, 'http'> : undefined
if (typeof image !== 'string' || !image || typeof url !== 'string' || (!url && opts.port !== '0' && !endpointHost)) {
// An empty url is an answer, not a missing one: a worker has none, and neither does a
// private-only HTTP service (the platform's insta-compute adapter returns '' for one). Only a
// v6-endpoint service is owed something in its place, the host: one this deploy asked for
// with --endpoint, or one the platform reports (a redeploy keeps its endpoint and passes none).
if (typeof image !== 'string' || !image || typeof url !== 'string' || (!url && !endpointHost && (isV6Endpoint(opts.endpoint) || isV6Endpoint(res.body?.endpoint)))) {
throw new Error('the deploy finished but the platform returned no image or URL for it — check `insta status`')
}
const optionalString = (field: string, v: unknown): string | undefined => {
Expand Down
17 changes: 13 additions & 4 deletions test/deploy-archive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,19 +120,28 @@ describe('deployArchive — the gated call and the poll after it', () => {
expect(process.exitCode).toBe(2)
})

it('accepts an empty worker URL but rejects it for a TCP deploy', async () => {
it('accepts the empty URL of a worker and of a private HTTP service', async () => {
// A private-only service on a real port goes live with url '' (SUP2-536, SUP2-543).
for (const port of ['0', '8080']) {
const { api: a, calls } = api([
{ status: 202, body: { operationId: 'op_1' } },
{ status: 200, body: { ...live, url: '' } },
])
const result = deployArchive(a, 'p1', ref, 'main', { port }, Date.now, noWait)
if (port === '0') await expect(result).resolves.toMatchObject({ url: '', image: live.imageRef })
else await expect(result).rejects.toThrow(/no image or URL/)
await expect(deployArchive(a, 'p1', ref, 'main', { port }, Date.now, noWait)).resolves.toMatchObject({ url: '', image: live.imageRef })
expect(calls[0]!.body.port).toBe(Number(port))
}
})

it('still rejects a live deploy with no image or a non-string URL', async () => {
for (const body of [{ ...live, imageRef: '' }, { ...live, url: undefined }]) {
const { api: a } = api([
{ status: 202, body: { operationId: 'op_1' } },
{ status: 200, body },
])
await expect(deployArchive(a, 'p1', ref, 'main', { port: '8080' }, Date.now, noWait)).rejects.toThrow(/no image or URL/)
}
})

it('carries every deploy option in the one gated body', async () => {
const { api: a, calls } = api([
{ status: 202, body: { status: 'accepted', operationId: 'op_1', state: 'queued' } },
Expand Down
12 changes: 11 additions & 1 deletion test/endpoint-tcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ describe('deploy --endpoint', () => {
['a tcp deploy', { url: '', endpointHost: HOST }, '7000', 'tcp', `tcp://${HOST}:7000`],
['an http deploy', { url: 'https://app.example' }, '7000', undefined, 'https://app.example'],
['a worker', { url: '' }, '0', undefined, '(no URL: a worker)'],
['a private HTTP service', { url: '' }, '8080', undefined, '(no public URL: a private service — `insta compute public-access <service>` shows its private address)'],
['a redeploy with no URL and no --port', { url: '' }, undefined, undefined, '(no public URL: a worker or a private service — `insta compute public-access <service>` shows its private address)'],
])('reports %s', (_label, r, port, endpoint, want) => {
expect(deployedTarget(r, port, endpoint)).toBe(want)
})
Expand All @@ -94,9 +96,17 @@ describe('archive deploy of a tcp service', () => {
expect(calls[0]!.body).toMatchObject({ endpoint: 'tcp', port: 7000 })
})

// The control: the same empty URL with no tcp host is still the "no URL" failure it always was.
// The control: a tcp deploy answered with neither a URL nor a tcp host is still the "no URL"
// failure it always was. (Without --endpoint an empty URL is a private HTTP service: SUP2-543.)
it('still refuses an empty URL with no tcp host', async () => {
const { api: a } = api({ url: '' })
await expect(deployArchive(a, 'p1', ref, 'main', { port: '7000', endpoint: 'tcp' }, Date.now, noWait)).rejects.toThrow(/no image or URL/)
})

// A redeploy passes no --endpoint, but the platform reports the endpoint the service kept: a tcp
// service answered with neither a URL nor a host is still refused, not taken for a private HTTP one.
it('refuses an empty URL with no host when the platform reports a v6 endpoint on a redeploy', async () => {
const { api: a } = api({ url: '', endpoint: 'tcp' })
await expect(deployArchive(a, 'p1', ref, 'main', { port: '7000' }, Date.now, noWait)).rejects.toThrow(/no image or URL/)
})
})
Expand Down
Loading