snapctl 0.35.0__py3-none-any.whl → 0.38.1__py3-none-any.whl

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/commands/byogs.py CHANGED
@@ -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
- from snapctl.utils.echo import info
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 | None,
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[list, None] = ByoGs._get_token_values(
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 | list:
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
- f"docker build --platform {build_platform} -t {self.input_tag} "
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
- f"Invalid command. Valid commands are {', '.join(ByoGs.SUBCOMMANDS)}.",
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 | None, sid: str, name: 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 | list:
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
- f"docker build --platform {build_platform} -t {self.input_tag} "
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
- F"JSON at path {self.byosnap_profile}",
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'Valid values are {", ".join(map(str, ByoSnap.VALID_CPU_MARKS))}',
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'Valid values are {", ".join(map(str, ByoSnap.VALID_MEMORY_MARKS))}',
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"are {', '.join(ByoSnap.SUBCOMMANDS)}.",
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
- f"Invalid Snap ID. Valid Snap IDs start with {ByoSnap.ID_PREFIX}.",
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"Snap ID should be less than {ByoSnap.SID_CHARACTER_LIMIT} characters",
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 {self.dockerfile} at path {self.path}",
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 len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
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
- f'Exception: Unable to find swagger.json at {self.path} {e}'
540
+ 'Exception: Unable to find swagger.json at ' +
541
+ f'{self.path} {e}'
499
542
  )
500
543
  else:
501
- info(f'No swagger.json found at {self.path}'
502
- '. Skipping swagger.json upload')
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
- f'Exception: Unable to find README.md at {self.path} {str(e)}')
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. Please use a different name',
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']
snapctl/commands/game.py CHANGED
@@ -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
- SNAPCTL_SUCCESS, SNAPCTL_GAME_CREATE_ERROR, SNAPCTL_GAME_ENUMERATE_ERROR, \
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 | None, name: str | None
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 | None = name
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
- f"Invalid command. Valid commands are {', '.join(Game.SUBCOMMANDS)}.",
43
- SNAPCTL_INPUT_ERROR)
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 | None,
32
- category: str | None,
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 | None = category
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 | list:
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 | None, snapend_id: str | None,
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 | None,
50
+ game_id: Union[str, None],
50
51
  # Clone
51
- name: str | None,
52
- env: str | None,
52
+ name: Union[str, None],
53
+ env: Union[str, None],
53
54
  # Clone, Apply, Promote
54
- manifest_path: str | None,
55
+ manifest_path: Union[str, None],
55
56
  # Download
56
- category: str, platform_type: str, protos_category: str, auth_type: str, snaps: str | None,
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 | None,
59
+ out_path: Union[str, None],
59
60
  # Update
