@metaplay/metaplay-auth 1.7.0 → 1.7.1
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 +10 -0
- package/dist/index.cjs +129 -141
- package/index.ts +9 -7
- package/package.json +9 -9
- package/src/auth.ts +3 -3
- package/src/deployment.ts +73 -42
- package/src/secret_store.ts +1 -1
- package/src/targetenvironment.ts +2 -2
- package/src/version.ts +1 -1
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,
|
|
@@ -154,7 +154,7 @@ async function fetchManageEnvironmentInfoWithHumanId(tokens: TokenSet, humanId:
|
|
|
154
154
|
* @param environment Environment slug.
|
|
155
155
|
* @returns The TargetEnvironment instance needed to operate with the environment.
|
|
156
156
|
*/
|
|
157
|
-
|
|
157
|
+
|
|
158
158
|
async function resolveTargetEnvironmentFromSlugs(
|
|
159
159
|
tokens: TokenSet,
|
|
160
160
|
organization: string,
|
|
@@ -630,7 +630,7 @@ program
|
|
|
630
630
|
reject(error)
|
|
631
631
|
} else {
|
|
632
632
|
// result contains an array of all the progress objects
|
|
633
|
-
logger.debug('
|
|
633
|
+
logger.debug('Successfully finished pushing docker image')
|
|
634
634
|
resolve(result)
|
|
635
635
|
}
|
|
636
636
|
},
|
|
@@ -749,7 +749,7 @@ program
|
|
|
749
749
|
reject(error)
|
|
750
750
|
} else {
|
|
751
751
|
// result contains an array of all the progress objects
|
|
752
|
-
logger.debug('
|
|
752
|
+
logger.debug('Successfully finished pulling image')
|
|
753
753
|
resolve(result)
|
|
754
754
|
}
|
|
755
755
|
},
|
|
@@ -790,14 +790,16 @@ program
|
|
|
790
790
|
options.helmChartRepo ?? imageLabels['io.metaplay.default_helm_repo'] ?? 'https://charts.metaplay.dev'
|
|
791
791
|
)
|
|
792
792
|
|
|
793
|
+
// Warn about Helm chart version needing to be specified explicitly (unless using a local chart)
|
|
794
|
+
if (!options.helmChartVersion && !options.localChartPath) {
|
|
795
|
+
console.warn('Warning: You should specify the Helm chart version explicitly with --helm-chart-version=<version>!')
|
|
796
|
+
}
|
|
797
|
+
|
|
793
798
|
// Resolve helmChartVersion, in order of precedence:
|
|
794
799
|
// - Specified in the cli options
|
|
795
800
|
// - Specified in the docker image label
|
|
796
801
|
// - Unknown, error out!
|
|
797
802
|
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
803
|
if (!helmChartVersionSpec) {
|
|
802
804
|
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
805
|
}
|
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.1",
|
|
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
28
|
"tsx": "4.19.1",
|
|
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.
|
|
29
|
+
"typescript": "5.6.3",
|
|
30
|
+
"vitest": "2.1.3",
|
|
31
|
+
"@aws-sdk/client-ecr": "3.675.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/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
|
@@ -113,7 +113,7 @@ export class TargetEnvironment {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
async fetchText(url: string, method: string): Promise<string> {
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
let response
|
|
118
118
|
try {
|
|
119
119
|
response = await fetch(url, {
|
|
@@ -168,7 +168,7 @@ export class TargetEnvironment {
|
|
|
168
168
|
logger.debug(`Getting Kubernetes KubeConfig from ${url}...`)
|
|
169
169
|
|
|
170
170
|
// get the execcredential and morph it into a kubeconfig which calls metaplay-auth for the token
|
|
171
|
-
|
|
171
|
+
|
|
172
172
|
let kubeExecCredential: KubeExecCredential
|
|
173
173
|
try {
|
|
174
174
|
kubeExecCredential = await this.fetchJson<KubeExecCredential>(url, 'POST')
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = "1.7.
|
|
1
|
+
export const PACKAGE_VERSION = "1.7.1"
|