Linux PurgeBackup

He encontrado esta fenomenal utilidad, creada por http://www.johnandcailin.com/. Es un script desarrollado en Python que nos permite borrar archivos en directorios pero manteniendo una política de retención de los mismos.

Me resulta muy útil para completar los scripts de backup que hago por Rsync. Yo es que no soy mucho de instalar herramientas de terceros, prefiero hacerlo con scripts (aunque no sean siempre míos). Porque no hay la herramienta perfecta y las curvas de aprendizaje me molestan mucho, cuando no son algo estándar. Por eso he descartado algunas herramientas como zmanda, bacula o duplicity. Prefiero mantener mis scripts de rsync.


#!/usr/bin/python

#
#   Copyright John Quinn, 2009
#
#   This program 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.
#
#   This program 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.
#
#   You should have received a copy of the GNU General Public License
#   along with this program.  If not, see .
#

import sys, os, getopt, time

def main(argv):
    # set default behavior
    force = False
    ages = 'unset'
    directory = 'unset'

    # process command line
    try:
       opts, args = getopt.getopt(argv, 'ha:d:f', ['help', 'ages=', 'directory=', 'force'])
       if len(args) > 0:
          print 'ingoring args: %s' % args
    except getopt.GetoptError, err:
       print str(err)
       usage()
       sys.exit(2)
    for opt, arg in opts:
       if opt in ('-h', '--help'):
           usage()
           sys.exit(2)
       elif opt in ('-d', '--directory'):
           directory = arg
       elif opt in ('-a', '--ages'):
           ages = arg
       elif opt in ('-f', '--force'):
           force = True

    # verify mandatory arguments
    if ages == 'unset':
       print 'age must be specified (the -a option)'
       usage()

    if directory == 'unset':
       print 'directory must be specified (the -d option)'
       usage()

    # purge the directory
    purge(directory, ages, force)


# print the usage message and bail
def usage():
    print 'Usage: purgeFiles [OPTION]...';
    print ' -h, --help                          Print this help message';
    print ' -a, --ages=age1,age2                Desired ages to keep (in days)';
    print ' -d, --directory=dir                 Target directory';
    print ' -f, --force                         Force deletion (no simulation mode)\n';
    print 'e.g. purgeFiles --ages=1,2,4,40 --directory=/tmp';
    print 'This would purge /tmp and try to keep a file of 40 days, 4 days, 2 days and 1 day old. '
    print 'Note: this would only do a simulation run. Specify --force to actually delete the files. '

    print '\nAuthor: John Quinn, http://johnandcailin.com/john';
    sys.exit(2)

# check for backup directory permissions etc
def checkBackupArea(directory):
    areaGood = True

    if os.path.exists(directory) == False:
        print 'filepurge: directory %s: does not exist\n' % directory
        usage()

    if os.path.isdir(directory) == False:
       print 'filepurge: %s is not a directory. please specify the directory to be purged\n' % directory
       usage()

    access = os.access(directory, os.W_OK|os.R_OK)
    if access == False:
        print 'filepurge: directory %s: not readable and writable\n' % directory
        usage()

# check our argument validity
def checkArguments(ages):
    clean, agesList = parseAgesArgument(ages)
    if clean != True:
        print 'filepurge: invalid ages argument: %s' % ages
        usage()
    return agesList


# parse ages argument and return a reverse-chronological list of desired ages in seconds from the epoch
def parseAgesArgument(ages):
    clean = True
    agesList = []
    agesStringList = ages.split(',')
    for ageString in agesStringList:
        if ageString.isdigit() != True:
            clean = False
            break
    for ageString in agesStringList:
        try:
            agesList.append(int(ageString))
        except ValueError:
            clean = False
            break

    agesList.sort(reverse=True)
    return clean, agesList

# return an age sorted directory listing (tuple list), with ages
def getSortedDirList(directory):
    dirList = os.listdir(directory)
    sortedDirList = []

    for baseName in dirList:
       fileName = os.path.join(directory,baseName)
       fileAge = os.path.getmtime(fileName)
       fileTuple = (fileAge, fileName, baseName)
       sortedDirList.append(fileTuple)
    sortedDirList.sort()

    return sortedDirList

# delete the file respecting the force mode
def deleteFile(file, force):
    baseName = file[2]
    fileName = file[1]
    if force == False:
        print '%s: Flagged for deletion' % baseName
    else:
        os.remove(fileName)
        print '%s: Deleted' % baseName

# convert an age in days to seconds from the epoch
def convertAgeToAgeEpoch(age):
    ageEpoch = int(time.time()) - (age * 24*60*60)
    return ageEpoch

# convert an age in seconds from the epoch to days
def convertAgeEpochToAge(ageEpoch):
    age = (int(time.time()) - ageEpoch) / (24*60*60)
    return age

# purge the specified directory
def purge(directory, ages, force):
    # make sure the backup directory is sound
    checkBackupArea(directory)

    # check arguments are sensible
    agesList = checkArguments(ages)

    # get the listing of the backup directory
    sortedDirList = getSortedDirList(directory)

    # go through each file in the directory and ensure that it's a keeper
    agesIterator = iter(agesList)
    currentDesiredAge = agesIterator.next()
    completedDesiredAges = False

    for i,file in enumerate(sortedDirList):
        currentDesiredAgeEpoch = convertAgeToAgeEpoch(currentDesiredAge)
        keeper = False
        fileAge = file[0]
        if i < len(sortedDirList)-1:
            nextFile = sortedDirList[i+1]
            nextFileAge = nextFile[0]

            # should we delete this file?
            if (completedDesiredAges == True) or ((fileAge < currentDesiredAgeEpoch) and (nextFileAge < currentDesiredAgeEpoch)):
                deleteFile(file, force)
            else:
                keeper = True
        else:
            # we're at the last item. if we're still looking, keep it
            if completedDesiredAges == False:
                keeper = True
            else:
                deleteFile(file, force)

        # lovely we found a keeper, so get the next file age to satisfy
        if keeper == True:
            fileAgeDays = convertAgeEpochToAge(fileAge)
            print '%s: keeping this file of age %d days to satisfy age %d days' % (file[2], fileAgeDays, currentDesiredAge)
            try:
                currentDesiredAge = agesIterator.next()
            except StopIteration:
                completedDesiredAges = True

    # if we haven't made our way through all the requested ages, process them anyway, solely to message the user
    if completedDesiredAges == False:
        while True:
            print 'no file to satisfy age %d' % currentDesiredAge
            try:
                currentDesiredAge = agesIterator.next()
            except StopIteration:
                break

if __name__ == '__main__':
    main(sys.argv[1:])

Cuando lo tengamos en nuestro Linux, le damos permisos de ejecución y se acabó, funciona perfecto.
Ej


./purgeBackup --force --ages=1,2,3,4,5,6,7,15,30,60,90,120 --directory=/backup/mysql/

Leave a Reply

Your email address will not be published. Required fields are marked *