snapctl 0.4.5__py3-none-any.whl → 0.22.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.

@@ -1,202 +1,285 @@
1
+ """
2
+ Snapend CLI commands
3
+ """
4
+ from sys import platform
5
+ from typing import Union
6
+
1
7
  import os
2
- import requests
3
8
  import time
9
+ import requests
10
+ from requests.exceptions import RequestException
4
11
 
5
12
  from rich.progress import Progress, SpinnerColumn, TextColumn
13
+ from snapctl.config.constants import SERVER_CALL_TIMEOUT
6
14
  from snapctl.config.hashes import CLIENT_SDK_TYPES, SERVER_SDK_TYPES, PROTOS_TYPES
7
15
  from snapctl.types.definitions import ResponseType
8
16
  from snapctl.utils.echo import error, success, info
9
- from sys import platform
10
- from typing import Union
11
17
 
12
- class Snapend:
13
- SUBCOMMANDS = ['download', 'update', 'state']
14
- DOWNLOAD_TYPES = ['client-sdk', 'server-sdk', 'protos']
15
- BLOCKING_CALL_SLEEP = 5
16
- MAX_BLOCKING_RETRIES = 24
17
18
 
18
- def __init__(self, subcommand: str, base_url: str, api_key: str, snapend_id: str, category: str, platform: str, path: Union[str, None], snaps: Union[str, None], byosnaps: Union[str, None], byogs: Union[str, None], blocking: bool = False) -> None:
19
- self.subcommand: str = subcommand
20
- self.base_url: str = base_url
21
- self.api_key: str = api_key
22
- self.snapend_id: str = snapend_id
23
- self.category: str = category
24
- self.download_types: Union[dict[str, dict[str, str]], None] = Snapend._make_download_type(category)
25
- self.platform: str = platform
26
- self.path: Union[str, None] = path
27
- self.snaps: Union[str, None] = snaps
28
- self.byosnap_list: Union[list, None] = Snapend._make_byosnap_list(byosnaps) if byosnaps else None
29
- self.byogs_list: Union[str, None] = Snapend._make_byogs_list(byogs) if byogs else None
30
- self.blocking: bool = blocking
19
+ class Snapend:
20
+ """
21
+ CLI commands exposed for a Snapend
22
+ """
23
+ SUBCOMMANDS = ['download', 'update', 'state']
24
+ DOWNLOAD_CATEGORY = [
25
+ 'client-sdk', 'server-sdk', 'protos', 'admin-settings'
26
+ ]
27
+ DOWNLOAD_TYPE_NOT_REQUIRED = ['admin-settings']
28
+ AUTH_TYPES = ['user', 'app']
29
+ BLOCKING_CALL_SLEEP = 5
30
+ MAX_BLOCKING_RETRIES = 24
31
31
 
32
- @staticmethod
33
- def _make_download_type(category: str):
34
- if category == 'client-sdk':
35
- return CLIENT_SDK_TYPES
36
- elif category == 'server-sdk':
37
- return SERVER_SDK_TYPES
38
- elif category == 'protos':
39
- return PROTOS_TYPES
40
- return None
32
+ def __init__(
33
+ self, subcommand: str, base_url: str, api_key: str | None, snapend_id: str, category: str,
34
+ platform_type: str, auth_type: str, path: Union[str, None], snaps: Union[str, None],
35
+ byosnaps: Union[str, None], byogs: Union[str, None], blocking: bool = False
36
+ ) -> None:
37
+ self.subcommand: str = subcommand
38
+ self.base_url: str = base_url
39
+ self.api_key: str = api_key
40
+ self.snapend_id: str = snapend_id
41
+ self.category: str = category
42
+ self.download_types: Union[
43
+ dict[str, dict[str, str]], None
44
+ ] = Snapend._make_download_type(category)
45
+ self.auth_type: str = auth_type
46
+ self.platform_type: str = platform_type
47
+ self.path: Union[str, None] = path
48
+ self.snaps: Union[str, None] = snaps
49
+ self.byosnap_list: Union[list, None] = Snapend._make_byosnap_list(
50
+ byosnaps) if byosnaps else None
51
+ self.byogs_list: Union[str, None] = Snapend._make_byogs_list(
52
+ byogs) if byogs else None
53
+ self.blocking: bool = blocking
41
54
 
