blob: 34f9b06cc8770fb06bd9bf3f693d4adba4a6bb09 [file] [log] [blame]
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01001from fastapi import FastAPI, Response, Request
2import aiodocker
3import psutil
4import sys
5import re
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +01006import time
7import os
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +01008import json
9import asyncio
10import redis
11from datetime import datetime
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010012
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010013
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010014containerIds_to_update = []
15host_stats_isUpdating = False
16app = FastAPI()
17
18
19@app.get("/host/stats")
20async def get_host_update_stats():
21 global host_stats_isUpdating
22
23 if host_stats_isUpdating == False:
24 print("start host stats task")
25 asyncio.create_task(get_host_stats())
26 host_stats_isUpdating = True
27
28 while True:
29 if redis_client.exists('host_stats'):
30 break
31 print("wait for host_stats results")
32 await asyncio.sleep(1.5)
33
34
35 print("host stats pulled")
36 stats = json.loads(redis_client.get('host_stats'))
37 return Response(content=json.dumps(stats, indent=4), media_type="application/json")
38
39@app.get("/containers/{container_id}/json")
40async def get_container(container_id : str):
41 if container_id and container_id.isalnum():
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010042 try:
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010043 for container in (await async_docker_client.containers.list()):
44 if container._id == container_id:
45 container_info = await container.show()
46 return Response(content=json.dumps(container_info, indent=4), media_type="application/json")
47
48 res = {
49 "type": "danger",
50 "msg": "no container found"
51 }
52 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010053 except Exception as e:
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010054 res = {
55 "type": "danger",
56 "msg": str(e)
57 }
58 return Response(content=json.dumps(res, indent=4), media_type="application/json")
59 else:
60 res = {
61 "type": "danger",
62 "msg": "no or invalid id defined"
63 }
64 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010065
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010066@app.get("/containers/json")
67async def get_containers():
68 containers = {}
69 try:
70 for container in (await async_docker_client.containers.list()):
71 container_info = await container.show()
72 containers.update({container_info['Id']: container_info})
73 return Response(content=json.dumps(containers, indent=4), media_type="application/json")
74 except Exception as e:
75 res = {
76 "type": "danger",
77 "msg": str(e)
78 }
79 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010080
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010081@app.post("/containers/{container_id}/{post_action}")
82async def post_containers(container_id : str, post_action : str, request: Request):
83 try :
84 request_json = await request.json()
85 except Exception as err:
86 request_json = {}
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +010087
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010088 if container_id and container_id.isalnum() and post_action:
89 try:
90 """Dispatch container_post api call"""
91 if post_action == 'exec':
92 if not request_json or not 'cmd' in request_json:
93 res = {
94 "type": "danger",
95 "msg": "cmd is missing"
96 }
97 return Response(content=json.dumps(res, indent=4), media_type="application/json")
98 if not request_json or not 'task' in request_json:
99 res = {
100 "type": "danger",
101 "msg": "task is missing"
102 }
103 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100104
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100105 api_call_method_name = '__'.join(['container_post', str(post_action), str(request_json['cmd']), str(request_json['task']) ])
106 else:
107 api_call_method_name = '__'.join(['container_post', str(post_action) ])
108
109 docker_utils = DockerUtils(async_docker_client)
110 api_call_method = getattr(docker_utils, api_call_method_name, lambda container_id: Response(content=json.dumps({'type': 'danger', 'msg':'container_post - unknown api call' }, indent=4), media_type="application/json"))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100111
112
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100113 print("api call: %s, container_id: %s" % (api_call_method_name, container_id))
114 return await api_call_method(container_id, request_json)
115 except Exception as e:
116 print("error - container_post: %s" % str(e))
117 res = {
118 "type": "danger",
119 "msg": str(e)
120 }
121 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100122
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100123 else:
124 res = {
125 "type": "danger",
126 "msg": "invalid container id or missing action"
127 }
128 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100129
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100130@app.post("/container/{container_id}/stats/update")
131async def post_container_update_stats(container_id : str):
132 global containerIds_to_update
133
134 # start update task for container if no task is running
135 if container_id not in containerIds_to_update:
136 asyncio.create_task(get_container_stats(container_id))
137 containerIds_to_update.append(container_id)
138
139 while True:
140 if redis_client.exists(container_id + '_stats'):
141 break
142 await asyncio.sleep(1.5)
143
144 stats = json.loads(redis_client.get(container_id + '_stats'))
145 return Response(content=json.dumps(stats, indent=4), media_type="application/json")
146
147
148
149
150class DockerUtils:
151 def __init__(self, docker_client):
152 self.docker_client = docker_client
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100153
154 # api call: container_post - post_action: stop
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100155 async def container_post__stop(self, container_id, request_json):
156 for container in (await self.docker_client.containers.list()):
157 if container._id == container_id:
158 await container.stop()
159 res = {
160 'type': 'success',
161 'msg': 'command completed successfully'
162 }
163 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100164
165 # api call: container_post - post_action: start
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100166 async def container_post__start(self, container_id, request_json):
167 for container in (await self.docker_client.containers.list()):
168 if container._id == container_id:
169 await container.start()
170 res = {
171 'type': 'success',
172 'msg': 'command completed successfully'
173 }
174 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100175
176
177 # api call: container_post - post_action: restart
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100178 async def container_post__restart(self, container_id, request_json):
179 for container in (await self.docker_client.containers.list()):
180 if container._id == container_id:
181 await container.restart()
182 res = {
183 'type': 'success',
184 'msg': 'command completed successfully'
185 }
186 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100187
188
189 # api call: container_post - post_action: top
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100190 async def container_post__top(self, container_id, request_json):
191 for container in (await self.docker_client.containers.list()):
192 if container._id == container_id:
193 ps_exec = await container.exec("ps")
194 async with ps_exec.start(detach=False) as stream:
195 ps_return = await stream.read_out()
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100196
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100197 exec_details = await ps_exec.inspect()
198 if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
199 res = {
200 'type': 'success',
201 'msg': ps_return.data.decode('utf-8')
202 }
203 return Response(content=json.dumps(res, indent=4), media_type="application/json")
204 else:
205 res = {
206 'type': 'danger',
207 'msg': ''
208 }
209 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100210
211
212 # api call: container_post - post_action: exec - cmd: mailq - task: delete
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100213 async def container_post__exec__mailq__delete(self, container_id, request_json):
214 if 'items' in request_json:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100215 r = re.compile("^[0-9a-fA-F]+$")
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100216 filtered_qids = filter(r.match, request_json['items'])
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100217 if filtered_qids:
218 flagged_qids = ['-d %s' % i for i in filtered_qids]
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100219 sanitized_string = str(' '.join(flagged_qids))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100220
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100221 for container in (await self.docker_client.containers.list()):
222 if container._id == container_id:
223 postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
224 return await exec_run_handler('generic', postsuper_r_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100225
226 # api call: container_post - post_action: exec - cmd: mailq - task: hold
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100227 async def container_post__exec__mailq__hold(self, container_id, request_json):
228 if 'items' in request_json:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100229 r = re.compile("^[0-9a-fA-F]+$")
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100230 filtered_qids = filter(r.match, request_json['items'])
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100231 if filtered_qids:
232 flagged_qids = ['-h %s' % i for i in filtered_qids]
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100233 sanitized_string = str(' '.join(flagged_qids))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100234
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100235 for container in (await self.docker_client.containers.list()):
236 if container._id == container_id:
237 postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
238 return await exec_run_handler('generic', postsuper_r_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100239
240 # api call: container_post - post_action: exec - cmd: mailq - task: cat
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100241 async def container_post__exec__mailq__cat(self, container_id, request_json):
242 if 'items' in request_json:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100243 r = re.compile("^[0-9a-fA-F]+$")
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100244 filtered_qids = filter(r.match, request_json['items'])
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100245 if filtered_qids:
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100246 sanitized_string = str(' '.join(filtered_qids))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100247
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100248 for container in (await self.docker_client.containers.list()):
249 if container._id == container_id:
250 postcat_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postcat -q " + sanitized_string], user='postfix')
251 return await exec_run_handler('utf8_text_only', postcat_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100252
253 # api call: container_post - post_action: exec - cmd: mailq - task: unhold
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100254 async def container_post__exec__mailq__unhold(self, container_id, request_json):
255 if 'items' in request_json:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100256 r = re.compile("^[0-9a-fA-F]+$")
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100257 filtered_qids = filter(r.match, request_json['items'])
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100258 if filtered_qids:
259 flagged_qids = ['-H %s' % i for i in filtered_qids]
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100260 sanitized_string = str(' '.join(flagged_qids))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100261
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100262 for container in (await self.docker_client.containers.list()):
263 if container._id == container_id:
264 postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postsuper " + sanitized_string])
265 return await exec_run_handler('generic', postsuper_r_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100266
267
268 # api call: container_post - post_action: exec - cmd: mailq - task: deliver
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100269 async def container_post__exec__mailq__deliver(self, container_id, request_json):
270 if 'items' in request_json:
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100271 r = re.compile("^[0-9a-fA-F]+$")
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100272 filtered_qids = filter(r.match, request_json['items'])
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100273 if filtered_qids:
274 flagged_qids = ['-i %s' % i for i in filtered_qids]
275
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100276 for container in (await self.docker_client.containers.list()):
277 if container._id == container_id:
278 for i in flagged_qids:
279 postsuper_r_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postqueue " + i], user='postfix')
280 async with postsuper_r_exec.start(detach=False) as stream:
281 postsuper_r_return = await stream.read_out()
282 # todo: check each exit code
283 res = {
284 'type': 'success',
285 'msg': 'Scheduled immediate delivery'
286 }
287 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100288
289
290 # api call: container_post - post_action: exec - cmd: mailq - task: list
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100291 async def container_post__exec__mailq__list(self, container_id, request_json):
292 for container in (await self.docker_client.containers.list()):
293 if container._id == container_id:
294 mailq_exec = await container.exec(["/usr/sbin/postqueue", "-j"], user='postfix')
295 return await exec_run_handler('utf8_text_only', mailq_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100296
297
298 # api call: container_post - post_action: exec - cmd: mailq - task: flush
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100299 async def container_post__exec__mailq__flush(self, container_id, request_json):
300 for container in (await self.docker_client.containers.list()):
301 if container._id == container_id:
302 postsuper_r_exec = await container.exec(["/usr/sbin/postqueue", "-f"], user='postfix')
303 return await exec_run_handler('generic', postsuper_r_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100304
305
306 # api call: container_post - post_action: exec - cmd: mailq - task: super_delete
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100307 async def container_post__exec__mailq__super_delete(self, container_id, request_json):
308 for container in (await self.docker_client.containers.list()):
309 if container._id == container_id:
310 postsuper_r_exec = await container.exec(["/usr/sbin/postsuper", "-d", "ALL"])
311 return await exec_run_handler('generic', postsuper_r_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100312
313
314 # api call: container_post - post_action: exec - cmd: system - task: fts_rescan
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100315 async def container_post__exec__system__fts_rescan(self, container_id, request_json):
316 if 'username' in request_json:
317 for container in (await self.docker_client.containers.list()):
318 if container._id == container_id:
319 rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -u '" + request_json['username'].replace("'", "'\\''") + "'"], user='vmail')
320 async with rescan_exec.start(detach=False) as stream:
321 rescan_return = await stream.read_out()
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100322
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100323 exec_details = await rescan_exec.inspect()
324 if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
325 res = {
326 'type': 'success',
327 'msg': 'fts_rescan: rescan triggered'
328 }
329 return Response(content=json.dumps(res, indent=4), media_type="application/json")
330 else:
331 res = {
332 'type': 'warning',
333 'msg': 'fts_rescan error'
334 }
335 return Response(content=json.dumps(res, indent=4), media_type="application/json")
336
337 if 'all' in request_json:
338 for container in (await self.docker_client.containers.list()):
339 if container._id == container_id:
340 rescan_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm fts rescan -A"], user='vmail')
341 async with rescan_exec.start(detach=False) as stream:
342 rescan_return = await stream.read_out()
343
344 exec_details = await rescan_exec.inspect()
345 if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
346 res = {
347 'type': 'success',
348 'msg': 'fts_rescan: rescan triggered'
349 }
350 return Response(content=json.dumps(res, indent=4), media_type="application/json")
351 else:
352 res = {
353 'type': 'warning',
354 'msg': 'fts_rescan error'
355 }
356 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100357
358
359 # api call: container_post - post_action: exec - cmd: system - task: df
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100360 async def container_post__exec__system__df(self, container_id, request_json):
361 if 'dir' in request_json:
362 for container in (await self.docker_client.containers.list()):
363 if container._id == container_id:
364 df_exec = await container.exec(["/bin/bash", "-c", "/bin/df -H '" + request_json['dir'].replace("'", "'\\''") + "' | /usr/bin/tail -n1 | /usr/bin/tr -s [:blank:] | /usr/bin/tr ' ' ','"], user='nobody')
365 async with df_exec.start(detach=False) as stream:
366 df_return = await stream.read_out()
367
368 print(df_return)
369 print(await df_exec.inspect())
370 exec_details = await df_exec.inspect()
371 if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
372 return df_return.data.decode('utf-8').rstrip()
373 else:
374 return "0,0,0,0,0,0"
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100375
376
377 # api call: container_post - post_action: exec - cmd: system - task: mysql_upgrade
378 def container_post__exec__system__mysql_upgrade(self, container_id):
Matthias Andreas Benkardc55bfae2021-01-02 07:35:21 +0100379 return jsonify(type='success', msg='mysql_upgrade: not touching fake MySQL', text='')
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100380
381 # api call: container_post - post_action: exec - cmd: system - task: mysql_tzinfo_to_sql
382 def container_post__exec__system__mysql_tzinfo_to_sql(self, container_id):
Matthias Andreas Benkardc55bfae2021-01-02 07:35:21 +0100383 return jsonify(type='success', msg='mysql_tzinfo_to_sql: not touching fake MySQL', text='')
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100384
385 # api call: container_post - post_action: exec - cmd: reload - task: dovecot
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100386 async def container_post__exec__reload__dovecot(self, container_id, request_json):
387 for container in (await self.docker_client.containers.list()):
388 if container._id == container_id:
389 reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/dovecot reload"])
390 return await exec_run_handler('generic', reload_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100391
392
393 # api call: container_post - post_action: exec - cmd: reload - task: postfix
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100394 async def container_post__exec__reload__postfix(self, container_id, request_json):
395 for container in (await self.docker_client.containers.list()):
396 if container._id == container_id:
397 reload_exec = await container.exec(["/bin/bash", "-c", "/usr/sbin/postfix reload"])
398 return await exec_run_handler('generic', reload_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100399
400
401 # api call: container_post - post_action: exec - cmd: reload - task: nginx
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100402 async def container_post__exec__reload__nginx(self, container_id, request_json):
403 for container in (await self.docker_client.containers.list()):
404 if container._id == container_id:
405 reload_exec = await container.exec(["/bin/sh", "-c", "/usr/sbin/nginx -s reload"])
406 return await exec_run_handler('generic', reload_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100407
408
409 # api call: container_post - post_action: exec - cmd: sieve - task: list
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100410 async def container_post__exec__sieve__list(self, container_id, request_json):
411 if 'username' in request_json:
412 for container in (await self.docker_client.containers.list()):
413 if container._id == container_id:
414 sieve_exec = await container.exec(["/bin/bash", "-c", "/usr/bin/doveadm sieve list -u '" + request_json['username'].replace("'", "'\\''") + "'"])
415 return await exec_run_handler('utf8_text_only', sieve_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100416
417
418 # api call: container_post - post_action: exec - cmd: sieve - task: print
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100419 async def container_post__exec__sieve__print(self, container_id, request_json):
420 if 'username' in request_json and 'script_name' in request_json:
421 for container in (await self.docker_client.containers.list()):
422 if container._id == container_id:
423 cmd = ["/bin/bash", "-c", "/usr/bin/doveadm sieve get -u '" + request_json['username'].replace("'", "'\\''") + "' '" + request_json['script_name'].replace("'", "'\\''") + "'"]
424 sieve_exec = await container.exec(cmd)
425 return await exec_run_handler('utf8_text_only', sieve_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100426
427
428 # api call: container_post - post_action: exec - cmd: maildir - task: cleanup
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100429 async def container_post__exec__maildir__cleanup(self, container_id, request_json):
430 if 'maildir' in request_json:
431 for container in (await self.docker_client.containers.list()):
432 if container._id == container_id:
433 sane_name = re.sub(r'\W+', '', request_json['maildir'])
434 cmd = ["/bin/bash", "-c", "if [[ -d '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' ]]; then /bin/mv '/var/vmail/" + request_json['maildir'].replace("'", "'\\''") + "' '/var/vmail/_garbage/" + str(int(time.time())) + "_" + sane_name + "'; fi"]
435 maildir_cleanup_exec = await container.exec(cmd, user='vmail')
436 return await exec_run_handler('generic', maildir_cleanup_exec)
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100437
438 # api call: container_post - post_action: exec - cmd: rspamd - task: worker_password
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100439 async def container_post__exec__rspamd__worker_password(self, container_id, request_json):
440 if 'raw' in request_json:
441 for container in (await self.docker_client.containers.list()):
442 if container._id == container_id:
443
444 cmd = "./set_worker_password.sh '" + request_json['raw'].replace("'", "'\\''") + "' 2> /dev/null"
445 rspamd_password_exec = await container.exec(cmd, user='_rspamd')
446 async with rspamd_password_exec.start(detach=False) as stream:
447 rspamd_password_return = await stream.read_out()
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100448
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100449 matched = False
450 if "OK" in rspamd_password_return.data.decode('utf-8'):
451 matched = True
452 await container.restart()
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100453
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100454 if matched:
455 res = {
456 'type': 'success',
457 'msg': 'command completed successfully'
458 }
459 return Response(content=json.dumps(res, indent=4), media_type="application/json")
460 else:
461 res = {
462 'type': 'danger',
463 'msg': 'command did not complete'
464 }
465 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100466
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100467
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100468
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100469async def exec_run_handler(type, exec_obj):
470 async with exec_obj.start(detach=False) as stream:
471 exec_return = await stream.read_out()
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100472
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100473 if exec_return == None:
474 exec_return = ""
475 else:
476 exec_return = exec_return.data.decode('utf-8')
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100477
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100478 if type == 'generic':
479 exec_details = await exec_obj.inspect()
480 if exec_details["ExitCode"] == None or exec_details["ExitCode"] == 0:
481 res = {
482 "type": "success",
483 "msg": "command completed successfully"
484 }
485 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100486 else:
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100487 res = {
488 "type": "success",
489 "msg": "'command failed: " + exec_return
490 }
491 return Response(content=json.dumps(res, indent=4), media_type="application/json")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100492 if type == 'utf8_text_only':
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100493 return Response(content=exec_return, media_type="text/plain")
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100494
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100495async def get_host_stats(wait=5):
496 global host_stats_isUpdating
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100497
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100498 try:
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100499 system_time = datetime.now()
500 host_stats = {
501 "cpu": {
502 "cores": psutil.cpu_count(),
503 "usage": psutil.cpu_percent()
504 },
505 "memory": {
506 "total": psutil.virtual_memory().total,
507 "usage": psutil.virtual_memory().percent,
508 "swap": psutil.swap_memory()
509 },
510 "uptime": time.time() - psutil.boot_time(),
511 "system_time": system_time.strftime("%d.%m.%Y %H:%M:%S")
512 }
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100513
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100514 redis_client.set('host_stats', json.dumps(host_stats), ex=10)
515 except Exception as e:
516 res = {
517 "type": "danger",
518 "msg": str(e)
519 }
520 print(json.dumps(res, indent=4))
Matthias Andreas Benkardb382b102021-01-02 15:32:21 +0100521
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100522 await asyncio.sleep(wait)
523 host_stats_isUpdating = False
524
525
526async def get_container_stats(container_id, wait=5, stop=False):
527 global containerIds_to_update
528
529 if container_id and container_id.isalnum():
530 try:
531 for container in (await async_docker_client.containers.list()):
532 if container._id == container_id:
533 res = await container.stats(stream=False)
534
535 if redis_client.exists(container_id + '_stats'):
536 stats = json.loads(redis_client.get(container_id + '_stats'))
537 else:
538 stats = []
539 stats.append(res[0])
540 if len(stats) > 3:
541 del stats[0]
542 redis_client.set(container_id + '_stats', json.dumps(stats), ex=60)
543 except Exception as e:
544 res = {
545 "type": "danger",
546 "msg": str(e)
547 }
548 print(json.dumps(res, indent=4))
549 else:
550 res = {
551 "type": "danger",
552 "msg": "no or invalid id defined"
553 }
554 print(json.dumps(res, indent=4))
555
556 await asyncio.sleep(wait)
557 if stop == True:
558 # update task was called second time, stop
559 containerIds_to_update.remove(container_id)
560 else:
561 # call update task a second time
562 await get_container_stats(container_id, wait=0, stop=True)
563
564
565if os.environ['REDIS_SLAVEOF_IP'] != "":
566 redis_client = redis.Redis(host=os.environ['REDIS_SLAVEOF_IP'], port=os.environ['REDIS_SLAVEOF_PORT'], db=0)
567else:
568 redis_client = redis.Redis(host='redis-mailcow', port=6379, db=0)
569
570async_docker_client = aiodocker.Docker(url='unix:///var/run/docker.sock')