@fly.io/sdk 0.1.0
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/LICENSE +21 -0
- package/dist/app.d.ts +181 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +212 -0
- package/dist/client.d.ts +42 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +200 -0
- package/dist/errors.d.ts +48 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +85 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/machine.d.ts +319 -0
- package/dist/machine.d.ts.map +1 -0
- package/dist/machine.js +249 -0
- package/dist/network.d.ts +45 -0
- package/dist/network.d.ts.map +1 -0
- package/dist/network.js +44 -0
- package/dist/organization.d.ts +20 -0
- package/dist/organization.d.ts.map +1 -0
- package/dist/organization.js +22 -0
- package/dist/regions.d.ts +35 -0
- package/dist/regions.d.ts.map +1 -0
- package/dist/regions.js +53 -0
- package/dist/secret.d.ts +55 -0
- package/dist/secret.d.ts.map +1 -0
- package/dist/secret.js +53 -0
- package/dist/token.d.ts +14 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +16 -0
- package/dist/types.d.ts +937 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +40 -0
- package/dist/volume.d.ts +72 -0
- package/dist/volume.d.ts.map +1 -0
- package/dist/volume.js +45 -0
- package/package.json +54 -0
- package/src/app.ts +462 -0
- package/src/client.ts +262 -0
- package/src/errors.ts +135 -0
- package/src/index.ts +141 -0
- package/src/machine.ts +644 -0
- package/src/network.ts +87 -0
- package/src/organization.ts +43 -0
- package/src/regions.ts +94 -0
- package/src/secret.ts +101 -0
- package/src/token.ts +29 -0
- package/src/types.ts +1072 -0
- package/src/volume.ts +124 -0
package/src/app.ts
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
// App management for Fly Machines REST + GraphQL API.
|
|
2
|
+
// Types aligned with OpenAPI spec at https://docs.machines.dev/spec/openapi3.json
|
|
3
|
+
|
|
4
|
+
import { Client } from './client.ts'
|
|
5
|
+
import type { FlyResult } from './errors.ts'
|
|
6
|
+
import type {
|
|
7
|
+
App as ApiApp,
|
|
8
|
+
AppOrganizationInfo as ApiAppOrganizationInfo,
|
|
9
|
+
AppSecret,
|
|
10
|
+
AppSecrets,
|
|
11
|
+
AppSecretsUpdateRequest,
|
|
12
|
+
AppSecretsUpdateResp,
|
|
13
|
+
CertificateCheckResponse,
|
|
14
|
+
CertificateDetail,
|
|
15
|
+
CreateAcmeCertificateRequest,
|
|
16
|
+
CreateAppDeployTokenRequest,
|
|
17
|
+
CreateAppResponse,
|
|
18
|
+
CreateCustomCertificateRequest,
|
|
19
|
+
DeleteAppSecretResponse,
|
|
20
|
+
DeleteSecretkeyResponse,
|
|
21
|
+
DecryptSecretkeyRequest,
|
|
22
|
+
DecryptSecretkeyResponse,
|
|
23
|
+
DestroyCustomCertificateResponse,
|
|
24
|
+
EncryptSecretkeyRequest,
|
|
25
|
+
EncryptSecretkeyResponse,
|
|
26
|
+
IPAssignment,
|
|
27
|
+
ListAppsResponse as ApiListAppsResponse,
|
|
28
|
+
ListCertificatesResponse,
|
|
29
|
+
ListIPAssignmentsResponse,
|
|
30
|
+
SecretKey,
|
|
31
|
+
SecretKeys,
|
|
32
|
+
SetAppSecretRequest,
|
|
33
|
+
SetAppSecretResponse,
|
|
34
|
+
SetSecretkeyRequest,
|
|
35
|
+
SetSecretkeyResponse,
|
|
36
|
+
SignSecretkeyRequest,
|
|
37
|
+
SignSecretkeyResponse,
|
|
38
|
+
VerifySecretkeyRequest,
|
|
39
|
+
} from './types.ts'
|
|
40
|
+
|
|
41
|
+
export type ListAppRequest = string
|
|
42
|
+
|
|
43
|
+
/** Matches OpenAPI ListAppsResponse schema. */
|
|
44
|
+
export type ListAppResponse = ApiListAppsResponse
|
|
45
|
+
|
|
46
|
+
/** Query params for GET /apps. org_slug is required, app_role is optional. */
|
|
47
|
+
export interface ListAppsParams {
|
|
48
|
+
org_slug: string
|
|
49
|
+
app_role?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type GetAppRequest = string
|
|
53
|
+
|
|
54
|
+
const getAppQuery = `query($name: String!) {
|
|
55
|
+
app(name: $name) {
|
|
56
|
+
name
|
|
57
|
+
status
|
|
58
|
+
organization {
|
|
59
|
+
name
|
|
60
|
+
slug
|
|
61
|
+
}
|
|
62
|
+
ipAddresses {
|
|
63
|
+
nodes {
|
|
64
|
+
type
|
|
65
|
+
region
|
|
66
|
+
address
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}`
|
|
71
|
+
|
|
72
|
+
export enum AppStatus {
|
|
73
|
+
deployed = 'deployed',
|
|
74
|
+
pending = 'pending',
|
|
75
|
+
suspended = 'suspended',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Matches OpenAPI AppOrganizationInfo schema. */
|
|
79
|
+
export type AppOrganizationInfo = ApiAppOrganizationInfo
|
|
80
|
+
|
|
81
|
+
/** Matches OpenAPI App schema — used in both GET /apps/{app_name} and ListAppsResponse. */
|
|
82
|
+
export type AppInfo = ApiApp
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Full app response from GraphQL getAppDetailed.
|
|
86
|
+
* Extends REST AppInfo with ipAddresses from the GraphQL query.
|
|
87
|
+
*/
|
|
88
|
+
export interface AppResponse {
|
|
89
|
+
name: string
|
|
90
|
+
status: AppStatus
|
|
91
|
+
organization: {
|
|
92
|
+
name: string
|
|
93
|
+
slug: string
|
|
94
|
+
}
|
|
95
|
+
ipAddresses: IPAddress[]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface IPAddress {
|
|
99
|
+
type: string
|
|
100
|
+
region?: string
|
|
101
|
+
address: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Matches OpenAPI CreateAppRequest schema.
|
|
106
|
+
* Note: the spec uses `name` (not `app_name`) for the app name field.
|
|
107
|
+
*/
|
|
108
|
+
export interface CreateAppRequest {
|
|
109
|
+
org_slug: string
|
|
110
|
+
name: string
|
|
111
|
+
network?: string
|
|
112
|
+
enable_subdomains?: boolean
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type DeleteAppRequest = string
|
|
116
|
+
|
|
117
|
+
export interface ListCertificatesRequest {
|
|
118
|
+
app_name: string
|
|
119
|
+
filter?: string
|
|
120
|
+
cursor?: string
|
|
121
|
+
limit?: number
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export interface RequestAcmeCertificateRequest {
|
|
125
|
+
app_name: string
|
|
126
|
+
request: CreateAcmeCertificateRequest
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface RequestCustomCertificateRequest {
|
|
130
|
+
app_name: string
|
|
131
|
+
request: CreateCustomCertificateRequest
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface CertificateRequest {
|
|
135
|
+
app_name: string
|
|
136
|
+
hostname: string
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export interface CreateDeployTokenRequest {
|
|
140
|
+
app_name: string
|
|
141
|
+
request: CreateAppDeployTokenRequest
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ListSecretKeysRequest {
|
|
145
|
+
app_name: string
|
|
146
|
+
min_version?: string
|
|
147
|
+
types?: string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface SecretKeyRequest {
|
|
151
|
+
app_name: string
|
|
152
|
+
secret_name: string
|
|
153
|
+
min_version?: string
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface SetSecretKeyRequest {
|
|
157
|
+
app_name: string
|
|
158
|
+
secret_name: string
|
|
159
|
+
request: SetSecretkeyRequest
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface SecretKeyDecryptRequest {
|
|
163
|
+
app_name: string
|
|
164
|
+
secret_name: string
|
|
165
|
+
request: DecryptSecretkeyRequest
|
|
166
|
+
min_version?: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface SecretKeyEncryptRequest {
|
|
170
|
+
app_name: string
|
|
171
|
+
secret_name: string
|
|
172
|
+
request: EncryptSecretkeyRequest
|
|
173
|
+
min_version?: string
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface SecretKeySignRequest {
|
|
177
|
+
app_name: string
|
|
178
|
+
secret_name: string
|
|
179
|
+
request: SignSecretkeyRequest
|
|
180
|
+
min_version?: string
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface SecretKeyVerifyRequest {
|
|
184
|
+
app_name: string
|
|
185
|
+
secret_name: string
|
|
186
|
+
request: VerifySecretkeyRequest
|
|
187
|
+
min_version?: string
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface ListSecretsRequest {
|
|
191
|
+
app_name: string
|
|
192
|
+
min_version?: string
|
|
193
|
+
show_secrets?: boolean
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface UpdateSecretsRequest {
|
|
197
|
+
app_name: string
|
|
198
|
+
request: AppSecretsUpdateRequest
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface SecretRequest {
|
|
202
|
+
app_name: string
|
|
203
|
+
secret_name: string
|
|
204
|
+
min_version?: string
|
|
205
|
+
show_secrets?: boolean
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface SetSecretRequest {
|
|
209
|
+
app_name: string
|
|
210
|
+
secret_name: string
|
|
211
|
+
request: SetAppSecretRequest
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export interface AssignIPAddressRequest {
|
|
215
|
+
app_name: string
|
|
216
|
+
request: {
|
|
217
|
+
region?: string
|
|
218
|
+
service_name?: string
|
|
219
|
+
type?: string
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface DeleteIPAddressRequest {
|
|
224
|
+
app_name: string
|
|
225
|
+
ip: string
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export class App {
|
|
229
|
+
private client: Client
|
|
230
|
+
|
|
231
|
+
constructor(client: Client) {
|
|
232
|
+
this.client = client
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async listApps(org_slug: ListAppRequest): Promise<FlyResult<ListAppResponse>> {
|
|
236
|
+
return await this.client.restOrThrow(`apps?org_slug=${org_slug}`)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** List apps with full query params (org_slug + optional app_role filter). */
|
|
240
|
+
async listAppsWithParams(params: ListAppsParams): Promise<FlyResult<ListAppResponse>> {
|
|
241
|
+
const query = new URLSearchParams({ org_slug: params.org_slug })
|
|
242
|
+
if (params.app_role) {
|
|
243
|
+
query.set('app_role', params.app_role)
|
|
244
|
+
}
|
|
245
|
+
return await this.client.restOrThrow(`apps?${query.toString()}`)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async getApp(app_name: GetAppRequest): Promise<FlyResult<AppInfo>> {
|
|
249
|
+
return await this.client.restOrThrow(`apps/${app_name}`)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async getAppDetailed(app_name: GetAppRequest): Promise<FlyResult<AppResponse>> {
|
|
253
|
+
const result = await this.client.gqlPostOrThrow<string, { app: AppResponse & { ipAddresses: { nodes: IPAddress[] } } }>({
|
|
254
|
+
query: getAppQuery,
|
|
255
|
+
variables: { name: app_name },
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
if (result instanceof Error) {
|
|
259
|
+
return result
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const { app } = result
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
...app,
|
|
266
|
+
ipAddresses: app.ipAddresses.nodes,
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async createApp(payload: CreateAppRequest): Promise<FlyResult<void>> {
|
|
271
|
+
return await this.client.restOrThrow('apps', 'POST', payload)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async deleteApp(app_name: DeleteAppRequest): Promise<FlyResult<void>> {
|
|
275
|
+
return await this.client.restOrThrow(`apps/${app_name}`, 'DELETE')
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async listCertificates(payload: ListCertificatesRequest): Promise<FlyResult<ListCertificatesResponse>> {
|
|
279
|
+
const { app_name, filter, cursor, limit } = payload
|
|
280
|
+
const params = new URLSearchParams()
|
|
281
|
+
if (filter) {
|
|
282
|
+
params.set('filter', filter)
|
|
283
|
+
}
|
|
284
|
+
if (cursor) {
|
|
285
|
+
params.set('cursor', cursor)
|
|
286
|
+
}
|
|
287
|
+
if (limit !== undefined) {
|
|
288
|
+
params.set('limit', String(limit))
|
|
289
|
+
}
|
|
290
|
+
const query = params.toString()
|
|
291
|
+
const path = `apps/${app_name}/certificates${query ? `?${query}` : ''}`
|
|
292
|
+
return await this.client.restOrThrow(path)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async requestAcmeCertificate(payload: RequestAcmeCertificateRequest): Promise<FlyResult<CertificateDetail>> {
|
|
296
|
+
const { app_name, request } = payload
|
|
297
|
+
return await this.client.restOrThrow(`apps/${app_name}/certificates/acme`, 'POST', request)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async requestCustomCertificate(payload: RequestCustomCertificateRequest): Promise<FlyResult<CertificateDetail>> {
|
|
301
|
+
const { app_name, request } = payload
|
|
302
|
+
return await this.client.restOrThrow(`apps/${app_name}/certificates/custom`, 'POST', request)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async getCertificate(payload: CertificateRequest): Promise<FlyResult<CertificateDetail>> {
|
|
306
|
+
const { app_name, hostname } = payload
|
|
307
|
+
return await this.client.restOrThrow(`apps/${app_name}/certificates/${hostname}`)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async deleteCertificate(payload: CertificateRequest): Promise<FlyResult<void>> {
|
|
311
|
+
const { app_name, hostname } = payload
|
|
312
|
+
return await this.client.restOrThrow(`apps/${app_name}/certificates/${hostname}`, 'DELETE')
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async deleteAcmeCertificates(payload: CertificateRequest): Promise<FlyResult<CertificateDetail>> {
|
|
316
|
+
const { app_name, hostname } = payload
|
|
317
|
+
return await this.client.restOrThrow(`apps/${app_name}/certificates/${hostname}/acme`, 'DELETE')
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async checkCertificate(payload: CertificateRequest): Promise<FlyResult<CertificateCheckResponse>> {
|
|
321
|
+
const { app_name, hostname } = payload
|
|
322
|
+
return await this.client.restOrThrow(`apps/${app_name}/certificates/${hostname}/check`, 'POST')
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async deleteCustomCertificate(payload: CertificateRequest): Promise<FlyResult<DestroyCustomCertificateResponse>> {
|
|
326
|
+
const { app_name, hostname } = payload
|
|
327
|
+
return await this.client.restOrThrow(`apps/${app_name}/certificates/${hostname}/custom`, 'DELETE')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async createDeployToken(payload: CreateDeployTokenRequest): Promise<FlyResult<CreateAppResponse>> {
|
|
331
|
+
const { app_name, request } = payload
|
|
332
|
+
return await this.client.restOrThrow(`apps/${app_name}/deploy_token`, 'POST', request)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async listIpAssignments(app_name: string): Promise<FlyResult<ListIPAssignmentsResponse>> {
|
|
336
|
+
return await this.client.restOrThrow(`apps/${app_name}/ip_assignments`)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async assignIpAddress(payload: AssignIPAddressRequest): Promise<FlyResult<IPAssignment>> {
|
|
340
|
+
const { app_name, request } = payload
|
|
341
|
+
return await this.client.restOrThrow(`apps/${app_name}/ip_assignments`, 'POST', request)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async deleteIpAssignment(payload: DeleteIPAddressRequest): Promise<FlyResult<void>> {
|
|
345
|
+
const { app_name, ip } = payload
|
|
346
|
+
return await this.client.restOrThrow(`apps/${app_name}/ip_assignments/${ip}`, 'DELETE')
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async listSecretKeys(payload: ListSecretKeysRequest): Promise<FlyResult<SecretKeys>> {
|
|
350
|
+
const { app_name, min_version, types } = payload
|
|
351
|
+
const params = new URLSearchParams()
|
|
352
|
+
if (min_version) {
|
|
353
|
+
params.set('min_version', min_version)
|
|
354
|
+
}
|
|
355
|
+
if (types) {
|
|
356
|
+
params.set('types', types)
|
|
357
|
+
}
|
|
358
|
+
const query = params.toString()
|
|
359
|
+
const path = `apps/${app_name}/secretkeys${query ? `?${query}` : ''}`
|
|
360
|
+
return await this.client.restOrThrow(path)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async getSecretKey(payload: SecretKeyRequest): Promise<FlyResult<SecretKey>> {
|
|
364
|
+
const { app_name, secret_name, min_version } = payload
|
|
365
|
+
const query = min_version ? `?min_version=${encodeURIComponent(min_version)}` : ''
|
|
366
|
+
return await this.client.restOrThrow(`apps/${app_name}/secretkeys/${secret_name}${query}`)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async setSecretKey(payload: SetSecretKeyRequest): Promise<FlyResult<SetSecretkeyResponse>> {
|
|
370
|
+
const { app_name, secret_name, request } = payload
|
|
371
|
+
return await this.client.restOrThrow(`apps/${app_name}/secretkeys/${secret_name}`, 'POST', request)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async deleteSecretKey(payload: SecretKeyRequest): Promise<FlyResult<DeleteSecretkeyResponse>> {
|
|
375
|
+
const { app_name, secret_name } = payload
|
|
376
|
+
return await this.client.restOrThrow(`apps/${app_name}/secretkeys/${secret_name}`, 'DELETE')
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async decryptSecretKey(payload: SecretKeyDecryptRequest): Promise<FlyResult<DecryptSecretkeyResponse>> {
|
|
380
|
+
const { app_name, secret_name, request, min_version } = payload
|
|
381
|
+
const query = min_version ? `?min_version=${encodeURIComponent(min_version)}` : ''
|
|
382
|
+
return await this.client.restOrThrow(
|
|
383
|
+
`apps/${app_name}/secretkeys/${secret_name}/decrypt${query}`,
|
|
384
|
+
'POST',
|
|
385
|
+
request,
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async encryptSecretKey(payload: SecretKeyEncryptRequest): Promise<FlyResult<EncryptSecretkeyResponse>> {
|
|
390
|
+
const { app_name, secret_name, request, min_version } = payload
|
|
391
|
+
const query = min_version ? `?min_version=${encodeURIComponent(min_version)}` : ''
|
|
392
|
+
return await this.client.restOrThrow(
|
|
393
|
+
`apps/${app_name}/secretkeys/${secret_name}/encrypt${query}`,
|
|
394
|
+
'POST',
|
|
395
|
+
request,
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async generateSecretKey(payload: SetSecretKeyRequest): Promise<FlyResult<SetSecretkeyResponse>> {
|
|
400
|
+
const { app_name, secret_name, request } = payload
|
|
401
|
+
return await this.client.restOrThrow(
|
|
402
|
+
`apps/${app_name}/secretkeys/${secret_name}/generate`,
|
|
403
|
+
'POST',
|
|
404
|
+
request,
|
|
405
|
+
)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async signSecretKey(payload: SecretKeySignRequest): Promise<FlyResult<SignSecretkeyResponse>> {
|
|
409
|
+
const { app_name, secret_name, request, min_version } = payload
|
|
410
|
+
const query = min_version ? `?min_version=${encodeURIComponent(min_version)}` : ''
|
|
411
|
+
return await this.client.restOrThrow(`apps/${app_name}/secretkeys/${secret_name}/sign${query}`, 'POST', request)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async verifySecretKey(payload: SecretKeyVerifyRequest): Promise<FlyResult<void>> {
|
|
415
|
+
const { app_name, secret_name, request, min_version } = payload
|
|
416
|
+
const query = min_version ? `?min_version=${encodeURIComponent(min_version)}` : ''
|
|
417
|
+
return await this.client.restOrThrow(`apps/${app_name}/secretkeys/${secret_name}/verify${query}`, 'POST', request)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async listSecrets(payload: ListSecretsRequest): Promise<FlyResult<AppSecrets>> {
|
|
421
|
+
const { app_name, min_version, show_secrets } = payload
|
|
422
|
+
const params = new URLSearchParams()
|
|
423
|
+
if (min_version) {
|
|
424
|
+
params.set('min_version', min_version)
|
|
425
|
+
}
|
|
426
|
+
if (show_secrets !== undefined) {
|
|
427
|
+
params.set('show_secrets', String(show_secrets))
|
|
428
|
+
}
|
|
429
|
+
const query = params.toString()
|
|
430
|
+
const path = `apps/${app_name}/secrets${query ? `?${query}` : ''}`
|
|
431
|
+
return await this.client.restOrThrow(path)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async updateSecrets(payload: UpdateSecretsRequest): Promise<FlyResult<AppSecretsUpdateResp>> {
|
|
435
|
+
const { app_name, request } = payload
|
|
436
|
+
return await this.client.restOrThrow(`apps/${app_name}/secrets`, 'POST', request)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async getSecret(payload: SecretRequest): Promise<FlyResult<AppSecret>> {
|
|
440
|
+
const { app_name, secret_name, min_version, show_secrets } = payload
|
|
441
|
+
const params = new URLSearchParams()
|
|
442
|
+
if (min_version) {
|
|
443
|
+
params.set('min_version', min_version)
|
|
444
|
+
}
|
|
445
|
+
if (show_secrets !== undefined) {
|
|
446
|
+
params.set('show_secrets', String(show_secrets))
|
|
447
|
+
}
|
|
448
|
+
const query = params.toString()
|
|
449
|
+
const path = `apps/${app_name}/secrets/${secret_name}${query ? `?${query}` : ''}`
|
|
450
|
+
return await this.client.restOrThrow(path)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async setSecret(payload: SetSecretRequest): Promise<FlyResult<SetAppSecretResponse>> {
|
|
454
|
+
const { app_name, secret_name, request } = payload
|
|
455
|
+
return await this.client.restOrThrow(`apps/${app_name}/secrets/${secret_name}`, 'POST', request)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async deleteSecret(payload: SecretRequest): Promise<FlyResult<DeleteAppSecretResponse>> {
|
|
459
|
+
const { app_name, secret_name } = payload
|
|
460
|
+
return await this.client.restOrThrow(`apps/${app_name}/secrets/${secret_name}`, 'DELETE')
|
|
461
|
+
}
|
|
462
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// HTTP client for Fly.io Machines REST API and GraphQL API.
|
|
2
|
+
// Uses native fetch (no cross-fetch dependency).
|
|
3
|
+
// Vendored from supabase/fly-admin with modifications.
|
|
4
|
+
|
|
5
|
+
import * as errore from 'errore'
|
|
6
|
+
|
|
7
|
+
import { App } from './app.ts'
|
|
8
|
+
import {
|
|
9
|
+
createFlyGraphQLError,
|
|
10
|
+
createFlyHttpError,
|
|
11
|
+
FlyApiError,
|
|
12
|
+
type FlyClientError,
|
|
13
|
+
type FlyResult,
|
|
14
|
+
} from './errors.ts'
|
|
15
|
+
import { Machine } from './machine.ts'
|
|
16
|
+
import { Network } from './network.ts'
|
|
17
|
+
import { Organization } from './organization.ts'
|
|
18
|
+
import { Regions } from './regions.ts'
|
|
19
|
+
import { Secret } from './secret.ts'
|
|
20
|
+
import { Token } from './token.ts'
|
|
21
|
+
import { Volume } from './volume.ts'
|
|
22
|
+
|
|
23
|
+
export const FLY_API_GRAPHQL = 'https://api.fly.io'
|
|
24
|
+
export const FLY_API_HOSTNAME = 'https://api.machines.dev'
|
|
25
|
+
|
|
26
|
+
interface GraphQLRequest<T> {
|
|
27
|
+
query: string
|
|
28
|
+
variables?: Record<string, T>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface GraphQLResponse<T> {
|
|
32
|
+
data: T
|
|
33
|
+
errors?: {
|
|
34
|
+
message: string
|
|
35
|
+
locations: { line: number; column: number }[]
|
|
36
|
+
}[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ClientConfig {
|
|
40
|
+
graphqlUrl?: string
|
|
41
|
+
apiUrl?: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class Client {
|
|
45
|
+
private graphqlUrl: string
|
|
46
|
+
private apiUrl: string
|
|
47
|
+
private apiKey: string
|
|
48
|
+
App: App
|
|
49
|
+
Machine: Machine
|
|
50
|
+
Regions: Regions
|
|
51
|
+
Network: Network
|
|
52
|
+
Organization: Organization
|
|
53
|
+
Secret: Secret
|
|
54
|
+
Volume: Volume
|
|
55
|
+
Token: Token
|
|
56
|
+
|
|
57
|
+
constructor(apiKey: string, { graphqlUrl, apiUrl }: ClientConfig = {}) {
|
|
58
|
+
if (!apiKey) {
|
|
59
|
+
throw new Error('Fly API Key is required')
|
|
60
|
+
}
|
|
61
|
+
this.graphqlUrl = graphqlUrl || FLY_API_GRAPHQL
|
|
62
|
+
this.apiUrl = apiUrl || FLY_API_HOSTNAME
|
|
63
|
+
this.apiKey = apiKey
|
|
64
|
+
this.App = new App(this)
|
|
65
|
+
this.Machine = new Machine(this)
|
|
66
|
+
this.Network = new Network(this)
|
|
67
|
+
this.Regions = new Regions(this)
|
|
68
|
+
this.Organization = new Organization(this)
|
|
69
|
+
this.Secret = new Secret(this)
|
|
70
|
+
this.Volume = new Volume(this)
|
|
71
|
+
this.Token = new Token(this)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getApiKey(): string {
|
|
75
|
+
return this.apiKey
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getApiUrl(): string {
|
|
79
|
+
return this.apiUrl
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getGraphqlUrl(): string {
|
|
83
|
+
return this.graphqlUrl
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async gqlPost<U, V>(payload: GraphQLRequest<U>): Promise<FlyClientError | V> {
|
|
87
|
+
const path = 'graphql'
|
|
88
|
+
const response = await fetch(`${this.graphqlUrl}/${path}`, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify(payload),
|
|
95
|
+
}).catch((cause: unknown) => {
|
|
96
|
+
return new FlyApiError({
|
|
97
|
+
method: 'POST',
|
|
98
|
+
path,
|
|
99
|
+
httpStatus: 0,
|
|
100
|
+
cause,
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
if (response instanceof Error) {
|
|
105
|
+
return response
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const responseText = await response.text().catch((cause: unknown) => {
|
|
109
|
+
return new FlyApiError({
|
|
110
|
+
method: 'POST',
|
|
111
|
+
path,
|
|
112
|
+
httpStatus: response.status,
|
|
113
|
+
cause,
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
if (responseText instanceof Error) {
|
|
118
|
+
return responseText
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const payloadOrError = parseJson({ text: responseText })
|
|
123
|
+
if (payloadOrError instanceof Error) {
|
|
124
|
+
return new FlyApiError({
|
|
125
|
+
method: 'POST',
|
|
126
|
+
path,
|
|
127
|
+
httpStatus: response.status,
|
|
128
|
+
cause: payloadOrError,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
return createFlyHttpError({
|
|
132
|
+
method: 'POST',
|
|
133
|
+
path,
|
|
134
|
+
httpStatus: response.status,
|
|
135
|
+
payload: payloadOrError,
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const payloadOrError = parseJson({ text: responseText })
|
|
140
|
+
if (payloadOrError instanceof Error) {
|
|
141
|
+
return new FlyApiError({
|
|
142
|
+
method: 'POST',
|
|
143
|
+
path,
|
|
144
|
+
httpStatus: response.status,
|
|
145
|
+
cause: payloadOrError,
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const parsed = payloadOrError as GraphQLResponse<V>
|
|
150
|
+
const { data, errors } = parsed
|
|
151
|
+
if (errors) {
|
|
152
|
+
return createFlyGraphQLError({
|
|
153
|
+
path,
|
|
154
|
+
messages: errors.map((error) => {
|
|
155
|
+
return error.message
|
|
156
|
+
}),
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return data
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async gqlPostOrThrow<U, V>(payload: GraphQLRequest<U>): Promise<FlyResult<V>> {
|
|
164
|
+
return await this.gqlPost<U, V>(payload)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async rest<V>(
|
|
168
|
+
path: string,
|
|
169
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
|
170
|
+
body?: unknown,
|
|
171
|
+
headers?: Record<string, string>,
|
|
172
|
+
): Promise<FlyClientError | V> {
|
|
173
|
+
const response = await fetch(`${this.apiUrl}/v1/${path}`, {
|
|
174
|
+
method,
|
|
175
|
+
headers: {
|
|
176
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
177
|
+
'Content-Type': 'application/json',
|
|
178
|
+
...headers,
|
|
179
|
+
},
|
|
180
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
181
|
+
}).catch((cause: unknown) => {
|
|
182
|
+
return new FlyApiError({
|
|
183
|
+
method,
|
|
184
|
+
path,
|
|
185
|
+
httpStatus: 0,
|
|
186
|
+
cause,
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
if (response instanceof Error) {
|
|
191
|
+
return response
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const responseText = await response.text().catch((cause: unknown) => {
|
|
195
|
+
return new FlyApiError({
|
|
196
|
+
method,
|
|
197
|
+
path,
|
|
198
|
+
httpStatus: response.status,
|
|
199
|
+
cause,
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
if (responseText instanceof Error) {
|
|
204
|
+
return responseText
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (!response.ok) {
|
|
208
|
+
const payloadOrError = parseJson({ text: responseText })
|
|
209
|
+
if (payloadOrError instanceof Error) {
|
|
210
|
+
return new FlyApiError({
|
|
211
|
+
method,
|
|
212
|
+
path,
|
|
213
|
+
httpStatus: response.status,
|
|
214
|
+
cause: payloadOrError,
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return createFlyHttpError({
|
|
219
|
+
method,
|
|
220
|
+
path,
|
|
221
|
+
httpStatus: response.status,
|
|
222
|
+
payload: payloadOrError,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!responseText) {
|
|
227
|
+
return undefined as V
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const payloadOrError = parseJson({ text: responseText })
|
|
231
|
+
if (payloadOrError instanceof Error) {
|
|
232
|
+
return new FlyApiError({
|
|
233
|
+
method,
|
|
234
|
+
path,
|
|
235
|
+
httpStatus: response.status,
|
|
236
|
+
cause: payloadOrError,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return payloadOrError as V
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async restOrThrow<V>(
|
|
244
|
+
path: string,
|
|
245
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
|
246
|
+
body?: unknown,
|
|
247
|
+
headers?: Record<string, string>,
|
|
248
|
+
): Promise<FlyResult<V>> {
|
|
249
|
+
return await this.rest(path, method, body, headers)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function parseJson({ text }: { text: string }): Error | unknown {
|
|
254
|
+
return errore.try({
|
|
255
|
+
try: () => {
|
|
256
|
+
return JSON.parse(text) as unknown
|
|
257
|
+
},
|
|
258
|
+
catch: (cause) => {
|
|
259
|
+
return new Error('Failed to parse JSON response', { cause })
|
|
260
|
+
},
|
|
261
|
+
})
|
|
262
|
+
}
|