42
- @staticmethod
43
- def _make_byosnap_list(byosnaps: str) -> list:
44
- byosnap_list = []
45
- for byosnap in byosnaps.split(','):
46
- byosnap = byosnap.strip()
47
- if len(byosnap.split(':')) != 2:
48
- return []
49
- byosnap_list.append({
50
- 'service_id': byosnap.split(':')[0],
51
- 'service_version': byosnap.split(':')[1]
52
- })
53
- return byosnap_list
55
+ @staticmethod
56
+ def _make_download_type(category: str):
57
+ if category == 'client-sdk':
58
+ return CLIENT_SDK_TYPES
59
+ if category == 'server-sdk':
60
+ return SERVER_SDK_TYPES
61
+ if category == 'protos':
62
+ return PROTOS_TYPES
63
+ return None
54
64
 
55
- @staticmethod
56
- def _make_byogs_list(byogs: str) -> list:
57
- byogs_list = []
58
- for byog in byogs.split(','):
59
- byog = byog.strip()
60
- if len(byog.split(':')) != 3:
61
- return []
62
- byogs_list.append({
63
- 'fleet_name': byog.split(':')[0],
64
- 'service_id': byog.split(':')[1],
65
- 'service_version': byog.split(':')[2]
66
- })
67
- return byogs_list
65
+ @staticmethod
66
+ def _make_byosnap_list(byosnaps: str) -> list:
67
+ byosnap_list = []
68
+ for byosnap in byosnaps.split(','):
69
+ byosnap = byosnap.strip()
70
+ if len(byosnap.split(':')) != 2:
71
+ return []
72
+ byosnap_list.append({
73
+ 'service_id': byosnap.split(':')[0],
74
+ 'service_version': byosnap.split(':')[1]
75
+ })
76
+ return byosnap_list
68
77
 
69
- def _get_snapend_state(self)-> str:
70
- try:
71
- url = f"{self.base_url}/v1/snapser-api/snapends/{self.snapend_id}"
72
- res = requests.get(url, headers={'api-key': self.api_key})
73
- clusterObject = res.json()
74
- if 'cluster' in clusterObject and 'id' in clusterObject['cluster'] and clusterObject['cluster']['id'] == self.snapend_id and 'state' in clusterObject['cluster']:
75
- return clusterObject['cluster']['state']
76
- except Exception as e:
77
- pass
78
- return 'INVALID'
78
+ @staticmethod
79
+ def _make_byogs_list(byogs: str) -> list:
80
+ byogs_list = []
81
+ for byog in byogs.split(','):
82
+ byog = byog.strip()
83
+ if len(byog.split(':')) != 3:
84
+ return []
85
+ byogs_list.append({
86
+ 'fleet_name': byog.split(':')[0],
87
+ 'service_id': byog.split(':')[1],
88
+ 'service_version': byog.split(':')[2]
89
+ })
90
+ return byogs_list
79
91
 
