snapctl 0.39.3__py3-none-any.whl → 0.41.0__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.

@@ -6,6 +6,7 @@ from binascii import Error as BinasciiError
6
6
  import json
7
7
  import os
8
8
  import re
9
+ import time
9
10
  import subprocess
10
11
  import platform as sys_platform
11
12
  from sys import platform
@@ -14,6 +15,7 @@ import requests
14
15
  from requests.exceptions import RequestException
15
16
 
16
17
  from rich.progress import Progress, SpinnerColumn, TextColumn
18
+ from snapctl.commands.snapend import Snapend
17
19
  from snapctl.config.constants import SERVER_CALL_TIMEOUT
18
20
  from snapctl.config.constants import HTTP_ERROR_SERVICE_VERSION_EXISTS, \
19
21
  HTTP_ERROR_TAG_NOT_AVAILABLE, HTTP_ERROR_ADD_ON_NOT_ENABLED, SNAPCTL_INPUT_ERROR, \
@@ -22,8 +24,11 @@ from snapctl.config.constants import HTTP_ERROR_SERVICE_VERSION_EXISTS, \
22
24
  SNAPCTL_BYOSNAP_PUBLISH_IMAGE_DUPLICATE_TAG_ERROR, \
23
25
  SNAPCTL_BYOSNAP_CREATE_DUPLICATE_NAME_ERROR, SNAPCTL_BYOSNAP_CREATE_PERMISSION_ERROR, \
24
26
  SNAPCTL_BYOSNAP_CREATE_ERROR, SNAPCTL_BYOSNAP_PUBLISH_VERSION_DUPLICATE_TAG_ERROR, \
25
- SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR
26
- from snapctl.utils.echo import success, info, warning
27
+ SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, HTTP_ERROR_SERVICE_IN_USE, \
28
+ SNAPCTL_BYOSNAP_UPDATE_VERSION_ERROR, SNAPCTL_BYOSNAP_UPDATE_VERSION_SERVICE_IN_USE_ERROR, \
29
+ SNAPCTL_BYOSNAP_UPDATE_VERSION_TAG_ERROR, SNAPCTL_BYOSNAP_NOT_FOUND, \
30
+ HTTP_ERROR_RESOURCE_NOT_FOUND
31
+ from snapctl.utils.echo import info, warning
27
32
  from snapctl.utils.helper import get_composite_token, snapctl_error, snapctl_success, \
28
33
  check_dockerfile_architecture
29
34
 
@@ -34,7 +39,8 @@ class ByoSnap:
34
39
  """
35
40
  ID_PREFIX = 'byosnap-'
36
41
  SUBCOMMANDS = [
37
- 'create', 'publish-image', 'publish-version', 'upload-docs',
42
+ 'create', 'publish-image', 'publish-version', 'upload-docs', 'update-version',
43
+ 'sync'
38
44
  ]
39
45
  PLATFORMS = ['linux/arm64', 'linux/amd64']
40
46
  LANGUAGES = ['go', 'python', 'ruby', 'c#', 'c++', 'rust', 'java', 'node']
@@ -47,16 +53,20 @@ class ByoSnap:
47
53
  MAX_MIN_REPLICAS = 4
48
54
 
49
55
  def __init__(
50
- self, subcommand: str, base_url: str, api_key: Union[str, None], sid: str, name: str,
51
- desc: str, platform_type: str, language: str, input_tag: Union[str, None],
52
- path: Union[str, None], resources_path: Union[str, None], dockerfile: str,
53
- prefix: str, version: Union[str, None], http_port: Union[int, None],
54
- byosnap_profile: Union[str, None], skip_build: bool = False,
55
- readiness_path: Union[str, None] = None, readiness_delay: Union[int, None] = None
56
+ self, *, subcommand: str, base_url: str, api_key: Union[str, None], sid: str,
57
+ name: Union[str, None] = None, desc: Union[str, None] = None,
58
+ platform_type: Union[str, None] = None, language: Union[str, None] = None,
59
+ tag: Union[str, None] = None, path: Union[str, None] = None,
60
+ resources_path: Union[str, None] = None, dockerfile: Union[str, None] = None,
61
+ prefix: Union[str, None] = None, version: Union[str, None] = None,
62
+ http_port: Union[int, None] = None,
63
+ byosnap_profile: Union[str, None] = None, skip_build: bool = False,
64
+ readiness_path: Union[str, None] = None, readiness_delay: Union[int, None] = None,
65
+ snapend_id: Union[str, None] = None, blocking: bool = False
56
66
  ) -> None:
57
67
  self.subcommand: str = subcommand
58
68
  self.base_url: str = base_url
59
- self.api_key: str = api_key
69
+ self.api_key: Union[str, None] = api_key
60
70
  self.sid: str = sid
61
71
  self.name: str = name
62
72
  self.desc: str = desc
@@ -71,7 +81,7 @@ class ByoSnap:
71
81
  self.token: Union[str, None] = None
72
82
  self.token_parts: Union[list, None] = ByoSnap._get_token_values(
73
83
  self.token) if self.token is not None else None
74
- self.input_tag: Union[str, None] = input_tag
84
+ self.tag: Union[str, None] = tag
75
85
  self.path: Union[str, None] = path
76
86
  self.resources_path: Union[str, None] = resources_path
77
87
  self.dockerfile: str = dockerfile
@@ -82,6 +92,8 @@ class ByoSnap:
82
92
  self.skip_build: bool = skip_build
83
93
  self.readiness_path: Union[str, None] = readiness_path
84
94
  self.readiness_delay: Union[int, None] = readiness_delay
95
+ self.snapend_id: Union[str, None] = snapend_id
96
+ self.blocking: bool = blocking
85
97
  # Validate the input
86
98
  self.validate_input()
87
99
 
@@ -126,13 +138,18 @@ class ByoSnap:
126
138
  "docker", "info"
127
139
  ], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
128
140
  if not result.returncode:
129
- return snapctl_success('BYOSnap dependencies verified',
130
- progress, no_exit=True)
141
+ return snapctl_success(
142
+ message='BYOSnap dependencies verified',
143
+ progress=progress, no_exit=True)
131
144
  except subprocess.CalledProcessError:
132
- snapctl_error('Snapctl Exception',
133
- SNAPCTL_BYOSNAP_DEPENDENCY_MISSING, progress)
134
- snapctl_error('Docker not running. Please start docker.',
135
- SNAPCTL_BYOSNAP_DEPENDENCY_MISSING, progress)
145
+ snapctl_error(
146
+ message='Snapctl Exception',
147
+ code=SNAPCTL_BYOSNAP_DEPENDENCY_MISSING, progress=progress)
148
+ finally:
149
+ progress.stop()
150
+ snapctl_error(
151
+ message='Docker not running. Please start docker.',
152
+ code=SNAPCTL_BYOSNAP_DEPENDENCY_MISSING, progress=progress)
136
153
 
137
154
  def _docker_login(self) -> None:
138
155
  """
