backup-service/backup.py

106 lines
4.1 KiB
Python
Executable File

#!/usr/bin/env python3
import base64
import os
from kubernetes import client, config
from kubernetes.client.api_client import ApiClient
from subprocess import Popen, PIPE
from flask import Flask, request, send_file
from urllib.parse import urlparse
import logging
import useful.logs
useful.logs.setup(json_fields={"msg": "message", "level": "levelname"})
logger = logging.getLogger()
TOKEN = os.environ["TOKEN"]
app = Flask(__name__)
if os.getenv("KUBECONFIG"):
config.load_kube_config()
else:
config.load_incluster_config()
def generate_targets():
with ApiClient() as api:
v1 = client.CoreV1Api(api)
api_instance = client.CustomObjectsApi(api)
for i in v1.list_namespace().items:
# Handle MySQL operator
targets = api_instance.list_namespaced_custom_object(
"mysql.oracle.com",
"v2",
i.metadata.name,
"innodbclusters")
for target in targets["items"]:
yield "innodbclusters", i.metadata.name, "-", target["metadata"]["name"]
# Handle MongoDB community operator
targets = api_instance.list_namespaced_custom_object(
"mongodbcommunity.mongodb.com",
"v1",
i.metadata.name,
"mongodbcommunity")
for target in targets["items"]:
for user in target["spec"]["users"]:
for role in user["roles"]:
yield "mongodbcommunity", i.metadata.name, \
user["name"], role["db"]
break
@app.route("/cronjob.sh")
def generate_script():
if request.headers.get("Authorization") != TOKEN:
raise
base = urlparse(request.base_url)._replace(path="", params="", query="", fragment="").geturl()
def generate():
for z in generate_targets():
path = "/stream/%s/%s/%s/%s\n" % z
yield "wget --content-disposition --header \"Authorization: %s\" %s%s" % (TOKEN, base, path)
return app.response_class(generate(), mimetype="text/plain")
@app.route("/stream/innodbclusters/<namespace>/<user>/<database>")
def stream_innodbclusters(namespace, user, database):
if request.headers.get("Authorization") != TOKEN:
raise
with ApiClient() as api:
v1 = client.CoreV1Api(api)
secret_name = "%s-backup" % (database)
secret = v1.read_namespaced_secret(secret_name, namespace)
backup_username = base64.b64decode(secret.data["backupUsername"]).decode("ascii")
backup_password = base64.b64decode(secret.data["backupPassword"]).decode("ascii")
cmd = "/usr/bin/mysqldump", "--all-databases", "-u", backup_username, \
"--password=%s" % backup_password, \
"-h", "%s.%s.svc.cluster.local" % (database, namespace)
logger.info("Executing: %s", " ".join(cmd))
process = Popen(cmd, stdout=PIPE, stdin=PIPE, close_fds=True, bufsize=4096 * 1024)
gzip = Popen(["/usr/bin/gzip", "-9"], stdout=PIPE, stdin=process.stdout, close_fds=True, bufsize=4096 * 1024)
return send_file(gzip.stdout,
mimetype="application/gzip",
as_attachment=True,
download_name="%s_%s.sql.gz" % (namespace, database))
@app.route("/stream/mongodbcommunity/<namespace>/<user>/<database>")
def stream_mongodbcommunity(namespace, user, database):
if request.headers.get("Authorization") != TOKEN:
raise
with ApiClient() as api:
v1 = client.CoreV1Api(api)
secret_name = "mongodb-%s-%s" % (database, user)
secret = v1.read_namespaced_secret(secret_name, namespace)
uri = base64.b64decode(secret.data["connectionString.standard"]).decode("ascii")
cmd = "/usr/bin/mongodump", "--uri", uri, "--gzip", "--archive", "--quiet"
logger.info("Executing: %s", " ".join(cmd))
process = Popen(cmd, stdout=PIPE, stdin=PIPE, close_fds=True, bufsize=4096 * 1024)
return send_file(process.stdout,
mimetype="application/tar+gzip",
as_attachment=True,
download_name="%s_%s.gz" % (namespace, database))
app.run(host="0.0.0.0", debug=False, threaded=True)