80
- def _blocking_get_status(self) -> bool:
81
- total_tries = 0
82
- while True:
83
- total_tries += 1
84
- if total_tries > Snapend.MAX_BLOCKING_RETRIES:
85
- error(f"Goign past maximum tries. Exiting...")
86
- return False
87
- current_state = self._get_snapend_state()
88
- if current_state == 'INVALID':
89
- error(f"Unable to get the snapend state. Exiting...")
90
- return False
91
- if current_state == 'LIVE':
92
- success('Updated your snapend. Your snapend is Live.')
93
- return True
94
- info('Current snapend state is ' + current_state)
95
- info(f"Retrying in {Snapend.BLOCKING_CALL_SLEEP} seconds...")
96
- time.sleep(Snapend.BLOCKING_CALL_SLEEP)
92
+ def _get_snapend_state(self) -> str:
93
+ try:
94
+ url = f"{self.base_url}/v1/snapser-api/snapends/{self.snapend_id}"
95
+ res = requests.get(
96
+ url, headers={'api-key': self.api_key}, timeout=SERVER_CALL_TIMEOUT
97
+ )
98
+ cluster_object = res.json()
99
+ if 'cluster' in cluster_object and 'id' in cluster_object['cluster'] and \
100
+ cluster_object['cluster']['id'] == self.snapend_id and \
101
+ 'state' in cluster_object['cluster']:
102
+ return cluster_object['cluster']['state']
103
+ except RequestException as e:
104
+ error(f"Exception: Unable to get Snapend state {e}")
105
+ return 'INVALID'
97
106
 
107
+ def _blocking_get_status(self) -> bool:
108
+ total_tries = 0
109
+ while True:
110
+ total_tries += 1
111
+ if total_tries > Snapend.MAX_BLOCKING_RETRIES:
112
+ error("Going past maximum tries. Exiting...")
113
+ return False
114
+ current_state = self._get_snapend_state()
115
+ if current_state == 'INVALID':
116
+ error("Unable to get the snapend state. Exiting...")
117
+ return False
118
+ if current_state == 'LIVE':
119
+ success('Updated your snapend. Your snapend is Live.')
120
+ return True
121
+ info(f'Current snapend state is {current_state}')
122
+ info(f"Retrying in {Snapend.BLOCKING_CALL_SLEEP} seconds...")
123
+ time.sleep(Snapend.BLOCKING_CALL_SLEEP)
98
124
 
99
- def validate_input(self) -> ResponseType:
100
- response: ResponseType = {
101
- 'error': True,
102
- 'msg': '',
103
- 'data': []
104
- }
105
- # Check subcommand
106
- if not self.subcommand in Snapend.SUBCOMMANDS:
107
- response['msg'] = f"Invalid command. Valid commands are {', '.join(Snapend.SUBCOMMANDS)}."
108
- return response
109
- # Check sdk-download commands
110
- if self.subcommand == 'download':
111
- if self.category not in Snapend.DOWNLOAD_TYPES:
112
- response['msg'] = f"Invalid SDK category. Valid categories are {', '.join(Snapend.DOWNLOAD_TYPES)}."
113
- return response
114
- if self.download_types is None:
115
- response['msg'] = f"Invalid Download type."
116
- return response
117
- # Check file path
118
- if self.path and not os.path.isdir(f"{self.path}"):
119
- response['msg'] = f"Invalid path {self.path}. Please enter a valid path to save your SDK"
120
- return response
121
- elif self.subcommand == 'update':
122
- byosnap_present = True
123
- if self.byosnap_list is None or len(self.byosnap_list) == 0:
124
- byosnap_present = False
125
- byogs_present = True
126
- if self.byogs_list is None or len(self.byogs_list) == 0:
127
- byogs_present = False
128
- if not byosnap_present and not byogs_present:
129
- response['msg'] = f"The update command needs one of byosnaps or byogs"
125
+ def validate_input(self) -> ResponseType:
126
+ """
127
+ Validator
128
+ """
129
+ response: ResponseType = {
130
+ 'error': True,
131
+ 'msg': '',
132
+ 'data': []
133
+ }
134
+ # Check API Key and Base URL
135
+ if not self.api_key or self.base_url == '':
136
+ response['msg'] = "Missing API Key."
137
+ return response
138
+ # Check subcommand
139
+ if not self.subcommand in Snapend.SUBCOMMANDS:
140
+ response['msg'] = \
141
+ f"Invalid command. Valid commands are {', '.join(Snapend.SUBCOMMANDS)}."
142
+ return response
143
+ # Check sdk-download commands
144
+ if self.subcommand == 'download':
145
+ if self.category not in Snapend.DOWNLOAD_CATEGORY:
146
+ response['msg'] = (
147
+ "Invalid SDK category. Valid categories are "
148
+ f"{', '.join(Snapend.DOWNLOAD_CATEGORY)}."
149
+ )
150
+ return response
151
+ if self.category not in Snapend.DOWNLOAD_TYPE_NOT_REQUIRED and \
152
+ (self.download_types is None or self.platform_type not in self.download_types):
153
+ response['msg'] = "Invalid Download type."
154
+ return response
155
+ # Check file path
156
+ if self.path and not os.path.isdir(f"{self.path}"):
157
+ response['msg'] = (
158
+ f"Invalid path {self.path}. "
159
+ "Please enter a valid path to save your SDK"
160
+ )
161
+ return response
162
+ # Check the auth type
163
+ if self.category == 'server-sdk' and self.auth_type not in Snapend.AUTH_TYPES:
164
+ response['msg'] = (
165
+ "Invalid auth type. Valid auth types are "
166
+ f"{', '.join(Snapend.AUTH_TYPES)}."
167
+ )
168
+ return response
169
+ # Check update commands
170
+ elif self.subcommand == 'update':
171
+ byosnap_present = True
172
+ if self.byosnap_list is None or len(self.byosnap_list) == 0:
173
+ byosnap_present = False
174
+ byogs_present = True
175
+ if self.byogs_list is None or len(self.byogs_list) == 0:
176
+ byogs_present = False
177
+ if not byosnap_present and not byogs_present:
178
+ response['msg'] = "The update command needs one of byosnaps or byogs"
179
+ return response
180
+ # Send success
181
+ response['error'] = False
130
182
  return response