@@ -162,17 +179,22 @@ class ByoSnap:
162
179
  f'--username {ecr_repo_username} --password-stdin {ecr_repo_url}'
163
180
  ], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
164
181
  if not response.returncode:
165
- return snapctl_success('BYOSnap ECR login successful',
166
- progress, no_exit=True)
182
+ return snapctl_success(
183
+ message='BYOSnap ECR login successful',
184
+ progress=progress, no_exit=True)
167
185
  except subprocess.CalledProcessError:
168
- snapctl_error('Snapctl Exception',
169
- SNAPCTL_BYOSNAP_ECR_LOGIN_ERROR, progress)
170
- snapctl_error('BYOSnap ECR login failure',
171
- SNAPCTL_BYOSNAP_ECR_LOGIN_ERROR, progress)
186
+ snapctl_error(
187
+ message='Snapctl Exception',
188
+ code=SNAPCTL_BYOSNAP_ECR_LOGIN_ERROR, progress=progress)
189
+ finally:
190
+ progress.stop()
191
+ snapctl_error(
192
+ message='BYOSnap ECR login failure',
193
+ code=SNAPCTL_BYOSNAP_ECR_LOGIN_ERROR, progress=progress)
172
194
 
173
195
  def _docker_build(self) -> None:
174
196
  # Get the data
175
- # image_tag = f'{self.sid}.{self.input_tag}'
197
+ # image_tag = f'{self.sid}.{self.tag}'
176
198
  build_platform = ByoSnap.DEFAULT_BUILD_PLATFORM
177
199
  if len(self.token_parts) == 4:
178
200
  build_platform = self.token_parts[3]
@@ -202,7 +224,7 @@ class ByoSnap:
202
224
  if platform == "win32":
203
225
  response = subprocess.run([
204
226
  # f"docker build --no-cache -t {tag} {path}"
205
- 'docker', 'build', '--platform', build_platform, '-t', self.input_tag,
227
+ 'docker', 'build', '--platform', build_platform, '-t', self.tag,
206
228
  '-f', docker_file_path, self.path
207
229
  ], shell=True, check=False)
208
230
  # stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
@@ -210,23 +232,27 @@ class ByoSnap:
210
232
  response = subprocess.run([
211
233
  # f"docker build --no-cache -t {tag} {path}"
212
234
  "docker build --platform " +
213
- f"{build_platform} -t {self.input_tag} " +
235
+ f"{build_platform} -t {self.tag} " +
214
236
  f"-f {docker_file_path} {self.path}"
215
237
  ], shell=True, check=False)
216
238
  # stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
217
239
  if not response.returncode:
218
- return snapctl_success('BYOSnap build successful',
219
- progress, no_exit=True)
240
+ return snapctl_success(
241
+ message='BYOSnap build successful', progress=progress, no_exit=True)
220
242
  except subprocess.CalledProcessError:
221
- snapctl_error('Snapctl Exception',
222
- SNAPCTL_BYOSNAP_BUILD_ERROR, progress)
223
- snapctl_error('BYOSnap build failure',
224
- SNAPCTL_BYOSNAP_BUILD_ERROR, progress)
243
+ snapctl_error(
244
+ message='Snapctl Exception',
245
+ code=SNAPCTL_BYOSNAP_BUILD_ERROR, progress=progress)
246
+ finally:
247
+ progress.stop()
248
+ snapctl_error(
249
+ message='BYOSnap build failure',
250
+ code=SNAPCTL_BYOSNAP_BUILD_ERROR, progress=progress)
225
251
 
226
252
  def _docker_tag(self) -> None:
227
253
  # Get the data
228
254
  ecr_repo_url = self.token_parts[0]
229
- image_tag = f'{self.sid}.{self.input_tag}'
255
+ image_tag = f'{self.sid}.{self.tag}'
230
256
  full_ecr_repo_url = f'{ecr_repo_url}:{image_tag}'
