@metaplay/metaplay-auth 1.7.0 → 1.7.2
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/CHANGELOG.md +20 -0
- package/dist/index.cjs +129 -141
- package/dist/{sshcrypto-OMBCGRSN.node → sshcrypto-FLTOM44A.node} +0 -0
- package/index.ts +41 -19
- package/package.json +10 -10
- package/src/auth.ts +3 -3
- package/src/config.ts +2 -1
- package/src/deployment.ts +73 -42
- package/src/secret_store.ts +1 -1
- package/src/targetenvironment.ts +22 -5
- package/src/version.ts +1 -1
|
Binary file
|
package/index.ts
CHANGED
|
@@ -89,7 +89,7 @@ interface PortalEnvironmentInfo {
|
|
|
89
89
|
* @param environment Environment slug.
|
|
90
90
|
* @returns The portal's information about the environment.
|
|
91
91
|
*/
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
async function fetchManagedEnvironmentInfoWithSlugs(
|
|
94
94
|
tokens: TokenSet,
|
|
95
95
|
organization: string,
|
|
@@ -107,10 +107,7 @@ async function fetchManagedEnvironmentInfoWithSlugs(
|
|
|
107
107
|
})
|
|
108
108
|
|
|
109
109
|
// Throw on server errors (eg, forbidden)
|
|
110
|
-
|
|
111
|
-
const errorData = await response.json()
|
|
112
|
-
throw new Error(`Failed to fetch environment details with error ${response.status}: ${JSON.stringify(errorData)}`)
|
|
113
|
-
}
|
|
110
|
+
await checkEnvironmentInfoResponseError(response)
|
|
114
111
|
|
|
115
112
|
// \todo Validate response?
|
|
116
113
|
const portalEnvInfo = (await response.json()) as PortalEnvironmentInfo
|
|
@@ -130,20 +127,30 @@ async function fetchManageEnvironmentInfoWithHumanId(tokens: TokenSet, humanId:
|
|
|
130
127
|
})
|
|
131
128
|
|
|
132
129
|
// Throw on server errors (eg, forbidden)
|
|
133
|
-
|
|
134
|
-
const errorData = await response.json()
|
|
135
|
-
throw new Error(`Failed to fetch environment details with error ${response.status}: ${JSON.stringify(errorData)}`)
|
|
136
|
-
}
|
|
130
|
+
await checkEnvironmentInfoResponseError(response)
|
|
137
131
|
|
|
138
132
|
// Return the result
|
|
139
133
|
const portalEnvInfos = (await response.json()) as PortalEnvironmentInfo[]
|
|
140
134
|
logger.debug(`Portal returned environment infos: ${JSON.stringify(portalEnvInfos, undefined, 2)}`)
|
|
141
135
|
if (portalEnvInfos.length === 0) {
|
|
142
|
-
throw new Error(`
|
|
136
|
+
throw new Error(`Environment ${humanId} does not exist or your account does not have permissions to access this environment`)
|
|
143
137
|
}
|
|
144
138
|
return portalEnvInfos[0]
|
|
145
139
|
}
|
|
146
140
|
|
|
141
|
+
async function checkEnvironmentInfoResponseError(response: Response): Promise<void> {
|
|
142
|
+
// Throw on server errors (eg, forbidden)
|
|
143
|
+
// Portal returns a helpful error message in 'message' field.
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const errorData = await response.json()
|
|
146
|
+
if (typeof(errorData) === 'object' && errorData !== null && 'message' in errorData && typeof(errorData.message) === 'string') {
|
|
147
|
+
throw new Error(errorData.message)
|
|
148
|
+
} else {
|
|
149
|
+
throw new Error(`Failed to fetch environment details with error ${response.status}: ${JSON.stringify(errorData)}`)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
147
154
|
/**
|
|
148
155
|
* Resolve the target environment based on (org, proj, env) tuple. We fetch the information
|
|
149
156
|
* from the portal and then construct the `TargetEnvironment` class with the required information
|
|
@@ -154,7 +161,7 @@ async function fetchManageEnvironmentInfoWithHumanId(tokens: TokenSet, humanId:
|
|
|
154
161
|
* @param environment Environment slug.
|
|
155
162
|
* @returns The TargetEnvironment instance needed to operate with the environment.
|
|
156
163
|
*/
|
|
157
|
-
|
|
164
|
+
|
|
158
165
|
async function resolveTargetEnvironmentFromSlugs(
|
|
159
166
|
tokens: TokenSet,
|
|
160
167
|
organization: string,
|
|
@@ -251,8 +258,10 @@ program
|
|
|
251
258
|
}
|
|
252
259
|
|
|
253
260
|
// Store the portal base URL for accessing globally
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
const overridePortalUrl: string | undefined = opts.portalBaseUrl ?? process.env.AUTHCLI_PORTAL_BASEURL
|
|
262
|
+
if (overridePortalUrl) {
|
|
263
|
+
logger.debug(`Using portal URL override: ${overridePortalUrl}`)
|
|
264
|
+
setPortalBaseUrl(overridePortalUrl)
|
|
256
265
|
}
|
|
257
266
|
|
|
258
267
|
// Store the stack API base URL for accessing globally
|
|
@@ -309,6 +318,7 @@ program
|
|
|
309
318
|
await removeTokens()
|
|
310
319
|
console.log('Done! You are now logged out.')
|
|
311
320
|
} catch (error) {
|
|
321
|
+
logger.debug('Invocation failed with error:', error)
|
|
312
322
|
if (error instanceof Error) {
|
|
313
323
|
console.error(`Error logging out: ${error.message}`)
|
|
314
324
|
}
|
|
@@ -328,6 +338,7 @@ program
|
|
|
328
338
|
const tokens = await loadTokens()
|
|
329
339
|
console.log(JSON.stringify(tokens, undefined, 2))
|
|
330
340
|
} catch (error) {
|
|
341
|
+
logger.debug('Invocation failed with error:', error)
|
|
331
342
|
if (error instanceof Error) {
|
|
332
343
|
console.error(`Error showing tokens: ${error.message}`)
|
|
333
344
|
}
|
|
@@ -381,8 +392,9 @@ program
|
|
|
381
392
|
console.log(kubeconfigPayload)
|
|
382
393
|
}
|
|
383
394
|
} catch (error) {
|
|
395
|
+
logger.debug('Invocation failed with error:', error)
|
|
384
396
|
if (error instanceof Error) {
|
|
385
|
-
console.error('Error getting KubeConfig:', error)
|
|
397
|
+
console.error('Error getting KubeConfig:', error.message)
|
|
386
398
|
}
|
|
387
399
|
exit(1)
|
|
388
400
|
}
|
|
@@ -417,6 +429,7 @@ program
|
|
|
417
429
|
const credentials = await targetEnv.getKubeExecCredential()
|
|
418
430
|
console.log(credentials)
|
|
419
431
|
} catch (error) {
|
|
432
|
+
logger.debug('Invocation failed with error:', error)
|
|
420
433
|
if (error instanceof Error) {
|
|
421
434
|
console.error(`Error getting Kubernetes ExecCredential: ${error.message}`)
|
|
422
435
|
}
|
|
@@ -464,6 +477,7 @@ program
|
|
|
464
477
|
)
|
|
465
478
|
}
|
|
466
479
|
} catch (error) {
|
|
480
|
+
logger.debug('Invocation failed with error:', error)
|
|
467
481
|
if (error instanceof Error) {
|
|
468
482
|
console.error(`Error getting AWS credentials: ${error.message}`)
|
|
469
483
|
}
|
|
@@ -522,6 +536,7 @@ program
|
|
|
522
536
|
)
|
|
523
537
|
}
|
|
524
538
|
} catch (error) {
|
|
539
|
+
logger.debug('Invocation failed with error:', error)
|
|
525
540
|
if (error instanceof Error) {
|
|
526
541
|
console.error(`Error getting docker login credentials: ${error.message}`)
|
|
527
542
|
}
|
|
@@ -551,6 +566,7 @@ program
|
|
|
551
566
|
const environment = await targetEnv.getEnvironmentDetails()
|
|
552
567
|
console.log(JSON.stringify(environment, undefined, 2))
|
|
553
568
|
} catch (error) {
|
|
569
|
+
logger.debug('Invocation failed with error:', error)
|
|
554
570
|
if (error instanceof Error) {
|
|
555
571
|
console.error(`Error getting environment details: ${error.message}`)
|
|
556
572
|
}
|
|
@@ -630,7 +646,7 @@ program
|
|
|
630
646
|
reject(error)
|
|
631
647
|
} else {
|
|
632
648
|
// result contains an array of all the progress objects
|
|
633
|
-
logger.debug('
|
|
649
|
+
logger.debug('Successfully finished pushing docker image')
|
|
634
650
|
resolve(result)
|
|
635
651
|
}
|
|
636
652
|
},
|
|
@@ -644,6 +660,7 @@ program
|
|
|
644
660
|
|
|
645
661
|
console.log(`Successfully pushed docker image to ${dstImageName}!`)
|
|
646
662
|
} catch (error) {
|
|
663
|
+
logger.debug('Invocation failed with error:', error)
|
|
647
664
|
if (error instanceof Error) {
|
|
648
665
|
console.error(`Failed to push docker image: ${error.message}`)
|
|
649
666
|
}
|
|
@@ -749,7 +766,7 @@ program
|
|
|
749
766
|
reject(error)
|
|
750
767
|
} else {
|
|
751
768
|
// result contains an array of all the progress objects
|
|
752
|
-
logger.debug('
|
|
769
|
+
logger.debug('Successfully finished pulling image')
|
|
753
770
|
resolve(result)
|
|
754
771
|
}
|
|
755
772
|
},
|
|
@@ -790,14 +807,16 @@ program
|
|
|
790
807
|
options.helmChartRepo ?? imageLabels['io.metaplay.default_helm_repo'] ?? 'https://charts.metaplay.dev'
|
|
791
808
|
)
|
|
792
809
|
|
|
810
|
+
// Warn about Helm chart version needing to be specified explicitly (unless using a local chart)
|
|
811
|
+
if (!options.helmChartVersion && !options.localChartPath) {
|
|
812
|
+
console.warn('Warning: You should specify the Helm chart version explicitly with --helm-chart-version=<version>!')
|
|
813
|
+
}
|
|
814
|
+
|
|
793
815
|
// Resolve helmChartVersion, in order of precedence:
|
|
794
816
|
// - Specified in the cli options
|
|
795
817
|
// - Specified in the docker image label
|
|
796
818
|
// - Unknown, error out!
|
|
797
819
|
const helmChartVersionSpec = options.helmChartVersion ?? imageLabels['io.metaplay.default_server_chart_version']
|
|
798
|
-
if (!options.helmChartVersion) {
|
|
799
|
-
console.warn('Warning: You should specify the Helm chart version with --helm-chart-version=<version>!')
|
|
800
|
-
}
|
|
801
820
|
if (!helmChartVersionSpec) {
|
|
802
821
|
throw new Error('No Helm chart version defined. With pre-R28 SDK versions, you must specify the Helm chart version explicitly with --helm-chart-version=<version>.')
|
|
803
822
|
}
|
|
@@ -884,11 +903,13 @@ program
|
|
|
884
903
|
console.log('Validating game server deployment...')
|
|
885
904
|
await checkGameServerDeployment(envInfo, kubeconfig, imageTag)
|
|
886
905
|
} catch (error) {
|
|
906
|
+
logger.debug('Invocation failed with error:', error)
|
|
887
907
|
const errMessage = error instanceof Error ? error.message : String(error)
|
|
888
908
|
console.error(`Failed to resolve game server deployment status: ${errMessage}`)
|
|
889
909
|
exit(2)
|
|
890
910
|
}
|
|
891
911
|
} catch (error) {
|
|
912
|
+
logger.debug('Invocation failed with error:', error)
|
|
892
913
|
if (error instanceof Error) {
|
|
893
914
|
console.error(`Error deploying game server into target environment: ${error.message}`)
|
|
894
915
|
}
|
|
@@ -933,6 +954,7 @@ program
|
|
|
933
954
|
// \todo Get requiredImageTag from the Helm chart
|
|
934
955
|
await checkGameServerDeployment(envInfo, kubeconfig, /* requiredImageTag: */ null)
|
|
935
956
|
} catch (error: any) {
|
|
957
|
+
logger.debug('Invocation failed with error:', error)
|
|
936
958
|
console.error(`Failed to check deployment status: ${error.message}`)
|
|
937
959
|
exit(1)
|
|
938
960
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaplay/metaplay-auth",
|
|
3
3
|
"description": "Utility CLI for authenticating with the Metaplay Auth and making authenticated calls to infrastructure endpoints.",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"homepage": "https://metaplay.io",
|
|
@@ -18,22 +18,22 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@metaplay/eslint-config": "workspace:*",
|
|
20
20
|
"@types/dockerode": "3.3.31",
|
|
21
|
-
"@types/express": "
|
|
21
|
+
"@types/express": "5.0.0",
|
|
22
22
|
"@types/js-yaml": "4.0.9",
|
|
23
23
|
"@types/jsonwebtoken": "9.0.7",
|
|
24
24
|
"@types/jwk-to-pem": "2.0.3",
|
|
25
|
-
"@types/node": "20.16.
|
|
25
|
+
"@types/node": "20.16.10",
|
|
26
26
|
"@types/semver": "7.5.8",
|
|
27
27
|
"esbuild": "0.24.0",
|
|
28
|
-
"tsx": "4.19.
|
|
29
|
-
"typescript": "5.6.
|
|
30
|
-
"vitest": "2.1.
|
|
31
|
-
"@aws-sdk/client-ecr": "3.
|
|
32
|
-
"@kubernetes/client-node": "1.0.0-
|
|
33
|
-
"@ory/client": "1.15.
|
|
28
|
+
"tsx": "4.19.2",
|
|
29
|
+
"typescript": "5.6.3",
|
|
30
|
+
"vitest": "2.1.4",
|
|
31
|
+
"@aws-sdk/client-ecr": "3.682.0",
|
|
32
|
+
"@kubernetes/client-node": "1.0.0-rc7",
|
|
33
|
+
"@ory/client": "1.15.7",
|
|
34
34
|
"commander": "12.1.0",
|
|
35
35
|
"dockerode": "4.0.2",
|
|
36
|
-
"h3": "1.
|
|
36
|
+
"h3": "1.13.0",
|
|
37
37
|
"js-yaml": "4.1.0",
|
|
38
38
|
"jsonwebtoken": "9.0.2",
|
|
39
39
|
"jwk-to-pem": "2.0.6",
|
package/src/auth.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
2
|
import { toNodeListener, createApp, defineEventHandler, getQuery, sendError } from 'h3'
|
|
3
3
|
import jwt from 'jsonwebtoken'
|
|
4
4
|
import jwkToPem from 'jwk-to-pem'
|
|
@@ -324,7 +324,7 @@ async function extendCurrentSessionWithRefreshToken(
|
|
|
324
324
|
* @param code
|
|
325
325
|
* @returns
|
|
326
326
|
*/
|
|
327
|
-
|
|
327
|
+
|
|
328
328
|
async function getTokensWithAuthorizationCode(
|
|
329
329
|
state: string,
|
|
330
330
|
redirectUri: string,
|
|
@@ -356,7 +356,7 @@ async function getTokensWithAuthorizationCode(
|
|
|
356
356
|
*/
|
|
357
357
|
export async function loadTokens(): Promise<TokenSet> {
|
|
358
358
|
try {
|
|
359
|
-
const tokens = (await getSecret('tokens')) as TokenSet
|
|
359
|
+
const tokens = (await getSecret('tokens')) as unknown as TokenSet
|
|
360
360
|
|
|
361
361
|
if (!tokens) {
|
|
362
362
|
throw new Error('Unable to load tokens. You need to login first.')
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Base URL to the Metaplay developer portal. Override with global flag --portal-base-url
|
|
2
|
+
* Base URL to the Metaplay developer portal. Override with global flag --portal-base-url
|
|
3
|
+
* or environment variable `AUTHCLI_PORTAL_BASEURL'.
|
|
3
4
|
*/
|
|
4
5
|
export let portalBaseUrl = 'https://portal.metaplay.dev'
|
|
5
6
|
|
package/src/deployment.ts
CHANGED
|
@@ -3,7 +3,8 @@ import os from 'os'
|
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import { exit } from 'process'
|
|
5
5
|
import { EnvironmentDetails, TargetEnvironment } from 'targetenvironment.js'
|
|
6
|
-
import
|
|
6
|
+
import { promises as dns } from 'dns'
|
|
7
|
+
import tls from 'tls'
|
|
7
8
|
|
|
8
9
|
import {
|
|
9
10
|
KubeConfig,
|
|
@@ -404,8 +405,8 @@ export async function waitForGameServerPodsToBeReady(
|
|
|
404
405
|
|
|
405
406
|
// Handle state of the deployment
|
|
406
407
|
if (anyPodsInPhase(podStatuses, GameServerPodPhase.Failed)) {
|
|
407
|
-
logger.error(`
|
|
408
|
-
console.log('
|
|
408
|
+
logger.error(`Game server start failed with pod statuses: ${JSON.stringify(podStatuses, undefined, 2)}`)
|
|
409
|
+
console.log('Game server failed to start due to the pods not starting properly! See above for details.')
|
|
409
410
|
for (let ndx = 0; ndx < pods.length; ndx += 1) {
|
|
410
411
|
const status = podStatuses[ndx]
|
|
411
412
|
const suffix = status.phase !== GameServerPodPhase.Ready ? ` -- ${status.message}` : ''
|
|
@@ -417,14 +418,14 @@ export async function waitForGameServerPodsToBeReady(
|
|
|
417
418
|
anyPodsInPhase(podStatuses, GameServerPodPhase.Pending) ||
|
|
418
419
|
anyPodsInPhase(podStatuses, GameServerPodPhase.Running)
|
|
419
420
|
) {
|
|
420
|
-
console.log('Waiting for
|
|
421
|
+
console.log('Waiting for pod(s) to be ready...')
|
|
421
422
|
for (let ndx = 0; ndx < pods.length; ndx += 1) {
|
|
422
423
|
const status = podStatuses[ndx]
|
|
423
424
|
const suffix = status.phase !== GameServerPodPhase.Ready ? ` -- ${status.message}` : ''
|
|
424
425
|
console.log(` ${pods[ndx].metadata?.name}: ${status.phase}${suffix}`)
|
|
425
426
|
}
|
|
426
427
|
} else if (allPodsInPhase(podStatuses, GameServerPodPhase.Ready)) {
|
|
427
|
-
console.log('
|
|
428
|
+
console.log('Game server pod(s) are up and ready to serve!')
|
|
428
429
|
return
|
|
429
430
|
} else {
|
|
430
431
|
console.log('Deployment in inconsistent state, waiting...')
|
|
@@ -445,9 +446,34 @@ export async function waitForGameServerPodsToBeReady(
|
|
|
445
446
|
}
|
|
446
447
|
}
|
|
447
448
|
|
|
449
|
+
async function waitForDomainResolution(hostname: string): Promise<void> {
|
|
450
|
+
// Use 15min timeout -- DNS propagation can take a while
|
|
451
|
+
const timeoutAt = Date.now() + 15 * 60 * 1000
|
|
452
|
+
|
|
453
|
+
while (true) {
|
|
454
|
+
try {
|
|
455
|
+
const addresses = await dns.lookup(hostname)
|
|
456
|
+
console.log(`Successfully resolved domain ${hostname} to:`, addresses)
|
|
457
|
+
return
|
|
458
|
+
} catch (err) {
|
|
459
|
+
if (Date.now() > timeoutAt) {
|
|
460
|
+
throw new Error(`Could not resolve domain ${hostname} before timeout.`)
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (err instanceof Error && (err as NodeJS.ErrnoException).code === 'ENOTFOUND') {
|
|
464
|
+
console.log(`Waiting for domain name ${hostname} to propagate... This can take up to 15 minutes on the first deploy.`)
|
|
465
|
+
} else {
|
|
466
|
+
console.log(`Failed to resolve ${hostname}: ${String(err)}. Retrying...`)
|
|
467
|
+
}
|
|
468
|
+
await delay(5000) // use long timeout as it can take ~5min for DNS to propagate
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
448
473
|
/**
|
|
449
474
|
* Wait until we can establish a client-simulating connection to the target
|
|
450
|
-
* game server
|
|
475
|
+
* game server and perform the TLS handshake and wait for the initial bytes
|
|
476
|
+
* to be received from the server, or a timeout happens.
|
|
451
477
|
* @param hostname Hostname of the target server to connect to.
|
|
452
478
|
* @param port Port to use for the connections (usually 9339).
|
|
453
479
|
*/
|
|
@@ -455,47 +481,49 @@ async function waitForGameServerClientEndpointToBeReady(
|
|
|
455
481
|
hostname: string,
|
|
456
482
|
port: number
|
|
457
483
|
): Promise<void> {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const socket = new net.Socket()
|
|
461
|
-
// Set a short timeout for each attempt
|
|
462
|
-
// Note: This does not include the DNS resolve time which can take a minute or so.
|
|
463
|
-
// \todo Consider adding a DNS resolve step before trying to connect.
|
|
464
|
-
socket.setTimeout(2000)
|
|
465
|
-
|
|
466
|
-
socket.on('connect', () => {
|
|
467
|
-
socket.destroy() // Clean up after success
|
|
468
|
-
resolve(true)
|
|
469
|
-
})
|
|
470
|
-
|
|
471
|
-
socket.on('error', () => {
|
|
472
|
-
socket.destroy() // Clean up after error
|
|
473
|
-
resolve(false)
|
|
474
|
-
})
|
|
475
|
-
|
|
476
|
-
socket.on('timeout', () => {
|
|
477
|
-
socket.destroy() // Clean up after timeout
|
|
478
|
-
resolve(false)
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
socket.connect(port, hostname)
|
|
482
|
-
})
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// Try for 2 min before giving up
|
|
486
|
-
const timeoutAt = Date.now() + 2 * 60 * 1000
|
|
484
|
+
// Try for a few minutes before giving up
|
|
485
|
+
const timeoutAt = Date.now() + 5 * 60 * 1000
|
|
487
486
|
|
|
488
487
|
while (Date.now() < timeoutAt) {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
488
|
+
try {
|
|
489
|
+
await new Promise<void>((resolve, reject) => {
|
|
490
|
+
const socket: tls.TLSSocket = tls.connect({ host: hostname, port }, () => {
|
|
491
|
+
if (socket.authorized) {
|
|
492
|
+
console.log('TLS handshake completed, waiting to receive data from the server...')
|
|
493
|
+
// \todo This is enough with direct connection to the server but with the upcoming
|
|
494
|
+
// client-traffic-proxy, we may need to exchange the proper handshake messages
|
|
495
|
+
socket.once('data', (bytes: Buffer) => {
|
|
496
|
+
const hexBytes = Array.from(bytes).map(byte => byte.toString(16).padStart(2, '0'))
|
|
497
|
+
console.log(`Received ${bytes.length} bytes from server: ${hexBytes.join(' ')}`)
|
|
498
|
+
socket.end()
|
|
499
|
+
resolve()
|
|
500
|
+
})
|
|
501
|
+
} else {
|
|
502
|
+
socket.end()
|
|
503
|
+
reject(new Error(`TLS handshake failed: ${String(socket.authorizationError)}`))
|
|
504
|
+
}
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
socket.on('error', reject)
|
|
508
|
+
|
|
509
|
+
socket.setTimeout(5000, () => {
|
|
510
|
+
socket.end()
|
|
511
|
+
reject(new Error('Timeout while waiting for data after handshake'))
|
|
512
|
+
})
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
// Success
|
|
516
|
+
console.log(`Successfully connected to the target environment ${hostname}:${port}`)
|
|
492
517
|
return
|
|
518
|
+
} catch (error) {
|
|
519
|
+
console.log(`Attempt failed, retrying: ${String(error)}`)
|
|
493
520
|
}
|
|
494
|
-
|
|
495
|
-
|
|
521
|
+
|
|
522
|
+
// Wait a bit before trying again
|
|
523
|
+
await delay(1000)
|
|
496
524
|
}
|
|
497
525
|
|
|
498
|
-
throw new Error(`Timeout while
|
|
526
|
+
throw new Error(`Timeout reached while waiting to establish connection to ${hostname}:${port}`)
|
|
499
527
|
}
|
|
500
528
|
|
|
501
529
|
/**
|
|
@@ -515,7 +543,10 @@ export async function checkGameServerDeployment(
|
|
|
515
543
|
console.log('Waiting for game server pods to be ready...')
|
|
516
544
|
await waitForGameServerPodsToBeReady(envInfo.deployment.kubernetes_namespace, kubeconfig, requiredImageTag)
|
|
517
545
|
|
|
518
|
-
//
|
|
546
|
+
// Resolve DNS for the target server
|
|
547
|
+
// \todo Use the -proxy address when client-traffic-proxy is in use
|
|
548
|
+
console.log(`Resolving the domain name '${envInfo.deployment.server_hostname}' of the server...`)
|
|
549
|
+
await waitForDomainResolution(envInfo.deployment.server_hostname)
|
|
519
550
|
|
|
520
551
|
// Check that the game server accepts traffic on its client-facing endpoint
|
|
521
552
|
const clientTrafficPort = 9339 // \todo check for other ports, too?
|
package/src/secret_store.ts
CHANGED
|
@@ -78,7 +78,7 @@ export async function setSecret(key: string, value: any): Promise<void> {
|
|
|
78
78
|
await fs.writeFile(filePath, content)
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
export async function getSecret(key: string): Promise<
|
|
81
|
+
export async function getSecret(key: string): Promise<string> {
|
|
82
82
|
logger.debug(`Getting secret ${key}...`)
|
|
83
83
|
|
|
84
84
|
const secrets = await loadSecrets()
|
package/src/targetenvironment.ts
CHANGED
|
@@ -85,6 +85,14 @@ export interface DockerCredentials {
|
|
|
85
85
|
registryUrl: string
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
class FetchJsonHttpError extends Error {
|
|
89
|
+
response: Response;
|
|
90
|
+
constructor(method: string, url: string, response: Response) {
|
|
91
|
+
super(`Failed to fetch ${method} ${url}: ${response.statusText}, response code=${response.status}`)
|
|
92
|
+
this.response = response;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
88
96
|
export class TargetEnvironment {
|
|
89
97
|
private readonly accessToken: string
|
|
90
98
|
private readonly humanId: string
|
|
@@ -106,14 +114,14 @@ export class TargetEnvironment {
|
|
|
106
114
|
})
|
|
107
115
|
|
|
108
116
|
if (response.status !== 200) {
|
|
109
|
-
throw new
|
|
117
|
+
throw new FetchJsonHttpError(method, url, response)
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
return (await response.json()) as T
|
|
113
121
|
}
|
|
114
122
|
|
|
115
123
|
async fetchText(url: string, method: string): Promise<string> {
|
|
116
|
-
|
|
124
|
+
|
|
117
125
|
let response
|
|
118
126
|
try {
|
|
119
127
|
response = await fetch(url, {
|
|
@@ -168,12 +176,21 @@ export class TargetEnvironment {
|
|
|
168
176
|
logger.debug(`Getting Kubernetes KubeConfig from ${url}...`)
|
|
169
177
|
|
|
170
178
|
// get the execcredential and morph it into a kubeconfig which calls metaplay-auth for the token
|
|
171
|
-
|
|
179
|
+
|
|
172
180
|
let kubeExecCredential: KubeExecCredential
|
|
173
181
|
try {
|
|
174
182
|
kubeExecCredential = await this.fetchJson<KubeExecCredential>(url, 'POST')
|
|
175
|
-
} catch {
|
|
176
|
-
|
|
183
|
+
} catch (error) {
|
|
184
|
+
// User-friendly error messages for well-known HTTP errors.
|
|
185
|
+
if (error instanceof FetchJsonHttpError) {
|
|
186
|
+
if (error.response.status === 404) {
|
|
187
|
+
throw new Error(`Environment ${this.humanId} does not exist.`)
|
|
188
|
+
} else if (error.response.status === 403) {
|
|
189
|
+
throw new Error(`Your account does not have permissions to access environment ${this.humanId}.`)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const errorMessage = (error instanceof Error) ? error.message : String(error)
|
|
193
|
+
throw new Error(`Failed to fetch Kubernetes KubeConfig: ${errorMessage}`)
|
|
177
194
|
}
|
|
178
195
|
|
|
179
196
|
if (!kubeExecCredential.spec.cluster) {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = "1.7.
|
|
1
|
+
export const PACKAGE_VERSION = "1.7.2"
|