131
- # Send success
132
- response['error'] = False
133
- return response
134
-
135
- def download(self) -> bool:
136
- with Progress(
137
- SpinnerColumn(),
138
- TextColumn("[progress.description]{task.description}"),
139
- transient=True,
140
- ) as progress:
141
- progress.add_task(description=f'Downloading your Custom {self.category}...', total=None)
142
- try:
143
- url = f"{self.base_url}/v1/snapser-api/snapends/{self.snapend_id}/download?category={self.category}&type={self.download_types[self.platform]['type']}&subtype={self.download_types[self.platform]['subtype']}"
144
- if self.snaps:
145
- url += f"&snaps={self.snaps}"
146
- res = requests.get(url, headers={'api-key': self.api_key})
147
- file_name = f"snapser-{self.snapend_id}-{self.category}-{self.platform}.zip"
148
- file_path_symbol = '/'
149
- if platform == 'win32':
150
- file_path_symbol = '\\'
151
- sdk_save_path = f"{self.path}{file_path_symbol}{file_name}" if self.path is not None else f"{os.getcwd()}{file_path_symbol}{file_name}"
152
- if res.ok:
153
- with open(sdk_save_path, "wb") as file:
154
- file.write(res.content)
155
- success(f"SDK saved at {sdk_save_path}")
156
- return True
157
- error(f'Unable to download your custom SDK')
158
- except Exception as e:
159
- error("Exception: Unable to download the SDK")
160
- return False
161
183
 
