@metaplay/metaplay-auth 1.7.2 → 1.8.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/CHANGELOG.md +15 -0
- package/dist/index.cjs +122 -122
- package/dist/sshcrypto-WBGBWUDW.node +0 -0
- package/index.ts +125 -150
- package/package.json +7 -7
- package/src/auth.ts +12 -4
- package/src/buildCommand.ts +5 -5
- package/src/deployment.ts +2 -2
- package/src/targetenvironment.ts +0 -1
- package/src/utils.ts +80 -2
- package/src/version.ts +1 -1
- package/dist/sshcrypto-FLTOM44A.node +0 -0
|
Binary file
|
package/index.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander'
|
|
3
|
-
import { randomBytes } from 'crypto'
|
|
4
3
|
import Docker from 'dockerode'
|
|
5
|
-
import { writeFile
|
|
6
|
-
import { tmpdir } from 'os'
|
|
4
|
+
import { writeFile } from 'fs/promises'
|
|
7
5
|
import { exit } from 'process'
|
|
8
6
|
import * as semver from 'semver'
|
|
9
7
|
|
|
@@ -15,7 +13,7 @@ import { portalBaseUrl, setPortalBaseUrl } from './src/config.js'
|
|
|
15
13
|
import { checkGameServerDeployment, debugGameServer } from './src/deployment.js'
|
|
16
14
|
import { logger, setLogLevel } from './src/logging.js'
|
|
17
15
|
import { TargetEnvironment } from './src/targetenvironment.js'
|
|
18
|
-
import {
|
|
16
|
+
import { isValidFQDN, removeTrailingSlash, fetchHelmChartVersions, resolveBestMatchingVersion, executeHelmCommand, getGameServerHelmRelease } from './src/utils.js'
|
|
19
17
|
import { PACKAGE_VERSION } from './src/version.js'
|
|
20
18
|
|
|
21
19
|
/** Stack API base url override, specified with the '--stack-api' global flag. */
|
|
@@ -64,8 +62,7 @@ interface PortalEnvironmentInfo {
|
|
|
64
62
|
type: string
|
|
65
63
|
|
|
66
64
|
/** Immutable human-readable identifier for the environment (eg, 'delicious-pumpkin' .. can also be legacy name like 'idler-develop') */
|
|
67
|
-
|
|
68
|
-
human_id?: string
|
|
65
|
+
human_id: string
|
|
69
66
|
|
|
70
67
|
/**
|
|
71
68
|
* Domain that the environment uses, eg, 'metaplay.games'. Old environments use 'p1.metaplay.io'.
|
|
@@ -161,26 +158,22 @@ async function checkEnvironmentInfoResponseError(response: Response): Promise<vo
|
|
|
161
158
|
* @param environment Environment slug.
|
|
162
159
|
* @returns The TargetEnvironment instance needed to operate with the environment.
|
|
163
160
|
*/
|
|
164
|
-
|
|
165
161
|
async function resolveTargetEnvironmentFromSlugs(
|
|
166
162
|
tokens: TokenSet,
|
|
167
163
|
organization: string,
|
|
168
164
|
project: string,
|
|
169
165
|
environment: string
|
|
170
166
|
): Promise<TargetEnvironment> {
|
|
171
|
-
// Fetch the deployment information from the portal
|
|
167
|
+
// Fetch the deployment information from the portal.
|
|
172
168
|
const portalEnvInfo = await fetchManagedEnvironmentInfoWithSlugs(tokens, organization, project, environment)
|
|
173
|
-
const humanId = portalEnvInfo.human_id
|
|
174
|
-
if (!humanId) {
|
|
175
|
-
throw new Error(`Portal returned missing human_id for environment '${organization}-${project}-${environment}'`)
|
|
176
|
-
}
|
|
177
169
|
|
|
170
|
+
// Check that environment is provisioned on a stack.
|
|
178
171
|
const stackDomain = portalEnvInfo.stack_domain
|
|
179
172
|
if (!stackDomain) {
|
|
180
173
|
throw new Error(`The environment ${portalEnvInfo.human_id} has not been provisioned to any infra stack (environment.stack_domain is empty).`)
|
|
181
174
|
}
|
|
182
175
|
|
|
183
|
-
return new TargetEnvironment(tokens.access_token,
|
|
176
|
+
return new TargetEnvironment(tokens.access_token, portalEnvInfo.human_id, `https://infra.${stackDomain}/stackapi`)
|
|
184
177
|
}
|
|
185
178
|
|
|
186
179
|
async function resolveTargetEnvironmentHumanId(tokens: TokenSet, humanId: string): Promise<TargetEnvironment> {
|
|
@@ -194,13 +187,12 @@ async function resolveTargetEnvironmentHumanId(tokens: TokenSet, humanId: string
|
|
|
194
187
|
stackApiBaseUrl = `https://infra.${portalEnvInfo.stack_domain}/stackapi`
|
|
195
188
|
}
|
|
196
189
|
|
|
197
|
-
|
|
198
190
|
return new TargetEnvironment(tokens.access_token, humanId, stackApiBaseUrl)
|
|
199
191
|
}
|
|
200
192
|
|
|
201
193
|
/**
|
|
202
|
-
* Helper for parsing the TargetEnvironment type from the command line arguments. Accepts either the
|
|
203
|
-
* (idler-test.p1.metaplay.io), a
|
|
194
|
+
* Helper for parsing the TargetEnvironment type from the command line arguments. Accepts either the environment address
|
|
195
|
+
* (idler-test.p1.metaplay.io), a slug-based address (metaplay-idler-test) or the legacy (organization, project, environment)
|
|
204
196
|
* tuple from options.
|
|
205
197
|
*/
|
|
206
198
|
async function resolveTargetEnvironment(
|
|
@@ -227,7 +219,7 @@ async function resolveTargetEnvironment(
|
|
|
227
219
|
// Three parts is tuple of slush '<organization>-<project>-<environment>'
|
|
228
220
|
return await resolveTargetEnvironmentFromSlugs(tokens, parts[0], parts[1], parts[2])
|
|
229
221
|
} else {
|
|
230
|
-
throw new Error(`Invalid environment address syntax '${address}'. Specify either
|
|
222
|
+
throw new Error(`Invalid environment address syntax '${address}'. Specify either the environment's id (eg, "delicious-elephant") or its slug tripled (eg, "<organization>-<project>-<environment>")`)
|
|
231
223
|
}
|
|
232
224
|
}
|
|
233
225
|
} else if (options.organization && options.project && options.environment) {
|
|
@@ -349,7 +341,7 @@ program
|
|
|
349
341
|
program
|
|
350
342
|
.command('get-kubeconfig')
|
|
351
343
|
.description('get kubeconfig for target environment')
|
|
352
|
-
.argument('[
|
|
344
|
+
.argument('[environment]', 'target environment (e.g. metaplay-idler-develop or tiny-squids)')
|
|
353
345
|
.option('-o, --organization <organization>', '[deprecated] organization name (e.g. metaplay)')
|
|
354
346
|
.option('-p, --project <project>', '[deprecated] project name (e.g. idler)')
|
|
355
347
|
.option('-e, --environment <environment>', '[deprecated] environment name (e.g. develop)')
|
|
@@ -360,11 +352,11 @@ program
|
|
|
360
352
|
})
|
|
361
353
|
.action(
|
|
362
354
|
async (
|
|
363
|
-
|
|
355
|
+
environment: string | undefined,
|
|
364
356
|
options: { organization?: string; project?: string; environment?: string; type?: string; output?: string }
|
|
365
357
|
) => {
|
|
366
358
|
try {
|
|
367
|
-
const targetEnv = await resolveTargetEnvironment(
|
|
359
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
368
360
|
|
|
369
361
|
// Default to credentialsType==dynamic for human users, and credentialsType==static for machine users
|
|
370
362
|
const tokens = await loadTokens()
|
|
@@ -412,7 +404,7 @@ program
|
|
|
412
404
|
program
|
|
413
405
|
.command('get-kubernetes-execcredential')
|
|
414
406
|
.description('[internal] get kubernetes credentials in execcredential format (used from the generated kubeconfigs)')
|
|
415
|
-
.argument('[
|
|
407
|
+
.argument('[environment]', 'target environment (e.g. metaplay-idler-develop or tiny-squids)')
|
|
416
408
|
.option('-o, --organization <organization>', '[deprecated] organization name (e.g. metaplay)')
|
|
417
409
|
.option('-p, --project <project>', '[deprecated] project name (e.g. idler)')
|
|
418
410
|
.option('-e, --environment <environment>', '[deprecated] environment name (e.g. develop)')
|
|
@@ -421,11 +413,11 @@ program
|
|
|
421
413
|
})
|
|
422
414
|
.action(
|
|
423
415
|
async (
|
|
424
|
-
|
|
416
|
+
environment: string | undefined,
|
|
425
417
|
options: { organization?: string; project?: string; environment?: string }
|
|
426
418
|
) => {
|
|
427
419
|
try {
|
|
428
|
-
const targetEnv = await resolveTargetEnvironment(
|
|
420
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
429
421
|
const credentials = await targetEnv.getKubeExecCredential()
|
|
430
422
|
console.log(credentials)
|
|
431
423
|
} catch (error) {
|
|
@@ -441,7 +433,7 @@ program
|
|
|
441
433
|
program
|
|
442
434
|
.command('get-aws-credentials')
|
|
443
435
|
.description('get AWS credentials for target environment')
|
|
444
|
-
.argument('[
|
|
436
|
+
.argument('[environment]', 'target environment (e.g. metaplay-idler-develop or tiny-squids)')
|
|
445
437
|
.option('-o, --organization <organization>', '[deprecated] organization name (e.g. metaplay)')
|
|
446
438
|
.option('-p, --project <project>', '[deprecated] project name (e.g. idler)')
|
|
447
439
|
.option('-e, --environment <environment>', '[deprecated] environment name (e.g. develop)')
|
|
@@ -451,7 +443,7 @@ program
|
|
|
451
443
|
})
|
|
452
444
|
.action(
|
|
453
445
|
async (
|
|
454
|
-
|
|
446
|
+
environment: string | undefined,
|
|
455
447
|
options: { organization?: string; project?: string; environment?: string; format?: string }
|
|
456
448
|
) => {
|
|
457
449
|
try {
|
|
@@ -459,7 +451,7 @@ program
|
|
|
459
451
|
throw new Error('Invalid format; must be one of json or env')
|
|
460
452
|
}
|
|
461
453
|
|
|
462
|
-
const targetEnv = await resolveTargetEnvironment(
|
|
454
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
463
455
|
|
|
464
456
|
// Get the AWS credentials
|
|
465
457
|
const credentials = await targetEnv.getAwsCredentials()
|
|
@@ -486,69 +478,10 @@ program
|
|
|
486
478
|
}
|
|
487
479
|
)
|
|
488
480
|
|
|
489
|
-
program
|
|
490
|
-
.command('get-docker-login')
|
|
491
|
-
.description('[deprecated] get docker login credentials for pushing the server image to target environment')
|
|
492
|
-
.argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
|
|
493
|
-
.option('-o, --organization <organization>', '[deprecated] organization name (e.g. metaplay)')
|
|
494
|
-
.option('-p, --project <project>', '[deprecated] project name (e.g. idler)')
|
|
495
|
-
.option('-e, --environment <environment>', '[deprecated] environment name (e.g. develop)')
|
|
496
|
-
.option('-f, --format <format>', 'output format (json or env)', 'json')
|
|
497
|
-
.hook('preAction', async () => {
|
|
498
|
-
await extendCurrentSession()
|
|
499
|
-
})
|
|
500
|
-
.action(
|
|
501
|
-
async (
|
|
502
|
-
gameserver: string | undefined,
|
|
503
|
-
options: { organization?: string; project?: string; environment?: string; format?: string }
|
|
504
|
-
) => {
|
|
505
|
-
try {
|
|
506
|
-
if (options.format !== 'json' && options.format !== 'env') {
|
|
507
|
-
throw new Error('Invalid format; must be one of json or env')
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
console.warn('The get-docker-login command is deprecated! Use the push-docker-image command instead.')
|
|
511
|
-
|
|
512
|
-
const targetEnv = await resolveTargetEnvironment(gameserver, options)
|
|
513
|
-
|
|
514
|
-
// Get environment info (region is needed for ECR)
|
|
515
|
-
logger.debug('Get environment info')
|
|
516
|
-
const environment = await targetEnv.getEnvironmentDetails()
|
|
517
|
-
const dockerRepo = environment.deployment.ecr_repo
|
|
518
|
-
|
|
519
|
-
// Resolve docker credentials for remote registry
|
|
520
|
-
logger.debug('Get docker credentials')
|
|
521
|
-
const dockerCredentials = await targetEnv.getDockerCredentials()
|
|
522
|
-
const { username, password } = dockerCredentials
|
|
523
|
-
|
|
524
|
-
// Output the docker repo & credentials
|
|
525
|
-
if (options.format === 'env') {
|
|
526
|
-
console.log(`export DOCKER_REPO=${dockerRepo}`)
|
|
527
|
-
console.log(`export DOCKER_USERNAME=${username}`)
|
|
528
|
-
console.log(`export DOCKER_PASSWORD=${password}`)
|
|
529
|
-
} else {
|
|
530
|
-
console.log(
|
|
531
|
-
JSON.stringify({
|
|
532
|
-
dockerRepo,
|
|
533
|
-
username,
|
|
534
|
-
password,
|
|
535
|
-
})
|
|
536
|
-
)
|
|
537
|
-
}
|
|
538
|
-
} catch (error) {
|
|
539
|
-
logger.debug('Invocation failed with error:', error)
|
|
540
|
-
if (error instanceof Error) {
|
|
541
|
-
console.error(`Error getting docker login credentials: ${error.message}`)
|
|
542
|
-
}
|
|
543
|
-
exit(1)
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
)
|
|
547
|
-
|
|
548
481
|
program
|
|
549
482
|
.command('get-environment')
|
|
550
483
|
.description('get details of an environment')
|
|
551
|
-
.argument('[
|
|
484
|
+
.argument('[environment]', 'target environment (e.g. metaplay-idler-develop or tiny-squids)')
|
|
552
485
|
.option('-o, --organization <organization>', '[deprecated] organization name (e.g. metaplay)')
|
|
553
486
|
.option('-p, --project <project>', '[deprecated] project name (e.g. idler)')
|
|
554
487
|
.option('-e, --environment <environment>', '[deprecated] environment name (e.g. develop)')
|
|
@@ -557,14 +490,14 @@ program
|
|
|
557
490
|
})
|
|
558
491
|
.action(
|
|
559
492
|
async (
|
|
560
|
-
|
|
493
|
+
environment: string | undefined,
|
|
561
494
|
options: { organization?: string; project?: string; environment?: string }
|
|
562
495
|
) => {
|
|
563
496
|
try {
|
|
564
|
-
const targetEnv = await resolveTargetEnvironment(
|
|
497
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
565
498
|
|
|
566
|
-
const
|
|
567
|
-
console.log(JSON.stringify(
|
|
499
|
+
const envInfo = await targetEnv.getEnvironmentDetails()
|
|
500
|
+
console.log(JSON.stringify(envInfo, undefined, 2))
|
|
568
501
|
} catch (error) {
|
|
569
502
|
logger.debug('Invocation failed with error:', error)
|
|
570
503
|
if (error instanceof Error) {
|
|
@@ -578,21 +511,21 @@ program
|
|
|
578
511
|
program
|
|
579
512
|
.command('push-docker-image')
|
|
580
513
|
.description('push docker image into the target environment image registry')
|
|
581
|
-
.argument('
|
|
514
|
+
.argument('environment', 'target environment (e.g. metaplay-idler-develop or tiny-squids)')
|
|
582
515
|
.argument('image-name', 'full name of the docker image to push (eg, the gameserver:<sha>)')
|
|
583
516
|
.hook('preAction', async () => {
|
|
584
517
|
await extendCurrentSession()
|
|
585
518
|
})
|
|
586
519
|
.action(
|
|
587
520
|
async (
|
|
588
|
-
|
|
521
|
+
environment: string | undefined,
|
|
589
522
|
imageName: string | undefined,
|
|
590
523
|
options: { organization?: string; project?: string; environment?: string; imageTag?: string }
|
|
591
524
|
) => {
|
|
592
525
|
try {
|
|
593
|
-
console.log(`Pushing docker image ${imageName} to target environment ${
|
|
526
|
+
console.log(`Pushing docker image ${imageName} to target environment ${environment}...`)
|
|
594
527
|
|
|
595
|
-
const targetEnv = await resolveTargetEnvironment(
|
|
528
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
596
529
|
|
|
597
530
|
// Get environment info (region is needed for ECR)
|
|
598
531
|
logger.debug('Get environment info')
|
|
@@ -672,7 +605,7 @@ program
|
|
|
672
605
|
program
|
|
673
606
|
.command('deploy-server')
|
|
674
607
|
.description('deploy a game server image to target environment')
|
|
675
|
-
.argument('
|
|
608
|
+
.argument('environment', 'target environment (e.g. metaplay-idler-develop or tiny-squids)')
|
|
676
609
|
.argument('image-tag', 'docker image tag to deploy (usually the SHA of the build)')
|
|
677
610
|
.requiredOption('-f, --values <path-to-values-file>', 'path to Helm values file to use for this deployment')
|
|
678
611
|
.option('--local-chart-path <path-to-chart-directory>', 'path to local Helm chart directory (to use a chart from local disk)')
|
|
@@ -684,7 +617,7 @@ program
|
|
|
684
617
|
})
|
|
685
618
|
.action(
|
|
686
619
|
async (
|
|
687
|
-
|
|
620
|
+
environment: string | undefined,
|
|
688
621
|
imageTag: string | undefined,
|
|
689
622
|
options: {
|
|
690
623
|
organization?: string
|
|
@@ -698,17 +631,19 @@ program
|
|
|
698
631
|
}
|
|
699
632
|
) => {
|
|
700
633
|
try {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
console.log(`Deploying server to ${gameserver} with image tag ${imageTag}...`)
|
|
634
|
+
console.log(`Deploying server to ${environment} with image tag ${imageTag}...`)
|
|
704
635
|
|
|
705
636
|
// Fetch target environment details
|
|
637
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
706
638
|
const envInfo = await targetEnv.getEnvironmentDetails()
|
|
707
639
|
|
|
640
|
+
// Validate imageTag.
|
|
708
641
|
if (!imageTag) {
|
|
709
642
|
throw new Error('Must specify a valid docker image tag as the image-tag argument, usually the SHA of the build')
|
|
710
643
|
}
|
|
711
|
-
|
|
644
|
+
if (imageTag.includes(':')) {
|
|
645
|
+
throw new Error('Invalid image tag: only specify the tag, not the repository name.')
|
|
646
|
+
}
|
|
712
647
|
|
|
713
648
|
if (!options.deploymentName) {
|
|
714
649
|
throw new Error(`Invalid Helm deployment name '${options.deploymentName}'; specify one with --deployment-name or use the default`)
|
|
@@ -850,50 +785,29 @@ program
|
|
|
850
785
|
// Fetch kubeconfig and write it to a temporary file
|
|
851
786
|
// \todo allow passing a custom kubeconfig file?
|
|
852
787
|
const kubeconfigPayload = await targetEnv.getKubeConfigWithEmbeddedCredentials()
|
|
853
|
-
const kubeconfigPath = pathJoin(tmpdir(), randomBytes(20).toString('hex'))
|
|
854
|
-
logger.debug(`Write temporary kubeconfig in ${kubeconfigPath}`)
|
|
855
|
-
await writeFile(kubeconfigPath, kubeconfigPayload, { mode: 0o600 })
|
|
856
|
-
|
|
857
|
-
try {
|
|
858
|
-
// Construct Helm invocation
|
|
859
|
-
const chartNameOrPath = options.localChartPath ?? 'metaplay-gameserver'
|
|
860
|
-
const helmArgs = ['upgrade', '--install', '--wait'] // \note wait for the pods to stabilize -- otherwise status check can read state before any changes to pods are applied
|
|
861
|
-
.concat(['--kubeconfig', kubeconfigPath])
|
|
862
|
-
.concat(['-n', envInfo.deployment.kubernetes_namespace])
|
|
863
|
-
.concat(['--values', options.values])
|
|
864
|
-
.concat(['--set-string', `image.tag=${imageTag}`])
|
|
865
|
-
.concat(sdkVersion ? ['--set-string', `sdk.version=${sdkVersion}`] : [])
|
|
866
|
-
.concat(!options.localChartPath ? ['--repo', helmChartRepo, '--version', resolvedHelmChartVersion] : [])
|
|
867
|
-
.concat([options.deploymentName])
|
|
868
|
-
.concat([chartNameOrPath])
|
|
869
|
-
logger.info(`Execute: helm ${helmArgs.join(' ')}`)
|
|
870
|
-
|
|
871
|
-
// Execute Helm
|
|
872
|
-
let helmResult
|
|
873
|
-
try {
|
|
874
|
-
helmResult = await executeCommand('helm', helmArgs)
|
|
875
|
-
// \todo output something from Helm result?
|
|
876
|
-
} catch (error) {
|
|
877
|
-
const errMessage = error instanceof Error ? error.message : String(error)
|
|
878
|
-
throw new Error(`Failed to execute 'helm': ${errMessage}. You need to have Helm v3 installed to deploy a game server with metaplay-auth.`)
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
// Throw on Helm non-success exit code
|
|
882
|
-
if (helmResult.exitCode !== 0) {
|
|
883
|
-
throw new Error(`Helm deploy failed with exit code ${helmResult.exitCode}: ${String(helmResult.stderr)}`)
|
|
884
|
-
}
|
|
885
788
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
)
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
}
|
|
789
|
+
// Construct Helm invocation
|
|
790
|
+
const chartNameOrPath = options.localChartPath ?? 'metaplay-gameserver'
|
|
791
|
+
const helmArgs = ['upgrade', '--install', '--wait'] // \note wait for the pods to stabilize -- otherwise status check can read state before any changes to pods are applied
|
|
792
|
+
.concat(['-n', envInfo.deployment.kubernetes_namespace])
|
|
793
|
+
.concat(['--values', options.values])
|
|
794
|
+
.concat(['--set-string', `image.tag=${imageTag}`])
|
|
795
|
+
.concat(sdkVersion ? ['--set-string', `sdk.version=${sdkVersion}`] : [])
|
|
796
|
+
.concat(!options.localChartPath ? ['--repo', helmChartRepo, '--version', resolvedHelmChartVersion] : [])
|
|
797
|
+
.concat([options.deploymentName])
|
|
798
|
+
.concat([chartNameOrPath])
|
|
799
|
+
logger.info(`Execute: helm ${helmArgs.join(' ')}`)
|
|
800
|
+
|
|
801
|
+
// Execute Helm upgrade
|
|
802
|
+
await executeHelmCommand(kubeconfigPayload, helmArgs)
|
|
803
|
+
|
|
804
|
+
const testingRepoSuffix =
|
|
805
|
+
!options.localChartPath && helmChartRepo !== 'https://charts.metaplay.dev'
|
|
806
|
+
? ` from repo ${helmChartRepo}`
|
|
807
|
+
: ''
|
|
808
|
+
console.log(
|
|
809
|
+
`Game server deployed to ${environment} with tag ${imageTag} using chart version ${resolvedHelmChartVersion}${testingRepoSuffix}`
|
|
810
|
+
)
|
|
897
811
|
|
|
898
812
|
// Check the status of the game server deployment
|
|
899
813
|
try {
|
|
@@ -918,19 +832,72 @@ program
|
|
|
918
832
|
}
|
|
919
833
|
)
|
|
920
834
|
|
|
835
|
+
program
|
|
836
|
+
.command('uninstall-server')
|
|
837
|
+
.description('remove the deployed game server from the target environment')
|
|
838
|
+
.argument('environment', 'target environment (e.g. metaplay-idler-develop or tiny-squids)')
|
|
839
|
+
.hook('preAction', async () => {
|
|
840
|
+
await extendCurrentSession()
|
|
841
|
+
})
|
|
842
|
+
.action(
|
|
843
|
+
async (
|
|
844
|
+
environment: string | undefined,
|
|
845
|
+
options: {
|
|
846
|
+
organization?: string
|
|
847
|
+
project?: string
|
|
848
|
+
environment?: string
|
|
849
|
+
}
|
|
850
|
+
) => {
|
|
851
|
+
try {
|
|
852
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
853
|
+
console.log(`Removing server from ${environment}...`)
|
|
854
|
+
|
|
855
|
+
// Fetch target environment details & kubeconfig (for Helm).
|
|
856
|
+
const envInfo = await targetEnv.getEnvironmentDetails()
|
|
857
|
+
const kubeconfigPayload = await targetEnv.getKubeConfigWithEmbeddedCredentials()
|
|
858
|
+
|
|
859
|
+
// Resolve whether a server Helm release exists.
|
|
860
|
+
const gameServerHelmRelease = await getGameServerHelmRelease(kubeconfigPayload)
|
|
861
|
+
|
|
862
|
+
// If no game server deployed, bail out.
|
|
863
|
+
if (gameServerHelmRelease === null) {
|
|
864
|
+
console.log('No server deployed, nothing to do')
|
|
865
|
+
exit(0)
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Construct Helm invocation.
|
|
869
|
+
const deploymentName = gameServerHelmRelease.name
|
|
870
|
+
const helmArgs = ['uninstall', '--wait'] // \note waits for resources to be deleted before returning
|
|
871
|
+
.concat(['-n', envInfo.deployment.kubernetes_namespace])
|
|
872
|
+
.concat([deploymentName])
|
|
873
|
+
logger.info(`Execute: helm ${helmArgs.join(' ')}`)
|
|
874
|
+
|
|
875
|
+
// Execute Helm uninstall
|
|
876
|
+
await executeHelmCommand(kubeconfigPayload, helmArgs)
|
|
877
|
+
console.log(`Server uninstalled from ${environment}`)
|
|
878
|
+
} catch (error) {
|
|
879
|
+
logger.debug('Uninstalling server failed with error:', error)
|
|
880
|
+
if (error instanceof Error) {
|
|
881
|
+
console.error(`Error uninstalling server: ${error.message}`)
|
|
882
|
+
}
|
|
883
|
+
exit(1)
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
)
|
|
887
|
+
|
|
921
888
|
program
|
|
922
889
|
.command('check-server-status')
|
|
923
890
|
.description('check the status of a deployed server and print out information that is helpful in debugging failed deployments')
|
|
924
|
-
.argument('[
|
|
891
|
+
.argument('[environment]', 'target environment (e.g. metaplay-idler-develop or tiny-squids)')
|
|
925
892
|
.hook('preAction', async () => {
|
|
926
893
|
await extendCurrentSession()
|
|
927
894
|
})
|
|
928
895
|
.action(
|
|
929
896
|
async (
|
|
930
|
-
|
|
897
|
+
environment: string | undefined,
|
|
931
898
|
options: { organization?: string; project?: string; environment?: string }
|
|
932
899
|
) => {
|
|
933
|
-
const targetEnv = await resolveTargetEnvironment(
|
|
900
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
934
901
|
|
|
935
902
|
try {
|
|
936
903
|
logger.debug('Get environment info')
|
|
@@ -938,17 +905,25 @@ program
|
|
|
938
905
|
|
|
939
906
|
// Load kubeconfig from file and throw error if validation fails.
|
|
940
907
|
logger.debug('Get kubeconfig')
|
|
908
|
+
let kubeconfigPayload: string
|
|
941
909
|
const kubeconfig = new KubeConfig()
|
|
942
910
|
try {
|
|
943
911
|
// Initialize kubeconfig with the payload fetched from the cloud
|
|
944
912
|
// \todo allow passing a custom kubeconfig file?
|
|
945
|
-
|
|
913
|
+
kubeconfigPayload = await targetEnv.getKubeConfigWithEmbeddedCredentials()
|
|
946
914
|
kubeconfig.loadFromString(kubeconfigPayload)
|
|
947
915
|
} catch (error) {
|
|
948
916
|
const errMessage = error instanceof Error ? error.message : String(error)
|
|
949
917
|
throw new Error(`Failed to load or validate kubeconfig. ${errMessage}`)
|
|
950
918
|
}
|
|
951
919
|
|
|
920
|
+
// Resolve Helm deployment
|
|
921
|
+
const gameServerHelmRelease = await getGameServerHelmRelease(kubeconfigPayload)
|
|
922
|
+
if (gameServerHelmRelease === null) {
|
|
923
|
+
console.log('No game server deployed in target environment')
|
|
924
|
+
exit(1)
|
|
925
|
+
}
|
|
926
|
+
|
|
952
927
|
// Run the checks and exit with success/failure exitCode depending on result
|
|
953
928
|
console.log(`Validating game server deployment`)
|
|
954
929
|
// \todo Get requiredImageTag from the Helm chart
|
|
@@ -964,19 +939,19 @@ program
|
|
|
964
939
|
program
|
|
965
940
|
.command('debug-server')
|
|
966
941
|
.description('run an ephemeral debug container against a game server pod running in the cloud')
|
|
967
|
-
.argument('
|
|
942
|
+
.argument('environment', 'target environment (e.g., metaplay-idler-develop or tiny-squids)')
|
|
968
943
|
.argument('[pod-name]', 'name of the pod to debug (must be specified if deployment has multiple pods)')
|
|
969
944
|
.hook('preAction', async () => {
|
|
970
945
|
await extendCurrentSession()
|
|
971
946
|
})
|
|
972
947
|
.action(
|
|
973
948
|
async (
|
|
974
|
-
|
|
949
|
+
environment: string,
|
|
975
950
|
podName: string | undefined,
|
|
976
951
|
options: { organization?: string; project?: string; environment?: string }
|
|
977
952
|
) => {
|
|
978
953
|
// Resolve target environment
|
|
979
|
-
const targetEnv = await resolveTargetEnvironment(
|
|
954
|
+
const targetEnv = await resolveTargetEnvironment(environment, options)
|
|
980
955
|
|
|
981
956
|
// Exec 'kubectl debug ...'
|
|
982
957
|
await debugGameServer(targetEnv, podName)
|
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.
|
|
4
|
+
"version": "1.8.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"homepage": "https://metaplay.io",
|
|
@@ -17,26 +17,26 @@
|
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@metaplay/eslint-config": "workspace:*",
|
|
20
|
-
"@types/dockerode": "3.3.
|
|
20
|
+
"@types/dockerode": "3.3.32",
|
|
21
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": "
|
|
25
|
+
"@types/node": "22.10.1",
|
|
26
26
|
"@types/semver": "7.5.8",
|
|
27
27
|
"esbuild": "0.24.0",
|
|
28
28
|
"tsx": "4.19.2",
|
|
29
29
|
"typescript": "5.6.3",
|
|
30
|
-
"vitest": "2.1.
|
|
31
|
-
"@aws-sdk/client-ecr": "3.
|
|
30
|
+
"vitest": "2.1.8",
|
|
31
|
+
"@aws-sdk/client-ecr": "3.699.0",
|
|
32
32
|
"@kubernetes/client-node": "1.0.0-rc7",
|
|
33
|
-
"@ory/client": "1.15.
|
|
33
|
+
"@ory/client": "1.15.13",
|
|
34
34
|
"commander": "12.1.0",
|
|
35
35
|
"dockerode": "4.0.2",
|
|
36
36
|
"h3": "1.13.0",
|
|
37
37
|
"js-yaml": "4.1.0",
|
|
38
38
|
"jsonwebtoken": "9.0.2",
|
|
39
|
-
"jwk-to-pem": "2.0.
|
|
39
|
+
"jwk-to-pem": "2.0.7",
|
|
40
40
|
"open": "8.4.2",
|
|
41
41
|
"semver": "7.6.3",
|
|
42
42
|
"tslog": "4.9.3"
|
package/src/auth.ts
CHANGED
|
@@ -35,7 +35,7 @@ const oidcApi = new OidcApi(
|
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* A helper function which generates a code verifier and challenge for
|
|
38
|
+
* A helper function which generates a code verifier and challenge for exchanging code from Ory server.
|
|
39
39
|
* @returns
|
|
40
40
|
*/
|
|
41
41
|
function generateCodeVerifierAndChallenge(): { verifier: string; challenge: string } {
|
|
@@ -131,10 +131,18 @@ export async function loginAndSaveTokens(): Promise<void> {
|
|
|
131
131
|
|
|
132
132
|
// Raise an error if the query parameters contain an error message.
|
|
133
133
|
if (error) {
|
|
134
|
+
let errorMessage
|
|
135
|
+
|
|
136
|
+
if (typeof error === 'object') {
|
|
137
|
+
errorMessage = JSON.stringify(error)
|
|
138
|
+
} else {
|
|
139
|
+
errorMessage = error
|
|
140
|
+
}
|
|
141
|
+
|
|
134
142
|
console.error(
|
|
135
|
-
`Error logging in. Received the following error:\n\n${
|
|
143
|
+
`Error logging in. Received the following error:\n\n${errorMessage}: ${String(errorDescription)}`
|
|
136
144
|
)
|
|
137
|
-
sendError(event, new Error(`Authentication failed: ${
|
|
145
|
+
sendError(event, new Error(`Authentication failed: ${errorMessage}: ${String(errorDescription)}`))
|
|
138
146
|
server.close()
|
|
139
147
|
process.exit(1)
|
|
140
148
|
}
|
|
@@ -331,7 +339,7 @@ async function getTokensWithAuthorizationCode(
|
|
|
331
339
|
verifier: string,
|
|
332
340
|
code: string
|
|
333
341
|
): Promise<{ id_token: string; access_token: string; refresh_token: string }> {
|
|
334
|
-
// TODO: the
|
|
342
|
+
// TODO: the authorization code exchange flow might be better to be handled by ory/client, could check if there's any useful tools there.
|
|
335
343
|
try {
|
|
336
344
|
const response = await fetch(tokenEndpoint, {
|
|
337
345
|
method: 'POST',
|
package/src/buildCommand.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { existsSync } from 'fs'
|
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import { exit } from 'process'
|
|
5
5
|
|
|
6
|
-
import { logger } from './logging.js'
|
|
7
6
|
import { pathJoin, executeCommand, ExecuteCommandResult } from './utils.js'
|
|
8
7
|
|
|
9
8
|
function resolveBuildEngine(engine?: string): string {
|
|
@@ -166,7 +165,7 @@ export function registerBuildCommand(program: Command): void {
|
|
|
166
165
|
if (commitId) {
|
|
167
166
|
console.log(`Using auto-detected commit id: ${commitId}`)
|
|
168
167
|
} else {
|
|
169
|
-
console.warn('Failed to auto-detect commit id, please specify with "--commit-id <git-sha>"')
|
|
168
|
+
console.warn('Warning: Failed to auto-detect commit id, please specify with "--commit-id <git-sha>"')
|
|
170
169
|
}
|
|
171
170
|
}
|
|
172
171
|
|
|
@@ -189,13 +188,14 @@ export function registerBuildCommand(program: Command): void {
|
|
|
189
188
|
if (buildNumber) {
|
|
190
189
|
console.log(`Using auto-detected build number: ${buildNumber}`)
|
|
191
190
|
} else {
|
|
192
|
-
console.warn('Failed to auto-detect build number, please specify with "--build-number <build-number>"')
|
|
191
|
+
console.warn('Warning: Failed to auto-detect build number, please specify with "--build-number <build-number>"')
|
|
193
192
|
}
|
|
194
193
|
}
|
|
195
194
|
|
|
196
195
|
// Resolve build engine to use
|
|
196
|
+
console.log('Resolve docker build engine...')
|
|
197
197
|
let buildEngine = resolveBuildEngine(options.engine)
|
|
198
|
-
|
|
198
|
+
console.log(`Using docker build engine: ${buildEngine}`)
|
|
199
199
|
|
|
200
200
|
// Check that docker is installed and running
|
|
201
201
|
const checkDockerResult = await executeCommand('docker', ['info'], { inheritStdio: false })
|
|
@@ -210,7 +210,7 @@ export function registerBuildCommand(program: Command): void {
|
|
|
210
210
|
inheritStdio: false
|
|
211
211
|
})
|
|
212
212
|
if (checkBuildxResult.exitCode !== 0) {
|
|
213
|
-
console.warn('Docker buildx is not supported on this machine, falling back to --engine=buildkit')
|
|
213
|
+
console.warn('Warning: Docker buildx is not supported on this machine, falling back to --engine=buildkit')
|
|
214
214
|
buildEngine = 'buildkit'
|
|
215
215
|
}
|
|
216
216
|
}
|
package/src/deployment.ts
CHANGED
|
@@ -32,7 +32,7 @@ interface GameServerPodStatus {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async function fetchGameServerPods(k8sApi: CoreV1Api, namespace: string): Promise<V1Pod[]> {
|
|
35
|
-
// Define label selector for gameserver
|
|
35
|
+
// Define label selector for gameserver pods
|
|
36
36
|
logger.debug(`Fetching game server pods from Kubernetes: namespace=${namespace}`)
|
|
37
37
|
const param: CoreV1ApiListNamespacedPodRequest = {
|
|
38
38
|
namespace,
|
|
@@ -465,7 +465,7 @@ async function waitForDomainResolution(hostname: string): Promise<void> {
|
|
|
465
465
|
} else {
|
|
466
466
|
console.log(`Failed to resolve ${hostname}: ${String(err)}. Retrying...`)
|
|
467
467
|
}
|
|
468
|
-
await delay(5000) // use long timeout as it can take ~
|
|
468
|
+
await delay(5000) // use long timeout as it can take ~15min for DNS to propagate
|
|
469
469
|
}
|
|
470
470
|
}
|
|
471
471
|
}
|