twc-cli 1.2.0__tar.gz → 1.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of twc-cli might be problematic. Click here for more details.
- {twc_cli-1.2.0 → twc_cli-1.3.1}/CHANGELOG.md +22 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/PKG-INFO +2 -1
- {twc_cli-1.2.0 → twc_cli-1.3.1}/pyproject.toml +2 -1
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/__main__.py +7 -3
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/__version__.py +1 -1
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/api/client.py +184 -2
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/commands/__init__.py +7 -3
- twc_cli-1.3.1/twc/commands/config.py +174 -0
- twc_cli-1.3.1/twc/commands/storage.py +823 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/fmt.py +14 -1
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/vars.py +1 -1
- twc_cli-1.2.0/setup.py +0 -43
- twc_cli-1.2.0/twc/commands/config.py +0 -47
- {twc_cli-1.2.0 → twc_cli-1.3.1}/COPYING +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/README.md +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/__init__.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/api/__init__.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/api/exceptions.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/click_ext.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/commands/account.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/commands/database.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/commands/image.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/commands/project.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/commands/server.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/commands/ssh_key.py +0 -0
- {twc_cli-1.2.0 → twc_cli-1.3.1}/twc/utils.py +0 -0
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
В этом файле описаны все значимые изменения в Timeweb Cloud CLI. В выпусках мы придерживается правил [семантического версионирования](https://semver.org/lang/ru/).
|
|
4
4
|
|
|
5
|
+
# Версия 1.3.1 (2023.04.13)
|
|
6
|
+
|
|
7
|
+
Изменено:
|
|
8
|
+
|
|
9
|
+
- В команде `twc s3 mb` опция `--preset-id` теперь необязательная. Будет выбран минимальный пресет.
|
|
10
|
+
|
|
11
|
+
Исправлено:
|
|
12
|
+
|
|
13
|
+
- Исправлена ошибка при попытке создать дополнительный профиль через `twc config`.
|
|
14
|
+
|
|
15
|
+
# Версия 1.3.0 (2023.04.13)
|
|
16
|
+
|
|
17
|
+
Добавлено:
|
|
18
|
+
|
|
19
|
+
- Добавлена команда `twc storage` (алиас `twc s3`) для управления бакетами в объектном хранилище Timeweb Cloud. Доступны базовые операции с бакетами. Также реализованы субкоманды для работы с доменами и пользователями хранилища. Обратите внимание, что в twc не планируется добавлять реализацию S3-клиента, пользуйтесь любым совместимым клиентом, например, [s3cmd](https://s3tools.org/s3cmd) или [rclone](https://rclone.org/), команда `twc s3 genconfig` позволит сгенерировать конфигурационный файл для этих утилит (см. [документацию](docs/ru/README.md#получение-конфигурации-для-s3-клиентов)).
|
|
20
|
+
- Добавлены субкоманды для `twc config`. Теперь можно: сделать дамп настроек CLI (`twc config dump`), изменять настройки без ручного редактирования файла (`twc config set`), получить путь до файла (`twc config file`), открыть файл в редакторе по умолчанию (`twc config edit`).
|
|
21
|
+
|
|
22
|
+
Изменено:
|
|
23
|
+
|
|
24
|
+
- Изменены тексты ошибок при работе с конфигурационным файлом, теперь они более наглядные и явно указывают на проблему.
|
|
25
|
+
- Изменено поведение команды `twc config`. Теперь если уже иммется конфигурационный файл команда предложит добавить новый профиль или отредактировать существующий.
|
|
26
|
+
|
|
5
27
|
# Версия 1.2.0 (2023.04.03)
|
|
6
28
|
|
|
7
29
|
Добавлено:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: twc-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.1
|
|
4
4
|
Summary: Timeweb Cloud Command Line Interface.
|
|
5
5
|
Home-page: https://github.com/timeweb-cloud/twc
|
|
6
6
|
License: MIT
|
|
@@ -15,6 +15,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
16
16
|
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
17
17
|
Requires-Dist: click-aliases (>=1.0.1,<2.0.0)
|
|
18
|
+
Requires-Dist: click-default-group (>=1.2.2,<2.0.0)
|
|
18
19
|
Requires-Dist: pygments (>=2.14.0,<3.0.0)
|
|
19
20
|
Requires-Dist: pyyaml (>=6.0,<7.0)
|
|
20
21
|
Requires-Dist: requests (>=2.28.1,<3.0.0)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "twc-cli"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.1"
|
|
4
4
|
description = "Timeweb Cloud Command Line Interface."
|
|
5
5
|
authors = ["ge <dev@timeweb.cloud>"]
|
|
6
6
|
homepage = "https://github.com/timeweb-cloud/twc"
|
|
@@ -20,6 +20,7 @@ click = "^8.1.3"
|
|
|
20
20
|
click-aliases = "^1.0.1"
|
|
21
21
|
toml = "^0.10.2"
|
|
22
22
|
pyyaml = "^6.0"
|
|
23
|
+
click-default-group = "^1.2.2"
|
|
23
24
|
|
|
24
25
|
[tool.poetry.group.dev.dependencies]
|
|
25
26
|
black = "^22.12.0"
|
|
@@ -12,6 +12,7 @@ from .commands.ssh_key import ssh_key
|
|
|
12
12
|
from .commands.image import image
|
|
13
13
|
from .commands.project import project
|
|
14
14
|
from .commands.database import database
|
|
15
|
+
from .commands.storage import storage
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class AliasedCmdGroup(click.Group):
|
|
@@ -44,7 +45,7 @@ class AliasedCmdGroup(click.Group):
|
|
|
44
45
|
continue
|
|
45
46
|
commands.append((subcommand, cmd))
|
|
46
47
|
|
|
47
|
-
if
|
|
48
|
+
if commands:
|
|
48
49
|
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
|
|
49
50
|
rows = []
|
|
50
51
|
for subcommand, cmd in commands:
|
|
@@ -53,8 +54,8 @@ class AliasedCmdGroup(click.Group):
|
|
|
53
54
|
for a in list(ALIASES.keys()):
|
|
54
55
|
if ALIASES[a].name == cmd.name:
|
|
55
56
|
alias = f" ({a})"
|
|
56
|
-
|
|
57
|
-
rows.append((subcommand + alias,
|
|
57
|
+
help_text = cmd.get_short_help_str(limit)
|
|
58
|
+
rows.append((subcommand + alias, help_text))
|
|
58
59
|
if rows:
|
|
59
60
|
with formatter.section(_("Commands")):
|
|
60
61
|
formatter.write_dl(rows)
|
|
@@ -73,6 +74,7 @@ cli.add_command(ssh_key)
|
|
|
73
74
|
cli.add_command(image)
|
|
74
75
|
cli.add_command(project)
|
|
75
76
|
cli.add_command(database)
|
|
77
|
+
cli.add_command(storage)
|
|
76
78
|
|
|
77
79
|
|
|
78
80
|
# -- Aliases list for root level commands. --
|
|
@@ -89,4 +91,6 @@ ALIASES = {
|
|
|
89
91
|
"p": project,
|
|
90
92
|
"dbs": database,
|
|
91
93
|
"db": database,
|
|
94
|
+
"storages": storage,
|
|
95
|
+
"s3": storage,
|
|
92
96
|
}
|
|
@@ -32,14 +32,15 @@ def raise_exceptions(func):
|
|
|
32
32
|
except AttributeError:
|
|
33
33
|
is_json = False
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
# Remove 201 to avoid API bug (201 with no body)
|
|
36
|
+
if status_code in [200, 400, 403, 404, 409, 429, 500]:
|
|
36
37
|
if is_json:
|
|
37
38
|
return response # Success
|
|
38
39
|
raise NonJSONResponseError(
|
|
39
40
|
f"Code: {status_code}, Response body: {response.text}"
|
|
40
41
|
)
|
|
41
42
|
|
|
42
|
-
if status_code
|
|
43
|
+
if status_code in [201, 204]:
|
|
43
44
|
return response # Success
|
|
44
45
|
|
|
45
46
|
if status_code == 401:
|
|
@@ -985,3 +986,184 @@ class TimewebCloud(metaclass=TimewebCloudMeta):
|
|
|
985
986
|
"""Restore database backup."""
|
|
986
987
|
url = f"{self.api_url}/dbs/{db_id}/backups/{backup_id}"
|
|
987
988
|
return requests.put(url, headers=self.headers, timeout=self.timeout)
|
|
989
|
+
|
|
990
|
+
# -----------------------------------------------------------------------
|
|
991
|
+
# Object Storage
|
|
992
|
+
|
|
993
|
+
def get_storage_presets(self):
|
|
994
|
+
"""Get storage presets list."""
|
|
995
|
+
url = f"{self.api_url}/presets/storages"
|
|
996
|
+
return requests.get(url, headers=self.headers, timeout=self.timeout)
|
|
997
|
+
|
|
998
|
+
def get_buckets(self):
|
|
999
|
+
"""Get buckets list."""
|
|
1000
|
+
url = f"{self.api_url}/storages/buckets"
|
|
1001
|
+
return requests.get(url, headers=self.headers, timeout=self.timeout)
|
|
1002
|
+
|
|
1003
|
+
def create_bucket(
|
|
1004
|
+
self, name: str = None, preset_id: int = None, is_public: bool = False
|
|
1005
|
+
):
|
|
1006
|
+
"""Create storage bucket."""
|
|
1007
|
+
url = f"{self.api_url}/storages/buckets"
|
|
1008
|
+
self.headers.update({"Content-Type": "application/json"})
|
|
1009
|
+
if is_public:
|
|
1010
|
+
bucket_type = "public"
|
|
1011
|
+
else:
|
|
1012
|
+
bucket_type = "private"
|
|
1013
|
+
payload = {
|
|
1014
|
+
"name": name,
|
|
1015
|
+
"type": bucket_type,
|
|
1016
|
+
"preset_id": preset_id,
|
|
1017
|
+
}
|
|
1018
|
+
return requests.post(
|
|
1019
|
+
url,
|
|
1020
|
+
headers=self.headers,
|
|
1021
|
+
timeout=self.timeout,
|
|
1022
|
+
data=json.dumps(payload),
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
def delete_bucket(
|
|
1026
|
+
self, bucket_id: int, delete_hash: str = None, code: int = None
|
|
1027
|
+
):
|
|
1028
|
+
"""Delete storage bucket."""
|
|
1029
|
+
url = f"{self.api_url}/storages/buckets/{bucket_id}"
|
|
1030
|
+
params = {}
|
|
1031
|
+
if delete_hash:
|
|
1032
|
+
params["hash"] = delete_hash
|
|
1033
|
+
if code:
|
|
1034
|
+
params["code"] = code
|
|
1035
|
+
return requests.delete(
|
|
1036
|
+
url, headers=self.headers, timeout=self.timeout, params=params
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
def update_bucket(
|
|
1040
|
+
self, bucket_id: int, preset_id: int = None, is_public: bool = None
|
|
1041
|
+
):
|
|
1042
|
+
"""Update storage bucket."""
|
|
1043
|
+
url = f"{self.api_url}/storages/buckets/{bucket_id}"
|
|
1044
|
+
self.headers.update({"Content-Type": "application/json"})
|
|
1045
|
+
payload = {}
|
|
1046
|
+
if is_public is None:
|
|
1047
|
+
pass
|
|
1048
|
+
elif is_public is False:
|
|
1049
|
+
payload["bucket_type"] = "private"
|
|
1050
|
+
elif is_public is True:
|
|
1051
|
+
payload["bucket_type"] = "public"
|
|
1052
|
+
if preset_id:
|
|
1053
|
+
payload["preset_id"] = preset_id
|
|
1054
|
+
return requests.patch(
|
|
1055
|
+
url,
|
|
1056
|
+
headers=self.headers,
|
|
1057
|
+
timeout=self.timeout,
|
|
1058
|
+
data=json.dumps(payload),
|
|
1059
|
+
)
|
|
1060
|
+
|
|
1061
|
+
def get_storage_users(self):
|
|
1062
|
+
"""Get storage users list."""
|
|
1063
|
+
url = f"{self.api_url}/storages/users"
|
|
1064
|
+
return requests.get(url, headers=self.headers, timeout=self.timeout)
|
|
1065
|
+
|
|
1066
|
+
def update_storage_user_secret(
|
|
1067
|
+
self, user_id: int = None, secret_key: str = None
|
|
1068
|
+
):
|
|
1069
|
+
"""Update storage user secret key."""
|
|
1070
|
+
url = f"{self.api_url}/storages/users/{user_id}"
|
|
1071
|
+
self.headers.update({"Content-Type": "application/json"})
|
|
1072
|
+
payload = {"secret_key": secret_key}
|
|
1073
|
+
return requests.patch(
|
|
1074
|
+
url,
|
|
1075
|
+
headers=self.headers,
|
|
1076
|
+
timeout=self.timeout,
|
|
1077
|
+
data=json.dumps(payload),
|
|
1078
|
+
)
|
|
1079
|
+
|
|
1080
|
+
def get_storage_transfer_status(self, bucket_id: int = None):
|
|
1081
|
+
"""Get storage transfer status."""
|
|
1082
|
+
url = f"{self.api_url}/storages/buckets/{bucket_id}/transfer-status"
|
|
1083
|
+
return requests.get(url, headers=self.headers, timeout=self.timeout)
|
|
1084
|
+
|
|
1085
|
+
def start_storage_transfer(
|
|
1086
|
+
self,
|
|
1087
|
+
src_bucket: str = None,
|
|
1088
|
+
dst_bucket: str = None,
|
|
1089
|
+
access_key: str = None,
|
|
1090
|
+
secret_key: str = None,
|
|
1091
|
+
location: str = None,
|
|
1092
|
+
endpoint: str = None,
|
|
1093
|
+
force_path_style: bool = False,
|
|
1094
|
+
):
|
|
1095
|
+
"""Start file transfer from any S3-compatible storage to Timeweb Cloud
|
|
1096
|
+
Object Storage.
|
|
1097
|
+
"""
|
|
1098
|
+
url = f"{self.api_url}/storages/transfer"
|
|
1099
|
+
self.headers.update({"Content-Type": "application/json"})
|
|
1100
|
+
payload = {
|
|
1101
|
+
"access_key": access_key,
|
|
1102
|
+
"secret_key": secret_key,
|
|
1103
|
+
"location": location,
|
|
1104
|
+
"is_force_path_style": force_path_style,
|
|
1105
|
+
"endpoint": endpoint,
|
|
1106
|
+
"bucket_name": src_bucket,
|
|
1107
|
+
"new_bucket_name": dst_bucket,
|
|
1108
|
+
}
|
|
1109
|
+
return requests.post(
|
|
1110
|
+
url,
|
|
1111
|
+
headers=self.headers,
|
|
1112
|
+
timeout=self.timeout,
|
|
1113
|
+
data=json.dumps(payload),
|
|
1114
|
+
)
|
|
1115
|
+
|
|
1116
|
+
def get_bucket_subdomains(self, bucket_id: int = None):
|
|
1117
|
+
"""Get bucket subdomains list."""
|
|
1118
|
+
url = f"{self.api_url}/storages/buckets/{bucket_id}/subdomains"
|
|
1119
|
+
return requests.get(url, headers=self.headers, timeout=self.timeout)
|
|
1120
|
+
|
|
1121
|
+
def add_bucket_subdomains(
|
|
1122
|
+
self,
|
|
1123
|
+
bucket_id: int = None,
|
|
1124
|
+
subdomains: list = None,
|
|
1125
|
+
):
|
|
1126
|
+
"""Add subdomains to bucket."""
|
|
1127
|
+
url = f"{self.api_url}/storages/buckets/{bucket_id}/subdomains"
|
|
1128
|
+
self.headers.update({"Content-Type": "application/json"})
|
|
1129
|
+
payload = {
|
|
1130
|
+
"subdomains": subdomains,
|
|
1131
|
+
}
|
|
1132
|
+
return requests.post(
|
|
1133
|
+
url,
|
|
1134
|
+
headers=self.headers,
|
|
1135
|
+
timeout=self.timeout,
|
|
1136
|
+
data=json.dumps(payload),
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
def delete_bucket_subdomains(
|
|
1140
|
+
self,
|
|
1141
|
+
bucket_id: int = None,
|
|
1142
|
+
subdomains: list = None,
|
|
1143
|
+
):
|
|
1144
|
+
"""Delete bucket subdomains."""
|
|
1145
|
+
url = f"{self.api_url}/storages/buckets/{bucket_id}/subdomains"
|
|
1146
|
+
self.headers.update({"Content-Type": "application/json"})
|
|
1147
|
+
payload = {
|
|
1148
|
+
"subdomains": subdomains,
|
|
1149
|
+
}
|
|
1150
|
+
return requests.delete(
|
|
1151
|
+
url,
|
|
1152
|
+
headers=self.headers,
|
|
1153
|
+
timeout=self.timeout,
|
|
1154
|
+
data=json.dumps(payload),
|
|
1155
|
+
)
|
|
1156
|
+
|
|
1157
|
+
def gen_cert_for_bucket_subdomain(self, subdomain: str = None):
|
|
1158
|
+
"""Generate TLS certificate for subdomain attached to bucket."""
|
|
1159
|
+
url = f"{self.api_url}/storages/certificates/generate"
|
|
1160
|
+
self.headers.update({"Content-Type": "application/json"})
|
|
1161
|
+
payload = {
|
|
1162
|
+
"subdomain": subdomain,
|
|
1163
|
+
}
|
|
1164
|
+
return requests.post(
|
|
1165
|
+
url,
|
|
1166
|
+
headers=self.headers,
|
|
1167
|
+
timeout=self.timeout,
|
|
1168
|
+
data=json.dumps(payload),
|
|
1169
|
+
)
|
|
@@ -37,10 +37,14 @@ def load_config(filepath: str = get_default_config_file()):
|
|
|
37
37
|
try:
|
|
38
38
|
with open(filepath, "r", encoding="utf-8") as file:
|
|
39
39
|
return toml.load(file)
|
|
40
|
-
except
|
|
41
|
-
sys.exit(
|
|
40
|
+
except FileNotFoundError:
|
|
41
|
+
sys.exit(
|
|
42
|
+
f"Configuration file {filepath} not found. Try run 'twc config'"
|
|
43
|
+
)
|
|
44
|
+
except OSError as error:
|
|
45
|
+
sys.exit(f"Error: Cannot read configuration file {filepath}: {error}")
|
|
42
46
|
except toml.TomlDecodeError as error:
|
|
43
|
-
sys.exit(f"Error: {filepath}: {error}")
|
|
47
|
+
sys.exit(f"Error: Check your TOML syntax in file {filepath}: {error}")
|
|
44
48
|
|
|
45
49
|
|
|
46
50
|
def set_value_from_config(ctx, param, value):
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""CLI configuration."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import ctypes
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
import yaml
|
|
9
|
+
import toml
|
|
10
|
+
import click
|
|
11
|
+
from click_default_group import DefaultGroup
|
|
12
|
+
|
|
13
|
+
from twc import fmt
|
|
14
|
+
from twc import utils
|
|
15
|
+
from twc.vars import DEFAULT_CONFIG
|
|
16
|
+
from . import (
|
|
17
|
+
options,
|
|
18
|
+
load_config,
|
|
19
|
+
get_default_config_file,
|
|
20
|
+
GLOBAL_OPTIONS,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def write_to_file(data: str, filepath: str):
|
|
25
|
+
try:
|
|
26
|
+
with open(filepath, "w", encoding="utf-8") as file:
|
|
27
|
+
toml.dump(data, file)
|
|
28
|
+
if os.name == "nt":
|
|
29
|
+
hidden_file_attr = 0x02
|
|
30
|
+
ctypes.windll.kernel32.SetFileAttributesW(
|
|
31
|
+
filepath, hidden_file_attr
|
|
32
|
+
)
|
|
33
|
+
click.echo(f"Done! Configuration is saved in {filepath}")
|
|
34
|
+
sys.exit(0)
|
|
35
|
+
except OSError as error:
|
|
36
|
+
sys.exit(f"Error: {error}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def make_config(filepath: str = get_default_config_file()):
|
|
40
|
+
"""Create new configuration file."""
|
|
41
|
+
if os.path.exists(filepath):
|
|
42
|
+
if click.confirm(
|
|
43
|
+
"You already have TWC CLI configured, continue?",
|
|
44
|
+
default=False,
|
|
45
|
+
):
|
|
46
|
+
current_config = load_config()
|
|
47
|
+
profile = click.prompt("Enter profile name", default="default")
|
|
48
|
+
token = click.prompt(f"Enter API token for '{profile}'")
|
|
49
|
+
try:
|
|
50
|
+
# Edit existing profile
|
|
51
|
+
current_config[profile] = utils.merge_dicts(
|
|
52
|
+
current_config[profile],
|
|
53
|
+
{"token": token},
|
|
54
|
+
)
|
|
55
|
+
except KeyError:
|
|
56
|
+
# Add new profile
|
|
57
|
+
current_config[profile] = {} # init new config section
|
|
58
|
+
current_config[profile] = utils.merge_dicts(
|
|
59
|
+
current_config[profile],
|
|
60
|
+
{"token": token},
|
|
61
|
+
)
|
|
62
|
+
write_to_file(current_config, filepath)
|
|
63
|
+
else:
|
|
64
|
+
sys.exit("Aborted!")
|
|
65
|
+
|
|
66
|
+
click.echo("Create new configuration file. Enter your API token.")
|
|
67
|
+
while True:
|
|
68
|
+
token = input("Token: ")
|
|
69
|
+
if token:
|
|
70
|
+
DEFAULT_CONFIG.update({"default": {"token": token}})
|
|
71
|
+
break
|
|
72
|
+
click.echo("Please enter token. Press ^C to cancel.")
|
|
73
|
+
write_to_file(DEFAULT_CONFIG, filepath)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------- #
|
|
77
|
+
# $ twc config #
|
|
78
|
+
# ------------------------------------------------------------- #
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@click.group(
|
|
82
|
+
"config", cls=DefaultGroup, default="init", default_if_no_args=True
|
|
83
|
+
)
|
|
84
|
+
@options(GLOBAL_OPTIONS[:2])
|
|
85
|
+
def config():
|
|
86
|
+
"""Manage CLI configuration."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ------------------------------------------------------------- #
|
|
90
|
+
# $ twc config init #
|
|
91
|
+
# ------------------------------------------------------------- #
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@config.command("init", help="Create confiration file and profile.")
|
|
95
|
+
@options(GLOBAL_OPTIONS[:2])
|
|
96
|
+
def config_init():
|
|
97
|
+
make_config()
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ------------------------------------------------------------- #
|
|
101
|
+
# $ twc config file #
|
|
102
|
+
# ------------------------------------------------------------- #
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@config.command("file", help="Display path to configuration file.")
|
|
106
|
+
@options(GLOBAL_OPTIONS[:4])
|
|
107
|
+
def config_file(config, verbose):
|
|
108
|
+
click.echo(click.format_filename(config.encode()))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ------------------------------------------------------------- #
|
|
112
|
+
# $ twc config dump #
|
|
113
|
+
# ------------------------------------------------------------- #
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@config.command("dump", help="Dump configuration.")
|
|
117
|
+
@options(GLOBAL_OPTIONS)
|
|
118
|
+
@click.option(
|
|
119
|
+
"--full", "full_dump", is_flag=True, help="Dump full configuration."
|
|
120
|
+
)
|
|
121
|
+
@click.option(
|
|
122
|
+
"--output",
|
|
123
|
+
"-o",
|
|
124
|
+
"output_format",
|
|
125
|
+
type=click.Choice(["toml", "yaml", "json"]),
|
|
126
|
+
default="toml",
|
|
127
|
+
show_default=True,
|
|
128
|
+
help="Output format.",
|
|
129
|
+
)
|
|
130
|
+
def config_dump(config, profile, verbose, full_dump, output_format):
|
|
131
|
+
if full_dump:
|
|
132
|
+
config_dict = load_config(config)
|
|
133
|
+
else:
|
|
134
|
+
config_dict = load_config(config)[profile]
|
|
135
|
+
|
|
136
|
+
if output_format == "toml":
|
|
137
|
+
fmt.print_colored(toml.dumps(config_dict), lang="toml")
|
|
138
|
+
elif output_format == "yaml":
|
|
139
|
+
fmt.print_colored(yaml.dump(config_dict), lang="yaml")
|
|
140
|
+
elif output_format == "json":
|
|
141
|
+
fmt.print_colored(json.dumps(config_dict), lang="json")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# ------------------------------------------------------------- #
|
|
145
|
+
# $ twc config set #
|
|
146
|
+
# ------------------------------------------------------------- #
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
@config.command("set", help="Set config parameter.")
|
|
150
|
+
@options(GLOBAL_OPTIONS)
|
|
151
|
+
@click.argument("params", nargs=-1, metavar="PARAM=VALUE")
|
|
152
|
+
def config_set(config, profile, verbose, params):
|
|
153
|
+
if not params:
|
|
154
|
+
raise click.UsageError("Nothing to do.")
|
|
155
|
+
config_dict = load_config(config)
|
|
156
|
+
params_dict = {}
|
|
157
|
+
for param in params:
|
|
158
|
+
key, value = param.split("=")
|
|
159
|
+
if value.isdigit():
|
|
160
|
+
value = int(value)
|
|
161
|
+
params_dict[key] = value
|
|
162
|
+
config_dict[profile] = utils.merge_dicts(config_dict[profile], params_dict)
|
|
163
|
+
write_to_file(config_dict, config)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# ------------------------------------------------------------- #
|
|
167
|
+
# $ twc config edit #
|
|
168
|
+
# ------------------------------------------------------------- #
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@config.command("edit", help="Open config file in default editor.")
|
|
172
|
+
@options(GLOBAL_OPTIONS[:4])
|
|
173
|
+
def config_edit(config, verbose):
|
|
174
|
+
click.launch(config)
|