blob: e9e569b2cf340dac4aabfcc1741afe1eaa61ec15 [file] [log] [blame]
Max Rasskazov7f3c53c2014-12-09 14:19:17 +03001#!/usr/bin/env python
2#-*- coding: utf-8 -*-
3
4
5import datetime
6import os
Max Rasskazove99d8372015-01-13 20:07:01 +03007import re
Max Rasskazov7f3c53c2014-12-09 14:19:17 +03008import subprocess
9import tempfile
10
11
12now = datetime.datetime.now()
13staging_snapshot_stamp = \
14 '{:04}-{:02}-{:02}-{:02}{:02}{:02}'.format(
15 now.year, now.month, now.day, now.hour, now.minute, now.second)
16
17
18class RemoteRsyncStaging(object):
19 def __init__(self,
20 mirror_name,
21 host,
22 module='mirror-sync',
23 root_path='fwm',
24 files_dir='files',
25 save_last_days=61,
26 rsync_extra_params='-v',
27 staging_postfix='staging'):
28 self.mirror_name = mirror_name
29 self.host = host
30 self.module = module
31 self.root_path = root_path
32 self.files_dir = files_dir
33 self.save_last_days = save_last_days
34 self.rsync_extra_params = rsync_extra_params
35 self.staging_snapshot_stamp = staging_snapshot_stamp
36 self.staging_postfix = staging_postfix
37
38 @property
39 def url(self):
40 return '{}::{}'.format(self.host, self.module)
41
42 @property
43 def root_url(self):
44 return '{}/{}'.format(self.url, self.root_path)
45
46 @property
47 def files_path(self):
48 return '{}/{}'.format(self.root_path, self.files_dir)
49
50 @property
51 def files_url(self):
52 return '{}/{}'.format(self.root_url, self.files_dir)
53
54 def http_url(self, path):
55 return 'http://{}/{}'.format(self.host, path)
56
57 def html_link(self, path, link_name):
58 return '<a href="{}">{}</a>'.format(self.http_url(path), link_name)
59
60 @property
61 def staging_dir(self):
62 return '{}-{}'.format(self.mirror_name, self.staging_snapshot_stamp)
63
64 @property
65 def staging_dir_path(self):
66 return '{}/{}'.format(self.files_path, self.staging_dir)
67
68 @property
69 def staging_dir_url(self):
70 return '{}/{}'.format(self.url, self.staging_dir_path)
71
72 @property
73 def staging_link(self):
74 return '{}-{}'.format(self.mirror_name, self.staging_postfix)
75
76 @property
77 def staging_link_path(self):
78 return '{}/{}'.format(self.files_path, self.staging_link)
79
80 @property
81 def staging_link_url(self):
82 return '{}/{}'.format(self.url, self.staging_link_path)
83
84 @property
85 def empty_dir(self):
86 if self.__dict__.get('_empty_dir') is None:
87 self._empty_dir = tempfile.mkdtemp()
88 return self._empty_dir
89
90 def symlink_to(self, target):
91 linkname = tempfile.mktemp()
92 os.symlink(target, linkname)
93 return linkname
94
95 def _shell(self, cmd, raise_error=True):
96 print cmd
97 process = subprocess.Popen(cmd,
98 stdin=subprocess.PIPE,
99 stdout=subprocess.PIPE,
100 stderr=subprocess.PIPE,
101 shell=True)
102 out, err = process.communicate()
103 exitcode = process.returncode
104 if process.returncode != 0 and raise_error:
105 msg = '"{cmd}" failed. Exit code == {exitcode}'\
106 '\n\nSTDOUT: \n{out}'\
107 '\n\nSTDERR: \n{err}'\
108 .format(**(locals()))
109 raise RuntimeError(msg)
110 return exitcode, out, err
111
112 def _do_rsync(self, source='', dest=None, opts='', extra=None):
113 if extra is None:
114 extra = self.rsync_extra_params
115 cmd = 'rsync {opts} {extra} {source} {dest}'.format(**(locals()))
116 return self._shell(cmd)
117
Max Rasskazove99d8372015-01-13 20:07:01 +0300118 def _rsync_ls(self, dirname=None, pattern=r'.*', opts=''):
Max Rasskazov7f3c53c2014-12-09 14:19:17 +0300119 if dirname is None:
120 dirname = '{}/'.format(self.root_path)
121 dest = '{}/{}'.format(self.url, dirname)
Max Rasskazov7f3c53c2014-12-09 14:19:17 +0300122 extra = self.rsync_extra_params + ' --no-v'
123 exitcode, out, err = self._do_rsync(dest=dest, opts=opts, extra=extra)
Max Rasskazove99d8372015-01-13 20:07:01 +0300124 regexp = re.compile(pattern)
125 out = [_ for _ in out.splitlines()
126 if (_.split()[-1] != '.') and
127 (regexp.match(_.split()[-1]) is not None)]
Max Rasskazov7f3c53c2014-12-09 14:19:17 +0300128 return exitcode, out, err
129
Max Rasskazove99d8372015-01-13 20:07:01 +0300130 def rsync_ls(self, dirname, pattern=r'.*'):
131 exitcode, out, err = self._rsync_ls(dirname, pattern=pattern)
132 out = [_.split()[-1] for _ in out]
133 return exitcode, out, err
134
135 def rsync_ls_dirs(self, dirname, pattern=r'.*'):
136 exitcode, out, err = self._rsync_ls(dirname, pattern=pattern)
Max Rasskazov7f3c53c2014-12-09 14:19:17 +0300137 out = [_.split()[-1] for _ in out if _.startswith('d')]
138 return exitcode, out, err
139
Max Rasskazove99d8372015-01-13 20:07:01 +0300140 def rsync_ls_symlinks(self, dirname, pattern=r'.*'):
141 exitcode, out, err = self._rsync_ls(dirname,
142 pattern=pattern,
143 opts='-l')
Max Rasskazov7f3c53c2014-12-09 14:19:17 +0300144 out = [_.split()[-3:] for _ in out if _.startswith('l')]
145 out = [[_[0], _[-1]] for _ in out]
146 return exitcode, out, err
147
148 def rsync_delete_file(self, filename):
149 dirname, filename = os.path.split(filename)
150 source = '{}/'.format(self.empty_dir)
151 dest = '{}/{}/'.format(self.url, dirname)
152 opts = "-r --delete --include={} '--exclude=*'".format(filename)
153 return self._do_rsync(source=source, dest=dest, opts=opts)
154
155 def rsync_delete_dir(self, dirname):
156 source = '{}/'.format(self.empty_dir)
157 dest = '{}/{}/'.format(self.url, dirname)
158 opts = "-a --delete"
159 exitcode, out, err = self._do_rsync(source=source,
160 dest=dest,
161 opts=opts)
162 return self.rsync_delete_file(dirname)
163
164 def rsync_staging_transfer(self, source, tgt_symlink_name=None):
165 if tgt_symlink_name is None:
166 tgt_symlink_name = self.mirror_name
167 opts = '--archive --force --ignore-errors '\
168 '--delete-excluded --no-owner --no-group --delete '\
169 '--link-dest=/{}'.format(self.staging_link_path)
170 try:
171 exitcode, out, err = self._do_rsync(source=source,
172 dest=self.staging_dir_url,
173 opts=opts)
174 self.rsync_delete_file(self.staging_link_path)
175 self._do_rsync(source=self.symlink_to(self.staging_dir),
176 dest=self.staging_link_url,
177 opts='-l')
178 # cleaning of old snapshots
179 return exitcode, out, err
180 except RuntimeError as e:
181 print e.message
182 self.rsync_delete_dir(self.staging_dir_path)
183 raise