~junaidali/charms/trusty/plumgrid-edge/docker-oil

« back to all changes in this revision

Viewing changes to bin/charm_helpers_sync.py

  • Committer: bbaqar at plumgrid
  • Date: 2016-04-25 09:18:40 UTC
  • mfrom: (26.1.3 plumgrid-edge)
  • Revision ID: bbaqar@plumgrid.com-20160425091840-sirw6bbzalts677s
Merge: Liberty/Mitaka support

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
 
 
3
# Copyright 2014-2015 Canonical Limited.
 
4
#
 
5
# This file is part of charm-helpers.
 
6
#
 
7
# charm-helpers is free software: you can redistribute it and/or modify
 
8
# it under the terms of the GNU Lesser General Public License version 3 as
 
9
# published by the Free Software Foundation.
 
10
#
 
11
# charm-helpers is distributed in the hope that it will be useful,
 
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
14
# GNU Lesser General Public License for more details.
 
15
#
 
16
# You should have received a copy of the GNU Lesser General Public License
 
17
# along with charm-helpers.  If not, see <http://www.gnu.org/licenses/>.
 
18
 
 
19
# Authors:
 
20
#   Adam Gandelman <adamg@ubuntu.com>
 
21
 
 
22
import logging
 
23
import optparse
 
24
import os
 
25
import subprocess
 
26
import shutil
 
27
import sys
 
28
import tempfile
 
29
import yaml
 
30
from fnmatch import fnmatch
 
31
 
 
32
import six
 
33
 
 
34
CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
 
35
 
 
36
 
 
37
def parse_config(conf_file):
 
38
    if not os.path.isfile(conf_file):
 
39
        logging.error('Invalid config file: %s.' % conf_file)
 
40
        return False
 
41
    return yaml.load(open(conf_file).read())
 
42
 
 
43
 
 
44
def clone_helpers(work_dir, branch):
 
45
    dest = os.path.join(work_dir, 'charm-helpers')
 
46
    logging.info('Checking out %s to %s.' % (branch, dest))
 
47
    cmd = ['bzr', 'checkout', '--lightweight', branch, dest]
 
48
    subprocess.check_call(cmd)
 
49
    return dest
 
50
 
 
51
 
 
52
def _module_path(module):
 
53
    return os.path.join(*module.split('.'))
 
54
 
 
55
 
 
56
def _src_path(src, module):
 
57
    return os.path.join(src, 'charmhelpers', _module_path(module))
 
58
 
 
59
 
 
60
def _dest_path(dest, module):
 
61
    return os.path.join(dest, _module_path(module))
 
62
 
 
63
 
 
64
def _is_pyfile(path):
 
65
    return os.path.isfile(path + '.py')
 
66
 
 
67
 
 
68
def ensure_init(path):
 
69
    '''
 
70
    ensure directories leading up to path are importable, omitting
 
71
    parent directory, eg path='/hooks/helpers/foo'/:
 
72
        hooks/
 
73
        hooks/helpers/__init__.py
 
74
        hooks/helpers/foo/__init__.py
 
75
    '''
 
76
    for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])):
 
77
        _i = os.path.join(d, '__init__.py')
 
78
        if not os.path.exists(_i):
 
79
            logging.info('Adding missing __init__.py: %s' % _i)
 
80
            open(_i, 'wb').close()
 
81
 
 
82
 
 
83
def sync_pyfile(src, dest):
 
84
    src = src + '.py'
 
85
    src_dir = os.path.dirname(src)
 
86
    logging.info('Syncing pyfile: %s -> %s.' % (src, dest))
 
87
    if not os.path.exists(dest):
 
88
        os.makedirs(dest)
 
89
    shutil.copy(src, dest)
 
90
    if os.path.isfile(os.path.join(src_dir, '__init__.py')):
 
91
        shutil.copy(os.path.join(src_dir, '__init__.py'),
 
92
                    dest)
 
93
    ensure_init(dest)
 
94
 
 
95
 
 
96
def get_filter(opts=None):
 
97
    opts = opts or []
 
98
    if 'inc=*' in opts:
 
99
        # do not filter any files, include everything
 
100
        return None
 
101
 
 
102
    def _filter(dir, ls):
 
103
        incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt]
 
104
        _filter = []
 
105
        for f in ls:
 
106
            _f = os.path.join(dir, f)
 
107
 
 
108
            if not os.path.isdir(_f) and not _f.endswith('.py') and incs:
 
109
                if True not in [fnmatch(_f, inc) for inc in incs]:
 
110
                    logging.debug('Not syncing %s, does not match include '
 
111
                                  'filters (%s)' % (_f, incs))
 
112
                    _filter.append(f)
 
113
                else:
 
114
                    logging.debug('Including file, which matches include '
 
115
                                  'filters (%s): %s' % (incs, _f))
 
116
            elif (os.path.isfile(_f) and not _f.endswith('.py')):
 
117
                logging.debug('Not syncing file: %s' % f)
 
118
                _filter.append(f)
 
119
            elif (os.path.isdir(_f) and not
 
120
                  os.path.isfile(os.path.join(_f, '__init__.py'))):
 
121
                logging.debug('Not syncing directory: %s' % f)
 
122
                _filter.append(f)
 
123
        return _filter
 
124
    return _filter
 
125
 
 
126
 
 
127
def sync_directory(src, dest, opts=None):
 
128
    if os.path.exists(dest):
 
