#! /usr/bin/python

""" Print commands to recreate users, volumes and ACL on a new SX cluster  """

VERSION = "%(prog)s 1.2"
import argparse
import sys
parser = argparse.ArgumentParser(
    formatter_class=argparse.RawDescriptionHelpFormatter,
    description=__doc__,
    epilog="Running this tool will generate two scripts:\n"
    " sx-backup.sh: backs up sx://cluster to BACKUPDIR\n"
    " sx-restore.sh: recreates users,volumes,acls restores BACKUPDIR\n"
)
parser.add_argument('-V', '--version', action='version', version=VERSION)
parser.add_argument('-b', "--backup-dir", required=True,
                    help="destination directory for cluster backup")
parser.add_argument("cluster", metavar="sx://[profile@]cluster",
                    help="specifies the cluster to backup")
args = parser.parse_args()

SXLS = 'sxls'
SXACL = 'sxacl'
SXVOL = 'sxvol'
SXCP = 'sxcp'

OLD = 'sx-backup.sh'
NEW = 'sx-restore.sh'
BACKUPDIR = args.backup_dir

from subprocess import CalledProcessError, Popen, PIPE
import sys
import pipes
import re
import os
import stat


def check_output(cmd):
    """ like subprocess.check_output, for python2.6 compatibility """
    process = Popen(cmd, stdout=PIPE)
    output = process.communicate()[0]
    retcode = process.poll()
    if retcode:
        raise CalledProcessError(retcode, cmd, output=output)
    return output


def try_subcommands(main, subcmds):
    """ Try to guess which subcommand the main command supports """
    for subcmd in subcmds:
        try:
            cmdhelp = check_output([main, subcmd, "--help"])
            if "Usage: %s %s" % (SXACL, subcmd) in cmdhelp:
                return subcmd
        except CalledProcessError:
            continue
    print >> sys.stderr, "Unknown sxacl command version"
    sys.exit(2)


def get_version(cmd):
    """ Retrieve version number of a tool """
    try:
        return check_output([cmd, "-V"]).split(" ")[1]
    except OSError as exc:
        print >> sys.stderr, "Failed to execute '%s': %s" % (cmd, exc)
        sys.exit(1)


def check_versions(commands):
    """ Ensure that all the commands are the same version """
    versions = set([get_version(cmd) for cmd in commands])
    if len(versions) > 1:
        print >> sys.stderr, "Tool version mismatch: ", list(versions)
        sys.exit(1)

check_versions([SXLS, SXACL, SXVOL, SXCP])
SXACL_LIST = try_subcommands(SXACL, ['volshow', 'show', 'list'])


def print_shell(dest, lst):
    """ Print properly quoted shell commands """
    dest.write(" ".join([pipes.quote(s) for s in lst]) + "\n")


def print_progress(dest, msg):
    """ Echo a message separated by newlines """
    print_shell(dest, ["echo"])
    print_shell(dest, ["echo", msg])
    print_shell(dest, ["echo"])


def create_users(sxurl):
    """ Print commands to create SX users """
    users = check_output([SXACL, "userlist", sxurl]).rstrip().split('\n')
    print_progress(new, "Creating users")
    for user_role in [s.rsplit(" (", 1) for s in users]:
        user, role = user_role
        if user == 'admin':
            continue
        role = role.rsplit(")", 1)[0]
        token = check_output([SXACL, "usergetkey", user, sxurl]).strip()
        print_shell(new, [SXACL, "useradd", "--role", role, user, sxurl,
                          "--force-key", token])


def list_acl(sxurl):
    """ Retrieve the ACL of a volume """
    lines = check_output([SXACL, SXACL_LIST, sxurl]).rstrip().split('\n')
    owner = None
    read_acl = []
    write_acl = []
    admin_acl = []
    firstuser = None
    for line in lines:
        user, allprivs = line.split(": ", 1)
        privs = allprivs.split(" ")
        if firstuser is None:
            firstuser = user
        if 'owner' in privs:
            owner = user
        if 'admin' in privs:
            admin_acl.append(user)
        if 'read' in privs:
            read_acl.append(user)
        if 'write' in privs:
            write_acl.append(user)
    # if owner is admin old sx versions don't always list owner priv
    if owner is None:
        owner = firstuser
    return owner, read_acl, write_acl, admin_acl


