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.
Files changed (47) hide show
  1. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/PKG-INFO +14 -3
  2. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/README.md +13 -2
  3. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/pyproject.toml +1 -1
  4. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/__init__.py +1 -1
  5. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/doctor.py +13 -1
  6. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/i18n.py +3 -0
  7. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/ssh.py +8 -2
  8. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/PKG-INFO +14 -3
  9. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_cli.py +24 -0
  10. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_ssh.py +33 -0
  11. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/LICENSE +0 -0
  12. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/setup.cfg +0 -0
  13. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/__main__.py +0 -0
  14. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/actions.py +0 -0
  15. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/api.py +0 -0
  16. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/cli.py +0 -0
  17. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/__init__.py +0 -0
  18. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/ask.py +0 -0
  19. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/common.py +0 -0
  20. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/feedback.py +0 -0
  21. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/image.py +0 -0
  22. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/instance.py +0 -0
  23. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/storage.py +0 -0
  24. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/commands/team.py +0 -0
  25. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/config.py +0 -0
  26. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/errors.py +0 -0
  27. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/insights.py +0 -0
  28. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/location.py +0 -0
  29. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/output.py +0 -0
  30. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/parsing.py +0 -0
  31. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/qa.py +0 -0
  32. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/runtime.py +0 -0
  33. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli/sdk.py +0 -0
  34. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/SOURCES.txt +0 -0
  35. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/dependency_links.txt +0 -0
  36. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/entry_points.txt +0 -0
  37. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/requires.txt +0 -0
  38. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/src/compshare_cli.egg-info/top_level.txt +0 -0
  39. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_actions.py +0 -0
  40. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_api.py +0 -0
  41. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_config.py +0 -0
  42. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_feedback.py +0 -0
  43. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_location.py +0 -0
  44. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_output.py +0 -0
  45. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_parsing.py +0 -0
  46. {compshare_cli-0.3.0 → compshare_cli-0.3.1}/tests/test_qa.py +0 -0
  47. {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.0
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.0`,要求 Python 3.9 或更高版本。
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.0`,要求 Python 3.9 或更高版本。
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "compshare-cli"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "Command-line client for CompShare GPU compute"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,3 +1,3 @@
1
1
  """CompShare command-line client."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.1"
@@ -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 os.name == "nt" else "compshare-ssh-askpass"
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 os.name == "nt" or not sys.stdin.isatty() or not sys.stdout.isatty():
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.0
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.0`,要求 Python 3.9 或更高版本。
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