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.

@@ -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 ERROR_SERVICE_VERSION_EXISTS, ERROR_TAG_NOT_AVAILABLE, \
18
- ERROR_ADD_ON_NOT_ENABLED
19
- from snapctl.types.definitions import ResponseType
20
- from snapctl.utils.echo import error, success, info
21
- from snapctl.utils.helper import get_composite_token
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
- 'build', 'push', 'upload-docs',
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) -> bool:
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
- with Progress(
99
- SpinnerColumn(),
100
- TextColumn("[progress.description]{task.description}"),
101
- transient=True,
102
- ) as progress:
103
- progress.add_task(
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
- error('Unable to initialize docker')
116
- return False
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) -> bool:
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
- with Progress(
128
- SpinnerColumn(),
129
- TextColumn("[progress.description]{task.description}"),
130
- transient=True,
131
- ) as progress:
132
- progress.add_task(
133
- description='Logging into Snapser Image Registry...', total=None)
134
- if platform == 'win32':
135
- response = subprocess.run([
136
- 'docker', 'login', '--username', ecr_repo_username,
137
- '--password', ecr_repo_token, ecr_repo_url
138
- ], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
139
- else:
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
- error('Unable to initialize docker')
155
- return False
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) -> bool:
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
- with Progress(
166
- SpinnerColumn(),
167
- TextColumn("[progress.description]{task.description}"),
168
- transient=True,
169
- ) as progress:
170
- progress.add_task(
171
- description='Building your snap...', total=None)
172
- docker_file_path = f"{self.path}/{self.dockerfile}"
173
- if platform == "win32":
174
- response = subprocess.run([
175
- # f"docker build --no-cache -t {tag} {path}"
176
- 'docker', 'build', '--platform', build_platform, '-t', image_tag,
177
- '-f', docker_file_path, self.path
178
- ], shell=True, check=False)
179
- # stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
180
- else:
181
- response = subprocess.run([
182
- # f"docker build --no-cache -t {tag} {path}"
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
- error('CLI Error')
193
- return False
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) -> bool:
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
- with Progress(
203
- SpinnerColumn(),
204
- TextColumn("[progress.description]{task.description}"),
205
- transient=True,
206
- ) as progress:
207
- progress.add_task(
208
- description='Tagging your snap...', total=None)
209
- if platform == "win32":
210
- response = subprocess.run([
211
- 'docker', 'tag', image_tag, full_ecr_repo_url
212
- ], shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, check=False)
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
- error('CLI Error')
224
- return False
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
- ecr_repo_url = self.token_parts[0]
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
- ) as progress:
240
- progress.add_task(description='Pushing your snap...', total=None)
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
- error('Unable to push your snap')
253
- return False
254
- success('Snap Upload Successful')
255
- return True
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) -> ResponseType:
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
- response['msg'] = "Missing API Key."
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
- response['msg'] = (
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
- response['msg'] = f"Invalid Snap ID. Valid Snap IDs start with {ByoSnap.ID_PREFIX}."
283
- return response
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
- response['msg'] = (
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
- response['msg'] = "Missing name"
294
- return response
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
- response['msg'] = (
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
- response['msg'] = (
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
- response['msg'] = 'Invalid token. Please reach out to your support team.'
311
- return response
312
- # Check tag
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
- response['msg'] = "Missing required parameter: tag"
323
- return response
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
- response['msg'] = "Missing required parameter: path"
326
- return response
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
- response['msg'] = f"Unable to find {self.dockerfile} at path {self.path}"
330
- return response
331
- elif self.subcommand == 'push':
332
- if not self.input_tag:
333
- response['msg'] = "Missing required parameter: tag"
334
- return response
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
- response['msg'] = "Missing required parameter: path"
338
- return response
400
+ snapctl_error(
401
+ "Missing required parameter: path", SNAPCTL_INPUT_ERROR)
339
402
  elif self.subcommand == 'publish-version':
340
- if not self.prefix:
341
- response['msg'] = "Missing prefix"
342
- return response
343
- if not self.version:
344
- response['msg'] = "Missing version"
345
- return response
346
- if not self.http_port:
347
- response['msg'] = "Missing Ingress HTTP Port"
348
- return response
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
- response['msg'] = "Prefix should start with a forward slash (/)"
351
- return response
415
+ snapctl_error("Prefix should start with a forward slash (/)",
416
+ SNAPCTL_INPUT_ERROR)
352
417
  if self.prefix.endswith('/'):
353
- response['msg'] = "Prefix should not end with a forward slash (/)"
354
- return response
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
- response['msg'] = "Version should be in the format vX.X.X"
358
- return response
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
- response['msg'] = "Ingress HTTP Port should be a number"
361
- return response
362
- # Send success
363
- response['error'] = False
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
- def build(self) -> bool:
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
- if not self._check_dependencies() or not self._docker_login() or \
375
- not self._docker_build():
376
- return False
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
- if not self._check_dependencies() or not self._docker_login() or \
388
- not self._docker_tag() or not self._docker_push():
389
- return False
390
- return True
456
+ self._check_dependencies()
457
+ self._docker_login()
458
+ self._docker_tag()
459
+ self._docker_push()
391
460
 
392
- def upload_docs(self) -> bool:
461
+ # Upper echelon commands
462
+ def upload_docs(self) -> None:
393
463
  '''
394
- Note this step is optional hence we always respond with a True
464
+ Note this step is optional hence we do not raise a typer.Exit
395
465
  '''
396
- swagger_file = f"{self.path}/swagger.json"
397
- readme_file = f"{self.path}/README.md"
398
- if os.path.isfile(swagger_file):
399
- # Push the swagger.json
400
- with Progress(
401
- SpinnerColumn(),
402
- TextColumn("[progress.description]{task.description}"),
403
- transient=True,
404
- ) as progress:
405
- progress.add_task(
406
- description='Uploading your API Json...', total=None)
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
- error('Unable to upload your swagger.json')
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
- else:
427
- info('No swagger.json found at ' + self.path +
428
- '. Skipping swagger.json upload')
498
+ else:
499
+ info(f'No swagger.json found at {self.path}'
500
+ '. Skipping swagger.json upload')
429
501
 
430
- # Push the README.md
431
- if os.path.isfile(readme_file):
432
- # Push the swagger.json
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
- error('Unable to upload your README.md')
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
- else:
460
- info(
461
- f'No README.md found at {self.path}. Skipping README.md upload'
462
- )
463
- return True
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
- # Upper echelon commands
466
- def create(self) -> bool:
532
+ def create(self) -> None:
467
533
  """
468
534
  Creating a new snap
469
535
  """
470
- with Progress(
536
+ progress = Progress(
471
537
  SpinnerColumn(),
472
538
  TextColumn("[progress.description]{task.description}"),
473
539
  transient=True,
474
- ) as progress:
475
- progress.add_task(description='Creating your snap...', total=None)
476
- try:
477
- payload = {
478
- "service_id": self.sid,
479
- "name": self.name,
480
- "description": self.desc,
481
- "platform": self.platform_type,
482
- "language": self.language,
483
- }
484
- res = requests.post(
485
- f"{self.base_url}/v1/snapser-api/byosnaps",
486
- json=payload, headers={'api-key': self.api_key},
487
- timeout=SERVER_CALL_TIMEOUT
488
- )
489
- if res.ok:
490
- return True
491
- response_json = res.json()
492
- info(response_json)
493
- if "api_error_code" in response_json and "message" in response_json:
494
- if response_json['api_error_code'] == ERROR_SERVICE_VERSION_EXISTS:
495
- error(
496
- 'Version already exists. Please update your version and try again'
497
- )
498
- elif response_json['api_error_code'] == ERROR_TAG_NOT_AVAILABLE:
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
- except RequestException as e:
511
- error(f"Exception: Unable to create your snap {e}")
512
- return False
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) -> bool:
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
- if not self._check_dependencies() or not self._docker_login() or \
525
- not self._docker_build() or not self._docker_tag() or not self._docker_push() or \
526
- not self.upload_docs():
527
- return False
528
- return True
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) -> bool:
601
+ def publish_version(self) -> None:
531
602
  """
532
603
  Publish the version
533
604
  """
534
- with Progress(
605
+ progress = Progress(
535
606
  SpinnerColumn(),
536
607
  TextColumn("[progress.description]{task.description}"),
537
608
  transient=True,
538
- ) as progress:
539
- progress.add_task(
540
- description='Publishing your snap...', total=None)
541
- try:
542
- payload = {
543
- "version": self.version,
544
- "image_tag": self.input_tag,
545
- "base_url": f"{self.prefix}/{self.sid}",
546
- "http_port": self.http_port,
547
- }
548
- res = requests.post(
549
- f"{self.base_url}/v1/snapser-api/byosnaps/{self.sid}/versions",
550
- json=payload, headers={'api-key': self.api_key},
551
- timeout=SERVER_CALL_TIMEOUT
552
- )
553
- if res.ok:
554
- return True
555
- response_json = res.json()
556
- if "api_error_code" in response_json:
557
- if response_json['api_error_code'] == ERROR_SERVICE_VERSION_EXISTS:
558
- error(
559
- 'Version already exists. Please update your version and try again'
560
- )
561
- if response_json['api_error_code'] == ERROR_TAG_NOT_AVAILABLE:
562
- error('Invalid tag. Please use the correct tag')
563
- else:
564
- error(
565
- f'Server error: {json.dumps(response_json, indent=2)}'
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
- except RequestException as e:
568
- error(
569
- f'Exception: Unable to publish a version for your snap {e}'
570
- )
571
- return False
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)