def create_volumes(sxurl):
    """ Print commands to create SX volumes and set ACLs """
    print_shell(new, ["date"])
    lines = check_output([SXLS, "-l", sxurl]).rstrip().split('\n')
    pattern = re.compile(r"^ *VOL +(r(ep)?:)?(?P<replica>\d+) +"
                         r"(rev:(?P<rev>\d+))? *"
                         r"(r?w?) *"
                         r"(?P<filter>[^ ]+)? +"
                         r"(\d+ +)?"  # used size, not present in old
                         r"(?P<size>\d+) +"
                         r"(\d+% +)?"  # use percentage, not present in old
                         r" *([^ ]+)? *"  # ignore owner, we get it from acl
                         r"(?P<url>sx://.+)$")
    volumes = []
    print_shell(new, ["echo", "Creating volumes"])
    for line in lines:
        if line == "":
            continue
        match = pattern.search(line)
        if match is None:
            print >> sys.stderr, "Cannot parse sxls output: " + line
            continue
        url = match.group('url')
        owner, read_acl, write_acl, admin_acl = list_acl(url)
        cmd = [SXVOL, "create", "--owner", owner,
               "--replica", match.group('replica'),
               "--size", match.group('size'), url]
        volfilter = match.group('filter')
        volrev = match.group('rev')
        if volfilter == 'aes256':
            print >> sys.stderr, ("\nWarning: "
                                  "Skipping encrypted volume '%s'\n" % url)
            continue
        if volfilter == '*unknown*':
            print >> sys.stderr, ("\nWarning: "
                                  "Skipping volume with unknown filter '%s'\n"
                                  % url)
            continue
        vol = url.rsplit("/", 1)[1]
        if vol.startswith("libres3-"):
            print >> sys.stderr, ("Skipping "
                                  "LibreS3 temporary volume '%s'" % url)
            continue
        volumes.append(url)
        if vol == '_sxsetup.conf':
            print >> sys.stderr, ("Skipping "
                                  "sxsetup configuration volume '%s'" % vol)
            continue
        if volfilter is None:
            print >> sys.stderr, ("Warning: cannot determine what filters"
                                  " are used by the volume '%s'"
                                  " (SX version too old)" % url)
        if volrev is None:
            print >> sys.stderr, ("cannot determine volume revision count"
                                  " for volume '%s'"
                                  " (SX version too old)" % url)

        if volfilter != '-' and volfilter is not None:
            cmd.extend(["--filter", volfilter])
        if volrev is not None:
            cmd.extend(["--max-revisions", volrev])
        print_shell(new, cmd)

        for user in read_acl:
            if user == owner or user in admin_acl or user == 'admin':
                continue
            print_shell(new, [SXACL, "volperm", "--grant=read", user, url])
        for user in write_acl:
            if user == owner or user in admin_acl or user == 'admin':
                continue
            print_shell(new, [SXACL, "volperm", "--grant=write", user, url])
        if owner not in read_acl:
            print_shell(new, [SXACL, "volperm", "--revoke=read", owner, url])
        if owner not in write_acl:
            print_shell(new, [SXACL, "volperm", "--revoke=write", owner, url])
        new.write("\n")
    print_shell(new, ["echo", "Restoring data"])
    for volurl in volumes:
        vol = volurl.rsplit("/", 1)[1]
        print_progress(old, "Backing up volume %s" % volurl)
        localdir = "%s/%s" % (BACKUPDIR, vol)
        print_shell(old, ["mkdir", "-m", "0700", "-p", localdir])
        print_shell(old, [SXCP, "-r", volurl, localdir])
        if vol == '_sxsetup.conf':
            print >> sys.stderr, ("Skipping "
                                  "sxsetup configuration volume '%s'" % vol)
            continue
        print_progress(new, "Restoring volume %s" % volurl)
        print_shell(new, [SXCP, "-r", localdir + "/", volurl + "/"])
    print_shell(new, ["date"])

os.umask(0o77)
with open(OLD, 'w') as old:
    with open(NEW, 'w') as new:
        print "Generating %s and %s" % (OLD, NEW)
        new.write("#! /bin/sh\n"
                  "set -e\n")
        old.write("#! /bin/sh\n"
                  "set -e\n")
        print_shell(old, ["mkdir", "-m", "0700", "-p", BACKUPDIR])
        create_users(args.cluster)
        create_volumes(args.cluster)
        os.chmod(OLD, stat.S_IRWXU)
        os.chmod(NEW, stat.S_IRWXU)
        print "Review %s (uncomment/edit paths as necessary)" % OLD
        print "Run %s on the old cluster" % OLD
        print "Review %s (uncomment/edit paths as necessary)" % NEW
        print "Stop old cluster"
        print "Use sxsetup --config-file to setup the new cluster"
        print "Run %s on the new cluster" % NEW
