ctyun-cli 1.26.0__tar.gz → 1.26.2__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.
- {ctyun_cli-1.26.0/src/ctyun_cli.egg-info → ctyun_cli-1.26.2}/PKG-INFO +8 -2
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/README.md +7 -1
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/pyproject.toml +1 -1
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/setup.py +1 -1
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2/src/ctyun_cli.egg-info}/PKG-INFO +8 -2
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ebs/client.py +96 -3
- ctyun_cli-1.26.2/src/ebs/commands.py +308 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/vpc/client.py +197 -6
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/vpc/commands.py +239 -0
- ctyun_cli-1.26.0/src/ebs/commands.py +0 -165
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/LICENSE +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/MANIFEST.in +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/PROJECT_SUMMARY.md +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/requirements.txt +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/setup.cfg +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/aiserver/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/aiserver/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/aiserver/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/aone/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/aone/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/aone/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/apm/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/apm/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/apm/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/audit/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/audit/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/audit/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/auth/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/auth/eop_signature.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/auth/signature.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/billing/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/billing/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/billing/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cce/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cce/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cce/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cda/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cda/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cda/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cli/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cli/main.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cloudpc/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cloudpc/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/cloudpc/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/config/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/config/settings.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/core/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/css/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/css/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/css/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/csscn/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/csscn/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/csscn/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctmysql/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctmysql/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctmysql/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctyun_cli/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctyun_cli.egg-info/SOURCES.txt +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctyun_cli.egg-info/dependency_links.txt +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctyun_cli.egg-info/entry_points.txt +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctyun_cli.egg-info/not-zip-safe +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctyun_cli.egg-info/requires.txt +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ctyun_cli.egg-info/top_level.txt +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/dps/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/dps/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/dps/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ebs/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ecs/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ecs/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ecs/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/elb/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/elb/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/elb/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/emr/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/emr/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/emr/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/iam/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/iam/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/iam/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ims/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ims/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/ims/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/kafka/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/kafka/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/kafka/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/lts/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/lts/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/lts/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/monitor/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/monitor/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/monitor/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/oceanfs/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/oceanfs/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/oceanfs/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/rdscmd/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/rdscmd/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/rdscmd/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/security/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/security/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/security/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/sfs/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/sfs/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/sfs/commands.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/utils/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/utils/cache.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/utils/helpers.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/vpc/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/zos/__init__.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/zos/client.py +0 -0
- {ctyun_cli-1.26.0 → ctyun_cli-1.26.2}/src/zos/commands.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctyun-cli
|
|
3
|
-
Version: 1.26.
|
|
3
|
+
Version: 1.26.2
|
|
4
4
|
Summary: 天翼云CLI工具 - 基于终端的云资源管理平台
|
|
5
5
|
Home-page: https://github.com/fengyucn/ctyun-cli
|
|
6
6
|
Author: Y.FENG
|
|
@@ -217,7 +217,13 @@ ctyun-cli cce list-clusters
|
|
|
217
217
|
|
|
218
218
|
## 📝 更新日志
|
|
219
219
|
|
|
220
|
-
**最新版本**: v1.26.
|
|
220
|
+
**最新版本**: v1.26.2 (2026-07-21)
|
|
221
|
+
- 🚀 **VPC 模块新增 13 个命令**:NAT 网关、SNAT/DNAT、对等连接、路由表、安全组规则、端口管理
|
|
222
|
+
|
|
223
|
+
**v1.26.1** (2026-07-21)
|
|
224
|
+
- 🚀 **EBS 模块新增 6 个命令**:云硬盘详情/按名称查询、快照列表/容量/策略
|
|
225
|
+
|
|
226
|
+
**v1.26.0** (2026-07-20)
|
|
221
227
|
- 🚀 **Redis 模块大幅扩展**:新增 52 个命令,涵盖备份管理、安全配置、Key 分析、监控诊断、参数模板、迁移任务等
|
|
222
228
|
|
|
223
229
|
**v1.25.0** (2026-07-17)
|
|
@@ -154,7 +154,13 @@ ctyun-cli cce list-clusters
|
|
|
154
154
|
|
|
155
155
|
## 📝 更新日志
|
|
156
156
|
|
|
157
|
-
**最新版本**: v1.26.
|
|
157
|
+
**最新版本**: v1.26.2 (2026-07-21)
|
|
158
|
+
- 🚀 **VPC 模块新增 13 个命令**:NAT 网关、SNAT/DNAT、对等连接、路由表、安全组规则、端口管理
|
|
159
|
+
|
|
160
|
+
**v1.26.1** (2026-07-21)
|
|
161
|
+
- 🚀 **EBS 模块新增 6 个命令**:云硬盘详情/按名称查询、快照列表/容量/策略
|
|
162
|
+
|
|
163
|
+
**v1.26.0** (2026-07-20)
|
|
158
164
|
- 🚀 **Redis 模块大幅扩展**:新增 52 个命令,涵盖备份管理、安全配置、Key 分析、监控诊断、参数模板、迁移任务等
|
|
159
165
|
|
|
160
166
|
**v1.25.0** (2026-07-17)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ctyun-cli
|
|
3
|
-
Version: 1.26.
|
|
3
|
+
Version: 1.26.2
|
|
4
4
|
Summary: 天翼云CLI工具 - 基于终端的云资源管理平台
|
|
5
5
|
Home-page: https://github.com/fengyucn/ctyun-cli
|
|
6
6
|
Author: Y.FENG
|
|
@@ -217,7 +217,13 @@ ctyun-cli cce list-clusters
|
|
|
217
217
|
|
|
218
218
|
## 📝 更新日志
|
|
219
219
|
|
|
220
|
-
**最新版本**: v1.26.
|
|
220
|
+
**最新版本**: v1.26.2 (2026-07-21)
|
|
221
|
+
- 🚀 **VPC 模块新增 13 个命令**:NAT 网关、SNAT/DNAT、对等连接、路由表、安全组规则、端口管理
|
|
222
|
+
|
|
223
|
+
**v1.26.1** (2026-07-21)
|
|
224
|
+
- 🚀 **EBS 模块新增 6 个命令**:云硬盘详情/按名称查询、快照列表/容量/策略
|
|
225
|
+
|
|
226
|
+
**v1.26.0** (2026-07-20)
|
|
221
227
|
- 🚀 **Redis 模块大幅扩展**:新增 52 个命令,涵盖备份管理、安全配置、Key 分析、监控诊断、参数模板、迁移任务等
|
|
222
228
|
|
|
223
229
|
**v1.25.0** (2026-07-17)
|
|
@@ -127,12 +127,12 @@ class EBSClient:
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
result = response.json()
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
if result.get('statusCode') != 800:
|
|
132
132
|
logger.warning(f"API返回错误: {result.get('message', '未知错误')}")
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
return result
|
|
135
|
-
|
|
135
|
+
|
|
136
136
|
except Exception as e:
|
|
137
137
|
logger.error(f"查询云硬盘列表失败: {e}")
|
|
138
138
|
import traceback
|
|
@@ -142,3 +142,96 @@ class EBSClient:
|
|
|
142
142
|
'message': str(e),
|
|
143
143
|
'returnObj': None
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
# ==================== 新增查询类 API(6个) ====================
|
|
147
|
+
|
|
148
|
+
def _get(self, path: str, query_params: Dict) -> Dict[str, Any]:
|
|
149
|
+
"""通用 GET 请求"""
|
|
150
|
+
url = f'https://{self.base_endpoint}{path}'
|
|
151
|
+
filtered = {k: v for k, v in query_params.items() if v is not None}
|
|
152
|
+
try:
|
|
153
|
+
headers = self.eop_auth.sign_request(
|
|
154
|
+
method='GET', url=url, query_params=filtered, body='',
|
|
155
|
+
extra_headers={}
|
|
156
|
+
)
|
|
157
|
+
logger.debug(f"请求URL: {url}")
|
|
158
|
+
logger.debug(f"查询参数: {filtered}")
|
|
159
|
+
|
|
160
|
+
response = self.client.session.get(
|
|
161
|
+
url, params=filtered, headers=headers, timeout=30
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
logger.debug(f"响应状态码: {response.status_code}")
|
|
165
|
+
|
|
166
|
+
if response.status_code != 200:
|
|
167
|
+
return {
|
|
168
|
+
'statusCode': response.status_code,
|
|
169
|
+
'message': f'HTTP {response.status_code}: {response.text}',
|
|
170
|
+
'returnObj': None
|
|
171
|
+
}
|
|
172
|
+
return response.json()
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"GET {path} 失败: {e}")
|
|
175
|
+
return {'statusCode': 500, 'message': str(e), 'returnObj': None}
|
|
176
|
+
|
|
177
|
+
def get_ebs_info(self, region_id: str, disk_id: str) -> Dict[str, Any]:
|
|
178
|
+
"""查询云硬盘详情(基于diskID)- GET /v4/ebs/info-ebs"""
|
|
179
|
+
logger.info(f"查询云硬盘详情: regionId={region_id}, diskID={disk_id}")
|
|
180
|
+
return self._get('/v4/ebs/info-ebs', {
|
|
181
|
+
'regionID': region_id, 'diskID': disk_id,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
def get_ebs_info_by_name(self, region_id: str, disk_name: str) -> Dict[str, Any]:
|
|
185
|
+
"""查询云硬盘详情(基于regionID和diskName)- GET /v4/ebs/info-by-name-ebs"""
|
|
186
|
+
logger.info(f"查询云硬盘详情(by name): regionId={region_id}, diskName={disk_name}")
|
|
187
|
+
return self._get('/v4/ebs/info-by-name-ebs', {
|
|
188
|
+
'regionID': region_id, 'diskName': disk_name,
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
def list_ebs_by_name(self, region_id: str, disk_name: str,
|
|
192
|
+
page_no: Optional[int] = None,
|
|
193
|
+
page_size: Optional[int] = None) -> Dict[str, Any]:
|
|
194
|
+
"""查询云硬盘列表(基于regionID和diskName)- GET /v4/ebs/list-by-name-ebs"""
|
|
195
|
+
logger.info(f"查询云硬盘列表(by name): regionId={region_id}, diskName={disk_name}")
|
|
196
|
+
return self._get('/v4/ebs/list-by-name-ebs', {
|
|
197
|
+
'regionID': region_id, 'diskName': disk_name,
|
|
198
|
+
'pageNo': page_no, 'pageSize': page_size,
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
def list_ebs_snapshots(self, region_id: str,
|
|
202
|
+
disk_id: Optional[str] = None,
|
|
203
|
+
snapshot_id: Optional[str] = None,
|
|
204
|
+
snapshot_name: Optional[str] = None,
|
|
205
|
+
snapshot_status: Optional[str] = None,
|
|
206
|
+
snapshot_type: Optional[str] = None,
|
|
207
|
+
volume_attr: Optional[str] = None,
|
|
208
|
+
retention_policy: Optional[str] = None,
|
|
209
|
+
max_results: Optional[int] = None,
|
|
210
|
+
next_token: Optional[str] = None) -> Dict[str, Any]:
|
|
211
|
+
"""查询云硬盘快照列表 - GET /v4/ebs_snapshot/list-ebs-snap"""
|
|
212
|
+
logger.info(f"查询云硬盘快照列表: regionId={region_id}")
|
|
213
|
+
return self._get('/v4/ebs_snapshot/list-ebs-snap', {
|
|
214
|
+
'regionID': region_id, 'diskID': disk_id, 'snapshotID': snapshot_id,
|
|
215
|
+
'snapshotName': snapshot_name, 'snapshotStatus': snapshot_status,
|
|
216
|
+
'snapshotType': snapshot_type, 'volumeAttr': volume_attr,
|
|
217
|
+
'retentionPolicy': retention_policy,
|
|
218
|
+
'maxResults': max_results, 'nextToken': next_token,
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
def query_ebs_snapshot_size(self, region_id: str) -> Dict[str, Any]:
|
|
222
|
+
"""查询云硬盘快照使用量 - GET /v4/ebs_snapshot/query_size-ebs-snap"""
|
|
223
|
+
logger.info(f"查询云硬盘快照使用量: regionId={region_id}")
|
|
224
|
+
return self._get('/v4/ebs_snapshot/query_size-ebs-snap', {
|
|
225
|
+
'regionID': region_id,
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
def query_ebs_snapshot_policy(self, region_id: str,
|
|
229
|
+
snapshot_policy_id: Optional[str] = None,
|
|
230
|
+
snapshot_policy_name: Optional[str] = None) -> Dict[str, Any]:
|
|
231
|
+
"""查询云硬盘自动快照策略 - GET /v4/ebs_snapshot/query-policy-ebs-snap"""
|
|
232
|
+
logger.info(f"查询云硬盘自动快照策略: regionId={region_id}")
|
|
233
|
+
return self._get('/v4/ebs_snapshot/query-policy-ebs-snap', {
|
|
234
|
+
'regionID': region_id,
|
|
235
|
+
'snapshotPolicyID': snapshot_policy_id,
|
|
236
|
+
'snapshotPolicyName': snapshot_policy_name,
|
|
237
|
+
})
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
"""
|
|
2
|
+
云硬盘(EBS)命令行接口
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from functools import wraps
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from .client import EBSClient
|
|
9
|
+
from utils import OutputFormatter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def handle_error(func):
|
|
13
|
+
"""错误处理装饰器"""
|
|
14
|
+
@wraps(func)
|
|
15
|
+
def wrapper(*args, **kwargs):
|
|
16
|
+
try:
|
|
17
|
+
return func(*args, **kwargs)
|
|
18
|
+
except Exception as e:
|
|
19
|
+
import sys
|
|
20
|
+
click.echo(f"错误: {e}", err=True)
|
|
21
|
+
sys.exit(1)
|
|
22
|
+
return wrapper
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def format_output(data, output_format='table'):
|
|
26
|
+
"""格式化输出"""
|
|
27
|
+
if output_format == 'json':
|
|
28
|
+
click.echo(OutputFormatter.format_json(data))
|
|
29
|
+
elif output_format == 'yaml':
|
|
30
|
+
try:
|
|
31
|
+
import yaml
|
|
32
|
+
click.echo(yaml.dump(data, allow_unicode=True, default_flow_style=False))
|
|
33
|
+
except ImportError:
|
|
34
|
+
click.echo("错误: 需要安装PyYAML库", err=True)
|
|
35
|
+
import sys
|
|
36
|
+
sys.exit(1)
|
|
37
|
+
else:
|
|
38
|
+
if isinstance(data, list) and data:
|
|
39
|
+
headers = list(data[0].keys())
|
|
40
|
+
table = OutputFormatter.format_table(data, headers)
|
|
41
|
+
click.echo(table)
|
|
42
|
+
elif isinstance(data, dict):
|
|
43
|
+
headers = ['字段', '值']
|
|
44
|
+
table_data = [[key, value] for key, value in data.items()]
|
|
45
|
+
table = OutputFormatter.format_table(table_data, headers)
|
|
46
|
+
click.echo(table)
|
|
47
|
+
else:
|
|
48
|
+
click.echo(data)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@click.group()
|
|
52
|
+
def ebs():
|
|
53
|
+
"""云硬盘(EBS)管理"""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@ebs.command('list')
|
|
58
|
+
@click.option('--region-id', required=True, help='资源池ID')
|
|
59
|
+
@click.option('--page', default=1, type=int, help='页码,默认1')
|
|
60
|
+
@click.option('--page-size', default=10, type=int, help='每页数量,默认10,最大300')
|
|
61
|
+
@click.option('--az-name', help='可用区')
|
|
62
|
+
@click.option('--project-id', help='企业项目ID')
|
|
63
|
+
@click.option('--disk-type', help='云硬盘类型(SATA/SAS/SSD/FAST-SSD)')
|
|
64
|
+
@click.option('--disk-mode', help='云硬盘模式(VBD/ISCSI/FCSAN)')
|
|
65
|
+
@click.option('--disk-status', help='云硬盘状态(in-use/available等)')
|
|
66
|
+
@click.option('--multi-attach', help='是否共享盘(true/false)')
|
|
67
|
+
@click.option('--is-system-volume', help='是否系统盘(true/false)')
|
|
68
|
+
@click.option('--is-encrypt', help='是否加密盘(true/false)')
|
|
69
|
+
@click.option('--query-content', help='模糊查询内容')
|
|
70
|
+
@click.option('--query-keys', help='模糊查询键(name,diskID,instanceID,instanceName)')
|
|
71
|
+
@click.option('--output', type=click.Choice(['table', 'json', 'yaml']), help='输出格式')
|
|
72
|
+
@click.pass_context
|
|
73
|
+
@handle_error
|
|
74
|
+
def list_ebs(ctx, region_id: str, page: int, page_size: int,
|
|
75
|
+
az_name: Optional[str], project_id: Optional[str],
|
|
76
|
+
disk_type: Optional[str], disk_mode: Optional[str],
|
|
77
|
+
disk_status: Optional[str], multi_attach: Optional[str],
|
|
78
|
+
is_system_volume: Optional[str], is_encrypt: Optional[str],
|
|
79
|
+
query_content: Optional[str], query_keys: Optional[str],
|
|
80
|
+
output: Optional[str]):
|
|
81
|
+
"""查询云硬盘列表"""
|
|
82
|
+
try:
|
|
83
|
+
client = ctx.obj['client']
|
|
84
|
+
output_format = output or ctx.obj.get('output', 'table')
|
|
85
|
+
|
|
86
|
+
ebs_client = EBSClient(client)
|
|
87
|
+
result = ebs_client.list_ebs(
|
|
88
|
+
region_id=region_id,
|
|
89
|
+
page_no=page,
|
|
90
|
+
page_size=page_size,
|
|
91
|
+
az_name=az_name,
|
|
92
|
+
project_id=project_id,
|
|
93
|
+
disk_type=disk_type,
|
|
94
|
+
disk_mode=disk_mode,
|
|
95
|
+
disk_status=disk_status,
|
|
96
|
+
multi_attach=multi_attach,
|
|
97
|
+
is_system_volume=is_system_volume,
|
|
98
|
+
is_encrypt=is_encrypt,
|
|
99
|
+
query_content=query_content,
|
|
100
|
+
query_keys=query_keys
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if result.get('statusCode') != 800:
|
|
104
|
+
error_msg = result.get('message', '未知错误')
|
|
105
|
+
click.echo(f"❌ 查询失败: {error_msg}", err=True)
|
|
106
|
+
import sys
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
return_obj = result.get('returnObj', {})
|
|
110
|
+
disk_list = return_obj.get('diskList', [])
|
|
111
|
+
|
|
112
|
+
if output_format in ['json', 'yaml']:
|
|
113
|
+
format_output(disk_list, output_format)
|
|
114
|
+
else:
|
|
115
|
+
if disk_list:
|
|
116
|
+
from tabulate import tabulate
|
|
117
|
+
from datetime import datetime
|
|
118
|
+
|
|
119
|
+
table_data = []
|
|
120
|
+
headers = ['云硬盘ID', '名称', '大小(GB)', '类型', '状态', '挂载主机', '创建时间']
|
|
121
|
+
|
|
122
|
+
for disk in disk_list:
|
|
123
|
+
disk_id = disk.get('diskID', '')
|
|
124
|
+
disk_name = disk.get('diskName', '')
|
|
125
|
+
disk_size = disk.get('diskSize', 0)
|
|
126
|
+
disk_type = disk.get('diskType', '')
|
|
127
|
+
disk_status = disk.get('diskStatus', '')
|
|
128
|
+
instance_name = disk.get('instanceName', '-')
|
|
129
|
+
|
|
130
|
+
create_time = disk.get('createTime', 0)
|
|
131
|
+
if isinstance(create_time, int) and create_time > 0:
|
|
132
|
+
try:
|
|
133
|
+
create_time = datetime.fromtimestamp(create_time / 1000).strftime('%Y-%m-%d %H:%M')
|
|
134
|
+
except:
|
|
135
|
+
create_time = str(create_time)
|
|
136
|
+
else:
|
|
137
|
+
create_time = '-'
|
|
138
|
+
|
|
139
|
+
table_data.append([
|
|
140
|
+
disk_id,
|
|
141
|
+
disk_name,
|
|
142
|
+
disk_size,
|
|
143
|
+
disk_type,
|
|
144
|
+
disk_status,
|
|
145
|
+
instance_name,
|
|
146
|
+
create_time
|
|
147
|
+
])
|
|
148
|
+
|
|
149
|
+
total_count = return_obj.get('totalCount', 0)
|
|
150
|
+
current_count = return_obj.get('currentCount', len(disk_list))
|
|
151
|
+
total_page = return_obj.get('totalPage', 1)
|
|
152
|
+
|
|
153
|
+
click.echo(f"\n云硬盘列表 (总计: {total_count} 个, 当前页: {current_count} 个, 第{page}/{total_page}页)\n")
|
|
154
|
+
table = tabulate(table_data, headers=headers, tablefmt='grid')
|
|
155
|
+
click.echo(table)
|
|
156
|
+
|
|
157
|
+
if page < total_page:
|
|
158
|
+
click.echo(f"\n提示: 使用 --page 参数查看其他页")
|
|
159
|
+
else:
|
|
160
|
+
click.echo("未找到云硬盘")
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
click.echo(f"运行出错: {e}", err=True)
|
|
164
|
+
import traceback
|
|
165
|
+
traceback.print_exc()
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ==================== 新增查询类命令(6个) ====================
|
|
169
|
+
|
|
170
|
+
@ebs.command('info')
|
|
171
|
+
@click.option('--region-id', '-r', required=True, help='资源池ID')
|
|
172
|
+
@click.option('--disk-id', '-d', required=True, help='云硬盘ID')
|
|
173
|
+
@click.option('--output', type=click.Choice(['table', 'json', 'yaml']), default='table')
|
|
174
|
+
@click.pass_context
|
|
175
|
+
def ebs_info(ctx, region_id, disk_id, output):
|
|
176
|
+
"""查询云硬盘详情(基于diskID)"""
|
|
177
|
+
client = EBSClient(ctx.obj['client'])
|
|
178
|
+
result = client.get_ebs_info(region_id=region_id, disk_id=disk_id)
|
|
179
|
+
if output == 'json' or result.get('statusCode') != 800:
|
|
180
|
+
click.echo(OutputFormatter.format_json(result)); return
|
|
181
|
+
ro = result.get('returnObj', {}) or {}
|
|
182
|
+
click.echo(f"云硬盘详情 ({disk_id}):")
|
|
183
|
+
if isinstance(ro, dict):
|
|
184
|
+
for k, v in ro.items():
|
|
185
|
+
click.echo(f" {k}: {v}")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@ebs.command('info-by-name')
|
|
189
|
+
@click.option('--region-id', '-r', required=True, help='资源池ID')
|
|
190
|
+
@click.option('--disk-name', '-n', required=True, help='云硬盘名称')
|
|
191
|
+
@click.option('--output', type=click.Choice(['table', 'json', 'yaml']), default='table')
|
|
192
|
+
@click.pass_context
|
|
193
|
+
def ebs_info_by_name(ctx, region_id, disk_name, output):
|
|
194
|
+
"""查询云硬盘详情(基于regionID和diskName)"""
|
|
195
|
+
client = EBSClient(ctx.obj['client'])
|
|
196
|
+
result = client.get_ebs_info_by_name(region_id=region_id, disk_name=disk_name)
|
|
197
|
+
if output == 'json' or result.get('statusCode') != 800:
|
|
198
|
+
click.echo(OutputFormatter.format_json(result)); return
|
|
199
|
+
ro = result.get('returnObj', {}) or {}
|
|
200
|
+
click.echo(f"云硬盘详情 (name={disk_name}):")
|
|
201
|
+
if isinstance(ro, dict):
|
|
202
|
+
for k, v in ro.items():
|
|
203
|
+
click.echo(f" {k}: {v}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@ebs.command('list-by-name')
|
|
207
|
+
@click.option('--region-id', '-r', required=True, help='资源池ID')
|
|
208
|
+
@click.option('--disk-name', '-n', required=True, help='云硬盘名称')
|
|
209
|
+
@click.option('--page', type=int, default=1, help='页码,默认1')
|
|
210
|
+
@click.option('--page-size', type=int, default=10, help='每页大小,默认10')
|
|
211
|
+
@click.option('--output', type=click.Choice(['table', 'json', 'yaml']), default='table')
|
|
212
|
+
@click.pass_context
|
|
213
|
+
def ebs_list_by_name(ctx, region_id, disk_name, page, page_size, output):
|
|
214
|
+
"""查询云硬盘列表(基于regionID和diskName)"""
|
|
215
|
+
client = EBSClient(ctx.obj['client'])
|
|
216
|
+
result = client.list_ebs_by_name(
|
|
217
|
+
region_id=region_id, disk_name=disk_name,
|
|
218
|
+
page_no=page, page_size=page_size)
|
|
219
|
+
if output == 'json' or result.get('statusCode') != 800:
|
|
220
|
+
click.echo(OutputFormatter.format_json(result)); return
|
|
221
|
+
ro = result.get('returnObj', {}) or {}
|
|
222
|
+
disk_list = ro.get('ebsList', []) or ro.get('diskList', []) or ro.get('list', []) or (ro if isinstance(ro, list) else [])
|
|
223
|
+
click.echo(f"云硬盘列表 (name={disk_name}, 共 {len(disk_list) if isinstance(disk_list, list) else 0} 个)")
|
|
224
|
+
if isinstance(disk_list, list):
|
|
225
|
+
for d in disk_list:
|
|
226
|
+
if isinstance(d, dict):
|
|
227
|
+
click.echo(f" {d.get('diskID','')} | {d.get('name', d.get('diskName',''))} | {d.get('size','')}GB | {d.get('status','')}")
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@ebs.command('snapshots')
|
|
231
|
+
@click.option('--region-id', '-r', required=True, help='资源池ID')
|
|
232
|
+
@click.option('--disk-id', '-d', help='云硬盘ID过滤')
|
|
233
|
+
@click.option('--snapshot-id', help='快照ID过滤')
|
|
234
|
+
@click.option('--snapshot-name', help='快照名称(模糊匹配)')
|
|
235
|
+
@click.option('--snapshot-status',
|
|
236
|
+
type=click.Choice(['available', 'freezing', 'creating', 'deleting', 'rollbacking', 'cloning', 'error']),
|
|
237
|
+
help='快照状态过滤')
|
|
238
|
+
@click.option('--snapshot-type', type=click.Choice(['manu', 'timer']), help='创建类型:manu 手动 / timer 自动')
|
|
239
|
+
@click.option('--volume-attr', type=click.Choice(['data', 'system']), help='云硬盘属性:data 数据盘 / system 系统盘')
|
|
240
|
+
@click.option('--retention-policy', type=click.Choice(['forever', 'custom']), help='保留策略')
|
|
241
|
+
@click.option('--max-results', type=int, help='期望返回数量(默认10,最大1000)')
|
|
242
|
+
@click.option('--next-token', help='接续查询token')
|
|
243
|
+
@click.option('--output', type=click.Choice(['table', 'json', 'yaml']), default='table')
|
|
244
|
+
@click.pass_context
|
|
245
|
+
def ebs_snapshots(ctx, region_id, disk_id, snapshot_id, snapshot_name,
|
|
246
|
+
snapshot_status, snapshot_type, volume_attr,
|
|
247
|
+
retention_policy, max_results, next_token, output):
|
|
248
|
+
"""查询云硬盘快照列表"""
|
|
249
|
+
client = EBSClient(ctx.obj['client'])
|
|
250
|
+
result = client.list_ebs_snapshots(
|
|
251
|
+
region_id=region_id, disk_id=disk_id, snapshot_id=snapshot_id,
|
|
252
|
+
snapshot_name=snapshot_name, snapshot_status=snapshot_status,
|
|
253
|
+
snapshot_type=snapshot_type, volume_attr=volume_attr,
|
|
254
|
+
retention_policy=retention_policy, max_results=max_results,
|
|
255
|
+
next_token=next_token)
|
|
256
|
+
if output == 'json' or result.get('statusCode') != 800:
|
|
257
|
+
click.echo(OutputFormatter.format_json(result)); return
|
|
258
|
+
ro = result.get('returnObj', {}) or {}
|
|
259
|
+
snap_list = ro.get('snapshotList', []) or ro.get('snapshots', []) or ro.get('list', []) or (ro if isinstance(ro, list) else [])
|
|
260
|
+
click.echo(f"云硬盘快照列表 (共 {len(snap_list) if isinstance(snap_list, list) else 0} 个)")
|
|
261
|
+
if isinstance(snap_list, list):
|
|
262
|
+
for s in snap_list:
|
|
263
|
+
if isinstance(s, dict):
|
|
264
|
+
click.echo(f" {s.get('snapshotID','')} | {s.get('snapshotName','')} | {s.get('status','')} | {s.get('size','')}GB")
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@ebs.command('snapshot-size')
|
|
268
|
+
@click.option('--region-id', '-r', required=True, help='资源池ID')
|
|
269
|
+
@click.option('--output', type=click.Choice(['table', 'json', 'yaml']), default='table')
|
|
270
|
+
@click.pass_context
|
|
271
|
+
def ebs_snapshot_size(ctx, region_id, output):
|
|
272
|
+
"""查询云硬盘快照使用量"""
|
|
273
|
+
client = EBSClient(ctx.obj['client'])
|
|
274
|
+
result = client.query_ebs_snapshot_size(region_id=region_id)
|
|
275
|
+
if output == 'json' or result.get('statusCode') != 800:
|
|
276
|
+
click.echo(OutputFormatter.format_json(result)); return
|
|
277
|
+
ro = result.get('returnObj', {}) or {}
|
|
278
|
+
click.echo(f"云硬盘快照使用量 ({region_id}):")
|
|
279
|
+
if isinstance(ro, dict):
|
|
280
|
+
for k, v in ro.items():
|
|
281
|
+
click.echo(f" {k}: {v}")
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@ebs.command('snapshot-policy')
|
|
285
|
+
@click.option('--region-id', '-r', required=True, help='资源池ID')
|
|
286
|
+
@click.option('--snapshot-policy-id', help='快照策略ID')
|
|
287
|
+
@click.option('--snapshot-policy-name', help='快照策略名称')
|
|
288
|
+
@click.option('--output', type=click.Choice(['table', 'json', 'yaml']), default='table')
|
|
289
|
+
@click.pass_context
|
|
290
|
+
def ebs_snapshot_policy(ctx, region_id, snapshot_policy_id, snapshot_policy_name, output):
|
|
291
|
+
"""查询云硬盘自动快照策略"""
|
|
292
|
+
client = EBSClient(ctx.obj['client'])
|
|
293
|
+
result = client.query_ebs_snapshot_policy(
|
|
294
|
+
region_id=region_id, snapshot_policy_id=snapshot_policy_id,
|
|
295
|
+
snapshot_policy_name=snapshot_policy_name)
|
|
296
|
+
if output == 'json' or result.get('statusCode') != 800:
|
|
297
|
+
click.echo(OutputFormatter.format_json(result)); return
|
|
298
|
+
ro = result.get('returnObj', {}) or {}
|
|
299
|
+
policy_list = ro.get('policyList', []) or ro.get('snapshotPolicies', []) or ro.get('list', []) or (ro if isinstance(ro, list) else [])
|
|
300
|
+
if isinstance(policy_list, list) and policy_list:
|
|
301
|
+
click.echo(f"云硬盘自动快照策略列表 (共 {len(policy_list)} 个)")
|
|
302
|
+
for p in policy_list:
|
|
303
|
+
if isinstance(p, dict):
|
|
304
|
+
click.echo(f" {p.get('snapshotPolicyID','')} | {p.get('snapshotPolicyName','')} | status={p.get('status','')}")
|
|
305
|
+
elif isinstance(ro, dict):
|
|
306
|
+
click.echo(f"云硬盘自动快照策略:")
|
|
307
|
+
for k, v in ro.items():
|
|
308
|
+
click.echo(f" {k}: {v}")
|