snapctl 0.32.2__tar.gz → 0.38.0__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.32.2 → snapctl-0.38.0}/PKG-INFO +26 -5
- {snapctl-0.32.2 → snapctl-0.38.0}/README.md +25 -4
- {snapctl-0.32.2 → snapctl-0.38.0}/pyproject.toml +1 -1
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/commands/byogs.py +27 -12
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/commands/byosnap.py +77 -21
- snapctl-0.38.0/snapctl/commands/generate.py +215 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/config/constants.py +3 -2
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/config/hashes.py +32 -6
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/main.py +43 -12
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/utils/echo.py +7 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/utils/helper.py +60 -1
- snapctl-0.32.2/snapctl/commands/generate.py +0 -93
- {snapctl-0.32.2 → snapctl-0.38.0}/LICENSE +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/__init__.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/__main__.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/commands/__init__.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/commands/game.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/commands/snapend.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/config/__init__.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/config/endpoints.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/types/__init__.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/snapctl/types/definitions.py +0 -0
- {snapctl-0.32.2 → snapctl-0.38.0}/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.0
|
|
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
|
|
@@ -272,9 +276,11 @@ snapctl byosnap publish-image --help
|
|
|
272
276
|
# $byosnap_sid = Snap ID for your snap
|
|
273
277
|
# $image_tag = An image tag for your snap
|
|
274
278
|
# $code_root_path = Local code path where your Dockerfile is present
|
|
279
|
+
# $skip-build = true/false. Default is false. Pass this flag as true to skip the build and head straight to tag and push. Build step needs to run and tagged using the --tag you pass to the publish-image command for this to work.
|
|
275
280
|
# Example:
|
|
276
281
|
# snapctl byosnap publish-image byosnap-jinks-flask --tag my-first-image --path /Users/DevName/Development/SnapserEngine/jinks_flask
|
|
277
282
|
snapctl byosnap publish-image $byosnap_sid --tag $image_tag --path $code_root_path
|
|
283
|
+
snapctl byosnap publish-image $byosnap_sid --tag $image_tag --skip-build
|
|
278
284
|
```
|
|
279
285
|
|
|
280
286
|
#### 7. byosnap publish-version
|
|
@@ -292,7 +298,7 @@ snapctl byosnap publish-version --help
|
|
|
292
298
|
# $prefix = Prefix for your snap Eg: /v1
|
|
293
299
|
# $version = Semantic version for your snap Eg: v0.0.1
|
|
294
300
|
# $ingress_port = Ingress port for your snap Eg: 5003
|
|
295
|
-
# $byosnap_profile = BYOSnap profile to configure dev, stage and prod settings for this snap. You can generate a base version of this file using the `snapctl generate byosnap
|
|
301
|
+
# $byosnap_profile = BYOSnap profile to configure dev, stage and prod settings for this snap. You can generate a base version of this file using the `snapctl generate profile --category byosnap --out-path <output_path>` command
|
|
296
302
|
# Example:
|
|
297
303
|
# snapctl byosnap publish-image byosnap-jinks-flask --tag my-first-image --prefix /v1 --version v0.0.1 --http-port 5003 --byosnap-profile /Users/DevName/Development/SnapserEngine/jinks_flask/snapser-byosnap-profile.json
|
|
298
304
|
snapctl byosnap publish-version $byosnap_sid --tag $image_tag --prefix $prefix --version $version --http-port $ingress_port --byosnap-profile $byosnap_profile
|
|
@@ -346,6 +352,10 @@ snapctl byogs push --tag $image_tag
|
|
|
346
352
|
Publish your custom game server image. This commend replaces the old way of creating, publishing image and
|
|
347
353
|
then publishing the byogs. Now all you have to do is publish your image and create a fleet using the web portal.
|
|
348
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
|
+
|
|
349
359
|
```
|
|
350
360
|
# Help for the byogs command
|
|
351
361
|
snapctl byogs publish --help
|
|
@@ -353,9 +363,11 @@ snapctl byogs publish --help
|
|
|
353
363
|
# Publish a new image
|
|
354
364
|
# $image_tag = An image tag for your snap
|
|
355
365
|
# $code_root_path = Local code path where your Dockerfile is present
|
|
366
|
+
# $skip-build = Default is false. Pass this flag as true to skip the build and head straight to tag and push. Build step needs to run and tagged using the --tag you pass to the publish-image command for this to work.
|
|
356
367
|
# Example:
|
|
357
368
|
# snapctl byogs publish --tag my-first-image --path /Users/DevName/Development/SnapserEngine/game_server
|
|
358
369
|
snapctl byogs publish --tag $image_tag --path $code_root_path
|
|
370
|
+
snapctl byogs publish --tag $image_tag --skip-build
|
|
359
371
|
```
|
|
360
372
|
|
|
361
373
|
|
|
@@ -393,11 +405,19 @@ See all the supported commands
|
|
|
393
405
|
snapctl generate --help
|
|
394
406
|
```
|
|
395
407
|
|
|
396
|
-
#### 2. BYOSnap Profile
|
|
408
|
+
#### 2. Generate BYOSnap Profile
|
|
397
409
|
Generate the base file for BYOSnap profile to be used in the `snapctl byosnap publish-version` command
|
|
398
410
|
|
|
399
411
|
```
|
|
400
|
-
snapctl generate
|
|
412
|
+
snapctl generate profile --category "byosnap" --out-path $output_path
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
#### 3. Generate ECR Credentials
|
|
417
|
+
Generate the ECR credentials. Game studios can use these credentials to self publish their images to Snapser.
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
snapctl generate credentials --category "ecr" --out-path $output_path
|
|
401
421
|
|
|
402
422
|
```
|
|
403
423
|
|
|
@@ -596,5 +616,6 @@ snapctl snapend state $snapend_id
|
|
|
596
616
|
| Error Code | Description |
|
|
597
617
|
|------------|----------------------------------------------------------|
|
|
598
618
|
| 80 | Generic generate error |
|
|
599
|
-
| 81 | Generate
|
|
619
|
+
| 81 | Generate profile error |
|
|
620
|
+
| 82 | Generate credentials error |
|
|
600
621
|
|
|
@@ -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
|
|
@@ -253,9 +257,11 @@ snapctl byosnap publish-image --help
|
|
|
253
257
|
# $byosnap_sid = Snap ID for your snap
|
|
254
258
|
# $image_tag = An image tag for your snap
|
|
255
259
|
# $code_root_path = Local code path where your Dockerfile is present
|
|
260
|
+
# $skip-build = true/false. Default is false. Pass this flag as true to skip the build and head straight to tag and push. Build step needs to run and tagged using the --tag you pass to the publish-image command for this to work.
|
|
256
261
|
# Example:
|
|
257
262
|
# snapctl byosnap publish-image byosnap-jinks-flask --tag my-first-image --path /Users/DevName/Development/SnapserEngine/jinks_flask
|
|
258
263
|
snapctl byosnap publish-image $byosnap_sid --tag $image_tag --path $code_root_path
|
|
264
|
+
snapctl byosnap publish-image $byosnap_sid --tag $image_tag --skip-build
|
|
259
265
|
```
|
|
260
266
|
|
|
261
267
|
#### 7. byosnap publish-version
|
|
@@ -273,7 +279,7 @@ snapctl byosnap publish-version --help
|
|
|
273
279
|
# $prefix = Prefix for your snap Eg: /v1
|
|
274
280
|
# $version = Semantic version for your snap Eg: v0.0.1
|
|
275
281
|
# $ingress_port = Ingress port for your snap Eg: 5003
|
|
276
|
-
# $byosnap_profile = BYOSnap profile to configure dev, stage and prod settings for this snap. You can generate a base version of this file using the `snapctl generate byosnap
|
|
282
|
+
# $byosnap_profile = BYOSnap profile to configure dev, stage and prod settings for this snap. You can generate a base version of this file using the `snapctl generate profile --category byosnap --out-path <output_path>` command
|
|
277
283
|
# Example:
|
|
278
284
|
# snapctl byosnap publish-image byosnap-jinks-flask --tag my-first-image --prefix /v1 --version v0.0.1 --http-port 5003 --byosnap-profile /Users/DevName/Development/SnapserEngine/jinks_flask/snapser-byosnap-profile.json
|
|
279
285
|
snapctl byosnap publish-version $byosnap_sid --tag $image_tag --prefix $prefix --version $version --http-port $ingress_port --byosnap-profile $byosnap_profile
|
|
@@ -327,6 +333,10 @@ snapctl byogs push --tag $image_tag
|
|
|
327
333
|
Publish your custom game server image. This commend replaces the old way of creating, publishing image and
|
|
328
334
|
then publishing the byogs. Now all you have to do is publish your image and create a fleet using the web portal.
|
|
329
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
|
+
|
|
330
340
|
```
|
|
331
341
|
# Help for the byogs command
|
|
332
342
|
snapctl byogs publish --help
|
|
@@ -334,9 +344,11 @@ snapctl byogs publish --help
|
|
|
334
344
|
# Publish a new image
|
|
335
345
|
# $image_tag = An image tag for your snap
|
|
336
346
|
# $code_root_path = Local code path where your Dockerfile is present
|
|
347
|
+
# $skip-build = Default is false. Pass this flag as true to skip the build and head straight to tag and push. Build step needs to run and tagged using the --tag you pass to the publish-image command for this to work.
|
|
337
348
|
# Example:
|
|
338
349
|
# snapctl byogs publish --tag my-first-image --path /Users/DevName/Development/SnapserEngine/game_server
|
|
339
350
|
snapctl byogs publish --tag $image_tag --path $code_root_path
|
|
351
|
+
snapctl byogs publish --tag $image_tag --skip-build
|
|
340
352
|
```
|
|
341
353
|
|
|
342
354
|
|
|
@@ -374,11 +386,19 @@ See all the supported commands
|
|
|
374
386
|
snapctl generate --help
|
|
375
387
|
```
|
|
376
388
|
|
|
377
|
-
#### 2. BYOSnap Profile
|
|
389
|
+
#### 2. Generate BYOSnap Profile
|
|
378
390
|
Generate the base file for BYOSnap profile to be used in the `snapctl byosnap publish-version` command
|
|
379
391
|
|
|
380
392
|
```
|
|
381
|
-
snapctl generate
|
|
393
|
+
snapctl generate profile --category "byosnap" --out-path $output_path
|
|
394
|
+
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
#### 3. Generate ECR Credentials
|
|
398
|
+
Generate the ECR credentials. Game studios can use these credentials to self publish their images to Snapser.
|
|
399
|
+
|
|
400
|
+
```
|
|
401
|
+
snapctl generate credentials --category "ecr" --out-path $output_path
|
|
382
402
|
|
|
383
403
|
```
|
|
384
404
|
|
|
@@ -577,4 +597,5 @@ snapctl snapend state $snapend_id
|
|
|
577
597
|
| Error Code | Description |
|
|
578
598
|
|------------|----------------------------------------------------------|
|
|
579
599
|
| 80 | Generic generate error |
|
|
580
|
-
| 81 | Generate
|
|
600
|
+
| 81 | Generate profile error |
|
|
601
|
+
| 82 | Generate credentials error |
|
|
@@ -5,17 +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
10
|
from typing import Union
|
|
10
11
|
|
|
11
|
-
import typer
|
|
12
12
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
13
13
|
from snapctl.config.constants import SNAPCTL_BYOGS_DEPENDENCY_MISSING, \
|
|
14
14
|
SNAPCTL_BYOGS_ECR_LOGIN_ERROR, SNAPCTL_BYOGS_BUILD_ERROR, \
|
|
15
15
|
SNAPCTL_BYOGS_TAG_ERROR, SNAPCTL_BYOGS_PUBLISH_ERROR, \
|
|
16
16
|
SNAPCTL_BYOGS_PUBLISH_DUPLICATE_TAG_ERROR, SNAPCTL_INPUT_ERROR
|
|
17
|
-
from snapctl.utils.
|
|
18
|
-
|
|
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
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
class ByoGs:
|
|
@@ -33,6 +34,7 @@ class ByoGs:
|
|
|
33
34
|
def __init__(
|
|
34
35
|
self, subcommand: str, base_url: str, api_key: str | None,
|
|
35
36
|
input_tag: Union[str, None], path: Union[str, None], dockerfile: str,
|
|
37
|
+
skip_build: bool = False
|
|
36
38
|
) -> None:
|
|
37
39
|
self.subcommand: str = subcommand
|
|
38
40
|
self.base_url: str = base_url
|
|
@@ -49,6 +51,7 @@ class ByoGs:
|
|
|
49
51
|
self.input_tag: Union[str, None] = input_tag
|
|
50
52
|
self.path: Union[str, None] = path
|
|
51
53
|
self.dockerfile: str = dockerfile
|
|
54
|
+
self.skip_build: bool = skip_build
|
|
52
55
|
# Validate input
|
|
53
56
|
self.validate_input()
|
|
54
57
|
|
|
@@ -143,23 +146,31 @@ class ByoGs:
|
|
|
143
146
|
progress.add_task(
|
|
144
147
|
description='Building your snap...', total=None)
|
|
145
148
|
try:
|
|
146
|
-
image_tag = f'{ByoGs.SID}.{self.input_tag}'
|
|
149
|
+
# image_tag = f'{ByoGs.SID}.{self.input_tag}'
|
|
147
150
|
build_platform = ByoGs.DEFAULT_BUILD_PLATFORM
|
|
148
151
|
if len(self.token_parts) == 4:
|
|
149
152
|
build_platform = self.token_parts[3]
|
|
150
153
|
# Build your snap
|
|
151
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
|
|
152
162
|
if platform == "win32":
|
|
153
163
|
response = subprocess.run([
|
|
154
164
|
# f"docker build --no-cache -t {tag} {path}"
|
|
155
|
-
'docker', 'build', '--platform', build_platform, '-t',
|
|
165
|
+
'docker', 'build', '--platform', build_platform, '-t', self.input_tag,
|
|
156
166
|
'-f', docker_file_path, self.path
|
|
157
167
|
], shell=True, check=False)
|
|
158
168
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
159
169
|
else:
|
|
160
170
|
response = subprocess.run([
|
|
161
171
|
# f"docker build --no-cache -t {tag} {path}"
|
|
162
|
-
f"docker build --platform {
|
|
172
|
+
f"docker build --platform {
|
|
173
|
+
build_platform} -t {self.input_tag} "
|
|
163
174
|
f"-f {docker_file_path} {self.path}"
|
|
164
175
|
], shell=True, check=False)
|
|
165
176
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
@@ -188,11 +199,11 @@ class ByoGs:
|
|
|
188
199
|
# Tag the repo
|
|
189
200
|
if platform == "win32":
|
|
190
201
|
response = subprocess.run([
|
|
191
|
-
'docker', 'tag',
|
|
202
|
+
'docker', 'tag', self.input_tag, full_ecr_repo_url
|
|
192
203
|
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
193
204
|
else:
|
|
194
205
|
response = subprocess.run([
|
|
195
|
-
f"docker tag {
|
|
206
|
+
f"docker tag {self.input_tag} {full_ecr_repo_url}"
|
|
196
207
|
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
197
208
|
if not response.returncode:
|
|
198
209
|
return snapctl_success('BYOGS tag successful', progress, no_exit=True)
|
|
@@ -246,7 +257,8 @@ class ByoGs:
|
|
|
246
257
|
# Check subcommand
|
|
247
258
|
if not self.subcommand in ByoGs.SUBCOMMANDS:
|
|
248
259
|
snapctl_error(
|
|
249
|
-
f"Invalid command. Valid commands are {
|
|
260
|
+
f"Invalid command. Valid commands are {
|
|
261
|
+
', '.join(ByoGs.SUBCOMMANDS)}.",
|
|
250
262
|
SNAPCTL_INPUT_ERROR)
|
|
251
263
|
# Validation for subcommands
|
|
252
264
|
if self.token_parts is None:
|
|
@@ -263,11 +275,11 @@ class ByoGs:
|
|
|
263
275
|
SNAPCTL_INPUT_ERROR
|
|
264
276
|
)
|
|
265
277
|
if self.subcommand in ['build', 'publish']:
|
|
266
|
-
if not self.path:
|
|
278
|
+
if not self.skip_build and not self.path:
|
|
267
279
|
snapctl_error("Missing required parameter: path",
|
|
268
280
|
SNAPCTL_INPUT_ERROR)
|
|
269
281
|
# Check path
|
|
270
|
-
if not os.path.isfile(f"{self.path}/{self.dockerfile}"):
|
|
282
|
+
if not self.skip_build and not os.path.isfile(f"{self.path}/{self.dockerfile}"):
|
|
271
283
|
snapctl_error(
|
|
272
284
|
f"Unable to find {self.dockerfile} at path {self.path}", SNAPCTL_INPUT_ERROR)
|
|
273
285
|
# elif self.subcommand == 'push':
|
|
@@ -313,7 +325,10 @@ class ByoGs:
|
|
|
313
325
|
"""
|
|
314
326
|
self._check_dependencies()
|
|
315
327
|
self._docker_login()
|
|
316
|
-
self.
|
|
328
|
+
if not self.skip_build:
|
|
329
|
+
self._docker_build()
|
|
330
|
+
else:
|
|
331
|
+
info('--skip-build set. Skipping the build step.')
|
|
317
332
|
self._docker_tag()
|
|
318
333
|
self._docker_push()
|
|
319
334
|
snapctl_success('BYOGS publish successful')
|
|
@@ -7,6 +7,7 @@ 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
12
|
from typing import Union
|
|
12
13
|
import requests
|
|
@@ -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,12 +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
50
|
self, subcommand: str, base_url: str, api_key: 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
|
-
http_port: Union[int, None], byosnap_profile: Union[str, None]
|
|
53
|
+
http_port: Union[int, None], byosnap_profile: Union[str, None],
|
|
54
|
+
skip_build: bool = False, readiness_path: Union[str, None] = None,
|
|
55
|
+
readiness_delay: Union[int, None] = None
|
|
50
56
|
) -> None:
|
|
51
57
|
self.subcommand: str = subcommand
|
|
52
58
|
self.base_url: str = base_url
|
|
@@ -72,6 +78,9 @@ class ByoSnap:
|
|
|
72
78
|
self.version: Union[str, None] = version
|
|
73
79
|
self.http_port: Union[int, None] = http_port
|
|
74
80
|
self.byosnap_profile: Union[str, None] = byosnap_profile
|
|
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
|
|
75
84
|
# Validate the input
|
|
76
85
|
self.validate_input()
|
|
77
86
|
|
|
@@ -162,7 +171,7 @@ class ByoSnap:
|
|
|
162
171
|
|
|
163
172
|
def _docker_build(self) -> None:
|
|
164
173
|
# Get the data
|
|
165
|
-
image_tag = f'{self.sid}.{self.input_tag}'
|
|
174
|
+
# image_tag = f'{self.sid}.{self.input_tag}'
|
|
166
175
|
build_platform = ByoSnap.DEFAULT_BUILD_PLATFORM
|
|
167
176
|
if len(self.token_parts) == 4:
|
|
168
177
|
build_platform = self.token_parts[3]
|
|
@@ -177,17 +186,25 @@ class ByoSnap:
|
|
|
177
186
|
try:
|
|
178
187
|
# Build your snap
|
|
179
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
|
|
180
196
|
if platform == "win32":
|
|
181
197
|
response = subprocess.run([
|
|
182
198
|
# f"docker build --no-cache -t {tag} {path}"
|
|
183
|
-
'docker', 'build', '--platform', build_platform, '-t',
|
|
199
|
+
'docker', 'build', '--platform', build_platform, '-t', self.input_tag,
|
|
184
200
|
'-f', docker_file_path, self.path
|
|
185
201
|
], shell=True, check=False)
|
|
186
202
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
187
203
|
else:
|
|
188
204
|
response = subprocess.run([
|
|
189
205
|
# f"docker build --no-cache -t {tag} {path}"
|
|
190
|
-
f"docker build --platform {
|
|
206
|
+
f"docker build --platform {
|
|
207
|
+
build_platform} -t {self.input_tag} "
|
|
191
208
|
f"-f {docker_file_path} {self.path}"
|
|
192
209
|
], shell=True, check=False)
|
|
193
210
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
@@ -217,11 +234,11 @@ class ByoSnap:
|
|
|
217
234
|
# Tag the repo
|
|
218
235
|
if platform == "win32":
|
|
219
236
|
response = subprocess.run([
|
|
220
|
-
'docker', 'tag',
|
|
237
|
+
'docker', 'tag', self.input_tag, full_ecr_repo_url
|
|
221
238
|
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
222
239
|
else:
|
|
223
240
|
response = subprocess.run([
|
|
224
|
-
f"docker tag {
|
|
241
|
+
f"docker tag {self.input_tag} {full_ecr_repo_url}"
|
|
225
242
|
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
226
243
|
if not response.returncode:
|
|
227
244
|
return snapctl_success('BYOSnap tag successful',
|
|
@@ -298,6 +315,7 @@ class ByoSnap:
|
|
|
298
315
|
SNAPCTL_INPUT_ERROR
|
|
299
316
|
)
|
|
300
317
|
for profile in ['dev_template', 'stage_template', 'prod_template']:
|
|
318
|
+
# Currently, not checking for 'min_replicas' not in profile_data[profile]
|
|
301
319
|
if 'cpu' not in profile_data[profile] or \
|
|
302
320
|
'memory' not in profile_data[profile] or \
|
|
303
321
|
'cmd' not in profile_data[profile] or \
|
|
@@ -310,13 +328,25 @@ class ByoSnap:
|
|
|
310
328
|
if profile_data[profile]['cpu'] not in ByoSnap.VALID_CPU_MARKS:
|
|
311
329
|
snapctl_error(
|
|
312
330
|
'Invalid CPU value in BYOSnap profile. '
|
|
313
|
-
f'Valid values are {
|
|
331
|
+
f'Valid values are {
|
|
332
|
+
", ".join(map(str, ByoSnap.VALID_CPU_MARKS))}',
|
|
314
333
|
SNAPCTL_INPUT_ERROR
|
|
315
334
|
)
|
|
316
335
|
if profile_data[profile]['memory'] not in ByoSnap.VALID_MEMORY_MARKS:
|
|
317
336
|
snapctl_error(
|
|
318
337
|
'Invalid Memory value in BYOSnap profile. '
|
|
319
|
-
f'Valid values are {
|
|
338
|
+
f'Valid values are {
|
|
339
|
+
", ".join(map(str, ByoSnap.VALID_MEMORY_MARKS))}',
|
|
340
|
+
SNAPCTL_INPUT_ERROR
|
|
341
|
+
)
|
|
342
|
+
if 'min_replicas' in profile_data[profile] and \
|
|
343
|
+
(not isinstance(profile_data[profile]['min_replicas'], int) or
|
|
344
|
+
int(profile_data[profile]['min_replicas']) < 0 or
|
|
345
|
+
int(profile_data[profile]['min_replicas']) > ByoSnap.MAX_MIN_REPLICAS):
|
|
346
|
+
snapctl_error(
|
|
347
|
+
'Invalid Min Replicas value in BYOSnap profile. '
|
|
348
|
+
f'Minimum replicas should be between 0 and {
|
|
349
|
+
ByoSnap.MAX_MIN_REPLICAS}',
|
|
320
350
|
SNAPCTL_INPUT_ERROR
|
|
321
351
|
)
|
|
322
352
|
|
|
@@ -340,13 +370,15 @@ class ByoSnap:
|
|
|
340
370
|
# Validate the SID
|
|
341
371
|
if not self.sid.startswith(ByoSnap.ID_PREFIX):
|
|
342
372
|
snapctl_error(
|
|
343
|
-
f"Invalid Snap ID. Valid Snap IDs start with {
|
|
373
|
+
f"Invalid Snap ID. Valid Snap IDs start with {
|
|
374
|
+
ByoSnap.ID_PREFIX}.",
|
|
344
375
|
SNAPCTL_INPUT_ERROR
|
|
345
376
|
)
|
|
346
377
|
if len(self.sid) > ByoSnap.SID_CHARACTER_LIMIT:
|
|
347
378
|
snapctl_error(
|
|
348
379
|
"Invalid Snap ID. "
|
|
349
|
-
f"Snap ID should be less than {
|
|
380
|
+
f"Snap ID should be less than {
|
|
381
|
+
ByoSnap.SID_CHARACTER_LIMIT} characters",
|
|
350
382
|
SNAPCTL_INPUT_ERROR
|
|
351
383
|
)
|
|
352
384
|
# Validation for subcommands
|
|
@@ -372,7 +404,7 @@ class ByoSnap:
|
|
|
372
404
|
if self.token_parts is None:
|
|
373
405
|
snapctl_error('Invalid token. Please reach out to your support team.',
|
|
374
406
|
SNAPCTL_INPUT_ERROR)
|
|
375
|
-
if self.subcommand in ['
|
|
407
|
+
if self.subcommand in ['publish-image']:
|
|
376
408
|
if not self.input_tag:
|
|
377
409
|
snapctl_error(
|
|
378
410
|
"Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
|
|
@@ -383,13 +415,14 @@ class ByoSnap:
|
|
|
383
415
|
f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
|
|
384
416
|
SNAPCTL_INPUT_ERROR
|
|
385
417
|
)
|
|
386
|
-
if not self.path:
|
|
418
|
+
if not self.skip_build and not self.path:
|
|
387
419
|
snapctl_error("Missing required parameter: path",
|
|
388
420
|
SNAPCTL_INPUT_ERROR)
|
|
389
421
|
# Check path
|
|
390
|
-
if not os.path.isfile(f"{self.path}/{self.dockerfile}"):
|
|
422
|
+
if not self.skip_build and not os.path.isfile(f"{self.path}/{self.dockerfile}"):
|
|
391
423
|
snapctl_error(
|
|
392
|
-
f"Unable to find {
|
|
424
|
+
f"Unable to find {
|
|
425
|
+
self.dockerfile} at path {self.path}",
|
|
393
426
|
SNAPCTL_INPUT_ERROR)
|
|
394
427
|
# elif self.subcommand == 'push':
|
|
395
428
|
# if not self.input_tag:
|
|
@@ -403,7 +436,8 @@ class ByoSnap:
|
|
|
403
436
|
if not self.input_tag:
|
|
404
437
|
snapctl_error(
|
|
405
438
|
"Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
|
|
406
|
-
if len(self.input_tag.split()) > 1 or
|
|
439
|
+
if len(self.input_tag.split()) > 1 or \
|
|
440
|
+
len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
|
|
407
441
|
snapctl_error(
|
|
408
442
|
"Tag should be a single word with maximum of "
|
|
409
443
|
f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
|
|
@@ -429,6 +463,18 @@ class ByoSnap:
|
|
|
429
463
|
if not self.http_port.isdigit():
|
|
430
464
|
snapctl_error("Ingress HTTP Port should be a number",
|
|
431
465
|
SNAPCTL_INPUT_ERROR)
|
|
466
|
+
if self.readiness_path is not None:
|
|
467
|
+
if self.readiness_path.strip() == '':
|
|
468
|
+
snapctl_error("Readiness path cannot be empty",
|
|
469
|
+
SNAPCTL_INPUT_ERROR)
|
|
470
|
+
if not self.readiness_path.strip().startswith('/'):
|
|
471
|
+
snapctl_error("Readiness path has to start with /",
|
|
472
|
+
SNAPCTL_INPUT_ERROR)
|
|
473
|
+
if self.readiness_delay is not None:
|
|
474
|
+
if self.readiness_delay < 0 or \
|
|
475
|
+
self.readiness_delay > ByoSnap.MAX_READINESS_TIMEOUT:
|
|
476
|
+
snapctl_error("Readiness delay should be between 0 "
|
|
477
|
+
f"and {ByoSnap.MAX_READINESS_TIMEOUT}", SNAPCTL_INPUT_ERROR)
|
|
432
478
|
# Check byosnap_profile path
|
|
433
479
|
self._validate_byosnap_profile()
|
|
434
480
|
|
|
@@ -493,7 +539,8 @@ class ByoSnap:
|
|
|
493
539
|
info('Unable to upload your swagger.json')
|
|
494
540
|
except RequestException as e:
|
|
495
541
|
info(
|
|
496
|
-
f'Exception: Unable to find swagger.json at {
|
|
542
|
+
f'Exception: Unable to find swagger.json at {
|
|
543
|
+
self.path} {e}'
|
|
497
544
|
)
|
|
498
545
|
else:
|
|
499
546
|
info(f'No swagger.json found at {self.path}'
|
|
@@ -559,7 +606,8 @@ class ByoSnap:
|
|
|
559
606
|
if "api_error_code" in response_json and "message" in response_json:
|
|
560
607
|
if response_json['api_error_code'] == HTTP_ERROR_SERVICE_VERSION_EXISTS:
|
|
561
608
|
snapctl_error(
|
|
562
|
-
f'BYOSnap {
|
|
609
|
+
f'BYOSnap {
|
|
610
|
+
self.name} already exists. Please use a different name',
|
|
563
611
|
SNAPCTL_BYOSNAP_CREATE_DUPLICATE_NAME_ERROR,
|
|
564
612
|
progress
|
|
565
613
|
)
|
|
@@ -592,10 +640,14 @@ class ByoSnap:
|
|
|
592
640
|
"""
|
|
593
641
|
self._check_dependencies()
|
|
594
642
|
self._docker_login()
|
|
595
|
-
self.
|
|
643
|
+
if not self.skip_build:
|
|
644
|
+
self._docker_build()
|
|
645
|
+
else:
|
|
646
|
+
info('--skip-build set. Skipping the build step.')
|
|
596
647
|
self._docker_tag()
|
|
597
648
|
self._docker_push()
|
|
598
|
-
self.
|
|
649
|
+
if self.path is not None:
|
|
650
|
+
self.upload_docs()
|
|
599
651
|
snapctl_success('BYOSNAP publish successful')
|
|
600
652
|
|
|
601
653
|
def publish_version(self) -> None:
|
|
@@ -622,6 +674,10 @@ class ByoSnap:
|
|
|
622
674
|
"image_tag": self.input_tag,
|
|
623
675
|
"base_url": f"{self.prefix}/{self.sid}",
|
|
624
676
|
"http_port": self.http_port,
|
|
677
|
+
"readiness_probe_config": {
|
|
678
|
+
"path": self.readiness_path,
|
|
679
|
+
"initial_delay_seconds": self.readiness_delay
|
|
680
|
+
},
|
|
625
681
|
"dev_template": profile_data['dev_template'],
|
|
626
682
|
"stage_template": profile_data['stage_template'],
|
|
627
683
|
"prod_template": profile_data['prod_template']
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Generate CLI commands
|
|
3
|
+
"""
|
|
4
|
+
import base64
|
|
5
|
+
from binascii import Error as BinasciiError
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from typing import Union
|
|
9
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
10
|
+
from snapctl.config.constants import SNAPCTL_INPUT_ERROR, \
|
|
11
|
+
SNAPCTL_GENERATE_GENERIC_ERROR, SNAPCTL_GENERATE_PROFILE_ERROR, \
|
|
12
|
+
SNAPCTL_GENERATE_CREDENTIALS_ERROR
|
|
13
|
+
from snapctl.config.hashes import BYOSNAP_TEMPLATE
|
|
14
|
+
from snapctl.utils.helper import get_composite_token, snapctl_error, snapctl_success
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Generate:
|
|
18
|
+
"""
|
|
19
|
+
Generate CLI commands
|
|
20
|
+
"""
|
|
21
|
+
SUBCOMMANDS = ['byosnap-profile', 'profile', 'credentials']
|
|
22
|
+
DEPRECATED_SOON_SUBCOMMANDS = ['byosnap-profile']
|
|
23
|
+
BYOSNAP_PROFILE_FN = 'snapser-byosnap-profile.json'
|
|
24
|
+
ECR_TOKEN_FN = 'snapser-ecr-credentials.json'
|
|
25
|
+
CATEGORIES = {
|
|
26
|
+
'profile': ['byosnap'],
|
|
27
|
+
'credentials': ['ecr']
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self, subcommand: str, base_url: str, api_key: str | None,
|
|
32
|
+
category: str | None,
|
|
33
|
+
out_path: Union[str, None]
|
|
34
|
+
) -> None:
|
|
35
|
+
self.subcommand: str = subcommand
|
|
36
|
+
self.base_url: str = base_url
|
|
37
|
+
self.api_key: str = api_key
|
|
38
|
+
self.category: str | None = category
|
|
39
|
+
self.out_path: Union[str, None] = out_path
|
|
40
|
+
# Validate input
|
|
41
|
+
self.validate_input()
|
|
42
|
+
|
|
43
|
+
# Private methods
|
|
44
|
+
@staticmethod
|
|
45
|
+
def _get_token_values(token: str) -> None | list:
|
|
46
|
+
"""
|
|
47
|
+
Get the token values
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
input_token = base64.b64decode(token).decode('ascii')
|
|
51
|
+
token_parts = input_token.split('|')
|
|
52
|
+
# url|web_app_token|service_id|ecr_repo_url|ecr_repo_username|ecr_repo_token
|
|
53
|
+
if len(token_parts) >= 3:
|
|
54
|
+
return token_parts
|
|
55
|
+
except BinasciiError:
|
|
56
|
+
pass
|
|
57
|
+
return None
|
|
58
|
+
# Validator
|
|
59
|
+
|
|
60
|
+
def validate_input(self) -> None:
|
|
61
|
+
"""
|
|
62
|
+
Validator
|
|
63
|
+
"""
|
|
64
|
+
if self.subcommand not in Generate.SUBCOMMANDS:
|
|
65
|
+
snapctl_error(
|
|
66
|
+
f"Invalid command {self.subcommand}. Valid command are "
|
|
67
|
+
f"{Generate.SUBCOMMANDS}",
|
|
68
|
+
SNAPCTL_INPUT_ERROR
|
|
69
|
+
)
|
|
70
|
+
# Check path
|
|
71
|
+
if self.subcommand == 'profile':
|
|
72
|
+
if self.category not in Generate.CATEGORIES['profile']:
|
|
73
|
+
snapctl_error(
|
|
74
|
+
f"Invalid category {self.category}. Valid category are "
|
|
75
|
+
f"{Generate.CATEGORIES['profile']}",
|
|
76
|
+
SNAPCTL_INPUT_ERROR
|
|
77
|
+
)
|
|
78
|
+
if not self.out_path:
|
|
79
|
+
snapctl_error(
|
|
80
|
+
"Path is required for profile generation",
|
|
81
|
+
SNAPCTL_INPUT_ERROR
|
|
82
|
+
)
|
|
83
|
+
elif self.subcommand == 'credentials':
|
|
84
|
+
if self.category not in Generate.CATEGORIES['credentials']:
|
|
85
|
+
snapctl_error(
|
|
86
|
+
f"Invalid category {self.category}. Valid category are "
|
|
87
|
+
f"{Generate.CATEGORIES['credentials']}",
|
|
88
|
+
SNAPCTL_INPUT_ERROR
|
|
89
|
+
)
|
|
90
|
+
if not self.out_path:
|
|
91
|
+
snapctl_error(
|
|
92
|
+
"Path is required for token generation",
|
|
93
|
+
SNAPCTL_INPUT_ERROR
|
|
94
|
+
)
|
|
95
|
+
# Now confirm that out-path is valid
|
|
96
|
+
if self.out_path and not os.path.isdir(self.out_path):
|
|
97
|
+
snapctl_error(
|
|
98
|
+
f"Invalid path {self.out_path}. Wont be able to "
|
|
99
|
+
"store the output file",
|
|
100
|
+
SNAPCTL_INPUT_ERROR
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
def byosnap_profile(self) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Generate snapser-byosnap-profile.json
|
|
106
|
+
"""
|
|
107
|
+
progress = Progress(
|
|
108
|
+
SpinnerColumn(),
|
|
109
|
+
TextColumn("[progress.description]{task.description}"),
|
|
110
|
+
transient=True,
|
|
111
|
+
)
|
|
112
|
+
progress.start()
|
|
113
|
+
progress.add_task(
|
|
114
|
+
description='Generating BYOSnap profile...', total=None)
|
|
115
|
+
try:
|
|
116
|
+
if self.out_path is not None:
|
|
117
|
+
file_save_path = os.path.join(
|
|
118
|
+
self.out_path, Generate.BYOSNAP_PROFILE_FN)
|
|
119
|
+
else:
|
|
120
|
+
file_save_path = os.path.join(
|
|
121
|
+
os.getcwd(), Generate.BYOSNAP_PROFILE_FN)
|
|
122
|
+
file_written = False
|
|
123
|
+
with open(file_save_path, "w") as file:
|
|
124
|
+
json.dump(BYOSNAP_TEMPLATE, file, indent=4)
|
|
125
|
+
file_written = True
|
|
126
|
+
if file_written:
|
|
127
|
+
snapctl_success(
|
|
128
|
+
"BYOSNAP Profile generation successful. "
|
|
129
|
+
f"{Generate.BYOSNAP_PROFILE_FN} saved at {file_save_path}",
|
|
130
|
+
progress
|
|
131
|
+
)
|
|
132
|
+
except (IOError, OSError) as file_error:
|
|
133
|
+
snapctl_error(f"File error: {file_error}",
|
|
134
|
+
SNAPCTL_GENERATE_PROFILE_ERROR, progress)
|
|
135
|
+
except json.JSONDecodeError as json_error:
|
|
136
|
+
snapctl_error(f"JSON error: {json_error}",
|
|
137
|
+
SNAPCTL_GENERATE_PROFILE_ERROR, progress)
|
|
138
|
+
snapctl_error(
|
|
139
|
+
"Failed to generate BYOSNAP Profile",
|
|
140
|
+
SNAPCTL_GENERATE_PROFILE_ERROR,
|
|
141
|
+
progress
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def profile(self) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Generate profile
|
|
147
|
+
"""
|
|
148
|
+
if self.category == 'byosnap':
|
|
149
|
+
self.byosnap_profile()
|
|
150
|
+
|
|
151
|
+
def ecr_credentials(self) -> None:
|
|
152
|
+
"""
|
|
153
|
+
Generate credentials
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
progress = Progress(
|
|
157
|
+
SpinnerColumn(),
|
|
158
|
+
TextColumn("[progress.description]{task.description}"),
|
|
159
|
+
transient=True,
|
|
160
|
+
)
|
|
161
|
+
progress.start()
|
|
162
|
+
progress.add_task(
|
|
163
|
+
description='Generating ECR credentials...', total=None)
|
|
164
|
+
try:
|
|
165
|
+
composite_token: Union[str, None] = get_composite_token(
|
|
166
|
+
self.base_url, self.api_key, 'byogs', {'service_id': 'byogs'}
|
|
167
|
+
)
|
|
168
|
+
token_parts = Generate._get_token_values(composite_token)
|
|
169
|
+
# url|web_app_token|service_id|ecr_repo_url|ecr_repo_username|ecr_repo_token
|
|
170
|
+
if token_parts is None or len(token_parts) != 4:
|
|
171
|
+
snapctl_error(
|
|
172
|
+
"Unable to retrieve token.",
|
|
173
|
+
SNAPCTL_GENERATE_GENERIC_ERROR,
|
|
174
|
+
progress
|
|
175
|
+
)
|
|
176
|
+
token_details = {
|
|
177
|
+
'ecr_repo_url': token_parts[0],
|
|
178
|
+
'ecr_repo_username': token_parts[1],
|
|
179
|
+
'ecr_repo_token': token_parts[2],
|
|
180
|
+
'ecr_repo_platform': token_parts[3]
|
|
181
|
+
}
|
|
182
|
+
if self.out_path is not None:
|
|
183
|
+
file_save_path = os.path.join(
|
|
184
|
+
self.out_path, Generate.ECR_TOKEN_FN)
|
|
185
|
+
else:
|
|
186
|
+
file_save_path = os.path.join(
|
|
187
|
+
os.getcwd(), Generate.ECR_TOKEN_FN)
|
|
188
|
+
file_written = False
|
|
189
|
+
with open(file_save_path, "w") as file:
|
|
190
|
+
json.dump(token_details, file, indent=4)
|
|
191
|
+
file_written = True
|
|
192
|
+
if file_written:
|
|
193
|
+
snapctl_success(
|
|
194
|
+
"ECR Token generation successful. "
|
|
195
|
+
f"{Generate.ECR_TOKEN_FN} saved at {file_save_path}",
|
|
196
|
+
progress
|
|
197
|
+
)
|
|
198
|
+
except (IOError, OSError) as file_error:
|
|
199
|
+
snapctl_error(f"File error: {file_error}",
|
|
200
|
+
SNAPCTL_GENERATE_CREDENTIALS_ERROR, progress)
|
|
201
|
+
except json.JSONDecodeError as json_error:
|
|
202
|
+
snapctl_error(f"JSON error: {json_error}",
|
|
203
|
+
SNAPCTL_GENERATE_CREDENTIALS_ERROR, progress)
|
|
204
|
+
snapctl_error(
|
|
205
|
+
"Failed to generate Token",
|
|
206
|
+
SNAPCTL_GENERATE_CREDENTIALS_ERROR,
|
|
207
|
+
progress
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
def credentials(self):
|
|
211
|
+
"""
|
|
212
|
+
Generate credentials
|
|
213
|
+
"""
|
|
214
|
+
if self.category == 'ecr':
|
|
215
|
+
self.ecr_credentials()
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Constants used by snapctl
|
|
3
3
|
"""
|
|
4
4
|
COMPANY_NAME = 'Snapser'
|
|
5
|
-
VERSION = '0.
|
|
5
|
+
VERSION = '0.38.0'
|
|
6
6
|
CONFIG_FILE_MAC = '~/.snapser/config'
|
|
7
7
|
CONFIG_FILE_WIN = '%homepath%\\.snapser\\config'
|
|
8
8
|
|
|
@@ -88,4 +88,5 @@ SNAPCTL_SNAPEND_STATE_ERROR = 75
|
|
|
88
88
|
|
|
89
89
|
# Generate Errors
|
|
90
90
|
SNAPCTL_GENERATE_GENERIC_ERROR = 80
|
|
91
|
-
|
|
91
|
+
SNAPCTL_GENERATE_PROFILE_ERROR = 81
|
|
92
|
+
SNAPCTL_GENERATE_CREDENTIALS_ERROR = 82
|
|
@@ -149,23 +149,49 @@ SNAPEND_MANIFEST_TYPES: dict[str, dict[str, str]] = {
|
|
|
149
149
|
},
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
SERVICE_IDS = [
|
|
152
|
+
SERVICE_IDS: list[str] = [
|
|
153
153
|
'analytics', 'auth', 'client-logs', 'events', 'experiments', 'gdpr', 'guilds', 'hades', 'iap',
|
|
154
154
|
'inventory', 'leaderboards', 'matchmaking', 'notifications', 'parties', 'profiles', 'quests',
|
|
155
155
|
'relay', 'remote-config', 'scheduler', 'sequencer', 'social-graph', 'statistics', 'storage',
|
|
156
156
|
'trackables', 'xp'
|
|
157
157
|
]
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
DEFAULT_BYOSNAP_DEV_TEMPLATE = {
|
|
160
160
|
'cpu': 100,
|
|
161
161
|
'memory': 0.125,
|
|
162
|
+
'min_replicas': 1,
|
|
162
163
|
'cmd': '',
|
|
163
164
|
'args': [],
|
|
164
|
-
'env_params': []
|
|
165
|
+
'env_params': [{'key': "SNAPSER_ENVIRONMENT", 'value': "DEVELOPMENT"}]
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
DEFAULT_BYOSNAP_STAGE_TEMPLATE = {
|
|
169
|
+
'cpu': 100,
|
|
170
|
+
'memory': 0.125,
|
|
171
|
+
'min_replicas': 1,
|
|
172
|
+
'cmd': '',
|
|
173
|
+
'args': [],
|
|
174
|
+
'env_params': [{'key': "SNAPSER_ENVIRONMENT", 'value': "STAGING"}]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
DEFAULT_BYOSNAP_PROD_TEMPLATE = {
|
|
178
|
+
'cpu': 100,
|
|
179
|
+
'memory': 0.125,
|
|
180
|
+
'min_replicas': 2,
|
|
181
|
+
'cmd': '',
|
|
182
|
+
'args': [],
|
|
183
|
+
'env_params': [{'key': "SNAPSER_ENVIRONMENT", 'value': "PRODUCTION"}]
|
|
165
184
|
}
|
|
166
185
|
|
|
167
186
|
BYOSNAP_TEMPLATE = {
|
|
168
|
-
'dev_template':
|
|
169
|
-
'stage_template':
|
|
170
|
-
'prod_template':
|
|
187
|
+
'dev_template': DEFAULT_BYOSNAP_DEV_TEMPLATE,
|
|
188
|
+
'stage_template': DEFAULT_BYOSNAP_STAGE_TEMPLATE,
|
|
189
|
+
'prod_template': DEFAULT_BYOSNAP_PROD_TEMPLATE
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
ARCHITECTURE_MAPPING: dict[str, str] = {
|
|
193
|
+
'x86_64': 'amd64',
|
|
194
|
+
'arm64': 'arm64',
|
|
195
|
+
'aarch64': 'arm64',
|
|
196
|
+
'amd64': 'amd64'
|
|
171
197
|
}
|
|
@@ -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 "{
|
|
79
|
+
f'Trying to extract API KEY from "{
|
|
80
|
+
config_file_path}:profile {profile}"'
|
|
80
81
|
)
|
|
81
82
|
result['value'] = config.get(
|
|
82
83
|
config_profile, extract_key, fallback=None, raw=True
|
|
@@ -203,7 +204,7 @@ def version_callback(value: bool = True):
|
|
|
203
204
|
def common(
|
|
204
205
|
ctx: typer.Context,
|
|
205
206
|
version: bool = typer.Option(
|
|
206
|
-
None, "--version",
|
|
207
|
+
None, "--version", "-v",
|
|
207
208
|
help="Get the Snapctl version.",
|
|
208
209
|
callback=version_callback
|
|
209
210
|
),
|
|
@@ -257,6 +258,9 @@ def byogs(
|
|
|
257
258
|
docker_file: str = typer.Option(
|
|
258
259
|
"Dockerfile", help="Dockerfile name to use"
|
|
259
260
|
),
|
|
261
|
+
skip_build: bool = typer.Option(
|
|
262
|
+
False, "--skip-build", help="(optional: publish) Skip the build step. You have to pass the image tag you used during the build step."
|
|
263
|
+
),
|
|
260
264
|
# overrides
|
|
261
265
|
api_key: Union[str, None] = typer.Option(
|
|
262
266
|
None, "--api-key", help="API Key override.", callback=api_key_context_callback
|
|
@@ -271,7 +275,7 @@ def byogs(
|
|
|
271
275
|
validate_command_context(ctx)
|
|
272
276
|
byogs_obj: ByoGs = ByoGs(
|
|
273
277
|
subcommand, ctx.obj['base_url'], ctx.obj['api_key'],
|
|
274
|
-
tag, path, docker_file,
|
|
278
|
+
tag, path, docker_file, skip_build
|
|
275
279
|
)
|
|
276
280
|
getattr(byogs_obj, subcommand.replace('-', '_'))()
|
|
277
281
|
success(f"BYOGs {subcommand} complete")
|
|
@@ -306,12 +310,12 @@ def byosnap(
|
|
|
306
310
|
# publish-image and publish-version
|
|
307
311
|
tag: str = typer.Option(
|
|
308
312
|
None, "--tag", help=(
|
|
309
|
-
"(req:
|
|
313
|
+
"(req: publish-image and publish-version) Tag for your snap"
|
|
310
314
|
)
|
|
311
315
|
),
|
|
312
316
|
# publish-image
|
|
313
317
|
path: Union[str, None] = typer.Option(
|
|
314
|
-
None, "--path", help="(req:
|
|
318
|
+
None, "--path", help="(req: publish-image) Path to your snap code"
|
|
315
319
|
),
|
|
316
320
|
docker_file: str = typer.Option(
|
|
317
321
|
"Dockerfile", help="Dockerfile name to use"
|
|
@@ -329,9 +333,18 @@ def byosnap(
|
|
|
329
333
|
),
|
|
330
334
|
byosnap_profile: Union[str, None] = typer.Option(
|
|
331
335
|
None, "--byosnap-profile", help=(
|
|
332
|
-
"(req: publish-version) Path to your byosnap-profile JSON file"
|
|
336
|
+
"(req: publish-version) Path to your byosnap-profile JSON file. You can generate a base version of this file using the `snapctl generate profile --category byosnap --out-path <output_path>` command."
|
|
333
337
|
)
|
|
334
338
|
),
|
|
339
|
+
skip_build: bool = typer.Option(
|
|
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."
|
|
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
|
+
),
|
|
335
348
|
# overrides
|
|
336
349
|
api_key: Union[str, None] = typer.Option(
|
|
337
350
|
None, "--api-key", help="API Key override.", callback=api_key_context_callback
|
|
@@ -347,7 +360,8 @@ def byosnap(
|
|
|
347
360
|
byosnap_obj: ByoSnap = ByoSnap(
|
|
348
361
|
subcommand, ctx.obj['base_url'], ctx.obj['api_key'], sid,
|
|
349
362
|
name, desc, platform_type, language, tag, path, docker_file,
|
|
350
|
-
prefix, version, http_port, byosnap_profile
|
|
363
|
+
prefix, version, http_port, byosnap_profile, skip_build,
|
|
364
|
+
readiness_path, readiness_delay
|
|
351
365
|
)
|
|
352
366
|
getattr(byosnap_obj, subcommand.replace('-', '_'))()
|
|
353
367
|
success(f"BYOSnap {subcommand} complete")
|
|
@@ -390,11 +404,28 @@ def generate(
|
|
|
390
404
|
ctx: typer.Context,
|
|
391
405
|
# Required fields
|
|
392
406
|
subcommand: str = typer.Argument(
|
|
393
|
-
..., help=
|
|
407
|
+
..., help=(
|
|
408
|
+
"Generate Subcommands: " + \
|
|
409
|
+
", ".join(Generate.SUBCOMMANDS) + "." + " "
|
|
410
|
+
"Deprecation Notice: " + \
|
|
411
|
+
",".join(Generate.DEPRECATED_SOON_SUBCOMMANDS) + \
|
|
412
|
+
" will be deprecated soon. "
|
|
413
|
+
"Use `snapctl generate profile --category byosnap --out-path <output_path>` command instead."
|
|
414
|
+
)
|
|
394
415
|
),
|
|
395
|
-
|
|
416
|
+
category: Union[str, None] = typer.Option(
|
|
417
|
+
None, "--category",
|
|
418
|
+
help=(
|
|
419
|
+
"(req: profile, token) (profile: " +
|
|
420
|
+
", ".join(Generate.CATEGORIES['profile']) +
|
|
421
|
+
") (token: " + ", ".join(Generate.CATEGORIES['credentials']) + ')'
|
|
422
|
+
)
|
|
423
|
+
),
|
|
424
|
+
# byosnap-profile, profile
|
|
396
425
|
out_path: Union[str, None] = typer.Option(
|
|
397
|
-
None, "--out-path", help=
|
|
426
|
+
None, "--out-path", help=(
|
|
427
|
+
"(req: byosnap-profile, profile, token) Path to output the byosnap profile"
|
|
428
|
+
)
|
|
398
429
|
),
|
|
399
430
|
# overrides
|
|
400
431
|
api_key: Union[str, None] = typer.Option(
|
|
@@ -410,14 +441,14 @@ def generate(
|
|
|
410
441
|
validate_command_context(ctx)
|
|
411
442
|
generate_obj: Generate = Generate(
|
|
412
443
|
subcommand, ctx.obj['base_url'], ctx.obj['api_key'],
|
|
413
|
-
out_path,
|
|
444
|
+
category, out_path,
|
|
414
445
|
)
|
|
415
446
|
getattr(generate_obj, subcommand.replace('-', '_'))()
|
|
416
447
|
success(f"Generate {subcommand} complete")
|
|
417
448
|
raise typer.Exit(code=SNAPCTL_SUCCESS)
|
|
418
449
|
|
|
419
450
|
|
|
420
|
-
@app.command()
|
|
451
|
+
@ app.command()
|
|
421
452
|
def snapend(
|
|
422
453
|
ctx: typer.Context,
|
|
423
454
|
# Required fields
|
|
@@ -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,18 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Helper functions for snapctl
|
|
3
3
|
"""
|
|
4
|
+
import re
|
|
4
5
|
import requests
|
|
5
6
|
import typer
|
|
6
7
|
from requests.exceptions import RequestException
|
|
7
8
|
from rich.progress import Progress
|
|
8
9
|
from snapctl.config.constants import HTTP_NOT_FOUND, HTTP_FORBIDDEN, HTTP_UNAUTHORIZED, \
|
|
9
10
|
SERVER_CALL_TIMEOUT, SNAPCTL_CONFIGURATION_ERROR, SNAPCTL_SUCCESS
|
|
11
|
+
from snapctl.config.hashes import ARCHITECTURE_MAPPING
|
|
10
12
|
from snapctl.utils.echo import error, success
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
def validate_api_key(base_url: str, api_key: str | None):
|
|
15
|
+
def validate_api_key(base_url: str, api_key: str | None) -> bool:
|
|
14
16
|
"""
|
|
15
17
|
This function validates the API Key
|
|
16
18
|
"""
|
|
@@ -74,6 +76,63 @@ def get_composite_token(base_url: str, api_key: str | None, action: str, params:
|
|
|
74
76
|
return res.json()['token']
|
|
75
77
|
|
|
76
78
|
|
|
79
|
+
def check_dockerfile_architecture(dockerfile_path: str, system_arch: str) -> dict[str, object]:
|
|
80
|
+
"""
|
|
81
|
+
Check the Dockerfile for architecture specific commands
|
|
82
|
+
"""
|
|
83
|
+
response = {
|
|
84
|
+
'error': False,
|
|
85
|
+
'message': ''
|
|
86
|
+
}
|
|
87
|
+
# Normalize system architecture
|
|
88
|
+
system_arch = ARCHITECTURE_MAPPING.get(system_arch, system_arch)
|
|
89
|
+
try:
|
|
90
|
+
lines = []
|
|
91
|
+
with open(dockerfile_path, 'r') as file:
|
|
92
|
+
lines = file.readlines()
|
|
93
|
+
for line_number, line in enumerate(lines, 1):
|
|
94
|
+
if line.startswith('#'):
|
|
95
|
+
continue
|
|
96
|
+
# Checking various build and run commands for architecture specifics
|
|
97
|
+
patterns = [
|
|
98
|
+
# FROM with platform
|
|
99
|
+
r'FROM --platform=linux/(\w+)',
|
|
100
|
+
# dotnet runtime
|
|
101
|
+
r'-r linux-(\w+)',
|
|
102
|
+
# Build args specifying arch
|
|
103
|
+
r'--build-arg ARCH=(\w+)',
|
|
104
|
+
# Environment variables setting arch
|
|
105
|
+
r'ENV ARCH=(\w+)',
|
|
106
|
+
# cmake specifying arch
|
|
107
|
+
r'cmake.*?-DARCH=(\w+)',
|
|
108
|
+
# make specifying arch
|
|
109
|
+
r'make.*?ARCH=(\w+)'
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
for pattern in patterns:
|
|
113
|
+
match = re.search(pattern, line)
|
|
114
|
+
if match and ARCHITECTURE_MAPPING.get(match.group(1)) != system_arch:
|
|
115
|
+
response['error'] = True
|
|
116
|
+
response['message'] = (
|
|
117
|
+
f'[Architecture Mismatch] Line {line_number}: "{
|
|
118
|
+
line.strip()}" '
|
|
119
|
+
f' of Dockerfile {dockerfile_path}'
|
|
120
|
+
f' specifies architecture {
|
|
121
|
+
match.group(1)}, which does not match the '
|
|
122
|
+
f' systems ({system_arch}).'
|
|
123
|
+
)
|
|
124
|
+
return response
|
|
125
|
+
except FileNotFoundError:
|
|
126
|
+
response['error'] = True
|
|
127
|
+
response['message'] = f'Dockerfile not found at {dockerfile_path}'
|
|
128
|
+
return response
|
|
129
|
+
except Exception as e:
|
|
130
|
+
response['error'] = True
|
|
131
|
+
response['message'] = f'Exception {e}'
|
|
132
|
+
return response
|
|
133
|
+
return response
|
|
134
|
+
|
|
135
|
+
|
|
77
136
|
def snapctl_success(message: str, progress: Progress | None = None, no_exit: bool = False):
|
|
78
137
|
"""
|
|
79
138
|
This function exits the snapctl
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Generate CLI commands
|
|
3
|
-
"""
|
|
4
|
-
import json
|
|
5
|
-
import os
|
|
6
|
-
from typing import Union
|
|
7
|
-
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
8
|
-
from snapctl.config.constants import SNAPCTL_INPUT_ERROR, \
|
|
9
|
-
SNAPCTL_GENERATE_GENERIC_ERROR
|
|
10
|
-
from snapctl.config.hashes import BYOSNAP_TEMPLATE
|
|
11
|
-
from snapctl.utils.echo import error
|
|
12
|
-
from snapctl.utils.helper import snapctl_error, snapctl_success
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Generate:
|
|
16
|
-
"""
|
|
17
|
-
Generate CLI commands
|
|
18
|
-
"""
|
|
19
|
-
SUBCOMMANDS = ['byosnap-profile']
|
|
20
|
-
BYOSNAP_PROFILE_FN = 'snapser-byosnap-profile.json'
|
|
21
|
-
|
|
22
|
-
def __init__(
|
|
23
|
-
self, subcommand: str, base_url: str, api_key: str | None,
|
|
24
|
-
out_path: Union[str, None]
|
|
25
|
-
) -> None:
|
|
26
|
-
self.subcommand: str = subcommand
|
|
27
|
-
self.base_url: str = base_url
|
|
28
|
-
self.api_key: str = api_key
|
|
29
|
-
self.out_path: Union[str, None] = out_path
|
|
30
|
-
# Validate input
|
|
31
|
-
self.validate_input()
|
|
32
|
-
|
|
33
|
-
# Validator
|
|
34
|
-
|
|
35
|
-
def validate_input(self) -> None:
|
|
36
|
-
"""
|
|
37
|
-
Validator
|
|
38
|
-
"""
|
|
39
|
-
if self.subcommand not in Generate.SUBCOMMANDS:
|
|
40
|
-
snapctl_error(
|
|
41
|
-
f"Invalid command {self.subcommand}. Valid command are "
|
|
42
|
-
f"{Generate.SUBCOMMANDS}",
|
|
43
|
-
SNAPCTL_INPUT_ERROR
|
|
44
|
-
)
|
|
45
|
-
if self.subcommand in ['byosnap-profile']:
|
|
46
|
-
# Check path
|
|
47
|
-
if self.out_path and not os.path.isdir(self.out_path):
|
|
48
|
-
snapctl_error(
|
|
49
|
-
f"Invalid path {self.out_path}. Wont be able to "
|
|
50
|
-
"store the byosnap-profile.json file",
|
|
51
|
-
SNAPCTL_INPUT_ERROR
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
def byosnap_profile(self) -> None:
|
|
55
|
-
"""
|
|
56
|
-
Generate snapser-byosnap-profile.json
|
|
57
|
-
"""
|
|
58
|
-
progress = Progress(
|
|
59
|
-
SpinnerColumn(),
|
|
60
|
-
TextColumn("[progress.description]{task.description}"),
|
|
61
|
-
transient=True,
|
|
62
|
-
)
|
|
63
|
-
progress.start()
|
|
64
|
-
progress.add_task(
|
|
65
|
-
description='Promoting your staging snapend...', total=None)
|
|
66
|
-
try:
|
|
67
|
-
if self.out_path is not None:
|
|
68
|
-
file_save_path = os.path.join(
|
|
69
|
-
self.out_path, Generate.BYOSNAP_PROFILE_FN)
|
|
70
|
-
else:
|
|
71
|
-
file_save_path = os.path.join(
|
|
72
|
-
os.getcwd(), Generate.BYOSNAP_PROFILE_FN)
|
|
73
|
-
file_written = False
|
|
74
|
-
with open(file_save_path, "w") as file:
|
|
75
|
-
json.dump(BYOSNAP_TEMPLATE, file, indent=4)
|
|
76
|
-
file_written = True
|
|
77
|
-
if file_written:
|
|
78
|
-
snapctl_success(
|
|
79
|
-
"BYOSNAP Profile generation successful. "
|
|
80
|
-
f"{Generate.BYOSNAP_PROFILE_FN} saved at {file_save_path}",
|
|
81
|
-
progress
|
|
82
|
-
)
|
|
83
|
-
except (IOError, OSError) as file_error:
|
|
84
|
-
snapctl_error(f"File error: {file_error}",
|
|
85
|
-
SNAPCTL_GENERATE_GENERIC_ERROR, progress)
|
|
86
|
-
except json.JSONDecodeError as json_error:
|
|
87
|
-
snapctl_error(f"JSON error: {json_error}",
|
|
88
|
-
SNAPCTL_GENERATE_GENERIC_ERROR, progress)
|
|
89
|
-
snapctl_error(
|
|
90
|
-
"Failed to generate BYOSNAP Profile",
|
|
91
|
-
SNAPCTL_GENERATE_GENERIC_ERROR,
|
|
92
|
-
progress
|
|
93
|
-
)
|
|
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
|
|
File without changes
|
|
File without changes
|