@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.
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, unlink } from 'fs/promises'
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 { pathJoin, isValidFQDN, executeCommand, removeTrailingSlash, fetchHelmChartVersions, resolveBestMatchingVersion } from './src/utils.js'
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
- // \todo Make field mandatory when portal returns valid values
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, humanId, `https://infra.${stackDomain}/stackapi`)
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 gameserver address
203
- * (idler-test.p1.metaplay.io), a shorthand address (metaplay-idler-test) or the (organization, project, environment)
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 "<organization>-<project>-<environment>", a humanId (eg, "delicious-elephant"), or a fully-qualified domain name (eg, idler-develop.p1.metaplay.io)`)
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('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
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
- gameserver: string | undefined,
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(gameserver, options)
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('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
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
- gameserver: string | undefined,
416
+ environment: string | undefined,
425
417
  options: { organization?: string; project?: string; environment?: string }
426
418
  ) => {
427
419
  try {
428
- const targetEnv = await resolveTargetEnvironment(gameserver, options)
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('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
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
- gameserver: string | undefined,
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(gameserver, options)
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('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
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
- gameserver: string | undefined,
493
+ environment: string | undefined,
561
494
  options: { organization?: string; project?: string; environment?: string }
562
495
  ) => {
563
496
  try {
564
- const targetEnv = await resolveTargetEnvironment(gameserver, options)
497
+ const targetEnv = await resolveTargetEnvironment(environment, options)
565
498
 
566
- const environment = await targetEnv.getEnvironmentDetails()
567
- console.log(JSON.stringify(environment, undefined, 2))
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('gameserver', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
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
- gameserver: string | undefined,
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 ${gameserver}...`)
526
+ console.log(`Pushing docker image ${imageName} to target environment ${environment}...`)
594
527
 
595
- const targetEnv = await resolveTargetEnvironment(gameserver, options)
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('gameserver', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
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
- gameserver: string | undefined,
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
- const targetEnv = await resolveTargetEnvironment(gameserver, options)
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
- // \todo validate that imageTag is just the version part (i.e, contains no ':')
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
- const testingRepoSuffix =
887
- !options.localChartPath && helmChartRepo !== 'https://charts.metaplay.dev'
888
- ? ` from repo ${helmChartRepo}`
889
- : ''
890
- console.log(
891
- `Game server deployed to ${gameserver} with tag ${imageTag} using chart version ${resolvedHelmChartVersion}${testingRepoSuffix}`
892
- )
893
- } finally {
894
- // Remove temporary kubeconfig file
895
- await unlink(kubeconfigPath)
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('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
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
- gameserver: string | undefined,
897
+ environment: string | undefined,
931
898
  options: { organization?: string; project?: string; environment?: string }
932
899
  ) => {
933
- const targetEnv = await resolveTargetEnvironment(gameserver, options)
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
- const kubeconfigPayload = await targetEnv.getKubeConfigWithEmbeddedCredentials()
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('gameserver', 'address of gameserver (e.g., metaplay-idler-develop or idler-develop.p1.metaplay.io)')
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
- gameserver: string,
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(gameserver, options)
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.7.2",
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.31",
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": "20.16.10",
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.4",
31
- "@aws-sdk/client-ecr": "3.682.0",
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.7",
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.6",
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 exchaning code from Ory server.
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${String(error)}: ${String(errorDescription)}`
143
+ `Error logging in. Received the following error:\n\n${errorMessage}: ${String(errorDescription)}`
136
144
  )
137
- sendError(event, new Error(`Authentication failed: ${String(error)}: ${String(errorDescription)}`))
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 authorication code exchange flow might be better to be handled by ory/client, could check if there's any useful toosl there.
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',
@@ -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
- logger.debug(`Using docker build engine: ${buildEngine}`)
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 ~5min for DNS to propagate
468
+ await delay(5000) // use long timeout as it can take ~15min for DNS to propagate
469
469
  }
470
470
  }
471
471
  }
@@ -121,7 +121,6 @@ export class TargetEnvironment {
121
121
  }
122
122
 
123
123
  async fetchText(url: string, method: string): Promise<string> {
124
-
125
124
  let response
126
125
  try {
127
126
  response = await fetch(url, {