Skip to content

Commit a9190a2

Browse files
committed
Added a server which manually starts database dumping on acess
1 parent 262fd75 commit a9190a2

6 files changed

Lines changed: 106 additions & 11 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
.idea/
1+
.idea/
2+
.vscode/

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ Ready to use simple example how to [backup SQL Database wtih this script](https:
88
To use this image you need to setup all of it's environment values. If one of it's values are missing, empty or of a wrong type, it will print out a message to log to let you know.
99

1010
Environment values meaning:
11-
- TEST: defaults to False, if set to True doesn't start the scheduler instead waits 30 seconds and runs database dump
11+
- TEST: defaults to False, if set to True doesn't start the scheduler or server and instead waits 30 seconds and runs database dump
1212
- CRON: crontab syntax that is used to determine when to backup database, if you're new to crontab or unsure you can use [crontab.guru](crontab.guru) website;
13+
- START_SERVER: defaults to True, if set to True starts a server which you can access to manually start database dumping
14+
- SERVER_PORT: default to 33399
15+
- SERVER_BASIC_AUTH_USER: default to admin; username for server basic auth
16+
- SERVER_BASIC_AUTH_PASSWORD: default to admin; password for server basic auth
1317
- DATABASE_TYPE: type of database you want to backup, available values [MySQL, PostgreSQL]
1418
- DATABASE_HOST: hostname or ip address to database
1519
- DATABASE_NAME: scheme name to backup

src/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ RUN apk add --no-cache postgresql-client mysql-client
55
RUN python -m pip install apscheduler boto3 slack_sdk
66

77
COPY scheduler.py /scheduler/scheduler.py
8+
COPY server.py /scheduler/server.py
89
COPY logging.ini /scheduler/logging.ini
910

1011
ENV PYTHONUNBUFFERED=TRUE

src/scheduler.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
from slack_sdk.webhook import WebhookClient
66

77
from apscheduler.triggers.cron import CronTrigger
8-
from apscheduler.schedulers.background import BlockingScheduler
8+
from apscheduler.schedulers.background import BackgroundScheduler
9+
10+
from server import AuthServer
11+
import time
12+
import random
913

1014
logging.config.fileConfig('logging.ini')
1115
logger = logging.getLogger('ddg_scheduler')
@@ -19,10 +23,17 @@ def sizeof_fmt(num):
1923
return "%.1f%s" % (num, 'PiB')
2024

2125

22-
def send_slack_message(environment, message, success=True):
26+
def send_slack_message(environment, message, type='SUCCESS'):
2327
webhook_url = environment['SLACK_WEBHOOK']
2428
project_name = environment['PROJECT_NAME']
2529

30+
color_map = {
31+
'SUCCESS': '#36a64f',
32+
'FAIL': '#ee2700',
33+
'OTHER': '#FFCC00'
34+
}
35+
fallback_color = '#808080'
36+
2637
logger.info(message)
2738

2839
if project_name and len(project_name) > 0:
@@ -35,7 +46,7 @@ def send_slack_message(environment, message, success=True):
3546
response = client.send(
3647
attachments=[
3748
{
38-
"color": "#36a64f" if success else "#ee2700",
49+
"color": color_map.get(type, fallback_color),
3950
"blocks": [
4051
{
4152
"type": "header",
@@ -73,6 +84,10 @@ def get_env():
7384
env_variables = {
7485
'TEST': {'type': bool, 'required': False, 'default': False},
7586
'CRON': {'type': str},
87+
'START_SERVER': {'type': bool, 'required': False, 'default': True},
88+
'SERVER_PORT': {'type': int, 'required': False, 'default': 33399},
89+
'SERVER_BASIC_AUTH_USER': {'type': str, 'required': False, 'default': 'admin'},
90+
'SERVER_BASIC_AUTH_PASSWORD': {'type': str, 'required': False, 'default': 'admin'},
7691
'DATABASE_TYPE': {'type': str, 'possible_values': ['postgresql', 'mysql']},
7792
'DATABASE_HOST': {'type': str},
7893
'DATABASE_NAME': {'type': str},
@@ -147,7 +162,7 @@ def dump_database():
147162

148163
if wait_exit_status != 0 or exit_status != 0:
149164
logger.error("RETURN CODE OF DUMP PROCESS != 0. CHECK OUTPUT ABOVE FOR ERRORS!")
150-
send_slack_message(environment, "Failed to create DB dump. Please check the error in the container logs.", False)
165+
send_slack_message(environment, "Failed to create DB dump. Please check the error in the container logs.", 'FAIL')
151166
else:
152167
file_size = 0
153168

@@ -171,22 +186,30 @@ def dump_database():
171186
send_slack_message(environment, f"Successfully created and uploaded DB dump ({sizeof_fmt(file_size)}).")
172187
except Exception as e:
173188
logger.exception(e)
174-
send_slack_message(environment, f"Failed to upload DB dump ({sizeof_fmt(file_size)}) to AWS Glacier. Please check the error in the container logs.", False)
189+
send_slack_message(environment, f"Failed to upload DB dump ({sizeof_fmt(file_size)}) to AWS Glacier. Please check the error in the container logs.", 'FAIL')
175190
else:
176191
logger.error(f'Database of type {database_type} is not supported. If you see this message something went horribly wrong.')
177192

178193

179194
if __name__ == "__main__":
180195
environment = get_env()
181196

182-
print(f"|{os.getenv('SLACK_WEBHOOK')}|")
183-
184197
if environment.get('TEST'):
185198
import time
186199
time.sleep(10)
187200
dump_database()
188201
time.sleep(300)
189202
else:
190-
dumper_scheduler = BlockingScheduler()
191-
dumper_scheduler.add_job(dump_database, CronTrigger.from_crontab(os.getenv('CRON')))
203+
dumper_scheduler = BackgroundScheduler()
204+
dumper_scheduler.add_job(dump_database, CronTrigger.from_crontab(environment.get('CRON')))
192205
dumper_scheduler.start()
206+
207+
if environment.get('START_SERVER'):
208+
def on_get():
209+
send_slack_message(environment, 'Backup triggered from server', 'OTHER')
210+
dump_database()
211+
212+
server = AuthServer(('', environment.get('SERVER_PORT')))
213+
server.set_auth(environment.get('SERVER_BASIC_AUTH_USER'), environment.get('SERVER_BASIC_AUTH_PASSWORD'))
214+
server.set_on_get(on_get)
215+
server.serve_forever()

src/server.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import http.server
2+
import base64
3+
import json
4+
5+
6+
class AuthServerHandler(http.server.BaseHTTPRequestHandler):
7+
8+
def do_AUTHHEAD(self):
9+
self.send_response(401)
10+
self.send_header(
11+
'WWW-Authenticate', 'Basic realm="Basic Realm"')
12+
self.send_header('Content-type', 'application/json')
13+
self.end_headers()
14+
15+
def do_GET(self):
16+
key = self.server.auth_key
17+
18+
''' Present frontpage with user authentication. '''
19+
if self.headers.get('Authorization') == None:
20+
self.do_AUTHHEAD()
21+
22+
response = {
23+
'success': False,
24+
'error': 'No auth header received'
25+
}
26+
27+
self.wfile.write(bytes(json.dumps(response), 'utf-8'))
28+
29+
elif self.headers.get('Authorization') == 'Basic ' + str(key):
30+
self.send_response(200)
31+
self.send_header('Content-type', 'application/json')
32+
self.end_headers()
33+
34+
self.server.on_get()
35+
36+
self.wfile.write(b'Hello')
37+
else:
38+
self.do_AUTHHEAD()
39+
40+
response = {
41+
'success': False,
42+
'error': 'Invalid credentials'
43+
}
44+
45+
self.wfile.write(bytes(json.dumps(response), 'utf-8'))
46+
47+
48+
class AuthServer(http.server.HTTPServer):
49+
auth_key = ''
50+
on_get = lambda: None
51+
52+
def __init__(self, address):
53+
super().__init__(address, AuthServerHandler)
54+
55+
def set_auth(self, username, password):
56+
self.auth_key = base64.b64encode(
57+
bytes('%s:%s' % (username, password), 'utf-8')).decode('ascii')
58+
59+
def set_on_get(self, on_get):
60+
self.on_get = on_get
61+
62+
def serve_forever(self, poll_interval=0.5):
63+
print(f'Starting server on {self.server_port}')
64+
super().serve_forever(poll_interval=poll_interval)

test/mysql_compose.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ services:
1313

1414
mysql_dumper:
1515
build: ../src
16+
ports:
17+
- 33399:33399
1618
environment:
1719
TEST: "true"
1820
CRON: "* * * * *"

0 commit comments

Comments
 (0)