snapctl 0.31.1__py3-none-any.whl → 0.32.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 +193 -367
- snapctl/commands/byosnap.py +397 -315
- snapctl/commands/game.py +63 -57
- snapctl/commands/generate.py +93 -0
- snapctl/commands/snapend.py +361 -281
- snapctl/config/constants.py +72 -8
- snapctl/config/hashes.py +14 -0
- snapctl/main.py +106 -124
- snapctl/types/definitions.py +20 -4
- snapctl/utils/echo.py +10 -2
- snapctl/utils/helper.py +60 -3
- snapctl-0.32.1.dist-info/LICENSE +29 -0
- {snapctl-0.31.1.dist-info → snapctl-0.32.1.dist-info}/METADATA +100 -70
- snapctl-0.32.1.dist-info/RECORD +23 -0
- snapctl-0.31.1.dist-info/RECORD +0 -21
- {snapctl-0.31.1.dist-info → snapctl-0.32.1.dist-info}/WHEEL +0 -0
- {snapctl-0.31.1.dist-info → snapctl-0.32.1.dist-info}/entry_points.txt +0 -0
snapctl/commands/byosnap.py
CHANGED
|
@@ -14,11 +14,16 @@ from requests.exceptions import RequestException
|
|
|
14
14
|
|
|
15
15
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
16
16
|
from snapctl.config.constants import SERVER_CALL_TIMEOUT
|
|
17
|
-
from snapctl.config.constants import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
from snapctl.config.constants import HTTP_ERROR_SERVICE_VERSION_EXISTS, \
|
|
18
|
+
HTTP_ERROR_TAG_NOT_AVAILABLE, HTTP_ERROR_ADD_ON_NOT_ENABLED, SNAPCTL_INPUT_ERROR, \
|
|
19
|
+
SNAPCTL_BYOSNAP_DEPENDENCY_MISSING, SNAPCTL_BYOSNAP_ECR_LOGIN_ERROR, \
|
|
20
|
+
SNAPCTL_BYOSNAP_BUILD_ERROR, SNAPCTL_BYOSNAP_TAG_ERROR, SNAPCTL_BYOSNAP_PUBLISH_IMAGE_ERROR, \
|
|
21
|
+
SNAPCTL_BYOSNAP_PUBLISH_IMAGE_DUPLICATE_TAG_ERROR, \
|
|
22
|
+
SNAPCTL_BYOSNAP_CREATE_DUPLICATE_NAME_ERROR, SNAPCTL_BYOSNAP_CREATE_PERMISSION_ERROR, \
|
|
23
|
+
SNAPCTL_BYOSNAP_CREATE_ERROR, SNAPCTL_BYOSNAP_PUBLISH_VERSION_DUPLICATE_TAG_ERROR, \
|
|
24
|
+
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
|
|
22
27
|
|
|
23
28
|
|
|
24
29
|
class ByoSnap:
|
|
@@ -27,20 +32,21 @@ class ByoSnap:
|
|
|
27
32
|
"""
|
|
28
33
|
ID_PREFIX = 'byosnap-'
|
|
29
34
|
SUBCOMMANDS = [
|
|
30
|
-
'
|
|
31
|
-
'create', 'publish-image', 'publish-version',
|
|
35
|
+
'create', 'publish-image', 'publish-version', 'upload-docs',
|
|
32
36
|
]
|
|
33
37
|
PLATFORMS = ['linux/arm64', 'linux/amd64']
|
|
34
38
|
LANGUAGES = ['go', 'python', 'ruby', 'c#', 'c++', 'rust', 'java', 'node']
|
|
35
39
|
DEFAULT_BUILD_PLATFORM = 'linux/arm64'
|
|
36
40
|
SID_CHARACTER_LIMIT = 47
|
|
37
41
|
TAG_CHARACTER_LIMIT = 80
|
|
42
|
+
VALID_CPU_MARKS = [100, 250, 500, 750, 1000, 1500, 2000, 3000]
|
|
43
|
+
VALID_MEMORY_MARKS = [0.125, 0.25, 0.5, 1, 2, 3, 4]
|
|
38
44
|
|
|
39
45
|
def __init__(
|
|
40
46
|
self, subcommand: str, base_url: str, api_key: str | None, sid: str, name: str,
|
|
41
47
|
desc: str, platform_type: str, language: str, input_tag: Union[str, None],
|
|
42
48
|
path: Union[str, None], dockerfile: str, prefix: str, version: Union[str, None],
|
|
43
|
-
http_port: Union[int, None]
|
|
49
|
+
http_port: Union[int, None], byosnap_profile: Union[str, None]
|
|
44
50
|
) -> None:
|
|
45
51
|
self.subcommand: str = subcommand
|
|
46
52
|
self.base_url: str = base_url
|
|
@@ -65,6 +71,9 @@ class ByoSnap:
|
|
|
65
71
|
self.prefix: str = prefix
|
|
66
72
|
self.version: Union[str, None] = version
|
|
67
73
|
self.http_port: Union[int, None] = http_port
|
|
74
|
+
self.byosnap_profile: Union[str, None] = byosnap_profile
|
|
75
|
+
# Validate the input
|
|
76
|
+
self.validate_input()
|
|
68
77
|
|
|
69
78
|
# Protected methods
|
|
70
79
|
@staticmethod
|
|
@@ -89,155 +98,156 @@ class ByoSnap:
|
|
|
89
98
|
pass
|
|
90
99
|
return None
|
|
91
100
|
|
|
92
|
-
def _check_dependencies(self) ->
|
|
101
|
+
def _check_dependencies(self) -> None:
|
|
93
102
|
"""
|
|
94
103
|
Check application dependencies
|
|
95
104
|
"""
|
|
105
|
+
progress = Progress(
|
|
106
|
+
SpinnerColumn(),
|
|
107
|
+
TextColumn("[progress.description]{task.description}"),
|
|
108
|
+
transient=True,
|
|
109
|
+
)
|
|
110
|
+
progress.start()
|
|
111
|
+
progress.add_task(
|
|
112
|
+
description='Checking dependencies...', total=None)
|
|
96
113
|
try:
|
|
97
114
|
# Check dependencies
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
description='Checking dependencies...', total=None)
|
|
105
|
-
try:
|
|
106
|
-
subprocess.run([
|
|
107
|
-
"docker", "--version"
|
|
108
|
-
], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
109
|
-
except subprocess.CalledProcessError:
|
|
110
|
-
error('Docker not present')
|
|
111
|
-
return False
|
|
112
|
-
success('Dependencies Verified')
|
|
113
|
-
return True
|
|
115
|
+
result = subprocess.run([
|
|
116
|
+
"docker", "info"
|
|
117
|
+
], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
118
|
+
if not result.returncode:
|
|
119
|
+
return snapctl_success('BYOSnap dependencies verified',
|
|
120
|
+
progress, no_exit=True)
|
|
114
121
|
except subprocess.CalledProcessError:
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
snapctl_error('Snapctl Exception',
|
|
123
|
+
SNAPCTL_BYOSNAP_DEPENDENCY_MISSING, progress)
|
|
124
|
+
snapctl_error('Docker not running. Please start docker.',
|
|
125
|
+
SNAPCTL_BYOSNAP_DEPENDENCY_MISSING, progress)
|
|
117
126
|
|
|
118
|
-
def _docker_login(self) ->
|
|
127
|
+
def _docker_login(self) -> None:
|
|
119
128
|
"""
|
|
120
129
|
Docker Login
|
|
121
130
|
"""
|
|
122
131
|
ecr_repo_url = self.token_parts[0]
|
|
123
132
|
ecr_repo_username = self.token_parts[1]
|
|
124
133
|
ecr_repo_token = self.token_parts[2]
|
|
134
|
+
progress = Progress(
|
|
135
|
+
SpinnerColumn(),
|
|
136
|
+
TextColumn("[progress.description]{task.description}"),
|
|
137
|
+
transient=True,
|
|
138
|
+
)
|
|
139
|
+
progress.start()
|
|
140
|
+
progress.add_task(
|
|
141
|
+
description='Logging into Snapser Image Registry...', total=None)
|
|
125
142
|
try:
|
|
126
143
|
# Login to Snapser Registry
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
response = subprocess.run([
|
|
141
|
-
f'echo "{ecr_repo_token}" | docker login '
|
|
142
|
-
f'--username {ecr_repo_username} --password-stdin {ecr_repo_url}'
|
|
143
|
-
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
144
|
-
if response.returncode:
|
|
145
|
-
error(
|
|
146
|
-
f'{response.returncode} - '
|
|
147
|
-
'Unable to connect to the Snapser Container Repository. '
|
|
148
|
-
'Please confirm if docker is running or try restarting docker'
|
|
149
|
-
)
|
|
150
|
-
return False
|
|
151
|
-
success('Login Successful')
|
|
152
|
-
return True
|
|
144
|
+
if platform == 'win32':
|
|
145
|
+
response = subprocess.run([
|
|
146
|
+
'docker', 'login', '--username', ecr_repo_username,
|
|
147
|
+
'--password', ecr_repo_token, ecr_repo_url
|
|
148
|
+
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
149
|
+
else:
|
|
150
|
+
response = subprocess.run([
|
|
151
|
+
f'echo "{ecr_repo_token}" | docker login '
|
|
152
|
+
f'--username {ecr_repo_username} --password-stdin {ecr_repo_url}'
|
|
153
|
+
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
154
|
+
if not response.returncode:
|
|
155
|
+
return snapctl_success('BYOSnap ECR login successful',
|
|
156
|
+
progress, no_exit=True)
|
|
153
157
|
except subprocess.CalledProcessError:
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
snapctl_error('Snapctl Exception',
|
|
159
|
+
SNAPCTL_BYOSNAP_ECR_LOGIN_ERROR, progress)
|
|
160
|
+
snapctl_error('BYOSnap ECR login failure',
|
|
161
|
+
SNAPCTL_BYOSNAP_ECR_LOGIN_ERROR, progress)
|
|
156
162
|
|
|
157
|
-
def _docker_build(self) ->
|
|
163
|
+
def _docker_build(self) -> None:
|
|
158
164
|
# Get the data
|
|
159
165
|
image_tag = f'{self.sid}.{self.input_tag}'
|
|
160
166
|
build_platform = ByoSnap.DEFAULT_BUILD_PLATFORM
|
|
161
167
|
if len(self.token_parts) == 4:
|
|
162
168
|
build_platform = self.token_parts[3]
|
|
169
|
+
progress = Progress(
|
|
170
|
+
SpinnerColumn(),
|
|
171
|
+
TextColumn("[progress.description]{task.description}"),
|
|
172
|
+
transient=True,
|
|
173
|
+
)
|
|
174
|
+
progress.start()
|
|
175
|
+
progress.add_task(
|
|
176
|
+
description='Building your snap...', total=None)
|
|
163
177
|
try:
|
|
164
178
|
# Build your snap
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
f"docker build --platform {build_platform} -t {image_tag} -f {docker_file_path} {self.path}"
|
|
184
|
-
], shell=True, check=False)
|
|
185
|
-
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
186
|
-
if response.returncode:
|
|
187
|
-
error('Unable to build docker')
|
|
188
|
-
return False
|
|
189
|
-
success('Build Successful')
|
|
190
|
-
return True
|
|
179
|
+
docker_file_path = os.path.join(self.path, self.dockerfile)
|
|
180
|
+
if platform == "win32":
|
|
181
|
+
response = subprocess.run([
|
|
182
|
+
# f"docker build --no-cache -t {tag} {path}"
|
|
183
|
+
'docker', 'build', '--platform', build_platform, '-t', image_tag,
|
|
184
|
+
'-f', docker_file_path, self.path
|
|
185
|
+
], shell=True, check=False)
|
|
186
|
+
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
187
|
+
else:
|
|
188
|
+
response = subprocess.run([
|
|
189
|
+
# f"docker build --no-cache -t {tag} {path}"
|
|
190
|
+
f"docker build --platform {build_platform} -t {image_tag} "
|
|
191
|
+
f"-f {docker_file_path} {self.path}"
|
|
192
|
+
], shell=True, check=False)
|
|
193
|
+
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
194
|
+
if not response.returncode:
|
|
195
|
+
return snapctl_success('BYOSnap build successful',
|
|
196
|
+
progress, no_exit=True)
|
|
191
197
|
except subprocess.CalledProcessError:
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
snapctl_error('Snapctl Exception',
|
|
199
|
+
SNAPCTL_BYOSNAP_BUILD_ERROR, progress)
|
|
200
|
+
snapctl_error('BYOSnap build failure',
|
|
201
|
+
SNAPCTL_BYOSNAP_BUILD_ERROR, progress)
|
|
194
202
|
|
|
195
|
-
def _docker_tag(self) ->
|
|
203
|
+
def _docker_tag(self) -> None:
|
|
196
204
|
# Get the data
|
|
197
205
|
ecr_repo_url = self.token_parts[0]
|
|
198
206
|
image_tag = f'{self.sid}.{self.input_tag}'
|
|
199
207
|
full_ecr_repo_url = f'{ecr_repo_url}:{image_tag}'
|
|
208
|
+
progress = Progress(
|
|
209
|
+
SpinnerColumn(),
|
|
210
|
+
TextColumn("[progress.description]{task.description}"),
|
|
211
|
+
transient=True,
|
|
212
|
+
)
|
|
213
|
+
progress.start()
|
|
214
|
+
progress.add_task(
|
|
215
|
+
description='Tagging your snap...', total=None)
|
|
200
216
|
try:
|
|
201
217
|
# Tag the repo
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
else:
|
|
214
|
-
response = subprocess.run([
|
|
215
|
-
f"docker tag {image_tag} {full_ecr_repo_url}"
|
|
216
|
-
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
217
|
-
if response.returncode:
|
|
218
|
-
error('Unable to tag your snap')
|
|
219
|
-
return False
|
|
220
|
-
success('Tag Successful')
|
|
221
|
-
return True
|
|
218
|
+
if platform == "win32":
|
|
219
|
+
response = subprocess.run([
|
|
220
|
+
'docker', 'tag', image_tag, full_ecr_repo_url
|
|
221
|
+
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
222
|
+
else:
|
|
223
|
+
response = subprocess.run([
|
|
224
|
+
f"docker tag {image_tag} {full_ecr_repo_url}"
|
|
225
|
+
], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
|
|
226
|
+
if not response.returncode:
|
|
227
|
+
return snapctl_success('BYOSnap tag successful',
|
|
228
|
+
progress, no_exit=True)
|
|
222
229
|
except subprocess.CalledProcessError:
|
|
223
|
-
|
|
224
|
-
|
|
230
|
+
snapctl_error('Snapctl Exception',
|
|
231
|
+
SNAPCTL_BYOSNAP_TAG_ERROR, progress)
|
|
232
|
+
snapctl_error('BYOSnap tag failure',
|
|
233
|
+
SNAPCTL_BYOSNAP_TAG_ERROR, progress)
|
|
225
234
|
|
|
226
235
|
def _docker_push(self) -> bool:
|
|
227
236
|
"""
|
|
228
237
|
Push the Snap image
|
|
229
238
|
"""
|
|
230
|
-
|
|
231
|
-
image_tag = f'{self.sid}.{self.input_tag}'
|
|
232
|
-
full_ecr_repo_url = f'{ecr_repo_url}:{image_tag}'
|
|
233
|
-
|
|
234
|
-
# Push the image
|
|
235
|
-
with Progress(
|
|
239
|
+
progress = Progress(
|
|
236
240
|
SpinnerColumn(),
|
|
237
241
|
TextColumn("[progress.description]{task.description}"),
|
|
238
242
|
transient=True,
|
|
239
|
-
)
|
|
240
|
-
|
|
243
|
+
)
|
|
244
|
+
progress.start()
|
|
245
|
+
progress.add_task(description='Pushing your snap...', total=None)
|
|
246
|
+
try:
|
|
247
|
+
# Push the image
|
|
248
|
+
ecr_repo_url = self.token_parts[0]
|
|
249
|
+
image_tag = f'{self.sid}.{self.input_tag}'
|
|
250
|
+
full_ecr_repo_url = f'{ecr_repo_url}:{image_tag}'
|
|
241
251
|
if platform == "win32":
|
|
242
252
|
response = subprocess.run([
|
|
243
253
|
'docker', 'push', full_ecr_repo_url
|
|
@@ -248,133 +258,192 @@ class ByoSnap:
|
|
|
248
258
|
f"docker push {full_ecr_repo_url}"
|
|
249
259
|
], shell=True, check=False)
|
|
250
260
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
251
|
-
if response.returncode:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
if not response.returncode:
|
|
262
|
+
return snapctl_success('BYOSnap upload successful',
|
|
263
|
+
progress, no_exit=True)
|
|
264
|
+
except subprocess.CalledProcessError:
|
|
265
|
+
snapctl_error('Snapctl Exception',
|
|
266
|
+
SNAPCTL_BYOSNAP_PUBLISH_IMAGE_ERROR, progress)
|
|
267
|
+
snapctl_error('BYOSnap upload failure. Duplicate image error.',
|
|
268
|
+
SNAPCTL_BYOSNAP_PUBLISH_IMAGE_DUPLICATE_TAG_ERROR, progress)
|
|
269
|
+
|
|
270
|
+
def _validate_byosnap_profile(self) -> None:
|
|
271
|
+
"""
|
|
272
|
+
Validate the BYOSnap profile
|
|
273
|
+
"""
|
|
274
|
+
if not self.byosnap_profile:
|
|
275
|
+
snapctl_error("Missing BYOSnap profile path", SNAPCTL_INPUT_ERROR)
|
|
276
|
+
if not os.path.isfile(self.byosnap_profile):
|
|
277
|
+
snapctl_error(
|
|
278
|
+
"Unable to find BYOSnap profile "
|
|
279
|
+
F"JSON at path {self.byosnap_profile}",
|
|
280
|
+
SNAPCTL_INPUT_ERROR
|
|
281
|
+
)
|
|
282
|
+
profile_data = None
|
|
283
|
+
with open(self.byosnap_profile, 'rb') as file:
|
|
284
|
+
try:
|
|
285
|
+
profile_data = json.load(file)
|
|
286
|
+
except json.JSONDecodeError:
|
|
287
|
+
pass
|
|
288
|
+
if not profile_data:
|
|
289
|
+
snapctl_error(
|
|
290
|
+
'Invalid BYOSnap profile JSON. Please check the JSON structure',
|
|
291
|
+
SNAPCTL_INPUT_ERROR
|
|
292
|
+
)
|
|
293
|
+
if 'dev_template' not in profile_data or \
|
|
294
|
+
'stage_template' not in profile_data or \
|
|
295
|
+
'prod_template' not in profile_data:
|
|
296
|
+
snapctl_error(
|
|
297
|
+
'Invalid BYOSnap profile JSON. Please check the JSON structure',
|
|
298
|
+
SNAPCTL_INPUT_ERROR
|
|
299
|
+
)
|
|
300
|
+
for profile in ['dev_template', 'stage_template', 'prod_template']:
|
|
301
|
+
if 'cpu' not in profile_data[profile] or \
|
|
302
|
+
'memory' not in profile_data[profile] or \
|
|
303
|
+
'cmd' not in profile_data[profile] or \
|
|
304
|
+
'args' not in profile_data[profile] or \
|
|
305
|
+
'env_params' not in profile_data[profile]:
|
|
306
|
+
snapctl_error(
|
|
307
|
+
'Invalid BYOSnap profile JSON. Please check the JSON structure',
|
|
308
|
+
SNAPCTL_INPUT_ERROR
|
|
309
|
+
)
|
|
310
|
+
if profile_data[profile]['cpu'] not in ByoSnap.VALID_CPU_MARKS:
|
|
311
|
+
snapctl_error(
|
|
312
|
+
'Invalid CPU value in BYOSnap profile. '
|
|
313
|
+
f'Valid values are {", ".join(map(str, ByoSnap.VALID_CPU_MARKS))}',
|
|
314
|
+
SNAPCTL_INPUT_ERROR
|
|
315
|
+
)
|
|
316
|
+
if profile_data[profile]['memory'] not in ByoSnap.VALID_MEMORY_MARKS:
|
|
317
|
+
snapctl_error(
|
|
318
|
+
'Invalid Memory value in BYOSnap profile. '
|
|
319
|
+
f'Valid values are {", ".join(map(str, ByoSnap.VALID_MEMORY_MARKS))}',
|
|
320
|
+
SNAPCTL_INPUT_ERROR
|
|
321
|
+
)
|
|
256
322
|
|
|
257
323
|
# Public methods
|
|
258
324
|
|
|
259
325
|
# Validator
|
|
260
|
-
def validate_input(self) ->
|
|
326
|
+
def validate_input(self) -> None:
|
|
261
327
|
"""
|
|
262
328
|
Validator
|
|
263
329
|
"""
|
|
264
|
-
response: ResponseType = {
|
|
265
|
-
'error': True,
|
|
266
|
-
'msg': '',
|
|
267
|
-
'data': []
|
|
268
|
-
}
|
|
269
330
|
# Check API Key and Base URL
|
|
270
331
|
if not self.api_key or self.base_url == '':
|
|
271
|
-
|
|
272
|
-
return response
|
|
332
|
+
snapctl_error("Missing API Key.", SNAPCTL_INPUT_ERROR)
|
|
273
333
|
# Check subcommand
|
|
274
334
|
if not self.subcommand in ByoSnap.SUBCOMMANDS:
|
|
275
|
-
|
|
276
|
-
"Invalid command. Valid commands "
|
|
277
|
-
f"are {', '.join(ByoSnap.SUBCOMMANDS)}."
|
|
335
|
+
snapctl_error(
|
|
336
|
+
"Invalid command. Valid commands "
|
|
337
|
+
f"are {', '.join(ByoSnap.SUBCOMMANDS)}.",
|
|
338
|
+
SNAPCTL_INPUT_ERROR
|
|
278
339
|
)
|
|
279
|
-
return response
|
|
280
340
|
# Validate the SID
|
|
281
341
|
if not self.sid.startswith(ByoSnap.ID_PREFIX):
|
|
282
|
-
|
|
283
|
-
|
|
342
|
+
snapctl_error(
|
|
343
|
+
f"Invalid Snap ID. Valid Snap IDs start with {ByoSnap.ID_PREFIX}.",
|
|
344
|
+
SNAPCTL_INPUT_ERROR
|
|
345
|
+
)
|
|
284
346
|
if len(self.sid) > ByoSnap.SID_CHARACTER_LIMIT:
|
|
285
|
-
|
|
347
|
+
snapctl_error(
|
|
286
348
|
"Invalid Snap ID. "
|
|
287
|
-
f"Snap ID should be less than {ByoSnap.SID_CHARACTER_LIMIT} characters"
|
|
349
|
+
f"Snap ID should be less than {ByoSnap.SID_CHARACTER_LIMIT} characters",
|
|
350
|
+
SNAPCTL_INPUT_ERROR
|
|
288
351
|
)
|
|
289
|
-
return response
|
|
290
352
|
# Validation for subcommands
|
|
291
353
|
if self.subcommand == 'create':
|
|
292
354
|
if self.name == '':
|
|
293
|
-
|
|
294
|
-
|
|
355
|
+
snapctl_error("Missing name", SNAPCTL_INPUT_ERROR)
|
|
356
|
+
if not self.language:
|
|
357
|
+
snapctl_error("Missing language", SNAPCTL_INPUT_ERROR)
|
|
295
358
|
if self.language not in ByoSnap.LANGUAGES:
|
|
296
|
-
|
|
359
|
+
snapctl_error(
|
|
297
360
|
"Invalid language. Valid languages are "
|
|
298
|
-
f"{', '.join(ByoSnap.LANGUAGES)}."
|
|
361
|
+
f"{', '.join(ByoSnap.LANGUAGES)}.",
|
|
362
|
+
SNAPCTL_INPUT_ERROR
|
|
299
363
|
)
|
|
300
|
-
return response
|
|
301
364
|
if self.platform_type not in ByoSnap.PLATFORMS:
|
|
302
|
-
|
|
365
|
+
snapctl_error(
|
|
303
366
|
"Invalid platform. Valid platforms are "
|
|
304
|
-
f"{', '.join(ByoSnap.PLATFORMS)}."
|
|
367
|
+
f"{', '.join(ByoSnap.PLATFORMS)}.",
|
|
368
|
+
SNAPCTL_INPUT_ERROR
|
|
305
369
|
)
|
|
306
|
-
return response
|
|
307
370
|
else:
|
|
308
371
|
# Check the token
|
|
309
372
|
if self.token_parts is None:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
if self.input_tag is None or len(self.input_tag.split()) > 1 or \
|
|
314
|
-
len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
|
|
315
|
-
response['msg'] = (
|
|
316
|
-
"Tag should be a single word with maximum of "
|
|
317
|
-
f"{ByoSnap.TAG_CHARACTER_LIMIT} characters"
|
|
318
|
-
)
|
|
319
|
-
return response
|
|
320
|
-
if self.subcommand == 'build' or self.subcommand == 'publish-image':
|
|
373
|
+
snapctl_error('Invalid token. Please reach out to your support team.',
|
|
374
|
+
SNAPCTL_INPUT_ERROR)
|
|
375
|
+
if self.subcommand in ['build', 'publish-image']:
|
|
321
376
|
if not self.input_tag:
|
|
322
|
-
|
|
323
|
-
|
|
377
|
+
snapctl_error(
|
|
378
|
+
"Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
|
|
379
|
+
if len(self.input_tag.split()) > 1 or \
|
|
380
|
+
len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
|
|
381
|
+
snapctl_error(
|
|
382
|
+
"Tag should be a single word with maximum of "
|
|
383
|
+
f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
|
|
384
|
+
SNAPCTL_INPUT_ERROR
|
|
385
|
+
)
|
|
324
386
|
if not self.path:
|
|
325
|
-
|
|
326
|
-
|
|
387
|
+
snapctl_error("Missing required parameter: path",
|
|
388
|
+
SNAPCTL_INPUT_ERROR)
|
|
327
389
|
# Check path
|
|
328
390
|
if not os.path.isfile(f"{self.path}/{self.dockerfile}"):
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
391
|
+
snapctl_error(
|
|
392
|
+
f"Unable to find {self.dockerfile} at path {self.path}",
|
|
393
|
+
SNAPCTL_INPUT_ERROR)
|
|
394
|
+
# elif self.subcommand == 'push':
|
|
395
|
+
# if not self.input_tag:
|
|
396
|
+
# error("Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
|
|
397
|
+
# raise typer.Exit(code=SNAPCTL_INPUT_ERROR)
|
|
335
398
|
elif self.subcommand == 'upload-docs':
|
|
336
399
|
if self.path is None:
|
|
337
|
-
|
|
338
|
-
|
|
400
|
+
snapctl_error(
|
|
401
|
+
"Missing required parameter: path", SNAPCTL_INPUT_ERROR)
|
|
339
402
|
elif self.subcommand == 'publish-version':
|
|
340
|
-
if not self.
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
if
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
403
|
+
if not self.input_tag:
|
|
404
|
+
snapctl_error(
|
|
405
|
+
"Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
|
|
406
|
+
if len(self.input_tag.split()) > 1 or len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
|
|
407
|
+
snapctl_error(
|
|
408
|
+
"Tag should be a single word with maximum of "
|
|
409
|
+
f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
|
|
410
|
+
SNAPCTL_INPUT_ERROR
|
|
411
|
+
)
|
|
412
|
+
if not self.prefix or self.prefix == '':
|
|
413
|
+
snapctl_error("Missing prefix", SNAPCTL_INPUT_ERROR)
|
|
349
414
|
if not self.prefix.startswith('/'):
|
|
350
|
-
|
|
351
|
-
|
|
415
|
+
snapctl_error("Prefix should start with a forward slash (/)",
|
|
416
|
+
SNAPCTL_INPUT_ERROR)
|
|
352
417
|
if self.prefix.endswith('/'):
|
|
353
|
-
|
|
354
|
-
|
|
418
|
+
snapctl_error("Prefix should not end with a forward slash (/)",
|
|
419
|
+
SNAPCTL_INPUT_ERROR)
|
|
420
|
+
if not self.version:
|
|
421
|
+
snapctl_error("Missing version", SNAPCTL_INPUT_ERROR)
|
|
355
422
|
pattern = r'^v\d+\.\d+\.\d+$'
|
|
356
423
|
if not re.match(pattern, self.version):
|
|
357
|
-
|
|
358
|
-
|
|
424
|
+
snapctl_error("Version should be in the format vX.X.X",
|
|
425
|
+
SNAPCTL_INPUT_ERROR)
|
|
426
|
+
if not self.http_port:
|
|
427
|
+
snapctl_error("Missing Ingress HTTP Port",
|
|
428
|
+
SNAPCTL_INPUT_ERROR)
|
|
359
429
|
if not self.http_port.isdigit():
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return response
|
|
430
|
+
snapctl_error("Ingress HTTP Port should be a number",
|
|
431
|
+
SNAPCTL_INPUT_ERROR)
|
|
432
|
+
# Check byosnap_profile path
|
|
433
|
+
self._validate_byosnap_profile()
|
|
365
434
|
|
|
366
435
|
# CRUD methods
|
|
367
|
-
|
|
436
|
+
|
|
437
|
+
def build(self) -> None:
|
|
368
438
|
"""
|
|
369
439
|
Build the image
|
|
370
440
|
1. Check Dependencies
|
|
371
441
|
2. Login to Snapser Registry
|
|
372
442
|
3. Build your snap
|
|
373
443
|
"""
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
return True
|
|
444
|
+
self._check_dependencies()
|
|
445
|
+
self._docker_login()
|
|
446
|
+
self._docker_build()
|
|
378
447
|
|
|
379
448
|
def push(self) -> bool:
|
|
380
449
|
"""
|
|
@@ -384,26 +453,29 @@ class ByoSnap:
|
|
|
384
453
|
3. Tag the snap
|
|
385
454
|
4. Push your snap
|
|
386
455
|
"""
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
456
|
+
self._check_dependencies()
|
|
457
|
+
self._docker_login()
|
|
458
|
+
self._docker_tag()
|
|
459
|
+
self._docker_push()
|
|
391
460
|
|
|
392
|
-
|
|
461
|
+
# Upper echelon commands
|
|
462
|
+
def upload_docs(self) -> None:
|
|
393
463
|
'''
|
|
394
|
-
Note this step is optional hence we
|
|
464
|
+
Note this step is optional hence we do not raise a typer.Exit
|
|
395
465
|
'''
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
466
|
+
progress = Progress(
|
|
467
|
+
SpinnerColumn(),
|
|
468
|
+
TextColumn("[progress.description]{task.description}"),
|
|
469
|
+
transient=True,
|
|
470
|
+
)
|
|
471
|
+
progress.start()
|
|
472
|
+
progress.add_task(
|
|
473
|
+
description='Uploading your BYOSnap Docs...', total=None)
|
|
474
|
+
try:
|
|
475
|
+
swagger_file = os.path.join(self.path, 'swagger.json')
|
|
476
|
+
readme_file = os.path.join(self.path, 'README.md')
|
|
477
|
+
if os.path.isfile(swagger_file):
|
|
478
|
+
# Push the swagger.json
|
|
407
479
|
try:
|
|
408
480
|
attachment_file = open(swagger_file, "rb")
|
|
409
481
|
url = (
|
|
@@ -418,25 +490,18 @@ class ByoSnap:
|
|
|
418
490
|
if test_res.ok:
|
|
419
491
|
success('Uploaded swagger.json')
|
|
420
492
|
else:
|
|
421
|
-
|
|
493
|
+
info('Unable to upload your swagger.json')
|
|
422
494
|
except RequestException as e:
|
|
423
495
|
info(
|
|
424
496
|
f'Exception: Unable to find swagger.json at {self.path} {e}'
|
|
425
497
|
)
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
498
|
+
else:
|
|
499
|
+
info(f'No swagger.json found at {self.path}'
|
|
500
|
+
'. Skipping swagger.json upload')
|
|
429
501
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
with Progress(
|
|
434
|
-
SpinnerColumn(),
|
|
435
|
-
TextColumn("[progress.description]{task.description}"),
|
|
436
|
-
transient=True,
|
|
437
|
-
) as progress:
|
|
438
|
-
progress.add_task(
|
|
439
|
-
description='Uploading your README...', total=None)
|
|
502
|
+
# Push the README.md
|
|
503
|
+
if os.path.isfile(readme_file):
|
|
504
|
+
# Push the swagger.json
|
|
440
505
|
try:
|
|
441
506
|
attachment_file = open(readme_file, "rb")
|
|
442
507
|
url = (
|
|
@@ -451,67 +516,71 @@ class ByoSnap:
|
|
|
451
516
|
if test_res.ok:
|
|
452
517
|
success('Uploaded README.md')
|
|
453
518
|
else:
|
|
454
|
-
|
|
519
|
+
info('Unable to upload your README.md')
|
|
455
520
|
except RequestException as e:
|
|
456
521
|
info(
|
|
457
|
-
f'Exception: Unable to find README.md at {self.path} {str(e)}'
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
)
|
|
463
|
-
|
|
522
|
+
f'Exception: Unable to find README.md at {self.path} {str(e)}')
|
|
523
|
+
else:
|
|
524
|
+
info(
|
|
525
|
+
f'No README.md found at {self.path}. Skipping README.md upload')
|
|
526
|
+
except RequestException as e:
|
|
527
|
+
info(f'Exception: Unable to upload your API Json {str(e)}')
|
|
528
|
+
finally:
|
|
529
|
+
progress.stop()
|
|
530
|
+
# snapctl_success('BYOSnap upload successful', no_exit=no_exit)
|
|
464
531
|
|
|
465
|
-
|
|
466
|
-
def create(self) -> bool:
|
|
532
|
+
def create(self) -> None:
|
|
467
533
|
"""
|
|
468
534
|
Creating a new snap
|
|
469
535
|
"""
|
|
470
|
-
|
|
536
|
+
progress = Progress(
|
|
471
537
|
SpinnerColumn(),
|
|
472
538
|
TextColumn("[progress.description]{task.description}"),
|
|
473
539
|
transient=True,
|
|
474
|
-
)
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
error('Invalid tag. Please use the correct tag')
|
|
500
|
-
elif response_json['api_error_code'] == ERROR_ADD_ON_NOT_ENABLED:
|
|
501
|
-
error(
|
|
502
|
-
'Missing Add-on. Please enable the add-on via the Snapser Web app.'
|
|
503
|
-
)
|
|
504
|
-
else:
|
|
505
|
-
error(f'Server error: {response_json["message"]}')
|
|
506
|
-
else:
|
|
507
|
-
error(
|
|
508
|
-
f'Server error: {json.dumps(response_json, indent=2)}'
|
|
540
|
+
)
|
|
541
|
+
progress.start()
|
|
542
|
+
progress.add_task(description='Creating your snap...', total=None)
|
|
543
|
+
try:
|
|
544
|
+
payload = {
|
|
545
|
+
"service_id": self.sid,
|
|
546
|
+
"name": self.name,
|
|
547
|
+
"description": self.desc,
|
|
548
|
+
"platform": self.platform_type,
|
|
549
|
+
"language": self.language,
|
|
550
|
+
}
|
|
551
|
+
res = requests.post(
|
|
552
|
+
f"{self.base_url}/v1/snapser-api/byosnaps",
|
|
553
|
+
json=payload, headers={'api-key': self.api_key},
|
|
554
|
+
timeout=SERVER_CALL_TIMEOUT
|
|
555
|
+
)
|
|
556
|
+
if res.ok:
|
|
557
|
+
snapctl_success('BYOSNAP create successful', progress)
|
|
558
|
+
response_json = res.json()
|
|
559
|
+
if "api_error_code" in response_json and "message" in response_json:
|
|
560
|
+
if response_json['api_error_code'] == HTTP_ERROR_SERVICE_VERSION_EXISTS:
|
|
561
|
+
snapctl_error(
|
|
562
|
+
f'BYOSnap {self.name} already exists. Please use a different name',
|
|
563
|
+
SNAPCTL_BYOSNAP_CREATE_DUPLICATE_NAME_ERROR,
|
|
564
|
+
progress
|
|
509
565
|
)
|
|
510
|
-
|
|
511
|
-
error(
|
|
512
|
-
|
|
566
|
+
# elif response_json['api_error_code'] == HTTP_ERROR_TAG_NOT_AVAILABLE:
|
|
567
|
+
# error('Invalid tag. Please use the correct tag')
|
|
568
|
+
if response_json['api_error_code'] == HTTP_ERROR_ADD_ON_NOT_ENABLED:
|
|
569
|
+
snapctl_error(
|
|
570
|
+
'Missing Add-on. Please enable the add-on via the Snapser Web app.',
|
|
571
|
+
SNAPCTL_BYOSNAP_CREATE_PERMISSION_ERROR, progress
|
|
572
|
+
)
|
|
573
|
+
snapctl_error(
|
|
574
|
+
f'Server error: {json.dumps(response_json, indent=2)}',
|
|
575
|
+
SNAPCTL_BYOSNAP_CREATE_ERROR, progress)
|
|
576
|
+
except RequestException as e:
|
|
577
|
+
snapctl_error(
|
|
578
|
+
f"Exception: Unable to create your snap {e}",
|
|
579
|
+
SNAPCTL_BYOSNAP_CREATE_ERROR, progress)
|
|
580
|
+
snapctl_error('Failed to create snap',
|
|
581
|
+
SNAPCTL_BYOSNAP_CREATE_ERROR, progress)
|
|
513
582
|
|
|
514
|
-
def publish_image(self) ->
|
|
583
|
+
def publish_image(self) -> None:
|
|
515
584
|
"""
|
|
516
585
|
Publish the image
|
|
517
586
|
1. Check Dependencies
|
|
@@ -521,51 +590,64 @@ class ByoSnap:
|
|
|
521
590
|
5. Push the image
|
|
522
591
|
6. Upload swagger.json
|
|
523
592
|
"""
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
593
|
+
self._check_dependencies()
|
|
594
|
+
self._docker_login()
|
|
595
|
+
self._docker_build()
|
|
596
|
+
self._docker_tag()
|
|
597
|
+
self._docker_push()
|
|
598
|
+
self.upload_docs()
|
|
599
|
+
snapctl_success('BYOSNAP publish successful')
|
|
529
600
|
|
|
530
|
-
def publish_version(self) ->
|
|
601
|
+
def publish_version(self) -> None:
|
|
531
602
|
"""
|
|
532
603
|
Publish the version
|
|
533
604
|
"""
|
|
534
|
-
|
|
605
|
+
progress = Progress(
|
|
535
606
|
SpinnerColumn(),
|
|
536
607
|
TextColumn("[progress.description]{task.description}"),
|
|
537
608
|
transient=True,
|
|
538
|
-
)
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
609
|
+
)
|
|
610
|
+
progress.start()
|
|
611
|
+
progress.add_task(
|
|
612
|
+
description='Publishing your snap...', total=None)
|
|
613
|
+
try:
|
|
614
|
+
profile_data = {}
|
|
615
|
+
profile_data['dev_template'] = None
|
|
616
|
+
profile_data['stage_template'] = None
|
|
617
|
+
profile_data['prod_template'] = None
|
|
618
|
+
with open(self.byosnap_profile, 'rb') as file:
|
|
619
|
+
profile_data = json.load(file)
|
|
620
|
+
payload = {
|
|
621
|
+
"version": self.version,
|
|
622
|
+
"image_tag": self.input_tag,
|
|
623
|
+
"base_url": f"{self.prefix}/{self.sid}",
|
|
624
|
+
"http_port": self.http_port,
|
|
625
|
+
"dev_template": profile_data['dev_template'],
|
|
626
|
+
"stage_template": profile_data['stage_template'],
|
|
627
|
+
"prod_template": profile_data['prod_template']
|
|
628
|
+
}
|
|
629
|
+
res = requests.post(
|
|
630
|
+
f"{self.base_url}/v1/snapser-api/byosnaps/{self.sid}/versions",
|
|
631
|
+
json=payload, headers={'api-key': self.api_key},
|
|
632
|
+
timeout=SERVER_CALL_TIMEOUT
|
|
633
|
+
)
|
|
634
|
+
if res.ok:
|
|
635
|
+
snapctl_success('BYOSNAP publish version successful', progress)
|
|
636
|
+
response_json = res.json()
|
|
637
|
+
if "api_error_code" in response_json:
|
|
638
|
+
if response_json['api_error_code'] == HTTP_ERROR_SERVICE_VERSION_EXISTS:
|
|
639
|
+
snapctl_error(
|
|
640
|
+
'Version already exists. Please update your version and try again',
|
|
641
|
+
SNAPCTL_BYOSNAP_PUBLISH_IMAGE_DUPLICATE_TAG_ERROR,
|
|
642
|
+
progress
|
|
566
643
|
)
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
644
|
+
if response_json['api_error_code'] == HTTP_ERROR_TAG_NOT_AVAILABLE:
|
|
645
|
+
snapctl_error('Invalid tag. Please use the correct tag',
|
|
646
|
+
SNAPCTL_BYOSNAP_PUBLISH_VERSION_DUPLICATE_TAG_ERROR, progress)
|
|
647
|
+
snapctl_error(f'Server error: {json.dumps(response_json, indent=2)}',
|
|
648
|
+
SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress)
|
|
649
|
+
except RequestException as e:
|
|
650
|
+
snapctl_error(f'Exception: Unable to publish a version for your snap {e}',
|
|
651
|
+
SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress)
|
|
652
|
+
snapctl_error('Failed to publish version',
|
|
653
|
+
SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress)
|