60
- byosnaps: str | None, byogs: str | None, blocking: bool = False
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 | None = game_id
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 | None = manifest_path
70
- self.manifest_file_name: str | None = Snapend._get_manifest_file_name(
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
- dict[str, dict[str, str]], None
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 | None = out_path
81
- self.snaps: str | None = snaps
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 | None = Snapend._make_byogs_list(
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 | None:
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
- dict[str, dict[str, str]], None
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 += f"&type={self.download_types[self.platform_type]['type']}"
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 += f"&subtype={self.download_types[self.platform_type]['subtype']}"
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"snapser-{self.snapend_id}-"
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
- f"Invalid command. Valid commands are {', '.join(Snapend.SUBCOMMANDS)}.",
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
- f"`snapctl snapend state --snapend-id {response['cluster']['id']}`",
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
- f"`snapctl snapend state --snapend-id {response['cluster']['id']}`",
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
- f"`snapctl snapend state --snapend-id {response['cluster']['id']}`",
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
- f"Snapend download successful. {self.category} saved at {self.out_path}",
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
- f'Snapend get state successful. Current snapend state is: {current_state}',
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)
@@ -2,7 +2,7 @@
2
2
  Constants used by snapctl
3
3
  """
4
4
  COMPANY_NAME = 'Snapser'
5
- VERSION = '0.35.0'
5
+ VERSION = '0.38.1'
6
6
  CONFIG_FILE_MAC = '~/.snapser/config'
7
7
  CONFIG_FILE_WIN = '%homepath%\\.snapser\\config'
8
8
 
snapctl/config/hashes.py CHANGED
@@ -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: dict[str, dict[str, str]] = {
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: dict[str, dict[str, str]] = {
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: dict[str, dict[str, str]] = {
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: dict[str, dict[str, str]] = {
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
+ }
snapctl/main.py CHANGED
@@ -44,7 +44,7 @@ app = typer.Typer(
44
44
  ######### HELPER METHODS #########
45
45
 
46
46
 
47
- def extract_config(extract_key: str, profile: str | None = None) -> object:
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 | None = "utf-8-sig" if platform == 'win32' else None
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 | None = os.getenv(CONFIG_PATH_KEY)
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
- f'Trying to extract API KEY from "{config_file_path}:profile {profile}"'
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 | None) -> 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 | None = None
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 | None = None
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")
snapctl/utils/echo.py CHANGED
@@ -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.
snapctl/utils/helper.py CHANGED
@@ -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 | None):
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 | None, action: str, params: object) -> 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 snapctl_success(message: str, progress: Progress | None = None, no_exit: bool = False):
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 | None = None):
145
+ def snapctl_error(message: str, code: int, progress: Union[Progress, None] = None):
89
146
  """
90
147
  This function exits the snapctl
91
148
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: snapctl
3
- Version: 0.35.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
@@ -0,0 +1,23 @@
1
+ snapctl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ snapctl/__main__.py,sha256=43jKoTk8b85hk_MT6499N3ruHdEfM8WBImd_-3VzjI8,116
3
+ snapctl/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ snapctl/commands/byogs.py,sha256=XdF8Eq8vDzKFZzQzeRAbApTK8HwXBGwe64lcWNBu-dQ,13515
5
+ snapctl/commands/byosnap.py,sha256=Z-yf7ssdvUnycIcZDN1JXZ-CO5gjq6yQZ7_bb6KYAeQ,31207
6
+ snapctl/commands/game.py,sha256=icAPD8derxtgXHZQvziHd1O8fwD5Kw7HFMA8yXOUSRQ,4344
7
+ snapctl/commands/generate.py,sha256=7z4nyqDuAieFmIg-XVIri2xkXPpDJd0E3uKYIfQtBqE,7891
8
+ snapctl/commands/snapend.py,sha256=wEeA9v0SCs0G2093wMIKVTQhPLCfx8i5Ladk9kMlSCM,30729
9
+ snapctl/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ snapctl/config/constants.py,sha256=s26pXoQWuafLkvtFtOv5sBHrAsZjVO3MUsG3AA3JqGQ,2735
11
+ snapctl/config/endpoints.py,sha256=VAeOmx3k3ukB-9XuGI65KtCJwFK-KFgzor-UWE8JU0g,297
12
+ snapctl/config/hashes.py,sha256=7jkMYfMAcgdque5PN10-B3QqwoXvtsuJKJr9dKZUvJ8,4139
13
+ snapctl/main.py,sha256=aaFB0insVsIKDp25Hc0J0t5JtIQJXxDgHbBwKaHVbBc,19619
14
+ snapctl/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ snapctl/types/definitions.py,sha256=EQzLeiXkJ8ISRlCqHMviNVsWWpmhWjpKaOBLdlvOTmY,644
16
+ snapctl/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ snapctl/utils/echo.py,sha256=V0qgjqqGXRiueMkq31enmNmdqciC8S90qGRcK8UupXA,1090
18
+ snapctl/utils/helper.py,sha256=XSn0x46mED86kQU1UlmuvJY9GsXO3Ncoba18Lb6FE8k,5522
19
+ snapctl-0.38.1.dist-info/LICENSE,sha256=6AcXm54KFSpmUI1ji9NIBd4Xl-DtjTqiyjBzfVb_CEk,2804
20
+ snapctl-0.38.1.dist-info/METADATA,sha256=HGOdEApmi0QmDIvWhSDUIO072EdxaOsGWzMO2eeoLJQ,21465
21
+ snapctl-0.38.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
22
+ snapctl-0.38.1.dist-info/entry_points.txt,sha256=tkKW9MzmFdRs6Bgkv29G78i9WEBK4WIOWunPfe3t2Wg,44
23
+ snapctl-0.38.1.dist-info/RECORD,,
@@ -1,23 +0,0 @@
1
- snapctl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- snapctl/__main__.py,sha256=43jKoTk8b85hk_MT6499N3ruHdEfM8WBImd_-3VzjI8,116
3
- snapctl/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- snapctl/commands/byogs.py,sha256=NYQLqFnaXQSTqwSfBCMYtDprDtu1bosEIlCf2jMUZdI,12987
5
- snapctl/commands/byosnap.py,sha256=Pd0IAJsyaq8EhyEJwHEQamQP9ud4nDu8mfkeqcj_188,28512
6
- snapctl/commands/game.py,sha256=7255xoGszkk2pPcc2sRJP98-3S0qfSKXFOl5CrsbCSY,4349
7
- snapctl/commands/generate.py,sha256=l1avy7YlCgscCBT40yh9L8OjD7ONMUE96BB2M553g30,7849
8
- snapctl/commands/snapend.py,sha256=cxBMNMfXyQeLdg5NyKZ2ujjUG7zYxOgyjmeAoD27t7I,29922
9
- snapctl/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- snapctl/config/constants.py,sha256=UPg9ycbx2OF6SIrt0X4SByMskcu39rnVxFdopc8Ga7M,2735
11
- snapctl/config/endpoints.py,sha256=VAeOmx3k3ukB-9XuGI65KtCJwFK-KFgzor-UWE8JU0g,297
12
- snapctl/config/hashes.py,sha256=KGzZCVeM3nI3AC4zOYkw-j0PVr9gAGrRefRI30Xvglg,3807
13
- snapctl/main.py,sha256=OW4Ct80h-BxMO4CvizTNNu9IasguV6gXWKXiC6HZBMc,19241
14
- snapctl/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- snapctl/types/definitions.py,sha256=EQzLeiXkJ8ISRlCqHMviNVsWWpmhWjpKaOBLdlvOTmY,644
16
- snapctl/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- snapctl/utils/echo.py,sha256=bvirxTlxv-EAwyf4FT0yAqY5fsevp3yimxKmgQV73Fc,941
18
- snapctl/utils/helper.py,sha256=jPXQnSbdOnktJdLotBugdHfVwkap-5MoApOmhJ-AsqA,3287
19
- snapctl-0.35.0.dist-info/LICENSE,sha256=6AcXm54KFSpmUI1ji9NIBd4Xl-DtjTqiyjBzfVb_CEk,2804
20
- snapctl-0.35.0.dist-info/METADATA,sha256=GaRoEVhkT5cGjpKMeGZmztpnm0RVTXjUpliPPzmIXb0,21045
21
- snapctl-0.35.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
22
- snapctl-0.35.0.dist-info/entry_points.txt,sha256=tkKW9MzmFdRs6Bgkv29G78i9WEBK4WIOWunPfe3t2Wg,44
23
- snapctl-0.35.0.dist-info/RECORD,,