Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@clubapplets-server
Last active September 13, 2022 05:13
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save clubapplets-server/fef944adcb0683bde15d to your computer and use it in GitHub Desktop.
Save clubapplets-server/fef944adcb0683bde15d to your computer and use it in GitHub Desktop.
Script to backup VirtualBox's VMs on Google drive
# -*- 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