162
- def update(self) -> bool:
163
- with Progress(
164
- SpinnerColumn(),
165
- TextColumn("[progress.description]{task.description}"),
166
- transient=True,
167
- ) as progress:
168
- progress.add_task(description=f'Updating your Snapend...', total=None)
169
- try:
170
- payload = {
171
- 'byosnap_updates': self.byosnap_list,
172
- 'byogs_updates': self.byogs_list
173
- }
174
- url = f"{self.base_url}/v1/snapser-api/snapends/{self.snapend_id}"
175
- res = requests.patch(url, json=payload, headers={'api-key': self.api_key})
176
- if res.ok:
177
- if self.blocking:
178
- return self._blocking_get_status()
179
- success('Snapend update has been initiated. You can check the status using `snapctl snapend state`')
180
- return True
181
- else:
182
- response_json = res.json()
183
- error(response_json['details'][0])
184
- except Exception as e:
185
- error(e.message)
186
- error("Exception: Unable to update your snapend")
187
- return False
184
+ def download(self) -> bool:
185
+ """
186
+ Download SDKs, Protos and Admin Settings
187
+ """
188
+ with Progress(
189
+ SpinnerColumn(),
190
+ TextColumn("[progress.description]{task.description}"),
191
+ transient=True,
192
+ ) as progress:
193
+ progress.add_task(
194
+ description=f'Downloading your Custom {self.category}...', total=None)
195
+ try:
196
+ url = (
197
+ f"{self.base_url}/v1/snapser-api/snapends/{self.snapend_id}/"
198
+ f"download?category={self.category}"
199
+ )
200
+ if self.category not in Snapend.DOWNLOAD_TYPE_NOT_REQUIRED:
201
+ url += (
202
+ f"&type={self.download_types[self.platform_type]['type']}"
203
+ f"&subtype={self.download_types[self.platform_type]['subtype']}"
204
+ )
205
+ url_auth_type: str = 'user'
206
+ if self.category == 'server-sdk' and self.auth_type == 'app':
207
+ url_auth_type = 'app'
208
+ url += f"&auth_type={url_auth_type}"
209
+ if self.snaps:
210
+ url += f"&snaps={self.snaps}"
211
+ res = requests.get(
212
+ url, headers={'api-key': self.api_key}, timeout=SERVER_CALL_TIMEOUT
213
+ )
214
+ fn: str = f"snapser-{self.snapend_id}-admin-settings.json"
215
+ if self.category != 'admin-settings':
216
+ fn = f"snapser-{self.snapend_id}-{self.category}-{self.platform_type}-{self.auth_type}.zip"
217
+ file_path_symbol = '/'
218
+ if platform == 'win32':
219
+ file_path_symbol = '\\'
220
+ if self.path is not None:
221
+ sdk_save_path = f"{self.path}{file_path_symbol}{fn}"
222
+ else:
223
+ sdk_save_path = f"{os.getcwd()}{file_path_symbol}{fn}"
224
+ if res.ok:
225
+ with open(sdk_save_path, "wb") as file:
226
+ file.write(res.content)
227
+ success(f"SDK saved at {sdk_save_path}")
228
+ return True
229
+ error('Unable to download your custom SDK')
230
+ except RequestException as e:
231
+ error(f"Exception: Unable to download the SDK {e}")
232
+ return False
188
233
 
189
- def state(self) -> bool:
190
- with Progress(
191
- SpinnerColumn(),
192
- TextColumn("[progress.description]{task.description}"),
193
- transient=True,
194
- ) as progress:
195
- progress.add_task(description=f'Getting your Snapend state...', total=None)
196
- current_state = self._get_snapend_state()
197
- if current_state != 'INVALID':
198
- success('Current snapend state is: ' + current_state)
199
- return True
200
- error(f"Unable to get the snapend state.")
201
- return False
234
+ def update(self) -> bool:
235
+ """
236
+ Update a Snapend
237
+ """
238
+ with Progress(
239
+ SpinnerColumn(),
240
+ TextColumn("[progress.description]{task.description}"),
241
+ transient=True,
242
+ ) as progress:
243
+ progress.add_task(
244
+ description='Updating your Snapend...', total=None)
245
+ try:
246
+ payload = {
247
+ 'byosnap_updates': self.byosnap_list,
248
+ 'byogs_updates': self.byogs_list
249
+ }
250
+ url = f"{self.base_url}/v1/snapser-api/snapends/{self.snapend_id}"
251
+ res = requests.patch(
252
+ url, json=payload, headers={'api-key': self.api_key},
253
+ timeout=SERVER_CALL_TIMEOUT
254
+ )
255
+ if res.ok:
256
+ if self.blocking:
257
+ return self._blocking_get_status()
258
+ success(
259
+ 'Snapend update has been initiated. '
260
+ 'You can check the status using `snapctl snapend state`'
261
+ )
262
+ return True
263
+ response_json = res.json()
264
+ error(response_json['details'][0])
265
+ except RequestException as e:
266
+ error(f"Exception: Unable to update your snapend {e}")
267
+ return False
202
268
 
