compshare-cli 0.3.0__tar.gz → 0.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.
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/PKG-INFO +14 -3
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/README.md +13 -2
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/pyproject.toml +1 -1
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/__init__.py +1 -1
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/doctor.py +13 -1
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/i18n.py +3 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/ssh.py +8 -2
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/PKG-INFO +14 -3
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_cli.py +24 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_ssh.py +33 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/LICENSE +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/setup.cfg +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/__main__.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/actions.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/api.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/cli.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/__init__.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/ask.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/common.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/feedback.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/image.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/instance.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/storage.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/team.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/config.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/errors.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/insights.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/location.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/output.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/parsing.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/qa.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/runtime.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/sdk.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/SOURCES.txt +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/dependency_links.txt +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/entry_points.txt +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/requires.txt +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/top_level.txt +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_actions.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_api.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_config.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_feedback.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_location.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_output.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_parsing.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_qa.py +0 -0
- {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_sdk.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compshare-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Command-line client for CompShare GPU compute
|
|
5
5
|
Author: CompShare
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -35,7 +35,7 @@ Dynamic: license-file
|
|
|
35
35
|
- 可通过 `compshare feedback` 反馈 CLI 问题和建议
|
|
36
36
|
- 基于官方 [`ucloud-sdk-python3`](https://github.com/ucloud/ucloud-sdk-python3)
|
|
37
37
|
|
|
38
|
-
当前版本:`0.3.
|
|
38
|
+
当前版本:`0.3.1`,要求 Python 3.9 或更高版本。
|
|
39
39
|
|
|
40
40
|
## 安装
|
|
41
41
|
|
|
@@ -54,12 +54,23 @@ source .venv/bin/activate
|
|
|
54
54
|
python -m pip install -e '.[dev]'
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Windows
|
|
57
|
+
Windows PowerShell 中可先激活虚拟环境,再直接调用 `compshare`:
|
|
58
58
|
|
|
59
59
|
```powershell
|
|
60
60
|
.\.venv\Scripts\Activate.ps1
|
|
61
|
+
compshare -h
|
|
62
|
+
compshare config --name default
|
|
61
63
|
```
|
|
62
64
|
|
|
65
|
+
不激活虚拟环境时,使用 PowerShell 调用运算符 `&` 直接执行虚拟环境内的入口:
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
& '.\.venv\Scripts\compshare.exe' -h
|
|
69
|
+
& '.\.venv\Scripts\compshare.exe' config --name default
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
PowerShell 会把单引号中的路径解析为字符串,所以带参数执行时不能省略 `&`。路径包含空格时必须保留引号;从当前目录运行可执行文件时要使用 `.\` 前缀。若 `Activate.ps1` 被本机执行策略拦截,可直接使用上述不激活的写法,无需修改系统策略。
|
|
73
|
+
|
|
63
74
|
## 配置
|
|
64
75
|
|
|
65
76
|
```bash
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- 可通过 `compshare feedback` 反馈 CLI 问题和建议
|
|
11
11
|
- 基于官方 [`ucloud-sdk-python3`](https://github.com/ucloud/ucloud-sdk-python3)
|
|
12
12
|
|
|
13
|
-
当前版本:`0.3.
|
|
13
|
+
当前版本:`0.3.1`,要求 Python 3.9 或更高版本。
|
|
14
14
|
|
|
15
15
|
## 安装
|
|
16
16
|
|
|
@@ -29,12 +29,23 @@ source .venv/bin/activate
|
|
|
29
29
|
python -m pip install -e '.[dev]'
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
Windows
|
|
32
|
+
Windows PowerShell 中可先激活虚拟环境,再直接调用 `compshare`:
|
|
33
33
|
|
|
34
34
|
```powershell
|
|
35
35
|
.\.venv\Scripts\Activate.ps1
|
|
36
|
+
compshare -h
|
|
37
|
+
compshare config --name default
|
|
36
38
|
```
|
|
37
39
|
|
|
40
|
+
不激活虚拟环境时,使用 PowerShell 调用运算符 `&` 直接执行虚拟环境内的入口:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
& '.\.venv\Scripts\compshare.exe' -h
|
|
44
|
+
& '.\.venv\Scripts\compshare.exe' config --name default
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
PowerShell 会把单引号中的路径解析为字符串,所以带参数执行时不能省略 `&`。路径包含空格时必须保留引号;从当前目录运行可执行文件时要使用 `.\` 前缀。若 `Activate.ps1` 被本机执行策略拦截,可直接使用上述不激活的写法,无需修改系统策略。
|
|
48
|
+
|
|
38
49
|
## 配置
|
|
39
50
|
|
|
40
51
|
```bash
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
import platform
|
|
4
5
|
import shutil
|
|
5
6
|
import stat
|
|
6
7
|
import sys
|
|
@@ -61,7 +62,18 @@ def run(state: Runtime) -> None:
|
|
|
61
62
|
)
|
|
62
63
|
)
|
|
63
64
|
|
|
64
|
-
if store.path.exists():
|
|
65
|
+
if store.path.exists() and platform.system() == "Windows":
|
|
66
|
+
checks.append(
|
|
67
|
+
_check(
|
|
68
|
+
"Permissions",
|
|
69
|
+
"Warning",
|
|
70
|
+
tr(
|
|
71
|
+
"Config {path}: Windows ACLs apply; POSIX permission check skipped.",
|
|
72
|
+
path=store.path,
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
elif store.path.exists():
|
|
65
77
|
file_mode = stat.S_IMODE(store.path.stat().st_mode)
|
|
66
78
|
directory_mode = stat.S_IMODE(store.path.parent.stat().st_mode)
|
|
67
79
|
permissions_ok = file_mode & 0o077 == 0 and directory_mode & 0o077 == 0
|
|
@@ -618,6 +618,9 @@ ZH_TRANSLATIONS: Dict[str, str] = {
|
|
|
618
618
|
"Config {path}: file {file_mode}, directory {directory_mode}": (
|
|
619
619
|
"配置 {path}:文件权限 {file_mode},目录权限 {directory_mode}"
|
|
620
620
|
),
|
|
621
|
+
"Config {path}: Windows ACLs apply; POSIX permission check skipped.": (
|
|
622
|
+
"配置 {path}:由 Windows ACL 管理访问权限,已跳过 POSIX 权限位检查"
|
|
623
|
+
),
|
|
621
624
|
"Using environment credentials": "正在使用环境变量凭证",
|
|
622
625
|
"Config file does not exist: {path}": "配置文件不存在:{path}",
|
|
623
626
|
"Connected; {count} availability zones returned.": "连接成功,返回 {count} 个可用区。",
|
|
@@ -40,6 +40,10 @@ class PasswordAutomationUnavailable(RuntimeError):
|
|
|
40
40
|
"""Raised when the current terminal cannot safely automate an SSH password."""
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
def _is_windows() -> bool:
|
|
44
|
+
return os.name == "nt"
|
|
45
|
+
|
|
46
|
+
|
|
43
47
|
def _password_authentication(argv: List[str], *, command_mode: bool = False) -> List[str]:
|
|
44
48
|
executable = os.path.basename(argv[0]).casefold()
|
|
45
49
|
if executable not in {"ssh", "ssh.exe"}:
|
|
@@ -133,7 +137,7 @@ def scp_upload_command(
|
|
|
133
137
|
|
|
134
138
|
|
|
135
139
|
def _askpass_executable() -> str:
|
|
136
|
-
name = "compshare-ssh-askpass.exe" if
|
|
140
|
+
name = "compshare-ssh-askpass.exe" if _is_windows() else "compshare-ssh-askpass"
|
|
137
141
|
sibling = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), name)
|
|
138
142
|
if os.path.isfile(sibling) and os.access(sibling, os.X_OK):
|
|
139
143
|
return sibling
|
|
@@ -193,7 +197,9 @@ def _run_with_askpass(command: List[str], password: str) -> int:
|
|
|
193
197
|
|
|
194
198
|
def connect_with_password(argv: List[str], password: str) -> int:
|
|
195
199
|
"""Run SSH interactively and answer its first password prompt."""
|
|
196
|
-
if
|
|
200
|
+
if _is_windows():
|
|
201
|
+
return _run_with_askpass(_password_authentication(argv), password)
|
|
202
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
197
203
|
raise PasswordAutomationUnavailable
|
|
198
204
|
|
|
199
205
|
try:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: compshare-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Command-line client for CompShare GPU compute
|
|
5
5
|
Author: CompShare
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -35,7 +35,7 @@ Dynamic: license-file
|
|
|
35
35
|
- 可通过 `compshare feedback` 反馈 CLI 问题和建议
|
|
36
36
|
- 基于官方 [`ucloud-sdk-python3`](https://github.com/ucloud/ucloud-sdk-python3)
|
|
37
37
|
|
|
38
|
-
当前版本:`0.3.
|
|
38
|
+
当前版本:`0.3.1`,要求 Python 3.9 或更高版本。
|
|
39
39
|
|
|
40
40
|
## 安装
|
|
41
41
|
|
|
@@ -54,12 +54,23 @@ source .venv/bin/activate
|
|
|
54
54
|
python -m pip install -e '.[dev]'
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
Windows
|
|
57
|
+
Windows PowerShell 中可先激活虚拟环境,再直接调用 `compshare`:
|
|
58
58
|
|
|
59
59
|
```powershell
|
|
60
60
|
.\.venv\Scripts\Activate.ps1
|
|
61
|
+
compshare -h
|
|
62
|
+
compshare config --name default
|
|
61
63
|
```
|
|
62
64
|
|
|
65
|
+
不激活虚拟环境时,使用 PowerShell 调用运算符 `&` 直接执行虚拟环境内的入口:
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
& '.\.venv\Scripts\compshare.exe' -h
|
|
69
|
+
& '.\.venv\Scripts\compshare.exe' config --name default
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
PowerShell 会把单引号中的路径解析为字符串,所以带参数执行时不能省略 `&`。路径包含空格时必须保留引号;从当前目录运行可执行文件时要使用 `.\` 前缀。若 `Activate.ps1` 被本机执行策略拦截,可直接使用上述不激活的写法,无需修改系统策略。
|
|
73
|
+
|
|
63
74
|
## 配置
|
|
64
75
|
|
|
65
76
|
```bash
|
|
@@ -1463,3 +1463,27 @@ def test_doctor_is_read_only_and_json(monkeypatch, tmp_path) -> None:
|
|
|
1463
1463
|
payload = json.loads(result.stdout)
|
|
1464
1464
|
assert payload["ok"] is True
|
|
1465
1465
|
assert {item["Check"] for item in payload["checks"]} >= {"Credentials", "API", "ssh"}
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
def test_doctor_does_not_treat_windows_mode_bits_as_posix_permissions(
|
|
1469
|
+
monkeypatch, tmp_path
|
|
1470
|
+
) -> None:
|
|
1471
|
+
path = tmp_path / "config.json"
|
|
1472
|
+
path.write_text("{}", encoding="utf-8")
|
|
1473
|
+
monkeypatch.setenv("COMPSHARE_CONFIG_FILE", str(path))
|
|
1474
|
+
monkeypatch.setenv("COMPSHARE_PUBLIC_KEY", "public")
|
|
1475
|
+
monkeypatch.setenv("COMPSHARE_PRIVATE_KEY", "private")
|
|
1476
|
+
monkeypatch.setattr(doctor_module.platform, "system", lambda: "Windows")
|
|
1477
|
+
monkeypatch.setattr(
|
|
1478
|
+
doctor_module,
|
|
1479
|
+
"call_captured",
|
|
1480
|
+
lambda state, action, params: ({"ZoneInfo": [{"Zone": "cn-a-01"}]}, None),
|
|
1481
|
+
)
|
|
1482
|
+
|
|
1483
|
+
result = runner.invoke(cli.app, ["--json", "doctor"])
|
|
1484
|
+
|
|
1485
|
+
assert result.exit_code == 0, result.output
|
|
1486
|
+
payload = json.loads(result.stdout)
|
|
1487
|
+
permissions = next(item for item in payload["checks"] if item["Check"] == "Permissions")
|
|
1488
|
+
assert permissions["Status"] == "Warning"
|
|
1489
|
+
assert "Windows ACL" in permissions["Detail"]
|
|
@@ -73,6 +73,39 @@ def test_connect_with_password_requires_an_interactive_terminal(monkeypatch) ->
|
|
|
73
73
|
raise AssertionError("expected PasswordAutomationUnavailable")
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
def test_connect_with_password_uses_askpass_on_windows(monkeypatch) -> None:
|
|
77
|
+
calls = []
|
|
78
|
+
monkeypatch.setattr(ssh, "_is_windows", lambda: True)
|
|
79
|
+
monkeypatch.setattr(
|
|
80
|
+
ssh,
|
|
81
|
+
"_run_with_askpass",
|
|
82
|
+
lambda argv, password: calls.append((argv, password)) or 23,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
exit_code = ssh.connect_with_password(
|
|
86
|
+
["ssh", "-p", "2222", "root@example.invalid"],
|
|
87
|
+
"instance-secret",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
assert exit_code == 23
|
|
91
|
+
argv, password = calls[0]
|
|
92
|
+
assert argv == [
|
|
93
|
+
"ssh",
|
|
94
|
+
"-o",
|
|
95
|
+
"PreferredAuthentications=password,keyboard-interactive",
|
|
96
|
+
"-o",
|
|
97
|
+
"PubkeyAuthentication=no",
|
|
98
|
+
"-o",
|
|
99
|
+
"NumberOfPasswordPrompts=1",
|
|
100
|
+
"-o",
|
|
101
|
+
"StrictHostKeyChecking=accept-new",
|
|
102
|
+
"-p",
|
|
103
|
+
"2222",
|
|
104
|
+
"root@example.invalid",
|
|
105
|
+
]
|
|
106
|
+
assert password == "instance-secret"
|
|
107
|
+
|
|
108
|
+
|
|
76
109
|
def test_execute_with_password_uses_askpass_without_password_in_argv(monkeypatch) -> None:
|
|
77
110
|
calls = []
|
|
78
111
|
monkeypatch.delenv(ssh._ASKPASS_PASSWORD_FILE_ENV, raising=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|