@metaplay/metaplay-auth 1.1.5 → 1.1.6
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 +11 -0
- package/README.md +9 -0
- package/dist/deployment.js +208 -0
- package/dist/deployment.js.map +1 -0
- package/dist/index.js +46 -4
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
- package/src/deployment.ts +248 -0
- package/src/index.ts +51 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.6] - 2024-03-12
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
* Added experimental `check-deployment` command for checking the status of a game server after deploying it.
|
|
8
|
+
* Added experimental `machine-login` command to login using machine credentials.
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
* The `show-tokens` now outputs the values by default in JSON, you can use `metaplay-auth show-tokens -f pretty` to get the old formatting.
|
|
13
|
+
|
|
3
14
|
## [1.1.5] - 2024-02-26
|
|
4
15
|
|
|
5
16
|
### Changed
|
package/README.md
CHANGED
|
@@ -20,6 +20,15 @@ npx @metaplay/metaplay-auth@latest show-tokens
|
|
|
20
20
|
npx @metaplay/metaplay-auth@latest logout
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
## Running locally
|
|
24
|
+
|
|
25
|
+
When making changes to the `metaplay-auth`, it's easiest to test by running it locally:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
MetaplaySDK/AuthCLI$ pnpm dev login
|
|
29
|
+
MetaplaySDK/AuthCLI$ pnpm dev show-tokens
|
|
30
|
+
```
|
|
31
|
+
|
|
23
32
|
## License
|
|
24
33
|
|
|
25
34
|
See the LICENSE file.
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { KubeConfig, CoreV1Api, V1Pod } from '@kubernetes/client-node';
|
|
2
|
+
import { logger } from './logging.js';
|
|
3
|
+
import { error } from 'console';
|
|
4
|
+
var GameServerPodPhase;
|
|
5
|
+
(function (GameServerPodPhase) {
|
|
6
|
+
GameServerPodPhase["Pending"] = "Pending";
|
|
7
|
+
GameServerPodPhase["Ready"] = "Ready";
|
|
8
|
+
GameServerPodPhase["Failed"] = "Failed";
|
|
9
|
+
GameServerPodPhase["Unknown"] = "Unknown";
|
|
10
|
+
})(GameServerPodPhase || (GameServerPodPhase = {}));
|
|
11
|
+
async function fetchGameServerPods(k8sApi, namespace) {
|
|
12
|
+
// Define label selector for gameserver
|
|
13
|
+
const labelSelector = 'app=metaplay-server';
|
|
14
|
+
try {
|
|
15
|
+
// Get gameserver pods in the namespace
|
|
16
|
+
const response = await k8sApi.listNamespacedPod(namespace, undefined, undefined, undefined, undefined, labelSelector);
|
|
17
|
+
// Return pod statuses
|
|
18
|
+
return response.body.items;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
// \todo Better error handling ..
|
|
22
|
+
console.log('Failed to fetch pods from Kubernetes:', error);
|
|
23
|
+
throw new Error('Failed to fetch pods from Kubernetes');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function resolvePodContainersConditions(pod) {
|
|
27
|
+
const containerStatuses = pod.status?.containerStatuses;
|
|
28
|
+
if (!containerStatuses || containerStatuses.length === 0) {
|
|
29
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Unable to determine pod container statuses: pod.status.containerStatuses is empty' };
|
|
30
|
+
}
|
|
31
|
+
// Only one container allowed in the game server pod
|
|
32
|
+
if (containerStatuses.length !== 1) {
|
|
33
|
+
throw new Error(`Internal error: Expecting only one container in the game server pod, got ${containerStatuses.length}`);
|
|
34
|
+
}
|
|
35
|
+
// Handle missing container state
|
|
36
|
+
const containerStatus = containerStatuses[0];
|
|
37
|
+
console.log('Container status:', containerStatus);
|
|
38
|
+
const containerState = containerStatus.state;
|
|
39
|
+
if (!containerState) {
|
|
40
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Unable to get container state' };
|
|
41
|
+
}
|
|
42
|
+
// Check if container running & ready
|
|
43
|
+
const containerName = containerStatus.name;
|
|
44
|
+
if (containerStatus.ready && containerState.running) {
|
|
45
|
+
return { phase: GameServerPodPhase.Ready, message: `Container ${containerName} is in ready phase and was started at ${containerState.running.startedAt}`, details: containerState.running };
|
|
46
|
+
}
|
|
47
|
+
// \note these may not be complete (or completely accurate)
|
|
48
|
+
const knownContainerFailureReasons = ['CrashLoopBackOff', 'Error', 'ImagePullBackOff', 'CreateContainerConfigError', 'OOMKilled', 'ContainerCannotRun', 'BackOff', 'InvalidImageName'];
|
|
49
|
+
const knownContainerPendingReasons = ['Init', 'Pending', 'PodInitializing'];
|
|
50
|
+
// Check if there's a previous terminated state (usually indicates a crash during server initialization)
|
|
51
|
+
const lastState = containerStatus.lastState;
|
|
52
|
+
if (lastState) {
|
|
53
|
+
if (lastState.terminated) {
|
|
54
|
+
// Try to detecth why the previous launch failed
|
|
55
|
+
if (containerState.waiting) {
|
|
56
|
+
const reason = containerState.waiting.reason;
|
|
57
|
+
if (knownContainerFailureReasons.includes(reason)) {
|
|
58
|
+
return { phase: GameServerPodPhase.Failed, message: `Container ${containerName} is in waiting state, reason=${reason}`, details: containerState.waiting };
|
|
59
|
+
}
|
|
60
|
+
else if (knownContainerPendingReasons.includes(reason)) {
|
|
61
|
+
return { phase: GameServerPodPhase.Pending, message: `Container ${containerName} is in waiting state, reason=${reason}`, details: containerState.waiting };
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return { phase: GameServerPodPhase.Unknown, message: `Container ${containerName} is in waiting state, reason=${reason}`, details: containerState.waiting };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else if (containerState.running) {
|
|
68
|
+
// This happens when the container is still initializing
|
|
69
|
+
return { phase: GameServerPodPhase.Pending, message: `Container ${containerName} is in running state`, details: containerState.running };
|
|
70
|
+
}
|
|
71
|
+
else if (containerState.terminated) {
|
|
72
|
+
return { phase: GameServerPodPhase.Failed, message: `Container ${containerName} is in terminated state`, details: containerState.terminated };
|
|
73
|
+
}
|
|
74
|
+
// Unable to determine launch failure reason, just return previous launch
|
|
75
|
+
return { phase: GameServerPodPhase.Failed, message: `Container ${containerName} previous launch failed with exitCode=${lastState.terminated.exitCode} and reason=${lastState.terminated.reason}`, details: lastState.terminated };
|
|
76
|
+
}
|
|
77
|
+
// \todo handle containerState.running states (including various initialization states)
|
|
78
|
+
// \todo handle containerState.terminated states (what do these even mean)
|
|
79
|
+
}
|
|
80
|
+
console.log('Game server pod container in unknown state:', containerState);
|
|
81
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Container in unknown state', details: containerState };
|
|
82
|
+
}
|
|
83
|
+
function resolvePodStatusConditions(pod) {
|
|
84
|
+
const conditions = pod.status?.conditions;
|
|
85
|
+
if (!conditions || conditions.length === 0) {
|
|
86
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Unable to determine pod status: pod.status.conditions is empty', details: pod.status };
|
|
87
|
+
}
|
|
88
|
+
// Bail if 'PodScheduled' is not yet true
|
|
89
|
+
const condPodScheduled = conditions.find(cond => cond.type === 'PodScheduled');
|
|
90
|
+
if (condPodScheduled?.status !== 'True') {
|
|
91
|
+
return { phase: GameServerPodPhase.Pending, message: `Pod has not yet been scheduled on a node: ${condPodScheduled?.message}`, details: condPodScheduled };
|
|
92
|
+
}
|
|
93
|
+
// Bail if 'Initialized' not is yet true
|
|
94
|
+
const condInitialized = conditions.find(cond => cond.type === 'Initialized');
|
|
95
|
+
if (condInitialized?.status !== 'True') {
|
|
96
|
+
return { phase: GameServerPodPhase.Pending, message: `Pod has not yet been initialized: ${condInitialized?.message}`, details: condInitialized };
|
|
97
|
+
}
|
|
98
|
+
// Bail if 'ContainersReady' is not yet true
|
|
99
|
+
const condContainersReady = conditions.find(cond => cond.type === 'ContainersReady');
|
|
100
|
+
if (condContainersReady?.status !== 'True') {
|
|
101
|
+
if (condContainersReady?.reason === 'ContainersNotReady') {
|
|
102
|
+
return resolvePodContainersConditions(pod);
|
|
103
|
+
}
|
|
104
|
+
return { phase: GameServerPodPhase.Pending, message: `Pod containers are not yet ready: ${condContainersReady?.message}`, details: condContainersReady };
|
|
105
|
+
}
|
|
106
|
+
// Bail if 'Ready' is not yet true
|
|
107
|
+
const condReady = conditions.find(cond => cond.type === 'Ready');
|
|
108
|
+
if (condReady?.status !== 'True') {
|
|
109
|
+
return { phase: GameServerPodPhase.Pending, message: `Pod is not yet ready: ${condReady?.message}`, details: condReady };
|
|
110
|
+
}
|
|
111
|
+
// resolvePodContainersConditions(pod) // DEBUG DEBUG enable to print container state for running pods
|
|
112
|
+
return { phase: GameServerPodPhase.Ready, message: 'Pod is ready to serve traffic' };
|
|
113
|
+
}
|
|
114
|
+
function resolvePodStatus(pod) {
|
|
115
|
+
// console.log('Pod.status:', JSON.stringify(pod.status, undefined, 2))
|
|
116
|
+
if (!pod.status) {
|
|
117
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Unable to access pod.status from Kubernetes' };
|
|
118
|
+
}
|
|
119
|
+
// Handle status.phase
|
|
120
|
+
const podPhase = pod.status?.phase;
|
|
121
|
+
switch (podPhase) {
|
|
122
|
+
case 'Pending':
|
|
123
|
+
// Pod not yet scheduled
|
|
124
|
+
return { phase: GameServerPodPhase.Pending, message: 'Pod is still in Pending phase' };
|
|
125
|
+
case 'Running':
|
|
126
|
+
// Pod has been scheduled and start -- note that the containers may have failed!
|
|
127
|
+
return resolvePodStatusConditions(pod);
|
|
128
|
+
case 'Succeeded': // Should not happen, the game server pods should never stop
|
|
129
|
+
case 'Failed': // Should not happen, the game server pods should never stop
|
|
130
|
+
case 'Unknown':
|
|
131
|
+
default:
|
|
132
|
+
return { phase: GameServerPodPhase.Unknown, message: `Invalid pod.status.phase: ${podPhase}` };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async function fetchPodLogs(k8sApi, pod) {
|
|
136
|
+
console.log('Fetching logs for pod..');
|
|
137
|
+
const podName = pod.metadata?.name;
|
|
138
|
+
const namespace = pod.metadata?.namespace;
|
|
139
|
+
const containerName = pod.spec?.containers[0].name; // \todo Handle multiple containers?
|
|
140
|
+
if (!podName || !namespace || !containerName) {
|
|
141
|
+
throw new Error('Unable to determine pod metadata');
|
|
142
|
+
}
|
|
143
|
+
const pretty = 'True';
|
|
144
|
+
const previous = false;
|
|
145
|
+
const tailLines = 100;
|
|
146
|
+
const timestamps = true;
|
|
147
|
+
try {
|
|
148
|
+
const response = await k8sApi.readNamespacedPodLog(podName, namespace, containerName, undefined, undefined, undefined, pretty, previous, undefined, tailLines, timestamps);
|
|
149
|
+
return response.body;
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
// \todo Better error handling ..
|
|
153
|
+
console.log('Failed to fetch pod logs from Kubernetes:', error);
|
|
154
|
+
throw new Error('Failed to fetch pod logs from Kubernetes');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function checkGameServerPod(k8sApi, pod) {
|
|
158
|
+
// console.log('Pod:', JSON.stringify(pod, undefined, 2))
|
|
159
|
+
// Classify game server status
|
|
160
|
+
const podStatus = resolvePodStatus(pod);
|
|
161
|
+
// If game server launch failed, get the error logs
|
|
162
|
+
if (podStatus.phase === GameServerPodPhase.Failed) {
|
|
163
|
+
const logs = await fetchPodLogs(k8sApi, pod);
|
|
164
|
+
console.log('Pod logs:\n' + logs);
|
|
165
|
+
}
|
|
166
|
+
console.log(`Pod ${pod.metadata?.name} status:`, podStatus);
|
|
167
|
+
return podStatus;
|
|
168
|
+
}
|
|
169
|
+
async function delay(ms) {
|
|
170
|
+
return await new Promise(resolve => setTimeout(resolve, ms));
|
|
171
|
+
}
|
|
172
|
+
export async function checkGameServerDeployment(namespace) {
|
|
173
|
+
logger.info(`Validating deployment: namespace=${namespace}, ..`);
|
|
174
|
+
// Create Kubernetes API instance (with default kubeconfig)
|
|
175
|
+
const kc = new KubeConfig();
|
|
176
|
+
kc.loadFromDefault();
|
|
177
|
+
const k8sApi = kc.makeApiClient(CoreV1Api);
|
|
178
|
+
// Figure out when to stop
|
|
179
|
+
const startTime = Date.now();
|
|
180
|
+
const timeoutAt = startTime + 1 * 60 * 1000; // 5min
|
|
181
|
+
while (true) {
|
|
182
|
+
// Check pod states
|
|
183
|
+
const pods = await fetchGameServerPods(k8sApi, namespace);
|
|
184
|
+
const podStatus = await checkGameServerPod(k8sApi, pods[0]); // \todo Handle all pods
|
|
185
|
+
switch (podStatus.phase) {
|
|
186
|
+
case GameServerPodPhase.Ready:
|
|
187
|
+
logger.error('Gameserver successfully started');
|
|
188
|
+
// \todo add further readiness checks -- ping endpoint, ping dashboard, other checks?
|
|
189
|
+
return 0;
|
|
190
|
+
case GameServerPodPhase.Failed:
|
|
191
|
+
logger.error('Gameserver failed to start');
|
|
192
|
+
return 1;
|
|
193
|
+
case GameServerPodPhase.Pending:
|
|
194
|
+
logger.error('Gameserver still starting');
|
|
195
|
+
break;
|
|
196
|
+
case GameServerPodPhase.Unknown:
|
|
197
|
+
default:
|
|
198
|
+
logger.error('Gameserver in unknown state');
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
if (Date.now() >= timeoutAt) {
|
|
202
|
+
logger.error('Timeout while waiting for gameserver to initialize');
|
|
203
|
+
return 124; // timeout
|
|
204
|
+
}
|
|
205
|
+
await delay(1000);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=deployment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment.js","sourceRoot":"","sources":["../src/deployment.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { loginAndSaveTokens, extendCurrentSession, loadTokens, removeTokens } from './auth.js';
|
|
3
|
+
import { loginAndSaveTokens, extendCurrentSession, loadTokens, saveTokens, removeTokens } from './auth.js';
|
|
4
4
|
import { StackAPI } from './stackapi.js';
|
|
5
|
+
import { checkGameServerDeployment } from './deployment.js';
|
|
5
6
|
import { setLogLevel } from './logging.js';
|
|
6
7
|
import { exit } from 'process';
|
|
7
8
|
const program = new Command();
|
|
8
9
|
program
|
|
9
10
|
.name('metaplay-auth')
|
|
10
11
|
.description('Authenticate with Metaplay and get AWS and Kubernetes credentials for game servers.')
|
|
11
|
-
.version('1.1.
|
|
12
|
+
.version('1.1.6')
|
|
12
13
|
.option('-d, --debug', 'enable debug output')
|
|
13
14
|
.hook('preAction', (thisCommand) => {
|
|
14
15
|
// Handle debug flag for all commands.
|
|
@@ -25,6 +26,26 @@ program.command('login')
|
|
|
25
26
|
.action(async () => {
|
|
26
27
|
await loginAndSaveTokens();
|
|
27
28
|
});
|
|
29
|
+
program.command('machine-login')
|
|
30
|
+
.description('[experimental] login to the Metaplay cloud using a machine account (using credentials in environment variable METAPLAY_CREDENTIALS)')
|
|
31
|
+
.action(async () => {
|
|
32
|
+
const credentials = process.env.METAPLAY_CREDENTIALS;
|
|
33
|
+
if (!credentials || credentials === '') {
|
|
34
|
+
throw new Error('Unable to find the credentials, the environment variable METAPLAY_CREDENTIALS is not defined!');
|
|
35
|
+
}
|
|
36
|
+
// Exchange the credentials for tokens. For now, as we don't have proper M2M accounts implemented, we're simulating it by ingesting the raw tokens JSON instead.
|
|
37
|
+
// \todo Replace (or extend) this with exchanging the credentials to usable tokens
|
|
38
|
+
let tokens;
|
|
39
|
+
try {
|
|
40
|
+
tokens = JSON.parse(credentials);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
throw new Error('Unable to parse METAPLAY_CREDENTIALS: not valid JSON. Expecting a JSON blob with the access tokens, as outputted by `metaplay-auth show-tokens`.');
|
|
44
|
+
}
|
|
45
|
+
// Save tokens
|
|
46
|
+
await saveTokens(tokens);
|
|
47
|
+
console.log('Successfully logged in to Metaplay cloud using machine account!');
|
|
48
|
+
});
|
|
28
49
|
program.command('logout')
|
|
29
50
|
.description('log out of your Metaplay account')
|
|
30
51
|
.action(async () => {
|
|
@@ -43,14 +64,23 @@ program.command('logout')
|
|
|
43
64
|
});
|
|
44
65
|
program.command('show-tokens')
|
|
45
66
|
.description('show loaded tokens')
|
|
67
|
+
.option('-f, --format <format>', 'output format (json or pretty)', 'json')
|
|
46
68
|
.hook('preAction', async () => {
|
|
47
69
|
await extendCurrentSession();
|
|
48
70
|
})
|
|
49
|
-
.action(async () => {
|
|
71
|
+
.action(async (options) => {
|
|
50
72
|
try {
|
|
73
|
+
if (options.format !== 'json' && options.format !== 'pretty') {
|
|
74
|
+
throw new Error('Invalid format; must be one of json or pretty');
|
|
75
|
+
}
|
|
51
76
|
// TODO: Could detect if not logged in and fail more gracefully?
|
|
52
77
|
const tokens = await loadTokens();
|
|
53
|
-
|
|
78
|
+
if (options.format === 'json') {
|
|
79
|
+
console.log(JSON.stringify(tokens));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(tokens);
|
|
83
|
+
}
|
|
54
84
|
}
|
|
55
85
|
catch (error) {
|
|
56
86
|
if (error instanceof Error) {
|
|
@@ -166,5 +196,17 @@ program.command('get-environment')
|
|
|
166
196
|
exit(1);
|
|
167
197
|
}
|
|
168
198
|
});
|
|
199
|
+
program.command('check-deployment')
|
|
200
|
+
.description('[experimental] check that a game server was successfully deployed, or print out useful error messages in case of failure')
|
|
201
|
+
.argument('[namespace]', 'kubernetes namespace of the deployment')
|
|
202
|
+
.hook('preAction', async () => {
|
|
203
|
+
await extendCurrentSession();
|
|
204
|
+
})
|
|
205
|
+
.action(async (namespace, options) => {
|
|
206
|
+
setLogLevel(0);
|
|
207
|
+
// Run the checks and exit with success/failure exitCode depending on result
|
|
208
|
+
const exitCode = await checkGameServerDeployment(namespace);
|
|
209
|
+
exit(exitCode);
|
|
210
|
+
});
|
|
169
211
|
void program.parseAsync();
|
|
170
212
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,27 +1,23 @@
|
|
|
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.1.
|
|
4
|
+
"version": "1.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"homepage": "https://metaplay.io",
|
|
8
8
|
"bin": "dist/index.js",
|
|
9
|
-
"scripts": {
|
|
10
|
-
"dev": "tsx src/index.ts",
|
|
11
|
-
"prepublish": "tsc"
|
|
12
|
-
},
|
|
13
9
|
"publishConfig": {
|
|
14
10
|
"access": "public"
|
|
15
11
|
},
|
|
16
12
|
"devDependencies": {
|
|
17
|
-
"@metaplay/eslint-config": "workspace:*",
|
|
18
|
-
"@metaplay/typescript-config": "workspace:*",
|
|
19
13
|
"@types/express": "^4.17.21",
|
|
20
14
|
"@types/jsonwebtoken": "^9.0.5",
|
|
21
15
|
"@types/jwk-to-pem": "^2.0.3",
|
|
22
16
|
"@types/node": "^20.11.20",
|
|
23
17
|
"tsx": "^4.7.1",
|
|
24
|
-
"typescript": "^5.1.6"
|
|
18
|
+
"typescript": "^5.1.6",
|
|
19
|
+
"@metaplay/eslint-config": "1.0.0",
|
|
20
|
+
"@metaplay/typescript-config": "1.0.0"
|
|
25
21
|
},
|
|
26
22
|
"dependencies": {
|
|
27
23
|
"@ory/client": "^1.6.2",
|
|
@@ -30,6 +26,11 @@
|
|
|
30
26
|
"jsonwebtoken": "^9.0.2",
|
|
31
27
|
"jwk-to-pem": "^2.0.5",
|
|
32
28
|
"open": "^10.0.2",
|
|
33
|
-
"tslog": "^4.9.2"
|
|
29
|
+
"tslog": "^4.9.2",
|
|
30
|
+
"@kubernetes/client-node": "^0.20.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"dev": "tsx src/index.ts",
|
|
34
|
+
"prepublish": "tsc"
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { KubeConfig, CoreV1Api, V1Pod } from '@kubernetes/client-node'
|
|
2
|
+
import { logger } from './logging.js'
|
|
3
|
+
import { error } from 'console'
|
|
4
|
+
|
|
5
|
+
enum GameServerPodPhase {
|
|
6
|
+
Pending = 'Pending', // Still being deployed
|
|
7
|
+
Ready = 'Ready', // Successfully started and ready to accept traffic
|
|
8
|
+
Failed = 'Failed', // Failed to start
|
|
9
|
+
Unknown = 'Unknown', // Unknown status -- treat like Pending
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface GameServerPodStatus {
|
|
13
|
+
phase: GameServerPodPhase
|
|
14
|
+
message: string
|
|
15
|
+
details?: any
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function fetchGameServerPods (k8sApi: CoreV1Api, namespace: string) {
|
|
19
|
+
// Define label selector for gameserver
|
|
20
|
+
const labelSelector = 'app=metaplay-server'
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Get gameserver pods in the namespace
|
|
24
|
+
const response = await k8sApi.listNamespacedPod(namespace, undefined, undefined, undefined, undefined, labelSelector)
|
|
25
|
+
|
|
26
|
+
// Return pod statuses
|
|
27
|
+
return response.body.items
|
|
28
|
+
} catch (error) {
|
|
29
|
+
// \todo Better error handling ..
|
|
30
|
+
console.log('Failed to fetch pods from Kubernetes:', error)
|
|
31
|
+
throw new Error('Failed to fetch pods from Kubernetes')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolvePodContainersConditions (pod: V1Pod): GameServerPodStatus {
|
|
36
|
+
const containerStatuses = pod.status?.containerStatuses
|
|
37
|
+
if (!containerStatuses || containerStatuses.length === 0) {
|
|
38
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Unable to determine pod container statuses: pod.status.containerStatuses is empty' }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Only one container allowed in the game server pod
|
|
42
|
+
if (containerStatuses.length !== 1) {
|
|
43
|
+
throw new Error(`Internal error: Expecting only one container in the game server pod, got ${containerStatuses.length}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Handle missing container state
|
|
47
|
+
const containerStatus = containerStatuses[0]
|
|
48
|
+
console.log('Container status:', containerStatus)
|
|
49
|
+
const containerState = containerStatus.state
|
|
50
|
+
if (!containerState) {
|
|
51
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Unable to get container state' }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if container running & ready
|
|
55
|
+
const containerName = containerStatus.name
|
|
56
|
+
if (containerStatus.ready && containerState.running) {
|
|
57
|
+
return { phase: GameServerPodPhase.Ready, message: `Container ${containerName} is in ready phase and was started at ${containerState.running.startedAt}`, details: containerState.running }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// \note these may not be complete (or completely accurate)
|
|
61
|
+
const knownContainerFailureReasons = ['CrashLoopBackOff', 'Error', 'ImagePullBackOff', 'CreateContainerConfigError', 'OOMKilled', 'ContainerCannotRun', 'BackOff', 'InvalidImageName']
|
|
62
|
+
const knownContainerPendingReasons = ['Init', 'Pending', 'PodInitializing']
|
|
63
|
+
|
|
64
|
+
// Check if there's a previous terminated state (usually indicates a crash during server initialization)
|
|
65
|
+
const lastState = containerStatus.lastState
|
|
66
|
+
if (lastState) {
|
|
67
|
+
if (lastState.terminated) {
|
|
68
|
+
// Try to detecth why the previous launch failed
|
|
69
|
+
if (containerState.waiting) {
|
|
70
|
+
const reason = containerState.waiting.reason
|
|
71
|
+
if (knownContainerFailureReasons.includes(reason as string)) {
|
|
72
|
+
return { phase: GameServerPodPhase.Failed, message: `Container ${containerName} is in waiting state, reason=${reason}`, details: containerState.waiting }
|
|
73
|
+
} else if (knownContainerPendingReasons.includes(reason as string)) {
|
|
74
|
+
return { phase: GameServerPodPhase.Pending, message: `Container ${containerName} is in waiting state, reason=${reason}`, details: containerState.waiting }
|
|
75
|
+
} else {
|
|
76
|
+
return { phase: GameServerPodPhase.Unknown, message: `Container ${containerName} is in waiting state, reason=${reason}`, details: containerState.waiting }
|
|
77
|
+
}
|
|
78
|
+
} else if (containerState.running) {
|
|
79
|
+
// This happens when the container is still initializing
|
|
80
|
+
return { phase: GameServerPodPhase.Pending, message: `Container ${containerName} is in running state`, details: containerState.running }
|
|
81
|
+
} else if (containerState.terminated) {
|
|
82
|
+
return { phase: GameServerPodPhase.Failed, message: `Container ${containerName} is in terminated state`, details: containerState.terminated }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Unable to determine launch failure reason, just return previous launch
|
|
86
|
+
return { phase: GameServerPodPhase.Failed, message: `Container ${containerName} previous launch failed with exitCode=${lastState.terminated.exitCode} and reason=${lastState.terminated.reason}`, details: lastState.terminated }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// \todo handle containerState.running states (including various initialization states)
|
|
90
|
+
// \todo handle containerState.terminated states (what do these even mean)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log('Game server pod container in unknown state:', containerState)
|
|
94
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Container in unknown state', details: containerState }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolvePodStatusConditions (pod: V1Pod): GameServerPodStatus {
|
|
98
|
+
const conditions = pod.status?.conditions
|
|
99
|
+
if (!conditions || conditions.length === 0) {
|
|
100
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Unable to determine pod status: pod.status.conditions is empty', details: pod.status }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Bail if 'PodScheduled' is not yet true
|
|
104
|
+
const condPodScheduled = conditions.find(cond => cond.type === 'PodScheduled')
|
|
105
|
+
if (condPodScheduled?.status !== 'True') {
|
|
106
|
+
return { phase: GameServerPodPhase.Pending, message: `Pod has not yet been scheduled on a node: ${condPodScheduled?.message}`, details: condPodScheduled }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Bail if 'Initialized' not is yet true
|
|
110
|
+
const condInitialized = conditions.find(cond => cond.type === 'Initialized')
|
|
111
|
+
if (condInitialized?.status !== 'True') {
|
|
112
|
+
return { phase: GameServerPodPhase.Pending, message: `Pod has not yet been initialized: ${condInitialized?.message}`, details: condInitialized }
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Bail if 'ContainersReady' is not yet true
|
|
116
|
+
const condContainersReady = conditions.find(cond => cond.type === 'ContainersReady')
|
|
117
|
+
if (condContainersReady?.status !== 'True') {
|
|
118
|
+
if (condContainersReady?.reason === 'ContainersNotReady') {
|
|
119
|
+
return resolvePodContainersConditions(pod)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return { phase: GameServerPodPhase.Pending, message: `Pod containers are not yet ready: ${condContainersReady?.message}`, details: condContainersReady }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Bail if 'Ready' is not yet true
|
|
126
|
+
const condReady = conditions.find(cond => cond.type === 'Ready')
|
|
127
|
+
if (condReady?.status !== 'True') {
|
|
128
|
+
return { phase: GameServerPodPhase.Pending, message: `Pod is not yet ready: ${condReady?.message}`, details: condReady }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// resolvePodContainersConditions(pod) // DEBUG DEBUG enable to print container state for running pods
|
|
132
|
+
return { phase: GameServerPodPhase.Ready, message: 'Pod is ready to serve traffic' }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resolvePodStatus (pod: V1Pod): GameServerPodStatus {
|
|
136
|
+
// console.log('Pod.status:', JSON.stringify(pod.status, undefined, 2))
|
|
137
|
+
|
|
138
|
+
if (!pod.status) {
|
|
139
|
+
return { phase: GameServerPodPhase.Unknown, message: 'Unable to access pod.status from Kubernetes' }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Handle status.phase
|
|
143
|
+
const podPhase = pod.status?.phase
|
|
144
|
+
switch (podPhase) {
|
|
145
|
+
case 'Pending':
|
|
146
|
+
// Pod not yet scheduled
|
|
147
|
+
return { phase: GameServerPodPhase.Pending, message: 'Pod is still in Pending phase' }
|
|
148
|
+
|
|
149
|
+
case 'Running':
|
|
150
|
+
// Pod has been scheduled and start -- note that the containers may have failed!
|
|
151
|
+
return resolvePodStatusConditions(pod)
|
|
152
|
+
|
|
153
|
+
case 'Succeeded': // Should not happen, the game server pods should never stop
|
|
154
|
+
case 'Failed': // Should not happen, the game server pods should never stop
|
|
155
|
+
case 'Unknown':
|
|
156
|
+
default:
|
|
157
|
+
return { phase: GameServerPodPhase.Unknown, message: `Invalid pod.status.phase: ${podPhase}` }
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function fetchPodLogs (k8sApi: CoreV1Api, pod: V1Pod) {
|
|
162
|
+
console.log('Fetching logs for pod..')
|
|
163
|
+
const podName = pod.metadata?.name
|
|
164
|
+
const namespace = pod.metadata?.namespace
|
|
165
|
+
const containerName = pod.spec?.containers[0].name // \todo Handle multiple containers?
|
|
166
|
+
if (!podName || !namespace || !containerName) {
|
|
167
|
+
throw new Error('Unable to determine pod metadata')
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const pretty = 'True'
|
|
171
|
+
const previous = false
|
|
172
|
+
const tailLines = 100
|
|
173
|
+
const timestamps = true
|
|
174
|
+
try {
|
|
175
|
+
const response = await k8sApi.readNamespacedPodLog(podName, namespace, containerName, undefined, undefined, undefined, pretty, previous, undefined, tailLines, timestamps)
|
|
176
|
+
return response.body
|
|
177
|
+
} catch (error) {
|
|
178
|
+
// \todo Better error handling ..
|
|
179
|
+
console.log('Failed to fetch pod logs from Kubernetes:', error)
|
|
180
|
+
throw new Error('Failed to fetch pod logs from Kubernetes')
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function checkGameServerPod (k8sApi: CoreV1Api, pod: V1Pod) {
|
|
185
|
+
// console.log('Pod:', JSON.stringify(pod, undefined, 2))
|
|
186
|
+
|
|
187
|
+
// Classify game server status
|
|
188
|
+
const podStatus = resolvePodStatus(pod)
|
|
189
|
+
|
|
190
|
+
// If game server launch failed, get the error logs
|
|
191
|
+
if (podStatus.phase === GameServerPodPhase.Failed) {
|
|
192
|
+
const logs = await fetchPodLogs(k8sApi, pod)
|
|
193
|
+
console.log('Pod logs:\n' + logs)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
console.log(`Pod ${pod.metadata?.name} status:`, podStatus)
|
|
197
|
+
return podStatus
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function delay (ms: number): Promise<void> {
|
|
201
|
+
return await new Promise<void>(resolve => setTimeout(resolve, ms))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function checkGameServerDeployment (namespace: string): Promise<number> {
|
|
205
|
+
logger.info(`Validating deployment: namespace=${namespace}, ..`)
|
|
206
|
+
|
|
207
|
+
// Create Kubernetes API instance (with default kubeconfig)
|
|
208
|
+
const kc = new KubeConfig()
|
|
209
|
+
kc.loadFromDefault()
|
|
210
|
+
const k8sApi = kc.makeApiClient(CoreV1Api)
|
|
211
|
+
|
|
212
|
+
// Figure out when to stop
|
|
213
|
+
const startTime = Date.now()
|
|
214
|
+
const timeoutAt = startTime + 1 * 60 * 1000 // 5min
|
|
215
|
+
|
|
216
|
+
while (true) {
|
|
217
|
+
// Check pod states
|
|
218
|
+
const pods = await fetchGameServerPods(k8sApi, namespace)
|
|
219
|
+
const podStatus = await checkGameServerPod(k8sApi, pods[0]) // \todo Handle all pods
|
|
220
|
+
|
|
221
|
+
switch (podStatus.phase) {
|
|
222
|
+
case GameServerPodPhase.Ready:
|
|
223
|
+
logger.error('Gameserver successfully started')
|
|
224
|
+
// \todo add further readiness checks -- ping endpoint, ping dashboard, other checks?
|
|
225
|
+
return 0
|
|
226
|
+
|
|
227
|
+
case GameServerPodPhase.Failed:
|
|
228
|
+
logger.error('Gameserver failed to start')
|
|
229
|
+
return 1
|
|
230
|
+
|
|
231
|
+
case GameServerPodPhase.Pending:
|
|
232
|
+
logger.error('Gameserver still starting')
|
|
233
|
+
break
|
|
234
|
+
|
|
235
|
+
case GameServerPodPhase.Unknown:
|
|
236
|
+
default:
|
|
237
|
+
logger.error('Gameserver in unknown state')
|
|
238
|
+
break
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (Date.now() >= timeoutAt) {
|
|
242
|
+
logger.error('Timeout while waiting for gameserver to initialize')
|
|
243
|
+
return 124 // timeout
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await delay(1000)
|
|
247
|
+
}
|
|
248
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander'
|
|
3
|
-
import { loginAndSaveTokens, extendCurrentSession, loadTokens, removeTokens } from './auth.js'
|
|
3
|
+
import { loginAndSaveTokens, extendCurrentSession, loadTokens, saveTokens, removeTokens } from './auth.js'
|
|
4
4
|
import { StackAPI } from './stackapi.js'
|
|
5
|
+
import { checkGameServerDeployment } from './deployment.js'
|
|
5
6
|
import { setLogLevel } from './logging.js'
|
|
6
7
|
import { exit } from 'process'
|
|
7
8
|
|
|
@@ -10,7 +11,7 @@ const program = new Command()
|
|
|
10
11
|
program
|
|
11
12
|
.name('metaplay-auth')
|
|
12
13
|
.description('Authenticate with Metaplay and get AWS and Kubernetes credentials for game servers.')
|
|
13
|
-
.version('1.1.
|
|
14
|
+
.version('1.1.6')
|
|
14
15
|
.option('-d, --debug', 'enable debug output')
|
|
15
16
|
.hook('preAction', (thisCommand) => {
|
|
16
17
|
// Handle debug flag for all commands.
|
|
@@ -28,6 +29,29 @@ program.command('login')
|
|
|
28
29
|
await loginAndSaveTokens()
|
|
29
30
|
})
|
|
30
31
|
|
|
32
|
+
program.command('machine-login')
|
|
33
|
+
.description('[experimental] login to the Metaplay cloud using a machine account (using credentials in environment variable METAPLAY_CREDENTIALS)')
|
|
34
|
+
.action(async () => {
|
|
35
|
+
const credentials = process.env.METAPLAY_CREDENTIALS
|
|
36
|
+
if (!credentials || credentials === '') {
|
|
37
|
+
throw new Error('Unable to find the credentials, the environment variable METAPLAY_CREDENTIALS is not defined!')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Exchange the credentials for tokens. For now, as we don't have proper M2M accounts implemented, we're simulating it by ingesting the raw tokens JSON instead.
|
|
41
|
+
// \todo Replace (or extend) this with exchanging the credentials to usable tokens
|
|
42
|
+
let tokens
|
|
43
|
+
try {
|
|
44
|
+
tokens = JSON.parse(credentials)
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error('Unable to parse METAPLAY_CREDENTIALS: not valid JSON. Expecting a JSON blob with the access tokens, as outputted by `metaplay-auth show-tokens`.')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Save tokens
|
|
50
|
+
await saveTokens(tokens)
|
|
51
|
+
|
|
52
|
+
console.log('Successfully logged in to Metaplay cloud using machine account!')
|
|
53
|
+
})
|
|
54
|
+
|
|
31
55
|
program.command('logout')
|
|
32
56
|
.description('log out of your Metaplay account')
|
|
33
57
|
.action(async () => {
|
|
@@ -47,14 +71,23 @@ program.command('logout')
|
|
|
47
71
|
|
|
48
72
|
program.command('show-tokens')
|
|
49
73
|
.description('show loaded tokens')
|
|
74
|
+
.option('-f, --format <format>', 'output format (json or pretty)', 'json')
|
|
50
75
|
.hook('preAction', async () => {
|
|
51
76
|
await extendCurrentSession()
|
|
52
77
|
})
|
|
53
|
-
.action(async () => {
|
|
78
|
+
.action(async (options) => {
|
|
54
79
|
try {
|
|
80
|
+
if (options.format !== 'json' && options.format !== 'pretty') {
|
|
81
|
+
throw new Error('Invalid format; must be one of json or pretty')
|
|
82
|
+
}
|
|
83
|
+
|
|
55
84
|
// TODO: Could detect if not logged in and fail more gracefully?
|
|
56
85
|
const tokens = await loadTokens()
|
|
57
|
-
|
|
86
|
+
if (options.format === 'json') {
|
|
87
|
+
console.log(JSON.stringify(tokens))
|
|
88
|
+
} else {
|
|
89
|
+
console.log(tokens)
|
|
90
|
+
}
|
|
58
91
|
} catch (error) {
|
|
59
92
|
if (error instanceof Error) {
|
|
60
93
|
console.error(`Error showing tokens: ${error.message}`)
|
|
@@ -183,4 +216,18 @@ program.command('get-environment')
|
|
|
183
216
|
}
|
|
184
217
|
})
|
|
185
218
|
|
|
219
|
+
program.command('check-deployment')
|
|
220
|
+
.description('[experimental] check that a game server was successfully deployed, or print out useful error messages in case of failure')
|
|
221
|
+
.argument('[namespace]', 'kubernetes namespace of the deployment')
|
|
222
|
+
.hook('preAction', async () => {
|
|
223
|
+
await extendCurrentSession()
|
|
224
|
+
})
|
|
225
|
+
.action(async (namespace: string, options) => {
|
|
226
|
+
setLogLevel(0)
|
|
227
|
+
|
|
228
|
+
// Run the checks and exit with success/failure exitCode depending on result
|
|
229
|
+
const exitCode = await checkGameServerDeployment(namespace)
|
|
230
|
+
exit(exitCode)
|
|
231
|
+
})
|
|
232
|
+
|
|
186
233
|
void program.parseAsync()
|