blob: 41997c9e30c76447ddca2ec03e4675649d68dda5 [file] [log] [blame]
Alexey Golubev6f55d7e2016-03-23 16:16:29 +03001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
Max Rasskazov9ae40e52016-06-09 12:22:58 +03004# Copyright (c) 2015-2016, Mirantis, Inc.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import logging
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030019import os
20import sys
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030021
Alexey Golubev4997bc02016-03-31 15:33:01 +030022from cliff import app
Alexey Golubev4997bc02016-03-31 15:33:01 +030023from cliff import command
Max Rasskazov9ae40e52016-06-09 12:22:58 +030024from cliff import commandmanager
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030025
Alexey Golubev4997bc02016-03-31 15:33:01 +030026import trsync
Max Rasskazov9ae40e52016-06-09 12:22:58 +030027
Alexey Golubev4997bc02016-03-31 15:33:01 +030028from trsync.objects import rsync_mirror
29
Max Rasskazov9ae40e52016-06-09 12:22:58 +030030
Alexey Golubev4997bc02016-03-31 15:33:01 +030031class PushCmd(command.Command):
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030032 log = logging.getLogger(__name__)
33
34 def get_description(self):
35 return "push SRC to several DST with snapshots"
36
37 def get_parser(self, prog_name):
38 parser = super(PushCmd, self).get_parser(prog_name)
39 parser.add_argument('source', help='Source path')
40 parser.add_argument('mirror_name', help='Mirror name')
41 parser.add_argument('-d', '--dest',
42 nargs='+',
43 required=True,
44 help='Destination rsync url')
45 parser.add_argument('-t', '--timestamp',
46 required=False,
Max Rasskazov9ae40e52016-06-09 12:22:58 +030047 help='Specified timestamp will be used for '
48 'snapshot. Format:yyyy-mm-dd-hhMMSS')
Max Rasskazov9f5e54e2016-06-08 23:40:52 +030049 parser.add_argument('--snapshots-dir', '--snapshot-dir',
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030050 required=False,
51 default='snapshots',
Max Rasskazov9f5e54e2016-06-08 23:40:52 +030052 help='Directory name for snapshots relative '
53 '"destination". "snapshots" by default')
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030054 parser.add_argument('--init-directory-structure',
55 action='store_true',
56 required=False,
57 default=False,
58 help='It specified, all directories including'
Max Rasskazov9ae40e52016-06-09 12:22:58 +030059 '"snapshots-dir" will be created on remote '
60 'location')
Max Rasskazov9f5e54e2016-06-08 23:40:52 +030061 parser.add_argument('--snapshot-lifetime', '--save-latest-days',
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030062 required=False,
63 default=61,
Max Rasskazov9ae40e52016-06-09 12:22:58 +030064 help='Snapshots for specified number of days will '
65 'be saved. All older will be removed. 61 by '
66 'default. 0 mean that old snapshots will not be '
67 'deleted, "None" mean that all snapshots '
68 'excluding latest will be deleted')
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030069 parser.add_argument('--latest-successful-postfix',
70 required=False,
71 default='latest',
72 help='Postfix for symlink to latest successfully '
Max Rasskazov9ae40e52016-06-09 12:22:58 +030073 'synced snapshot. Also used as --link-dest '
74 'target. "latest" by default.')
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030075 parser.add_argument('-s', '--symlinks',
76 nargs='+',
77 required=False,
78 default=[],
Max Rasskazov9ae40e52016-06-09 12:22:58 +030079 help='Update additional symlinks relative '
80 'destination')
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030081 parser.add_argument('--extra',
82 required=False,
83 default='',
Max Rasskazov9ae40e52016-06-09 12:22:58 +030084 help='String with additional rsync parameters. '
85 'For example it may be "\--dry-run '
86 '--any-rsync-option".Use "\\" to disable '
87 'argparse to parse extra value.')
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030088
89 return parser
90
91 def take_action(self, parsed_args):
92 properties = vars(parsed_args)
93 source_dir = properties.pop('source', None)
Max Rasskazov7a502232016-04-12 09:37:49 +030094 mirror_name = properties.pop('mirror_name', None).strip('/')
Alexey Golubev6f55d7e2016-03-23 16:16:29 +030095 symlinks = properties.pop('symlinks', None)
96 servers = properties.pop('dest', None)
97 if properties['extra'].startswith('\\'):
98 properties['extra'] = properties['extra'][1:]
99 properties['rsync_extra_params'] = properties.pop('extra')
Max Rasskazov9f5e54e2016-06-08 23:40:52 +0300100 properties['snapshot_lifetime'] = \
101 None if properties['snapshot_lifetime'] == 'None' \
102 else int(properties['snapshot_lifetime'])
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300103
104 failed = list()
105 for server in servers:
106 source_dir = os.path.realpath(source_dir)
107 if not source_dir.endswith('/'):
108 source_dir += '/'
Alexey Golubev4997bc02016-03-31 15:33:01 +0300109 remote = rsync_mirror.TRsync(server, **properties)
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300110 try:
111 remote.push(source_dir, mirror_name, symlinks=symlinks)
112 except Exception as e:
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300113 print(e.message)
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300114 failed.append(server)
115
116 if failed:
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300117 print("Failed to push to {}".format(str(failed)))
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300118 sys.exit(1)
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300119 # self.app.stdout.write(parsed_args.arg + "\n")
120
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300121
Alexey Golubev4997bc02016-03-31 15:33:01 +0300122class RemoveCmd(command.Command):
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300123 log = logging.getLogger(__name__)
124
125 def get_description(self):
126 return "remove all specified paths from several DST recursively"
127
128 def get_parser(self, prog_name):
129 parser = super(RemoveCmd, self).get_parser(prog_name)
130
131 parser.add_argument('path',
132 nargs='+',
133 help='Path to remove')
134 parser.add_argument('-d', '--dest',
135 nargs='+',
136 required=True,
137 help='Destination rsync url')
138 parser.add_argument('--extra',
139 required=False,
140 default='',
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300141 help='String with additional rsync parameters. '
142 'For example it may be "\--dry-run '
143 '--any-rsync-option". Use "\\" to disable '
144 'argparse to parse extra value.')
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300145 return parser
146
147 def take_action(self, parsed_args):
148 properties = vars(parsed_args)
149 servers = properties.pop('dest', None)
150 path = properties.pop('path', None)
151 if properties['extra'].startswith('\\'):
152 properties['extra'] = properties['extra'][1:]
153 properties['init_directory_structure'] = False
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300154 properties['rsync_extra_params'] = properties.pop('extra')
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300155
156 failed = list()
157 for server in servers:
Alexey Golubev4997bc02016-03-31 15:33:01 +0300158 remote = rsync_mirror.TRsync(server, **properties)
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300159 try:
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300160 print("Removing items {}".format(str(path)))
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300161 remote.rm_all(path)
162 except Exception as e:
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300163 print(e.message)
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300164 failed.append(server)
165
166 if failed:
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300167 print("Failed to remove {}".format(str(failed)))
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300168 sys.exit(1)
169
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300170
Alexey Golubev4997bc02016-03-31 15:33:01 +0300171class TRsyncApp(app.App):
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300172 log = logging.getLogger(__name__)
173
174 def __init__(self):
175 super(TRsyncApp, self).__init__(
176 description='TRsync',
177 version=trsync.__version__,
Alexey Golubev4997bc02016-03-31 15:33:01 +0300178 command_manager=commandmanager.CommandManager('trsync'),
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300179 deferred_help=True,
180 )
181
Max Rasskazov9ae40e52016-06-09 12:22:58 +0300182
Alexey Golubev6f55d7e2016-03-23 16:16:29 +0300183def main(argv=sys.argv[1:]):
184 app = TRsyncApp()
185 return app.run(argv)
186
187if __name__ == '__main__':
188 sys.exit(main(sys.argv[1:]))