Added CLI interface to trsync
Added an interface based on cliff to call push/remove commands.
Change-Id: Ic308974da577386c975a96fb1edb259175601680
diff --git a/requirements.txt b/requirements.txt
index 59b1755..505fbb5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,4 +2,5 @@
# of appearance. Changing the order has an impact on the overall integration
# process, which may cause wedges in the gate later.
-pbr>=1.6
\ No newline at end of file
+pbr>=1.6
+cliff>=1.4.5
diff --git a/setup.cfg b/setup.cfg
index bd94c2d..6bad8ba 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,6 @@
[metadata]
name = trsync
+version = 0.5
summary = rsync wrapper that implements transactional synchronization with remote location
description-file =
README.rst
@@ -43,4 +44,17 @@
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
-output_file = trsync/locale/trsync.pot
\ No newline at end of file
+output_file = trsync/locale/trsync.pot
+
+[entry_points]
+console_scripts =
+ trsync=trsync.cmd.cli:main
+
+trsync =
+ push = trsync.cmd.cli:PushCmd
+ remove = trsync.cmd.cli:RemoveCmd
+
+[global]
+setup-hooks =
+ pbr.hooks.setup_hook
+ setup_hooks.setup_hook
diff --git a/setup.py b/setup.py
index 4b34a0a..056c16c 100644
--- a/setup.py
+++ b/setup.py
@@ -26,4 +26,4 @@
setuptools.setup(
setup_requires=['pbr'],
- pbr=True)
\ No newline at end of file
+ pbr=True)
diff --git a/setup_hooks.py b/setup_hooks.py
new file mode 100644
index 0000000..94cc99e
--- /dev/null
+++ b/setup_hooks.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2015 Mirantis, Inc.
+#
+# 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 2 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+
+def setup_hook(config):
+ import pbr
+ import pbr.packaging
+
+ # this monkey patch is to avoid appending git version to version
+ pbr.packaging._get_version_from_git = lambda pre_version: pre_version
diff --git a/trsync/__init__.py b/trsync/__init__.py
index 4fe32df..d523281 100644
--- a/trsync/__init__.py
+++ b/trsync/__init__.py
@@ -14,6 +14,8 @@
import pbr.version
-
-__version__ = pbr.version.VersionInfo(
- 'trsync').version_string()
\ No newline at end of file
+try:
+ __version__ = pbr.version.VersionInfo(
+ 'trsync').version_string()
+except Exception as e:
+ __version__ = "0.0.0"
diff --git a/trsync/cmd/cli.py b/trsync/cmd/cli.py
new file mode 100644
index 0000000..ff5a81b
--- /dev/null
+++ b/trsync/cmd/cli.py
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+import logging
+from cliff.app import App
+from cliff.commandmanager import CommandManager
+from cliff.command import Command
+
+from trsync.objects.rsync_mirror import TRsync
+
+class PushCmd(Command):
+ log = logging.getLogger(__name__)
+
+ def get_description(self):
+ return "push SRC to several DST with snapshots"
+
+ def get_parser(self, prog_name):
+ parser = super(PushCmd, self).get_parser(prog_name)
+ parser.add_argument('source', help='Source path')
+ parser.add_argument('mirror_name', help='Mirror name')
+ parser.add_argument('-d', '--dest',
+ nargs='+',
+ required=True,
+ help='Destination rsync url')
+ parser.add_argument('-t', '--timestamp',
+ required=False,
+ help='Specified timestamp will be used for snapshot.'
+ 'Format:yyyy-mm-dd-hhMMSS')
+ parser.add_argument('--snapshot-dir',
+ required=False,
+ default='snapshots',
+ help='Directory name for snapshots. "snapshots" '
+ 'by default')
+ parser.add_argument('--init-directory-structure',
+ action='store_true',
+ required=False,
+ default=False,
+ help='It specified, all directories including'
+ '"snapshots-dir" will be created on remote location')
+ parser.add_argument('--save-latest-days',
+ required=False,
+ default=61,
+ help='Snapshots for specified number of days will be '
+ 'saved. All older will be removed. 61 by default. '
+ '0 mean that old snapshots will not be deleted, '
+ '"None" mean that all snapshots excluding latest '
+ 'will be deleted')
+ parser.add_argument('--latest-successful-postfix',
+ required=False,
+ default='latest',
+ help='Postfix for symlink to latest successfully '
+ 'synced snapshot. Also used as --link-dest target. '
+ '"latest" by default.')
+ parser.add_argument('-s', '--symlinks',
+ nargs='+',
+ required=False,
+ default=[],
+ help='Update additional symlinks relative destination')
+ parser.add_argument('--extra',
+ required=False,
+ default='',
+ #action='store_const',
+ help='String with additional rsync parameters. For '
+ 'example it may be "\--dry-run --any-rsync-option".'
+ 'Use "\\" to disable argparse to parse extra value.')
+
+ return parser
+
+ def take_action(self, parsed_args):
+ properties = vars(parsed_args)
+ source_dir = properties.pop('source', None)
+ mirror_name = properties.pop('mirror_name', None)
+ symlinks = properties.pop('symlinks', None)
+ servers = properties.pop('dest', None)
+ if properties['extra'].startswith('\\'):
+ properties['extra'] = properties['extra'][1:]
+ properties['rsync_extra_params'] = properties.pop('extra')
+ properties['save_latest_days'] = \
+ None if properties['save_latest_days'] == 'None' \
+ else int(properties['save_latest_days'])
+
+ failed = list()
+ for server in servers:
+ source_dir = os.path.realpath(source_dir)
+ if not source_dir.endswith('/'):
+ source_dir += '/'
+ remote = TRsync(server, **properties)
+ try:
+ remote.push(source_dir, mirror_name, symlinks=symlinks)
+ except Exception as e:
+ print e.message
+ failed.append(server)
+
+ if failed:
+ print "Failed to push to {}".format(str(failed))
+ sys.exit(1)
+ #self.app.stdout.write(parsed_args.arg + "\n")
+
+class RemoveCmd(Command):
+ log = logging.getLogger(__name__)
+
+ def get_description(self):
+ return "remove all specified paths from several DST recursively"
+
+ def get_parser(self, prog_name):
+ parser = super(RemoveCmd, self).get_parser(prog_name)
+
+ parser.add_argument('path',
+ nargs='+',
+ help='Path to remove')
+ parser.add_argument('-d', '--dest',
+ nargs='+',
+ required=True,
+ help='Destination rsync url')
+ parser.add_argument('--extra',
+ required=False,
+ default='',
+ help='String with additional rsync parameters. For '
+ 'example it may be "\--dry-run --any-rsync-option".'
+ 'Use "\\" to disable argparse to parse extra value.')
+ return parser
+
+ def take_action(self, parsed_args):
+ properties = vars(parsed_args)
+ servers = properties.pop('dest', None)
+ path = properties.pop('path', None)
+ if properties['extra'].startswith('\\'):
+ properties['extra'] = properties['extra'][1:]
+ properties['init_directory_structure'] = False
+ properties['rsync_extra_params'] = properties.pop('extra')# + ' --dry-run'
+
+ failed = list()
+ for server in servers:
+ remote = TRsync(server, **properties)
+ try:
+ print "Removing items {}".format(str(path))
+ remote.rm_all(path)
+ except Exception as e:
+ print e.message
+ failed.append(server)
+
+ if failed:
+ print "Failed to remove {}".format(str(failed))
+ sys.exit(1)
+
+class TRsyncApp(App):
+ log = logging.getLogger(__name__)
+
+ def __init__(self):
+ super(TRsyncApp, self).__init__(
+ description='TRsync',
+ version=trsync.__version__,
+ command_manager=CommandManager('trsync'),
+ deferred_help=True,
+ )
+
+def main(argv=sys.argv[1:]):
+ app = TRsyncApp()
+ return app.run(argv)
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))