2
# Copyright 2013 Canonical Ltd.
5
# Adam Gandelman <adamg@ubuntu.com>
16
from fnmatch import fnmatch
18
CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
21
def parse_config(conf_file):
22
if not os.path.isfile(conf_file):
23
logging.error('Invalid config file: %s.' % conf_file)
25
return yaml.load(open(conf_file).read())
28
def clone_helpers(work_dir, branch):
29
dest = os.path.join(work_dir, 'charm-helpers')
30
logging.info('Checking out %s to %s.' % (branch, dest))
31
cmd = ['bzr', 'branch', branch, dest]
32
subprocess.check_call(cmd)
36
def _module_path(module):
37
return os.path.join(*module.split('.'))
40
def _src_path(src, module):
41
return os.path.join(src, 'charmhelpers', _module_path(module))
44
def _dest_path(dest, module):
45
return os.path.join(dest, _module_path(module))
49
return os.path.isfile(path + '.py')
52
def ensure_init(path):
54
ensure directories leading up to path are importable, omitting
55
parent directory, eg path='/hooks/helpers/foo'/:
57
hooks/helpers/__init__.py
58
hooks/helpers/foo/__init__.py
60
for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])):
61
_i = os.path.join(d, '__init__.py')
62
if not os.path.exists(_i):
63
logging.info('Adding missing __init__.py: %s' % _i)
64
open(_i, 'wb').close()
67
def sync_pyfile(src, dest):
69
src_dir = os.path.dirname(src)
70
logging.info('Syncing pyfile: %s -> %s.' % (src, dest))
71
if not os.path.exists(dest):
73
shutil.copy(src, dest)
74
if os.path.isfile(os.path.join(src_dir, '__init__.py')):
75
shutil.copy(os.path.join(src_dir, '__init__.py'),
80
def get_filter(opts=None):
83
# do not filter any files, include everything
87
incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt]
90
_f = os.path.join(dir, f)
92
if not os.path.isdir(_f) and not _f.endswith('.py') and incs:
93
if True not in [fnmatch(_f, inc) for inc in incs]:
94
logging.debug('Not syncing %s, does not match include '
95
'filters (%s)' % (_f, incs))
98
logging.debug('Including file, which matches include '
99
'filters (%s): %s' % (incs, _f))
100
elif (os.path.isfile(_f) and not _f.endswith('.py')):
101
logging.debug('Not syncing file: %s' % f)
103
elif (os.path.isdir(_f) and not
104
os.path.isfile(os.path.join(_f, '__init__.py'))):
105
logging.debug('Not syncing directory: %s' % f)
111
def sync_directory(src, dest, opts=None):
112
if os.path.exists(dest):
113
logging.debug('Removing existing directory: %s' % dest)
115
logging.info('Syncing directory: %s -> %s.' % (src, dest))
117
shutil.copytree(src, dest, ignore=get_filter(opts))
121
def sync(src, dest, module, opts=None):
122
if os.path.isdir(_src_path(src, module)):
123
sync_directory(_src_path(src, module), _dest_path(dest, module), opts)
124
elif _is_pyfile(_src_path(src, module)):
125
sync_pyfile(_src_path(src, module),
126
os.path.dirname(_dest_path(dest, module)))
128
logging.warn('Could not sync: %s. Neither a pyfile or directory, '
129
'does it even exist?' % module)
132
def parse_sync_options(options):
135
return options.split(',')
138
def extract_options(inc, global_options=None):
139
global_options = global_options or []
140
if global_options and isinstance(global_options, basestring):
141
global_options = [global_options]
143
return (inc, global_options)
144
inc, opts = inc.split('|')
145
return (inc, parse_sync_options(opts) + global_options)
148
def sync_helpers(include, src, dest, options=None):
149
if not os.path.isdir(dest):
152
global_options = parse_sync_options(options)
155
if isinstance(inc, str):
156
inc, opts = extract_options(inc, global_options)
157
sync(src, dest, inc, opts)
158
elif isinstance(inc, dict):
159
# could also do nested dicts here.
160
for k, v in inc.iteritems():
161
if isinstance(v, list):
163
inc, opts = extract_options(m, global_options)
164
sync(src, dest, '%s.%s' % (k, inc), opts)
166
if __name__ == '__main__':
167
parser = optparse.OptionParser()
168
parser.add_option('-c', '--config', action='store', dest='config',
169
default=None, help='helper config file')
170
parser.add_option('-D', '--debug', action='store_true', dest='debug',
171
default=False, help='debug')
172
parser.add_option('-b', '--branch', action='store', dest='branch',
173
help='charm-helpers bzr branch (overrides config)')
174
parser.add_option('-d', '--destination', action='store', dest='dest_dir',
175
help='sync destination dir (overrides config)')
176
(opts, args) = parser.parse_args()
179
logging.basicConfig(level=logging.DEBUG)
181
logging.basicConfig(level=logging.INFO)
184
logging.info('Loading charm helper config from %s.' % opts.config)
185
config = parse_config(opts.config)
187
logging.error('Could not parse config from %s.' % opts.config)
192
if 'branch' not in config:
193
config['branch'] = CHARM_HELPERS_BRANCH
195
config['branch'] = opts.branch
197
config['destination'] = opts.dest_dir
199
if 'destination' not in config:
200
logging.error('No destination dir. specified as option or config.')
203
if 'include' not in config:
205
logging.error('No modules to sync specified as option or config.')
207
config['include'] = []
208
[config['include'].append(a) for a in args]
211
if 'options' in config:
212
sync_options = config['options']
213
tmpd = tempfile.mkdtemp()
215
checkout = clone_helpers(tmpd, config['branch'])
216
sync_helpers(config['include'], checkout, config['destination'],
217
options=sync_options)
219
logging.error("Could not sync: %s" % e)
222
logging.debug('Cleaning up %s' % tmpd)