├── .README.MD_images
├── 03508508.png
├── 0655ae9e.png
├── 31851b79.png
├── 7f1ab67c.png
├── a2d1fdf3.png
├── abd90457.png
├── b69421f5.png
├── c082551f.png
├── e815d712.png
└── f71ed087.png
├── README.MD
├── bot.py
├── config.json
└── utils.py
/.README.MD_images/03508508.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/03508508.png
--------------------------------------------------------------------------------
/.README.MD_images/0655ae9e.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/0655ae9e.png
--------------------------------------------------------------------------------
/.README.MD_images/31851b79.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/31851b79.png
--------------------------------------------------------------------------------
/.README.MD_images/7f1ab67c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/7f1ab67c.png
--------------------------------------------------------------------------------
/.README.MD_images/a2d1fdf3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/a2d1fdf3.png
--------------------------------------------------------------------------------
/.README.MD_images/abd90457.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/abd90457.png
--------------------------------------------------------------------------------
/.README.MD_images/b69421f5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/b69421f5.png
--------------------------------------------------------------------------------
/.README.MD_images/c082551f.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/c082551f.png
--------------------------------------------------------------------------------
/.README.MD_images/e815d712.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/e815d712.png
--------------------------------------------------------------------------------
/.README.MD_images/f71ed087.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/f71ed087.png
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # 🏺 `azure-helper-bot`
2 |
3 | 
4 |
5 | ### 🍾 用 telegram bot 管理 Azure 虚拟机
6 |
7 | ### 🔖 主要功能
8 |
9 | + 管理 Azure 账号,管理账号订阅
10 | + 创建和管理 Azure 虚拟机,更换虚拟机 IP
11 |
12 | ### 📦 部分截图
13 |
14 | 1. 账号添加和管理
15 |
16 | 
17 |
18 | 
19 |
20 | 
21 |
22 | 2. 实例创建和管理
23 |
24 | 
25 |
26 | 
27 |
28 | 
29 |
30 | 
31 |
32 | 
33 |
34 | ### 🌌 用法
35 |
36 | 1. 在 [@botfather](https://t.me/botfather) 新建一个 bot,记下 `bot token`
37 |
38 | 2. 选择相应的系统平台,下载 release 中的一键包和 `config.json`
39 |
40 | 3. 按照你的需求编辑 `config.json`,`*` 的部分需要去掉或编辑
41 |
42 | ```
43 | {
44 | "BOT": {
45 | "NAME": "Azure 小助手",
46 | "TOKEN": "*bot 的 token",
47 | "ADMINS": [*你的 telegram 数字 id]
48 | },
49 | "VM": {
50 | "DISK_SIZE_DEFAULT": *默认磁盘大小,
51 | "LOCATIONS": {
52 | "*创建虚拟机时显示的地区": "*可根据需求编辑",
53 | "美国中": "centralus",
54 | "美国东": "eastus",
55 | "美国西": "westus",
56 | "东亚": "eastasia",
57 | "东南亚": "southeastasia",
58 | "日本东": "japaneast",
59 | "日本西": "japanwest",
60 | "韩国中": "koreacentral",
61 | "韩国南": "koreasouth"
62 | },
63 | "SIZES": {
64 | "*创建虚拟机时显示的虚拟机型号": "*可根据需求编辑",
65 | "B1s": "Standard_B1s",
66 | "B2s": "Standard_B2s",
67 | "B4ms": "Standard_B4ms",
68 | "D2s_v3": "Standard_D2s_v3",
69 | "D4s_v3": "Standard_D4s_v3",
70 | "F1s": "Standard_F1s",
71 | "F2": "Standard_F2",
72 | "F2s": "Standard_F2s",
73 | "F4s": "Standard_F4s"
74 | },
75 | "OS_INFOS": {
76 | "*创建虚拟机时显示的虚拟机镜像": "*可根据需求编辑",
77 | "Ubuntu 16.04": {
78 | "os": "Ubuntu 16.04",
79 | "image_reference": {
80 | "publisher": "Canonical",
81 | "offer": "UbuntuServer",
82 | "sku": "16.04-LTS",
83 | "version": "latest"
84 | }
85 | },
86 | "Ubuntu 18.04": {
87 | "os": "Ubuntu 18.04",
88 | "image_reference": {
89 | "publisher": "Canonical",
90 | "offer": "UbuntuServer",
91 | "sku": "18.04-LTS",
92 | "version": "latest"
93 | }
94 | },
95 | "CentOs 7.5": {
96 | "os": "CentOs 7.5",
97 | "image_reference": {
98 | "publisher": "OpenLogic",
99 | "offer": "CentOS",
100 | "sku": "7.5",
101 | "version": "latest"
102 | }
103 | },
104 | "CentOS 8.2": {
105 | "os": "CentOS 8.2",
106 | "image_reference": {
107 | "publisher": "OpenLogic",
108 | "offer": "CentOS",
109 | "sku": "8_2",
110 | "version": "latest"
111 | }
112 | },
113 | "Debian 10": {
114 | "os": "Debian 10",
115 | "image_reference": {
116 | "publisher": "debian",
117 | "offer": "debian-10",
118 | "sku": "10",
119 | "version": "latest"
120 | }
121 | },
122 | "Debian 9": {
123 | "os": "Debian 9",
124 | "image_reference": {
125 | "publisher": "credativ",
126 | "offer": "Debian",
127 | "sku": "9",
128 | "version": "latest"
129 | }
130 | }
131 | }
132 | }
133 | }
134 | ```
135 |
136 | 4. 给予运行权限并运行
137 |
138 | ```
139 | chmod +x az*
140 | /az*
141 | ```
142 |
143 | 
144 |
145 | ### 🐛 使用问题
146 |
147 | + 不是通过 bot 创建的虚拟机可能更换 IP 等操作失败
148 | + 不支持多个订阅的账号
149 | + 由于使用设备码 `(device code)` 授权,token 有效期只有 3 个月,之后需要重新添加
150 |
151 | ***
152 |
153 | + bot 年久失修,满足日常简单需求,不再维护
154 | + 新版网页版 Azure 小助手已经发布
155 |
156 |
--------------------------------------------------------------------------------
/bot.py:
--------------------------------------------------------------------------------
1 | import re
2 | import json
3 | import logging
4 | import traceback
5 | from time import sleep
6 |
7 | import telebot
8 | from telebot import types
9 |
10 | from utils import user_dict, UserDict, RefreshToken
11 |
12 | from az.az_token import Token
13 | from az.az_sub import Subscription
14 | from az.az_rg import ResourceGroup
15 | from az.az_nic import Network
16 | from az.az_vm import VirtualMachine
17 | from az.az_config import VM_LOCATIONS, VM_SIZES, VM_OS_INFOS
18 |
19 | config = json.load(open('config.json', 'r', encoding='utf-8'))
20 | BOT_TOKEN = config['BOT']['TOKEN']
21 | BOT_ADMINS = config['BOT']['ADMINS']
22 | BOT_NAME = config['BOT']['NAME']
23 |
24 | bot = telebot.TeleBot(BOT_TOKEN)
25 |
26 |
27 | @bot.message_handler(content_types=['text'])
28 | def handle_text(m):
29 | try:
30 | logger.info(m)
31 |
32 | if m.from_user.id not in BOT_ADMINS:
33 | return
34 |
35 | start(m)
36 | except Exception as e:
37 | traceback.print_exc()
38 | handle_exception(e, m)
39 |
40 |
41 | @bot.callback_query_handler(func=lambda call: True)
42 | def handle_callback(call):
43 | try:
44 | logger.info(call)
45 |
46 | if call.from_user.id not in BOT_ADMINS or \
47 | len(call.data.split(':')) != 3:
48 | return
49 |
50 | action = call.data.split(':')[0]
51 | sub_action = call.data.split(':')[1]
52 | value = call.data.split(':')[2]
53 |
54 | if action == 'aa':
55 | add_account(call)
56 |
57 | elif action == 'ma':
58 | if sub_action == '':
59 | list_accounts(call, '管理账号', action, 'ss')
60 | elif sub_action == 'ss':
61 | show_account_sub(call, value)
62 | elif sub_action == 'rm':
63 | remove_account(call, value)
64 |
65 | elif action == 'cvm':
66 | if sub_action == '':
67 | list_accounts(call, '创建实例', action, 'ce')
68 | elif sub_action == 'ce':
69 | set_refresh_token_list_sub(call, value, create_vm_set_subscription_id_list_size)
70 | elif sub_action == 'cs':
71 | create_vm_set_subscription_id_list_size(call, value)
72 | elif sub_action == 'csize':
73 | create_vm_set_size_list_os(call, value)
74 | elif sub_action == 'cos':
75 | create_vm_set_os_list_location(call, value)
76 | elif sub_action == 'cl':
77 | create_vm_set_location_confirm_create(call, value)
78 | elif sub_action == 'ok':
79 | create_vm(call)
80 | elif sub_action == 'cancel':
81 | edit_cancel(call)
82 |
83 | elif action == 'mvm':
84 | if sub_action == '':
85 | list_accounts(call, '管理实例', action, 'ce')
86 | elif sub_action == 'ce':
87 | set_refresh_token_list_sub(call, value, manage_vm_set_subscription_id_list_vm)
88 | elif sub_action == 'cs':
89 | manage_vm_set_subscription_id_list_vm(call, value)
90 | elif sub_action == 'gvm':
91 | get_vm(call, value)
92 | elif sub_action == 'ip':
93 | change_vm_ip(call, value)
94 | elif sub_action == 'del':
95 | delete_vm(call, value)
96 |
97 | except Exception as e:
98 | traceback.print_exc()
99 | handle_exception(e, call)
100 |
101 |
102 | def handle_exception(e, d):
103 | bot.send_message(
104 | text=f'出错啦\n{e}
',
105 | chat_id=d.from_user.id,
106 | parse_mode='HTML'
107 | )
108 | start(d)
109 |
110 |
111 | def start(m):
112 | user_dict[m.from_user.id] = UserDict()
113 |
114 | markup = types.InlineKeyboardMarkup(row_width=2)
115 | markup.add(
116 | types.InlineKeyboardButton(text='添加账号', callback_data='aa::'),
117 | types.InlineKeyboardButton(text='管理账号', callback_data='ma::'),
118 | types.InlineKeyboardButton(text='创建实例', callback_data='cvm::'),
119 | types.InlineKeyboardButton(text='管理实例', callback_data='mvm::')
120 | )
121 | bot.send_message(
122 | text=f'欢迎使用 {BOT_NAME}\n'
123 | f'你可以管理 Azure 账号,创建实例,更换 IP 等\n\n'
124 | f'请选择你要进行的操作:',
125 | chat_id=m.from_user.id,
126 | parse_mode='HTML',
127 | reply_markup=markup
128 | )
129 |
130 |
131 | def edit_cancel(call):
132 | bot.edit_message_text(
133 | text='已取消',
134 | chat_id=call.from_user.id,
135 | message_id=call.message.message_id
136 | )
137 |
138 |
139 | def add_account(call):
140 | msg = bot.edit_message_text(
141 | text='正在进行 添加账号\n\n'
142 | '请打开 '
143 | '此链接'
144 | ' ,登录你要添加的 Azure 账号,找到页面上显示的 租户 (Tenant) ID 并回复'
145 | ':',
146 | chat_id=call.from_user.id,
147 | message_id=call.message.message_id,
148 | parse_mode='HTML'
149 | )
150 | bot.register_next_step_handler(msg, add_account_get_tenant_id)
151 |
152 |
153 | def add_account_get_tenant_id(m):
154 | try:
155 | tenant_id = re.search(r'[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', m.text).group()
156 |
157 | az_token = Token(tenant_id)
158 | user_code_info = az_token.get_user_code_info()
159 |
160 | msg = bot.send_message(
161 | text='租户 (Tenant) ID 获取成功\n\n'
162 | '请打开 '
163 | f'此链接'
164 | f' ,输入授权码 {user_code_info["user_code"]}
'
165 | f'并登录刚才的 Azure 账号,授权码将在 {user_code_info["expires_in"]} 秒后过期,如已登录请等待刷新...',
166 | chat_id=m.from_user.id,
167 | parse_mode='HTML'
168 | )
169 |
170 | sleep(5)
171 |
172 | token = az_token.get(user_code_info)
173 |
174 | if token['tenantId'] == tenant_id:
175 | t = f'{token["userId"]}
添加成功'
176 | RefreshToken().save(token["userId"], token['refreshToken'])
177 | start(m)
178 |
179 | else:
180 | t = '账号添加失败,租户 (Tenant) ID 不一致'
181 |
182 | bot.edit_message_text(
183 | text=t,
184 | chat_id=m.from_user.id,
185 | message_id=msg.message_id,
186 | parse_mode='HTML'
187 | )
188 |
189 | except AttributeError:
190 | bot.send_message(
191 | text='租户 (Tenant) ID 无效',
192 | chat_id=m.from_user.id
193 | )
194 | except Exception as e:
195 | handle_exception(e, m)
196 |
197 |
198 | def list_accounts(call, action_text, action, sub_cation):
199 | emails = RefreshToken().list()
200 |
201 | markup = types.InlineKeyboardMarkup(row_width=1)
202 | if emails:
203 | for email in emails:
204 | markup.add(
205 | types.InlineKeyboardButton(text=email, callback_data=f'{action}:{sub_cation}:{email}')
206 | )
207 |
208 | t = f'正在进行 {action_text}\n\n' \
209 | '请选择 Azure 账号:'
210 | else:
211 | t = '你还没有添加 Azure 账号'
212 |
213 | bot.edit_message_text(
214 | text=t,
215 | chat_id=call.from_user.id,
216 | message_id=call.message.message_id,
217 | reply_markup=markup,
218 | parse_mode='HTML'
219 | )
220 |
221 |
222 | def show_account_sub(call, email):
223 | t = f'已选择 {email}
\n\n'
224 | bot.edit_message_text(
225 | text=t + '获取账号订阅中...',
226 | chat_id=call.from_user.id,
227 | message_id=call.message.message_id,
228 | parse_mode='HTML'
229 | )
230 |
231 | refresh_token = RefreshToken().get(email)
232 | az_sub = Subscription(refresh_token)
233 | subs = az_sub.list()
234 | for sub in subs:
235 | t += f'订阅名称: {sub["display_name"]}
\n' \
236 | f'订阅状态: {sub["state"]}
\n\n'
237 |
238 | markup = types.InlineKeyboardMarkup()
239 | markup.add(types.InlineKeyboardButton(text='移除账号', callback_data='ma:rm:' + email))
240 | bot.edit_message_text(
241 | text=t,
242 | chat_id=call.from_user.id,
243 | message_id=call.message.message_id,
244 | reply_markup=markup,
245 | parse_mode='HTML'
246 | )
247 |
248 |
249 | def remove_account(call, email):
250 | RefreshToken().remove(email)
251 | bot.edit_message_text(
252 | text=f'{email} 已移除',
253 | chat_id=call.from_user.id,
254 | message_id=call.message.message_id
255 | )
256 |
257 |
258 | def set_refresh_token_list_sub(call, email, next_step):
259 | bot.edit_message_text(
260 | text=f'已选择 {email}
\n\n'
261 | '获取账号订阅中...',
262 | chat_id=call.from_user.id,
263 | message_id=call.message.message_id,
264 | parse_mode='HTML'
265 | )
266 |
267 | refresh_token = RefreshToken().get(email)
268 |
269 | user_dict[call.from_user.id].email = email
270 | user_dict[call.from_user.id].refresh_token = refresh_token
271 |
272 | az_sub = Subscription(refresh_token)
273 | subs = az_sub.list()
274 |
275 | if subs[0]['state'] == 'Enabled':
276 | next_step(call, subs[0]['subscription_id'])
277 | return
278 |
279 | t = f'{email}
下没有可用订阅'
280 | markup = types.InlineKeyboardMarkup(row_width=1)
281 |
282 | bot.edit_message_text(
283 | text=t,
284 | chat_id=call.from_user.id,
285 | message_id=call.message.message_id,
286 | reply_markup=markup,
287 | parse_mode='HTML'
288 | )
289 |
290 |
291 | def create_vm_set_subscription_id_list_size(call, subscription_id):
292 | user_dict[call.from_user.id].subscription_id = subscription_id
293 |
294 | markup = types.InlineKeyboardMarkup(row_width=2)
295 | buttons = []
296 | for size in VM_SIZES:
297 | buttons.append(
298 | types.InlineKeyboardButton(
299 | text=size,
300 | callback_data='cvm:csize:' + size
301 | )
302 | )
303 | markup.add(*buttons)
304 |
305 | bot.edit_message_text(
306 | text='正在进行 创建实例\n\n'
307 | '请选择实例规格:\n',
308 | chat_id=call.from_user.id,
309 | message_id=call.message.message_id,
310 | reply_markup=markup,
311 | parse_mode='HTML'
312 | )
313 |
314 |
315 | def create_vm_set_size_list_os(call, size):
316 | user_dict[call.from_user.id].size = VM_SIZES[size]
317 |
318 | markup = types.InlineKeyboardMarkup(row_width=2)
319 | buttons = []
320 | for os in VM_OS_INFOS:
321 | buttons.append(
322 | types.InlineKeyboardButton(
323 | text=os,
324 | callback_data='cvm:cos:' + os
325 | )
326 | )
327 |
328 | markup.add(*buttons)
329 |
330 | bot.edit_message_text(
331 | text='正在进行 创建实例\n\n'
332 | f'已选择规格: {size}
\n'
333 | '请选择镜像:',
334 | chat_id=call.from_user.id,
335 | message_id=call.message.message_id,
336 | reply_markup=markup,
337 | parse_mode='HTML'
338 | )
339 |
340 |
341 | def create_vm_set_os_list_location(call, os):
342 | user_dict[call.from_user.id].os_info = VM_OS_INFOS[os]
343 |
344 | markup = types.InlineKeyboardMarkup(row_width=2)
345 | buttons = []
346 | for location in VM_LOCATIONS:
347 | buttons.append(
348 | types.InlineKeyboardButton(
349 | text=location,
350 | callback_data='cvm:cl:' + location
351 | )
352 | )
353 |
354 | markup.add(*buttons)
355 |
356 | bot.edit_message_text(
357 | text='正在进行 创建实例\n\n'
358 | f'已选择规格: {user_dict[call.from_user.id].size}
\n'
359 | f'已选择镜像: {os}
\n'
360 | '请选择地区:',
361 | chat_id=call.from_user.id,
362 | message_id=call.message.message_id,
363 | reply_markup=markup,
364 | parse_mode='HTML'
365 | )
366 |
367 |
368 | def create_vm_set_location_confirm_create(call, location):
369 | user_dict[call.from_user.id].location = VM_LOCATIONS[location]
370 |
371 | markup = types.InlineKeyboardMarkup(row_width=2)
372 | markup.add(
373 | types.InlineKeyboardButton(text='确认', callback_data='cvm:ok:'),
374 | types.InlineKeyboardButton(text='取消', callback_data='cvm:cancel:')
375 | )
376 | bot.edit_message_text(
377 | text='正在进行 创建实例\n\n'
378 | f'已选择规格: {user_dict[call.from_user.id].size}
\n'
379 | f'已选择镜像: {user_dict[call.from_user.id].os_info["os"]}
\n'
380 | f'已选择地区: {location}
\n'
381 | '确认创建实例 ?',
382 | chat_id=call.from_user.id,
383 | message_id=call.message.message_id,
384 | reply_markup=markup,
385 | parse_mode='HTML'
386 | )
387 |
388 |
389 | def create_vm(call):
390 | bot.edit_message_text(
391 | text='正在进行 创建实例\n\n'
392 | '创建资源组...',
393 | chat_id=call.from_user.id,
394 | message_id=call.message.message_id,
395 | parse_mode='HTML'
396 | )
397 |
398 | refresh_token = user_dict[call.from_user.id].refresh_token
399 | subscription_id = user_dict[call.from_user.id].subscription_id
400 | size = user_dict[call.from_user.id].size
401 | os_info = user_dict[call.from_user.id].os_info
402 | location = user_dict[call.from_user.id].location
403 |
404 | az_rg = ResourceGroup(refresh_token, subscription_id)
405 | rgn = az_rg.create(location=location)
406 |
407 | bot.edit_message_text(
408 | text='正在进行 创建实例\n\n'
409 | '创建虚拟网络..',
410 | chat_id=call.from_user.id,
411 | message_id=call.message.message_id,
412 | parse_mode='HTML'
413 | )
414 | az_nic = Network(refresh_token, subscription_id, rgn)
415 | az_nic.create_virtual_network(location)
416 | subnet_id = az_nic.create_subnet()
417 |
418 | bot.edit_message_text(
419 | text='正在进行 创建实例\n\n'
420 | '分配动态 IP...',
421 | chat_id=call.from_user.id,
422 | message_id=call.message.message_id,
423 | parse_mode='HTML'
424 | )
425 | public_ip_id = az_nic.create_public_ip(location)
426 |
427 | bot.edit_message_text(
428 | text='正在进行 创建实例\n\n'
429 | '创建安全组...',
430 | chat_id=call.from_user.id,
431 | message_id=call.message.message_id,
432 | parse_mode='HTML'
433 | )
434 | nsg_id = az_nic.create_network_security_group(location)
435 | az_nic.security_rules_allow_all()
436 |
437 | bot.edit_message_text(
438 | text='正在进行 创建实例\n\n'
439 | '更新网络接口...',
440 | chat_id=call.from_user.id,
441 | message_id=call.message.message_id,
442 | parse_mode='HTML'
443 | )
444 | nic_id = az_nic.create_or_update_network_interface_client(subnet_id, nsg_id, location, public_ip_id)
445 |
446 | bot.edit_message_text(
447 | text='正在进行 创建实例\n\n'
448 | '部署实例中........\n',
449 | chat_id=call.from_user.id,
450 | message_id=call.message.message_id,
451 | parse_mode='HTML'
452 | )
453 | az_vm = VirtualMachine(refresh_token, subscription_id, rgn)
454 | vm_info = az_vm.create(size, os_info, location, nic_id)
455 | public_ip = az_nic.get_public_ip()
456 | vm_info.update({
457 | 'ip': public_ip
458 | })
459 |
460 | bot.edit_message_text(
461 | text='正在进行 创建实例\n\n'
462 | '创建实例完成\n'
463 | f'名称: {vm_info["vm_name"]}
\n'
464 | f'IP: {vm_info["ip"]}
\n'
465 | f'用户名: {vm_info["username"]}
\n'
466 | f'密码: {vm_info["password"]}
\n'
467 | f'镜像: {vm_info["os"]}
\n'
468 | f'规格: {vm_info["size"]}
\n'
469 | f'地区: {vm_info["location"]}
\n'
470 | f'创建时间: {vm_info["time"]}
\n',
471 | chat_id=call.from_user.id,
472 | message_id=call.message.message_id,
473 | parse_mode='HTML'
474 | )
475 |
476 |
477 | def manage_vm_set_subscription_id_list_vm(call, subscription_id):
478 | user_dict[call.from_user.id].subscription_id = subscription_id
479 | refresh_token = user_dict[call.from_user.id].refresh_token
480 | email = user_dict[call.from_user.id].email
481 |
482 | bot.edit_message_text(
483 | text=f'已选择 {email}
\n\n'
484 | '获取实例中...',
485 | chat_id=call.from_user.id,
486 | message_id=call.message.message_id,
487 | parse_mode='HTML'
488 | )
489 |
490 | az_vm = VirtualMachine(refresh_token, subscription_id)
491 | vms = az_vm.list()
492 |
493 | t = f'已选择 {email}
\n\n' \
494 | '没有实例'
495 | markup = types.InlineKeyboardMarkup(row_width=1)
496 | if vms:
497 | t = '正在进行 管理实例\n' \
498 | '请选择实例:'
499 | for vm in vms:
500 | markup.add(
501 | types.InlineKeyboardButton(text=vm['name'], callback_data='mvm:gvm:' + vm['name'])
502 | )
503 |
504 | bot.edit_message_text(
505 | text=t,
506 | chat_id=call.from_user.id,
507 | message_id=call.message.message_id,
508 | reply_markup=markup,
509 | parse_mode='HTML'
510 | )
511 |
512 |
513 | def get_vm(call, vm_name):
514 | refresh_token = user_dict[call.from_user.id].refresh_token
515 | subscription_id = user_dict[call.from_user.id].subscription_id
516 |
517 | bot.edit_message_text(
518 | text='正在进行 管理实例\n\n'
519 | f'获取实例 {vm_name}
中...',
520 | chat_id=call.from_user.id,
521 | message_id=call.message.message_id,
522 | parse_mode='HTML'
523 | )
524 |
525 | vm_info = {}
526 |
527 | az_vm = VirtualMachine(refresh_token, subscription_id, vm_name.replace('-vm', '-rg'))
528 | vms = az_vm.list()
529 | for vm in vms:
530 | if vm['name'] == vm_name:
531 | vm_info = vm
532 | break
533 |
534 | vm_instance_view = az_vm.instance_view()
535 |
536 | az_nic = Network(refresh_token, subscription_id, vm_name.replace('-vm', '-rg'))
537 | public_ip = az_nic.get_public_ip()
538 |
539 | try:
540 | vm_info.update({
541 | 'os': vm_instance_view.os_name + ' ' + vm_instance_view.os_version,
542 | 'status': vm_instance_view.statuses[1].display_status,
543 | 'ip': public_ip
544 | })
545 | except TypeError:
546 | bot.edit_message_text(
547 | text='获取实例信息失败, 可能在创建或开机或删除过程中,请稍后再试',
548 | chat_id=call.from_user.id,
549 | message_id=call.message.message_id,
550 | parse_mode='HTML'
551 | )
552 | return
553 |
554 | markup = types.InlineKeyboardMarkup(row_width=2)
555 | markup.add(
556 | types.InlineKeyboardButton(text='更换 IP', callback_data='mvm:ip:' + vm_name),
557 | types.InlineKeyboardButton(text='删除实例', callback_data='mvm:del:' + vm_name),
558 | )
559 | bot.edit_message_text(
560 | text='正在进行 管理实例\n\n'
561 | f'实例信息:\n'
562 | f'名称: {vm_info["name"]}
\n'
563 | f'IP: {vm_info["ip"]}
\n'
564 | f'用户名: {vm_info["os_profile"]["admin_username"]}
\n'
565 | f'镜像: {vm_info["os"]}
\n'
566 | f'规格: {vm_info["hardware_profile"]["vm_size"]}
\n'
567 | f'地区: {vm_info["location"]}
\n'
568 | f'状态: {vm_info["status"]}
\n\n'
569 | f'请选择操作:',
570 | chat_id=call.from_user.id,
571 | message_id=call.message.message_id,
572 | reply_markup=markup,
573 | parse_mode='HTML'
574 | )
575 |
576 |
577 | def change_vm_ip(call, vm_name):
578 | refresh_token = user_dict[call.from_user.id].refresh_token
579 | subscription_id = user_dict[call.from_user.id].subscription_id
580 |
581 | bot.edit_message_text(
582 | text='正在进行 管理实例\n\n'
583 | f'更换实例 {vm_name}
IP 中...',
584 | chat_id=call.from_user.id,
585 | message_id=call.message.message_id,
586 | parse_mode='HTML'
587 | )
588 |
589 | az_nic = Network(refresh_token, subscription_id, vm_name.replace('-vm', '-rg'))
590 | nic = az_nic.get_network_interface_client()
591 | public_ip_id = nic.ip_configurations[0].public_ip_address.id
592 |
593 | az_nic.create_or_update_network_interface_client(
594 | subnet_id=nic.ip_configurations[0].subnet.id,
595 | nsg_id=nic.network_security_group.id,
596 | location=nic.location,
597 | public_ip_id=None
598 | )
599 |
600 | az_nic.create_or_update_network_interface_client(
601 | subnet_id=nic.ip_configurations[0].subnet.id,
602 | nsg_id=nic.network_security_group.id,
603 | location=nic.location,
604 | public_ip_id=public_ip_id
605 | )
606 |
607 | get_vm(call, vm_name)
608 |
609 |
610 | def delete_vm(call, vm_name):
611 | refresh_token = user_dict[call.from_user.id].refresh_token
612 | subscription_id = user_dict[call.from_user.id].subscription_id
613 |
614 | bot.edit_message_text(
615 | text='正在进行 管理 VM\n\n'
616 | f'删除实例 {vm_name}
及其资源组中...',
617 | chat_id=call.from_user.id,
618 | message_id=call.message.message_id,
619 | parse_mode='HTML'
620 | )
621 |
622 | az_rg = ResourceGroup(refresh_token, subscription_id)
623 | az_rg.delete(vm_name.replace('-vm', '-rg'))
624 |
625 | bot.edit_message_text(
626 | text='正在进行 管理 VM\n\n'
627 | f'删除实例 {vm_name}
完成',
628 | chat_id=call.from_user.id,
629 | message_id=call.message.message_id,
630 | parse_mode='HTML'
631 | )
632 |
633 |
634 | logger = telebot.logger
635 | telebot.logger.setLevel(logging.INFO)
636 |
637 | bot.polling(none_stop=True)
638 |
--------------------------------------------------------------------------------
/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "BOT": {
3 | "NAME": "Azure 小助手",
4 | "TOKEN": "",
5 | "ADMINS": []
6 | },
7 | "VM": {
8 | "DISK_SIZE_DEFAULT": 64,
9 | "LOCATIONS": {
10 | "美国中": "centralus",
11 | "美国东": "eastus",
12 | "美国西": "westus",
13 | "东亚": "eastasia",
14 | "东南亚": "southeastasia",
15 | "日本东": "japaneast",
16 | "日本西": "japanwest",
17 | "韩国中": "koreacentral",
18 | "韩国南": "koreasouth"
19 | },
20 | "SIZES": {
21 | "B1s": "Standard_B1s",
22 | "B2s": "Standard_B2s",
23 | "B4ms": "Standard_B4ms",
24 | "D2s_v3": "Standard_D2s_v3",
25 | "D4s_v3": "Standard_D4s_v3",
26 | "F1s": "Standard_F1s",
27 | "F2": "Standard_F2",
28 | "F2s": "Standard_F2s",
29 | "F4s": "Standard_F4s"
30 | },
31 | "OS_INFOS": {
32 | "Ubuntu 16.04": {
33 | "os": "Ubuntu 16.04",
34 | "image_reference": {
35 | "publisher": "Canonical",
36 | "offer": "UbuntuServer",
37 | "sku": "16.04-LTS",
38 | "version": "latest"
39 | }
40 | },
41 | "Ubuntu 18.04": {
42 | "os": "Ubuntu 18.04",
43 | "image_reference": {
44 | "publisher": "Canonical",
45 | "offer": "UbuntuServer",
46 | "sku": "18.04-LTS",
47 | "version": "latest"
48 | }
49 | },
50 | "CentOs 7.5": {
51 | "os": "CentOs 7.5",
52 | "image_reference": {
53 | "publisher": "OpenLogic",
54 | "offer": "CentOS",
55 | "sku": "7.5",
56 | "version": "latest"
57 | }
58 | },
59 | "CentOS 8.2": {
60 | "os": "CentOS 8.2",
61 | "image_reference": {
62 | "publisher": "OpenLogic",
63 | "offer": "CentOS",
64 | "sku": "8_2",
65 | "version": "latest"
66 | }
67 | },
68 | "Debian 10": {
69 | "os": "Debian 10",
70 | "image_reference": {
71 | "publisher": "debian",
72 | "offer": "debian-10",
73 | "sku": "10",
74 | "version": "latest"
75 | }
76 | },
77 | "Debian 9": {
78 | "os": "Debian 9",
79 | "image_reference": {
80 | "publisher": "credativ",
81 | "offer": "Debian",
82 | "sku": "9",
83 | "version": "latest"
84 | }
85 | }
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | user_dict = {}
4 |
5 |
6 | class UserDict:
7 |
8 | def __init__(self):
9 | self.email = None
10 | self.refresh_token = None
11 | self.subscription_id = None
12 |
13 |
14 | class RefreshToken:
15 |
16 | def __init__(self):
17 | self.base_path = os.path.join(os.getcwd(), 'rtokens')
18 | if not os.path.exists(self.base_path):
19 | os.mkdir(self.base_path)
20 |
21 | def save(self, email: str, refresh_token: str):
22 | open(os.path.join(self.base_path, email), 'w').write(refresh_token)
23 |
24 | def list(self):
25 | return os.listdir(self.base_path)
26 |
27 | def get(self, email: str):
28 | return open(os.path.join(self.base_path, email), 'r').read()
29 |
30 | def remove(self, email: str):
31 | os.remove(os.path.join(self.base_path, email))
32 |
--------------------------------------------------------------------------------