129
        logging.debug('Removing existing directory: %s' % dest)
 
130
        shutil.rmtree(dest)
 
131
    logging.info('Syncing directory: %s -> %s.' % (src, dest))
 
132
 
 
133
    shutil.copytree(src, dest, ignore=get_filter(opts))
 
134
    ensure_init(dest)
 
135
 
 
136
 
 
137
def sync(src, dest, module, opts=None):
 
138
 
 
139
    # Sync charmhelpers/__init__.py for bootstrap code.
 
140
    sync_pyfile(_src_path(src, '__init__'), dest)
 
141
 
 
142
    # Sync other __init__.py files in the path leading to module.
 
143
    m = []
 
144
    steps = module.split('.')[:-1]
 
145
    while steps:
 
146
        m.append(steps.pop(0))
 
147
        init = '.'.join(m + ['__init__'])
 
148
        sync_pyfile(_src_path(src, init),
 
149
                    os.path.dirname(_dest_path(dest, init)))
 
150
 
 
151
    # Sync the module, or maybe a .py file.
 
152
    if os.path.isdir(_src_path(src, module)):
 
153
        sync_directory(_src_path(src, module), _dest_path(dest, module), opts)
 
154
    elif _is_pyfile(_src_path(src, module)):
 
155
        sync_pyfile(_src_path(src, module),
 
156
                    os.path.dirname(_dest_path(dest, module)))
 
157
    else:
 
158
        logging.warn('Could not sync: %s. Neither a pyfile or directory, '
 
159
                     'does it even exist?' % module)
 
160
 
 
161
 
 
162
def parse_sync_options(options):
 
163
    if not options:
 
164
        return []
 
165
    return options.split(',')
 
166
 
 
167
 
 
168
def extract_options(inc, global_options=None):
 
169
    global_options = global_options or []
 
170
    if global_options and isinstance(global_options, six.string_types):
 
171
        global_options = [global_options]
 
172
    if '|' not in inc:
 
173
        return (inc, global_options)
 
174
    inc, opts = inc.split('|')
 
175
    return (inc, parse_sync_options(opts) + global_options)
 
176
 
 
177
 
 
178
def sync_helpers(include, src, dest, options=None):
 
179
    if not os.path.isdir(dest):
 
180
        os.makedirs(dest)
 
181
 
 
182
    global_options = parse_sync_options(options)
 
183
 
 
184
    for inc in include:
 
185
        if isinstance(inc, str):
 
186
            inc, opts = extract_options(inc, global_options)
 
187
            sync(src, dest, inc, opts)
 
188
        elif isinstance(inc, dict):
 
189
            # could also do nested dicts here.
 
190
            for k, v in six.iteritems(inc):
 
191
                if isinstance(v, list):
 
192
                    for m in v:
 
193
                        inc, opts = extract_options(m, global_options)
 
194
                        sync(src, dest, '%s.%s' % (k, inc), opts)
 
195
 
 
196
if __name__ == '__main__':
 
197
    parser = optparse.OptionParser()
 
198
    parser.add_option('-c', '--config', action='store', dest='config',
 
199
                      default=None, help='helper config file')
 
200
    parser.add_option('-D', '--debug', action='store_true', dest='debug',
 
201
                      default=False, help='debug')
 
202
    parser.add_option('-b', '--branch', action='store', dest='branch',
 
203
                      help='charm-helpers bzr branch (overrides config)')
 
204
    parser.add_option('-d', '--destination', action='store', dest='dest_dir',
 
205
                      help='sync destination dir (overrides config)')
 
206
    (opts, args) = parser.parse_args()
 
207
 
 
208
    if opts.debug:
 
209
        logging.basicConfig(level=logging.DEBUG)
 
210
    else:
 
211
        logging.basicConfig(level=logging.INFO)
 
212
 
 
213
    if opts.config:
 
214
        logging.info('Loading charm helper config from %s.' % opts.config)
 
215
        config = parse_config(opts.config)
 
216
        if not config:
 
217
            logging.error('Could not parse config from %s.' % opts.config)
 
218
            sys.exit(1)
 
219
    else:
 
220
        config = {}
 
221
 
 
222
    if 'branch' not in config:
 
223
        config['branch'] = CHARM_HELPERS_BRANCH
 
224
    if opts.branch:
 
225
        config['branch'] = opts.branch
 
226
    if opts.dest_dir:
 
227
        config['destination'] = opts.dest_dir
 
228
 
 
229
    if 'destination' not in config:
 
230
        logging.error('No destination dir. specified as option or config.')
 
231
        sys.exit(1)
 
232
 
 
233
    if 'include' not in config:
 
234
        if not args:
 
235
            logging.error('No modules to sync specified as option or config.')
 
236
            sys.exit(1)
 
237
        config['include'] = []
 
238
        [config['include'].append(a) for a in args]
 
239
 
 
240
    sync_options = None
 
241
    if 'options' in config:
 
242
        sync_options = config['options']
 
243
    tmpd = tempfile.mkdtemp()
 
244
    try:
 
245
        checkout = clone_helpers(tmpd, config['branch'])
 
246
        sync_helpers(config['include'], checkout, config['destination'],
 
247
                     options=sync_options)
 
248
    except Exception as e:
 
249
        logging.error("Could not sync: %s" % e)
 
250
        raise e
 
251
    finally:
 
252
        logging.debug('Cleaning up %s' % tmpd)
 
253
        shutil.rmtree(tmpd)