Last active
September 13, 2022 05:13
-
-
Save clubapplets-server/fef944adcb0683bde15d to your computer and use it in GitHub Desktop.
Script to backup VirtualBox's VMs on Google drive
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf8 -*- | |
# !/usr/bin/env python3 | |
""" | |
ApplETS VM Backup script automatically creates a backup of all VirtualBox | |
virtual machines, stores the backups in Google drive and | |
deletes too old backups in Google drive. Crontab it if you | |
want to make it every a definite period of time. | |
Copyright 2015 ApplETS applets@ens.etsmtl.ca | |
ApplETS VM Backup script is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
ApplETS VM Backup script is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
""" | |
import sys | |
import os | |
import re | |
import datetime | |
import httplib2 | |
import oauth2client | |
import subprocess | |
import argparse | |
import logging | |
from pathlib import Path | |
from monthdelta import monthdelta | |
from apiclient import discovery | |
from apiclient import errors | |
from oauth2client import client | |
from oauth2client import tools | |
from apiclient.http import MediaFileUpload | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args() | |
SCOPES = 'https://www.googleapis.com/auth/drive' | |
CLIENT_SECRET_FILE = 'client_secret.json' | |
APPLICATION_NAME = 'BackUp2Drive' | |
BACKUP_FOLDER_NAME = 'backup' | |
TIME_UNTIL_BACKUP_DELETION = 2 # In months | |
def insert_file(service, title, description, parent_id, mime_type, file_name): | |
"""Insert a new file in a Google drive folder. | |
:param service: Drive API service instance. | |
:param title: Title of the file to insert, including the extension. | |
:param description: Description of the file to insert. | |
:param parent_id: Parent folder's ID. | |
:param mime_type: MIME type of the file to insert. | |
:param file_name: Filename of the file to insert. | |
:return: Inserted file metadata if successful, None otherwise. | |
""" | |
logger.info("Inserting " + title + " in Google drive") | |
media_body = MediaFileUpload(file_name, mimetype=mime_type, resumable=True) | |
body = { | |
'title': title, | |
'description': description, | |
'mimeType': mime_type | |
} | |
# Set the parent folder. | |
if parent_id: | |
body['parents'] = [{'id': parent_id}] | |
try: | |
file = service.files().insert( | |
body=body, | |
media_body=media_body).execute() | |
return file | |
except errors.HttpError as error: | |
logger.error('An error occured: %s' % error) | |
return None | |
def get_credentials(): | |
"""Gets valid user credentials from storage. | |
If nothing has been stored, or if the stored credentials are invalid, | |
the OAuth2 flow is completed to obtain the new credentials. | |
:return:Credentials, the obtained credential. | |
""" | |
home_dir = Path(os.path.expanduser('~')) | |
credential_dir = home_dir / '.credentials' | |
if not credential_dir.exists(): | |
credential_dir.mkdir(0o755) | |
credential_path = str(credential_dir / 'backup-credentials.json') | |
store = oauth2client.file.Storage(credential_path) | |
credentials = store.get() | |
if not credentials or credentials.invalid: | |
flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES) | |
flow.user_agent = APPLICATION_NAME | |
if flags: | |
credentials = tools.run_flow(flow, store, flags) | |
else: # Needed only for compatability with Python 2.6 | |
credentials = tools.run(flow, store) | |
logger.info('Storing credentials to ' + credential_path) | |
return credentials | |
def delete_file(service, file): | |
"""Permanently delete a file, skipping the trash. | |
:param service: Drive API service instance | |
:param file_id: ID of the file to delete | |
""" | |
logger.info("Deleting " + file['title'] + "...") | |
try: | |
service.files().delete(fileId=file['id']).execute() | |
except errors.HttpError as error: | |
logger.error('An error occurred: %s' % error) | |
def clean_old_backup_on_drive(service, backup_folder, time_until_backup_deletion): | |
""" Deletes too old files on Google drive according to the date indicated in file name | |
:param service: Drive API service instance | |
:param backup_folder: Folder where backups are stored | |
:param time_until_backup_deletion: Number of months to keep files | |
""" | |
# Regular expression to extract date from a file name | |
pattern = re.compile(r"(\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2})") | |
results = service.files().list(q="'" + backup_folder['id'] + "' in parents").execute() | |
files = results.get('items', []) | |
for file in files: | |
match = pattern.search(file['title']) | |
if match: | |
date_string = match.group(1) | |
date = datetime.datetime.strptime(date_string, '%Y-%m-%d_%H:%M:%S') | |
if date < (date.today() - monthdelta(time_until_backup_deletion)): | |
delete_file(service, file) | |
def get_folder_from_name(service, folder_name): | |
"""Returns folder object situated on Google Drive | |
:param service: Drive API service instance | |
:param folder_name: Name of a folder on Google drive | |
:return: Folder object | |
""" | |
results = service.files().list(q="title = '" + folder_name + "'").execute() | |
if not results.get('items', []): | |
logger.error("Backup folder name couldn't be resolved.") | |
sys.exit(1) | |
# Returns backup folder | |
return results.get('items', [])[0] | |
def main(): | |
""" | |
Backup strategy for virtualbox's virtual machines | |
""" | |
credentials = get_credentials() | |
http = credentials.authorize(httplib2.Http()) | |
service = discovery.build('drive', 'v2', http=http) | |
backup_folder = get_folder_from_name(service, BACKUP_FOLDER_NAME) | |
clean_old_backup_on_drive(service, backup_folder, TIME_UNTIL_BACKUP_DELETION) | |
# For backup all VMs | |
output = subprocess.check_output(["vboxmanage", "list", "vms"]) | |
list_vms = re.findall(r'"([^"]*)"', str(output)) | |
for vm in list_vms: | |
file_name = vm + "-" + str(datetime.datetime.now()) + ".ova" | |
subprocess.call(["vboxmanage", "controlvm", vm, "poweroff"]) | |
subprocess.call(["vboxmanage", "export", vm, "-o", file_name]) | |
subprocess.call(["vboxmanage", "startvm", vm, "--type", "headless"]) | |
insert_file(service, file_name, "Backup VM", | |
backup_folder['id'], | |
"application/octet-stream", | |
file_name) | |
subprocess.call(["rm", file_name]) | |
sys.exit(0) | |
if __name__ == '__main__': | |
main() | |
__author__ = 'gnut3ll4' | |
__date__ = '2015-07-019' | |
__copyright__ = "Copyright 2015, ApplETS VM Backup script" | |
__credits__ = ["Thibaut Tauveron", "Charles Levesque"] | |
__license__ = "GPL" | |
__version__ = "1.0.1" | |
__email__ = "applets@ens.etsmtl.ca" | |
__status__ = "Production" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment