snapctl 0.49.4__tar.gz → 0.50.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of snapctl might be problematic. Click here for more details.

Files changed (43) hide show
  1. {snapctl-0.49.4 → snapctl-0.50.0}/PKG-INFO +1 -1
  2. {snapctl-0.49.4 → snapctl-0.50.0}/pyproject.toml +1 -1
  3. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/commands/byogs.py +16 -2
  4. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/commands/byosnap.py +53 -25
  5. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/commands/byows.py +2 -4
  6. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/config/constants.py +1 -1
  7. snapctl-0.50.0/snapctl/data/releases/beta-0.50.0.mdx +10 -0
  8. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/main.py +1 -2
  9. snapctl-0.50.0/snapctl/utils/echo.py +52 -0
  10. snapctl-0.49.4/snapctl/data/releases/beta-0.49.4.mdx +0 -8
  11. snapctl-0.49.4/snapctl/utils/echo.py +0 -41
  12. {snapctl-0.49.4 → snapctl-0.50.0}/LICENSE +0 -0
  13. {snapctl-0.49.4 → snapctl-0.50.0}/README.md +0 -0
  14. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/__init__.py +0 -0
  15. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/__main__.py +0 -0
  16. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/commands/__init__.py +0 -0
  17. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/commands/game.py +0 -0
  18. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/commands/generate.py +0 -0
  19. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/commands/release_notes.py +0 -0
  20. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/commands/snapend.py +0 -0
  21. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/config/__init__.py +0 -0
  22. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/config/endpoints.py +0 -0
  23. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/config/hashes.py +0 -0
  24. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/__init__.py +0 -0
  25. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/profiles/__init__.py +0 -0
  26. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/profiles/snapser-byosnap-profile.json +0 -0
  27. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/profiles/snapser-byosnap-profile.yaml +0 -0
  28. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/profiles/snapser-byosnap-profile.yml +0 -0
  29. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/__init__.py +0 -0
  30. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.46.0.mdx +0 -0
  31. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.46.4.mdx +0 -0
  32. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.47.0.mdx +0 -0
  33. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.47.1.mdx +0 -0
  34. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.47.2.mdx +0 -0
  35. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.48.0.mdx +0 -0
  36. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.49.0.mdx +0 -0
  37. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.49.1.mdx +0 -0
  38. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.49.2.mdx +0 -0
  39. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/data/releases/beta-0.49.3.mdx +0 -0
  40. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/types/__init__.py +0 -0
  41. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/types/definitions.py +0 -0
  42. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/utils/__init__.py +0 -0
  43. {snapctl-0.49.4 → snapctl-0.50.0}/snapctl/utils/helper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: snapctl
3
- Version: 0.49.4
3
+ Version: 0.50.0
4
4
  Summary: Snapser CLI Tool
5
5
  Author: Ajinkya Apte
6
6
  Author-email: aj@snapser.com
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "snapctl"
3
- version = "0.49.4"
3
+ version = "0.50.0"
4
4
  description = "Snapser CLI Tool"
5
5
  authors = ["Ajinkya Apte <aj@snapser.com>"]
6
6
  readme = "README.md"
@@ -105,6 +105,16 @@ class ByoGs:
105
105
  pass
106
106
  return None
107
107
 
108
+ @staticmethod
109
+ def _docker_supports_buildkit():
110
+ try:
111
+ version = subprocess.check_output(
112
+ ["docker", "version", "--format", "{{.Server.Version}}"])
113
+ major, minor = map(int, version.decode().split(".")[:2])
114
+ return (major > 18) or (major == 18 and minor >= 9)
115
+ except Exception:
116
+ return False
117
+
108
118
  def _check_dependencies(self) -> None:
109
119
  progress = Progress(
110
120
  SpinnerColumn(),
@@ -200,6 +210,10 @@ class ByoGs:
200
210
  progress.add_task(
201
211
  description='Building your snap...', total=None)
202
212
  try:
213
+ env = os.environ.copy()
214
+ if ByoGs._docker_supports_buildkit():
215
+ info('Docker BuildKit is supported. Enabling it.')
216
+ env["DOCKER_BUILDKIT"] = "1"
203
217
  # image_tag = f'{ByoGs.SID}.{self.tag}'
204
218
  build_platform = ByoGs.DEFAULT_BUILD_PLATFORM
205
219
  if len(self.token_parts) == 4:
@@ -217,7 +231,7 @@ class ByoGs:
217
231
  # f"docker build --no-cache -t {tag} {path}"
218
232
  'docker', 'build', '--platform', build_platform, '-t', self.tag,
219
233
  '-f', self.docker_path_filename, self.path
220
- ], shell=True, check=False)
234
+ ], shell=True, check=False, env=env)
221
235
  # stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
222
236
  else:
223
237
  response = subprocess.run([
@@ -225,7 +239,7 @@ class ByoGs:
225
239
  "docker build --platform " +
226
240
  f"{build_platform} -t {self.tag} " +
227
241
  f"-f {self.docker_path_filename} {self.path}"
228
- ], shell=True, check=False)
242
+ ], shell=True, check=False, env=env)
229
243
  # stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
230
244
  if not response.returncode:
231
245
  return snapctl_success(
@@ -509,16 +509,19 @@ class ByoSnap:
509
509
  outfile.write(line)
510
510
  return True
511
511
  except FileNotFoundError:
512
- print(f"[ERROR] Could not find profile file: {resource_filename}")
512
+ warning(
513
+ f"[ERROR] Could not find profile file: {resource_filename}")
514
+ return False
515
+
516
+ @staticmethod
517
+ def _docker_supports_buildkit():
518
+ try:
519
+ version = subprocess.check_output(
520
+ ["docker", "version", "--format", "{{.Server.Version}}"])
521
+ major, minor = map(int, version.decode().split(".")[:2])
522
+ return (major > 18) or (major == 18 and minor >= 9)
523
+ except Exception:
513
524
  return False
514
- # @staticmethod
515
- # def _handle_output_file(input_filepath, output_filepath) -> bool:
516
- # file_written = False
517
- # with open(input_filepath, 'r') as in_file, open(output_filepath, 'w') as outfile:
518
- # for line in in_file:
519
- # outfile.write(line)
520
- # file_written = True
521
- # return file_written
522
525
 
523
526
  def _get_profile_contents(self) -> dict:
524
527
  """
@@ -588,13 +591,15 @@ class ByoSnap:
588
591
  code=SNAPCTL_INPUT_ERROR
589
592
  )
590
593
  # IMPORTANT: This is where the profile data is set and validated
594
+ #
595
+ # Update: June 2, 2025 - We removed the line that updated the self.platform_type
596
+ # self.platform_type = profile_data_obj['platform']
591
597
  self.profile_data = profile_data_obj
592
598
  ByoSnap._validate_byosnap_profile_data(self.profile_data)
593
599
  # End: IMPORTANT: This is where the profile data is set
594
600
  # Now apply the overrides
595
601
  self.name = self.profile_data['name']
596
602
  self.desc = self.profile_data['description']
597
- self.platform_type = self.profile_data['platform']
598
603
  self.language = self.profile_data['language']
599
604
  self.prefix = self.profile_data['prefix']
600
605
  # Setup the final ingress external port
@@ -716,6 +721,10 @@ class ByoSnap:
716
721
  build_platform = ByoSnap.DEFAULT_BUILD_PLATFORM
717
722
  if len(self.token_parts) == 4:
718
723
  build_platform = self.token_parts[3]
724
+ if self.platform_type is not None:
725
+ build_platform = self.platform_type
726
+ # if len(self.token_parts) == 4:
727
+ # build_platform = self.token_parts[3]
719
728
  progress = Progress(
720
729
  SpinnerColumn(),
721
730
  TextColumn("[progress.description]{task.description}"),
@@ -725,6 +734,10 @@ class ByoSnap:
725
734
  progress.add_task(
726
735
  description='Building your snap...', total=None)
727
736
  try:
737
+ env = os.environ.copy()
738
+ if ByoSnap._docker_supports_buildkit():
739
+ info('Docker BuildKit is supported. Enabling it.')
740
+ env["DOCKER_BUILDKIT"] = "1"
728
741
  # Warning check for architecture specific commands
729
742
  info(f'Building on system architecture {sys_platform.machine()}')
730
743
  check_response = check_dockerfile_architecture(
@@ -737,7 +750,7 @@ class ByoSnap:
737
750
  # f"docker build --no-cache -t {remote_tag} {path}"
738
751
  'docker', 'build', '--platform', build_platform, '-t', self.remote_tag,
739
752
  '-f', self.docker_path_filename, self.path
740
- ], shell=True, check=False)
753
+ ], shell=True, check=False, env=env)
741
754
  # stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
742
755
  else:
743
756
  response = subprocess.run([
@@ -745,7 +758,7 @@ class ByoSnap:
745
758
  "docker build --platform " +
746
759
  f"{build_platform} -t {self.remote_tag} " +
747
760
  f"-f {self.docker_path_filename} {self.path}"
748
- ], shell=True, check=False)
761
+ ], shell=True, check=False, env=env)
749
762
  # stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
750
763
  if not response.returncode:
751
764
  return snapctl_success(
@@ -943,15 +956,21 @@ class ByoSnap:
943
956
  if self.path is None and self.resources_path is None:
944
957
  snapctl_error(
945
958
  "Missing one of: path or resources-path parameter", SNAPCTL_INPUT_ERROR)
946
- if not self.tag:
947
- snapctl_error("Missing tag", SNAPCTL_INPUT_ERROR)
948
- if len(self.tag.split()) > 1 or \
949
- len(self.tag) > ByoSnap.TAG_CHARACTER_LIMIT:
950
- snapctl_error(
951
- "Tag should be a single word with maximum of " +
952
- f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
953
- SNAPCTL_INPUT_ERROR
954
- )
959
+ if not self.tag and not self.version:
960
+ snapctl_error("Missing tag or version", SNAPCTL_INPUT_ERROR)
961
+ if self.tag:
962
+ if len(self.tag.split()) > 1 or \
963
+ len(self.tag) > ByoSnap.TAG_CHARACTER_LIMIT:
964
+ snapctl_error(
965
+ "Tag should be a single word with maximum of " +
966
+ f"{ByoSnap.TAG_CHARACTER_LIMIT} characters",
967
+ SNAPCTL_INPUT_ERROR
968
+ )
969
+ if self.version:
970
+ pattern = r'^v\d+\.\d+\.\d+$'
971
+ if not re.match(pattern, self.version):
972
+ snapctl_error(message="Version should be in the format vX.X.X",
973
+ code=SNAPCTL_INPUT_ERROR)
955
974
  elif self.subcommand == 'publish-version':
956
975
  # Setup
957
976
  self._setup_token_and_token_parts(
@@ -1127,6 +1146,9 @@ class ByoSnap:
1127
1146
  progress.add_task(
1128
1147
  description='Uploading your BYOSnap Docs...', total=None)
1129
1148
  try:
1149
+ upload_tag = self.tag
1150
+ if not upload_tag:
1151
+ upload_tag = self.version
1130
1152
  if self.resources_path:
1131
1153
  base_dir = self.resources_path
1132
1154
  else:
@@ -1141,7 +1163,7 @@ class ByoSnap:
1141
1163
  info(f'Uploading swagger.json at {swagger_file}')
1142
1164
  url = (
1143
1165
  f"{self.base_url}/v1/snapser-api/byosnaps/"
1144
- f"{self.byosnap_id}/docs/{self.tag}/openapispec"
1166
+ f"{self.byosnap_id}/docs/{upload_tag}/openapispec"
1145
1167
  )
1146
1168
  test_res = requests.post(
1147
1169
  url, files={"attachment": attachment_file},
@@ -1181,7 +1203,7 @@ class ByoSnap:
1181
1203
  with open(readme_file, "rb") as attachment_file:
1182
1204
  url = (
1183
1205
  f"{self.base_url}/v1/snapser-api/byosnaps/"
1184
- f"{self.byosnap_id}/docs/{self.tag}/markdown"
1206
+ f"{self.byosnap_id}/docs/{upload_tag}/markdown"
1185
1207
  )
1186
1208
  test_res = requests.post(
1187
1209
  url, files={"attachment": attachment_file},
@@ -1213,7 +1235,7 @@ class ByoSnap:
1213
1235
  with open(file_path, "rb") as attachment_file:
1214
1236
  url = (
1215
1237
  f"{self.base_url}/v1/snapser-api/byosnaps/"
1216
- f"{self.byosnap_id}/docs/{self.tag}/tools"
1238
+ f"{self.byosnap_id}/docs/{upload_tag}/tools"
1217
1239
  )
1218
1240
  test_res = requests.post(
1219
1241
  url, files={"attachment": attachment_file},
@@ -1370,7 +1392,11 @@ class ByoSnap:
1370
1392
  "prod_template": prod_template,
1371
1393
  # Currently not supported so we are just hardcoding an empty list
1372
1394
  "egress": {"ports": []},
1395
+ # Platform override
1373
1396
  }
1397
+ if self.platform_type is not None:
1398
+ payload['platform_override'] = self.platform_type
1399
+
1374
1400
  res = requests.post(
1375
1401
  f"{self.base_url}/v1/snapser-api/byosnaps/{self.byosnap_id}/versions",
1376
1402
  json=payload, headers={'api-key': self.api_key},
@@ -1452,6 +1478,8 @@ class ByoSnap:
1452
1478
  # Currently not supported so we are just hardcoding an empty list
1453
1479
  "egress": {"ports": []},
1454
1480
  }
1481
+ if self.platform_type is not None:
1482
+ payload['platform_override'] = self.platform_type
1455
1483
  res = requests.patch(
1456
1484
  f"{self.base_url}/v1/snapser-api/byosnaps/{self.byosnap_id}/versions/{self.version}",
1457
1485
  json=payload, headers={'api-key': self.api_key},
@@ -1504,7 +1532,7 @@ class ByoSnap:
1504
1532
  "service_id": self.byosnap_id,
1505
1533
  "name": self.name,
1506
1534
  "description": self.desc,
1507
- "platform": self.platform_type,
1535
+ "platform": self.profile_data['platform'],
1508
1536
  "language": self.language,
1509
1537
  }
1510
1538
  res = requests.post(
@@ -99,12 +99,11 @@ class Byows:
99
99
  def _get_export_commands(snap_ids, port):
100
100
  '''
101
101
  Generate export commands for the given snap IDs and port.
102
- Replace hyphens with underscores in environment variable names.
103
102
  '''
104
103
  env_vars = []
105
104
 
106
105
  for snap_id in snap_ids:
107
- upper_id = snap_id.upper().replace("-", "_")
106
+ upper_id = snap_id.upper()
108
107
  env_vars.append(f"SNAPEND_{upper_id}_GRPC_URL=localhost:{port}")
109
108
  env_vars.append(
110
109
  f"SNAPEND_{upper_id}_HTTP_URL=http://localhost:{port}")
@@ -122,12 +121,11 @@ class Byows:
122
121
  def _generate_env_file(snap_ids, port):
123
122
  '''
124
123
  Generate an environment file with the given snap IDs and port.
125
- Replace hyphens with underscores in environment variable names.
126
124
  '''
127
125
  env_lines = []
128
126
 
129
127
  for snap_id in snap_ids:
130
- upper_id = snap_id.upper().replace("-", "_")
128
+ upper_id = snap_id.upper()
131
129
  env_lines.append(
132
130
  f"export SNAPEND_{upper_id}_GRPC_URL=localhost:{port}")
133
131
  env_lines.append(
@@ -3,7 +3,7 @@ Constants used by snapctl
3
3
  """
4
4
  COMPANY_NAME = 'Snapser'
5
5
  VERSION_PREFIX = 'beta-'
6
- VERSION = '0.49.4'
6
+ VERSION = '0.50.0'
7
7
  CONFIG_FILE_MAC = '~/.snapser/config'
8
8
  CONFIG_FILE_WIN = '%homepath%\\.snapser\\config'
9
9
 
@@ -0,0 +1,10 @@
1
+ ## beta-0.50.0
2
+ ##### June 5, 2025
3
+
4
+ ### Bug fix
5
+ - Success, info or warning messages were sometimes getting truncated in the CLI output. This has been fixed.
6
+ - Snapctl byosnap and byogs now support features that could only work with Buildkit.
7
+
8
+ ### Feature
9
+ - The `snapctl byosnap upload-docs` command now supports `--version` to keep it consistent with all the new commands like `sync` and `publish`.
10
+
@@ -3,7 +3,6 @@
3
3
  """
4
4
  import configparser
5
5
  import os
6
- import json
7
6
  from sys import platform
8
7
  from typing import Union
9
8
  import typer
@@ -403,7 +402,7 @@ def byosnap(
403
402
  help="(req: create) Language of your snap - " +
404
403
  ", ".join(ByoSnap.LANGUAGES) + "."
405
404
  ),
406
- # publish-image, publish-version, publish, sync
405
+ # publish-image, publish-version, publish, sync, upload-docs
407
406
  tag: str = typer.Option(
408
407
  None, "--tag", help=(
409
408
  "(req: publish-image, publish-version, upload-docs) (optional: publish, sync) Tag for your snap"
@@ -0,0 +1,52 @@
1
+ """
2
+ This module contains functions to print messages to the console.
3
+ """
4
+ import json
5
+ import typer
6
+ from rich import print
7
+ from rich.markup import escape
8
+ from snapctl.config.constants import SNAPCTL_ERROR
9
+ from snapctl.types.definitions import ErrorResponse
10
+ # Run `python -m rich.emoji` to get a list of all emojis that are supported
11
+
12
+
13
+ def _stringify_message(msg: object) -> str:
14
+ if isinstance(msg, (dict, list)):
15
+ return json.dumps(msg, indent=2)
16
+ return str(msg)
17
+
18
+
19
+ def error(msg: object, code: int = SNAPCTL_ERROR, data: object = None) -> None:
20
+ """
21
+ Prints an error message to the console.
22
+ """
23
+ msg = _stringify_message(msg)
24
+ error_response = ErrorResponse(
25
+ error=True, code=code, msg=msg, data=data if data else ''
26
+ )
27
+ print(f"[bold red]Error[/bold red] {escape(msg)}")
28
+ typer.echo(json.dumps(error_response.to_dict()), err=True)
29
+
30
+
31
+ def warning(msg: object) -> None:
32
+ """
33
+ Prints a warning message to the console.
34
+ """
35
+ msg = _stringify_message(msg)
36
+ print(f"[bold yellow]Warning[/bold yellow] {escape(msg)}")
37
+
38
+
39
+ def info(msg: object) -> None:
40
+ """
41
+ Prints an info message to the console.
42
+ """
43
+ msg = _stringify_message(msg)
44
+ print(f"[bold blue]Info[/bold blue] {escape(msg)}")
45
+
46
+
47
+ def success(msg: object) -> None:
48
+ """
49
+ Prints a success message to the console.
50
+ """
51
+ msg = _stringify_message(msg)
52
+ print(f"[bold green]Success[/bold green] {escape(msg)}")
@@ -1,8 +0,0 @@
1
- ## beta-0.49.4
2
- ##### May 20, 2025
3
-
4
- ### Bug Fixes
5
-
6
- #### Bring your own workstation (BYOW)
7
- - Environment exports no longer include hyphens in the variable names.
8
-
@@ -1,41 +0,0 @@
1
- """
2
- This module contains functions to print messages to the console.
3
- """
4
- import json
5
- import typer
6
- from rich import print
7
- from snapctl.config.constants import SNAPCTL_ERROR
8
- from snapctl.types.definitions import ErrorResponse
9
- # Run `python -m rich.emoji` to get a list of all emojis that are supported
10
-
11
-
12
- def error(msg: str, code: int = SNAPCTL_ERROR, data: object = None) -> None:
13
- """
14
- Prints an error message to the console.
15
- """
16
- error_response = ErrorResponse(
17
- error=True, code=code, msg=msg, data=data if data else ''
18
- )
19
- print(f"[bold red]Error[/bold red] {msg}")
20
- typer.echo(json.dumps(error_response.to_dict()), err=True)
21
-
22
-
23
- def warning(msg: str) -> None:
24
- """
25
- Prints a warning message to the console.
26
- """
27
- print(f"[bold yellow]Warning[/bold yellow] {msg}")
28
-
29
-
30
- def info(msg: str) -> None:
31
- """
32
- Prints an info message to the console.
33
- """
34
- print(f"[bold blue]Info[/bold blue] {msg}")
35
-
36
-
37
- def success(msg: str) -> None:
38
- """
39
- Prints a success message to the console.
40
- """
41
- print(f"[bold green]Success[/bold green] {msg}")
File without changes
File without changes
File without changes
File without changes