snapctl 0.35.0__tar.gz → 0.38.1__tar.gz
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.
Potentially problematic release.
This version of snapctl might be problematic. Click here for more details.
- {snapctl-0.35.0 → snapctl-0.38.1}/PKG-INFO +9 -1
- {snapctl-0.35.0 → snapctl-0.38.1}/README.md +8 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/pyproject.toml +1 -1
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/commands/byogs.py +21 -10
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/commands/byosnap.py +82 -30
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/commands/game.py +7 -9
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/commands/generate.py +11 -11
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/commands/snapend.py +65 -50
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/config/constants.py +1 -1
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/config/hashes.py +21 -9
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/main.py +16 -9
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/utils/echo.py +7 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/utils/helper.py +61 -4
- {snapctl-0.35.0 → snapctl-0.38.1}/LICENSE +0 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/__init__.py +0 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/__main__.py +0 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/commands/__init__.py +0 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/config/__init__.py +0 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/config/endpoints.py +0 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/types/__init__.py +0 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/types/definitions.py +0 -0
- {snapctl-0.35.0 → snapctl-0.38.1}/snapctl/utils/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: snapctl
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.38.1
|
|
4
4
|
Summary: Snapser CLI Tool
|
|
5
5
|
Author: Ajinkya Apte
|
|
6
6
|
Author-email: aj@snapser.com
|
|
@@ -264,6 +264,10 @@ snapctl byosnap upload-docs $byosnap_sid --tag $image_tag --path $code_root_path
|
|
|
264
264
|
Publish a custom snap code image. This command executes, `build`, `push` and `upload-docs` one
|
|
265
265
|
after the other.
|
|
266
266
|
|
|
267
|
+
**IMPORTANT**: Take note of the hardware architecture of machine and your Dockerfile commands.
|
|
268
|
+
Commands in docker file may be hardware architecture specific. Snapser throws a warning if it detects
|
|
269
|
+
a mismatch.
|
|
270
|
+
|
|
267
271
|
```
|
|
268
272
|
# Help for the byosnap command
|
|
269
273
|
snapctl byosnap publish-image --help
|
|
@@ -348,6 +352,10 @@ snapctl byogs push --tag $image_tag
|
|
|
348
352
|
Publish your custom game server image. This commend replaces the old way of creating, publishing image and
|
|
349
353
|
then publishing the byogs. Now all you have to do is publish your image and create a fleet using the web portal.
|
|
350
354
|
|
|
355
|
+
**IMPORTANT**: Take note of the hardware architecture of machine and your Dockerfile commands.
|
|
356
|
+
Commands in docker file may be hardware architecture specific. Snapser throws a warning if it detects
|
|
357
|
+
a mismatch.
|
|
358
|
+
|
|
351
359
|
```
|
|
352
360
|
# Help for the byogs command
|
|
353
361
|
snapctl byogs publish --help
|
|
@@ -245,6 +245,10 @@ snapctl byosnap upload-docs $byosnap_sid --tag $image_tag --path $code_root_path
|
|
|
245
245
|
Publish a custom snap code image. This command executes, `build`, `push` and `upload-docs` one
|
|
246
246
|
after the other.
|
|
247
247
|
|
|
248
|
+
**IMPORTANT**: Take note of the hardware architecture of machine and your Dockerfile commands.
|
|
249
|
+
Commands in docker file may be hardware architecture specific. Snapser throws a warning if it detects
|
|
250
|
+
a mismatch.
|
|
251
|
+
|
|
248
252
|
```
|
|
249
253
|
# Help for the byosnap command
|
|
250
254
|
snapctl byosnap publish-image --help
|
|
@@ -329,6 +333,10 @@ snapctl byogs push --tag $image_tag
|
|
|
329
333
|
Publish your custom game server image. This commend replaces the old way of creating, publishing image and
|
|
330
334
|
then publishing the byogs. Now all you have to do is publish your image and create a fleet using the web portal.
|
|
331
335
|
|
|
336
|
+
**IMPORTANT**: Take note of the hardware architecture of machine and your Dockerfile commands.
|
|
337
|
+
Commands in docker file may be hardware architecture specific. Snapser throws a warning if it detects
|
|
338
|
+
a mismatch.
|
|
339
|
+
|
|
332
340
|
```
|
|
333
341
|
# Help for the byogs command
|
|
334
342
|
snapctl byogs publish --help
|
|
@@ -5,16 +5,18 @@ import base64
|
|
|
5
5
|
from binascii import Error as BinasciiError
|
|
6
6
|
import os
|
|
7
7
|
import subprocess
|
|
8
|
+
import platform as sys_platform
|
|
8
9
|
from sys import platform
|
|
9
|
-
from typing import Union
|
|
10
|
+
from typing import Union, List
|
|
10
11
|
|
|
11
12
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
12
13
|
from snapctl.config.constants import SNAPCTL_BYOGS_DEPENDENCY_MISSING, \
|
|
13
14
|
SNAPCTL_BYOGS_ECR_LOGIN_ERROR, SNAPCTL_BYOGS_BUILD_ERROR, \
|
|
14
15
|
SNAPCTL_BYOGS_TAG_ERROR, SNAPCTL_BYOGS_PUBLISH_ERROR, \
|
|
15
16
|
SNAPCTL_BYOGS_PUBLISH_DUPLICATE_TAG_ERROR, SNAPCTL_INPUT_ERROR
|
|
16
|
-
from snapctl.utils.helper import get_composite_token, snapctl_error, snapctl_success
|
|
17
|
-
|
|
17
|
+
from snapctl.utils.helper import get_composite_token, snapctl_error, snapctl_success, \
|
|
18
|
+
check_dockerfile_architecture
|
|
19
|
+
from snapctl.utils.echo import info, warning
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
class ByoGs:
|
|
@@ -30,7 +32,7 @@ class ByoGs:
|
|
|
30
32
|
TAG_CHARACTER_LIMIT = 80
|
|
31
33
|
|
|
32
34
|
def __init__(
|
|
33
|
-
self, subcommand: str, base_url: str, api_key: str
|
|
35
|
+
self, subcommand: str, base_url: str, api_key: Union[str, None],
|
|
34
36
|
input_tag: Union[str, None], path: Union[str, None], dockerfile: str,
|
|
35
37
|
skip_build: bool = False
|
|
36
38
|
) -> None:
|
|
@@ -44,7 +46,7 @@ class ByoGs:
|
|
|
44
46
|
self.token: Union[str, None] = get_composite_token(
|
|
45
47
|
base_url, api_key, 'byogs', {'service_id': ByoGs.SID}
|
|
46
48
|
)
|
|
47
|
-
self.token_parts: Union[
|
|
49
|
+
self.token_parts: Union[List, None] = ByoGs._get_token_values(
|
|
48
50
|
self.token) if self.token is not None else None
|
|
49
51
|
self.input_tag: Union[str, None] = input_tag
|
|
50
52
|
self.path: Union[str, None] = path
|
|
@@ -56,7 +58,7 @@ class ByoGs:
|
|
|
56
58
|
# Protected methods
|
|
57
59
|
|
|
58
60
|
@staticmethod
|
|
59
|
-
def _get_token_values(token: str) -> None
|
|
61
|
+
def _get_token_values(token: str) -> Union[None, List]:
|
|
60
62
|
"""
|
|
61
63
|
Get the token values
|
|
62
64
|
"""
|
|
@@ -122,7 +124,7 @@ class ByoGs:
|
|
|
122
124
|
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
123
125
|
else:
|
|
124
126
|
response = subprocess.run([
|
|
125
|
-
f'echo "{ecr_repo_token}" | docker login '
|
|
127
|
+
f'echo "{ecr_repo_token}" | docker login ' +
|
|
126
128
|
f'--username {ecr_repo_username} --password-stdin {ecr_repo_url}'
|
|
127
129
|
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
128
130
|
if not response.returncode:
|
|
@@ -150,6 +152,13 @@ class ByoGs:
|
|
|
150
152
|
build_platform = self.token_parts[3]
|
|
151
153
|
# Build your snap
|
|
152
154
|
docker_file_path = os.path.join(self.path, self.dockerfile)
|
|
155
|
+
# Warning check for architecture specific commands
|
|
156
|
+
info(f'Building on system architecture {sys_platform.machine()}')
|
|
157
|
+
check_response = check_dockerfile_architecture(
|
|
158
|
+
docker_file_path, sys_platform.machine())
|
|
159
|
+
if check_response['error']:
|
|
160
|
+
warning(check_response['message'])
|
|
161
|
+
# Build the image
|
|
153
162
|
if platform == "win32":
|
|
154
163
|
response = subprocess.run([
|
|
155
164
|
# f"docker build --no-cache -t {tag} {path}"
|
|
@@ -160,7 +169,8 @@ class ByoGs:
|
|
|
160
169
|
else:
|
|
161
170
|
response = subprocess.run([
|
|
162
171
|
# f"docker build --no-cache -t {tag} {path}"
|
|
163
|
-
|
|
172
|
+
"docker build --platform " +
|
|
173
|
+
f"{build_platform} -t {self.input_tag} " +
|
|
164
174
|
f"-f {docker_file_path} {self.path}"
|
|
165
175
|
], shell=True, check=False)
|
|
166
176
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
@@ -247,7 +257,8 @@ class ByoGs:
|
|
|
247
257
|
# Check subcommand
|
|
248
258
|
if not self.subcommand in ByoGs.SUBCOMMANDS:
|
|
249
259
|
snapctl_error(
|
|
250
|
-
|
|
260
|
+
"Invalid command. Valid commands are " +
|
|
261
|
+
f"{', '.join(ByoGs.SUBCOMMANDS)}.",
|
|
251
262
|
SNAPCTL_INPUT_ERROR)
|
|
252
263
|
# Validation for subcommands
|
|
253
264
|
if self.token_parts is None:
|
|
@@ -259,7 +270,7 @@ class ByoGs:
|
|
|
259
270
|
SNAPCTL_INPUT_ERROR)
|
|
260
271
|
if len(self.input_tag.split()) > 1 or len(self.input_tag) > ByoGs.TAG_CHARACTER_LIMIT:
|
|
261
272
|
snapctl_error(
|
|
262
|
-
"Tag should be a single word with maximum of "
|
|
273
|
+
"Tag should be a single word with maximum of " +
|
|
263
274
|
f"{ByoGs.TAG_CHARACTER_LIMIT} characters",
|
|
264
275
|
SNAPCTL_INPUT_ERROR
|
|
265
276
|
)
|
|
@@ -7,8 +7,9 @@ import json
|
|
|
7
7
|
import os
|
|
8
8
|
import re
|
|
9
9
|
import subprocess
|
|
10
|
+
import platform as sys_platform
|
|
10
11
|
from sys import platform
|
|
11
|
-
from typing import Union
|
|
12
|
+
from typing import Union, List
|
|
12
13
|
import requests
|
|
13
14
|
from requests.exceptions import RequestException
|
|
14
15
|
|
|
@@ -22,8 +23,9 @@ from snapctl.config.constants import HTTP_ERROR_SERVICE_VERSION_EXISTS, \
|
|
|
22
23
|
SNAPCTL_BYOSNAP_CREATE_DUPLICATE_NAME_ERROR, SNAPCTL_BYOSNAP_CREATE_PERMISSION_ERROR, \
|
|
23
24
|
SNAPCTL_BYOSNAP_CREATE_ERROR, SNAPCTL_BYOSNAP_PUBLISH_VERSION_DUPLICATE_TAG_ERROR, \
|
|
24
25
|
SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR
|
|
25
|
-
from snapctl.utils.echo import success, info
|
|
26
|
-
from snapctl.utils.helper import get_composite_token, snapctl_error, snapctl_success
|
|
26
|
+
from snapctl.utils.echo import success, info, warning
|
|
27
|
+
from snapctl.utils.helper import get_composite_token, snapctl_error, snapctl_success, \
|
|
28
|
+
check_dockerfile_architecture
|
|
27
29
|
|
|
28
30
|
|
|
29
31
|
class ByoSnap:
|
|
@@ -41,13 +43,16 @@ class ByoSnap:
|
|
|
41
43
|
TAG_CHARACTER_LIMIT = 80
|
|
42
44
|
VALID_CPU_MARKS = [100, 250, 500, 750, 1000, 1500, 2000, 3000]
|
|
43
45
|
VALID_MEMORY_MARKS = [0.125, 0.25, 0.5, 1, 2, 3, 4]
|
|
46
|
+
MAX_READINESS_TIMEOUT = 30
|
|
47
|
+
MAX_MIN_REPLICAS = 4
|
|
44
48
|
|
|
45
49
|
def __init__(
|
|
46
|
-
self, subcommand: str, base_url: str, api_key: str
|
|
50
|
+
self, subcommand: str, base_url: str, api_key: Union[str, None], sid: str, name: str,
|
|
47
51
|
desc: str, platform_type: str, language: str, input_tag: Union[str, None],
|
|
48
52
|
path: Union[str, None], dockerfile: str, prefix: str, version: Union[str, None],
|
|
49
53
|
http_port: Union[int, None], byosnap_profile: Union[str, None],
|
|
50
|
-
skip_build: bool = False
|
|
54
|
+
skip_build: bool = False, readiness_path: Union[str, None] = None,
|
|
55
|
+
readiness_delay: Union[int, None] = None
|
|
51
56
|
) -> None:
|
|
52
57
|
self.subcommand: str = subcommand
|
|
53
58
|
self.base_url: str = base_url
|
|
@@ -74,12 +79,14 @@ class ByoSnap:
|
|
|
74
79
|
self.http_port: Union[int, None] = http_port
|
|
75
80
|
self.byosnap_profile: Union[str, None] = byosnap_profile
|
|
76
81
|
self.skip_build: bool = skip_build
|
|
82
|
+
self.readiness_path: Union[str, None] = readiness_path
|
|
83
|
+
self.readiness_delay: Union[int, None] = readiness_delay
|
|
77
84
|
# Validate the input
|
|
78
85
|
self.validate_input()
|
|
79
86
|
|
|
80
87
|
# Protected methods
|
|
81
88
|
@staticmethod
|
|
82
|
-
def _get_token_values(token: str) -> None
|
|
89
|
+
def _get_token_values(token: str) -> Union[None, List]:
|
|
83
90
|
"""
|
|
84
91
|
Method to break open the token
|
|
85
92
|
"""
|
|
@@ -150,7 +157,7 @@ class ByoSnap:
|
|
|
150
157
|
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
151
158
|
else:
|
|
152
159
|
response = subprocess.run([
|
|
153
|
-
f'echo "{ecr_repo_token}" | docker login '
|
|
160
|
+
f'echo "{ecr_repo_token}" | docker login ' +
|
|
154
161
|
f'--username {ecr_repo_username} --password-stdin {ecr_repo_url}'
|
|
155
162
|
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
156
163
|
if not response.returncode:
|
|
@@ -179,6 +186,13 @@ class ByoSnap:
|
|
|
179
186
|
try:
|
|
180
187
|
# Build your snap
|
|
181
188
|
docker_file_path = os.path.join(self.path, self.dockerfile)
|
|
189
|
+
# Warning check for architecture specific commands
|
|
190
|
+
info(f'Building on system architecture {sys_platform.machine()}')
|
|
191
|
+
check_response = check_dockerfile_architecture(
|
|
192
|
+
docker_file_path, sys_platform.machine())
|
|
193
|
+
if check_response['error']:
|
|
194
|
+
warning(check_response['message'])
|
|
195
|
+
# Build the image
|
|
182
196
|
if platform == "win32":
|
|
183
197
|
response = subprocess.run([
|
|
184
198
|
# f"docker build --no-cache -t {tag} {path}"
|
|
@@ -189,7 +203,8 @@ class ByoSnap:
|
|
|
189
203
|
else:
|
|
190
204
|
response = subprocess.run([
|
|
191
205
|
# f"docker build --no-cache -t {tag} {path}"
|
|
192
|
-
|
|
206
|
+
"docker build --platform " +
|
|
207
|
+
f"{build_platform} -t {self.input_tag} " +
|
|
193
208
|
f"-f {docker_file_path} {self.path}"
|
|
194
209
|
], shell=True, check=False)
|
|
195
210
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
@@ -277,8 +292,8 @@ class ByoSnap:
|
|
|
277
292
|
snapctl_error("Missing BYOSnap profile path", SNAPCTL_INPUT_ERROR)
|
|
278
293
|
if not os.path.isfile(self.byosnap_profile):
|
|
279
294
|
snapctl_error(
|
|
280
|
-
"Unable to find BYOSnap profile "
|
|
281
|
-
|
|
295
|
+
"Unable to find BYOSnap profile " +
|
|
296
|
+
f"JSON at path {self.byosnap_profile}",
|
|
282
297
|
SNAPCTL_INPUT_ERROR
|
|
283
298
|
)
|
|
284
299
|
profile_data = None
|
|
@@ -300,6 +315,7 @@ class ByoSnap:
|
|
|
300
315
|
SNAPCTL_INPUT_ERROR
|
|
301
316
|
)
|
|
302
317
|
for profile in ['dev_template', 'stage_template', 'prod_template']:
|
|
318
|
+
# Currently, not checking for 'min_replicas' not in profile_data[profile]
|
|
303
319
|
if 'cpu' not in profile_data[profile] or \
|
|
304
320
|
'memory' not in profile_data[profile] or \
|
|
305
321
|
'cmd' not in profile_data[profile] or \
|
|
@@ -311,14 +327,24 @@ class ByoSnap:
|
|
|
311
327
|
)
|
|
312
328
|
if profile_data[profile]['cpu'] not in ByoSnap.VALID_CPU_MARKS:
|
|
313
329
|
snapctl_error(
|
|
314
|
-
'Invalid CPU value in BYOSnap profile. '
|
|
315
|
-
f'
|
|
330
|
+
'Invalid CPU value in BYOSnap profile. Valid values are' +
|
|
331
|
+
f'{", ".join(map(str, ByoSnap.VALID_CPU_MARKS))}',
|
|
316
332
|
SNAPCTL_INPUT_ERROR
|
|
317
333
|
)
|
|
318
334
|
if profile_data[profile]['memory'] not in ByoSnap.VALID_MEMORY_MARKS:
|
|
319
335
|
snapctl_error(
|
|
320
|
-
'Invalid Memory value in BYOSnap profile. '
|
|
321
|
-
f'
|
|
336
|
+
'Invalid Memory value in BYOSnap profile. Valid values are ' +
|
|
337
|
+
f'{", ".join(map(str, ByoSnap.VALID_MEMORY_MARKS))}',
|
|
338
|
+
SNAPCTL_INPUT_ERROR
|
|
339
|
+
)
|
|
340
|
+
if 'min_replicas' in profile_data[profile] and \
|
|
341
|
+
(not isinstance(profile_data[profile]['min_replicas'], int) or
|
|
342
|
+
int(profile_data[profile]['min_replicas']) < 0 or
|
|
343
|
+
int(profile_data[profile]['min_replicas']) > ByoSnap.MAX_MIN_REPLICAS):
|
|
344
|
+
snapctl_error(
|
|
345
|
+
'Invalid Min Replicas value in BYOSnap profile. ' +
|
|
346
|
+
'Minimum replicas should be between 0 and ' +
|
|
347
|
+
f'{ByoSnap.MAX_MIN_REPLICAS}',
|
|
322
348
|
SNAPCTL_INPUT_ERROR
|
|
323
349
|
)
|
|
324
350
|
|
|
@@ -335,20 +361,21 @@ class ByoSnap:
|
|
|
335
361
|
# Check subcommand
|
|
336
362
|
if not self.subcommand in ByoSnap.SUBCOMMANDS:
|
|
337
363
|
snapctl_error(
|
|
338
|
-
"Invalid command. Valid commands "
|
|
339
|
-
f"
|
|
364
|
+
"Invalid command. Valid commands are " +
|
|
365
|
+
f"{', '.join(ByoSnap.SUBCOMMANDS)}.",
|
|
340
366
|
SNAPCTL_INPUT_ERROR
|
|
341
367
|
)
|
|
342
368
|
# Validate the SID
|
|
343
369
|
if not self.sid.startswith(ByoSnap.ID_PREFIX):
|
|
344
370
|
snapctl_error(
|
|
345
|
-
|
|
371
|
+
"Invalid Snap ID. Valid Snap IDs start with " +
|
|
372
|
+
f"{ByoSnap.ID_PREFIX}.",
|
|
346
373
|
SNAPCTL_INPUT_ERROR
|
|
347
374
|
)
|
|
348
375
|
if len(self.sid) > ByoSnap.SID_CHARACTER_LIMIT:
|
|
349
376
|
snapctl_error(
|
|
350
|
-
"Invalid Snap ID. "
|
|
351
|
-
f"
|
|
377
|
+
"Invalid Snap ID. Snap ID should be less than " +
|
|
378
|
+
f"{ByoSnap.SID_CHARACTER_LIMIT} characters",
|
|
352
379
|
SNAPCTL_INPUT_ERROR
|
|
353
380
|
)
|
|
354
381
|
# Validation for subcommands
|
|
@@ -359,13 +386,13 @@ class ByoSnap:
|
|
|
359
386
|
snapctl_error("Missing language", SNAPCTL_INPUT_ERROR)
|
|
360
387
|
if self.language not in ByoSnap.LANGUAGES:
|
|
361
388
|
snapctl_error(
|
|
362
|
-
"Invalid language. Valid languages are "
|
|
389
|
+
"Invalid language. Valid languages are " +
|
|
363
390
|
f"{', '.join(ByoSnap.LANGUAGES)}.",
|
|
364
391
|
SNAPCTL_INPUT_ERROR
|
|
365
392
|
)
|
|
366
393
|
if self.platform_type not in ByoSnap.PLATFORMS:
|
|
367
394
|
snapctl_error(
|
|
368
|
-
"Invalid platform. Valid platforms are "
|
|
395
|
+
"Invalid platform. Valid platforms are " +
|
|
369
396
|
f"{', '.join(ByoSnap.PLATFORMS)}.",
|
|
370
397
|
SNAPCTL_INPUT_ERROR
|
|
371
398
|
)
|
|
@@ -381,7 +408,7 @@ class ByoSnap:
|
|
|
381
408
|
if len(self.input_tag.split()) > 1 or \
|
|
382
409
|
len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
|
|
383
410
|
snapctl_error(
|
|
384
|
-
"Tag should be a single word with maximum of "
|
|
411
|
+
"Tag should be a single word with maximum of " +
|
|
385
412
|
f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
|
|
386
413
|
SNAPCTL_INPUT_ERROR
|
|
387
414
|
)
|
|
@@ -391,7 +418,8 @@ class ByoSnap:
|
|
|
391
418
|
# Check path
|
|
392
419
|
if not self.skip_build and not os.path.isfile(f"{self.path}/{self.dockerfile}"):
|
|
393
420
|
snapctl_error(
|
|
394
|
-
f"Unable to find
|
|
421
|
+
f"Unable to find " +
|
|
422
|
+
f"{self.dockerfile} at path {self.path}",
|
|
395
423
|
SNAPCTL_INPUT_ERROR)
|
|
396
424
|
# elif self.subcommand == 'push':
|
|
397
425
|
# if not self.input_tag:
|
|
@@ -405,9 +433,10 @@ class ByoSnap:
|
|
|
405
433
|
if not self.input_tag:
|
|
406
434
|
snapctl_error(
|
|
407
435
|
"Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
|
|
408
|
-
if len(self.input_tag.split()) > 1 or
|
|
436
|
+
if len(self.input_tag.split()) > 1 or \
|
|
437
|
+
len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
|
|
409
438
|
snapctl_error(
|
|
410
|
-
"Tag should be a single word with maximum of "
|
|
439
|
+
"Tag should be a single word with maximum of " +
|
|
411
440
|
f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
|
|
412
441
|
SNAPCTL_INPUT_ERROR
|
|
413
442
|
)
|
|
@@ -431,6 +460,19 @@ class ByoSnap:
|
|
|
431
460
|
if not self.http_port.isdigit():
|
|
432
461
|
snapctl_error("Ingress HTTP Port should be a number",
|
|
433
462
|
SNAPCTL_INPUT_ERROR)
|
|
463
|
+
if self.readiness_path is not None:
|
|
464
|
+
if self.readiness_path.strip() == '':
|
|
465
|
+
snapctl_error("Readiness path cannot be empty",
|
|
466
|
+
SNAPCTL_INPUT_ERROR)
|
|
467
|
+
if not self.readiness_path.strip().startswith('/'):
|
|
468
|
+
snapctl_error("Readiness path has to start with /",
|
|
469
|
+
SNAPCTL_INPUT_ERROR)
|
|
470
|
+
if self.readiness_delay is not None:
|
|
471
|
+
if self.readiness_delay < 0 or \
|
|
472
|
+
self.readiness_delay > ByoSnap.MAX_READINESS_TIMEOUT:
|
|
473
|
+
snapctl_error(
|
|
474
|
+
"Readiness delay should be between 0 " +
|
|
475
|
+
f"and {ByoSnap.MAX_READINESS_TIMEOUT}", SNAPCTL_INPUT_ERROR)
|
|
434
476
|
# Check byosnap_profile path
|
|
435
477
|
self._validate_byosnap_profile()
|
|
436
478
|
|
|
@@ -495,11 +537,14 @@ class ByoSnap:
|
|
|
495
537
|
info('Unable to upload your swagger.json')
|
|
496
538
|
except RequestException as e:
|
|
497
539
|
info(
|
|
498
|
-
|
|
540
|
+
'Exception: Unable to find swagger.json at ' +
|
|
541
|
+
f'{self.path} {e}'
|
|
499
542
|
)
|
|
500
543
|
else:
|
|
501
|
-
info(
|
|
502
|
-
|
|
544
|
+
info(
|
|
545
|
+
f'No swagger.json found at {self.path}' +
|
|
546
|
+
'. Skipping swagger.json upload'
|
|
547
|
+
)
|
|
503
548
|
|
|
504
549
|
# Push the README.md
|
|
505
550
|
if os.path.isfile(readme_file):
|
|
@@ -521,7 +566,9 @@ class ByoSnap:
|
|
|
521
566
|
info('Unable to upload your README.md')
|
|
522
567
|
except RequestException as e:
|
|
523
568
|
info(
|
|
524
|
-
|
|
569
|
+
'Exception: Unable to find README.md at ' +
|
|
570
|
+
f'{self.path} {str(e)}'
|
|
571
|
+
)
|
|
525
572
|
else:
|
|
526
573
|
info(
|
|
527
574
|
f'No README.md found at {self.path}. Skipping README.md upload')
|
|
@@ -561,7 +608,8 @@ class ByoSnap:
|
|
|
561
608
|
if "api_error_code" in response_json and "message" in response_json:
|
|
562
609
|
if response_json['api_error_code'] == HTTP_ERROR_SERVICE_VERSION_EXISTS:
|
|
563
610
|
snapctl_error(
|
|
564
|
-
f'BYOSnap {self.name} already exists.
|
|
611
|
+
f'BYOSnap {self.name} already exists. ' +
|
|
612
|
+
'Please use a different name',
|
|
565
613
|
SNAPCTL_BYOSNAP_CREATE_DUPLICATE_NAME_ERROR,
|
|
566
614
|
progress
|
|
567
615
|
)
|
|
@@ -628,6 +676,10 @@ class ByoSnap:
|
|
|
628
676
|
"image_tag": self.input_tag,
|
|
629
677
|
"base_url": f"{self.prefix}/{self.sid}",
|
|
630
678
|
"http_port": self.http_port,
|
|
679
|
+
"readiness_probe_config": {
|
|
680
|
+
"path": self.readiness_path,
|
|
681
|
+
"initial_delay_seconds": self.readiness_delay
|
|
682
|
+
},
|
|
631
683
|
"dev_template": profile_data['dev_template'],
|
|
632
684
|
"stage_template": profile_data['stage_template'],
|
|
633
685
|
"prod_template": profile_data['prod_template']
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Snapend CLI commands
|
|
3
3
|
"""
|
|
4
|
+
from typing import Union, Dict
|
|
4
5
|
import requests
|
|
5
6
|
from requests.exceptions import RequestException
|
|
6
|
-
|
|
7
|
-
import typer
|
|
8
7
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
9
8
|
from snapctl.config.constants import SERVER_CALL_TIMEOUT, SNAPCTL_INPUT_ERROR, \
|
|
10
|
-
|
|
9
|
+
SNAPCTL_GAME_CREATE_ERROR, SNAPCTL_GAME_ENUMERATE_ERROR, \
|
|
11
10
|
HTTP_ERROR_DUPLICATE_GAME_NAME, SNAPCTL_GAME_CREATE_DUPLICATE_NAME_ERROR
|
|
12
|
-
from snapctl.utils.echo import error, success
|
|
13
11
|
from snapctl.utils.helper import snapctl_error, snapctl_success
|
|
14
12
|
|
|
15
13
|
|
|
@@ -20,12 +18,12 @@ class Game:
|
|
|
20
18
|
SUBCOMMANDS = ['create', 'enumerate']
|
|
21
19
|
|
|
22
20
|
def __init__(
|
|
23
|
-
self, subcommand: str, base_url: str, api_key: str
|
|
21
|
+
self, subcommand: str, base_url: str, api_key: Union[str, None], name: Union[str, None]
|
|
24
22
|
) -> None:
|
|
25
23
|
self.subcommand: str = subcommand
|
|
26
24
|
self.base_url: str = base_url
|
|
27
25
|
self.api_key: str = api_key
|
|
28
|
-
self.name: str
|
|
26
|
+
self.name: Union[str, None] = name
|
|
29
27
|
# Validate input
|
|
30
28
|
self.validate_input()
|
|
31
29
|
|
|
@@ -38,9 +36,9 @@ class Game:
|
|
|
38
36
|
snapctl_error("Missing API Key.", SNAPCTL_INPUT_ERROR)
|
|
39
37
|
# Check subcommand
|
|
40
38
|
if not self.subcommand in Game.SUBCOMMANDS:
|
|
41
|
-
snapctl_error(
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
snapctl_error("Invalid command. Valid commands are" +
|
|
40
|
+
f"{', '.join(Game.SUBCOMMANDS)}.",
|
|
41
|
+
SNAPCTL_INPUT_ERROR)
|
|
44
42
|
# Check sdk-download commands
|
|
45
43
|
if self.subcommand == 'create':
|
|
46
44
|
if self.name is None or self.name == '':
|
|
@@ -5,7 +5,7 @@ import base64
|
|
|
5
5
|
from binascii import Error as BinasciiError
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
-
from typing import Union
|
|
8
|
+
from typing import Union, List
|
|
9
9
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
10
|
from snapctl.config.constants import SNAPCTL_INPUT_ERROR, \
|
|
11
11
|
SNAPCTL_GENERATE_GENERIC_ERROR, SNAPCTL_GENERATE_PROFILE_ERROR, \
|
|
@@ -28,21 +28,21 @@ class Generate:
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
def __init__(
|
|
31
|
-
self, subcommand: str, base_url: str, api_key: str
|
|
32
|
-
category: str
|
|
31
|
+
self, subcommand: str, base_url: str, api_key: Union[str, None],
|
|
32
|
+
category: Union[str, None],
|
|
33
33
|
out_path: Union[str, None]
|
|
34
34
|
) -> None:
|
|
35
35
|
self.subcommand: str = subcommand
|
|
36
36
|
self.base_url: str = base_url
|
|
37
37
|
self.api_key: str = api_key
|
|
38
|
-
self.category: str
|
|
38
|
+
self.category: Union[str, None] = category
|
|
39
39
|
self.out_path: Union[str, None] = out_path
|
|
40
40
|
# Validate input
|
|
41
41
|
self.validate_input()
|
|
42
42
|
|
|
43
43
|
# Private methods
|
|
44
44
|
@staticmethod
|
|
45
|
-
def _get_token_values(token: str) -> None
|
|
45
|
+
def _get_token_values(token: str) -> Union[None, List]:
|
|
46
46
|
"""
|
|
47
47
|
Get the token values
|
|
48
48
|
"""
|
|
@@ -63,7 +63,7 @@ class Generate:
|
|
|
63
63
|
"""
|
|
64
64
|
if self.subcommand not in Generate.SUBCOMMANDS:
|
|
65
65
|
snapctl_error(
|
|
66
|
-
f"Invalid command {self.subcommand}. Valid command are "
|
|
66
|
+
f"Invalid command {self.subcommand}. Valid command are " +
|
|
67
67
|
f"{Generate.SUBCOMMANDS}",
|
|
68
68
|
SNAPCTL_INPUT_ERROR
|
|
69
69
|
)
|
|
@@ -71,7 +71,7 @@ class Generate:
|
|
|
71
71
|
if self.subcommand == 'profile':
|
|
72
72
|
if self.category not in Generate.CATEGORIES['profile']:
|
|
73
73
|
snapctl_error(
|
|
74
|
-
f"Invalid category {self.category}. Valid category are "
|
|
74
|
+
f"Invalid category {self.category}. Valid category are " +
|
|
75
75
|
f"{Generate.CATEGORIES['profile']}",
|
|
76
76
|
SNAPCTL_INPUT_ERROR
|
|
77
77
|
)
|
|
@@ -83,7 +83,7 @@ class Generate:
|
|
|
83
83
|
elif self.subcommand == 'credentials':
|
|
84
84
|
if self.category not in Generate.CATEGORIES['credentials']:
|
|
85
85
|
snapctl_error(
|
|
86
|
-
f"Invalid category {self.category}. Valid category are "
|
|
86
|
+
f"Invalid category {self.category}. Valid category are " +
|
|
87
87
|
f"{Generate.CATEGORIES['credentials']}",
|
|
88
88
|
SNAPCTL_INPUT_ERROR
|
|
89
89
|
)
|
|
@@ -95,7 +95,7 @@ class Generate:
|
|
|
95
95
|
# Now confirm that out-path is valid
|
|
96
96
|
if self.out_path and not os.path.isdir(self.out_path):
|
|
97
97
|
snapctl_error(
|
|
98
|
-
f"Invalid path {self.out_path}. Wont be able to "
|
|
98
|
+
f"Invalid path {self.out_path}. Wont be able to " +
|
|
99
99
|
"store the output file",
|
|
100
100
|
SNAPCTL_INPUT_ERROR
|
|
101
101
|
)
|
|
@@ -125,7 +125,7 @@ class Generate:
|
|
|
125
125
|
file_written = True
|
|
126
126
|
if file_written:
|
|
127
127
|
snapctl_success(
|
|
128
|
-
"BYOSNAP Profile generation successful. "
|
|
128
|
+
"BYOSNAP Profile generation successful. " +
|
|
129
129
|
f"{Generate.BYOSNAP_PROFILE_FN} saved at {file_save_path}",
|
|
130
130
|
progress
|
|
131
131
|
)
|
|
@@ -191,7 +191,7 @@ class Generate:
|
|
|
191
191
|
file_written = True
|
|
192
192
|
if file_written:
|
|
193
193
|
snapctl_success(
|
|
194
|
-
"ECR Token generation successful. "
|
|
194
|
+
"ECR Token generation successful. " +
|
|
195
195
|
f"{Generate.ECR_TOKEN_FN} saved at {file_save_path}",
|
|
196
196
|
progress
|
|
197
197
|
)
|
|
@@ -7,6 +7,7 @@ import os
|
|
|
7
7
|
import json
|
|
8
8
|
import time
|
|
9
9
|
import requests
|
|
10
|
+
from typing import Dict
|
|
10
11
|
from requests.exceptions import RequestException
|
|
11
12
|
|
|
12
13
|
import typer
|
|
@@ -44,44 +45,44 @@ class Snapend:
|
|
|
44
45
|
MAX_BLOCKING_RETRIES = 120
|
|
45
46
|
|
|
46
47
|
def __init__(
|
|
47
|
-
self, subcommand: str, base_url: str, api_key: str
|
|
48
|
+
self, subcommand: str, base_url: str, api_key: Union[str, None], snapend_id: Union[str, None],
|
|
48
49
|
# Enumerate, Clone
|
|
49
|
-
game_id: str
|
|
50
|
+
game_id: Union[str, None],
|
|
50
51
|
# Clone
|
|
51
|
-
name: str
|
|
52
|
-
env: str
|
|
52
|
+
name: Union[str, None],
|
|
53
|
+
env: Union[str, None],
|
|
53
54
|
# Clone, Apply, Promote
|
|
54
|
-
manifest_path: str
|
|
55
|
+
manifest_path: Union[str, None],
|
|
55
56
|
# Download
|
|
56
|
-
category: str, platform_type: str, protos_category: str, auth_type: str, snaps: str
|
|
57
|
+
category: str, platform_type: str, protos_category: str, auth_type: str, snaps: Union[str, None],
|
|
57
58
|
# Clone, Apply, Promote, Download
|
|
58
|
-
out_path: str
|
|
59
|
+
out_path: Union[str, None],
|
|
59
60
|
# Update
|
|
60
|
-
byosnaps: str
|
|
61
|
+
byosnaps: Union[str, None], byogs: Union[str, None], blocking: bool = False
|
|
61
62
|
) -> None:
|
|
62
63
|
self.subcommand: str = subcommand
|
|
63
64
|
self.base_url: str = base_url
|
|
64
65
|
self.api_key: str = api_key
|
|
65
66
|
self.snapend_id: str = snapend_id
|
|
66
|
-
self.game_id: str
|
|
67
|
+
self.game_id: Union[str, None] = game_id
|
|
67
68
|
self.name: str = name
|
|
68
69
|
self.env: str = env
|
|
69
|
-
self.manifest_path: str
|
|
70
|
-
self.manifest_file_name: str
|
|
70
|
+
self.manifest_path: Union[str, None] = manifest_path
|
|
71
|
+
self.manifest_file_name: Union[str, None] = Snapend._get_manifest_file_name(
|
|
71
72
|
manifest_path
|
|
72
73
|
)
|
|
73
74
|
self.category: str = category
|
|
74
75
|
self.download_types: Union[
|
|
75
|
-
|
|
76
|
+
Dict[str, Dict[str, str]], None
|
|
76
77
|
] = Snapend._make_download_type(category)
|
|
77
78
|
self.protos_category: str = protos_category
|
|
78
79
|
self.auth_type: str = auth_type
|
|
79
80
|
self.platform_type: str = platform_type
|
|
80
|
-
self.out_path: str
|
|
81
|
-
self.snaps: str
|
|
81
|
+
self.out_path: Union[str, None] = out_path
|
|
82
|
+
self.snaps: Union[str, None] = snaps
|
|
82
83
|
self.byosnap_list: Union[list, None] = Snapend._make_byosnap_list(
|
|
83
84
|
byosnaps) if byosnaps else None
|
|
84
|
-
self.byogs_list: str
|
|
85
|
+
self.byogs_list: Union[str, None] = Snapend._make_byogs_list(
|
|
85
86
|
byogs) if byogs else None
|
|
86
87
|
self.blocking: bool = blocking
|
|
87
88
|
# Validate input
|
|
@@ -90,7 +91,7 @@ class Snapend:
|
|
|
90
91
|
# Helpers
|
|
91
92
|
|
|
92
93
|
@staticmethod
|
|
93
|
-
def _get_manifest_file_name(manifest_path: str) -> str
|
|
94
|
+
def _get_manifest_file_name(manifest_path: str) -> Union[str, None]:
|
|
94
95
|
if manifest_path and manifest_path != '' and os.path.isfile(manifest_path):
|
|
95
96
|
file_name = os.path.basename(manifest_path)
|
|
96
97
|
if file_name.endswith('.json') or file_name.endswith('.yml') or \
|
|
@@ -181,7 +182,7 @@ class Snapend:
|
|
|
181
182
|
self.category = download_category
|
|
182
183
|
self.platform_type = platform_type
|
|
183
184
|
self.download_types: Union[
|
|
184
|
-
|
|
185
|
+
Dict[str, Dict[str, str]], None
|
|
185
186
|
] = Snapend._make_download_type(download_category)
|
|
186
187
|
|
|
187
188
|
def _execute_download(self) -> bool:
|
|
@@ -191,13 +192,15 @@ class Snapend:
|
|
|
191
192
|
f"download?category={self.category}"
|
|
192
193
|
)
|
|
193
194
|
if self.category not in Snapend.DOWNLOAD_TYPE_NOT_REQUIRED:
|
|
194
|
-
url +=
|
|
195
|
+
url += "&type=" + \
|
|
196
|
+
f"{self.download_types[self.platform_type]['type']}"
|
|
195
197
|
# If Protos, add protos category
|
|
196
198
|
if self.category == 'protos':
|
|
197
199
|
url += f"&subtype={self.protos_category}"
|
|
198
200
|
# If client or server SDK, add sub type and auth type
|
|
199
201
|
if self.category in ['client-sdk', 'server-sdk']:
|
|
200
|
-
url +=
|
|
202
|
+
url += "&subtype=" + \
|
|
203
|
+
f"{self.download_types[self.platform_type]['subtype']}"
|
|
201
204
|
url_auth_type: str = 'user'
|
|
202
205
|
if self.category == 'server-sdk' and self.auth_type == 'app':
|
|
203
206
|
url_auth_type = 'app'
|
|
@@ -212,20 +215,14 @@ class Snapend:
|
|
|
212
215
|
if self.category == 'admin-settings':
|
|
213
216
|
fn = f"snapser-{self.snapend_id}-admin-settings.json"
|
|
214
217
|
elif self.category == 'snapend-manifest':
|
|
215
|
-
fn =
|
|
216
|
-
f"
|
|
217
|
-
f"manifest.{self.download_types[self.platform_type]['type']}"
|
|
218
|
-
)
|
|
218
|
+
fn = f"snapser-{self.snapend_id}-manifest." + \
|
|
219
|
+
f"{self.download_types[self.platform_type]['type']}"
|
|
219
220
|
elif self.category == 'protos':
|
|
220
|
-
fn =
|
|
221
|
-
f"snapser-{self.snapend_id}-{self.category}"
|
|
221
|
+
fn = f"snapser-{self.snapend_id}-{self.category}" + \
|
|
222
222
|
f"-{self.platform_type}-{self.protos_category}.zip"
|
|
223
|
-
)
|
|
224
223
|
else:
|
|
225
|
-
fn =
|
|
226
|
-
f"snapser-{self.snapend_id}-{self.category}"
|
|
224
|
+
fn = f"snapser-{self.snapend_id}-{self.category}" + \
|
|
227
225
|
f"-{self.platform_type}-{self.auth_type}.zip"
|
|
228
|
-
)
|
|
229
226
|
if self.out_path is not None:
|
|
230
227
|
file_save_path = os.path.join(self.out_path, fn)
|
|
231
228
|
else:
|
|
@@ -256,7 +253,8 @@ class Snapend:
|
|
|
256
253
|
# Check subcommand
|
|
257
254
|
if not self.subcommand in Snapend.SUBCOMMANDS:
|
|
258
255
|
snapctl_error(
|
|
259
|
-
|
|
256
|
+
"Invalid command. Valid commands are " +
|
|
257
|
+
f"{', '.join(Snapend.SUBCOMMANDS)}.",
|
|
260
258
|
SNAPCTL_INPUT_ERROR
|
|
261
259
|
)
|
|
262
260
|
if self.subcommand == 'enumerate':
|
|
@@ -279,11 +277,23 @@ class Snapend:
|
|
|
279
277
|
if not self.manifest_path:
|
|
280
278
|
snapctl_error(
|
|
281
279
|
"Missing required parameter: manifest_path", SNAPCTL_INPUT_ERROR)
|
|
280
|
+
if not os.path.isfile(self.manifest_path):
|
|
281
|
+
snapctl_error(
|
|
282
|
+
f"Invalid path {
|
|
283
|
+
self.manifest_path}. Please enter a valid path to the manifest file",
|
|
284
|
+
SNAPCTL_INPUT_ERROR
|
|
285
|
+
)
|
|
282
286
|
elif self.subcommand == 'apply':
|
|
283
287
|
if not self.manifest_path:
|
|
284
288
|
snapctl_error(
|
|
285
289
|
"Missing required parameter: manifest_path", SNAPCTL_INPUT_ERROR)
|
|
286
290
|
raise typer.Exit(code=SNAPCTL_INPUT_ERROR)
|
|
291
|
+
if not os.path.isfile(self.manifest_path):
|
|
292
|
+
snapctl_error(
|
|
293
|
+
f"Invalid path {
|
|
294
|
+
self.manifest_path}. Please enter a valid path to the manifest file",
|
|
295
|
+
SNAPCTL_INPUT_ERROR
|
|
296
|
+
)
|
|
287
297
|
if not self.manifest_file_name:
|
|
288
298
|
snapctl_error(
|
|
289
299
|
"Invalid manifest file. Supported formats are .json, .yml, .yaml",
|
|
@@ -426,15 +436,16 @@ class Snapend:
|
|
|
426
436
|
'Snapend clone successful. Do not forget to download the latest manifest.',
|
|
427
437
|
progress)
|
|
428
438
|
snapctl_error(
|
|
429
|
-
'Snapend clone has been initiated but the Snapend is not up yet.'
|
|
439
|
+
'Snapend clone has been initiated but the Snapend is not up yet.' +
|
|
430
440
|
'Please try checking the status of the Snapend in some time',
|
|
431
441
|
SNAPCTL_SNAPEND_CLONE_TIMEOUT_ERROR,
|
|
432
442
|
progress
|
|
433
443
|
)
|
|
434
444
|
snapctl_success(
|
|
435
|
-
"Snapend clone has been initiated. "
|
|
436
|
-
"You can check the status using "
|
|
437
|
-
|
|
445
|
+
"Snapend clone has been initiated. " +
|
|
446
|
+
"You can check the status using " +
|
|
447
|
+
"`snapctl snapend state --snapend-id" +
|
|
448
|
+
f"{response['cluster']['id']}`",
|
|
438
449
|
progress
|
|
439
450
|
)
|
|
440
451
|
except RequestException as e:
|
|
@@ -488,18 +499,19 @@ class Snapend:
|
|
|
488
499
|
# self.manifest_file_name.split('.')[-1])
|
|
489
500
|
# self._execute_download()
|
|
490
501
|
snapctl_success(
|
|
491
|
-
'Snapend apply successful. Do not forget to download '
|
|
502
|
+
'Snapend apply successful. Do not forget to download ' +
|
|
492
503
|
'the latest manifest.',
|
|
493
504
|
progress)
|
|
494
505
|
snapctl_error(
|
|
495
|
-
'Snapend apply has been initiated but the Snapend is not up yet.'
|
|
506
|
+
'Snapend apply has been initiated but the Snapend is not up yet.' +
|
|
496
507
|
'Please try checking the status of the Snapend in some time',
|
|
497
508
|
SNAPCTL_SNAPEND_APPLY_TIMEOUT_ERROR, progress
|
|
498
509
|
)
|
|
499
510
|
snapctl_success(
|
|
500
|
-
"Snapend apply has been initiated. "
|
|
501
|
-
"You can check the status using "
|
|
502
|
-
|
|
511
|
+
"Snapend apply has been initiated. " +
|
|
512
|
+
"You can check the status using " +
|
|
513
|
+
"`snapctl snapend state --snapend-id" +
|
|
514
|
+
f"{response['cluster']['id']}`",
|
|
503
515
|
progress
|
|
504
516
|
)
|
|
505
517
|
except RequestException as e:
|
|
@@ -552,20 +564,21 @@ class Snapend:
|
|
|
552
564
|
# self._execute_download()
|
|
553
565
|
# Fetch the new manifest
|
|
554
566
|
snapctl_success(
|
|
555
|
-
'Snapend promote successful. Do not forget to '
|
|
567
|
+
'Snapend promote successful. Do not forget to ' +
|
|
556
568
|
'download the latest manifest.',
|
|
557
569
|
progress
|
|
558
570
|
)
|
|
559
571
|
snapctl_error(
|
|
560
|
-
'Snapend apply has been initiated but the Snapend is not up yet.'
|
|
572
|
+
'Snapend apply has been initiated but the Snapend is not up yet.' +
|
|
561
573
|
'Please try checking the status of the Snapend in some time',
|
|
562
574
|
SNAPCTL_SNAPEND_PROMOTE_TIMEOUT_ERROR,
|
|
563
575
|
progress
|
|
564
576
|
)
|
|
565
577
|
snapctl_success(
|
|
566
|
-
"Snapend apply has been initiated. "
|
|
567
|
-
"You can check the status using "
|
|
568
|
-
|
|
578
|
+
"Snapend apply has been initiated. " +
|
|
579
|
+
"You can check the status using " +
|
|
580
|
+
"`snapctl snapend state --snapend-id" +
|
|
581
|
+
f"{response['cluster']['id']}`",
|
|
569
582
|
progress
|
|
570
583
|
)
|
|
571
584
|
snapctl_error(f'Unable to promote the manifest. Reason: {res.text}',
|
|
@@ -593,7 +606,8 @@ class Snapend:
|
|
|
593
606
|
try:
|
|
594
607
|
if self._execute_download():
|
|
595
608
|
snapctl_success(
|
|
596
|
-
|
|
609
|
+
"Snapend download successful. " +
|
|
610
|
+
f"{self.category} saved at {self.out_path}",
|
|
597
611
|
progress
|
|
598
612
|
)
|
|
599
613
|
except RequestException as e:
|
|
@@ -636,19 +650,19 @@ class Snapend:
|
|
|
636
650
|
# self.manifest_file_name.split('.')[-1])
|
|
637
651
|
# self._execute_download()
|
|
638
652
|
snapctl_success(
|
|
639
|
-
'Snapend update successful. Do not forget to '
|
|
653
|
+
'Snapend update successful. Do not forget to ' +
|
|
640
654
|
'download the latest manifest.',
|
|
641
655
|
progress
|
|
642
656
|
)
|
|
643
657
|
snapctl_error(
|
|
644
|
-
'Snapend update has been initiated. '
|
|
658
|
+
'Snapend update has been initiated. ' +
|
|
645
659
|
'You can check the status using `snapctl snapend state`',
|
|
646
660
|
SNAPCTL_SNAPEND_UPDATE_TIMEOUT_ERROR,
|
|
647
661
|
progress
|
|
648
662
|
)
|
|
649
663
|
snapctl_success(
|
|
650
|
-
"Snapend update has been initiated. "
|
|
651
|
-
"You can check the status using "
|
|
664
|
+
"Snapend update has been initiated. " +
|
|
665
|
+
"You can check the status using " +
|
|
652
666
|
f"`snapctl snapend state --snapend-id {self.snapend_id}`",
|
|
653
667
|
progress
|
|
654
668
|
)
|
|
@@ -678,7 +692,8 @@ class Snapend:
|
|
|
678
692
|
current_state = self._get_snapend_state()
|
|
679
693
|
if current_state != 'INVALID':
|
|
680
694
|
snapctl_success(
|
|
681
|
-
|
|
695
|
+
'Snapend get state successful. Current snapend state is: ' +
|
|
696
|
+
f'{current_state}',
|
|
682
697
|
progress)
|
|
683
698
|
snapctl_error("Unable to get the snapend state.",
|
|
684
699
|
SNAPCTL_SNAPEND_STATE_ERROR, progress)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
1
3
|
"""
|
|
2
4
|
This file contains the hashes / list constants
|
|
3
5
|
"""
|
|
4
|
-
CLIENT_SDK_TYPES:
|
|
6
|
+
CLIENT_SDK_TYPES: Dict[str, Dict[str, str]] = {
|
|
5
7
|
'unity': {
|
|
6
8
|
'type': 'csharp',
|
|
7
9
|
'subtype': 'unity',
|
|
@@ -52,7 +54,7 @@ CLIENT_SDK_TYPES: dict[str, dict[str, str]] = {
|
|
|
52
54
|
},
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
SERVER_SDK_TYPES:
|
|
57
|
+
SERVER_SDK_TYPES: Dict[str, Dict[str, str]] = {
|
|
56
58
|
'csharp': {
|
|
57
59
|
'type': 'csharp',
|
|
58
60
|
'subtype': '',
|
|
@@ -119,7 +121,7 @@ SERVER_SDK_TYPES: dict[str, dict[str, str]] = {
|
|
|
119
121
|
},
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
PROTOS_TYPES:
|
|
124
|
+
PROTOS_TYPES: Dict[str, Dict[str, str]] = {
|
|
123
125
|
'cpp': {
|
|
124
126
|
'type': 'cpp',
|
|
125
127
|
'subtype': '',
|
|
@@ -138,7 +140,7 @@ PROTOS_TYPES: dict[str, dict[str, str]] = {
|
|
|
138
140
|
},
|
|
139
141
|
}
|
|
140
142
|
|
|
141
|
-
SNAPEND_MANIFEST_TYPES:
|
|
143
|
+
SNAPEND_MANIFEST_TYPES: Dict[str, Dict[str, str]] = {
|
|
142
144
|
'json': {
|
|
143
145
|
'type': 'json',
|
|
144
146
|
'subtype': '',
|
|
@@ -149,39 +151,49 @@ SNAPEND_MANIFEST_TYPES: dict[str, dict[str, str]] = {
|
|
|
149
151
|
},
|
|
150
152
|
}
|
|
151
153
|
|
|
152
|
-
SERVICE_IDS = [
|
|
154
|
+
SERVICE_IDS: List[str] = [
|
|
153
155
|
'analytics', 'auth', 'client-logs', 'events', 'experiments', 'gdpr', 'guilds', 'hades', 'iap',
|
|
154
156
|
'inventory', 'leaderboards', 'matchmaking', 'notifications', 'parties', 'profiles', 'quests',
|
|
155
157
|
'relay', 'remote-config', 'scheduler', 'sequencer', 'social-graph', 'statistics', 'storage',
|
|
156
158
|
'trackables', 'xp'
|
|
157
159
|
]
|
|
158
160
|
|
|
159
|
-
DEFAULT_BYOSNAP_DEV_TEMPLATE = {
|
|
161
|
+
DEFAULT_BYOSNAP_DEV_TEMPLATE: Dict[str, object] = {
|
|
160
162
|
'cpu': 100,
|
|
161
163
|
'memory': 0.125,
|
|
164
|
+
'min_replicas': 1,
|
|
162
165
|
'cmd': '',
|
|
163
166
|
'args': [],
|
|
164
167
|
'env_params': [{'key': "SNAPSER_ENVIRONMENT", 'value': "DEVELOPMENT"}]
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
DEFAULT_BYOSNAP_STAGE_TEMPLATE = {
|
|
170
|
+
DEFAULT_BYOSNAP_STAGE_TEMPLATE: Dict[str, object] = {
|
|
168
171
|
'cpu': 100,
|
|
169
172
|
'memory': 0.125,
|
|
173
|
+
'min_replicas': 1,
|
|
170
174
|
'cmd': '',
|
|
171
175
|
'args': [],
|
|
172
176
|
'env_params': [{'key': "SNAPSER_ENVIRONMENT", 'value': "STAGING"}]
|
|
173
177
|
}
|
|
174
178
|
|
|
175
|
-
DEFAULT_BYOSNAP_PROD_TEMPLATE = {
|
|
179
|
+
DEFAULT_BYOSNAP_PROD_TEMPLATE: Dict[str, object] = {
|
|
176
180
|
'cpu': 100,
|
|
177
181
|
'memory': 0.125,
|
|
182
|
+
'min_replicas': 2,
|
|
178
183
|
'cmd': '',
|
|
179
184
|
'args': [],
|
|
180
185
|
'env_params': [{'key': "SNAPSER_ENVIRONMENT", 'value': "PRODUCTION"}]
|
|
181
186
|
}
|
|
182
187
|
|
|
183
|
-
BYOSNAP_TEMPLATE = {
|
|
188
|
+
BYOSNAP_TEMPLATE: Dict[str, Dict[str, object]] = {
|
|
184
189
|
'dev_template': DEFAULT_BYOSNAP_DEV_TEMPLATE,
|
|
185
190
|
'stage_template': DEFAULT_BYOSNAP_STAGE_TEMPLATE,
|
|
186
191
|
'prod_template': DEFAULT_BYOSNAP_PROD_TEMPLATE
|
|
187
192
|
}
|
|
193
|
+
|
|
194
|
+
ARCHITECTURE_MAPPING: Dict[str, str] = {
|
|
195
|
+
'x86_64': 'amd64',
|
|
196
|
+
'arm64': 'arm64',
|
|
197
|
+
'aarch64': 'arm64',
|
|
198
|
+
'amd64': 'amd64'
|
|
199
|
+
}
|
|
@@ -44,7 +44,7 @@ app = typer.Typer(
|
|
|
44
44
|
######### HELPER METHODS #########
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
def extract_config(extract_key: str, profile: str
|
|
47
|
+
def extract_config(extract_key: str, profile: Union[str, None] = None) -> object:
|
|
48
48
|
"""
|
|
49
49
|
Extracts the API Key from the environment variable and if not present from the config file
|
|
50
50
|
"""
|
|
@@ -58,9 +58,9 @@ def extract_config(extract_key: str, profile: str | None = None) -> object:
|
|
|
58
58
|
result['location'] = 'environment-variable'
|
|
59
59
|
result['value'] = env_api_key
|
|
60
60
|
return result
|
|
61
|
-
encoding: str
|
|
61
|
+
encoding: Union[str, None] = "utf-8-sig" if platform == 'win32' else None
|
|
62
62
|
# Option 2 - Get the API Key from CONFIG PATH environment variable
|
|
63
|
-
config_file_path: str
|
|
63
|
+
config_file_path: Union[str, None] = os.getenv(CONFIG_PATH_KEY)
|
|
64
64
|
# Option 3 - Get the API Key from the hardcoded config file we look for
|
|
65
65
|
if config_file_path is None:
|
|
66
66
|
if platform == 'win32':
|
|
@@ -76,7 +76,8 @@ def extract_config(extract_key: str, profile: str | None = None) -> object:
|
|
|
76
76
|
result['location'] = f'"{config_file_path}:profile {profile}"'
|
|
77
77
|
config_profile = f'profile {profile}'
|
|
78
78
|
info(
|
|
79
|
-
|
|
79
|
+
'Trying to extract API KEY from ' +
|
|
80
|
+
f'{config_file_path}:profile {profile}"'
|
|
80
81
|
)
|
|
81
82
|
result['value'] = config.get(
|
|
82
83
|
config_profile, extract_key, fallback=None, raw=True
|
|
@@ -87,7 +88,7 @@ def extract_config(extract_key: str, profile: str | None = None) -> object:
|
|
|
87
88
|
return result
|
|
88
89
|
|
|
89
90
|
|
|
90
|
-
def get_base_url(api_key: str
|
|
91
|
+
def get_base_url(api_key: Union[str, None]) -> str:
|
|
91
92
|
"""
|
|
92
93
|
Returns the base url based on the api_key
|
|
93
94
|
"""
|
|
@@ -140,7 +141,7 @@ def default_context_callback(ctx: typer.Context):
|
|
|
140
141
|
|
|
141
142
|
def api_key_context_callback(
|
|
142
143
|
ctx: typer.Context,
|
|
143
|
-
api_key: str
|
|
144
|
+
api_key: Union[str, None] = None
|
|
144
145
|
):
|
|
145
146
|
"""
|
|
146
147
|
Callback to set the context for the api_key
|
|
@@ -159,7 +160,7 @@ def api_key_context_callback(
|
|
|
159
160
|
|
|
160
161
|
def profile_context_callback(
|
|
161
162
|
ctx: typer.Context,
|
|
162
|
-
profile: str
|
|
163
|
+
profile: Union[str, None] = None
|
|
163
164
|
):
|
|
164
165
|
"""
|
|
165
166
|
Callback to set the context for the profile
|
|
@@ -338,7 +339,12 @@ def byosnap(
|
|
|
338
339
|
skip_build: bool = typer.Option(
|
|
339
340
|
False, "--skip-build", help="(optional: publish-image) Skip the build step. You have to pass the image tag you used during the build step."
|
|
340
341
|
),
|
|
341
|
-
|
|
342
|
+
readiness_path: str = typer.Option(
|
|
343
|
+
None, "--readiness-path", help="(req: publish-version) Readiness path for your snap"
|
|
344
|
+
),
|
|
345
|
+
readiness_delay: int = typer.Option(
|
|
346
|
+
None, "--readiness-delay", help="(req: publish-version) Delay before readiness check"
|
|
347
|
+
),
|
|
342
348
|
# overrides
|
|
343
349
|
api_key: Union[str, None] = typer.Option(
|
|
344
350
|
None, "--api-key", help="API Key override.", callback=api_key_context_callback
|
|
@@ -354,7 +360,8 @@ def byosnap(
|
|
|
354
360
|
byosnap_obj: ByoSnap = ByoSnap(
|
|
355
361
|
subcommand, ctx.obj['base_url'], ctx.obj['api_key'], sid,
|
|
356
362
|
name, desc, platform_type, language, tag, path, docker_file,
|
|
357
|
-
prefix, version, http_port, byosnap_profile, skip_build
|
|
363
|
+
prefix, version, http_port, byosnap_profile, skip_build,
|
|
364
|
+
readiness_path, readiness_delay
|
|
358
365
|
)
|
|
359
366
|
getattr(byosnap_obj, subcommand.replace('-', '_'))()
|
|
360
367
|
success(f"BYOSnap {subcommand} complete")
|
|
@@ -20,6 +20,13 @@ def error(msg: str, code: int = SNAPCTL_ERROR, data: object = None) -> None:
|
|
|
20
20
|
typer.echo(json.dumps(error_response.to_dict()), err=True)
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
def warning(msg: str) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Prints a warning message to the console.
|
|
26
|
+
"""
|
|
27
|
+
print(f"[bold yellow]Warning[/bold yellow] {msg}")
|
|
28
|
+
|
|
29
|
+
|
|
23
30
|
def info(msg: str) -> None:
|
|
24
31
|
"""
|
|
25
32
|
Prints an info message to the console.
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Helper functions for snapctl
|
|
3
3
|
"""
|
|
4
|
+
from typing import Union, Dict
|
|
5
|
+
import re
|
|
4
6
|
import requests
|
|
5
7
|
import typer
|
|
6
8
|
from requests.exceptions import RequestException
|
|
7
9
|
from rich.progress import Progress
|
|
8
10
|
from snapctl.config.constants import HTTP_NOT_FOUND, HTTP_FORBIDDEN, HTTP_UNAUTHORIZED, \
|
|
9
11
|
SERVER_CALL_TIMEOUT, SNAPCTL_CONFIGURATION_ERROR, SNAPCTL_SUCCESS
|
|
12
|
+
from snapctl.config.hashes import ARCHITECTURE_MAPPING
|
|
10
13
|
from snapctl.utils.echo import error, success
|
|
11
14
|
|
|
12
15
|
|
|
13
|
-
def validate_api_key(base_url: str, api_key: str
|
|
16
|
+
def validate_api_key(base_url: str, api_key: Union[str, None]) -> bool:
|
|
14
17
|
"""
|
|
15
18
|
This function validates the API Key
|
|
16
19
|
"""
|
|
@@ -43,7 +46,7 @@ def validate_api_key(base_url: str, api_key: str | None):
|
|
|
43
46
|
raise typer.Exit(code=SNAPCTL_CONFIGURATION_ERROR)
|
|
44
47
|
|
|
45
48
|
|
|
46
|
-
def get_composite_token(base_url: str, api_key: str
|
|
49
|
+
def get_composite_token(base_url: str, api_key: Union[str, None], action: str, params: object) -> str:
|
|
47
50
|
"""
|
|
48
51
|
This function exchanges the api_key for a composite token.
|
|
49
52
|
"""
|
|
@@ -74,7 +77,61 @@ def get_composite_token(base_url: str, api_key: str | None, action: str, params:
|
|
|
74
77
|
return res.json()['token']
|
|
75
78
|
|
|
76
79
|
|
|
77
|
-
def
|
|
80
|
+
def check_dockerfile_architecture(dockerfile_path: str, system_arch: str) -> Dict[str, object]:
|
|
81
|
+
"""
|
|
82
|
+
Check the Dockerfile for architecture specific commands
|
|
83
|
+
"""
|
|
84
|
+
response = {
|
|
85
|
+
'error': False,
|
|
86
|
+
'message': ''
|
|
87
|
+
}
|
|
88
|
+
# Normalize system architecture
|
|
89
|
+
system_arch = ARCHITECTURE_MAPPING.get(system_arch, system_arch)
|
|
90
|
+
try:
|
|
91
|
+
lines = []
|
|
92
|
+
with open(dockerfile_path, 'r') as file:
|
|
93
|
+
lines = file.readlines()
|
|
94
|
+
for line_number, line in enumerate(lines, 1):
|
|
95
|
+
if line.startswith('#'):
|
|
96
|
+
continue
|
|
97
|
+
# Checking various build and run commands for architecture specifics
|
|
98
|
+
patterns = [
|
|
99
|
+
# FROM with platform
|
|
100
|
+
r'FROM --platform=linux/(\w+)',
|
|
101
|
+
# dotnet runtime
|
|
102
|
+
r'-r linux-(\w+)',
|
|
103
|
+
# Build args specifying arch
|
|
104
|
+
r'--build-arg ARCH=(\w+)',
|
|
105
|
+
# Environment variables setting arch
|
|
106
|
+
r'ENV ARCH=(\w+)',
|
|
107
|
+
# cmake specifying arch
|
|
108
|
+
r'cmake.*?-DARCH=(\w+)',
|
|
109
|
+
# make specifying arch
|
|
110
|
+
r'make.*?ARCH=(\w+)'
|
|
111
|
+
]
|
|
112
|
+
|
|
113
|
+
for pattern in patterns:
|
|
114
|
+
match = re.search(pattern, line)
|
|
115
|
+
if match and ARCHITECTURE_MAPPING.get(match.group(1)) != system_arch:
|
|
116
|
+
response['error'] = True
|
|
117
|
+
response['message'] = '[Architecture Mismatch] Line ' + \
|
|
118
|
+
f'{line_number}: "{line.strip()}" ' + \
|
|
119
|
+
f'of Dockerfile {dockerfile_path} ' + \
|
|
120
|
+
f'specifies architecture {match.group(1)}, which does not match the ' + \
|
|
121
|
+
f'systems ({system_arch}).'
|
|
122
|
+
return response
|
|
123
|
+
except FileNotFoundError:
|
|
124
|
+
response['error'] = True
|
|
125
|
+
response['message'] = f'Dockerfile not found at {dockerfile_path}'
|
|
126
|
+
return response
|
|
127
|
+
except Exception as e:
|
|
128
|
+
response['error'] = True
|
|
129
|
+
response['message'] = f'Exception {e}'
|
|
130
|
+
return response
|
|
131
|
+
return response
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def snapctl_success(message: str, progress: Union[Progress, None] = None, no_exit: bool = False):
|
|
78
135
|
"""
|
|
79
136
|
This function exits the snapctl
|
|
80
137
|
"""
|
|
@@ -85,7 +142,7 @@ def snapctl_success(message: str, progress: Progress | None = None, no_exit: boo
|
|
|
85
142
|
raise typer.Exit(code=SNAPCTL_SUCCESS)
|
|
86
143
|
|
|
87
144
|
|
|
88
|
-
def snapctl_error(message: str, code: int, progress: Progress
|
|
145
|
+
def snapctl_error(message: str, code: int, progress: Union[Progress, None] = None):
|
|
89
146
|
"""
|
|
90
147
|
This function exits the snapctl
|
|
91
148
|
"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|