231
257
  progress = Progress(
232
258
  SpinnerColumn(),
@@ -240,20 +266,24 @@ class ByoSnap:
240
266
  # Tag the repo
241
267
  if platform == "win32":
242
268
  response = subprocess.run([
243
- 'docker', 'tag', self.input_tag, full_ecr_repo_url
269
+ 'docker', 'tag', self.tag, full_ecr_repo_url
244
270
  ], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
245
271
  else:
246
272
  response = subprocess.run([
247
- f"docker tag {self.input_tag} {full_ecr_repo_url}"
273
+ f"docker tag {self.tag} {full_ecr_repo_url}"
248
274
  ], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
249
275
  if not response.returncode:
250
- return snapctl_success('BYOSnap tag successful',
251
- progress, no_exit=True)
276
+ return snapctl_success(
277
+ message='BYOSnap tag successful', progress=progress, no_exit=True)
252
278
  except subprocess.CalledProcessError:
253
- snapctl_error('Snapctl Exception',
254
- SNAPCTL_BYOSNAP_TAG_ERROR, progress)
255
- snapctl_error('BYOSnap tag failure',
256
- SNAPCTL_BYOSNAP_TAG_ERROR, progress)
279
+ snapctl_error(
280
+ message='Snapctl Exception',
281
+ code=SNAPCTL_BYOSNAP_TAG_ERROR, progress=progress)
282
+ finally:
283
+ progress.stop()
284
+ snapctl_error(
285
+ message='BYOSnap tag failure',
286
+ code=SNAPCTL_BYOSNAP_TAG_ERROR, progress=progress)
257
287
 
258
288
  def _docker_push(self) -> bool:
259
289
  """
@@ -269,7 +299,7 @@ class ByoSnap:
269
299
  try:
270
300
  # Push the image
271
301
  ecr_repo_url = self.token_parts[0]
272
- image_tag = f'{self.sid}.{self.input_tag}'
302
+ image_tag = f'{self.sid}.{self.tag}'
273
303
  full_ecr_repo_url = f'{ecr_repo_url}:{image_tag}'
274
304
  if platform == "win32":
275
305
  response = subprocess.run([
@@ -282,25 +312,30 @@ class ByoSnap:
282
312
  ], shell=True, check=False)
283
313
  # stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
284
314
  if not response.returncode:
285
- return snapctl_success('BYOSnap upload successful',
286
- progress, no_exit=True)
315
+ return snapctl_success(
316
+ message='BYOSnap upload successful', progress=progress, no_exit=True)
287
317
  except subprocess.CalledProcessError:
288
- snapctl_error('Snapctl Exception',
289
- SNAPCTL_BYOSNAP_PUBLISH_IMAGE_ERROR, progress)
290
- snapctl_error('BYOSnap upload failure. Duplicate image error.',
291
- SNAPCTL_BYOSNAP_PUBLISH_IMAGE_DUPLICATE_TAG_ERROR, progress)
318
+ snapctl_error(
319
+ message='Snapctl Exception',
320
+ code=SNAPCTL_BYOSNAP_PUBLISH_IMAGE_ERROR, progress=progress)
321
+ finally:
322
+ progress.stop()
323
+ snapctl_error(
324
+ message='BYOSnap upload failure. Duplicate image error.',
325
+ code=SNAPCTL_BYOSNAP_PUBLISH_IMAGE_DUPLICATE_TAG_ERROR, progress=progress)
292
326
 
293
327
  def _validate_byosnap_profile(self) -> None:
294
328
  """
295
329
  Validate the BYOSnap profile
296
330
  """
297
331
  if not self.byosnap_profile:
298
- snapctl_error("Missing BYOSnap profile path", SNAPCTL_INPUT_ERROR)
332
+ snapctl_error(
333
+ message="Missing BYOSnap profile path", code=SNAPCTL_INPUT_ERROR)
299
334
  if not os.path.isfile(self.byosnap_profile):
300
335
  snapctl_error(
301
- "Unable to find BYOSnap profile " +
336
+ message="Unable to find BYOSnap profile " +
302
337
  f"JSON at path {self.byosnap_profile}",
303
- SNAPCTL_INPUT_ERROR
338
+ code=SNAPCTL_INPUT_ERROR
304
339
  )
305
340
  profile_data = None
306
341
  with open(self.byosnap_profile, 'rb') as file:
@@ -310,15 +345,15 @@ class ByoSnap:
310
345
  pass
311
346
  if not profile_data:
312
347
  snapctl_error(
313
- 'Invalid BYOSnap profile JSON. Please check the JSON structure',
314
- SNAPCTL_INPUT_ERROR
348
+ message='Invalid BYOSnap profile JSON. Please check the JSON structure',
349
+ code=SNAPCTL_INPUT_ERROR
315
350
  )
316
351
  if 'dev_template' not in profile_data or \
317
352
  'stage_template' not in profile_data or \
318
353
  'prod_template' not in profile_data:
319
354
  snapctl_error(
320
- 'Invalid BYOSnap profile JSON. Please check the JSON structure',
321
- SNAPCTL_INPUT_ERROR
355
+ message='Invalid BYOSnap profile JSON. Please check the JSON structure',
356
+ code=SNAPCTL_INPUT_ERROR
322
357
  )
323
358
  for profile in ['dev_template', 'stage_template', 'prod_template']:
324
359
  # Currently, not checking for 'min_replicas' not in profile_data[profile]
@@ -328,30 +363,30 @@ class ByoSnap:
328
363
  'args' not in profile_data[profile] or \
329
364
  'env_params' not in profile_data[profile]:
330
365
  snapctl_error(
331
- 'Invalid BYOSnap profile JSON. Please check the JSON structure',
332
- SNAPCTL_INPUT_ERROR
366
+ message='Invalid BYOSnap profile JSON. Please check the JSON structure',
367
+ code=SNAPCTL_INPUT_ERROR
333
368
  )
334
369
  if profile_data[profile]['cpu'] not in ByoSnap.VALID_CPU_MARKS:
335
370
  snapctl_error(
336
- 'Invalid CPU value in BYOSnap profile. Valid values are' +
371
+ message='Invalid CPU value in BYOSnap profile. Valid values are' +
337
372
  f'{", ".join(map(str, ByoSnap.VALID_CPU_MARKS))}',
338
- SNAPCTL_INPUT_ERROR
373
+ code=SNAPCTL_INPUT_ERROR
339
374
  )
340
375
  if profile_data[profile]['memory'] not in ByoSnap.VALID_MEMORY_MARKS:
341
376
  snapctl_error(
342
- 'Invalid Memory value in BYOSnap profile. Valid values are ' +
377
+ message='Invalid Memory value in BYOSnap profile. Valid values are ' +
343
378
  f'{", ".join(map(str, ByoSnap.VALID_MEMORY_MARKS))}',
344
- SNAPCTL_INPUT_ERROR
379
+ code=SNAPCTL_INPUT_ERROR
345
380
  )
346
381
  if 'min_replicas' in profile_data[profile] and \
347
382
  (not isinstance(profile_data[profile]['min_replicas'], int) or
348
383
  int(profile_data[profile]['min_replicas']) < 0 or
349
384
  int(profile_data[profile]['min_replicas']) > ByoSnap.MAX_MIN_REPLICAS):
350
385
  snapctl_error(
351
- 'Invalid Min Replicas value in BYOSnap profile. ' +
386
+ message='Invalid Min Replicas value in BYOSnap profile. ' +
352
387
  'Minimum replicas should be between 0 and ' +
353
388
  f'{ByoSnap.MAX_MIN_REPLICAS}',
354
- SNAPCTL_INPUT_ERROR
389
+ code=SNAPCTL_INPUT_ERROR
355
390
  )
356
391
 
357
392
  def _clean_slate(self) -> None:
@@ -375,7 +410,8 @@ class ByoSnap:
375
410
  f"docker logout {ecr_domain}"
376
411
  ], shell=True, check=False)
377
412
  if not logout_response.returncode:
378
- return snapctl_success('Cleanup complete.', progress, no_exit=True)
413
+ return snapctl_success(
414
+ message='Cleanup complete.', progress=progress, no_exit=True)
379
415
  except subprocess.CalledProcessError:
380
416
  warning('Unable to initialize with a clean slate.')
381
417
  finally:
@@ -390,130 +426,183 @@ class ByoSnap:
390
426
  """
391
427
  # Check API Key and Base URL
392
428
  if not self.api_key or self.base_url == '':
393
- snapctl_error("Missing API Key.", SNAPCTL_INPUT_ERROR)
429
+ snapctl_error(
430
+ message="Missing API Key.", code=SNAPCTL_INPUT_ERROR)
394
431
  # Check subcommand
395
432
  if not self.subcommand in ByoSnap.SUBCOMMANDS:
396
433
  snapctl_error(
397
- "Invalid command. Valid commands are " +
434
+ message="Invalid command. Valid commands are " +
398
435
  f"{', '.join(ByoSnap.SUBCOMMANDS)}.",
399
- SNAPCTL_INPUT_ERROR
436
+ code=SNAPCTL_INPUT_ERROR
400
437
  )
401
438
  # Validate the SID
402
439
  if not self.sid.startswith(ByoSnap.ID_PREFIX):
403
440
  snapctl_error(
404
- "Invalid Snap ID. Valid Snap IDs start with " +
441
+ message="Invalid Snap ID. Valid Snap IDs start with " +
405
442
  f"{ByoSnap.ID_PREFIX}.",
406
- SNAPCTL_INPUT_ERROR
443
+ code=SNAPCTL_INPUT_ERROR
407
444
  )
408
445
  if len(self.sid) > ByoSnap.SID_CHARACTER_LIMIT:
409
446
  snapctl_error(
410
- "Invalid Snap ID. Snap ID should be less than " +
447
+ message="Invalid Snap ID. Snap ID should be less than " +
411
448
  f"{ByoSnap.SID_CHARACTER_LIMIT} characters",
412
- SNAPCTL_INPUT_ERROR
449
+ code=SNAPCTL_INPUT_ERROR
413
450
  )
414
451
  # Validation for subcommands
415
452
  if self.subcommand == 'create':
416
453
  if self.name == '':
417
- snapctl_error("Missing name", SNAPCTL_INPUT_ERROR)
454
+ snapctl_error(message="Missing name", code=SNAPCTL_INPUT_ERROR)
418
455
  if not self.language:
419
- snapctl_error("Missing language", SNAPCTL_INPUT_ERROR)
456
+ snapctl_error(message="Missing language",
457
+ code=SNAPCTL_INPUT_ERROR)
420
458
  if self.language not in ByoSnap.LANGUAGES:
421
459
  snapctl_error(
422
- "Invalid language. Valid languages are " +
460
+ message="Invalid language. Valid languages are " +
423
461
  f"{', '.join(ByoSnap.LANGUAGES)}.",
424
- SNAPCTL_INPUT_ERROR
462
+ code=SNAPCTL_INPUT_ERROR
425
463
  )
426
464
  if self.platform_type not in ByoSnap.PLATFORMS:
427
465
  snapctl_error(
428
- "Invalid platform. Valid platforms are " +
466
+ message="Invalid platform. Valid platforms are " +
429
467
  f"{', '.join(ByoSnap.PLATFORMS)}.",
468
+ code=SNAPCTL_INPUT_ERROR
469
+ )
470
+ return
471
+ # Now validate all other commands Check the token
472
+ if self.token_parts is None:
473
+ snapctl_error('Invalid token. Please reach out to your support team.',
474
+ SNAPCTL_INPUT_ERROR)
475
+ if self.subcommand in ['publish-image']:
476
+ if not self.tag:
477
+ snapctl_error(
478
+ "Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
479
+ if len(self.tag.split()) > 1 or \
480
+ len(self.tag) > ByoSnap.TAG_CHARACTER_LIMIT:
481
+ snapctl_error(
482
+ "Tag should be a single word with maximum of " +
483
+ f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
430
484
  SNAPCTL_INPUT_ERROR
431
485
  )
432
- else:
433
- # Check the token
434
- if self.token_parts is None:
435
- snapctl_error('Invalid token. Please reach out to your support team.',
486
+ if not self.skip_build and not self.path:
487
+ snapctl_error("Missing required parameter: path",
436
488
  SNAPCTL_INPUT_ERROR)
437
- if self.subcommand in ['publish-image']:
438
- if not self.input_tag:
439
- snapctl_error(
440
- "Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
441
- if len(self.input_tag.split()) > 1 or \
442
- len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
443
- snapctl_error(
444
- "Tag should be a single word with maximum of " +
445
- f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
446
- SNAPCTL_INPUT_ERROR
447
- )
448
- if not self.skip_build and not self.path:
449
- snapctl_error("Missing required parameter: path",
450
- SNAPCTL_INPUT_ERROR)
451
- # Check path
452
- if self.resources_path:
453
- docker_file_path = \
454
- f"{self.resources_path}/{self.dockerfile}"
455
- else:
456
- docker_file_path = f"{self.path}/{self.dockerfile}"
457
- if not self.skip_build and not os.path.isfile(docker_file_path):
458
- snapctl_error(
459
- "Unable to find " +
460
- f"{self.dockerfile} at path {docker_file_path}",
461
- SNAPCTL_INPUT_ERROR)
462
- # elif self.subcommand == 'push':
463
- # if not self.input_tag:
464
- # error("Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
465
- # raise typer.Exit(code=SNAPCTL_INPUT_ERROR)
466
- elif self.subcommand == 'upload-docs':
467
- if self.path is None and self.resources_path is None:
468
- snapctl_error(
469
- "Missing one of: path or resources-path parameter", SNAPCTL_INPUT_ERROR)
470
- elif self.subcommand == 'publish-version':
471
- if not self.input_tag:
472
- snapctl_error(
473
- "Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
474
- if len(self.input_tag.split()) > 1 or \
475
- len(self.input_tag) > ByoSnap.TAG_CHARACTER_LIMIT:
476
- snapctl_error(
477
- "Tag should be a single word with maximum of " +
478
- f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
479
- SNAPCTL_INPUT_ERROR
480
- )
481
- if not self.prefix or self.prefix == '':
482
- snapctl_error("Missing prefix", SNAPCTL_INPUT_ERROR)
483
- if not self.prefix.startswith('/'):
484
- snapctl_error("Prefix should start with a forward slash (/)",
485
- SNAPCTL_INPUT_ERROR)
486
- if self.prefix.endswith('/'):
487
- snapctl_error("Prefix should not end with a forward slash (/)",
488
- SNAPCTL_INPUT_ERROR)
489
- if not self.version:
490
- snapctl_error("Missing version", SNAPCTL_INPUT_ERROR)
491
- pattern = r'^v\d+\.\d+\.\d+$'
492
- if not re.match(pattern, self.version):
493
- snapctl_error("Version should be in the format vX.X.X",
494
- SNAPCTL_INPUT_ERROR)
495
- if not self.http_port:
496
- snapctl_error("Missing Ingress HTTP Port",
489
+ # Check path
490
+ if self.resources_path:
491
+ docker_file_path = \
492
+ f"{self.resources_path}/{self.dockerfile}"
493
+ else:
494
+ docker_file_path = f"{self.path}/{self.dockerfile}"
495
+ if not self.skip_build and not os.path.isfile(docker_file_path):
496
+ snapctl_error(
497
+ "Unable to find " +
498
+ f"{self.dockerfile} at path {docker_file_path}",
499
+ SNAPCTL_INPUT_ERROR)
500
+ # elif self.subcommand == 'push':
501
+ # if not self.tag:
502
+ # error("Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
503
+ # raise typer.Exit(code=SNAPCTL_INPUT_ERROR)
504
+ elif self.subcommand == 'upload-docs':
505
+ if self.path is None and self.resources_path is None:
506
+ snapctl_error(
507
+ "Missing one of: path or resources-path parameter", SNAPCTL_INPUT_ERROR)
508
+ elif self.subcommand == 'publish-version':
509
+ if not self.tag:
510
+ snapctl_error(
511
+ "Missing required parameter: tag", SNAPCTL_INPUT_ERROR)
512
+ if len(self.tag.split()) > 1 or \
513
+ len(self.tag) > ByoSnap.TAG_CHARACTER_LIMIT:
514
+ snapctl_error(
515
+ "Tag should be a single word with maximum of " +
516
+ f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
517
+ SNAPCTL_INPUT_ERROR
518
+ )
519
+ if not self.prefix or self.prefix == '':
520
+ snapctl_error("Missing prefix", SNAPCTL_INPUT_ERROR)
521
+ if not self.prefix.startswith('/'):
522
+ snapctl_error("Prefix should start with a forward slash (/)",
523
+ SNAPCTL_INPUT_ERROR)
524
+ if self.prefix.endswith('/'):
525
+ snapctl_error("Prefix should not end with a forward slash (/)",
526
+ SNAPCTL_INPUT_ERROR)
527
+ if not self.version:
528
+ snapctl_error("Missing version", SNAPCTL_INPUT_ERROR)
529
+ pattern = r'^v\d+\.\d+\.\d+$'
530
+ if not re.match(pattern, self.version):
531
+ snapctl_error("Version should be in the format vX.X.X",
532
+ SNAPCTL_INPUT_ERROR)
533
+ if not self.http_port:
534
+ snapctl_error("Missing Ingress HTTP Port",
535
+ SNAPCTL_INPUT_ERROR)
536
+ if not self.http_port.isdigit():
537
+ snapctl_error("Ingress HTTP Port should be a number",
538
+ SNAPCTL_INPUT_ERROR)
539
+ if self.readiness_path is not None:
540
+ if self.readiness_path.strip() == '':
541
+ snapctl_error("Readiness path cannot be empty",
497
542
  SNAPCTL_INPUT_ERROR)
498
- if not self.http_port.isdigit():
499
- snapctl_error("Ingress HTTP Port should be a number",
543
+ if not self.readiness_path.strip().startswith('/'):
544
+ snapctl_error("Readiness path has to start with /",
500
545
  SNAPCTL_INPUT_ERROR)
501
- if self.readiness_path is not None:
502
- if self.readiness_path.strip() == '':
503
- snapctl_error("Readiness path cannot be empty",
504
- SNAPCTL_INPUT_ERROR)
505
- if not self.readiness_path.strip().startswith('/'):
506
- snapctl_error("Readiness path has to start with /",
507
- SNAPCTL_INPUT_ERROR)
508
- if self.readiness_delay is not None:
509
- if self.readiness_delay < 0 or \
510
- self.readiness_delay > ByoSnap.MAX_READINESS_TIMEOUT:
511
- snapctl_error(
512
- "Readiness delay should be between 0 " +
513
- f"and {ByoSnap.MAX_READINESS_TIMEOUT}", SNAPCTL_INPUT_ERROR)
514
- # Check byosnap_profile path
515
- self._validate_byosnap_profile()
516
-
546
+ if self.readiness_delay is not None:
547
+ if self.readiness_delay < 0 or \
548
+ self.readiness_delay > ByoSnap.MAX_READINESS_TIMEOUT:
549
+ snapctl_error(
550
+ "Readiness delay should be between 0 " +
551
+ f"and {ByoSnap.MAX_READINESS_TIMEOUT}", SNAPCTL_INPUT_ERROR)
552
+ # Check byosnap_profile path
553
+ self._validate_byosnap_profile()
554
+ elif self.subcommand == 'update-version':
555
+ if not self.version:
556
+ snapctl_error(message="Missing version",
557
+ code=SNAPCTL_INPUT_ERROR)
558
+ pattern = r'^v\d+\.\d+\.\d+$'
559
+ if not re.match(pattern, self.version):
560
+ snapctl_error(
561
+ message="Version should be in the format vX.X.X",
562
+ code=SNAPCTL_INPUT_ERROR)
563
+ if not self.tag:
564
+ snapctl_error(
565
+ message="Missing required parameter: tag", code=SNAPCTL_INPUT_ERROR)
566
+ if len(self.tag.split()) > 1 or \
567
+ len(self.tag) > ByoSnap.TAG_CHARACTER_LIMIT:
568
+ snapctl_error(
569
+ message="Tag should be a single word with maximum of " +
570
+ f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
571
+ code=SNAPCTL_INPUT_ERROR
572
+ )
573
+ elif self.subcommand == 'sync':
574
+ if not self.version:
575
+ snapctl_error(message="Missing version",
576
+ code=SNAPCTL_INPUT_ERROR)
577
+ pattern = r'^v\d+\.\d+\.\d+$'
578
+ if not re.match(pattern, self.version):
579
+ snapctl_error(message="Version should be in the format vX.X.X",
580
+ code=SNAPCTL_INPUT_ERROR)
581
+ if not self.tag:
582
+ snapctl_error(
583
+ message="Missing required parameter: tag", code=SNAPCTL_INPUT_ERROR)
584
+ if len(self.tag.split()) > 1 or \
585
+ len(self.tag) > ByoSnap.TAG_CHARACTER_LIMIT:
586
+ snapctl_error(
587
+ message="Tag should be a single word with maximum of " +
588
+ f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
589
+ code=SNAPCTL_INPUT_ERROR
590
+ )
591
+ if not self.skip_build and not self.path:
592
+ snapctl_error(
593
+ message="Missing required parameter: path",
594
+ code=SNAPCTL_INPUT_ERROR)
595
+ # Check path
596
+ if self.resources_path:
597
+ docker_file_path = \
598
+ f"{self.resources_path}/{self.dockerfile}"
599
+ else:
600
+ docker_file_path = f"{self.path}/{self.dockerfile}"
601
+ if not self.skip_build and not os.path.isfile(docker_file_path):
602
+ snapctl_error(
603
+ message="Unable to find " +
604
+ f"{self.dockerfile} at path {docker_file_path}",
605
+ code=SNAPCTL_INPUT_ERROR)
517
606
  # CRUD methods
518
607
 
519
608
  def build(self) -> None:
@@ -541,9 +630,9 @@ class ByoSnap:
541
630
  self._docker_push()
542
631
 
543
632
  # Upper echelon commands
544
- def upload_docs(self) -> None:
633
+ def upload_docs(self, no_exit: bool = False) -> None:
545
634
  '''
546
- Note this step is optional hence we do not raise a typer.Exit
635
+ Note this step is optional hence we do not raise a typer.Exit
547
636
  '''
548
637
  progress = Progress(
549
638
  SpinnerColumn(),
@@ -562,23 +651,24 @@ class ByoSnap:
562
651
  swagger_file = os.path.join(base_dir, 'swagger.json')
563
652
  readme_file = os.path.join(base_dir, 'README.md')
564
653
 
654
+ # Upload swagger.json
565
655
  if os.path.isfile(swagger_file):
566
- # Push the swagger.json
567
656
  try:
568
- attachment_file = open(swagger_file, "rb")
569
- url = (
570
- f"{self.base_url}/v1/snapser-api/byosnaps/"
571
- f"{self.sid}/docs/{self.input_tag}/openapispec"
572
- )
573
- test_res = requests.post(
574
- url, files={"attachment": attachment_file},
575
- headers={'api-key': self.api_key},
576
- timeout=SERVER_CALL_TIMEOUT
577
- )
578
- if test_res.ok:
579
- success('Uploaded swagger.json')
580
- else:
581
- info('Unable to upload your swagger.json')
657
+ with open(swagger_file, "rb") as attachment_file:
658
+ url = (
659
+ f"{self.base_url}/v1/snapser-api/byosnaps/"
660
+ f"{self.sid}/docs/{self.tag}/openapispec"
661
+ )
662
+ test_res = requests.post(
663
+ url, files={"attachment": attachment_file},
664
+ headers={'api-key': self.api_key},
665
+ timeout=SERVER_CALL_TIMEOUT
666
+ )
667
+ if test_res.ok:
668
+ snapctl_success(
669
+ message='Uploaded swagger.json', progress=None, no_exit=True)
670
+ else:
671
+ info('Unable to upload your swagger.json')
582
672
  except RequestException as e:
583
673
  info(
584
674
  'Exception: Unable to find swagger.json at ' +
@@ -586,28 +676,28 @@ class ByoSnap:
586
676
  )
587
677
  else:
588
678
  info(
589
- f'No swagger.json found at {base_dir}' +
590
- '. Skipping swagger.json upload'
679
+ f'No swagger.json found at {
680
+ base_dir}. Skipping swagger.json upload'
591
681
  )
592
682
 
593
- # Push the README.md
683
+ # Upload README.md
594
684
  if os.path.isfile(readme_file):
595
- # Push the swagger.json
596
685
  try:
597
- attachment_file = open(readme_file, "rb")
598
- url = (
599
- f"{self.base_url}/v1/snapser-api/byosnaps/"
600
- f"{self.sid}/docs/{self.input_tag}/markdown"
601
- )
602
- test_res = requests.post(
603
- url, files={"attachment": attachment_file},
604
- headers={'api-key': self.api_key},
605
- timeout=SERVER_CALL_TIMEOUT
606
- )
607
- if test_res.ok:
608
- success('Uploaded README.md')
609
- else:
610
- info('Unable to upload your README.md')
686
+ with open(readme_file, "rb") as attachment_file:
687
+ url = (
688
+ f"{self.base_url}/v1/snapser-api/byosnaps/"
689
+ f"{self.sid}/docs/{self.tag}/markdown"
690
+ )
691
+ test_res = requests.post(
692
+ url, files={"attachment": attachment_file},
693
+ headers={'api-key': self.api_key},
694
+ timeout=SERVER_CALL_TIMEOUT
695
+ )
696
+ if test_res.ok:
697
+ snapctl_success(
698
+ message='Uploaded README.md', progress=None, no_exit=True)
699
+ else:
700
+ info('Unable to upload your README.md')
611
701
  except RequestException as e:
612
702
  info(
613
703
  'Exception: Unable to find README.md at ' +
@@ -615,14 +705,45 @@ class ByoSnap:
615
705
  )
616
706
  else:
617
707
  info(
618
- f'No README.md found at {base_dir}. Skipping README.md upload')
708
+ f'No README.md found at {
709
+ base_dir}. Skipping README.md upload'
710
+ )
711
+
712
+ # Upload any snapser-tool-*.json files
713
+ for file_name in os.listdir(base_dir):
714
+ if file_name.startswith("snapser-tool-") and file_name.endswith(".json"):
715
+ file_path = os.path.join(base_dir, file_name)
716
+ if os.path.isfile(file_path):
717
+ try:
718
+ with open(file_path, "rb") as attachment_file:
719
+ url = (
720
+ f"{self.base_url}/v1/snapser-api/byosnaps/"
721
+ f"{self.sid}/docs/{self.tag}/tools"
722
+ )
723
+ test_res = requests.post(
724
+ url, files={"attachment": attachment_file},
725
+ headers={'api-key': self.api_key},
726
+ timeout=SERVER_CALL_TIMEOUT
727
+ )
728
+ if test_res.ok:
729
+ snapctl_success(
730
+ message=f'Uploaded tool {file_name}',
731
+ progress=None, no_exit=True)
732
+ else:
733
+ info(f'Unable to upload tool {file_name}')
734
+ except RequestException as e:
735
+ info('Exception: Unable to upload tool ' +
736
+ f'{file_name} {str(e)}')
737
+
738
+ # Show success message
739
+ snapctl_success(
740
+ message='Completed the docs uploading process', progress=progress, no_exit=no_exit)
619
741
  except RequestException as e:
620
742
  info(f'Exception: Unable to upload your API Json {str(e)}')
621
743
  finally:
622
744
  progress.stop()
623
- # snapctl_success('BYOSnap upload successful', no_exit=no_exit)
624
745
 
625
- def create(self) -> None:
746
+ def create(self, no_exit: bool = False) -> None:
626
747
  """
627
748
  Creating a new snap
628
749
  """
@@ -647,34 +768,44 @@ class ByoSnap:
647
768
  timeout=SERVER_CALL_TIMEOUT
648
769
  )
649
770
  if res.ok:
650
- snapctl_success('BYOSNAP create successful', progress)
771
+ return snapctl_success(
772
+ message='BYOSNAP create successful', progress=progress, no_exit=no_exit)
651
773
  response_json = res.json()
652
774
  if "api_error_code" in response_json and "message" in response_json:
775
+ if response_json['api_error_code'] == HTTP_ERROR_RESOURCE_NOT_FOUND:
776
+ snapctl_error(
777
+ message='BYOSnap not found.',
778
+ code=SNAPCTL_BYOSNAP_NOT_FOUND, progress=progress
779
+ )
653
780
  if response_json['api_error_code'] == HTTP_ERROR_SERVICE_VERSION_EXISTS:
654
781
  snapctl_error(
655
- f'BYOSnap {self.name} already exists. ' +
782
+ message=f'BYOSnap {self.name} already exists. ' +
656
783
  'Please use a different name',
657
- SNAPCTL_BYOSNAP_CREATE_DUPLICATE_NAME_ERROR,
658
- progress
784
+ code=SNAPCTL_BYOSNAP_CREATE_DUPLICATE_NAME_ERROR,
785
+ progress=progress
659
786
  )
660
787
  # elif response_json['api_error_code'] == HTTP_ERROR_TAG_NOT_AVAILABLE:
661
788
  # error('Invalid tag. Please use the correct tag')
662
789
  if response_json['api_error_code'] == HTTP_ERROR_ADD_ON_NOT_ENABLED:
663
790
  snapctl_error(
664
- 'Missing Add-on. Please enable the add-on via the Snapser Web app.',
665
- SNAPCTL_BYOSNAP_CREATE_PERMISSION_ERROR, progress
791
+ message='Missing Add-on. Please enable the add-on via the Snapser Web app.',
792
+ code=SNAPCTL_BYOSNAP_CREATE_PERMISSION_ERROR,
793
+ progress=progress
666
794
  )
667
795
  snapctl_error(
668
- f'Server error: {json.dumps(response_json, indent=2)}',
669
- SNAPCTL_BYOSNAP_CREATE_ERROR, progress)
796
+ message=f'Server error: {json.dumps(response_json, indent=2)}',
797
+ code=SNAPCTL_BYOSNAP_CREATE_ERROR, progress=progress)
670
798
  except RequestException as e:
671
799
  snapctl_error(
672
- f"Exception: Unable to create your snap {e}",
673
- SNAPCTL_BYOSNAP_CREATE_ERROR, progress)
674
- snapctl_error('Failed to create snap',
675
- SNAPCTL_BYOSNAP_CREATE_ERROR, progress)
800
+ message=f"Exception: Unable to create your snap {e}",
801
+ code=SNAPCTL_BYOSNAP_CREATE_ERROR, progress=progress)
802
+ finally:
803
+ progress.stop()
804
+ snapctl_error(
805
+ message='Failed to create snap',
806
+ code=SNAPCTL_BYOSNAP_CREATE_ERROR, progress=progress)
676
807
 
677
- def publish_image(self) -> None:
808
+ def publish_image(self, no_exit: bool = False) -> None:
678
809
  """
679
810
  Publish the image
680
811
  1. Check Dependencies
@@ -694,10 +825,11 @@ class ByoSnap:
694
825
  self._docker_login()
695
826
  self._docker_push()
696
827
  if self.path is not None or self.resources_path is not None:
697
- self.upload_docs()
698
- snapctl_success('BYOSNAP publish successful')
828
+ self.upload_docs(no_exit=True)
829
+ snapctl_success(
830
+ message='BYOSNAP publish successful', no_exit=no_exit)
699
831
 
700
- def publish_version(self) -> None:
832
+ def publish_version(self, no_exit: bool = False) -> None:
701
833
  """
702
834
  Publish the version
703
835
  """
@@ -718,7 +850,7 @@ class ByoSnap:
718
850
  profile_data = json.load(file)
719
851
  payload = {
720
852
  "version": self.version,
721
- "image_tag": self.input_tag,
853
+ "image_tag": self.tag,
722
854
  "base_url": f"{self.prefix}/{self.sid}",
723
855
  "http_port": self.http_port,
724
856
  "readiness_probe_config": {
@@ -735,22 +867,115 @@ class ByoSnap:
735
867
  timeout=SERVER_CALL_TIMEOUT
736
868
  )
737
869
  if res.ok:
738
- snapctl_success('BYOSNAP publish version successful', progress)
870
+ return snapctl_success(
871
+ message='BYOSNAP publish version successful',
872
+ progress=progress, no_exit=no_exit)
739
873
  response_json = res.json()
740
874
  if "api_error_code" in response_json:
875
+ if response_json['api_error_code'] == HTTP_ERROR_RESOURCE_NOT_FOUND:
876
+ snapctl_error(
877
+ message='BYOSnap not found.',
878
+ code=SNAPCTL_BYOSNAP_NOT_FOUND, progress=progress
879
+ )
741
880
  if response_json['api_error_code'] == HTTP_ERROR_SERVICE_VERSION_EXISTS:
742
881
  snapctl_error(
743
- 'Version already exists. Please update your version and try again',
744
- SNAPCTL_BYOSNAP_PUBLISH_IMAGE_DUPLICATE_TAG_ERROR,
745
- progress
882
+ message='Version already exists. Please update your version and try again',
883
+ code=SNAPCTL_BYOSNAP_PUBLISH_IMAGE_DUPLICATE_TAG_ERROR,
884
+ progress=progress
746
885
  )
747
886
  if response_json['api_error_code'] == HTTP_ERROR_TAG_NOT_AVAILABLE:
748
- snapctl_error('Invalid tag. Please use the correct tag',
749
- SNAPCTL_BYOSNAP_PUBLISH_VERSION_DUPLICATE_TAG_ERROR, progress)
750
- snapctl_error(f'Server error: {json.dumps(response_json, indent=2)}',
751
- SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress)
887
+ snapctl_error(
888
+ message='Invalid tag. Please use the correct tag.',
889
+ code=SNAPCTL_BYOSNAP_PUBLISH_VERSION_DUPLICATE_TAG_ERROR,
890
+ progress=progress)
891
+ snapctl_error(
892
+ message=f'Server error: {json.dumps(response_json, indent=2)}',
893
+ code=SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress=progress)
752
894
  except RequestException as e:
753
- snapctl_error(f'Exception: Unable to publish a version for your snap {e}',
754
- SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress)
755
- snapctl_error('Failed to publish version',
756
- SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress)
895
+ snapctl_error(
896
+ message='Exception: Unable to publish a ' +
897
+ f'version for your snap. Exception: {e}',
898
+ code=SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress=progress)
899
+ finally:
900
+ progress.stop()
901
+ snapctl_error(
902
+ message='Failed to publish version',
903
+ code=SNAPCTL_BYOSNAP_PUBLISH_VERSION_ERROR, progress=progress)
904
+
905
+ def update_version(self, no_exit: bool = False) -> None:
906
+ """
907
+ Update the byosnap version
908
+ """
909
+ progress = Progress(
910
+ SpinnerColumn(),
911
+ TextColumn("[progress.description]{task.description}"),
912
+ transient=True,
913
+ )
914
+ progress.start()
915
+ progress.add_task(
916
+ description='Updating your Byosnap...', total=None)
917
+ try:
918
+ payload = {
919
+ 'image_tag': self.tag,
920
+ }
921
+ res = requests.patch(
922
+ f"{self.base_url}/v1/snapser-api/byosnaps/{self.sid}/versions/{self.version}",
923
+ json=payload, headers={'api-key': self.api_key},
924
+ timeout=SERVER_CALL_TIMEOUT
925
+ )
926
+ if res.ok:
927
+ return snapctl_success(
928
+ message='BYOSNAP update version successful',
929
+ progress=progress, no_exit=no_exit)
930
+ response_json = res.json()
931
+ if "api_error_code" in response_json:
932
+ if response_json['api_error_code'] == HTTP_ERROR_RESOURCE_NOT_FOUND:
933
+ snapctl_error(
934
+ message='BYOSnap not found.',
935
+ code=SNAPCTL_BYOSNAP_NOT_FOUND, progress=progress
936
+ )
937
+ if response_json['api_error_code'] == HTTP_ERROR_SERVICE_IN_USE:
938
+ snapctl_error(
939
+ message='Version already in use in a staging or production snapend. ' +
940
+ 'Please publish an unused version and start using that instead.',
941
+ code=SNAPCTL_BYOSNAP_UPDATE_VERSION_SERVICE_IN_USE_ERROR,
942
+ progress=progress
943
+ )
944
+ if response_json['api_error_code'] == HTTP_ERROR_TAG_NOT_AVAILABLE:
945
+ snapctl_error(
946
+ message='Invalid tag. Please use the correct tag.',
947
+ code=SNAPCTL_BYOSNAP_UPDATE_VERSION_TAG_ERROR, progress=progress)
948
+ snapctl_error(
949
+ message=f'Server error: {json.dumps(response_json, indent=2)}',
950
+ code=SNAPCTL_BYOSNAP_UPDATE_VERSION_ERROR, progress=progress)
951
+ except RequestException as e:
952
+ snapctl_error(
953
+ message='Exception: Unable to update a ' +
954
+ f'version for your snap. Exception: {e}',
955
+ code=SNAPCTL_BYOSNAP_UPDATE_VERSION_ERROR, progress=progress)
956
+ finally:
957
+ progress.stop()
958
+ snapctl_error(
959
+ message='Failed to update version',
960
+ code=SNAPCTL_BYOSNAP_UPDATE_VERSION_ERROR, progress=progress)
961
+
962
+ def sync(self) -> None:
963
+ '''
964
+ Sync the snap
965
+ '''
966
+ try:
967
+ self.tag = f'{self.tag}-{int(time.time())}'
968
+ self.publish_image(no_exit=True)
969
+ self.update_version(no_exit=True)
970
+ byosnap_list: str = f"{self.sid}:{self.version}"
971
+ snapend = Snapend(
972
+ subcommand='update', base_url=self.base_url, api_key=self.api_key,
973
+ snapend_id=self.snapend_id, byosnaps=byosnap_list, blocking=self.blocking
974
+ )
975
+ snapend.update(no_exit=True)
976
+ return snapctl_success(message='BYOSNAP sync successful')
977
+ except RequestException as e:
978
+ snapctl_error(
979
+ message='Exception: Unable to update a ' +
980
+ f' version for your snap. Exception: {e}',
981
+ code=SNAPCTL_BYOSNAP_UPDATE_VERSION_ERROR)