Advertisement
justin_hanekom

tar-home.py

Apr 21st, 2019 (edited)
596
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.40 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. from __future__ import (
  5.     absolute_import, division, print_function, unicode_literals
  6. )
  7. from nine import (
  8.     IS_PYTHON2, basestring, chr, class_types, filter, integer_types,
  9.     implements_iterator, implements_to_string, implements_repr,
  10.     input, iterkeys, iteritems, itervalues, long, map,
  11.     native_str, nine, nimport, range, range_list, reraise, str, zip
  12. )
  13.  
  14. argparse = nimport('argparse')
  15. contextlib = nimport('contextlib')
  16. glob = nimport('glob')
  17. grp = nimport('grp')
  18. os = nimport('os')
  19. pwd = nimport('pwd')
  20. subprocess = nimport('subprocess')
  21. sys = nimport('sys')
  22. time = nimport('time')
  23.  
  24. DEFAULT_PREFIX = 'home_'
  25. DEFAULT_SUFFIX = '.tar.gz'
  26. DEFAULT_KEEP = 5
  27.  
  28.  
  29. def run():
  30.     """Runs this program.
  31.  
  32.    The program generates a new tar file for a given home directory.
  33.    """
  34.     start_time = time.time()
  35.     options = parse_cmd_line(
  36.         default_prefix=DEFAULT_PREFIX,
  37.         default_suffix=DEFAULT_SUFFIX,
  38.         default_keep=DEFAULT_KEEP)
  39.     remove_old_files(
  40.         pattern='{destdir}{sep}{prefix}*{suffix}'.format(
  41.             destdir=options['destdir'],
  42.             sep=os.sep,
  43.             prefix=options['prefix'],
  44.             suffix=options['suffix']),
  45.         keep=options['keep'] - 1, # subtract 1 because new tar will be created
  46.         remove=options['remove'],
  47.         verbose=options['verbose'])
  48.     dest_tar=generate_tar_name(
  49.         dest_dir=options['destdir'],
  50.         prefix=options['prefix'],
  51.         suffix=options['suffix'])
  52.     tar_src_dir(
  53.         src_dir=options['srcdir'],
  54.         dest_tar=dest_tar,
  55.         verbose=options['verbose'])
  56.     change_file_owner_group(
  57.         filename=dest_tar,
  58.         user=options['user'],
  59.         group=options['user'],
  60.         verbose=options['verbose'])
  61.     if options['verbose']:
  62.         print('Done, in {} seconds!'.format(time.time() - start_time))
  63.  
  64.  
  65. def generate_tar_name(**kwargs):
  66.     """Generates a (hopefully) unique fully qualified tar filename.
  67.  
  68.    Arguments:
  69.        kwargs: a dictionary with the following keys:-
  70.            dest_dir: where to save the tar file
  71.            prefix: the prefix to use for the tar file
  72.            suffix: the suffix to use for the tar file
  73.  
  74.    Returns:
  75.        A unique tar name of the format:
  76.            <dest_dir>/<prefix><year><month><day><hour><minute>second><suffix>
  77.        where the year, month, day, etc. values represent the local time
  78.        at the point at which this function is called.
  79.    """
  80.     dest_dir = kwargs.pop('dest_dir')
  81.     prefix = kwargs.pop('prefix')
  82.     suffix = kwargs.pop('suffix')
  83.     if kwargs:
  84.         raise TypeError('Unexpected **kwargs: %r' % kwargs)
  85.     localtime = time.localtime()
  86.     return ('{dest_dir}{sep}{prefix}{year:04d}{month:02d}{day:02d}' +
  87.         '{hour:02d}{minute:02d}{second:02d}{suffix}').format(
  88.             dest_dir=dest_dir,
  89.             prefix=prefix,
  90.             suffix=suffix,
  91.             year=localtime[0],
  92.             month=localtime[1],
  93.             day=localtime[2],
  94.             hour=localtime[3],
  95.             minute=localtime[4],
  96.             second=localtime[5],
  97.             sep=os.sep)
  98.  
  99.  
  100. def parse_cmd_line(**kwargs):
  101.     """Parses the command-line arguments.
  102.  
  103.    Arguments:
  104.        kwargs: a dictionary with the following keys:-
  105.            default_prefix: the default prefix for generated tar files
  106.            default_suffix: the default suffix for generated tar files
  107.            default_keep:   the default number of tar files to keep if removing
  108.                            old tar files
  109.  
  110.    Returns:
  111.        A dictionary with each of the supplied command-line arguments. If
  112.        no value for an item was supplied on the command-line, then the
  113.        default value for that it is returned.
  114.    """
  115.     default_prefix = kwargs.pop('default_prefix')
  116.     default_suffix = kwargs.pop('default_suffix')
  117.     default_keep = kwargs.pop('default_keep')
  118.     if kwargs:
  119.         raise TypeError('Unexpected **kwargs: %r' % kwargs)
  120.     parser = argparse.ArgumentParser(
  121.         description="Archives (tars) the contents of a user's home directory")
  122.     parser.add_argument(
  123.         'user',
  124.         help=' '.join([
  125.             'specify the owner of the archive file;',
  126.             'the archive files user and group will be set to this value']))
  127.     parser.add_argument(
  128.         'srcdir',
  129.         help='specify the owners home directory')
  130.     parser.add_argument(
  131.         'destdir',
  132.         help='specify the directory that the new archive file will be saved to')
  133.     parser.add_argument(
  134.         '--prefix', '-p',
  135.         default=default_prefix,
  136.         help=' '.join([
  137.             'specify the prefix that the newly created archive file will have',
  138.             "(default: '%(default)s')"]))
  139.     parser.add_argument(
  140.         '--suffix', '-x',
  141.         default=default_suffix,
  142.         help=' '.join([
  143.             'specify the suffix that the newly created archive file will have',
  144.             "(default: '%(default)s')"]))
  145.     parser.add_argument(
  146.         '--keep', '-k',
  147.         type=int,
  148.         default=default_keep,
  149.         help='specify how many archives to keep (default: %(default)i)')
  150.     parser.add_argument(
  151.         '--remove', '-r',
  152.         action='store_true',
  153.         default=False,
  154.         help='specify this so that obsolete archives will be removed')
  155.     parser.add_argument(
  156.         '--verbose', '-v',
  157.         action='store_true',
  158.         default=False,
  159.         help='specify this to display verbose output')
  160.     # vars() turns Namespace into a regular dictionary
  161.     options = vars(parser.parse_args())
  162.     options['srcdir'] = chomp_sep(options['srcdir'])
  163.     options['destdir'] = chomp_sep(options['destdir'])
  164.     options['keep'] = max(options['keep'], 1) # keep must be at least 1
  165.     return options
  166.  
  167.  
  168. def chomp_sep(dir_name):
  169.     """Removes any trailing directory separator characters from the given
  170.    directory name.
  171.  
  172.    Arguments:
  173.        dir_name: the name that has to have any trailing slashes removed
  174.  
  175.    Returns:
  176.        The directory name with no trailing separator characters
  177.    """
  178.     while dir_name.endswith(os.sep):
  179.         dir_name = dir_name[:-1]
  180.     return dir_name
  181.  
  182.  
  183. def remove_old_files(**kwargs):
  184.     """Only keeps the newest files that match the given pattern.
  185.  
  186.    Arguments:
  187.        kwargs: a dictionary with the following keys:-
  188.            'pattern'   - the glob pattern of existing files
  189.            'keep'      - the number of existing files to keep
  190.            'remove'    - whether or not to remove old files
  191.            'verbose'   - whether or not to output text describing non-fatal
  192.                          events
  193.  
  194.    Returns:
  195.        None
  196.    """
  197.     pattern = kwargs.pop('pattern')
  198.     keep= kwargs.pop('keep')
  199.     remove = kwargs.pop('remove')
  200.     verbose= kwargs.pop('verbose')
  201.     if kwargs:
  202.         raise TypeError('Unexpected **kwargs: %r' % kwargs)
  203.     if not remove:
  204.         return
  205.     matching = glob.glob(pattern)
  206.     if keep >= len(matching):
  207.         if verbose:
  208.             print("No files matching pattern '{}' need to be removed".format(
  209.                 pattern))
  210.         return
  211.     files = [tup[1] for tup in sorted([(os.path.getmtime(f), f)
  212.                     for f in matching])]
  213.     for f in files[keep:]:
  214.         os.remove(f)
  215.         if verbose:
  216.             print("Removed file '{filename}'".format(filename=f))
  217.  
  218.  
  219. def tar_src_dir(src_dir, dest_tar, verbose):
  220.     """Archives the source directory into a newly created destination tar.
  221.  
  222.    Arguments:
  223.        src_dir:    the source directory to be tarred
  224.        dest_tar:   the name of the tar file to create
  225.        verbose:    whether or not to output text describing non-fatal events
  226.  
  227.    Returns:
  228.        None
  229.   """
  230.     src_dir = os.path.abspath(src_dir)
  231.     index = src_dir.rindex(os.sep)
  232.     base_dir = src_dir[0:index]
  233.     home_dir = src_dir[index+1:]
  234.     if verbose:
  235.         verbose_flag = '--verbose '
  236.     else:
  237.         verbose_flag = ''
  238.     tar_cmd = ''.join(['sudo tar ',
  239.                         '--create --preserve-permissions --atime-preserve ',
  240.                         "--use-compress-program='pigz' ",
  241.                         verbose_flag,
  242.                         "--file={} ".format(dest_tar),
  243.                         home_dir,
  244.                       ])
  245.     with chdir(base_dir):
  246.         if verbose:
  247.             subprocess.call(tar_cmd, shell=True)
  248.         else:
  249.             subprocess.call('{} {}'.format(tar_cmd, '&>/var/null'), shell=True)
  250.     # Ensure that the file is flushed to disk
  251.     subprocess.call('sync; sleep 10; sync', shell=True)
  252.  
  253.  
  254. @contextlib.contextmanager
  255. def chdir(new_dir):
  256.     """Context manager which allows code to run in a different directory.
  257.  
  258.    (1) Stores the current working directory;
  259.    (2) switches to the directory: <new_dir>;
  260.    (3) executes any code run in this context; and then
  261.    (4) returns to the original working directory.
  262.  
  263.    Returns:
  264.        None
  265.    """
  266.     cwd = os.getcwd()
  267.     os.chdir(new_dir)
  268.     try:
  269.         yield
  270.     finally:
  271.         os.chdir(cwd)
  272.  
  273.  
  274. def change_file_owner_group(**kwargs):
  275.     """Changes the owner and group of the named file.
  276.  
  277.    Arguments:
  278.        kwargs: a dictionary with the following keys:-
  279.            'filename': the file whose ownership is to be changed
  280.            'user':     the new owner to assign to the file
  281.            'group':    the new group to assign to the file;
  282.                        this is the same as 'user' if not given
  283.            'verbose':  whether to output text describing non-fatal events
  284.  
  285.    Returns:
  286.        None
  287.    """
  288.     filename = kwargs.pop('filename')
  289.     user = kwargs.pop('user')
  290.     group = kwargs.pop('group', user)
  291.     verbose= kwargs.pop('verbose')
  292.     if kwargs:
  293.         raise TypeError('Unexpected **kwargs: %r' % kwargs)
  294.     try:
  295.         os.chown(filename,
  296.                  pwd.getpwnam(user).pw_uid,
  297.                  grp.getgrnam(group).gr_gid)
  298.         if verbose:
  299.             print("Changed ownership of '{filename}' to {user}:{group}".format(
  300.                 filename=filename,
  301.                 user=user,
  302.                 group=group))
  303.     except os.OSError:
  304.         print("Unable to change ownership of "
  305.             + "'{filename}' to {user}:{group}".format(
  306.                 filename=filename,
  307.                 user=user,
  308.                 group=group),
  309.             file=sys.stderr)
  310.  
  311.  
  312. if __name__ == '__main__':
  313.     run()
  314.  
Tags: python
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement