Skip to content

Commit bdf752d

Browse files
committed
feat: implement fly deployment action
1 parent 351dc3f commit bdf752d

21 files changed

+29679
-751
lines changed

.eslintrc.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"@typescript-eslint/func-call-spacing": ["error", "never"],
2525
"@typescript-eslint/no-array-constructor": "error",
2626
"@typescript-eslint/no-empty-interface": "error",
27-
"@typescript-eslint/no-explicit-any": "error",
27+
"@typescript-eslint/no-explicit-any": "warn",
2828
"@typescript-eslint/no-extraneous-class": "error",
2929
"@typescript-eslint/no-for-in-array": "error",
3030
"@typescript-eslint/no-inferrable-types": "error",
@@ -35,6 +35,8 @@
3535
"@typescript-eslint/no-unnecessary-type-assertion": "error",
3636
"@typescript-eslint/no-useless-constructor": "error",
3737
"@typescript-eslint/no-var-requires": "error",
38+
"no-shadow": "off",
39+
"@typescript-eslint/no-shadow": "error",
3840
"@typescript-eslint/prefer-for-of": "warn",
3941
"@typescript-eslint/prefer-function-type": "warn",
4042
"@typescript-eslint/prefer-includes": "error",

__tests__/app.test.ts

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import nock from 'nock'
2+
import {describe, it} from '@jest/globals'
3+
import {FLY_API_GRAPHQL} from '../src/fly/client'
4+
import {createApp, deleteApp} from '../src/fly/app'
5+
6+
describe('app', () => {
7+
const organizationId = 'personal'
8+
9+
it('creates app', async () => {
10+
nock(FLY_API_GRAPHQL)
11+
.post('/graphql')
12+
.reply(200, {
13+
data: {
14+
createApp: {
15+
app: {
16+
id: 'ctwntjgykzxhfncfwrfo',
17+
name: 'ctwntjgykzxhfncfwrfo',
18+
organization: {slug: 'supabase-dev'},
19+
config: {
20+
definition: {
21+
kill_timeout: 5,
22+
kill_signal: 'SIGINT',
23+
processes: [],
24+
experimental: {auto_rollback: true},
25+
services: [
26+
{
27+
processes: ['app'],
28+
protocol: 'tcp',
29+
internal_port: 8080,
30+
concurrency: {
31+
soft_limit: 20,
32+
hard_limit: 25,
33+
type: 'connections'
34+
},
35+
ports: [
36+
{port: 80, handlers: ['http'], force_https: true},
37+
{port: 443, handlers: ['tls', 'http']}
38+
],
39+
tcp_checks: [
40+
{
41+
interval: '15s',
42+
timeout: '2s',
43+
grace_period: '1s',
44+
restart_limit: 0
45+
}
46+
],
47+
http_checks: [],
48+
script_checks: []
49+
}
50+
],
51+
env: {}
52+
}
53+
},
54+
regions: [{name: 'Hong Kong, Hong Kong', code: 'hkg'}]
55+
}
56+
}
57+
}
58+
})
59+
const data = await createApp({
60+
name: 'ctwntjgykzxhfncfwrfo',
61+
organizationId
62+
})
63+
console.dir(data, {depth: 10})
64+
})
65+
66+
it('deletes app', async () => {
67+
nock(FLY_API_GRAPHQL)
68+
.post('/graphql')
69+
.reply(200, {
70+
data: {
71+
deleteApp: {
72+
organization: {
73+
id: organizationId
74+
}
75+
}
76+
}
77+
})
78+
const data = await deleteApp('ctwntjgykzxhfncfwrfo')
79+
console.dir(data, {depth: 10})
80+
})
81+
})

__tests__/machine.test.ts

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import nock from 'nock'
2+
import {describe, it} from '@jest/globals'
3+
import {
4+
ConnectionHandler,
5+
createMachine,
6+
deleteMachine,
7+
FLY_API_HOSTNAME,
8+
MachineConfig,
9+
startMachine,
10+
stopMachine
11+
} from '../src/fly/machine'
12+
13+
describe('machine', () => {
14+
it('creates machine', async () => {
15+
const name = 'ctwntjgykzxhfncfwrfo'
16+
nock(FLY_API_HOSTNAME)
17+
.post(`/v1/apps/${name}/machines`)
18+
.reply(200, {
19+
id: '9080e966ae7487',
20+
name: 'ctwntjgykzxhfncfwrfo',
21+
state: 'created',
22+
region: 'hkg',
23+
instance_id: '01GSYXD50E7F114CX7SRCT2H41',
24+
private_ip: 'fdaa:1:698b:a7b:a8:33bd:e6da:2',
25+
config: {
26+
env: {PGDATA: '/mnt/postgres/data'},
27+
init: {},
28+
image: 'sweatybridge/postgres:all-in-one',
29+
mounts: [
30+
{
31+
path: '/mnt/postgres',
32+
size_gb: 2,
33+
volume: 'vol_g67340kqe5pvydxw',
34+
name: 'ctwntjgykzxhfncfwrfo_pgdata'
35+
}
36+
],
37+
restart: {},
38+
services: [
39+
{
40+
protocol: 'tcp',
41+
internal_port: 8000,
42+
ports: [
43+
{port: 443, handlers: ['tls', 'http']},
44+
{port: 80, handlers: ['http']}
45+
]
46+
},
47+
{
48+
protocol: 'tcp',
49+
internal_port: 5432,
50+
ports: [{port: 5432, handlers: ['pg_tls']}]
51+
}
52+
],
53+
size: 'shared-cpu-4x',
54+
guest: {cpu_kind: 'shared', cpus: 4, memory_mb: 1024},
55+
checks: {
56+
pgrst: {
57+
port: 3000,
58+
type: 'http',
59+
interval: '15s',
60+
timeout: '10s',
61+
method: 'HEAD',
62+
path: '/'
63+
}
64+
}
65+
},
66+
image_ref: {
67+
registry: 'registry-1.docker.io',
68+
repository: 'sweatybridge/postgres',
69+
tag: 'all-in-one',
70+
digest:
71+
'sha256:df2014e5d037bf960a1240e300a913a97ef0d4486d22cbd1b7b92a7cbf487a7c',
72+
labels: null
73+
},
74+
created_at: '2023-02-23T10:34:20Z',
75+
updated_at: '0001-01-01T00:00:00Z',
76+
checks: [
77+
{
78+
name: 'pgrst',
79+
status: 'warning',
80+
output: 'the machine is created',
81+
updated_at: '2023-02-23T10:34:20.084624847Z'
82+
}
83+
]
84+
})
85+
const config: MachineConfig = {
86+
image: 'sweatybridge/postgres:all-in-one',
87+
size: 'shared-cpu-4x',
88+
env: {
89+
PGDATA: '/mnt/postgres/data'
90+
},
91+
services: [
92+
{
93+
ports: [
94+
{
95+
port: 443,
96+
handlers: [ConnectionHandler.TLS, ConnectionHandler.HTTP]
97+
},
98+
{
99+
port: 80,
100+
handlers: [ConnectionHandler.HTTP]
101+
}
102+
],
103+
protocol: 'tcp',
104+
internal_port: 8000
105+
},
106+
{
107+
ports: [
108+
{
109+
port: 5432,
110+
handlers: [ConnectionHandler.PG_TLS]
111+
}
112+
],
113+
protocol: 'tcp',
114+
internal_port: 5432
115+
}
116+
],
117+
mounts: [
118+
{
119+
volume: 'vol_g67340kqe5pvydxw',
120+
path: '/mnt/postgres'
121+
}
122+
],
123+
checks: {
124+
pgrst: {
125+
type: 'http',
126+
port: 3000,
127+
method: 'HEAD',
128+
path: '/',
129+
interval: '15s',
130+
timeout: '10s'
131+
}
132+
}
133+
}
134+
const data = await createMachine({name, config})
135+
console.dir(data, {depth: 10})
136+
})
137+
138+
it('deletes machine', async () => {
139+
const appId = 'ctwntjgykzxhfncfwrfo'
140+
const machineId = '4d891ed1b96987'
141+
nock(FLY_API_HOSTNAME)
142+
.delete(`/v1/apps/${appId}/machines/${machineId}`)
143+
.reply(200, {ok: true})
144+
const data = await deleteMachine({appId, machineId})
145+
console.dir(data, {depth: 5})
146+
})
147+
148+
it('stops machine', async () => {
149+
const appId = 'ctwntjgykzxhfncfwrfo'
150+
const machineId = '9080e966ae2487'
151+
nock(FLY_API_HOSTNAME)
152+
.post(`/v1/apps/${appId}/machines/${machineId}/stop`, {
153+
appId,
154+
machineId,
155+
signal: 'SIGTERM'
156+
})
157+
.reply(200, {ok: true})
158+
const data = await stopMachine({appId, machineId})
159+
console.dir(data, {depth: 5})
160+
})
161+
162+
it('starts machine', async () => {
163+
const appId = 'ctwntjgykzxhfncfwrfo'
164+
const machineId = '9080e966ae2487'
165+
nock(FLY_API_HOSTNAME)
166+
.post(`/v1/apps/${appId}/machines/${machineId}/start`)
167+
.reply(200, {ok: true})
168+
const data = await startMachine({appId, machineId})
169+
console.dir(data, {depth: 5})
170+
})
171+
})

__tests__/main.test.ts

+8-15
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,8 @@
1-
import {wait} from '../src/wait'
21
import * as process from 'process'
32
import * as cp from 'child_process'
43
import * as path from 'path'
54
import {expect, test} from '@jest/globals'
65

7-
test('throws invalid number', async () => {
8-
const input = parseInt('foo', 10)
9-
await expect(wait(input)).rejects.toThrow('milliseconds not a number')
10-
})
11-
12-
test('wait 500 ms', async () => {
13-
const start = new Date()
14-
await wait(500)
15-
const end = new Date()
16-
var delta = Math.abs(end.getTime() - start.getTime())
17-
expect(delta).toBeGreaterThan(450)
18-
})
19-
206
// shows how the runner will run a javascript action with env / stdout protocol
217
test('test runs', () => {
228
process.env['INPUT_MILLISECONDS'] = '500'
@@ -25,5 +11,12 @@ test('test runs', () => {
2511
const options: cp.ExecFileSyncOptions = {
2612
env: process.env
2713
}
28-
console.log(cp.execFileSync(np, [ip], options).toString())
14+
try {
15+
console.log(cp.execFileSync(np, [ip], options).toString())
16+
} catch (error) {
17+
// @ts-ignore TS18046: 'error' is of type 'unknown'.
18+
expect(error.stdout).toStrictEqual(
19+
Buffer.from('::error::missing required env: FLY_API_TOKEN\n')
20+
)
21+
}
2922
})

__tests__/network.test.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import nock from 'nock'
2+
import {describe, it} from '@jest/globals'
3+
import {FLY_API_GRAPHQL} from '../src/fly/client'
4+
import {
5+
AddressType,
6+
allocateIpAddress,
7+
releaseIpAddress
8+
} from '../src/fly/network'
9+
10+
describe('network', () => {
11+
it('allocates ip address', async () => {
12+
nock(FLY_API_GRAPHQL)
13+
.post('/graphql')
14+
.reply(200, {
15+
data: {
16+
allocateIpAddress: {
17+
ipAddress: {
18+
id: 'ip_lm6k9x4qw0g1qp7r',
19+
address: '2a09:8280:1::a:e929',
20+
type: 'v6',
21+
region: 'global',
22+
createdAt: '2023-02-23T11:01:36Z'
23+
}
24+
}
25+
}
26+
})
27+
const data = await allocateIpAddress({
28+
appId: 'ctwntjgykzxhfncfwrfo',
29+
type: AddressType.v6
30+
})
31+
console.dir(data, {depth: 5})
32+
})
33+
34+
it('releases ip address', async () => {
35+
nock(FLY_API_GRAPHQL)
36+
.post('/graphql')
37+
.reply(200, {
38+
data: {
39+
releaseIpAddress: {
40+
app: {
41+
name: 'ctwntjgykzxhfncfwrfo'
42+
}
43+
}
44+
}
45+
})
46+
const data = await releaseIpAddress({
47+
appId: 'ctwntjgykzxhfncfwrfo',
48+
ip: '2a09:8280:1::1:e80d'
49+
})
50+
console.dir(data, {depth: 5})
51+
})
52+
})

0 commit comments

Comments
 (0)