snapctl 0.22.3__py3-none-any.whl → 0.26.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.
- snapctl/commands/byogs.py +4 -2
- snapctl/commands/byosnap.py +4 -2
- snapctl/commands/game.py +107 -0
- snapctl/commands/snapend.py +364 -55
- snapctl/config/constants.py +5 -2
- snapctl/config/endpoints.py +1 -0
- snapctl/config/hashes.py +11 -0
- snapctl/main.py +123 -20
- {snapctl-0.22.3.dist-info → snapctl-0.26.1.dist-info}/METADATA +75 -12
- snapctl-0.26.1.dist-info/RECORD +21 -0
- snapctl-0.22.3.dist-info/RECORD +0 -20
- {snapctl-0.22.3.dist-info → snapctl-0.26.1.dist-info}/WHEEL +0 -0
- {snapctl-0.22.3.dist-info → snapctl-0.26.1.dist-info}/entry_points.txt +0 -0
snapctl/commands/byogs.py
CHANGED
|
@@ -161,16 +161,18 @@ class ByoGs:
|
|
|
161
161
|
) as progress:
|
|
162
162
|
progress.add_task(
|
|
163
163
|
description='Building your snap...', total=None)
|
|
164
|
+
docker_file_path = f"{self.path}/{self.dockerfile}"
|
|
164
165
|
if platform == "win32":
|
|
165
166
|
response = subprocess.run([
|
|
166
167
|
# f"docker build --no-cache -t {tag} {path}"
|
|
167
|
-
'docker', 'build', '--platform', build_platform, '-t', image_tag,
|
|
168
|
+
'docker', 'build', '--platform', build_platform, '-t', image_tag,
|
|
169
|
+
'-f', docker_file_path, self.path
|
|
168
170
|
], shell=True, check=False)
|
|
169
171
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
170
172
|
else:
|
|
171
173
|
response = subprocess.run([
|
|
172
174
|
# f"docker build --no-cache -t {tag} {path}"
|
|
173
|
-
f"docker build --platform {build_platform} -t {image_tag} {self.path}"
|
|
175
|
+
f"docker build --platform {build_platform} -t {image_tag} -f {docker_file_path} {self.path}"
|
|
174
176
|
], shell=True, check=False)
|
|
175
177
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
176
178
|
if response.returncode:
|
snapctl/commands/byosnap.py
CHANGED
|
@@ -169,16 +169,18 @@ class ByoSnap:
|
|
|
169
169
|
) as progress:
|
|
170
170
|
progress.add_task(
|
|
171
171
|
description='Building your snap...', total=None)
|
|
172
|
+
docker_file_path = f"{self.path}/{self.dockerfile}"
|
|
172
173
|
if platform == "win32":
|
|
173
174
|
response = subprocess.run([
|
|
174
175
|
# f"docker build --no-cache -t {tag} {path}"
|
|
175
|
-
'docker', 'build', '--platform', build_platform, '-t', image_tag,
|
|
176
|
+
'docker', 'build', '--platform', build_platform, '-t', image_tag,
|
|
177
|
+
'-f', docker_file_path, self.path
|
|
176
178
|
], shell=True, check=False)
|
|
177
179
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
178
180
|
else:
|
|
179
181
|
response = subprocess.run([
|
|
180
182
|
# f"docker build --no-cache -t {tag} {path}"
|
|
181
|
-
f"docker build --platform {build_platform} -t {image_tag} {self.path}"
|
|
183
|
+
f"docker build --platform {build_platform} -t {image_tag} -f {docker_file_path} {self.path}"
|
|
182
184
|
], shell=True, check=False)
|
|
183
185
|
# stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
|
|
184
186
|
if response.returncode:
|
snapctl/commands/game.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Snapend CLI commands
|
|
3
|
+
"""
|
|
4
|
+
import requests
|
|
5
|
+
from requests.exceptions import RequestException
|
|
6
|
+
|
|
7
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
8
|
+
from snapctl.config.constants import SERVER_CALL_TIMEOUT
|
|
9
|
+
from snapctl.types.definitions import ResponseType
|
|
10
|
+
from snapctl.utils.echo import error, success
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Game:
|
|
14
|
+
"""
|
|
15
|
+
CLI commands exposed for a Game
|
|
16
|
+
"""
|
|
17
|
+
SUBCOMMANDS = ['create', 'enumerate']
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self, subcommand: str, base_url: str, api_key: str | None, name: str | None
|
|
21
|
+
) -> None:
|
|
22
|
+
self.subcommand: str = subcommand
|
|
23
|
+
self.base_url: str = base_url
|
|
24
|
+
self.api_key: str = api_key
|
|
25
|
+
self.name: str | None = name
|
|
26
|
+
|
|
27
|
+
def validate_input(self) -> ResponseType:
|
|
28
|
+
"""
|
|
29
|
+
Validator
|
|
30
|
+
"""
|
|
31
|
+
response: ResponseType = {
|
|
32
|
+
'error': True,
|
|
33
|
+
'msg': '',
|
|
34
|
+
'data': []
|
|
35
|
+
}
|
|
36
|
+
# Check API Key and Base URL
|
|
37
|
+
if not self.api_key or self.base_url == '':
|
|
38
|
+
response['msg'] = "Missing API Key."
|
|
39
|
+
return response
|
|
40
|
+
# Check subcommand
|
|
41
|
+
if not self.subcommand in Game.SUBCOMMANDS:
|
|
42
|
+
response['msg'] = \
|
|
43
|
+
f"Invalid command. Valid commands are {', '.join(Game.SUBCOMMANDS)}."
|
|
44
|
+
return response
|
|
45
|
+
# Check sdk-download commands
|
|
46
|
+
if self.subcommand == 'create':
|
|
47
|
+
if self.name is None or self.name == '':
|
|
48
|
+
response['msg'] = "Missing game name."
|
|
49
|
+
return response
|
|
50
|
+
# Send success
|
|
51
|
+
response['error'] = False
|
|
52
|
+
return response
|
|
53
|
+
|
|
54
|
+
def create(self) -> bool:
|
|
55
|
+
"""
|
|
56
|
+
Create a game
|
|
57
|
+
"""
|
|
58
|
+
with Progress(
|
|
59
|
+
SpinnerColumn(),
|
|
60
|
+
TextColumn("[progress.description]{task.description}"),
|
|
61
|
+
transient=True,
|
|
62
|
+
) as progress:
|
|
63
|
+
progress.add_task(
|
|
64
|
+
description='Creating a new game on Snapser...', total=None)
|
|
65
|
+
try:
|
|
66
|
+
url = f"{self.base_url}/v1/snapser-api/games"
|
|
67
|
+
payload = {
|
|
68
|
+
'name': self.name
|
|
69
|
+
}
|
|
70
|
+
res = requests.post(
|
|
71
|
+
url, headers={'api-key': self.api_key},
|
|
72
|
+
json=payload, timeout=SERVER_CALL_TIMEOUT
|
|
73
|
+
)
|
|
74
|
+
if res.ok:
|
|
75
|
+
success(f"Game {self.name} has been created successfully.")
|
|
76
|
+
return True
|
|
77
|
+
error('Unable to create a new game. Reason: ' + res.text)
|
|
78
|
+
except RequestException as e:
|
|
79
|
+
error(f"Exception: Unable to download the SDK {e}")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def enumerate(self) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Enumerate all games
|
|
85
|
+
"""
|
|
86
|
+
with Progress(
|
|
87
|
+
SpinnerColumn(),
|
|
88
|
+
TextColumn("[progress.description]{task.description}"),
|
|
89
|
+
transient=True,
|
|
90
|
+
) as progress:
|
|
91
|
+
progress.add_task(
|
|
92
|
+
description='Enumerating all your games...', total=None)
|
|
93
|
+
try:
|
|
94
|
+
url = f"{self.base_url}/v1/snapser-api/games"
|
|
95
|
+
res = requests.get(
|
|
96
|
+
url, headers={'api-key': self.api_key},
|
|
97
|
+
timeout=SERVER_CALL_TIMEOUT
|
|
98
|
+
)
|
|
99
|
+
response_json = res.json()
|
|
100
|
+
if res.ok:
|
|
101
|
+
if 'games' in response_json:
|
|
102
|
+
success(response_json['games'])
|
|
103
|
+
return True
|
|
104
|
+
error(response_json)
|
|
105
|
+
except RequestException as e:
|
|
106
|
+
error(f"Exception: Unable to update your snapend {e}")
|
|
107
|
+
return False
|
snapctl/commands/snapend.py
CHANGED
|
@@ -5,13 +5,15 @@ from sys import platform
|
|
|
5
5
|
from typing import Union
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
import json
|
|
8
9
|
import time
|
|
9
10
|
import requests
|
|
10
11
|
from requests.exceptions import RequestException
|
|
11
12
|
|
|
12
13
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
13
14
|
from snapctl.config.constants import SERVER_CALL_TIMEOUT
|
|
14
|
-
from snapctl.config.hashes import CLIENT_SDK_TYPES, SERVER_SDK_TYPES, PROTOS_TYPES
|
|
15
|
+
from snapctl.config.hashes import CLIENT_SDK_TYPES, SERVER_SDK_TYPES, PROTOS_TYPES, \
|
|
16
|
+
SNAPEND_MANIFEST_TYPES
|
|
15
17
|
from snapctl.types.definitions import ResponseType
|
|
16
18
|
from snapctl.utils.echo import error, success, info
|
|
17
19
|
|
|
@@ -20,38 +22,69 @@ class Snapend:
|
|
|
20
22
|
"""
|
|
21
23
|
CLI commands exposed for a Snapend
|
|
22
24
|
"""
|
|
23
|
-
SUBCOMMANDS = [
|
|
25
|
+
SUBCOMMANDS = [
|
|
26
|
+
'enumerate', 'clone', 'apply',
|
|
27
|
+
'download', 'update', 'state'
|
|
28
|
+
]
|
|
24
29
|
DOWNLOAD_CATEGORY = [
|
|
25
|
-
'client-sdk', 'server-sdk', 'protos', 'admin-settings'
|
|
30
|
+
'client-sdk', 'server-sdk', 'protos', 'admin-settings', 'snapend-manifest'
|
|
26
31
|
]
|
|
27
32
|
DOWNLOAD_TYPE_NOT_REQUIRED = ['admin-settings']
|
|
28
33
|
AUTH_TYPES = ['user', 'app']
|
|
34
|
+
ENV_TYPES = ['DEVELOPMENT', 'STAGING']
|
|
29
35
|
BLOCKING_CALL_SLEEP = 5
|
|
30
|
-
MAX_BLOCKING_RETRIES =
|
|
36
|
+
MAX_BLOCKING_RETRIES = 120
|
|
31
37
|
|
|
32
38
|
def __init__(
|
|
33
|
-
self, subcommand: str, base_url: str, api_key: str | None, snapend_id: str
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
self, subcommand: str, base_url: str, api_key: str | None, snapend_id: str | None,
|
|
40
|
+
# Enumerate, Clone
|
|
41
|
+
game_id: str | None,
|
|
42
|
+
# Clone
|
|
43
|
+
name: str | None,
|
|
44
|
+
env: str | None,
|
|
45
|
+
# Clone, Apply, Promote
|
|
46
|
+
manifest_path: str | None,
|
|
47
|
+
# Download
|
|
48
|
+
category: str, platform_type: str, auth_type: str, snaps: str | None,
|
|
49
|
+
# Clone, Apply, Promote, Download
|
|
50
|
+
out_path: str | None,
|
|
51
|
+
# Update
|
|
52
|
+
byosnaps: str | None, byogs: str | None, blocking: bool = False
|
|
36
53
|
) -> None:
|
|
37
54
|
self.subcommand: str = subcommand
|
|
38
55
|
self.base_url: str = base_url
|
|
39
56
|
self.api_key: str = api_key
|
|
40
57
|
self.snapend_id: str = snapend_id
|
|
58
|
+
self.game_id: str | None = game_id
|
|
59
|
+
self.name: str = name
|
|
60
|
+
self.env: str = env
|
|
61
|
+
self.manifest_path: str | None = manifest_path
|
|
62
|
+
self.manifest_file_name: str | None = Snapend._get_manifest_file_name(
|
|
63
|
+
manifest_path
|
|
64
|
+
)
|
|
41
65
|
self.category: str = category
|
|
42
66
|
self.download_types: Union[
|
|
43
67
|
dict[str, dict[str, str]], None
|
|
44
68
|
] = Snapend._make_download_type(category)
|
|
45
69
|
self.auth_type: str = auth_type
|
|
46
70
|
self.platform_type: str = platform_type
|
|
47
|
-
self.
|
|
48
|
-
self.snaps:
|
|
71
|
+
self.out_path: str | None = out_path
|
|
72
|
+
self.snaps: str | None = snaps
|
|
49
73
|
self.byosnap_list: Union[list, None] = Snapend._make_byosnap_list(
|
|
50
74
|
byosnaps) if byosnaps else None
|
|
51
|
-
self.byogs_list:
|
|
75
|
+
self.byogs_list: str | None = Snapend._make_byogs_list(
|
|
52
76
|
byogs) if byogs else None
|
|
53
77
|
self.blocking: bool = blocking
|
|
54
78
|
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _get_manifest_file_name(manifest_path: str) -> str | None:
|
|
81
|
+
if manifest_path and manifest_path != '' and os.path.isfile(manifest_path):
|
|
82
|
+
file_name = os.path.basename(manifest_path)
|
|
83
|
+
if file_name.endswith('.json') or file_name.endswith('.yml') or \
|
|
84
|
+
file_name.endswith('.yaml'):
|
|
85
|
+
return file_name
|
|
86
|
+
return None
|
|
87
|
+
|
|
55
88
|
@staticmethod
|
|
56
89
|
def _make_download_type(category: str):
|
|
57
90
|
if category == 'client-sdk':
|
|
@@ -60,6 +93,8 @@ class Snapend:
|
|
|
60
93
|
return SERVER_SDK_TYPES
|
|
61
94
|
if category == 'protos':
|
|
62
95
|
return PROTOS_TYPES
|
|
96
|
+
if category == 'snapend-manifest':
|
|
97
|
+
return SNAPEND_MANIFEST_TYPES
|
|
63
98
|
return None
|
|
64
99
|
|
|
65
100
|
@staticmethod
|
|
@@ -112,16 +147,88 @@ class Snapend:
|
|
|
112
147
|
error("Going past maximum tries. Exiting...")
|
|
113
148
|
return False
|
|
114
149
|
current_state = self._get_snapend_state()
|
|
115
|
-
if current_state
|
|
116
|
-
|
|
150
|
+
if current_state != 'IN_PROGRESS':
|
|
151
|
+
if current_state == 'LIVE':
|
|
152
|
+
success('Updated your snapend. Your snapend is Live.')
|
|
153
|
+
return True
|
|
154
|
+
error(
|
|
155
|
+
f"Update not completed successfully. Your Snapend status is {current_state}.")
|
|
117
156
|
return False
|
|
118
|
-
if current_state == 'LIVE':
|
|
119
|
-
success('Updated your snapend. Your snapend is Live.')
|
|
120
|
-
return True
|
|
121
157
|
info(f'Current snapend state is {current_state}')
|
|
122
158
|
info(f"Retrying in {Snapend.BLOCKING_CALL_SLEEP} seconds...")
|
|
123
159
|
time.sleep(Snapend.BLOCKING_CALL_SLEEP)
|
|
124
160
|
|
|
161
|
+
def _assign_snapend_id(self, snapend_id: str) -> None:
|
|
162
|
+
self.snapend_id = snapend_id
|
|
163
|
+
|
|
164
|
+
def _setup_for_download(self, platform_type: str) -> bool:
|
|
165
|
+
'''
|
|
166
|
+
Called by subcommands that want to initiate a download of the new manifest post update
|
|
167
|
+
'''
|
|
168
|
+
download_category: str = 'snapend-manifest'
|
|
169
|
+
self.category = download_category
|
|
170
|
+
self.platform_type = platform_type
|
|
171
|
+
self.download_types: Union[
|
|
172
|
+
dict[str, dict[str, str]], None
|
|
173
|
+
] = Snapend._make_download_type(download_category)
|
|
174
|
+
|
|
175
|
+
def _execute_download(self) -> bool:
|
|
176
|
+
try:
|
|
177
|
+
url = (
|
|
178
|
+
f"{self.base_url}/v1/snapser-api/snapends/{self.snapend_id}/"
|
|
179
|
+
f"download?category={self.category}"
|
|
180
|
+
)
|
|
181
|
+
if self.category not in Snapend.DOWNLOAD_TYPE_NOT_REQUIRED:
|
|
182
|
+
url += (
|
|
183
|
+
f"&type={self.download_types[self.platform_type]['type']}"
|
|
184
|
+
f"&subtype={self.download_types[self.platform_type]['subtype']}"
|
|
185
|
+
)
|
|
186
|
+
url_auth_type: str = 'user'
|
|
187
|
+
if self.category == 'server-sdk' and self.auth_type == 'app':
|
|
188
|
+
url_auth_type = 'app'
|
|
189
|
+
url += f"&auth_type={url_auth_type}"
|
|
190
|
+
if self.snaps:
|
|
191
|
+
url += f"&snaps={self.snaps}"
|
|
192
|
+
res = requests.get(
|
|
193
|
+
url, headers={'api-key': self.api_key}, timeout=SERVER_CALL_TIMEOUT
|
|
194
|
+
)
|
|
195
|
+
fn: str = ''
|
|
196
|
+
if self.category == 'admin-settings':
|
|
197
|
+
fn = f"snapser-{self.snapend_id}-admin-settings.json"
|
|
198
|
+
elif self.category == 'snapend-manifest':
|
|
199
|
+
fn = (
|
|
200
|
+
f"snapser-{self.snapend_id}-"
|
|
201
|
+
f"manifest.{self.download_types[self.platform_type]['type']}"
|
|
202
|
+
)
|
|
203
|
+
else:
|
|
204
|
+
fn = (
|
|
205
|
+
f"snapser-{self.snapend_id}-{self.category}"
|
|
206
|
+
f"-{self.platform_type}-{self.auth_type}.zip"
|
|
207
|
+
)
|
|
208
|
+
file_path_symbol = '/'
|
|
209
|
+
if platform == 'win32':
|
|
210
|
+
file_path_symbol = '\\'
|
|
211
|
+
if self.out_path is not None:
|
|
212
|
+
file_save_path = f"{self.out_path}{file_path_symbol}{fn}"
|
|
213
|
+
else:
|
|
214
|
+
file_save_path = f"{os.getcwd()}{file_path_symbol}{fn}"
|
|
215
|
+
if res.ok:
|
|
216
|
+
content: bytes = res.content
|
|
217
|
+
with open(file_save_path, "wb") as file:
|
|
218
|
+
if self.category in ['admin-settings']:
|
|
219
|
+
content = json.loads(res.content)
|
|
220
|
+
json.dump(content, file, indent=4)
|
|
221
|
+
else:
|
|
222
|
+
file.write(res.content)
|
|
223
|
+
success(f"{self.category} saved at {file_save_path}")
|
|
224
|
+
return True
|
|
225
|
+
error(f'Unable to download {self.category}')
|
|
226
|
+
except RequestException as e:
|
|
227
|
+
error(
|
|
228
|
+
f"Exception: Unable to download {self.category}. Reason: {e}"
|
|
229
|
+
)
|
|
230
|
+
return False
|
|
231
|
+
|
|
125
232
|
def validate_input(self) -> ResponseType:
|
|
126
233
|
"""
|
|
127
234
|
Validator
|
|
@@ -140,8 +247,34 @@ class Snapend:
|
|
|
140
247
|
response['msg'] = \
|
|
141
248
|
f"Invalid command. Valid commands are {', '.join(Snapend.SUBCOMMANDS)}."
|
|
142
249
|
return response
|
|
143
|
-
|
|
144
|
-
|
|
250
|
+
if self.subcommand == 'enumerate':
|
|
251
|
+
if not self.game_id:
|
|
252
|
+
response['msg'] = "Missing required parameter: game_id"
|
|
253
|
+
return response
|
|
254
|
+
elif self.subcommand == 'clone':
|
|
255
|
+
if not self.game_id:
|
|
256
|
+
response['msg'] = "Missing required parameter: game_id"
|
|
257
|
+
return response
|
|
258
|
+
if not self.name:
|
|
259
|
+
response['msg'] = "Missing required parameter: name"
|
|
260
|
+
return response
|
|
261
|
+
if self.env.upper() not in Snapend.ENV_TYPES:
|
|
262
|
+
response['msg'] = (
|
|
263
|
+
"Invalid environment. Valid environments are "
|
|
264
|
+
f"{', '.join(Snapend.ENV_TYPES)}."
|
|
265
|
+
)
|
|
266
|
+
return response
|
|
267
|
+
if not self.manifest_path:
|
|
268
|
+
response['msg'] = "Missing required parameter: manifest_path"
|
|
269
|
+
return response
|
|
270
|
+
elif self.subcommand == 'apply':
|
|
271
|
+
if not self.manifest_path:
|
|
272
|
+
response['msg'] = "Missing required parameter: manifest_path"
|
|
273
|
+
return response
|
|
274
|
+
if not self.manifest_file_name:
|
|
275
|
+
response['msg'] = "Invalid manifest file. Supported formats are .json, .yml, .yaml"
|
|
276
|
+
return response
|
|
277
|
+
elif self.subcommand == 'download':
|
|
145
278
|
if self.category not in Snapend.DOWNLOAD_CATEGORY:
|
|
146
279
|
response['msg'] = (
|
|
147
280
|
"Invalid SDK category. Valid categories are "
|
|
@@ -153,10 +286,10 @@ class Snapend:
|
|
|
153
286
|
response['msg'] = "Invalid Download type."
|
|
154
287
|
return response
|
|
155
288
|
# Check file path
|
|
156
|
-
if self.
|
|
289
|
+
if self.out_path and not os.path.isdir(f"{self.out_path}"):
|
|
157
290
|
response['msg'] = (
|
|
158
|
-
f"Invalid path {self.
|
|
159
|
-
"Please enter a valid path to save your
|
|
291
|
+
f"Invalid path {self.out_path}. "
|
|
292
|
+
"Please enter a valid path to save your output file"
|
|
160
293
|
)
|
|
161
294
|
return response
|
|
162
295
|
# Check the auth type
|
|
@@ -166,6 +299,10 @@ class Snapend:
|
|
|
166
299
|
f"{', '.join(Snapend.AUTH_TYPES)}."
|
|
167
300
|
)
|
|
168
301
|
return response
|
|
302
|
+
elif self.subcommand == 'promote':
|
|
303
|
+
if not self.snapend_id:
|
|
304
|
+
response['msg'] = "Missing required parameter: snapend_id"
|
|
305
|
+
return response
|
|
169
306
|
# Check update commands
|
|
170
307
|
elif self.subcommand == 'update':
|
|
171
308
|
byosnap_present = True
|
|
@@ -181,9 +318,10 @@ class Snapend:
|
|
|
181
318
|
response['error'] = False
|
|
182
319
|
return response
|
|
183
320
|
|
|
184
|
-
|
|
321
|
+
## Subcommands ##
|
|
322
|
+
def enumerate(self) -> bool:
|
|
185
323
|
"""
|
|
186
|
-
|
|
324
|
+
List Snapends
|
|
187
325
|
"""
|
|
188
326
|
with Progress(
|
|
189
327
|
SpinnerColumn(),
|
|
@@ -191,46 +329,217 @@ class Snapend:
|
|
|
191
329
|
transient=True,
|
|
192
330
|
) as progress:
|
|
193
331
|
progress.add_task(
|
|
194
|
-
description=
|
|
332
|
+
description='Enumerating all your game snapends...', total=None)
|
|
195
333
|
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}"
|
|
334
|
+
url = f"{self.base_url}/v1/snapser-api/snapends?game_id={self.game_id}"
|
|
211
335
|
res = requests.get(
|
|
212
|
-
url, headers={'api-key': self.api_key},
|
|
336
|
+
url, headers={'api-key': self.api_key},
|
|
337
|
+
timeout=SERVER_CALL_TIMEOUT
|
|
213
338
|
)
|
|
214
|
-
|
|
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}"
|
|
339
|
+
response_json = res.json()
|
|
224
340
|
if res.ok:
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
341
|
+
if 'clusters' in response_json:
|
|
342
|
+
success(response_json['clusters'])
|
|
343
|
+
return True
|
|
344
|
+
error(response_json)
|
|
345
|
+
except RequestException as e:
|
|
346
|
+
error(f"Exception: Unable to update your snapend {e}")
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
def clone(self) -> bool:
|
|
350
|
+
"""
|
|
351
|
+
Create a Snapend from a manifest
|
|
352
|
+
"""
|
|
353
|
+
with Progress(
|
|
354
|
+
SpinnerColumn(),
|
|
355
|
+
TextColumn("[progress.description]{task.description}"),
|
|
356
|
+
transient=True,
|
|
357
|
+
) as progress:
|
|
358
|
+
progress.add_task(
|
|
359
|
+
description='Applying your manifest...', total=None)
|
|
360
|
+
try:
|
|
361
|
+
with open(self.manifest_path, 'rb') as file:
|
|
362
|
+
files = {'snapend-manifest': file}
|
|
363
|
+
payload = {
|
|
364
|
+
'game_id': self.game_id,
|
|
365
|
+
'name': self.name,
|
|
366
|
+
'env': self.env.upper(),
|
|
367
|
+
'ext': self.manifest_file_name.split('.')[-1]
|
|
368
|
+
}
|
|
369
|
+
url = f"{self.base_url}/v1/snapser-api/snapends/snapend-manifest"
|
|
370
|
+
res = requests.post(
|
|
371
|
+
url, headers={'api-key': self.api_key},
|
|
372
|
+
files=files, data=payload, timeout=SERVER_CALL_TIMEOUT
|
|
373
|
+
)
|
|
374
|
+
if res.ok:
|
|
375
|
+
# extract the cluster ID
|
|
376
|
+
response = res.json()
|
|
377
|
+
if 'cluster' not in response or 'id' not in response['cluster']:
|
|
378
|
+
error(
|
|
379
|
+
'Something went wrong. Please try again in sometime.'
|
|
380
|
+
)
|
|
381
|
+
return False
|
|
382
|
+
self._assign_snapend_id(response['cluster']['id'])
|
|
383
|
+
info(
|
|
384
|
+
f"Cluster ID assigned: {response['cluster']['id']}")
|
|
385
|
+
if self.blocking:
|
|
386
|
+
status = self._blocking_get_status()
|
|
387
|
+
# Fetch the new manifest
|
|
388
|
+
if status is True:
|
|
389
|
+
# TODO: Uncomment this if we want to do an auto download
|
|
390
|
+
# self._setup_for_download(
|
|
391
|
+
# self.manifest_file_name.split('.')[-1])
|
|
392
|
+
# self._execute_download()
|
|
393
|
+
info(
|
|
394
|
+
'Do not forget to download the latest manifest.'
|
|
395
|
+
)
|
|
396
|
+
return True
|
|
397
|
+
info(
|
|
398
|
+
'Snapend clone has been initiated but the Snapend is not up yet.'
|
|
399
|
+
'Please try checking the status of the Snapend in some time'
|
|
400
|
+
)
|
|
401
|
+
return False
|
|
402
|
+
info(
|
|
403
|
+
"Snapend clone has been initiated. "
|
|
404
|
+
"You can check the status using "
|
|
405
|
+
f"`snapctl snapend state --snapend-id {response['cluster']['id']}`"
|
|
406
|
+
)
|
|
407
|
+
return True
|
|
408
|
+
error('Unable to apply the manifest. Reason: ' + res.text)
|
|
409
|
+
except RequestException as e:
|
|
410
|
+
error(f"Exception: Unable to apply the manifest snapend {e}")
|
|
411
|
+
return False
|
|
412
|
+
|
|
413
|
+
def apply(self) -> bool:
|
|
414
|
+
"""
|
|
415
|
+
Apply a manifest
|
|
416
|
+
"""
|
|
417
|
+
with Progress(
|
|
418
|
+
SpinnerColumn(),
|
|
419
|
+
TextColumn("[progress.description]{task.description}"),
|
|
420
|
+
transient=True,
|
|
421
|
+
) as progress:
|
|
422
|
+
progress.add_task(
|
|
423
|
+
description='Applying your manifest...', total=None)
|
|
424
|
+
try:
|
|
425
|
+
with open(self.manifest_path, 'rb') as file:
|
|
426
|
+
files = {'snapend-manifest': file}
|
|
427
|
+
payload = {
|
|
428
|
+
'ext': self.manifest_file_name.split('.')[-1]
|
|
429
|
+
}
|
|
430
|
+
url = f"{self.base_url}/v1/snapser-api/snapends/snapend-manifest"
|
|
431
|
+
res = requests.put(
|
|
432
|
+
url, headers={'api-key': self.api_key},
|
|
433
|
+
files=files, data=payload, timeout=SERVER_CALL_TIMEOUT
|
|
434
|
+
)
|
|
435
|
+
if res.ok:
|
|
436
|
+
# extract the cluster ID
|
|
437
|
+
response = res.json()
|
|
438
|
+
if 'cluster' not in response or 'id' not in response['cluster']:
|
|
439
|
+
error(
|
|
440
|
+
'Something went wrong. Please try again in sometime.'
|
|
441
|
+
)
|
|
442
|
+
return False
|
|
443
|
+
self._assign_snapend_id(response['cluster']['id'])
|
|
444
|
+
if self.blocking:
|
|
445
|
+
status = self._blocking_get_status()
|
|
446
|
+
# Fetch the new manifest
|
|
447
|
+
if status is True:
|
|
448
|
+
# TODO: Uncomment this if we want to do an auto download
|
|
449
|
+
# self._setup_for_download(
|
|
450
|
+
# self.manifest_file_name.split('.')[-1])
|
|
451
|
+
# self._execute_download()
|
|
452
|
+
info(
|
|
453
|
+
'Do not forget to download the latest manifest.'
|
|
454
|
+
)
|
|
455
|
+
return True
|
|
456
|
+
info(
|
|
457
|
+
'Snapend apply has been initiated but the Snapend is not up yet.'
|
|
458
|
+
'Please try checking the status of the Snapend in some time'
|
|
459
|
+
)
|
|
460
|
+
return False
|
|
461
|
+
info(
|
|
462
|
+
"Snapend apply has been initiated. "
|
|
463
|
+
"You can check the status using "
|
|
464
|
+
f"`snapctl snapend state --snapend-id {response['cluster']['id']}`"
|
|
465
|
+
)
|
|
466
|
+
return True
|
|
467
|
+
error('Unable to apply the manifest. Reason: ' + res.text)
|
|
468
|
+
except RequestException as e:
|
|
469
|
+
error(f"Exception: Unable to apply the manifest snapend {e}")
|
|
470
|
+
return False
|
|
471
|
+
|
|
472
|
+
def promote(self) -> bool:
|
|
473
|
+
"""
|
|
474
|
+
Promote a staging manifest to production
|
|
475
|
+
"""
|
|
476
|
+
with Progress(
|
|
477
|
+
SpinnerColumn(),
|
|
478
|
+
TextColumn("[progress.description]{task.description}"),
|
|
479
|
+
transient=True,
|
|
480
|
+
) as progress:
|
|
481
|
+
progress.add_task(
|
|
482
|
+
description='Promoting your staging snapend...', total=None)
|
|
483
|
+
try:
|
|
484
|
+
with open(self.manifest_path, 'rb') as file:
|
|
485
|
+
payload = {
|
|
486
|
+
'snapend_id': self.snapend_id
|
|
487
|
+
}
|
|
488
|
+
url = f"{self.base_url}/v1/snapser-api/snapends/promote"
|
|
489
|
+
res = requests.put(
|
|
490
|
+
url, headers={'api-key': self.api_key},
|
|
491
|
+
json=payload, timeout=SERVER_CALL_TIMEOUT
|
|
492
|
+
)
|
|
493
|
+
if res.ok:
|
|
494
|
+
# extract the cluster ID
|
|
495
|
+
response = res.json()
|
|
496
|
+
if 'cluster' not in response or 'id' not in response['cluster']:
|
|
497
|
+
error(
|
|
498
|
+
'Something went wrong. Please try again in sometime.'
|
|
499
|
+
)
|
|
500
|
+
return False
|
|
501
|
+
self._assign_snapend_id(response['cluster']['id'])
|
|
502
|
+
if self.blocking:
|
|
503
|
+
status = self._blocking_get_status()
|
|
504
|
+
if status is True:
|
|
505
|
+
# TODO: Uncomment this if we want to do an auto download
|
|
506
|
+
# self._setup_for_download(
|
|
507
|
+
# self.manifest_file_name.split('.')[-1])
|
|
508
|
+
# self._execute_download()
|
|
509
|
+
# Fetch the new manifest
|
|
510
|
+
info(
|
|
511
|
+
'Do not forget to download the latest manifest.'
|
|
512
|
+
)
|
|
513
|
+
return True
|
|
514
|
+
info(
|
|
515
|
+
'Snapend apply has been initiated but the Snapend is not up yet.'
|
|
516
|
+
'Please try checking the status of the Snapend in some time'
|
|
517
|
+
)
|
|
518
|
+
return False
|
|
519
|
+
info(
|
|
520
|
+
"Snapend apply has been initiated. "
|
|
521
|
+
"You can check the status using "
|
|
522
|
+
f"`snapctl snapend state --snapend-id {response['cluster']['id']}`"
|
|
523
|
+
)
|
|
524
|
+
return True
|
|
525
|
+
error('Unable to promote the manifest. Reason: ' + res.text)
|
|
230
526
|
except RequestException as e:
|
|
231
|
-
error(f"Exception: Unable to
|
|
527
|
+
error(f"Exception: Unable to apply the manifest snapend {e}")
|
|
232
528
|
return False
|
|
233
529
|
|
|
530
|
+
def download(self) -> bool:
|
|
531
|
+
"""
|
|
532
|
+
Download SDKs, Protos, Admin Settings and Configuration
|
|
533
|
+
"""
|
|
534
|
+
with Progress(
|
|
535
|
+
SpinnerColumn(),
|
|
536
|
+
TextColumn("[progress.description]{task.description}"),
|
|
537
|
+
transient=True,
|
|
538
|
+
) as progress:
|
|
539
|
+
progress.add_task(
|
|
540
|
+
description=f'Downloading your Custom {self.category}...', total=None)
|
|
541
|
+
return self._execute_download()
|
|
542
|
+
|
|
234
543
|
def update(self) -> bool:
|
|
235
544
|
"""
|
|
236
545
|
Update a Snapend
|
snapctl/config/constants.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Constants used by snapctl
|
|
3
3
|
"""
|
|
4
|
-
|
|
4
|
+
COMPANY_NAME = 'Snapser'
|
|
5
|
+
VERSION = '0.26.1'
|
|
5
6
|
CONFIG_FILE_MAC = '~/.snapser/config'
|
|
6
|
-
CONFIG_FILE_WIN = '%
|
|
7
|
+
CONFIG_FILE_WIN = '%homepath%\\.snapser\\config'
|
|
8
|
+
|
|
7
9
|
DEFAULT_PROFILE = 'default'
|
|
8
10
|
API_KEY = 'SNAPSER_API_KEY'
|
|
11
|
+
URL_KEY = 'SNAPSER_URL_KEY'
|
|
9
12
|
SERVER_CALL_TIMEOUT = 30
|
|
10
13
|
|
|
11
14
|
SNAPCTL_SUCCESS = 0
|
snapctl/config/endpoints.py
CHANGED
|
@@ -3,6 +3,7 @@ This file contains the endpoints for the Snapser API.
|
|
|
3
3
|
"""
|
|
4
4
|
END_POINTS = {
|
|
5
5
|
'DEV': 'https://gateway.dev.snapser.io/snapser',
|
|
6
|
+
'DEV_TWO': 'https://gateway.dev.snapser.io/devtwo',
|
|
6
7
|
'PLAYTEST': 'https://gateway.dev.snapser.io/playtest',
|
|
7
8
|
'PROD': 'https://gateway.snapser.com/snapser'
|
|
8
9
|
}
|
snapctl/config/hashes.py
CHANGED
|
@@ -126,6 +126,17 @@ PROTOS_TYPES: dict[str, dict[str, str]] = {
|
|
|
126
126
|
},
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
SNAPEND_MANIFEST_TYPES: dict[str, dict[str, str]] = {
|
|
130
|
+
'json': {
|
|
131
|
+
'type': 'json',
|
|
132
|
+
'subtype': '',
|
|
133
|
+
},
|
|
134
|
+
'yaml': {
|
|
135
|
+
'type': 'yaml',
|
|
136
|
+
'subtype': '',
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
|
|
129
140
|
SERVICE_IDS = [
|
|
130
141
|
'analytics', 'auth', 'client-logs', 'events', 'experiments', 'gdpr', 'guilds', 'hades', 'iap',
|
|
131
142
|
'inventory', 'leaderboards', 'matchmaking', 'notifications', 'parties', 'profiles', 'quests',
|
snapctl/main.py
CHANGED
|
@@ -6,23 +6,43 @@ import os
|
|
|
6
6
|
from sys import platform
|
|
7
7
|
from typing import Union, Callable
|
|
8
8
|
import typer
|
|
9
|
+
import pyfiglet
|
|
9
10
|
|
|
10
11
|
from snapctl.commands.byosnap import ByoSnap
|
|
11
12
|
from snapctl.commands.byogs import ByoGs
|
|
13
|
+
from snapctl.commands.game import Game
|
|
12
14
|
from snapctl.commands.snapend import Snapend
|
|
13
|
-
from snapctl.config.constants import
|
|
14
|
-
VERSION, SNAPCTL_SUCCESS, SNAPCTL_ERROR
|
|
15
|
+
from snapctl.config.constants import COMPANY_NAME, API_KEY, URL_KEY, CONFIG_FILE_MAC, \
|
|
16
|
+
CONFIG_FILE_WIN, DEFAULT_PROFILE, VERSION, SNAPCTL_SUCCESS, SNAPCTL_ERROR
|
|
15
17
|
from snapctl.config.endpoints import END_POINTS
|
|
16
|
-
from snapctl.config.hashes import CLIENT_SDK_TYPES, SERVER_SDK_TYPES, PROTOS_TYPES, SERVICE_IDS
|
|
18
|
+
from snapctl.config.hashes import CLIENT_SDK_TYPES, SERVER_SDK_TYPES, PROTOS_TYPES, SERVICE_IDS, \
|
|
19
|
+
SNAPEND_MANIFEST_TYPES
|
|
17
20
|
from snapctl.types.definitions import ResponseType
|
|
18
21
|
from snapctl.utils.echo import error, success, info
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
######### Globals #########
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def draw_ascii_text():
|
|
27
|
+
"""
|
|
28
|
+
Draws the ascii text for Snapser
|
|
29
|
+
"""
|
|
30
|
+
ascii_text = pyfiglet.figlet_format(COMPANY_NAME)
|
|
31
|
+
typer.echo(ascii_text)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
app = typer.Typer(
|
|
35
|
+
help=draw_ascii_text(),
|
|
36
|
+
context_settings={
|
|
37
|
+
"help_option_names": ["-h", "--help"]
|
|
38
|
+
}
|
|
39
|
+
)
|
|
40
|
+
|
|
21
41
|
|
|
22
42
|
######### HELPER METHODS #########
|
|
23
43
|
|
|
24
44
|
|
|
25
|
-
def
|
|
45
|
+
def extract_config(extract_key: str, profile: str | None = None) -> object:
|
|
26
46
|
"""
|
|
27
47
|
Extracts the API Key from the environment variable and if not present from the config file
|
|
28
48
|
"""
|
|
@@ -31,7 +51,7 @@ def extract_api_key(profile: str | None = None) -> object:
|
|
|
31
51
|
'value': None
|
|
32
52
|
}
|
|
33
53
|
# Option 1
|
|
34
|
-
env_api_key = os.getenv(
|
|
54
|
+
env_api_key = os.getenv(extract_key)
|
|
35
55
|
if env_api_key is not None:
|
|
36
56
|
result['location'] = 'environment-variable'
|
|
37
57
|
result['value'] = env_api_key
|
|
@@ -50,11 +70,11 @@ def extract_api_key(profile: str | None = None) -> object:
|
|
|
50
70
|
config.read(config_file_path, encoding=encoding)
|
|
51
71
|
config_profile: str = DEFAULT_PROFILE
|
|
52
72
|
if profile is not None and profile != '' and profile != DEFAULT_PROFILE:
|
|
53
|
-
result['location'] = f'
|
|
73
|
+
result['location'] = f'{config_file_path}:profile {profile}'
|
|
54
74
|
config_profile = f'profile {profile}'
|
|
55
|
-
info(f"Trying to extract API KEY from
|
|
75
|
+
info(f"Trying to extract API KEY from profile {profile}")
|
|
56
76
|
result['value'] = config.get(
|
|
57
|
-
config_profile,
|
|
77
|
+
config_profile, extract_key, fallback=None, raw=True
|
|
58
78
|
)
|
|
59
79
|
else:
|
|
60
80
|
error(
|
|
@@ -66,10 +86,17 @@ def get_base_url(api_key: str | None) -> str:
|
|
|
66
86
|
"""
|
|
67
87
|
Returns the base url based on the api_key
|
|
68
88
|
"""
|
|
89
|
+
# Check if the user has a URL override
|
|
90
|
+
url_key_obj = extract_config(URL_KEY, None)
|
|
91
|
+
if url_key_obj['value'] is not None:
|
|
92
|
+
return url_key_obj['value']
|
|
93
|
+
# If there was no override then we use the default
|
|
69
94
|
if api_key is None:
|
|
70
95
|
return ''
|
|
71
96
|
if api_key.startswith('dev_'):
|
|
72
97
|
return END_POINTS['DEV']
|
|
98
|
+
if api_key.startswith('devtwo_'):
|
|
99
|
+
return END_POINTS['DEV_TWO']
|
|
73
100
|
if api_key.startswith('playtest_'):
|
|
74
101
|
return END_POINTS['PLAYTEST']
|
|
75
102
|
return END_POINTS['PROD']
|
|
@@ -103,7 +130,7 @@ def default_context_callback(ctx: typer.Context):
|
|
|
103
130
|
# Ensure ctx object is instantiated
|
|
104
131
|
ctx.ensure_object(dict)
|
|
105
132
|
# Extract the api_key
|
|
106
|
-
api_key_obj =
|
|
133
|
+
api_key_obj = extract_config(API_KEY, None)
|
|
107
134
|
ctx.obj['version'] = VERSION
|
|
108
135
|
ctx.obj['api_key'] = api_key_obj['value']
|
|
109
136
|
ctx.obj['api_key_location'] = api_key_obj['location']
|
|
@@ -144,7 +171,7 @@ def profile_context_callback(
|
|
|
144
171
|
# info("In Profile Callback")
|
|
145
172
|
# Ensure ctx object is instantiated
|
|
146
173
|
ctx.ensure_object(dict)
|
|
147
|
-
api_key_obj =
|
|
174
|
+
api_key_obj = extract_config(API_KEY, profile)
|
|
148
175
|
if api_key_obj['value'] is None and profile is not None and profile != '':
|
|
149
176
|
conf_file = ''
|
|
150
177
|
if platform == 'win32':
|
|
@@ -233,7 +260,9 @@ def byosnap(
|
|
|
233
260
|
),
|
|
234
261
|
# publish-image and publish-version
|
|
235
262
|
tag: str = typer.Option(
|
|
236
|
-
None, "--tag", help=
|
|
263
|
+
None, "--tag", help=(
|
|
264
|
+
"(req: build, push publish-image and publish-version) Tag for your snap"
|
|
265
|
+
)
|
|
237
266
|
),
|
|
238
267
|
# publish-image
|
|
239
268
|
path: Union[str, None] = typer.Option(
|
|
@@ -360,6 +389,44 @@ def byogs(
|
|
|
360
389
|
success(f"BYOGs {subcommand} complete")
|
|
361
390
|
|
|
362
391
|
|
|
392
|
+
@app.command()
|
|
393
|
+
def game(
|
|
394
|
+
ctx: typer.Context,
|
|
395
|
+
# Required fields
|
|
396
|
+
subcommand: str = typer.Argument(
|
|
397
|
+
..., help="Game Subcommands: " + ", ".join(Game.SUBCOMMANDS) + "."
|
|
398
|
+
),
|
|
399
|
+
# name
|
|
400
|
+
name: str = typer.Option(
|
|
401
|
+
None, "--name",
|
|
402
|
+
help=("(req: create) Name of your game: ")
|
|
403
|
+
),
|
|
404
|
+
# overrides
|
|
405
|
+
api_key: Union[str, None] = typer.Option(
|
|
406
|
+
None, "--api-key", help="API Key override.", callback=api_key_context_callback
|
|
407
|
+
),
|
|
408
|
+
profile: Union[str, None] = typer.Option(
|
|
409
|
+
None, "--profile", help="Profile to use.", callback=profile_context_callback
|
|
410
|
+
),
|
|
411
|
+
) -> None:
|
|
412
|
+
"""
|
|
413
|
+
Game commands
|
|
414
|
+
"""
|
|
415
|
+
validate_command_context(ctx)
|
|
416
|
+
game_obj: Game = Game(
|
|
417
|
+
subcommand, ctx.obj['base_url'], ctx.obj['api_key'], name)
|
|
418
|
+
validate_input_response: ResponseType = game_obj.validate_input()
|
|
419
|
+
if validate_input_response['error']:
|
|
420
|
+
error(validate_input_response['msg'])
|
|
421
|
+
raise typer.Exit(SNAPCTL_ERROR)
|
|
422
|
+
command_method = subcommand.replace('-', '_')
|
|
423
|
+
method: Callable[..., bool] = getattr(game_obj, command_method)
|
|
424
|
+
if not method():
|
|
425
|
+
error(f"Game {subcommand} failed")
|
|
426
|
+
raise typer.Exit(SNAPCTL_ERROR)
|
|
427
|
+
success(f"Game {subcommand} complete")
|
|
428
|
+
|
|
429
|
+
|
|
363
430
|
@app.command()
|
|
364
431
|
def snapend(
|
|
365
432
|
ctx: typer.Context,
|
|
@@ -367,7 +434,21 @@ def snapend(
|
|
|
367
434
|
subcommand: str = typer.Argument(
|
|
368
435
|
..., help="Snapend Subcommands: " + ", ".join(Snapend.SUBCOMMANDS) + "."
|
|
369
436
|
),
|
|
370
|
-
snapend_id: str = typer.Argument(..., help="Snapend Id"),
|
|
437
|
+
# snapend_id: str = typer.Argument(..., help="Snapend Id"),
|
|
438
|
+
snapend_id: str = typer.Option(
|
|
439
|
+
None, "--snapend-id",
|
|
440
|
+
help=("(req: update, download) Snapend Id")
|
|
441
|
+
),
|
|
442
|
+
# enumerate
|
|
443
|
+
game_id: str = typer.Option(
|
|
444
|
+
None, "--game-id",
|
|
445
|
+
help="(req: enumerate, clone) Game Id"
|
|
446
|
+
),
|
|
447
|
+
# apply, clone
|
|
448
|
+
manifest_path: str = typer.Option(
|
|
449
|
+
None, "--manifest-path",
|
|
450
|
+
help="(req: apply|clone) Path to the manifest file"
|
|
451
|
+
),
|
|
371
452
|
# download
|
|
372
453
|
category: str = typer.Option(
|
|
373
454
|
None, "--category",
|
|
@@ -376,22 +457,22 @@ def snapend(
|
|
|
376
457
|
", ".join(Snapend.DOWNLOAD_CATEGORY) + "."
|
|
377
458
|
)
|
|
378
459
|
),
|
|
379
|
-
path: Union[str, None] = typer.Option(
|
|
380
|
-
None, "--path", help="(req: download) Path to save the SDK"),
|
|
381
460
|
platform_type: str = typer.Option(
|
|
382
461
|
None, "--type",
|
|
383
462
|
help=(
|
|
384
463
|
"(req: --category client-sdk|server-sdk|protos --type ) "
|
|
385
464
|
"SDK Types: client-sdk(" + ", ".join(CLIENT_SDK_TYPES.keys()) +
|
|
386
465
|
") server-sdk(" + ", ".join(SERVER_SDK_TYPES.keys()) +
|
|
387
|
-
") protos(" + ", ".join(PROTOS_TYPES.keys()) + ")"
|
|
466
|
+
") protos(" + ", ".join(PROTOS_TYPES.keys()) + ")" +
|
|
467
|
+
") snapend-manifest(" + \
|
|
468
|
+
", ".join(SNAPEND_MANIFEST_TYPES.keys()) + ")"
|
|
388
469
|
)
|
|
389
470
|
),
|
|
390
471
|
auth_type: str = typer.Option(
|
|
391
472
|
'user', "--auth-type",
|
|
392
473
|
help=(
|
|
393
474
|
"(optional: download) Only applicable for --category server-sdk --auth-type"
|
|
394
|
-
"Auth-Types: (
|
|
475
|
+
"Auth-Types: (" + ", ".join(Snapend.AUTH_TYPES) + ")"
|
|
395
476
|
)
|
|
396
477
|
),
|
|
397
478
|
snaps: Union[str, None] = typer.Option(
|
|
@@ -402,6 +483,17 @@ def snapend(
|
|
|
402
483
|
"snaps(" + ", ".join(SERVICE_IDS)
|
|
403
484
|
)
|
|
404
485
|
),
|
|
486
|
+
# Clone
|
|
487
|
+
name: Union[str, None] = typer.Option(
|
|
488
|
+
None, "--name", help="(req: clone) Snapend name"),
|
|
489
|
+
env: Union[str, None] = typer.Option(
|
|
490
|
+
None, "--env", help=(
|
|
491
|
+
"(req: clone) Snapend environment"
|
|
492
|
+
"Environments: (" + ", ".join(Snapend.ENV_TYPES) + ")"
|
|
493
|
+
)),
|
|
494
|
+
# Download, Apply, Clone
|
|
495
|
+
out_path: Union[str, None] = typer.Option(
|
|
496
|
+
None, "--out-path", help="(optional: download|apply|clone) Path to save the output file"),
|
|
405
497
|
# update
|
|
406
498
|
byosnaps: str = typer.Option(
|
|
407
499
|
None, "--byosnaps",
|
|
@@ -417,6 +509,7 @@ def snapend(
|
|
|
417
509
|
"Eg: fleet-1:service-1:v1.0.0,fleet-2:service-2:v1.0.0"
|
|
418
510
|
)
|
|
419
511
|
),
|
|
512
|
+
# create, update, promote, apply, clone
|
|
420
513
|
blocking: bool = typer.Option(
|
|
421
514
|
False, "--blocking",
|
|
422
515
|
help=(
|
|
@@ -437,9 +530,19 @@ def snapend(
|
|
|
437
530
|
"""
|
|
438
531
|
validate_command_context(ctx)
|
|
439
532
|
snapend_obj: Snapend = Snapend(
|
|
440
|
-
subcommand, ctx.obj['base_url'], ctx.obj['api_key'],
|
|
441
|
-
|
|
442
|
-
|
|
533
|
+
subcommand, ctx.obj['base_url'], ctx.obj['api_key'], snapend_id,
|
|
534
|
+
# Enumerate, Clone
|
|
535
|
+
game_id,
|
|
536
|
+
# Clone
|
|
537
|
+
name, env,
|
|
538
|
+
# Apply, Clone
|
|
539
|
+
manifest_path,
|
|
540
|
+
# Download
|
|
541
|
+
category, platform_type, auth_type, snaps,
|
|
542
|
+
# Download, Apply and Clone
|
|
543
|
+
out_path,
|
|
544
|
+
# Update
|
|
545
|
+
byosnaps, byogs, blocking
|
|
443
546
|
)
|
|
444
547
|
validate_input_response: ResponseType = snapend_obj.validate_input()
|
|
445
548
|
if validate_input_response['error']:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: snapctl
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.1
|
|
4
4
|
Summary: Snapser CLI Tool
|
|
5
5
|
Author: Ajinkya Apte
|
|
6
6
|
Author-email: aj@snapser.com
|
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
12
|
Requires-Dist: configparser (>=6.0.0,<7.0.0)
|
|
13
|
+
Requires-Dist: pyfiglet (>=1.0.2,<2.0.0)
|
|
13
14
|
Requires-Dist: requests (>=2.28.2,<3.0.0)
|
|
14
15
|
Requires-Dist: typer[all] (>=0.7.0,<0.8.0)
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
@@ -392,7 +393,7 @@ snapctl byogs publish-version --help
|
|
|
392
393
|
snapctl byogs publish-version $byogs_sid --tag $image_tag --prefix $prefix --version $version --http-port $ingress_port
|
|
393
394
|
```
|
|
394
395
|
|
|
395
|
-
###
|
|
396
|
+
### Game
|
|
396
397
|
|
|
397
398
|
#### 1. snapend help
|
|
398
399
|
|
|
@@ -403,27 +404,89 @@ See all the supported commands
|
|
|
403
404
|
snapctl snapend --help
|
|
404
405
|
```
|
|
405
406
|
|
|
407
|
+
#### 2. Create a game
|
|
408
|
+
Create a game
|
|
409
|
+
```
|
|
410
|
+
snapctl game create --name $gameName
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
#### 3. Enumerate games
|
|
414
|
+
List all the games
|
|
415
|
+
```
|
|
416
|
+
snapctl game enumerate
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
### Snapend
|
|
421
|
+
|
|
422
|
+
#### 1. snapend help
|
|
423
|
+
|
|
424
|
+
See all the supported commands
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
# Help for the snapend command
|
|
428
|
+
snapctl snapend --help
|
|
429
|
+
```
|
|
430
|
+
|
|
406
431
|
#### 2. Snapend Downloads
|
|
407
432
|
|
|
408
|
-
Download SDKs and Protos for your Snapend
|
|
433
|
+
Download Manifest, SDKs and Protos for your Snapend
|
|
409
434
|
|
|
410
435
|
```
|
|
411
|
-
# Help for the
|
|
436
|
+
# Help for the download command
|
|
412
437
|
snapctl snapend download --help
|
|
413
438
|
|
|
414
439
|
# Download your Snapend SDK and Protos
|
|
415
440
|
# $snapend_id = Cluster Id
|
|
416
|
-
# $category = client-sdk, server-sdk, protos
|
|
417
|
-
# $
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
#
|
|
441
|
+
# $category = snapend-manifest, client-sdk, server-sdk, protos
|
|
442
|
+
# $type = One of the supported types:
|
|
443
|
+
# snapend-manifest(yaml, json)
|
|
444
|
+
# client-sdk(unity, unreal, roblox, godot, cocos, ios-objc, ios-swift, android-java, android-kotlin, web-ts, web-js),
|
|
445
|
+
# server-sdk(csharp, cpp, lua, ts, go, python, kotlin, java, c, node, js, perl, php, closure, ruby, rust),
|
|
446
|
+
# protos(go, csharp, cpp, python)
|
|
421
447
|
# Example:
|
|
448
|
+
# snapctl snapend download gx5x6bc0 --category snapend-manifest --type yaml
|
|
422
449
|
# snapctl snapend download gx5x6bc0 --category client-sdk --type unity
|
|
423
|
-
snapctl snapend download $snapend_id --category $category --type $
|
|
450
|
+
snapctl snapend download $snapend_id --category $category --type $type
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### 3. Clone Snapend
|
|
454
|
+
|
|
455
|
+
Clone a Snapend from an existing manifest. Passing the blocking flag ensures your CLI command waits till the new Snapend is up.
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
# Help for the download command
|
|
459
|
+
snapctl snapend clone --help
|
|
460
|
+
|
|
461
|
+
# Download your Snapend SDK and Protos
|
|
462
|
+
# $gameId = Game Id
|
|
463
|
+
# $snapendName = Name of your new Snapend
|
|
464
|
+
# $env = One of development, staging
|
|
465
|
+
# $pathToManifest = Path to the manifest file; should include the file name
|
|
466
|
+
# Example:
|
|
467
|
+
# snapctl snapend clone --game-id 2581d802-aca-496c-8a76-1953ad0db165 --name new-snapend --env development --manifest-path "C:\Users\name\Downloads\snapser-ox1bcyim-manifest.json" --blocking
|
|
468
|
+
snapctl snapend clone --game-id $gameId --name $snapendName --env $env --manifest-path "$pathToManifest"
|
|
469
|
+
snapctl snapend clone --game-id $gameId --name $snapendName --env $env --manifest-path "$pathToManifest" --blocking
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
#### 4. Apply Snapend Changes
|
|
473
|
+
|
|
474
|
+
Apply changes to your Snapend from a manifest. You should have the latest manifest before applying changes. This is to prevent
|
|
475
|
+
a user stomping over someone elses changes. Passing the blocking flag ensures your CLI command waits till the update is complete.
|
|
476
|
+
|
|
477
|
+
```
|
|
478
|
+
# Help for the download command
|
|
479
|
+
snapctl snapend apply --help
|
|
480
|
+
|
|
481
|
+
# Download your Snapend SDK and Protos
|
|
482
|
+
# $pathToManifest = Path to the manifest file; should include the file name
|
|
483
|
+
# Example:
|
|
484
|
+
# snapctl snapend apply --manifest-path "C:\Users\name\Downloads\snapser-ox1bcyim-manifest.json" --blocking
|
|
485
|
+
snapctl snapend apply --manifest-path "$pathToManifest"
|
|
486
|
+
snapctl snapend apply --manifest-path "$pathToManifest" --blocking
|
|
424
487
|
```
|
|
425
488
|
|
|
426
|
-
####
|
|
489
|
+
#### 5. Update Snapend BYOSnap or BYOGs versions
|
|
427
490
|
|
|
428
491
|
Update your BYOSnap or BYOGs versions for the Snapend
|
|
429
492
|
|
|
@@ -443,7 +506,7 @@ snapctl snapend update --help
|
|
|
443
506
|
snapctl snapend update $snapend_id --byosnaps $byosnaps --byogs $byogs --blocking
|
|
444
507
|
```
|
|
445
508
|
|
|
446
|
-
####
|
|
509
|
+
#### 6. Get the Snapend state
|
|
447
510
|
|
|
448
511
|
Get the Snapend state
|
|
449
512
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
snapctl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
snapctl/__main__.py,sha256=43jKoTk8b85hk_MT6499N3ruHdEfM8WBImd_-3VzjI8,116
|
|
3
|
+
snapctl/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
snapctl/commands/byogs.py,sha256=S77gBVT7W-6Ph7D8YU_2YfFNFHhlnBYYg3hafuw_w5Q,19216
|
|
5
|
+
snapctl/commands/byosnap.py,sha256=RQv3r30dkz8n1BgEnE-tAnAzPLoz-CLEQoiy1WKZQeg,23172
|
|
6
|
+
snapctl/commands/game.py,sha256=rnsTqrXavE5D-KULXkDBQ-PBI8sSKK4it3S_YMJfiUY,3633
|
|
7
|
+
snapctl/commands/snapend.py,sha256=Wyw9XEYn5b8YdllJumhsaoXqNeEA6XNxGRxToqmgf5o,25263
|
|
8
|
+
snapctl/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
snapctl/config/constants.py,sha256=Lgp7ddPGBBtAaqdFPk7VRV0FIHmeSYWRvXUPVBz8m40,525
|
|
10
|
+
snapctl/config/endpoints.py,sha256=VAeOmx3k3ukB-9XuGI65KtCJwFK-KFgzor-UWE8JU0g,297
|
|
11
|
+
snapctl/config/hashes.py,sha256=R0t5xwY6DJpwiJZfo1SFh2qvADmDIrdPV7lD_IKbnJg,2889
|
|
12
|
+
snapctl/main.py,sha256=aSlAfOj3Lrb4gw5QHTWQ1gqCUHgtZ39GJ8SHxliTDik,18516
|
|
13
|
+
snapctl/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
snapctl/types/definitions.py,sha256=rkRyTBHzeQtCt_uYObgYvL5txnVq8r_n5g4IUAq2FWc,233
|
|
15
|
+
snapctl/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
snapctl/utils/echo.py,sha256=0nhWYDBQTfZGtl8EK21hJhj0now2rQmCT-yYMCKupy4,584
|
|
17
|
+
snapctl/utils/helper.py,sha256=xasSsg-0DgHT-TWejh2IXW-s9AYntO1D2O6Uy6BQLcE,1381
|
|
18
|
+
snapctl-0.26.1.dist-info/METADATA,sha256=7nVCIinJ40Wa14vFcSeWFVq2wdIax7l4eiUPBXrZZ6U,15246
|
|
19
|
+
snapctl-0.26.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
20
|
+
snapctl-0.26.1.dist-info/entry_points.txt,sha256=tkKW9MzmFdRs6Bgkv29G78i9WEBK4WIOWunPfe3t2Wg,44
|
|
21
|
+
snapctl-0.26.1.dist-info/RECORD,,
|
snapctl-0.22.3.dist-info/RECORD
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
snapctl/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
snapctl/__main__.py,sha256=43jKoTk8b85hk_MT6499N3ruHdEfM8WBImd_-3VzjI8,116
|
|
3
|
-
snapctl/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
snapctl/commands/byogs.py,sha256=hXRyqEYb_k3gdqmbXn1TqHYa0dDp6yOGQIgvxY03OC8,19077
|
|
5
|
-
snapctl/commands/byosnap.py,sha256=Yf9f5Os6vdfHBRVKx_E4IqAGctAtf32QobqUG0fBukw,23033
|
|
6
|
-
snapctl/commands/snapend.py,sha256=fX35lRZwqs2VSyu8XZL4FGrWmWSN6uFIF7L4qraV5K4,11420
|
|
7
|
-
snapctl/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
snapctl/config/constants.py,sha256=t7i5_OgJmURLXxfv_QKUbsj7mC9AoiOUMjhM5cGM0Ug,472
|
|
9
|
-
snapctl/config/endpoints.py,sha256=7aSu-tWwF4NcSh1eEpNlAa-Cjg2mitBLudSf41Wb47M,241
|
|
10
|
-
snapctl/config/hashes.py,sha256=yf2800SRpq1pr0BrEuj4CndoyZA1i2jrhg7dLRWNp7Y,2696
|
|
11
|
-
snapctl/main.py,sha256=3k7aLK3kcL8AX0CLD9LD7e0-kX6mamruEIYn8Nbpi3o,15356
|
|
12
|
-
snapctl/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
|
-
snapctl/types/definitions.py,sha256=rkRyTBHzeQtCt_uYObgYvL5txnVq8r_n5g4IUAq2FWc,233
|
|
14
|
-
snapctl/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
snapctl/utils/echo.py,sha256=0nhWYDBQTfZGtl8EK21hJhj0now2rQmCT-yYMCKupy4,584
|
|
16
|
-
snapctl/utils/helper.py,sha256=xasSsg-0DgHT-TWejh2IXW-s9AYntO1D2O6Uy6BQLcE,1381
|
|
17
|
-
snapctl-0.22.3.dist-info/METADATA,sha256=PE7iPefecHhnAIA1bZKHqOWiotsDMeadf0gOKLIrB2g,13204
|
|
18
|
-
snapctl-0.22.3.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
19
|
-
snapctl-0.22.3.dist-info/entry_points.txt,sha256=tkKW9MzmFdRs6Bgkv29G78i9WEBK4WIOWunPfe3t2Wg,44
|
|
20
|
-
snapctl-0.22.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|