Advertisement
justin_hanekom

apt-clone-clone.py

Apr 30th, 2019
348
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 8.57 KB | None | 0 0
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3.  
  4. # File: apt-clone-clone.py
  5. # Copyright (c) 2018-2019 Justin Hanekom <justin_hanekom@yahoo.com>
  6. # Licensed under the MIT License
  7.  
  8. # Permission is hereby granted, free of charge, to any person obtaining
  9. # a copy of this software and associated documentation files
  10. # (the "Software"), to deal in the Software without restriction,
  11. # including without limitation the rights to use, copy, modify, merge,
  12. # publish, distribute, sublicense, and/or sell copies of the Software,
  13. # and to permit persons to whom the Software is furnished to do so,
  14. # subject to the following conditions:
  15. #
  16. # The above copyright notice and this permission notice shall be
  17. # included in all copies or substantial portions of the Software.
  18. #
  19. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  20. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  21. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  22. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
  23. # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  24. # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
  25. # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  26.  
  27. from __future__ import (
  28.     absolute_import, division, print_function, unicode_literals
  29. )
  30. from nine import (
  31.     IS_PYTHON2, basestring, chr, class_types, filter, integer_types,
  32.     implements_iterator, implements_to_string, implements_repr,
  33.     input, iterkeys, iteritems, itervalues, long, map,
  34.     native_str, nine, nimport, range, range_list, reraise, str, zip
  35. )
  36.  
  37. argparse = nimport('argparse')
  38. glob = nimport('glob')
  39. grp = nimport('grp')
  40. os = nimport('os')
  41. pwd = nimport('pwd')
  42. subprocess = nimport('subprocess')
  43. sys = nimport('sys')
  44. time = nimport('time')
  45.  
  46. DEFAULT_PREFIX = 'home_'
  47.  
  48.  
  49. def run():
  50.     """Runs this program.
  51.  
  52.    The program runs 'apt-clone clone' to store the current package list
  53.    into a time-stamped file in the given destination directory.
  54.  
  55.    Returns:
  56.        None
  57.    """
  58.     start_time = time.time()
  59.     options = parse_cmd_line(default_prefix=DEFAULT_PREFIX)
  60.     remove_old_files(
  61.         pattern='{destdir}{sep}{prefix}*'.format(
  62.             destdir=options['destdir'],
  63.             sep=os.sep,
  64.             prefix=options['prefix']),
  65.         remove=options['remove'],
  66.         verbose=options['verbose'])
  67.     dest_prefix = generate_results_file_prefix(
  68.         dest_dir=options['destdir'],
  69.         prefix=options['prefix'])
  70.     apt_clone_clone(
  71.         dest_prefix=dest_prefix,
  72.         verbose=options['verbose'])
  73.     change_file_owner_group(
  74.         pattern='{}*apt-clone*'.format(dest_prefix),
  75.         user=options['user'],
  76.         group=options['user'],
  77.         verbose=options['verbose'])
  78.     if options['verbose']:
  79.         print('Done, in {} seconds!'.format(time.time() - start_time))
  80.  
  81.  
  82. def parse_cmd_line(default_prefix):
  83.     """Parses the command-line arguments.
  84.  
  85.    Arguments:
  86.        default_prefix: the default prefix for current package list
  87.  
  88.    Returns:
  89.        A dictionary with each of the supplied command-line arguments.
  90.    """
  91.     parser = argparse.ArgumentParser(
  92.         description="Stores apt-clone clone results")
  93.     parser.add_argument(
  94.         'user',
  95.         help=' '.join([
  96.             'specify the name of the owner of the apt-clone clone results file;',
  97.             'the results files user and group will be set to this value']))
  98.     parser.add_argument(
  99.         'destdir',
  100.         help=' '.join([
  101.             'specify the directory that the new results file',
  102.             'will be saved into']))
  103.     parser.add_argument(
  104.         '--prefix', '-p',
  105.         default=default_prefix,
  106.         help=' '.join([
  107.             'specify the prefix to use for the newly created results file',
  108.             "(default: '%(default)s')"]))
  109.     parser.add_argument(
  110.         '--remove', '-r',
  111.         action='store_true',
  112.         default=False,
  113.         help='specify this to have obsolete results files removed')
  114.     parser.add_argument(
  115.         '--verbose', '-v',
  116.         action='store_true',
  117.         default=False,
  118.         help='specify this to display verbose output')
  119.     # vars() turns Namespace into a regular dictionary
  120.     options = vars(parser.parse_args())
  121.     options['destdir'] = chomp_sep(options['destdir'])
  122.     return options
  123.  
  124.  
  125. def chomp_sep(dir_name):
  126.     """Removes any trailing directory separator characters from the given
  127.    directory name.
  128.  
  129.    Arguments:
  130.        dir_name: the name that has to have any trailing slashes removed
  131.  
  132.    Returns:
  133.        The directory name with no trailing separator characters
  134.    """
  135.     while dir_name.endswith(os.sep):
  136.         dir_name = dir_name[:-1]
  137.     return dir_name
  138.  
  139.  
  140. def generate_results_file_prefix(dest_dir, prefix):
  141.     """Generates a results file filename prefix.
  142.  
  143.    Arguments:
  144.        dest_dir: where the results file should be saved
  145.        prefix: the prefix to use for the results file
  146.  
  147.    Returns:
  148.        A results file filename prefix of the form:
  149.            <dest_dir>/<prefix><year><month><day><hour><minute><second>
  150.        where the year, month, day, etc. values represent the local time
  151.    """
  152.     localtime = time.localtime()
  153.     return ('{dest_dir}{sep}{prefix}{year:04d}{month:02d}{day:02d}' +
  154.         '{hour:02d}{minute:02d}{second:02d}').format(
  155.             dest_dir=dest_dir,
  156.             prefix=prefix,
  157.             year=localtime[0],
  158.             month=localtime[1],
  159.             day=localtime[2],
  160.             hour=localtime[3],
  161.             minute=localtime[4],
  162.             second=localtime[5],
  163.             sep=os.sep)
  164.  
  165.  
  166. def remove_old_files(**kwargs):
  167.     """Only keeps the newest files that match a glob pattern.
  168.  
  169.    Arguments:
  170.        kwargs: a dictionary with the following keys:-
  171.            'pattern'   - the glob pattern of files to remove
  172.            'remove'    - whether to remove old files
  173.            'verbose'   - whether to output text describing non-fatal events
  174.  
  175.    Returns:
  176.        None
  177.    """
  178.     pattern = kwargs.pop('pattern')
  179.     remove = kwargs.pop('remove')
  180.     verbose= kwargs.pop('verbose')
  181.     if kwargs:
  182.         raise TypeError('Unexpected **kwargs: %r' % kwargs)
  183.     if not remove:
  184.         return
  185.     for f in glob.glob(pattern):
  186.         os.remove(f)
  187.         if verbose:
  188.             print("Removed file '{filename}'".format(filename=f))
  189.  
  190.  
  191. def apt_clone_clone(dest_prefix, verbose):
  192.     """Creates a clone file
  193.  
  194.    Arguments:
  195.        dest_prefix:    the prefix to use for the apt-clone clone results file
  196.        verbose:        whether to output text describing non-fatal events
  197.  
  198.    Returns:
  199.        None
  200.   """
  201.     subprocess.call(
  202.         'apt-clone clone "{}" &>/dev/null'.format(dest_prefix),
  203.         shell=True)
  204.     if verbose:
  205.         print('Performed apt-clone clone to: {}'.format(dest_prefix))
  206.  
  207.  
  208. def change_file_owner_group(**kwargs):
  209.     """Changes the owner and group of the named file.
  210.  
  211.    Arguments:
  212.        kwargs: a dictionary with the following keys:-
  213.            'pattern':  the glob pattern of files to change ownership of
  214.            'user':     the new owner to assign to the file
  215.            'group':    the new group to assign to the file;
  216.                        this is the same as 'user' if not given
  217.            'verbose':  whether to output text describing non-fatal events
  218.  
  219.    Returns:
  220.        None
  221.    """
  222.     pattern = kwargs.pop('pattern')
  223.     user = kwargs.pop('user')
  224.     group = kwargs.pop('group', user)
  225.     verbose= kwargs.pop('verbose')
  226.     if kwargs:
  227.         raise TypeError('Unexpected **kwargs: %r' % kwargs)
  228.     for filename in glob.glob(pattern):
  229.         try:
  230.             os.chown(filename,
  231.                      pwd.getpwnam(user).pw_uid,
  232.                      grp.getgrnam(group).gr_gid)
  233.             if verbose:
  234.                 print(
  235.                     ' '.join(
  236.                         "Changed ownership of '{filename}'",
  237.                         "'{user}:{group}'".format(
  238.                             filename=filename,
  239.                             user=user,
  240.                             group=group)))
  241.         except os.OSError as err:
  242.             print("Unable to change ownership of "
  243.                 + "'{filename}' to '{user}:{group}' => {err}".format(
  244.                     filename=filename,
  245.                     user=user,
  246.                     group=group,
  247.                     err=err),
  248.                 file=sys.stderr)
  249.  
  250.  
  251. if __name__ == '__main__':
  252.     run()
  253.  
  254. # vim: set filetype=python smartindent autoindent smarttab expandtab tabstop=4 softtabstop=4 shiftwidth=4 autoread
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement