tccli 3.0.1189.1__py2.py3-none-any.whl → 3.0.1191.1__py2.py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- tccli/__init__.py +1 -1
- tccli/command.py +7 -1
- tccli/configure.py +11 -3
- tccli/loaders.py +43 -8
- tccli/oauth.py +115 -0
- tccli/plugin.py +30 -0
- tccli/plugins/__init__.py +0 -0
- tccli/plugins/auth/__init__.py +59 -0
- tccli/plugins/auth/browser_flow.py +88 -0
- tccli/plugins/auth/login.py +130 -0
- tccli/plugins/auth/logout.py +21 -0
- tccli/plugins/auth/texts.py +30 -0
- tccli/plugins/test/__init__.py +87 -0
- tccli/plugins/test/add.py +31 -0
- tccli/services/__init__.py +2 -1
- tccli/services/apm/v20210622/api.json +19 -0
- tccli/services/cam/cam_client.py +138 -32
- tccli/services/cam/v20190116/api.json +203 -0
- tccli/services/cam/v20190116/examples.json +16 -0
- tccli/services/cdb/v20170320/api.json +4 -4
- tccli/services/cfg/v20210820/api.json +1 -1
- tccli/services/cfw/v20190904/api.json +82 -2
- tccli/services/dasb/dasb_client.py +53 -0
- tccli/services/dasb/v20191018/api.json +168 -0
- tccli/services/dasb/v20191018/examples.json +8 -0
- tccli/services/dlc/v20210125/api.json +287 -37
- tccli/services/dlc/v20210125/examples.json +18 -18
- tccli/services/dsgc/v20190723/api.json +13 -3
- tccli/services/emr/v20190103/api.json +1 -1
- tccli/services/ess/v20201111/api.json +56 -23
- tccli/services/ess/v20201111/examples.json +7 -1
- tccli/services/essbasic/v20210526/api.json +28 -1
- tccli/services/essbasic/v20210526/examples.json +6 -0
- tccli/services/iotexplorer/v20190423/api.json +2 -2
- tccli/services/iss/iss_client.py +118 -65
- tccli/services/iss/v20230517/api.json +78 -0
- tccli/services/iss/v20230517/examples.json +8 -0
- tccli/services/mps/v20190612/api.json +257 -8
- tccli/services/ocr/v20181119/api.json +10 -0
- tccli/services/region/v20220627/api.json +11 -11
- tccli/services/region/v20220627/examples.json +1 -1
- tccli/services/sms/v20210111/api.json +9 -9
- tccli/services/ssl/v20191205/api.json +3 -3
- tccli/services/ssl/v20191205/examples.json +2 -2
- tccli/services/tat/v20201028/api.json +10 -0
- tccli/services/tcb/v20180608/api.json +2 -2
- tccli/services/tdmq/v20200217/api.json +2 -2
- tccli/services/tdmq/v20200217/examples.json +2 -2
- tccli/services/tke/tke_client.py +364 -46
- tccli/services/tke/v20180525/api.json +9 -0
- tccli/services/tke/v20220501/api.json +525 -0
- tccli/services/tke/v20220501/examples.json +54 -0
- tccli/services/tmt/v20180321/api.json +2 -2
- tccli/services/vdb/v20230616/api.json +29 -1
- tccli/services/vod/v20180717/api.json +30 -9
- tccli/services/vod/v20180717/examples.json +1 -1
- tccli/services/vod/v20240718/api.json +121 -0
- tccli/services/vod/v20240718/examples.json +31 -0
- tccli/services/vod/vod_client.py +58 -0
- tccli/services/vpc/v20170312/api.json +9 -0
- tccli/services/wedata/v20210820/api.json +1 -1
- {tccli-3.0.1189.1.dist-info → tccli-3.0.1191.1.dist-info}/METADATA +2 -2
- {tccli-3.0.1189.1.dist-info → tccli-3.0.1191.1.dist-info}/RECORD +66 -54
- {tccli-3.0.1189.1.dist-info → tccli-3.0.1191.1.dist-info}/WHEEL +0 -0
- {tccli-3.0.1189.1.dist-info → tccli-3.0.1191.1.dist-info}/entry_points.txt +0 -0
- {tccli-3.0.1189.1.dist-info → tccli-3.0.1191.1.dist-info}/license_files/LICENSE +0 -0
tccli/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = '3.0.
|
1
|
+
__version__ = '3.0.1191.1'
|
tccli/command.py
CHANGED
@@ -6,6 +6,8 @@ import copy
|
|
6
6
|
import tccli.services as Services
|
7
7
|
import tccli.options_define as Options_define
|
8
8
|
from collections import OrderedDict
|
9
|
+
|
10
|
+
from tccli import oauth
|
9
11
|
from tccli.utils import Utils
|
10
12
|
from tccli.argument import CLIArgument, CustomArgument, ListArgument, BooleanArgument
|
11
13
|
from tccli.exceptions import UnknownArgumentError
|
@@ -176,12 +178,15 @@ class ServiceCommand(BaseCommand):
|
|
176
178
|
service_model = self._get_service_model()
|
177
179
|
for action in service_model["actions"]:
|
178
180
|
action_model = service_model["actions"][action]
|
181
|
+
action_caller = action_model.get("action_caller", None)
|
182
|
+
if not action_caller:
|
183
|
+
action_caller = Services.action_caller(self._service_name)()[action]
|
179
184
|
command_map[action] = ActionCommand(
|
180
185
|
service_name=self._service_name,
|
181
186
|
version=self._version,
|
182
187
|
action_name=action,
|
183
188
|
action_model=action_model,
|
184
|
-
action_caller=
|
189
|
+
action_caller=action_caller,
|
185
190
|
)
|
186
191
|
return command_map
|
187
192
|
|
@@ -286,6 +291,7 @@ class ActionCommand(BaseCommand):
|
|
286
291
|
action_parameters = self.cli_unfold_argument.build_action_parameters(parsed_args)
|
287
292
|
else:
|
288
293
|
action_parameters = self._build_action_parameters(parsed_args, self.argument_map)
|
294
|
+
oauth.maybe_refresh_credential(parsed_globals.profile if parsed_globals.profile else "default")
|
289
295
|
return self._action_caller(action_parameters, vars(parsed_globals))
|
290
296
|
|
291
297
|
def create_help_command(self):
|
tccli/configure.py
CHANGED
@@ -416,16 +416,24 @@ class ConfigureCommand(BasicConfigure):
|
|
416
416
|
|
417
417
|
def init_configures(self):
|
418
418
|
config = {}
|
419
|
-
|
419
|
+
import argparse
|
420
|
+
parser = argparse.ArgumentParser()
|
421
|
+
parser.add_argument("--profile", type=str)
|
422
|
+
args, _ = parser.parse_known_args()
|
423
|
+
profile = args.profile or "default"
|
424
|
+
profile_file = "%s.configure" % profile
|
425
|
+
|
426
|
+
if not self._profile_existed(profile_file)[0]:
|
420
427
|
config = {
|
421
428
|
"region": "ap-guangzhou",
|
422
429
|
"output": "json",
|
423
430
|
"arrayCount": 10,
|
424
431
|
"warning": "off"
|
425
432
|
}
|
426
|
-
self._init_configure(
|
433
|
+
self._init_configure(profile_file, config)
|
434
|
+
|
427
435
|
for profile_name in os.listdir(self.cli_path):
|
428
|
-
if profile_name ==
|
436
|
+
if profile_name == profile_file:
|
429
437
|
continue
|
430
438
|
if profile_name.endswith(".configure"):
|
431
439
|
self._init_configure(profile_name, {})
|
tccli/loaders.py
CHANGED
@@ -9,6 +9,7 @@ from tccli.utils import Utils
|
|
9
9
|
from tccli import __version__
|
10
10
|
from tccli.services import SERVICE_VERSIONS
|
11
11
|
from collections import OrderedDict
|
12
|
+
import tccli.plugin as plugin
|
12
13
|
|
13
14
|
BASE_TYPE = ["int64", "uint64", "string", "float", "bool", "date", "datetime", "datetime_iso", "binary"]
|
14
15
|
CLI_BASE_TYPE = ["Integer", "String", "Float", "Timestamp", "Boolean", "Binary"]
|
@@ -175,7 +176,15 @@ class Loader(object):
|
|
175
176
|
return version[1:5] + "-" + version[5:7] + "-" + version[7:9]
|
176
177
|
|
177
178
|
def get_available_services(self):
|
178
|
-
|
179
|
+
services = copy.deepcopy(SERVICE_VERSIONS)
|
180
|
+
for name, vers in plugin.import_plugins().items():
|
181
|
+
if name not in services:
|
182
|
+
services[name] = []
|
183
|
+
for ver, spec in vers.items():
|
184
|
+
api_ver = spec["metadata"]["apiVersion"]
|
185
|
+
if api_ver not in services[name]:
|
186
|
+
services[name].append(api_ver)
|
187
|
+
return services
|
179
188
|
|
180
189
|
def get_service_default_version(self, service):
|
181
190
|
args = sys.argv[1:]
|
@@ -194,15 +203,41 @@ class Loader(object):
|
|
194
203
|
services_path = self.get_services_path()
|
195
204
|
version = "v" + version.replace('-', '')
|
196
205
|
apis_path = os.path.join(services_path, service, version, "api.json")
|
197
|
-
|
206
|
+
model = {
|
207
|
+
"metadata": {},
|
208
|
+
"actions": {},
|
209
|
+
"objects": {},
|
210
|
+
}
|
211
|
+
if os.path.exists(apis_path):
|
212
|
+
if six.PY2:
|
213
|
+
with open(apis_path, 'r') as f:
|
214
|
+
model = json.load(f)
|
215
|
+
else:
|
216
|
+
with open(apis_path, 'r', encoding='utf-8') as f:
|
217
|
+
model = json.load(f)
|
218
|
+
|
219
|
+
# merge plugins
|
220
|
+
for plugin_name, vers in plugin.import_plugins().items():
|
221
|
+
|
222
|
+
if plugin_name != service:
|
223
|
+
continue
|
224
|
+
|
225
|
+
for ver, spec in vers.items():
|
226
|
+
|
227
|
+
# 2017-03-12 -> v20170312
|
228
|
+
compact_ver = 'v' + ver.replace('-', '')
|
229
|
+
|
230
|
+
if compact_ver != version:
|
231
|
+
continue
|
232
|
+
|
233
|
+
model["metadata"].update(spec["metadata"])
|
234
|
+
model["actions"].update(spec["actions"])
|
235
|
+
model["objects"].update(spec["objects"])
|
236
|
+
|
237
|
+
if not model:
|
198
238
|
raise Exception("Not find service:%s version:%s model" % (service, version))
|
199
239
|
|
200
|
-
|
201
|
-
with open(apis_path, 'r') as f:
|
202
|
-
return json.load(f)
|
203
|
-
else:
|
204
|
-
with open(apis_path, 'r', encoding='utf-8') as f:
|
205
|
-
return json.load(f)
|
240
|
+
return model
|
206
241
|
|
207
242
|
def get_service_description(self, service, version):
|
208
243
|
service_model = self.get_service_model(service, version)
|
tccli/oauth.py
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
import json
|
2
|
+
import os
|
3
|
+
import time
|
4
|
+
|
5
|
+
import requests
|
6
|
+
import uuid
|
7
|
+
|
8
|
+
_API_ENDPOINT = "https://cli.cloud.tencent.com"
|
9
|
+
_CRED_REFRESH_SAFE_DUR = 60 * 5
|
10
|
+
_ACCESS_REFRESH_SAFE_DUR = 60 * 5
|
11
|
+
|
12
|
+
|
13
|
+
def maybe_refresh_credential(profile):
|
14
|
+
cred_path = cred_path_of_profile(profile)
|
15
|
+
try:
|
16
|
+
with open(cred_path, "r") as cred_file:
|
17
|
+
cred = json.load(cred_file)
|
18
|
+
except IOError:
|
19
|
+
# file not found, don't check
|
20
|
+
return
|
21
|
+
|
22
|
+
if cred.get("type") != "oauth":
|
23
|
+
return
|
24
|
+
|
25
|
+
try:
|
26
|
+
now = time.time()
|
27
|
+
|
28
|
+
expires_at = cred["expiresAt"]
|
29
|
+
if expires_at - now > _CRED_REFRESH_SAFE_DUR:
|
30
|
+
return
|
31
|
+
|
32
|
+
token_info = cred["oauth"]
|
33
|
+
site = token_info["site"]
|
34
|
+
access_expires = token_info["expiresAt"]
|
35
|
+
if access_expires - now < _ACCESS_REFRESH_SAFE_DUR:
|
36
|
+
refresh_token = token_info["refreshToken"]
|
37
|
+
open_id = token_info["openId"]
|
38
|
+
new_token = refresh_user_token(refresh_token, open_id, site)
|
39
|
+
token_info.update(new_token)
|
40
|
+
|
41
|
+
access_token = token_info["accessToken"]
|
42
|
+
new_cred = get_temp_cred(access_token, site)
|
43
|
+
save_credential(token_info, new_cred, profile)
|
44
|
+
|
45
|
+
except KeyError as e:
|
46
|
+
print("failed to refresh credential, your credential file(%s) is corrupted, %s" % (cred_path, e))
|
47
|
+
|
48
|
+
except Exception as e:
|
49
|
+
print("failed to refresh credential, %s" % e)
|
50
|
+
|
51
|
+
|
52
|
+
def refresh_user_token(ref_token, open_id, site):
|
53
|
+
api_endpoint = _API_ENDPOINT + "/refresh_user_token"
|
54
|
+
body = {
|
55
|
+
"TraceId": str(uuid.uuid4()),
|
56
|
+
"RefreshToken": ref_token,
|
57
|
+
"OpenId": open_id,
|
58
|
+
"Site": site,
|
59
|
+
}
|
60
|
+
http_response = requests.post(api_endpoint, json=body, verify=False)
|
61
|
+
resp = http_response.json()
|
62
|
+
|
63
|
+
if "Error" in resp:
|
64
|
+
raise ValueError("refresh_user_token: %s" % json.dumps(resp))
|
65
|
+
|
66
|
+
return {
|
67
|
+
"accessToken": resp["AccessToken"],
|
68
|
+
"expiresAt": resp["ExpiresAt"],
|
69
|
+
}
|
70
|
+
|
71
|
+
|
72
|
+
def get_temp_cred(access_token, site):
|
73
|
+
api_endpoint = _API_ENDPOINT + "/get_temp_cred"
|
74
|
+
body = {
|
75
|
+
"TraceId": str(uuid.uuid4()),
|
76
|
+
"AccessToken": access_token,
|
77
|
+
"Site": site,
|
78
|
+
}
|
79
|
+
http_response = requests.post(api_endpoint, json=body, verify=False)
|
80
|
+
resp = http_response.json()
|
81
|
+
|
82
|
+
if "Error" in resp:
|
83
|
+
raise ValueError("get_temp_key: %s" % json.dumps(resp))
|
84
|
+
|
85
|
+
return {
|
86
|
+
"secretId": resp["SecretId"],
|
87
|
+
"secretKey": resp["SecretKey"],
|
88
|
+
"token": resp["Token"],
|
89
|
+
"expiresAt": resp["ExpiresAt"],
|
90
|
+
}
|
91
|
+
|
92
|
+
|
93
|
+
def cred_path_of_profile(profile):
|
94
|
+
return os.path.join(os.path.expanduser("~"), ".tccli", profile + ".credential")
|
95
|
+
|
96
|
+
|
97
|
+
def save_credential(token, new_cred, profile):
|
98
|
+
cred_path = cred_path_of_profile(profile)
|
99
|
+
|
100
|
+
cred = {
|
101
|
+
"type": "oauth",
|
102
|
+
"secretId": new_cred["secretId"],
|
103
|
+
"secretKey": new_cred["secretKey"],
|
104
|
+
"token": new_cred["token"],
|
105
|
+
"expiresAt": new_cred["expiresAt"],
|
106
|
+
"oauth": {
|
107
|
+
"openId": token["openId"],
|
108
|
+
"accessToken": token["accessToken"],
|
109
|
+
"expiresAt": token["expiresAt"],
|
110
|
+
"refreshToken": token["refreshToken"],
|
111
|
+
"site": token["site"],
|
112
|
+
},
|
113
|
+
}
|
114
|
+
with open(cred_path, "w") as cred_file:
|
115
|
+
json.dump(cred, cred_file, indent=4)
|
tccli/plugin.py
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
import importlib
|
2
|
+
import logging
|
3
|
+
import pkgutil
|
4
|
+
|
5
|
+
import tccli.plugins as plugins
|
6
|
+
|
7
|
+
_plugins = {}
|
8
|
+
_imported = False
|
9
|
+
|
10
|
+
|
11
|
+
def import_plugins():
|
12
|
+
global _imported
|
13
|
+
|
14
|
+
if not _imported:
|
15
|
+
_reimport_plugins()
|
16
|
+
_imported = True
|
17
|
+
|
18
|
+
return _plugins
|
19
|
+
|
20
|
+
|
21
|
+
def _reimport_plugins():
|
22
|
+
for _, name, _ in pkgutil.iter_modules(plugins.__path__, plugins.__name__ + "."):
|
23
|
+
mod = importlib.import_module(name)
|
24
|
+
register_service = getattr(mod, "register_service", None)
|
25
|
+
if not register_service:
|
26
|
+
logging.warning("invalid module %s" % name)
|
27
|
+
continue
|
28
|
+
register_service(_plugins)
|
29
|
+
|
30
|
+
return _plugins
|
File without changes
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
from tccli.plugins.auth.login import login_command_entrypoint
|
3
|
+
from tccli.plugins.auth.logout import logout_command_entrypoint
|
4
|
+
|
5
|
+
service_name = "auth"
|
6
|
+
service_version = "2024-08-20"
|
7
|
+
|
8
|
+
_spec = {
|
9
|
+
"metadata": {
|
10
|
+
"serviceShortName": service_name,
|
11
|
+
"apiVersion": service_version,
|
12
|
+
"description": "auth related commands",
|
13
|
+
},
|
14
|
+
"actions": {
|
15
|
+
"login": {
|
16
|
+
"name": "登陆",
|
17
|
+
"document": "login through sso",
|
18
|
+
"input": "loginRequest",
|
19
|
+
"output": "loginResponse",
|
20
|
+
"action_caller": login_command_entrypoint,
|
21
|
+
},
|
22
|
+
"logout": {
|
23
|
+
"name": "登出",
|
24
|
+
"document": "remove local credential file",
|
25
|
+
"input": "logoutRequest",
|
26
|
+
"output": "logoutResponse",
|
27
|
+
"action_caller": logout_command_entrypoint,
|
28
|
+
},
|
29
|
+
},
|
30
|
+
"objects": {
|
31
|
+
"loginRequest": {
|
32
|
+
"members": [
|
33
|
+
{
|
34
|
+
"name": "browser",
|
35
|
+
"member": "string",
|
36
|
+
"type": "string",
|
37
|
+
"required": False,
|
38
|
+
"document": "use browser=no to indicate no browser login mode",
|
39
|
+
},
|
40
|
+
],
|
41
|
+
},
|
42
|
+
"loginResponse": {
|
43
|
+
"members": [],
|
44
|
+
},
|
45
|
+
"logoutRequest": {
|
46
|
+
"members": [],
|
47
|
+
},
|
48
|
+
"logoutResponse": {
|
49
|
+
"members": [],
|
50
|
+
},
|
51
|
+
},
|
52
|
+
"version": "1.0",
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
def register_service(specs):
|
57
|
+
specs[service_name] = {
|
58
|
+
service_version: _spec,
|
59
|
+
}
|
@@ -0,0 +1,88 @@
|
|
1
|
+
import time
|
2
|
+
import traceback
|
3
|
+
import socket
|
4
|
+
from threading import Thread
|
5
|
+
from tccli import oauth
|
6
|
+
|
7
|
+
try:
|
8
|
+
from urlparse import urlparse, parse_qs
|
9
|
+
from Queue import Queue
|
10
|
+
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
|
11
|
+
from SocketServer import ThreadingTCPServer, TCPServer
|
12
|
+
except ImportError:
|
13
|
+
from urllib.parse import urlparse, parse_qs
|
14
|
+
from queue import Queue
|
15
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
16
|
+
from socketserver import ThreadingTCPServer, TCPServer
|
17
|
+
|
18
|
+
|
19
|
+
# chrome keeps previous connection alive, so use threading to avoid blocking
|
20
|
+
class ThreadingHTTPServer(ThreadingTCPServer):
|
21
|
+
allow_reuse_address = 1
|
22
|
+
|
23
|
+
def server_bind(self):
|
24
|
+
"""Override server_bind to store the server name."""
|
25
|
+
TCPServer.server_bind(self)
|
26
|
+
host, port = self.socket.getsockname()[:2]
|
27
|
+
self.server_name = socket.getfqdn(host)
|
28
|
+
self.server_port = port
|
29
|
+
|
30
|
+
|
31
|
+
class HTTPHandler(BaseHTTPRequestHandler):
|
32
|
+
result_queue = Queue(1)
|
33
|
+
|
34
|
+
def do_GET(self):
|
35
|
+
try:
|
36
|
+
parsed_url = urlparse(self.path)
|
37
|
+
query_vals = parse_qs(parsed_url.query)
|
38
|
+
|
39
|
+
open_id = query_vals.get("open_id")[0]
|
40
|
+
access_token = query_vals.get("access_token")[0]
|
41
|
+
refresh_token = query_vals.get("refresh_token")[0]
|
42
|
+
expires_at = int(query_vals.get("expires_at")[0])
|
43
|
+
state = query_vals.get("state")[0]
|
44
|
+
redirect_url = query_vals.get("redirect_url")[0]
|
45
|
+
site = query_vals.get("site")[0]
|
46
|
+
token = {
|
47
|
+
"openId": open_id,
|
48
|
+
"accessToken": access_token,
|
49
|
+
"refreshToken": refresh_token,
|
50
|
+
"expiresAt": expires_at,
|
51
|
+
"state": state,
|
52
|
+
"site": site,
|
53
|
+
}
|
54
|
+
cred = oauth.get_temp_cred(token["accessToken"], token["site"])
|
55
|
+
self.result_queue.put((token, cred))
|
56
|
+
self.send_response(307)
|
57
|
+
self.send_header("Location", redirect_url)
|
58
|
+
self.end_headers()
|
59
|
+
except Exception:
|
60
|
+
err = traceback.format_exc()
|
61
|
+
print(err)
|
62
|
+
self.send_response(400)
|
63
|
+
self.end_headers()
|
64
|
+
self.wfile.write("login failed due to the following error:\n\n".encode("utf-8"))
|
65
|
+
self.wfile.write(err.encode("utf-8"))
|
66
|
+
self.wfile.flush()
|
67
|
+
|
68
|
+
# suppress debug message
|
69
|
+
def log_message(self, format, *args):
|
70
|
+
pass
|
71
|
+
|
72
|
+
|
73
|
+
def try_run(start_search_port, end_search_port):
|
74
|
+
port = start_search_port
|
75
|
+
|
76
|
+
while port <= end_search_port:
|
77
|
+
server_address = ('', port)
|
78
|
+
try:
|
79
|
+
ThreadingHTTPServer.daemon_threads = True
|
80
|
+
httpd = ThreadingHTTPServer(server_address, HTTPHandler)
|
81
|
+
t = Thread(target=httpd.serve_forever)
|
82
|
+
t.setDaemon(True)
|
83
|
+
t.start()
|
84
|
+
return port, HTTPHandler.result_queue
|
85
|
+
except socket.error:
|
86
|
+
port += 1
|
87
|
+
|
88
|
+
raise socket.error("no port available from range [%d, %d]" % (start_search_port, end_search_port))
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
import base64
|
3
|
+
import json
|
4
|
+
import random
|
5
|
+
import string
|
6
|
+
import sys
|
7
|
+
import time
|
8
|
+
from six.moves.urllib.parse import urlencode
|
9
|
+
import webbrowser
|
10
|
+
|
11
|
+
from tccli import oauth
|
12
|
+
from tccli.plugins.auth import texts
|
13
|
+
|
14
|
+
_APP_ID = 100038427476
|
15
|
+
_AUTH_URL = "https://cloud.tencent.com/open/authorize"
|
16
|
+
_REDIRECT_URL = "https://cli.cloud.tencent.com/oauth"
|
17
|
+
_SITE = "cn"
|
18
|
+
_DEFAULT_LANG = "zh-CN"
|
19
|
+
|
20
|
+
_START_SEARCH_PORT = 9000
|
21
|
+
_END_SEARCH_PORT = _START_SEARCH_PORT + 100
|
22
|
+
|
23
|
+
|
24
|
+
def print_message(msg):
|
25
|
+
print(msg)
|
26
|
+
sys.stdout.flush()
|
27
|
+
|
28
|
+
|
29
|
+
def login_command_entrypoint(args, parsed_globals):
|
30
|
+
language = parsed_globals.get("language")
|
31
|
+
if not language:
|
32
|
+
language = _DEFAULT_LANG
|
33
|
+
texts.set_lang(language)
|
34
|
+
|
35
|
+
profile = parsed_globals.get("profile", "default")
|
36
|
+
if not profile:
|
37
|
+
profile = "default"
|
38
|
+
|
39
|
+
browser = args.get("browser")
|
40
|
+
|
41
|
+
login(browser != "no", profile, language)
|
42
|
+
|
43
|
+
|
44
|
+
def login(use_browser, profile, language):
|
45
|
+
characters = string.ascii_letters + string.digits
|
46
|
+
state = ''.join(random.choice(characters) for _ in range(10))
|
47
|
+
|
48
|
+
if use_browser:
|
49
|
+
token, cred = _get_token(state, language)
|
50
|
+
else:
|
51
|
+
token, cred = _get_token_no_browser(state, language)
|
52
|
+
|
53
|
+
if token["state"] != state:
|
54
|
+
raise ValueError("invalid state %s" % token["state"])
|
55
|
+
|
56
|
+
oauth.save_credential(token, cred, profile)
|
57
|
+
|
58
|
+
print_message("")
|
59
|
+
print_message(texts.get("login_success") % oauth.cred_path_of_profile(profile))
|
60
|
+
|
61
|
+
|
62
|
+
def _get_token(state, language):
|
63
|
+
from tccli.plugins.auth import browser_flow
|
64
|
+
|
65
|
+
port, result_queue = browser_flow.try_run(_START_SEARCH_PORT, _END_SEARCH_PORT)
|
66
|
+
|
67
|
+
redirect_params = {
|
68
|
+
"redirect_url": "http://localhost:%d" % port,
|
69
|
+
"lang": language,
|
70
|
+
"site": _SITE,
|
71
|
+
}
|
72
|
+
redirect_query = urlencode(redirect_params)
|
73
|
+
redirect_url = _REDIRECT_URL + "?" + redirect_query
|
74
|
+
url_params = {
|
75
|
+
"scope": "login",
|
76
|
+
"app_id": _APP_ID,
|
77
|
+
"redirect_url": redirect_url,
|
78
|
+
"state": state,
|
79
|
+
}
|
80
|
+
url_query = urlencode(url_params)
|
81
|
+
auth_url = _AUTH_URL + "?" + url_query
|
82
|
+
|
83
|
+
if not webbrowser.open(auth_url):
|
84
|
+
print_message(texts.get("login_failed_due_to_no_browser"))
|
85
|
+
sys.exit(1)
|
86
|
+
|
87
|
+
print_message(texts.get("login_prompt"))
|
88
|
+
print_message(auth_url)
|
89
|
+
|
90
|
+
# use polling to avoid being unresponsive in python2
|
91
|
+
while result_queue.empty():
|
92
|
+
time.sleep(1)
|
93
|
+
|
94
|
+
result = result_queue.get()
|
95
|
+
if isinstance(result, Exception):
|
96
|
+
raise result
|
97
|
+
|
98
|
+
return result
|
99
|
+
|
100
|
+
|
101
|
+
def _get_token_no_browser(state, language):
|
102
|
+
redirect_params = {
|
103
|
+
"browser": "no",
|
104
|
+
"lang": language,
|
105
|
+
"site": _SITE,
|
106
|
+
}
|
107
|
+
redirect_query = urlencode(redirect_params)
|
108
|
+
redirect_url = _REDIRECT_URL + "?" + redirect_query
|
109
|
+
url_params = {
|
110
|
+
"scope": "login",
|
111
|
+
"app_id": _APP_ID,
|
112
|
+
"redirect_url": redirect_url,
|
113
|
+
"state": state,
|
114
|
+
}
|
115
|
+
url_query = urlencode(url_params)
|
116
|
+
auth_url = _AUTH_URL + "?" + url_query
|
117
|
+
|
118
|
+
print_message(texts.get("login_prompt_no_browser"))
|
119
|
+
print_message("")
|
120
|
+
print_message(auth_url)
|
121
|
+
|
122
|
+
try:
|
123
|
+
input_func = raw_input
|
124
|
+
except NameError:
|
125
|
+
input_func = input
|
126
|
+
|
127
|
+
user_input = input_func(texts.get("login_prompt_code_no_browser"))
|
128
|
+
token = json.loads(base64.b64decode(user_input))
|
129
|
+
cred = oauth.get_temp_cred(token["accessToken"], token["site"])
|
130
|
+
return token, cred
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
import os
|
3
|
+
|
4
|
+
from tccli import oauth
|
5
|
+
from tccli.plugins.auth import texts
|
6
|
+
|
7
|
+
|
8
|
+
def logout_command_entrypoint(args, parsed_globals):
|
9
|
+
language = parsed_globals.get("language")
|
10
|
+
if not language:
|
11
|
+
language = "zh-CN"
|
12
|
+
texts.set_lang(language)
|
13
|
+
|
14
|
+
profile = parsed_globals.get("profile", "default")
|
15
|
+
if not profile:
|
16
|
+
profile = "default"
|
17
|
+
|
18
|
+
cred_path = oauth.cred_path_of_profile(profile)
|
19
|
+
if os.path.exists(cred_path):
|
20
|
+
os.remove(cred_path)
|
21
|
+
print(texts.get("logout") % cred_path)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
_lang = "zh-CN"
|
3
|
+
|
4
|
+
texts = {
|
5
|
+
"zh-CN": {
|
6
|
+
"login_prompt": "您的浏览器已打开, 请根据提示完成登录",
|
7
|
+
"login_prompt_no_browser": "在浏览器中转到以下链接, 并根据提示完成登录:",
|
8
|
+
"login_prompt_code_no_browser": "完成后,输入浏览器中提供的验证码:",
|
9
|
+
"login_failed_due_to_no_browser": "无法打开浏览器, 请尝试添加 '--browser no' 选项",
|
10
|
+
"login_success": "登陆成功, 密钥凭证已被写入: %s",
|
11
|
+
"logout": "登出成功, 密钥凭证已被删除: %s",
|
12
|
+
},
|
13
|
+
"en-US": {
|
14
|
+
"login_prompt": "Your browser is open, please complete the login according to the prompts",
|
15
|
+
"login_prompt_no_browser": "Go to the following link in your browser, and complete the sign-in prompts:",
|
16
|
+
"login_prompt_code_no_browser": "Once finished, enter the verification code provided in your browser:",
|
17
|
+
"login_failed_due_to_no_browser": "Failed to launch browser, please try option '--browser no'",
|
18
|
+
"login_success": "Login succeed, credential has been written to %s",
|
19
|
+
"logout": "Logout succeed, credential has been removed: %s",
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
|
24
|
+
def set_lang(lang):
|
25
|
+
global _lang
|
26
|
+
_lang = lang
|
27
|
+
|
28
|
+
|
29
|
+
def get(key):
|
30
|
+
return texts[_lang][key]
|