aliyun-controller 0.1.0__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.
- aliyun_controller-0.1.0/.gitignore +12 -0
- aliyun_controller-0.1.0/PKG-INFO +154 -0
- aliyun_controller-0.1.0/README.md +125 -0
- aliyun_controller-0.1.0/aliyun_controller/__init__.py +4 -0
- aliyun_controller-0.1.0/aliyun_controller/config.yaml.example +5 -0
- aliyun_controller-0.1.0/aliyun_controller/main.py +186 -0
- aliyun_controller-0.1.0/aliyun_controller/modules/__init__.py +3 -0
- aliyun_controller-0.1.0/aliyun_controller/modules/billing.py +189 -0
- aliyun_controller-0.1.0/aliyun_controller/modules/dns.py +520 -0
- aliyun_controller-0.1.0/aliyun_controller.egg-info/PKG-INFO +154 -0
- aliyun_controller-0.1.0/aliyun_controller.egg-info/SOURCES.txt +16 -0
- aliyun_controller-0.1.0/aliyun_controller.egg-info/dependency_links.txt +1 -0
- aliyun_controller-0.1.0/aliyun_controller.egg-info/entry_points.txt +2 -0
- aliyun_controller-0.1.0/aliyun_controller.egg-info/requires.txt +9 -0
- aliyun_controller-0.1.0/aliyun_controller.egg-info/top_level.txt +1 -0
- aliyun_controller-0.1.0/pyproject.toml +61 -0
- aliyun_controller-0.1.0/requirements.txt +5 -0
- aliyun_controller-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aliyun-controller
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A command-line tool for managing Alibaba Cloud services including billing queries and DNS management
|
|
5
|
+
Author-email: Moha-Master <hongkongreporter@outlook.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Moha-Master/Aliyun-Controller
|
|
8
|
+
Project-URL: Repository, https://github.com/Moha-Master/Aliyun-Controller
|
|
9
|
+
Project-URL: Issues, https://github.com/Moha-Master/Aliyun-Controller/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: alibabacloud-ecs20140526
|
|
22
|
+
Requires-Dist: alibabacloud-bssopenapi20171214
|
|
23
|
+
Requires-Dist: alibabacloud-alidns20150109
|
|
24
|
+
Requires-Dist: InquirerPy
|
|
25
|
+
Requires-Dist: PyYAML
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
29
|
+
|
|
30
|
+
# 阿里云控制台工具 (aliyun-controller)
|
|
31
|
+
|
|
32
|
+
这是一个调用阿里云 Python SDK 在命令行中执行常用操作的简单工具,目前具有账单查询和 DNS 管理功能。
|
|
33
|
+
|
|
34
|
+
## 功能特性
|
|
35
|
+
|
|
36
|
+
- **账单查询**:查询指定月份的阿里云账单总额和明细
|
|
37
|
+
- **流量统计**:查询指定月份的公网流出流量总量
|
|
38
|
+
- **DNS 管理**:管理域名解析记录,包括增删改查操作
|
|
39
|
+
|
|
40
|
+
## 后续计划
|
|
41
|
+
|
|
42
|
+
- 加入**ECS控制**的相关内容
|
|
43
|
+
- 加入**OOS控制**的相关内容
|
|
44
|
+
|
|
45
|
+
## 安装指南
|
|
46
|
+
|
|
47
|
+
### 通过 pip 安装(推荐)
|
|
48
|
+
|
|
49
|
+
可以通过 pip 直接安装:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install aliyun-controller
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
安装后,可以直接使用 `aliyunctl` 命令运行程序:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
aliyunctl
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 从源码运行(开发模式)
|
|
62
|
+
|
|
63
|
+
如果你希望进行二次开发或修改源码,可以从源码安装:
|
|
64
|
+
|
|
65
|
+
1. 克隆此项目到本地:
|
|
66
|
+
```bash
|
|
67
|
+
git clone <项目地址>
|
|
68
|
+
cd aliyun-controller
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
2. 创建虚拟环境(推荐):
|
|
72
|
+
```bash
|
|
73
|
+
python -m venv venv
|
|
74
|
+
source venv/bin/activate # Linux/Mac
|
|
75
|
+
# 或在 Windows 上: venv\Scripts\activate
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
3. 安装依赖:
|
|
79
|
+
```bash
|
|
80
|
+
pip install -e .
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
4. 创建阿里云 RAM 用户并授权:
|
|
84
|
+
- 登录阿里云控制台。
|
|
85
|
+
- 进入 RAM 访问控制。
|
|
86
|
+
- 在左侧导航栏选择 用户 > 创建用户。
|
|
87
|
+
- 设置登录名称和显示名称,勾选 为该用户自动生成AccessKey。
|
|
88
|
+
- 创建成功后,请务必保存好 AccessKey ID 和 AccessKey Secret,它们只显示一次。
|
|
89
|
+
- 为新创建的 RAM 用户授权:
|
|
90
|
+
- 在用户详情页,点击 添加权限。
|
|
91
|
+
- 选择 AliyunBSSReadOnlyAccess 和 AliyunDNSFullAccess 权限。
|
|
92
|
+
- 点击 确定 完成授权。
|
|
93
|
+
|
|
94
|
+
5. 配置阿里云访问密钥:
|
|
95
|
+
默认情况下,程序会在 `~/.configs/aliyun-controller` 目录下查找配置文件。
|
|
96
|
+
你可以复制示例配置文件并修改:
|
|
97
|
+
```bash
|
|
98
|
+
mkdir -p ~/.configs/aliyun-controller
|
|
99
|
+
cp config.yaml.example ~/.configs/aliyun-controller/config.yaml
|
|
100
|
+
```
|
|
101
|
+
然后编辑 `~/.configs/aliyun-controller/config.yaml` 文件,填入你创建的阿里云RAM用户的 AccessKey ID 和 AccessKey Secret:
|
|
102
|
+
```yaml
|
|
103
|
+
access_key_id: your_access_key_id
|
|
104
|
+
access_key_secret: your_access_key_secret
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
你也可以使用 `--dir/-D` 参数指定配置文件所在的目录:
|
|
108
|
+
```bash
|
|
109
|
+
aliyunctl -D /path/to/your/config/dir
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 使用方法
|
|
113
|
+
|
|
114
|
+
安装后,可以直接使用 `aliyunctl` 命令运行程序:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
aliyunctl
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
程序将显示交互式菜单,你可以选择以下功能:
|
|
121
|
+
|
|
122
|
+
1. **查询总流出流量**:查看指定月份的公网总流出流量
|
|
123
|
+
2. **归纳账单**:查看指定月份的账单明细和总额
|
|
124
|
+
3. **DNS解析管理**:管理域名解析记录
|
|
125
|
+
|
|
126
|
+
你也可以使用 `--dir/-D` 参数指定配置文件所在的目录:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
aliyunctl -D /path/to/your/config/dir
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 账单查询
|
|
133
|
+
|
|
134
|
+
- 程序会默认查询当前月份的账单
|
|
135
|
+
- 你也可以输入其他月份(格式:YYYY-MM / YYYY-M)进行查询
|
|
136
|
+
- 支持分页查询和重新查询
|
|
137
|
+
|
|
138
|
+
### DNS 管理
|
|
139
|
+
|
|
140
|
+
- 选择要管理的域名
|
|
141
|
+
- 查看所有解析记录
|
|
142
|
+
- 添加、编辑或删除解析记录
|
|
143
|
+
- 支持按不同方式排序记录(创建时间、二级域名、首字母)
|
|
144
|
+
|
|
145
|
+
## 权限要求
|
|
146
|
+
|
|
147
|
+
为了正常使用所有功能,你的阿里云 RAM 用户记得开放以下权限:
|
|
148
|
+
|
|
149
|
+
- `AliyunBSSReadOnlyAccess`:用于账单查询
|
|
150
|
+
- `AliyunDNSFullAccess`:用于 DNS 管理
|
|
151
|
+
|
|
152
|
+
## 日志记录
|
|
153
|
+
|
|
154
|
+
程序会在运行目录下生成 `app.log` 文件,记录操作日志和错误信息。
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# 阿里云控制台工具 (aliyun-controller)
|
|
2
|
+
|
|
3
|
+
这是一个调用阿里云 Python SDK 在命令行中执行常用操作的简单工具,目前具有账单查询和 DNS 管理功能。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- **账单查询**:查询指定月份的阿里云账单总额和明细
|
|
8
|
+
- **流量统计**:查询指定月份的公网流出流量总量
|
|
9
|
+
- **DNS 管理**:管理域名解析记录,包括增删改查操作
|
|
10
|
+
|
|
11
|
+
## 后续计划
|
|
12
|
+
|
|
13
|
+
- 加入**ECS控制**的相关内容
|
|
14
|
+
- 加入**OOS控制**的相关内容
|
|
15
|
+
|
|
16
|
+
## 安装指南
|
|
17
|
+
|
|
18
|
+
### 通过 pip 安装(推荐)
|
|
19
|
+
|
|
20
|
+
可以通过 pip 直接安装:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install aliyun-controller
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
安装后,可以直接使用 `aliyunctl` 命令运行程序:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
aliyunctl
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 从源码运行(开发模式)
|
|
33
|
+
|
|
34
|
+
如果你希望进行二次开发或修改源码,可以从源码安装:
|
|
35
|
+
|
|
36
|
+
1. 克隆此项目到本地:
|
|
37
|
+
```bash
|
|
38
|
+
git clone <项目地址>
|
|
39
|
+
cd aliyun-controller
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
2. 创建虚拟环境(推荐):
|
|
43
|
+
```bash
|
|
44
|
+
python -m venv venv
|
|
45
|
+
source venv/bin/activate # Linux/Mac
|
|
46
|
+
# 或在 Windows 上: venv\Scripts\activate
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
3. 安装依赖:
|
|
50
|
+
```bash
|
|
51
|
+
pip install -e .
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
4. 创建阿里云 RAM 用户并授权:
|
|
55
|
+
- 登录阿里云控制台。
|
|
56
|
+
- 进入 RAM 访问控制。
|
|
57
|
+
- 在左侧导航栏选择 用户 > 创建用户。
|
|
58
|
+
- 设置登录名称和显示名称,勾选 为该用户自动生成AccessKey。
|
|
59
|
+
- 创建成功后,请务必保存好 AccessKey ID 和 AccessKey Secret,它们只显示一次。
|
|
60
|
+
- 为新创建的 RAM 用户授权:
|
|
61
|
+
- 在用户详情页,点击 添加权限。
|
|
62
|
+
- 选择 AliyunBSSReadOnlyAccess 和 AliyunDNSFullAccess 权限。
|
|
63
|
+
- 点击 确定 完成授权。
|
|
64
|
+
|
|
65
|
+
5. 配置阿里云访问密钥:
|
|
66
|
+
默认情况下,程序会在 `~/.configs/aliyun-controller` 目录下查找配置文件。
|
|
67
|
+
你可以复制示例配置文件并修改:
|
|
68
|
+
```bash
|
|
69
|
+
mkdir -p ~/.configs/aliyun-controller
|
|
70
|
+
cp config.yaml.example ~/.configs/aliyun-controller/config.yaml
|
|
71
|
+
```
|
|
72
|
+
然后编辑 `~/.configs/aliyun-controller/config.yaml` 文件,填入你创建的阿里云RAM用户的 AccessKey ID 和 AccessKey Secret:
|
|
73
|
+
```yaml
|
|
74
|
+
access_key_id: your_access_key_id
|
|
75
|
+
access_key_secret: your_access_key_secret
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
你也可以使用 `--dir/-D` 参数指定配置文件所在的目录:
|
|
79
|
+
```bash
|
|
80
|
+
aliyunctl -D /path/to/your/config/dir
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 使用方法
|
|
84
|
+
|
|
85
|
+
安装后,可以直接使用 `aliyunctl` 命令运行程序:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
aliyunctl
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
程序将显示交互式菜单,你可以选择以下功能:
|
|
92
|
+
|
|
93
|
+
1. **查询总流出流量**:查看指定月份的公网总流出流量
|
|
94
|
+
2. **归纳账单**:查看指定月份的账单明细和总额
|
|
95
|
+
3. **DNS解析管理**:管理域名解析记录
|
|
96
|
+
|
|
97
|
+
你也可以使用 `--dir/-D` 参数指定配置文件所在的目录:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
aliyunctl -D /path/to/your/config/dir
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 账单查询
|
|
104
|
+
|
|
105
|
+
- 程序会默认查询当前月份的账单
|
|
106
|
+
- 你也可以输入其他月份(格式:YYYY-MM / YYYY-M)进行查询
|
|
107
|
+
- 支持分页查询和重新查询
|
|
108
|
+
|
|
109
|
+
### DNS 管理
|
|
110
|
+
|
|
111
|
+
- 选择要管理的域名
|
|
112
|
+
- 查看所有解析记录
|
|
113
|
+
- 添加、编辑或删除解析记录
|
|
114
|
+
- 支持按不同方式排序记录(创建时间、二级域名、首字母)
|
|
115
|
+
|
|
116
|
+
## 权限要求
|
|
117
|
+
|
|
118
|
+
为了正常使用所有功能,你的阿里云 RAM 用户记得开放以下权限:
|
|
119
|
+
|
|
120
|
+
- `AliyunBSSReadOnlyAccess`:用于账单查询
|
|
121
|
+
- `AliyunDNSFullAccess`:用于 DNS 管理
|
|
122
|
+
|
|
123
|
+
## 日志记录
|
|
124
|
+
|
|
125
|
+
程序会在运行目录下生成 `app.log` 文件,记录操作日志和错误信息。
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import re
|
|
3
|
+
import logging
|
|
4
|
+
import traceback
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from InquirerPy import prompt
|
|
9
|
+
from InquirerPy.base.control import Choice
|
|
10
|
+
from aliyun_controller.modules.billing import get_outbound_traffic_module, summarize_billing_module
|
|
11
|
+
from aliyun_controller.modules.dns import dns_management_module
|
|
12
|
+
|
|
13
|
+
# 配置日志
|
|
14
|
+
logging.basicConfig(
|
|
15
|
+
level=logging.INFO,
|
|
16
|
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
17
|
+
handlers=[
|
|
18
|
+
logging.FileHandler("app.log", encoding='utf-8'),
|
|
19
|
+
logging.StreamHandler()
|
|
20
|
+
]
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def parse_args():
|
|
24
|
+
"""解析命令行参数"""
|
|
25
|
+
parser = argparse.ArgumentParser(description="阿里云控制台工具")
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"-D", "--dir",
|
|
28
|
+
help="配置文件目录路径",
|
|
29
|
+
default=os.path.expanduser("~/.config/aliyun-controller")
|
|
30
|
+
)
|
|
31
|
+
return parser.parse_args()
|
|
32
|
+
|
|
33
|
+
def _prompt_for_billing_cycle() -> str | None:
|
|
34
|
+
"""
|
|
35
|
+
提示用户输入账单周期,并验证格式。
|
|
36
|
+
支持 YYYY-MM 和 YYYY-M 格式。
|
|
37
|
+
如果用户取消输入,则返回 None。
|
|
38
|
+
"""
|
|
39
|
+
date_prompt = [
|
|
40
|
+
{
|
|
41
|
+
"type": "input",
|
|
42
|
+
"message": "请输入要查询的月份 (格式 YYYY-MM 或 YYYY-M):",
|
|
43
|
+
"name": "cycle",
|
|
44
|
+
"validate": lambda val: re.match(r"^\d{4}-(0?[1-9]|1[0-2])$", val) is not None,
|
|
45
|
+
"invalid_message": "格式错误,请输入 YYYY-MM 或 YYYY-M 格式的月份。"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
try:
|
|
49
|
+
answer = prompt(date_prompt)
|
|
50
|
+
if not answer: # 用户按 Ctrl+C
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
cycle = answer.get("cycle")
|
|
54
|
+
year, month = cycle.split('-')
|
|
55
|
+
if len(month) == 1:
|
|
56
|
+
month = '0' + month
|
|
57
|
+
return f"{year}-{month}"
|
|
58
|
+
except KeyboardInterrupt:
|
|
59
|
+
return None
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logging.error(f"输入月份时发生错误: {e}")
|
|
62
|
+
logging.debug(traceback.format_exc())
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
def query_and_repeat(query_function):
|
|
66
|
+
"""
|
|
67
|
+
包装查询函数:先查当月,然后提供子菜单让用户选择继续查询或返回。
|
|
68
|
+
:param query_function: 接受 billing_cycle 参数的查询函数。
|
|
69
|
+
"""
|
|
70
|
+
# 1. 默认查询当月
|
|
71
|
+
current_cycle = datetime.datetime.now().strftime("%Y-%m")
|
|
72
|
+
print(f"\n--- 正在查询默认月份 {current_cycle} 的账单 ---")
|
|
73
|
+
try:
|
|
74
|
+
query_function(current_cycle)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logging.error(f"查询账单时发生错误: {e}")
|
|
77
|
+
logging.debug(traceback.format_exc())
|
|
78
|
+
print(f"\n查询账单时发生错误,请查看日志了解详情。")
|
|
79
|
+
|
|
80
|
+
# 2. 进入子菜单循环
|
|
81
|
+
while True:
|
|
82
|
+
sub_menu_prompt = [
|
|
83
|
+
{
|
|
84
|
+
"type": "list",
|
|
85
|
+
"message": "请选择接下来的操作:",
|
|
86
|
+
"choices": [
|
|
87
|
+
Choice("set_date", name="查询其他月份"),
|
|
88
|
+
Choice("return", name="返回主菜单")
|
|
89
|
+
],
|
|
90
|
+
"name": "sub_action"
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
result = prompt(sub_menu_prompt)
|
|
96
|
+
if not result: # 用户按 Ctrl+C
|
|
97
|
+
print("\n已返回主菜单。")
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
action = result.get("sub_action")
|
|
101
|
+
if action == "set_date":
|
|
102
|
+
new_cycle = _prompt_for_billing_cycle()
|
|
103
|
+
if new_cycle:
|
|
104
|
+
print(f"\n--- 正在查询 {new_cycle} 的账单 ---")
|
|
105
|
+
try:
|
|
106
|
+
query_function(new_cycle)
|
|
107
|
+
except Exception as e:
|
|
108
|
+
logging.error(f"查询账单时发生错误: {e}")
|
|
109
|
+
logging.debug(traceback.format_exc())
|
|
110
|
+
print(f"\n查询账单时发生错误,请查看日志了解详情。")
|
|
111
|
+
else:
|
|
112
|
+
print("\n输入已取消。")
|
|
113
|
+
continue # 重新显示子菜单
|
|
114
|
+
elif action == "return":
|
|
115
|
+
print("\n已返回主菜单。")
|
|
116
|
+
break
|
|
117
|
+
except Exception as e:
|
|
118
|
+
logging.error(f"执行操作时发生错误: {e}")
|
|
119
|
+
logging.debug(traceback.format_exc())
|
|
120
|
+
print(f"\n执行操作时发生错误,请查看日志了解详情。")
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
def main():
|
|
124
|
+
"""
|
|
125
|
+
主函数,提供交互式菜单
|
|
126
|
+
"""
|
|
127
|
+
args = parse_args()
|
|
128
|
+
|
|
129
|
+
# 设置配置目录环境变量,供模块使用
|
|
130
|
+
os.environ['ALIYUN_CONTROLLER_CONFIG_DIR'] = args.dir
|
|
131
|
+
|
|
132
|
+
print("阿里云控制台工具")
|
|
133
|
+
print("=" * 30)
|
|
134
|
+
|
|
135
|
+
while True:
|
|
136
|
+
questions = [
|
|
137
|
+
{
|
|
138
|
+
"type": "list",
|
|
139
|
+
"message": "请选择要执行的功能:",
|
|
140
|
+
"choices": [
|
|
141
|
+
Choice("get_traffic", name="1. 查询总流出流量"),
|
|
142
|
+
Choice("summarize_bill", name="2. 归纳账单"),
|
|
143
|
+
Choice("manage_dns", name="3. DNS解析管理"),
|
|
144
|
+
Choice(value=None, name="[退出]")
|
|
145
|
+
],
|
|
146
|
+
"name": "action",
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
result = prompt(questions)
|
|
152
|
+
if not result:
|
|
153
|
+
print("\n已退出。")
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
action = result.get("action")
|
|
157
|
+
|
|
158
|
+
if action == "get_traffic":
|
|
159
|
+
query_and_repeat(get_outbound_traffic_module)
|
|
160
|
+
elif action == "summarize_bill":
|
|
161
|
+
query_and_repeat(summarize_billing_module)
|
|
162
|
+
elif action == "manage_dns":
|
|
163
|
+
try:
|
|
164
|
+
dns_management_module()
|
|
165
|
+
except Exception as e:
|
|
166
|
+
logging.error(f"DNS管理模块发生错误: {e}")
|
|
167
|
+
logging.debug(traceback.format_exc())
|
|
168
|
+
print(f"\nDNS管理模块发生错误,请查看日志了解详情。")
|
|
169
|
+
elif action is None:
|
|
170
|
+
print("已退出。")
|
|
171
|
+
break
|
|
172
|
+
except Exception as e:
|
|
173
|
+
logging.error(f"主菜单执行时发生错误: {e}")
|
|
174
|
+
logging.debug(traceback.format_exc())
|
|
175
|
+
print(f"\n执行操作时发生错误,请查看日志了解详情。")
|
|
176
|
+
continue
|
|
177
|
+
|
|
178
|
+
if __name__ == "__main__":
|
|
179
|
+
try:
|
|
180
|
+
main()
|
|
181
|
+
except KeyboardInterrupt:
|
|
182
|
+
print("\n\n检测到中断,程序已退出。")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
logging.error(f"程序运行时发生未处理的错误: {e}")
|
|
185
|
+
logging.debug(traceback.format_exc())
|
|
186
|
+
print(f"\n程序运行时发生未处理的错误,请查看日志了解详情。")
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from alibabacloud_bssopenapi20171214.client import Client as BssOpenApi20171214Client
|
|
5
|
+
from alibabacloud_bssopenapi20171214.models import DescribeInstanceBillRequest
|
|
6
|
+
from alibabacloud_tea_openapi import models as open_api_models
|
|
7
|
+
|
|
8
|
+
def load_config():
|
|
9
|
+
"""加载配置文件"""
|
|
10
|
+
config_dir = os.environ.get('ALIYUN_CONTROLLER_CONFIG_DIR',
|
|
11
|
+
os.path.expanduser('~/.config/aliyun-controller'))
|
|
12
|
+
config_path = Path(config_dir) / 'config.yaml'
|
|
13
|
+
example_path = Path(config_dir) / 'config.yaml.example'
|
|
14
|
+
package_example_path = Path(__file__).parent.parent / 'config.yaml.example'
|
|
15
|
+
|
|
16
|
+
# 检查配置目录是否存在,如果不存在则创建
|
|
17
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
18
|
+
|
|
19
|
+
# 如果示例文件不存在,则从包中复制
|
|
20
|
+
if not example_path.exists() and package_example_path.exists():
|
|
21
|
+
import shutil
|
|
22
|
+
shutil.copy(package_example_path, example_path)
|
|
23
|
+
|
|
24
|
+
# 如果配置文件不存在,则报错退出
|
|
25
|
+
if not config_path.exists():
|
|
26
|
+
print("配置文件不存在,请参考 config.yaml.example 创建 config.yaml 文件")
|
|
27
|
+
raise FileNotFoundError(f"配置文件不存在: {config_path}")
|
|
28
|
+
|
|
29
|
+
with open(config_path, 'r', encoding='utf-8') as f:
|
|
30
|
+
return yaml.safe_load(f)
|
|
31
|
+
|
|
32
|
+
class AliCloudBssQuerier:
|
|
33
|
+
def __init__(self):
|
|
34
|
+
"""
|
|
35
|
+
初始化客户端
|
|
36
|
+
"""
|
|
37
|
+
config = load_config()
|
|
38
|
+
self.client = BssOpenApi20171214Client(
|
|
39
|
+
open_api_models.Config(
|
|
40
|
+
access_key_id=config['access_key_id'],
|
|
41
|
+
access_key_secret=config['access_key_secret'],
|
|
42
|
+
region_id="cn-hangzhou",
|
|
43
|
+
)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def fetch_bill_details(self, billing_cycle: str, subscription_type: str) -> list:
|
|
47
|
+
"""
|
|
48
|
+
根据指定的账单类型,分页获取所有账单明细。
|
|
49
|
+
"""
|
|
50
|
+
all_items = []
|
|
51
|
+
next_token = None
|
|
52
|
+
try:
|
|
53
|
+
while True:
|
|
54
|
+
request = DescribeInstanceBillRequest(
|
|
55
|
+
billing_cycle=billing_cycle,
|
|
56
|
+
subscription_type=subscription_type,
|
|
57
|
+
is_billing_item=True,
|
|
58
|
+
max_results=300
|
|
59
|
+
)
|
|
60
|
+
if next_token:
|
|
61
|
+
request.next_token = next_token
|
|
62
|
+
|
|
63
|
+
response = self.client.describe_instance_bill(request)
|
|
64
|
+
|
|
65
|
+
response_dict = response.body.to_map()
|
|
66
|
+
data = response_dict.get('Data', {})
|
|
67
|
+
if not data:
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
items_list = data.get('Items', [])
|
|
71
|
+
all_items.extend(items_list)
|
|
72
|
+
|
|
73
|
+
next_token = data.get('NextToken')
|
|
74
|
+
if not next_token:
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
return all_items
|
|
78
|
+
|
|
79
|
+
except Exception as e:
|
|
80
|
+
print(f"\n查询 [{subscription_type}] 类型账单时出错: {e}")
|
|
81
|
+
return []
|
|
82
|
+
|
|
83
|
+
def fetch_all_bill_details(self, billing_cycle: str) -> list:
|
|
84
|
+
"""
|
|
85
|
+
获取所有类型的账单明细(PayAsYouGo + Subscription)
|
|
86
|
+
"""
|
|
87
|
+
all_items = []
|
|
88
|
+
all_items.extend(self.fetch_bill_details(billing_cycle, 'PayAsYouGo'))
|
|
89
|
+
all_items.extend(self.fetch_bill_details(billing_cycle, 'Subscription'))
|
|
90
|
+
return all_items
|
|
91
|
+
|
|
92
|
+
def convert_usage_to_bytes(self, usage: float, unit: str) -> float:
|
|
93
|
+
"""
|
|
94
|
+
将用量转换为字节
|
|
95
|
+
"""
|
|
96
|
+
unit = unit.upper()
|
|
97
|
+
if unit == 'GB':
|
|
98
|
+
return usage * 1024 * 1024 * 1024
|
|
99
|
+
elif unit == 'MB':
|
|
100
|
+
return usage * 1024 * 1024
|
|
101
|
+
elif unit == 'KB':
|
|
102
|
+
return usage * 1024
|
|
103
|
+
else:
|
|
104
|
+
return usage
|
|
105
|
+
|
|
106
|
+
def get_outbound_traffic_module(billing_cycle: str):
|
|
107
|
+
"""
|
|
108
|
+
流量查询模块
|
|
109
|
+
"""
|
|
110
|
+
querier = AliCloudBssQuerier()
|
|
111
|
+
total_usage_bytes = 0.0
|
|
112
|
+
|
|
113
|
+
TRAFFIC_ITEMS_CODES = [
|
|
114
|
+
"ECS_Out_Bytes",
|
|
115
|
+
"Eip_Out_Bytes",
|
|
116
|
+
"Cdn_domestic_flow",
|
|
117
|
+
"Cdn_overseas_flow",
|
|
118
|
+
"OSS_Out_Traffic",
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
print(f"\n正在查询账单周期 {billing_cycle} 的账单明细...")
|
|
122
|
+
|
|
123
|
+
all_items = querier.fetch_all_bill_details(billing_cycle)
|
|
124
|
+
|
|
125
|
+
if not all_items:
|
|
126
|
+
print("未发现任何账单明细。")
|
|
127
|
+
return
|
|
128
|
+
|
|
129
|
+
print("账单明细获取成功,开始计算总流量...")
|
|
130
|
+
for item in all_items:
|
|
131
|
+
if item.get('BillingItemCode') in TRAFFIC_ITEMS_CODES:
|
|
132
|
+
usage_str = item.get('Usage')
|
|
133
|
+
unit = (item.get('UsageUnit') or '').upper()
|
|
134
|
+
if usage_str:
|
|
135
|
+
try:
|
|
136
|
+
usage = float(usage_str)
|
|
137
|
+
if usage > 0:
|
|
138
|
+
usage_bytes = querier.convert_usage_to_bytes(usage, unit)
|
|
139
|
+
total_usage_bytes += usage_bytes
|
|
140
|
+
except ValueError:
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
total_traffic_gb = total_usage_bytes / (1024 * 1024 * 1024)
|
|
144
|
+
print("\n" + "="*45)
|
|
145
|
+
print(f"账单周期 {billing_cycle} 的总公网流出流量: {total_traffic_gb:.4f} GB")
|
|
146
|
+
print("="*45)
|
|
147
|
+
|
|
148
|
+
def summarize_billing_module(billing_cycle: str):
|
|
149
|
+
"""
|
|
150
|
+
当月完整账单归纳模块
|
|
151
|
+
"""
|
|
152
|
+
querier = AliCloudBssQuerier()
|
|
153
|
+
summary = {}
|
|
154
|
+
|
|
155
|
+
print(f"\n正在获取账单周期 {billing_cycle} 的所有账单明细...")
|
|
156
|
+
all_items = querier.fetch_all_bill_details(billing_cycle)
|
|
157
|
+
|
|
158
|
+
if not all_items:
|
|
159
|
+
print("未发现任何账单明细。")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
for item in all_items:
|
|
163
|
+
product_code = item.get('ProductCode', 'Unknown')
|
|
164
|
+
product_name = item.get('ProductName', 'Unknown')
|
|
165
|
+
amount = float(item.get('PretaxAmount', 0.0))
|
|
166
|
+
if product_code not in summary:
|
|
167
|
+
summary[product_code] = {'product_name': product_name, 'total_amount': 0.0, 'count': 0}
|
|
168
|
+
summary[product_code]['product_name'] = product_name # 更新产品名称(同一产品代码可能有多个名称,取最后一个)
|
|
169
|
+
summary[product_code]['total_amount'] += amount
|
|
170
|
+
summary[product_code]['count'] += 1
|
|
171
|
+
|
|
172
|
+
# 按金额从大到小排序
|
|
173
|
+
sorted_summary = sorted(summary.items(), key=lambda x: x[1]['total_amount'], reverse=True)
|
|
174
|
+
|
|
175
|
+
print("\n" + "="*70)
|
|
176
|
+
print(f"账单周期 {billing_cycle} 消费归纳".center(70))
|
|
177
|
+
print("="*70)
|
|
178
|
+
print(f"{'产品名称':<25} {'产品代码':<15} {'账单条数':<10} {'总金额 (元)':<15}")
|
|
179
|
+
print("-"*70)
|
|
180
|
+
|
|
181
|
+
total_amount = 0.0
|
|
182
|
+
for product_code, data in sorted_summary:
|
|
183
|
+
total_amount += data['total_amount']
|
|
184
|
+
product_name = data['product_name'][:24] # 截断过长的产品名称
|
|
185
|
+
print(f"{product_name:<25} {product_code:<15} {data['count']:<10} {data['total_amount']:<15.2f}")
|
|
186
|
+
|
|
187
|
+
print("-"*70)
|
|
188
|
+
print(f"总计: {total_amount:.2f} 元".rjust(70))
|
|
189
|
+
print("="*70)
|