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