150 lines
3.9 KiB
Python
150 lines
3.9 KiB
Python
from pathlib import Path
|
|
from datetime import datetime
|
|
import requests
|
|
import os
|
|
|
|
|
|
class Spec:
|
|
# Base class has no skeleton
|
|
__SKELETON = {}
|
|
|
|
def __init__(self, name, destination, limit, title, events=None):
|
|
self.name = name
|
|
self.destination = Path(destination)
|
|
self.limit = limit
|
|
self.title = title
|
|
self.events = [] if events is None else events
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"name": self.name,
|
|
"destination": str(self.destination),
|
|
"limit": self.limit,
|
|
"notify": {
|
|
"title": self.title,
|
|
"events": self.events
|
|
}
|
|
}
|
|
|
|
def backup(self):
|
|
raise NotImplementedError()
|
|
|
|
def remove_redundant(self):
|
|
tarballs = sorted(self.destination.glob('*.tar.gz'),
|
|
key=os.path.getmtime, reverse=True)
|
|
|
|
if len(tarballs) >= self.limit:
|
|
for path in tarballs[self.limit - 1:]:
|
|
path.unlink()
|
|
|
|
def notify(self, status_code):
|
|
if status_code:
|
|
if "failure" not in self.events:
|
|
return
|
|
|
|
message = "backup for {} failed.".format(self.name)
|
|
|
|
else:
|
|
if "success" not in self.events:
|
|
return
|
|
|
|
message = "backup for {} succeeded.".format(self.name)
|
|
|
|
# Read API key from env vars
|
|
try:
|
|
key = os.environ["IFTTT_API_KEY"]
|
|
|
|
# Don't send notification if there's not API key defined
|
|
except KeyError:
|
|
return
|
|
|
|
url = "https://maker.ifttt.com/trigger/{}/with/key/{}".format(
|
|
"phone_notifications",
|
|
key
|
|
)
|
|
|
|
data = {
|
|
"value1": self.title,
|
|
"value2": message
|
|
}
|
|
|
|
requests.post(url, data=data)
|
|
|
|
def get_filename(self):
|
|
return '{}_{}.tar.gz'.format(
|
|
self.name,
|
|
datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
|
|
)
|
|
|
|
@staticmethod
|
|
def from_dict(name, data) -> "Specification":
|
|
if data.get("volume", False):
|
|
return VolumeSpec.from_dict(name, data)
|
|
|
|
return DirSpec.from_dict(name, data)
|
|
|
|
@staticmethod
|
|
def from_file(path: str):
|
|
with open(path, 'r') as yaml_file:
|
|
data = yaml.load(yaml_file, Loader=yaml.Loader)
|
|
|
|
return [Spec.from_dict(name, info)
|
|
for name, info in data["specs"].items()]
|
|
|
|
|
|
class DirSpec(Spec):
|
|
def __init__(self, name, source, destination, limit, title, events=None):
|
|
super().__init__(name, destination, limit, title, events)
|
|
|
|
self.source = Path(source)
|
|
|
|
def backup(self):
|
|
self.remove_redundant()
|
|
|
|
status_code = os.system(
|
|
"tar -C '{}' -czf '{}' -- .".format(
|
|
self.source,
|
|
self.destination / self.get_filename()
|
|
)
|
|
)
|
|
|
|
self.notify(status_code)
|
|
|
|
@staticmethod
|
|
def from_dict(name, data):
|
|
return DirSpec(
|
|
name,
|
|
data["source"],
|
|
data["destination"],
|
|
data["limit"],
|
|
data["notify"]["title"],
|
|
data["notify"]["events"]
|
|
)
|
|
|
|
class VolumeSpec(Spec):
|
|
def __init__(self, name, volume, destination, limit, title, events=None):
|
|
super().__init__(name, destination, limit, title, events)
|
|
|
|
self.volume = volume
|
|
|
|
def backup(self):
|
|
status_code = os.system(
|
|
"docker run --rm -v '{}:/from' -v '{}:/to' alpine:latest "
|
|
"tar -C /from -czf '/to/{}' -- .".format(
|
|
self.volume,
|
|
self.destination,
|
|
self.get_filename()
|
|
)
|
|
)
|
|
|
|
@staticmethod
|
|
def from_dict(name, data):
|
|
return VolumeSpec(
|
|
name,
|
|
data["source"],
|
|
data["destination"],
|
|
data["limit"],
|
|
data["notify"]["title"],
|
|
data["notify"]["events"]
|
|
)
|