FabricΒΆ

Sample tasks:

@task
def db_backup():
    """
    Backup the database to S3 just like the nightly cron job
    """
    require('environment')
    require('instance', provided_by='instance')
    manage_run("dbbackup --encrypt")


def db_exists(dbname):
    """
    Return True if a db named DBNAME exists on the remote host.
    """
    require('environment', provided_by=SERVER_ENVIRONMENTS)
    output = sudo('psql -l --pset=format=unaligned', user='postgres')
    dbnames = [line.split('|')[0] for line in output.splitlines()]
    return dbname in dbnames


@task
def db_dump(file):
    """
    Dump an instance's database to a remote file.

    Example:

      `fab staging instance:iraq db_dump:/tmp/staging_iraq.dump`

    dumps to staging_iraq.dump
    """
    require('environment', provided_by=SERVER_ENVIRONMENTS)
    require('instance', provided_by='instance')
    remote_file = file

    if files.exists(file):
        if not confirm("Remote file {file} exists and will be overwritten.  Okay?"
                .format(file=remote_file)):
            abort("ERROR: aborting")

    # Don't need remote DB user and password because we're going to run pg_dump as user postgres
    sudo('pg_dump --format=custom --file={outputfile} {dbname}'
         .format(dbname=env.db_name, outputfile=remote_file),
         user='postgres')
    print("Database from {environment} {instance} has been dumped to remote file {file}"
          .format(environment=env.environment, instance=env.instance, file=remote_file))


@task
def local_restore(file):
    """
    Restore a local dump file to the local instance's database.
    :param file:
    :return:
    """
    # Find out the local DB settings
    import sys
    sys.path[0:0] = ['.']
    from cts.settings.local import DATABASES
    DB = DATABASES['default']
    assert DB['ENGINE'] == 'django.contrib.gis.db.backends.postgis'
    dbname = DB['NAME']
    owner = DB['USER'] or os.getenv('USER')
    local('dropdb {dbname} || true'.format(dbname=dbname), shell="/bin/sh")
    local('createdb --encoding UTF8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8 --template=template0 --owner {owner} {dbname}'.format(owner=owner, dbname=dbname))
    local('sudo -u postgres pg_restore -Ox -j4 --dbname={dbname} {file}'.format(dbname=dbname, file=file))


@task
def db_restore(file):
    """
    Restore a remote DB dump file to a remote instance's database.

    This will rename the existing database to {previous_name}_bak
    and create a completely new database with what's in the dump.

    If there's already a backup database, the restore will fail.

    Example:

      `fab staging instance:iraq db_restore:/tmp/staging_iraq.dump`

    :param file: The remote file to restore.
    """
    require('environment', provided_by=SERVER_ENVIRONMENTS)
    require('instance', provided_by='instance')

    renamed = False
    restored = False

    if not files.exists(file):
        abort("Remote file {file} does not exist".format(file=file))

    try:
        if db_exists(env.db_name):
            # Rename existing DB to backup
            db_backup = '{dbname}_bak'.format(dbname=env.db_name)
            if db_exists(db_backup):
                if confirm("There's already a database named {db_backup}. Replace with new backup?"
                        .format(db_backup=db_backup)):
                    sudo('dropdb {db_backup}'.format(db_backup=db_backup),
                         user='postgres')
                else:
                    abort("ERROR: There's already a database named {db_backup}. "
                          "Restoring would clobber it."
                          .format(db_backup=db_backup))
            sudo('psql -c "ALTER DATABASE {dbname} RENAME TO {db_backup}"'
                 .format(dbname=env.db_name, db_backup=db_backup),
                 user='postgres')
            renamed = True
            print("Renamed {dbname} to {db_backup}".format(dbname=env.db_name, db_backup=db_backup))

        remote_file = file

        # Create new, very empty database.
        # * We can't use --create on the pg_restore because that will always restore to whatever
        #   db name was saved in the dump file, and we don't want to be restricted that way.
        # * Any extensions the backed-up database had will be included in the restore, so we
        #   don't need to enable them now.

        # If these parameters change, also change the parameters in conf/salt/project/db/init.sls
        # (TODO: we could use the output of psql -l to copy most of these settings from the
        # existing database.)
        sudo('createdb --encoding UTF8 --lc-collate=en_US.UTF-8 '
             '--lc-ctype=en_US.UTF-8 --template=template0 --owner {owner} {dbname}'
             .format(dbname=env.db_name, owner=env.db_owner),
             user='postgres')

        # Don't need remote DB user and password because we're going to
        # run pg_restore as user postgres
        sudo('pg_restore -1 --dbname={dbname} {filename}'
             .format(dbname=env.db_name, filename=remote_file),
             user='postgres')
        restored = True

        # Run ANALYZE on the db to help Postgres optimize how it accesses it
        sudo('psql {dbname} -c ANALYZE'.format(dbname=env.db_name),
             user='postgres')

        print("Database for {environment} {instance} has been restored from remote file {file}"
              .format(environment=env.environment, instance=env.instance, file=remote_file))
    finally:
        if renamed and not restored:
            print("Error occurred after renaming current database, trying to rename it back")
            if db_exists(env.db_name):
                # We already created the new db, but restore failed; delete it
                sudo('dropdb {dbname}'.format(dbname=env.dbname), user='postgres')
            sudo('psql -c "ALTER DATABASE {db_backup} RENAME TO {dbname}"'
                 .format(dbname=env.db_name, db_backup=db_backup),
                 user='postgres')
            print("Successfully put back the original database.")