3
# Copyright 2013 Canonical Ltd.
6
# Adam Gandelman <adamg@ubuntu.com>
18
from fnmatch import fnmatch
20
CHARM_HELPERS_BRANCH = 'lp:charm-helpers'
23
def parse_config(conf_file):
24
if not os.path.isfile(conf_file):
25
logging.error('Invalid config file: %s.' % conf_file)
27
return yaml.load(open(conf_file).read())
30
def clone_helpers(work_dir, branch):
31
dest = os.path.join(work_dir, 'charm-helpers')
32
logging.info('Checking out %s to %s.' % (branch, dest))
33
cmd = ['bzr', 'branch', branch, dest]
34
subprocess.check_call(cmd)
38
def _module_path(module):
39
return os.path.join(*module.split('.'))
42
def _src_path(src, module):
43
return os.path.join(src, 'charmhelpers', _module_path(module))
46
def _dest_path(dest, module):
47
return os.path.join(dest, _module_path(module))
51
return os.path.isfile(path + '.py')
54
def ensure_init(path):
56
ensure directories leading up to path are importable, omitting
57
parent directory, eg path='/hooks/helpers/foo'/:
59
hooks/helpers/__init__.py
60
hooks/helpers/foo/__init__.py
62
for d, dirs, files in os.walk(os.path.join(*path.split('/')[:2])):
63
_i = os.path.join(d, '__init__.py')
64
if not os.path.exists(_i):
65
logging.info('Adding missing __init__.py: %s' % _i)
66
open(_i, 'wb').close()
69
def sync_pyfile(src, dest):
71
src_dir = os.path.dirname(src)
72
logging.info('Syncing pyfile: %s -> %s.' % (src, dest))
73
if not os.path.exists(dest):
75
shutil.copy(src, dest)
76
if os.path.isfile(os.path.join(src_dir, '__init__.py')):
77
shutil.copy(os.path.join(src_dir, '__init__.py'),
82
def get_filter(opts=None):
85
# do not filter any files, include everything
89
incs = [opt.split('=').pop() for opt in opts if 'inc=' in opt]
92
_f = os.path.join(dir, f)
94
if not os.path.isdir(_f) and not _f.endswith('.py') and incs:
95
if True not in [fnmatch(_f, inc) for inc in incs]:
96
logging.debug('Not syncing %s, does not match include '
97
'filters (%s)' % (_f, incs))
100
logging.debug('Including file, which matches include '
101
'filters (%s): %s' % (incs, _f))
102
elif (os.path.isfile(_f) and not _f.endswith('.py')):
103
logging.debug('Not syncing file: %s' % f)
105
elif (os.path.isdir(_f) and not
106
os.path.isfile(os.path.join(_f, '__init__.py'))):
107
logging.debug('Not syncing directory: %s' % f)
113
def sync_directory(src, dest, opts=None):
114
if os.path.exists(dest):
115
logging.debug('Removing existing directory: %s' % dest)
117
logging.info('Syncing directory: %s -> %s.' % (src, dest))
119
shutil.copytree(src, dest, ignore=get_filter(opts))
123
def sync(src, dest, module, opts=None):
124
if os.path.isdir(_src_path(src, module)):
125
sync_directory(_src_path(src, module), _dest_path(dest, module), opts)
126
elif _is_pyfile(_src_path(src, module)):
127
sync_pyfile(_src_path(src, module),
128
os.path.dirname(_dest_path(dest, module)))
130
logging.warn('Could not sync: %s. Neither a pyfile or directory, '
131
'does it even exist?' % module)
134
def parse_sync_options(options):
137
return options.split(',')
140
def extract_options(inc, global_options=None):
141
global_options = global_options or []
142
if global_options and isinstance(global_options, basestring):
143
global_options = [global_options]
145
return (inc, global_options)
146
inc, opts = inc.split('|')
147
return (inc, parse_sync_options(opts) + global_options)
150
def sync_helpers(include, src, dest, options=None):
151
if not os.path.isdir(dest):
154
global_options = parse_sync_options(options)
157
if isinstance(inc, str):
158
inc, opts = extract_options(inc, global_options)
159
sync(src, dest, inc, opts)
160
elif isinstance(inc, dict):
161
# could also do nested dicts here.
162
for k, v in inc.iteritems():
163
if isinstance(v, list):
165
inc, opts = extract_options(m, global_options)
166
sync(src, dest, '%s.%s' % (k, inc), opts)
168
if __name__ == '__main__':
169
parser = optparse.OptionParser()
170
parser.add_option('-c', '--config', action='store', dest='config',
171
default=None, help='helper config file')
172
parser.add_option('-D', '--debug', action='store_true', dest='debug',
173
default=False, help='debug')
174
parser.add_option('-b', '--branch', action='store', dest='branch',
175
help='charm-helpers bzr branch (overrides config)')
176
parser.add_option('-d', '--destination', action='store', dest='dest_dir',
177
help='sync destination dir (overrides config)')
178
(opts, args) = parser.parse_args()
181
logging.basicConfig(level=logging.DEBUG)
183
logging.basicConfig(level=logging.INFO)
186
logging.info('Loading charm helper config from %s.' % opts.config)
187
config = parse_config(opts.config)
189
logging.error('Could not parse config from %s.' % opts.config)
194
if 'branch' not in config:
195
config['branch'] = CHARM_HELPERS_BRANCH
197
config['branch'] = opts.branch
199
config['destination'] = opts.dest_dir
201
if 'destination' not in config:
202
logging.error('No destination dir. specified as option or config.')
205
if 'include' not in config:
207
logging.error('No modules to sync specified as option or config.')
209
config['include'] = []
210
[config['include'].append(a) for a in args]
213
if 'options' in config:
214
sync_options = config['options']
215
tmpd = tempfile.mkdtemp()
217
checkout = clone_helpers(tmpd, config['branch'])
218
sync_helpers(config['include'], checkout, config['destination'],
219
options=sync_options)
221
logging.error("Could not sync: %s" % e)
224
logging.debug('Cleaning up %s' % tmpd)