2020-03-26 18:58:50 +00:00
import os
import datetime
import tarfile
import hashlib
import frappe
import boto3
from frappe . utils import get_sites , random_string
2020-07-10 17:45:36 +00:00
from frappe . installer import (
make_conf ,
get_conf_params ,
make_site_dirs ,
update_site_config
)
2020-08-04 10:11:58 +00:00
from constants import COMMON_SITE_CONFIG_FILE , DATE_FORMAT , RDS_DB , RDS_PRIVILEGES
2020-07-10 17:45:36 +00:00
from utils import (
run_command ,
list_directories ,
set_key_in_site_config ,
get_site_config ,
get_config ,
get_password ,
check_s3_environment_variables ,
)
2020-03-26 18:58:50 +00:00
2020-04-28 20:15:59 +00:00
2020-03-26 18:58:50 +00:00
def get_backup_dir ( ) :
return os . path . join (
os . path . expanduser ( ' ~ ' ) ,
' backups '
)
2020-04-28 20:15:59 +00:00
2020-07-10 04:37:22 +00:00
def decompress_db ( database_file , site ) :
command = [ " gunzip " , " -c " , database_file ]
with open ( database_file . replace ( " .gz " , " " ) , " w " ) as db_file :
print ( ' Extract Database GZip for site {} ' . format ( site ) )
run_command ( command , stdout = db_file )
2020-03-26 18:58:50 +00:00
2020-04-28 20:15:59 +00:00
2020-06-25 17:53:31 +00:00
def restore_database ( files_base , site_config_path , site ) :
2020-03-27 10:37:12 +00:00
# restore database
2020-03-26 18:58:50 +00:00
database_file = files_base + ' -database.sql.gz '
2020-07-10 04:37:22 +00:00
decompress_db ( database_file , site )
2020-03-26 18:58:50 +00:00
config = get_config ( )
2020-06-29 17:58:10 +00:00
# Set db_type if it exists in backup site_config.json
set_key_in_site_config ( ' db_type ' , site , site_config_path )
# Set db_host if it exists in backup site_config.json
set_key_in_site_config ( ' db_host ' , site , site_config_path )
# Set db_port if it exists in backup site_config.json
set_key_in_site_config ( ' db_port ' , site , site_config_path )
2020-03-26 18:58:50 +00:00
2020-06-29 17:58:10 +00:00
# get updated site_config
site_config = get_site_config ( site )
2020-03-26 18:58:50 +00:00
2020-06-29 17:58:10 +00:00
# if no db_type exists, default to mariadb
db_type = site_config . get ( ' db_type ' , ' mariadb ' )
is_database_restored = False
if db_type == ' mariadb ' :
restore_mariadb (
config = config ,
site_config = site_config ,
database_file = database_file )
is_database_restored = True
elif db_type == ' postgres ' :
restore_postgres (
config = config ,
site_config = site_config ,
database_file = database_file )
is_database_restored = True
if is_database_restored :
# Set encryption_key if it exists in backup site_config.json
set_key_in_site_config ( ' encryption_key ' , site , site_config_path )
2020-03-26 18:58:50 +00:00
def restore_files ( files_base ) :
public_files = files_base + ' -files.tar '
# extract tar
public_tar = tarfile . open ( public_files )
print ( ' Extracting {} ' . format ( public_files ) )
public_tar . extractall ( )
2020-04-28 20:15:59 +00:00
2020-03-26 18:58:50 +00:00
def restore_private_files ( files_base ) :
private_files = files_base + ' -private-files.tar '
private_tar = tarfile . open ( private_files )
print ( ' Extracting {} ' . format ( private_files ) )
private_tar . extractall ( )
2020-04-28 20:15:59 +00:00
2020-03-26 18:58:50 +00:00
def pull_backup_from_s3 ( ) :
2020-07-10 17:45:36 +00:00
check_s3_environment_variables ( )
2020-03-26 18:58:50 +00:00
# https://stackoverflow.com/a/54672690
s3 = boto3 . resource (
' s3 ' ,
2020-04-26 17:18:35 +00:00
region_name = os . environ . get ( ' REGION ' ) ,
2020-03-26 18:58:50 +00:00
aws_access_key_id = os . environ . get ( ' ACCESS_KEY_ID ' ) ,
aws_secret_access_key = os . environ . get ( ' SECRET_ACCESS_KEY ' ) ,
endpoint_url = os . environ . get ( ' ENDPOINT_URL ' )
)
bucket_dir = os . environ . get ( ' BUCKET_DIR ' )
bucket_name = os . environ . get ( ' BUCKET_NAME ' )
bucket = s3 . Bucket ( bucket_name )
# Change directory to /home/frappe/backups
os . chdir ( get_backup_dir ( ) )
2020-05-22 09:45:33 +00:00
backup_files = [ ]
sites = set ( )
site_timestamps = set ( )
download_backups = [ ]
2020-04-28 20:15:59 +00:00
for obj in bucket . objects . filter ( Prefix = bucket_dir ) :
2020-10-06 15:17:56 +00:00
if obj . get ( ) [ " ContentType " ] == " application/x-directory " :
continue
2020-04-28 20:15:59 +00:00
backup_file = obj . key . replace ( os . path . join ( bucket_dir , ' ' ) , ' ' )
2020-05-22 09:45:33 +00:00
backup_files . append ( backup_file )
site_name , timestamp , backup_type = backup_file . split ( ' / ' )
site_timestamp = site_name + ' / ' + timestamp
sites . add ( site_name )
site_timestamps . add ( site_timestamp )
# sort sites for latest backups
for site in sites :
backup_timestamps = [ ]
for site_timestamp in site_timestamps :
site_name , timestamp = site_timestamp . split ( ' / ' )
if site == site_name :
timestamp_datetime = datetime . datetime . strptime (
timestamp , DATE_FORMAT
)
backup_timestamps . append ( timestamp )
download_backups . append ( site + ' / ' + max ( backup_timestamps ) )
# Only download latest backups
for backup_file in backup_files :
for backup in download_backups :
if backup in backup_file :
if not os . path . exists ( os . path . dirname ( backup_file ) ) :
os . makedirs ( os . path . dirname ( backup_file ) )
print ( ' Downloading {} ' . format ( backup_file ) )
bucket . download_file ( bucket_dir + ' / ' + backup_file , backup_file )
2020-03-26 18:58:50 +00:00
os . chdir ( os . path . join ( os . path . expanduser ( ' ~ ' ) , ' frappe-bench ' , ' sites ' ) )
2020-04-28 20:15:59 +00:00
2020-06-29 17:58:10 +00:00
def restore_postgres ( config , site_config , database_file ) :
# common config
common_site_config_path = os . path . join ( os . getcwd ( ) , COMMON_SITE_CONFIG_FILE )
db_root_user = config . get ( ' root_login ' )
if not db_root_user :
postgres_user = os . environ . get ( ' DB_ROOT_USER ' )
if not postgres_user :
print ( ' Variable DB_ROOT_USER not set ' )
exit ( 1 )
db_root_user = postgres_user
update_site_config (
" root_login " ,
db_root_user ,
validate = False ,
site_config_path = common_site_config_path )
db_root_password = config . get ( ' root_password ' )
if not db_root_password :
root_password = get_password ( ' POSTGRES_PASSWORD ' )
if not root_password :
print ( ' Variable POSTGRES_PASSWORD not set ' )
exit ( 1 )
db_root_password = root_password
update_site_config (
" root_password " ,
db_root_password ,
validate = False ,
site_config_path = common_site_config_path )
# site config
db_host = site_config . get ( ' db_host ' )
2020-06-30 01:02:46 +00:00
db_port = site_config . get ( ' db_port ' , 5432 )
2020-06-29 17:58:10 +00:00
db_name = site_config . get ( ' db_name ' )
db_password = site_config . get ( ' db_password ' )
2020-07-10 09:34:29 +00:00
psql_command = [ " psql " ]
psql_uri = f " postgres:// { db_root_user } : { db_root_password } @ { db_host } : { db_port } "
2020-06-29 17:58:10 +00:00
print ( ' Restoring PostgreSQL ' )
2020-07-10 09:34:29 +00:00
run_command ( psql_command + [ psql_uri , " -c " , f " DROP DATABASE IF EXISTS \" { db_name } \" " ] )
run_command ( psql_command + [ psql_uri , " -c " , f " DROP USER IF EXISTS { db_name } " ] )
run_command ( psql_command + [ psql_uri , " -c " , f " CREATE DATABASE \" { db_name } \" " ] )
run_command ( psql_command + [ psql_uri , " -c " , f " CREATE user { db_name } password ' { db_password } ' " ] )
run_command ( psql_command + [ psql_uri , " -c " , f " GRANT ALL PRIVILEGES ON DATABASE \" { db_name } \" TO { db_name } " ] )
2020-07-10 10:48:25 +00:00
with open ( database_file . replace ( ' .gz ' , ' ' ) , ' r ' ) as db_file :
run_command ( psql_command + [ f " { psql_uri } / { db_name } " , " < " ] , stdin = db_file )
2020-06-29 17:58:10 +00:00
def restore_mariadb ( config , site_config , database_file ) :
db_root_password = get_password ( ' MYSQL_ROOT_PASSWORD ' )
if not db_root_password :
print ( ' Variable MYSQL_ROOT_PASSWORD not set ' )
exit ( 1 )
db_root_user = os . environ . get ( " DB_ROOT_USER " , ' root ' )
2020-06-30 01:02:46 +00:00
db_host = site_config . get ( ' db_host ' , config . get ( ' db_host ' ) )
2020-07-10 13:51:59 +00:00
db_port = site_config . get ( ' db_port ' , config . get ( ' db_port ' , 3306 ) )
2020-07-09 11:31:50 +00:00
db_name = site_config . get ( ' db_name ' )
db_password = site_config . get ( ' db_password ' )
2020-06-30 01:02:46 +00:00
2020-06-29 17:58:10 +00:00
# mysql command prefix
2020-07-09 11:31:50 +00:00
mysql_command = [ " mysql " , f " -u { db_root_user } " , f " -h { db_host } " , f " -p { db_root_password } " , f " -P { db_port } " ]
2020-06-29 17:58:10 +00:00
# drop db if exists for clean restore
2020-07-10 13:51:59 +00:00
drop_database = mysql_command + [ " -e " , f " DROP DATABASE IF EXISTS ` { db_name } `; " ]
2020-07-09 11:31:50 +00:00
run_command ( drop_database )
2020-06-29 17:58:10 +00:00
# create db
2020-07-10 13:51:59 +00:00
create_database = mysql_command + [ " -e " , f " CREATE DATABASE IF NOT EXISTS ` { db_name } `; " ]
2020-07-09 11:31:50 +00:00
run_command ( create_database )
2020-06-29 17:58:10 +00:00
# create user
2020-07-10 13:51:59 +00:00
create_user = mysql_command + [ " -e " , f " CREATE USER IF NOT EXISTS ' { db_name } ' @ ' % ' IDENTIFIED BY ' { db_password } ' ; FLUSH PRIVILEGES; " ]
2020-07-09 11:31:50 +00:00
run_command ( create_user )
2020-06-29 17:58:10 +00:00
# grant db privileges to user
2020-08-04 10:11:58 +00:00
grant_privileges = " ALL PRIVILEGES "
# for Amazon RDS
if config . get ( RDS_DB ) or site_config . get ( RDS_DB ) :
grant_privileges = RDS_PRIVILEGES
grant_privileges_command = mysql_command + [ " -e " , f " GRANT { grant_privileges } ON ` { db_name } `.* TO ' { db_name } ' @ ' % ' IDENTIFIED BY ' { db_password } ' ; FLUSH PRIVILEGES; " ]
run_command ( grant_privileges_command )
2020-06-29 17:58:10 +00:00
print ( ' Restoring MariaDB ' )
2020-07-10 13:51:59 +00:00
with open ( database_file . replace ( ' .gz ' , ' ' ) , ' r ' ) as db_file :
2020-07-10 14:15:28 +00:00
run_command ( mysql_command + [ f " { db_name } " ] , stdin = db_file )
2020-06-29 17:58:10 +00:00
2020-03-26 18:58:50 +00:00
def main ( ) :
backup_dir = get_backup_dir ( )
if len ( list_directories ( backup_dir ) ) == 0 :
pull_backup_from_s3 ( )
for site in list_directories ( backup_dir ) :
2020-04-28 20:15:59 +00:00
site_slug = site . replace ( ' . ' , ' _ ' )
backups = [ datetime . datetime . strptime ( backup , DATE_FORMAT ) for backup in list_directories ( os . path . join ( backup_dir , site ) ) ]
2020-03-26 18:58:50 +00:00
latest_backup = max ( backups ) . strftime ( DATE_FORMAT )
files_base = os . path . join ( backup_dir , site , latest_backup , ' ' )
files_base + = latest_backup + ' - ' + site_slug
2020-06-25 18:12:55 +00:00
site_config_path = files_base + ' -site_config_backup.json '
if not os . path . exists ( site_config_path ) :
site_config_path = os . path . join ( backup_dir , site , ' site_config.json ' )
2020-03-26 18:58:50 +00:00
if site in get_sites ( ) :
2020-06-29 17:58:10 +00:00
print ( ' Overwrite site {} ' . format ( site ) )
2020-06-25 17:53:31 +00:00
restore_database ( files_base , site_config_path , site )
2020-03-26 18:58:50 +00:00
restore_private_files ( files_base )
restore_files ( files_base )
else :
site_config = get_conf_params (
db_name = ' _ ' + hashlib . sha1 ( site . encode ( ) ) . hexdigest ( ) [ : 16 ] ,
db_password = random_string ( 16 )
)
frappe . local . site = site
frappe . local . sites_path = os . getcwd ( )
frappe . local . site_path = os . getcwd ( ) + ' / ' + site
make_conf (
db_name = site_config . get ( ' db_name ' ) ,
2020-03-27 10:37:12 +00:00
db_password = site_config . get ( ' db_password ' ) ,
2020-03-26 18:58:50 +00:00
)
2020-03-27 10:37:12 +00:00
make_site_dirs ( )
2020-06-29 17:58:10 +00:00
print ( ' Create site {} ' . format ( site ) )
2020-06-26 00:17:17 +00:00
restore_database ( files_base , site_config_path , site )
2020-03-26 18:58:50 +00:00
restore_private_files ( files_base )
restore_files ( files_base )
2020-06-29 17:58:10 +00:00
if frappe . redis_server :
frappe . redis_server . connection_pool . disconnect ( )
2020-03-26 18:58:50 +00:00
exit ( 0 )
2020-04-28 20:15:59 +00:00
2020-03-26 18:58:50 +00:00
if __name__ == " __main__ " :
main ( )