#!/usr/bin/python3

## THIS SCRIPT IS NEED TO BE TESTED


# Need version python version 3.6 or greater!

##########################################################

# Import required python libraries

import os
import os.path
import subprocess
import time
import sys
import datetime
import pipes
import argparse
import logging

from subprocess import PIPE, run

# using logging module for dry run
logging.basicConfig(level=logging.INFO)

dry_run = False

# Funtions

# Function to exit with error message
def exit_with_error(message, exit_value = 1):
    sys.stderr.write(F'{message}\n')
    sys.exit(exit_value)

# Function to run shell commands
def command(arg):
    result = run(arg, stdout=PIPE, stderr=None, universal_newlines=True, shell=True)
    return result.stdout

# It checks for the existence of the password file.  If the password
# file does not exist it exits from the program with an error. If the
# password _does_ exist, then simply return. The external script that does
# the excryption will extract the password.
def check_encrypt_password_file(password_file):
    # Check whether password file is accessible or not
    try:
        with open(password_file):
            pass
    except IOError:
            exit_with_error("default password file '%s' not accessible" % (password_file), 4)

def dry_run_message(message):
    if (dry_run):
        logging.info(F'DRY RUN: {message}')

# Funtion to backup to GCS
def backup_to_gcs(args):

    src_dir = args.source_directory

    gcs_bucket = args.gcs_bucket

    date = command("date +%Y%m%d%H%M")

    gcs_dir = args.gcs_bucket_dir

    gcs_key_file = args.gcs_key_file

    gcs_filename = args.gcs_backup_filename

    bucket_file = os.path.join(gcs_bucket, gcs_dir, gcs_filename)
    #bucket_file = os.path.join(gcs_bucket, gcs_dir, gcs_filename) + '.' + date

    gsutil_args = "-o GSUtil:parallel_process_count=8 -o GSUtil:parallel_thread_count=1"

    tar_cmd = F'/usr/bin/tar -P --exclude=".*" -czf'

    gcs_auth = F'gcloud --no-user-output-enabled auth activate-service-account --quiet --key-file={gcs_key_file}'

    gcs_cp = F'gsutil {gsutil_args} -q cp -p -'

    if args.encrypt:
        password_file = args.encrypt_password_file
        dry_run_message(F'password file is {password_file}')
        encrypt_cmd = F'/usr/bin/encrypt-file -e -p {password_file}'
        # ENCRYPT
        check_encrypt_password_file(args.encrypt_password_file)
        bucket_file = F'{bucket_file}.encrypted.{date}'
        dry_run_message(F'''Backup Stanford specific repositories and encrypt to GCS bucket {bucket_file}''')

        cmd = F'{tar_cmd} - {src_dir} | {encrypt_cmd} | {gcs_cp} {bucket_file}'
    else:
        # DO NOT ENCRYPT
        dry_run_message(F'Backup Stanford specific repositories (but do not encrypt) to GCS bucket {bucket_file}')

        cmd = F'{tar_cmd} - {src_dir} | {gcs_cp} {bucket_file}.{date}'

    dry_run_message(F'about to run command "{cmd}"')
    if (dry_run):
        pass
    else:
        command(gcs_auth)
        command(cmd)

## Arguments

parser = argparse.ArgumentParser(description='Script to backup Stanford specific repositories')

parser.add_argument('--dry-run',
                    help='No-op, show what would happen but do not backup anything',
                    action='store_true')

parser.add_argument('-e',
                    '--encrypt',
                    action='store_true',
                    help='encrypt file; requires the "-p" option')

parser.add_argument('-p',
                    '--encrypt_password_file',
                    action='store',
                    help="uses first line of file as encryption password")

parser.add_argument('-k',
                    '--gcs_key_file',
                    action='store',
                    help="uses first line of file as encryption password")

parser.add_argument('-b',
                    '--gcs_bucket',
                    action='store',
                    help='gcs bucket to store backups; requires the "-d" option')

parser.add_argument('-f',
                    '--gcs_backup_filename',
                    action='store',
                    default='backup.tar.gz',
                    help='GCS backup filename')

parser.add_argument('-d',
                    '--gcs_bucket_dir',
                    action='store',
                    help='gcs bucket directory')

parser.add_argument('-s',
                    '--source_directory',
                    action='store',
                    required=True,
                    help="directory to store backups")


args = parser.parse_args()

# Print help if script is called without any arguments
if len(sys.argv)==1:
    parser.print_help(sys.stderr)
    sys.exit(1)

# Check gcs bucket arguments
if args.gcs_bucket and args.gcs_bucket_dir:
    pass
elif args.gcs_bucket and not args.gcs_bucket_dir:
    exit_with_error("'-b/--gcs_bucket' requires '-d/--gcs_bucket-dir' option", 1)
elif args.gcs_bucket_dir and not args.gcs_bucket:
    exit_with_error("'-d/--gcs_bucket_dir' requires '-b/--gcs_bucket' option", 2)
else:
    exit_with_error("Required options -b (bucket) and -d (bucket_dir)", 3)

if args.gcs_key_file:
    # Check the config file exists
    if os.path.exists(args.gcs_key_file):
       pass
    else:
       exit_with_error("GCS Key file does not exist")
else:
    exit_with_error("Required GCS key file to authenticate with google", 4)

# Check encrpt arguments
if args.encrypt and args.encrypt_password_file:
    pass
elif args.encrypt and not args.encrypt_password_file:
    exit_with_error("'-e/--encrypt' requires '-p/--encrypt-password-file' option", 1)
elif args.encrypt_password_file and not args.encrypt:
    exit_with_error("'-p/--encrypt-password-file' requires '-e/--encrypt' option", 2)
else:
    pass

dry_run = args.dry_run

backup_to_gcs(args)

# Documentation.  Use a hack to hide this from the shell.  Because of the
# above exit line, this should never be executed.
DOCS=<<__END_OF_DOCS__

=head1 NAME

backup_directory_to_gcs - Script to encrypt and backup directory to GCS

=head1 SYNOPSIS

backup_directory_to_gcs [B<-sdfbkpe>]

=head1 DESCRIPTION

B<backup_directory_to_gcs> Script used to compress, encrypt the file or directory
and backup google cloud storage

=head1 OPTIONS

=over 8

=item B<--dry-run>

show what would happen but do not backup anything (noop).

=item B<-e>, B<--encrypt>,

encrypt file; requires the "-p" option.

=item B<-b>, B<--gcs_bucket>

gcs bucket to store backups; requires the "-d" option.

=item B<-f>, B<--gcs_backup_filename>

gcs backup filename

=item B<-d>, B<--gcs_bucket_dir>

gcs bucket directory

=item B<-s>, B<--source_directory>

directory to store backups

=item B<-p>, B<--encrypt_password_file>

uses first line of file as encryption password

=item B<-k>, B<--gcs_key_file>

uses first line of file as encryption password.

=back

=head1 AUTHOR

Srinivas Rao Puttagunta <psr123@stanford.edu>

=cut

__END_OF_DOCS__