269
+ def state(self) -> bool:
270
+ """
271
+ Get the state of a Snapend
272
+ """
273
+ with Progress(
274
+ SpinnerColumn(),
275
+ TextColumn("[progress.description]{task.description}"),
276
+ transient=True,
277
+ ) as progress:
278
+ progress.add_task(
279
+ description='Getting your Snapend state...', total=None)
280
+ current_state = self._get_snapend_state()
281
+ if current_state != 'INVALID':
282
+ success('Current snapend state is: ' + current_state)
283
+ return True
284
+ error("Unable to get the snapend state.")
285
+ return False
@@ -1,8 +1,12 @@
1
- VERSION = '0.4.5'
1
+ """
2
+ Constants used by snapctl
3
+ """
4
+ VERSION = '0.22.1'
2
5
  CONFIG_FILE_MAC = '~/.snapser/config'
3
6
  CONFIG_FILE_WIN = '%homepath%\.snapser\config'
4
7
  DEFAULT_PROFILE = 'default'
5
8
  API_KEY = 'SNAPSER_API_KEY'
9
+ SERVER_CALL_TIMEOUT = 30
6
10
 
7
11
  SNAPCTL_SUCCESS = 0
8
12
  SNAPCTL_ERROR = 1
@@ -16,3 +20,4 @@ HTTP_CONFLICT = 409
16
20
  # Error codes
17
21
  ERROR_SERVICE_VERSION_EXISTS = 542
18
22
  ERROR_TAG_NOT_AVAILABLE = 544
23
+ ERROR_ADD_ON_NOT_ENABLED = 547
@@ -1,5 +1,8 @@
1
- ENDPOINTS = {
2
- 'DEV': 'https://gateway.dev.snapser.io/snapser',
3
- 'PLAYTEST': 'https://gateway.dev.snapser.io/playtest',
4
- 'PROD': 'https://gateway.snapser.com/snapser'
5
- }
1
+ """
2
+ This file contains the endpoints for the Snapser API.
3
+ """
4
+ END_POINTS = {
5
+ 'DEV': 'https://gateway.dev.snapser.io/snapser',
6
+ 'PLAYTEST': 'https://gateway.dev.snapser.io/playtest',
7
+ 'PROD': 'https://gateway.snapser.com/snapser'
8
+ }
snapctl/config/hashes.py CHANGED
@@ -1,3 +1,6 @@
1
+ """
2
+ This file contains the hashes / list constants
3
+ """
1
4
  CLIENT_SDK_TYPES: dict[str, dict[str, str]] = {
2
5
  'unity': {
3
6
  'type': 'csharp',
@@ -50,7 +53,7 @@ CLIENT_SDK_TYPES: dict[str, dict[str, str]] = {
50
53
  }
51
54
 
52
55
  SERVER_SDK_TYPES: dict[str, dict[str, str]] = {
53
- 'csharp': {
56
+ 'csharp': {
54
57
  'type': 'csharp',
55
58
  'subtype': '',
56
59
  },
@@ -117,8 +120,15 @@ SERVER_SDK_TYPES: dict[str, dict[str, str]] = {
117
120
  }
118
121
 
119
122
  PROTOS_TYPES: dict[str, dict[str, str]] = {
120
- 'go': {
123
+ 'go': {
121
124
  'type': 'go',
122
125
  'subtype': '',
123
126
  },
124
- }
127
+ }
128
+
129
+ SERVICE_IDS = [
130
+ 'analytics', 'auth', 'client-logs', 'events', 'experiments', 'gdpr', 'guilds', 'hades', 'iap',
131
+ 'inventory', 'leaderboards', 'matchmaking', 'notifications', 'parties', 'profiles', 'quests',
132
+ 'relay', 'remote-config', 'scheduler', 'sequencer', 'social-graph', 'statistics', 'storage',
133
+ 'trackables', 'xp'
134
+ ]