Merge lp:~1chb1n/charms/trusty/mongodb/ch-sync-mitaka into lp:charms/trusty/mongodb

Proposed by Ryan Beisner on 2016-01-11
Status: Merged
Merged at revision: 82
Proposed branch: lp:~1chb1n/charms/trusty/mongodb/ch-sync-mitaka
Merge into: lp:charms/trusty/mongodb
Diff against target: 839 lines (+302/-118)
11 files modified
charmhelpers/contrib/charmsupport/nrpe.py (+52/-14)
charmhelpers/contrib/python/packages.py (+13/-4)
charmhelpers/core/hookenv.py (+54/-6)
charmhelpers/core/host.py (+94/-22)
charmhelpers/core/hugepage.py (+2/-0)
charmhelpers/core/services/helpers.py (+14/-5)
charmhelpers/core/templating.py (+21/-8)
charmhelpers/fetch/__init__.py (+10/-2)
charmhelpers/fetch/archiveurl.py (+1/-1)
charmhelpers/fetch/bzrurl.py (+22/-32)
charmhelpers/fetch/giturl.py (+19/-24)
To merge this branch: bzr merge lp:~1chb1n/charms/trusty/mongodb/ch-sync-mitaka
Reviewer Review Type Date Requested Status
Review Queue (community) automated testing Approve on 2016-01-15
José Antonio Rey 2016-01-11 Approve on 2016-01-13
Review via email: mp+282211@code.launchpad.net

Description of the Change

Sync charm-helpers for Mitaka cloud archive capability.

To post a comment you must log in.

charm_lint_check #17089 mongodb for 1chb1n mp282211
    LINT OK: passed

Build: http://10.245.162.77:8080/job/charm_lint_check/17089/

charm_unit_test #15963 mongodb for 1chb1n mp282211
    UNIT OK: passed

Build: http://10.245.162.77:8080/job/charm_unit_test/15963/

charm_amulet_test #8698 mongodb for 1chb1n mp282211
    AMULET OK: passed

Build: http://10.245.162.77:8080/job/charm_amulet_test/8698/

José Antonio Rey (jose) wrote :

+1 LGTM

review: Approve
Review Queue (review-queue) wrote :

This item has failed automated testing! Results available here http://juju-ci.vapour.ws:8080/job/charm-bundle-test-lxc/2146/

review: Needs Fixing (automated testing)
Review Queue (review-queue) wrote :

The results (PASS) are in and available here: http://juju-ci.vapour.ws:8080/job/charm-bundle-test-aws/2126/

review: Approve (automated testing)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'charmhelpers/contrib/charmsupport/nrpe.py'
2--- charmhelpers/contrib/charmsupport/nrpe.py 2015-08-19 13:58:52 +0000
3+++ charmhelpers/contrib/charmsupport/nrpe.py 2016-01-11 18:37:30 +0000
4@@ -148,6 +148,13 @@
5 self.description = description
6 self.check_cmd = self._locate_cmd(check_cmd)
7
8+ def _get_check_filename(self):
9+ return os.path.join(NRPE.nrpe_confdir, '{}.cfg'.format(self.command))
10+
11+ def _get_service_filename(self, hostname):
12+ return os.path.join(NRPE.nagios_exportdir,
13+ 'service__{}_{}.cfg'.format(hostname, self.command))
14+
15 def _locate_cmd(self, check_cmd):
16 search_path = (
17 '/usr/lib/nagios/plugins',
18@@ -163,9 +170,21 @@
19 log('Check command not found: {}'.format(parts[0]))
20 return ''
21
22+ def _remove_service_files(self):
23+ if not os.path.exists(NRPE.nagios_exportdir):
24+ return
25+ for f in os.listdir(NRPE.nagios_exportdir):
26+ if f.endswith('_{}.cfg'.format(self.command)):
27+ os.remove(os.path.join(NRPE.nagios_exportdir, f))
28+
29+ def remove(self, hostname):
30+ nrpe_check_file = self._get_check_filename()
31+ if os.path.exists(nrpe_check_file):
32+ os.remove(nrpe_check_file)
33+ self._remove_service_files()
34+
35 def write(self, nagios_context, hostname, nagios_servicegroups):
36- nrpe_check_file = '/etc/nagios/nrpe.d/{}.cfg'.format(
37- self.command)
38+ nrpe_check_file = self._get_check_filename()
39 with open(nrpe_check_file, 'w') as nrpe_check_config:
40 nrpe_check_config.write("# check {}\n".format(self.shortname))
41 nrpe_check_config.write("command[{}]={}\n".format(
42@@ -180,9 +199,7 @@
43
44 def write_service_config(self, nagios_context, hostname,
45 nagios_servicegroups):
46- for f in os.listdir(NRPE.nagios_exportdir):
47- if re.search('.*{}.cfg'.format(self.command), f):
48- os.remove(os.path.join(NRPE.nagios_exportdir, f))
49+ self._remove_service_files()
50
51 templ_vars = {
52 'nagios_hostname': hostname,
53@@ -192,8 +209,7 @@
54 'command': self.command,
55 }
56 nrpe_service_text = Check.service_template.format(**templ_vars)
57- nrpe_service_file = '{}/service__{}_{}.cfg'.format(
58- NRPE.nagios_exportdir, hostname, self.command)
59+ nrpe_service_file = self._get_service_filename(hostname)
60 with open(nrpe_service_file, 'w') as nrpe_service_config:
61 nrpe_service_config.write(str(nrpe_service_text))
62
63@@ -218,12 +234,32 @@
64 if hostname:
65 self.hostname = hostname
66 else:
67- self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
68+ nagios_hostname = get_nagios_hostname()
69+ if nagios_hostname:
70+ self.hostname = nagios_hostname
71+ else:
72+ self.hostname = "{}-{}".format(self.nagios_context, self.unit_name)
73 self.checks = []
74
75 def add_check(self, *args, **kwargs):
76 self.checks.append(Check(*args, **kwargs))
77
78+ def remove_check(self, *args, **kwargs):
79+ if kwargs.get('shortname') is None:
80+ raise ValueError('shortname of check must be specified')
81+
82+ # Use sensible defaults if they're not specified - these are not
83+ # actually used during removal, but they're required for constructing
84+ # the Check object; check_disk is chosen because it's part of the
85+ # nagios-plugins-basic package.
86+ if kwargs.get('check_cmd') is None:
87+ kwargs['check_cmd'] = 'check_disk'
88+ if kwargs.get('description') is None:
89+ kwargs['description'] = ''
90+
91+ check = Check(*args, **kwargs)
92+ check.remove(self.hostname)
93+
94 def write(self):
95 try:
96 nagios_uid = pwd.getpwnam('nagios').pw_uid
97@@ -260,7 +296,7 @@
98 :param str relation_name: Name of relation nrpe sub joined to
99 """
100 for rel in relations_of_type(relation_name):
101- if 'nagios_hostname' in rel:
102+ if 'nagios_host_context' in rel:
103 return rel['nagios_host_context']
104
105
106@@ -301,11 +337,13 @@
107 upstart_init = '/etc/init/%s.conf' % svc
108 sysv_init = '/etc/init.d/%s' % svc
109 if os.path.exists(upstart_init):
110- nrpe.add_check(
111- shortname=svc,
112- description='process check {%s}' % unit_name,
113- check_cmd='check_upstart_job %s' % svc
114- )
115+ # Don't add a check for these services from neutron-gateway
116+ if svc not in ['ext-port', 'os-charm-phy-nic-mtu']:
117+ nrpe.add_check(
118+ shortname=svc,
119+ description='process check {%s}' % unit_name,
120+ check_cmd='check_upstart_job %s' % svc
121+ )
122 elif os.path.exists(sysv_init):
123 cronpath = '/etc/cron.d/nagios-service-check-%s' % svc
124 cron_file = ('*/5 * * * * root '
125
126=== modified file 'charmhelpers/contrib/python/packages.py'
127--- charmhelpers/contrib/python/packages.py 2015-08-19 13:58:52 +0000
128+++ charmhelpers/contrib/python/packages.py 2016-01-11 18:37:30 +0000
129@@ -42,8 +42,12 @@
130 yield "--{0}={1}".format(key, value)
131
132
133-def pip_install_requirements(requirements, **options):
134- """Install a requirements file """
135+def pip_install_requirements(requirements, constraints=None, **options):
136+ """Install a requirements file.
137+
138+ :param constraints: Path to pip constraints file.
139+ http://pip.readthedocs.org/en/stable/user_guide/#constraints-files
140+ """
141 command = ["install"]
142
143 available_options = ('proxy', 'src', 'log', )
144@@ -51,8 +55,13 @@
145 command.append(option)
146
147 command.append("-r {0}".format(requirements))
148- log("Installing from file: {} with options: {}".format(requirements,
149- command))
150+ if constraints:
151+ command.append("-c {0}".format(constraints))
152+ log("Installing from file: {} with constraints {} "
153+ "and options: {}".format(requirements, constraints, command))
154+ else:
155+ log("Installing from file: {} with options: {}".format(requirements,
156+ command))
157 pip_execute(command)
158
159
160
161=== modified file 'charmhelpers/core/hookenv.py'
162--- charmhelpers/core/hookenv.py 2015-10-07 10:45:38 +0000
163+++ charmhelpers/core/hookenv.py 2016-01-11 18:37:30 +0000
164@@ -491,6 +491,19 @@
165
166
167 @cached
168+def peer_relation_id():
169+ '''Get the peers relation id if a peers relation has been joined, else None.'''
170+ md = metadata()
171+ section = md.get('peers')
172+ if section:
173+ for key in section:
174+ relids = relation_ids(key)
175+ if relids:
176+ return relids[0]
177+ return None
178+
179+
180+@cached
181 def relation_to_interface(relation_name):
182 """
183 Given the name of a relation, return the interface that relation uses.
184@@ -504,12 +517,12 @@
185 def relation_to_role_and_interface(relation_name):
186 """
187 Given the name of a relation, return the role and the name of the interface
188- that relation uses (where role is one of ``provides``, ``requires``, or ``peer``).
189+ that relation uses (where role is one of ``provides``, ``requires``, or ``peers``).
190
191 :returns: A tuple containing ``(role, interface)``, or ``(None, None)``.
192 """
193 _metadata = metadata()
194- for role in ('provides', 'requires', 'peer'):
195+ for role in ('provides', 'requires', 'peers'):
196 interface = _metadata.get(role, {}).get(relation_name, {}).get('interface')
197 if interface:
198 return role, interface
199@@ -521,7 +534,7 @@
200 """
201 Given a role and interface name, return a list of relation names for the
202 current charm that use that interface under that role (where role is one
203- of ``provides``, ``requires``, or ``peer``).
204+ of ``provides``, ``requires``, or ``peers``).
205
206 :returns: A list of relation names.
207 """
208@@ -542,7 +555,7 @@
209 :returns: A list of relation names.
210 """
211 results = []
212- for role in ('provides', 'requires', 'peer'):
213+ for role in ('provides', 'requires', 'peers'):
214 results.extend(role_and_interface_to_relations(role, interface_name))
215 return results
216
217@@ -624,7 +637,7 @@
218
219
220 @cached
221-def storage_get(attribute="", storage_id=""):
222+def storage_get(attribute=None, storage_id=None):
223 """Get storage attributes"""
224 _args = ['storage-get', '--format=json']
225 if storage_id:
226@@ -638,7 +651,7 @@
227
228
229 @cached
230-def storage_list(storage_name=""):
231+def storage_list(storage_name=None):
232 """List the storage IDs for the unit"""
233 _args = ['storage-list', '--format=json']
234 if storage_name:
235@@ -820,6 +833,7 @@
236
237 def translate_exc(from_exc, to_exc):
238 def inner_translate_exc1(f):
239+ @wraps(f)
240 def inner_translate_exc2(*args, **kwargs):
241 try:
242 return f(*args, **kwargs)
243@@ -864,6 +878,40 @@
244 subprocess.check_call(cmd)
245
246
247+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
248+def payload_register(ptype, klass, pid):
249+ """ is used while a hook is running to let Juju know that a
250+ payload has been started."""
251+ cmd = ['payload-register']
252+ for x in [ptype, klass, pid]:
253+ cmd.append(x)
254+ subprocess.check_call(cmd)
255+
256+
257+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
258+def payload_unregister(klass, pid):
259+ """ is used while a hook is running to let Juju know
260+ that a payload has been manually stopped. The <class> and <id> provided
261+ must match a payload that has been previously registered with juju using
262+ payload-register."""
263+ cmd = ['payload-unregister']
264+ for x in [klass, pid]:
265+ cmd.append(x)
266+ subprocess.check_call(cmd)
267+
268+
269+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
270+def payload_status_set(klass, pid, status):
271+ """is used to update the current status of a registered payload.
272+ The <class> and <id> provided must match a payload that has been previously
273+ registered with juju using payload-register. The <status> must be one of the
274+ follow: starting, started, stopping, stopped"""
275+ cmd = ['payload-status-set']
276+ for x in [klass, pid, status]:
277+ cmd.append(x)
278+ subprocess.check_call(cmd)
279+
280+
281 @cached
282 def juju_version():
283 """Full version string (eg. '1.23.3.1-trusty-amd64')"""
284
285=== modified file 'charmhelpers/core/host.py'
286--- charmhelpers/core/host.py 2015-10-07 10:45:38 +0000
287+++ charmhelpers/core/host.py 2016-01-11 18:37:30 +0000
288@@ -67,10 +67,14 @@
289 """Pause a system service.
290
291 Stop it, and prevent it from starting again at boot."""
292- stopped = service_stop(service_name)
293+ stopped = True
294+ if service_running(service_name):
295+ stopped = service_stop(service_name)
296 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
297 sysv_file = os.path.join(initd_dir, service_name)
298- if os.path.exists(upstart_file):
299+ if init_is_systemd():
300+ service('disable', service_name)
301+ elif os.path.exists(upstart_file):
302 override_path = os.path.join(
303 init_dir, '{}.override'.format(service_name))
304 with open(override_path, 'w') as fh:
305@@ -78,9 +82,9 @@
306 elif os.path.exists(sysv_file):
307 subprocess.check_call(["update-rc.d", service_name, "disable"])
308 else:
309- # XXX: Support SystemD too
310 raise ValueError(
311- "Unable to detect {0} as either Upstart {1} or SysV {2}".format(
312+ "Unable to detect {0} as SystemD, Upstart {1} or"
313+ " SysV {2}".format(
314 service_name, upstart_file, sysv_file))
315 return stopped
316
317@@ -92,7 +96,9 @@
318 Reenable starting again at boot. Start the service"""
319 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
320 sysv_file = os.path.join(initd_dir, service_name)
321- if os.path.exists(upstart_file):
322+ if init_is_systemd():
323+ service('enable', service_name)
324+ elif os.path.exists(upstart_file):
325 override_path = os.path.join(
326 init_dir, '{}.override'.format(service_name))
327 if os.path.exists(override_path):
328@@ -100,34 +106,42 @@
329 elif os.path.exists(sysv_file):
330 subprocess.check_call(["update-rc.d", service_name, "enable"])
331 else:
332- # XXX: Support SystemD too
333 raise ValueError(
334- "Unable to detect {0} as either Upstart {1} or SysV {2}".format(
335+ "Unable to detect {0} as SystemD, Upstart {1} or"
336+ " SysV {2}".format(
337 service_name, upstart_file, sysv_file))
338
339- started = service_start(service_name)
340+ started = service_running(service_name)
341+ if not started:
342+ started = service_start(service_name)
343 return started
344
345
346 def service(action, service_name):
347 """Control a system service"""
348- cmd = ['service', service_name, action]
349+ if init_is_systemd():
350+ cmd = ['systemctl', action, service_name]
351+ else:
352+ cmd = ['service', service_name, action]
353 return subprocess.call(cmd) == 0
354
355
356-def service_running(service):
357+def service_running(service_name):
358 """Determine whether a system service is running"""
359- try:
360- output = subprocess.check_output(
361- ['service', service, 'status'],
362- stderr=subprocess.STDOUT).decode('UTF-8')
363- except subprocess.CalledProcessError:
364- return False
365+ if init_is_systemd():
366+ return service('is-active', service_name)
367 else:
368- if ("start/running" in output or "is running" in output):
369- return True
370- else:
371+ try:
372+ output = subprocess.check_output(
373+ ['service', service_name, 'status'],
374+ stderr=subprocess.STDOUT).decode('UTF-8')
375+ except subprocess.CalledProcessError:
376 return False
377+ else:
378+ if ("start/running" in output or "is running" in output):
379+ return True
380+ else:
381+ return False
382
383
384 def service_available(service_name):
385@@ -142,8 +156,29 @@
386 return True
387
388
389-def adduser(username, password=None, shell='/bin/bash', system_user=False):
390- """Add a user to the system"""
391+SYSTEMD_SYSTEM = '/run/systemd/system'
392+
393+
394+def init_is_systemd():
395+ return os.path.isdir(SYSTEMD_SYSTEM)
396+
397+
398+def adduser(username, password=None, shell='/bin/bash', system_user=False,
399+ primary_group=None, secondary_groups=None):
400+ """
401+ Add a user to the system.
402+
403+ Will log but otherwise succeed if the user already exists.
404+
405+ :param str username: Username to create
406+ :param str password: Password for user; if ``None``, create a system user
407+ :param str shell: The default shell for the user
408+ :param bool system_user: Whether to create a login or system user
409+ :param str primary_group: Primary group for user; defaults to their username
410+ :param list secondary_groups: Optional list of additional groups
411+
412+ :returns: The password database entry struct, as returned by `pwd.getpwnam`
413+ """
414 try:
415 user_info = pwd.getpwnam(username)
416 log('user {0} already exists!'.format(username))
417@@ -158,6 +193,16 @@
418 '--shell', shell,
419 '--password', password,
420 ])
421+ if not primary_group:
422+ try:
423+ grp.getgrnam(username)
424+ primary_group = username # avoid "group exists" error
425+ except KeyError:
426+ pass
427+ if primary_group:
428+ cmd.extend(['-g', primary_group])
429+ if secondary_groups:
430+ cmd.extend(['-G', ','.join(secondary_groups)])
431 cmd.append(username)
432 subprocess.check_call(cmd)
433 user_info = pwd.getpwnam(username)
434@@ -566,7 +611,14 @@
435 os.chdir(cur)
436
437
438-def chownr(path, owner, group, follow_links=True):
439+def chownr(path, owner, group, follow_links=True, chowntopdir=False):
440+ """
441+ Recursively change user and group ownership of files and directories
442+ in given path. Doesn't chown path itself by default, only its children.
443+
444+ :param bool follow_links: Also Chown links if True
445+ :param bool chowntopdir: Also chown path itself if True
446+ """
447 uid = pwd.getpwnam(owner).pw_uid
448 gid = grp.getgrnam(group).gr_gid
449 if follow_links:
450@@ -574,6 +626,10 @@
451 else:
452 chown = os.lchown
453
454+ if chowntopdir:
455+ broken_symlink = os.path.lexists(path) and not os.path.exists(path)
456+ if not broken_symlink:
457+ chown(path, uid, gid)
458 for root, dirs, files in os.walk(path):
459 for name in dirs + files:
460 full = os.path.join(root, name)
461@@ -584,3 +640,19 @@
462
463 def lchownr(path, owner, group):
464 chownr(path, owner, group, follow_links=False)
465+
466+
467+def get_total_ram():
468+ '''The total amount of system RAM in bytes.
469+
470+ This is what is reported by the OS, and may be overcommitted when
471+ there are multiple containers hosted on the same machine.
472+ '''
473+ with open('/proc/meminfo', 'r') as f:
474+ for line in f.readlines():
475+ if line:
476+ key, value, unit = line.split()
477+ if key == 'MemTotal:':
478+ assert unit == 'kB', 'Unknown unit'
479+ return int(value) * 1024 # Classic, not KiB.
480+ raise NotImplementedError()
481
482=== modified file 'charmhelpers/core/hugepage.py'
483--- charmhelpers/core/hugepage.py 2015-10-07 10:45:38 +0000
484+++ charmhelpers/core/hugepage.py 2016-01-11 18:37:30 +0000
485@@ -46,6 +46,8 @@
486 group_info = add_group(group)
487 gid = group_info.gr_gid
488 add_user_to_group(user, group)
489+ if max_map_count < 2 * nr_hugepages:
490+ max_map_count = 2 * nr_hugepages
491 sysctl_settings = {
492 'vm.nr_hugepages': nr_hugepages,
493 'vm.max_map_count': max_map_count,
494
495=== modified file 'charmhelpers/core/services/helpers.py'
496--- charmhelpers/core/services/helpers.py 2015-08-19 13:58:52 +0000
497+++ charmhelpers/core/services/helpers.py 2016-01-11 18:37:30 +0000
498@@ -243,33 +243,40 @@
499 :param str source: The template source file, relative to
500 `$CHARM_DIR/templates`
501
502- :param str target: The target to write the rendered template to
503+ :param str target: The target to write the rendered template to (or None)
504 :param str owner: The owner of the rendered file
505 :param str group: The group of the rendered file
506 :param int perms: The permissions of the rendered file
507 :param partial on_change_action: functools partial to be executed when
508 rendered file changes
509+ :param jinja2 loader template_loader: A jinja2 template loader
510+
511+ :return str: The rendered template
512 """
513 def __init__(self, source, target,
514 owner='root', group='root', perms=0o444,
515- on_change_action=None):
516+ on_change_action=None, template_loader=None):
517 self.source = source
518 self.target = target
519 self.owner = owner
520 self.group = group
521 self.perms = perms
522 self.on_change_action = on_change_action
523+ self.template_loader = template_loader
524
525 def __call__(self, manager, service_name, event_name):
526 pre_checksum = ''
527 if self.on_change_action and os.path.isfile(self.target):
528 pre_checksum = host.file_hash(self.target)
529 service = manager.get_service(service_name)
530- context = {}
531+ context = {'ctx': {}}
532 for ctx in service.get('required_data', []):
533 context.update(ctx)
534- templating.render(self.source, self.target, context,
535- self.owner, self.group, self.perms)
536+ context['ctx'].update(ctx)
537+
538+ result = templating.render(self.source, self.target, context,
539+ self.owner, self.group, self.perms,
540+ template_loader=self.template_loader)
541 if self.on_change_action:
542 if pre_checksum == host.file_hash(self.target):
543 hookenv.log(
544@@ -278,6 +285,8 @@
545 else:
546 self.on_change_action()
547
548+ return result
549+
550
551 # Convenience aliases for templates
552 render_template = template = TemplateCallback
553
554=== modified file 'charmhelpers/core/templating.py'
555--- charmhelpers/core/templating.py 2015-02-25 00:24:56 +0000
556+++ charmhelpers/core/templating.py 2016-01-11 18:37:30 +0000
557@@ -21,13 +21,14 @@
558
559
560 def render(source, target, context, owner='root', group='root',
561- perms=0o444, templates_dir=None, encoding='UTF-8'):
562+ perms=0o444, templates_dir=None, encoding='UTF-8', template_loader=None):
563 """
564 Render a template.
565
566 The `source` path, if not absolute, is relative to the `templates_dir`.
567
568- The `target` path should be absolute.
569+ The `target` path should be absolute. It can also be `None`, in which
570+ case no file will be written.
571
572 The context should be a dict containing the values to be replaced in the
573 template.
574@@ -36,6 +37,9 @@
575
576 If omitted, `templates_dir` defaults to the `templates` folder in the charm.
577
578+ The rendered template will be written to the file as well as being returned
579+ as a string.
580+
581 Note: Using this requires python-jinja2; if it is not installed, calling
582 this will attempt to use charmhelpers.fetch.apt_install to install it.
583 """
584@@ -52,17 +56,26 @@
585 apt_install('python-jinja2', fatal=True)
586 from jinja2 import FileSystemLoader, Environment, exceptions
587
588- if templates_dir is None:
589- templates_dir = os.path.join(hookenv.charm_dir(), 'templates')
590- loader = Environment(loader=FileSystemLoader(templates_dir))
591+ if template_loader:
592+ template_env = Environment(loader=template_loader)
593+ else:
594+ if templates_dir is None:
595+ templates_dir = os.path.join(hookenv.charm_dir(), 'templates')
596+ template_env = Environment(loader=FileSystemLoader(templates_dir))
597 try:
598 source = source
599- template = loader.get_template(source)
600+ template = template_env.get_template(source)
601 except exceptions.TemplateNotFound as e:
602 hookenv.log('Could not load template %s from %s.' %
603 (source, templates_dir),
604 level=hookenv.ERROR)
605 raise e
606 content = template.render(context)
607- host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
608- host.write_file(target, content.encode(encoding), owner, group, perms)
609+ if target is not None:
610+ target_dir = os.path.dirname(target)
611+ if not os.path.exists(target_dir):
612+ # This is a terrible default directory permission, as the file
613+ # or its siblings will often contain secrets.
614+ host.mkdir(os.path.dirname(target), owner, group, perms=0o755)
615+ host.write_file(target, content.encode(encoding), owner, group, perms)
616+ return content
617
618=== modified file 'charmhelpers/fetch/__init__.py'
619--- charmhelpers/fetch/__init__.py 2015-08-19 00:54:50 +0000
620+++ charmhelpers/fetch/__init__.py 2016-01-11 18:37:30 +0000
621@@ -98,6 +98,14 @@
622 'liberty/proposed': 'trusty-proposed/liberty',
623 'trusty-liberty/proposed': 'trusty-proposed/liberty',
624 'trusty-proposed/liberty': 'trusty-proposed/liberty',
625+ # Mitaka
626+ 'mitaka': 'trusty-updates/mitaka',
627+ 'trusty-mitaka': 'trusty-updates/mitaka',
628+ 'trusty-mitaka/updates': 'trusty-updates/mitaka',
629+ 'trusty-updates/mitaka': 'trusty-updates/mitaka',
630+ 'mitaka/proposed': 'trusty-proposed/mitaka',
631+ 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
632+ 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
633 }
634
635 # The order of this list is very important. Handlers should be listed in from
636@@ -225,12 +233,12 @@
637
638 def apt_mark(packages, mark, fatal=False):
639 """Flag one or more packages using apt-mark"""
640+ log("Marking {} as {}".format(packages, mark))
641 cmd = ['apt-mark', mark]
642 if isinstance(packages, six.string_types):
643 cmd.append(packages)
644 else:
645 cmd.extend(packages)
646- log("Holding {}".format(packages))
647
648 if fatal:
649 subprocess.check_call(cmd, universal_newlines=True)
650@@ -411,7 +419,7 @@
651 importlib.import_module(package),
652 classname)
653 plugin_list.append(handler_class())
654- except (ImportError, AttributeError):
655+ except NotImplementedError:
656 # Skip missing plugins so that they can be ommitted from
657 # installation if desired
658 log("FetchHandler {} not found, skipping plugin".format(
659
660=== modified file 'charmhelpers/fetch/archiveurl.py'
661--- charmhelpers/fetch/archiveurl.py 2015-08-19 00:54:50 +0000
662+++ charmhelpers/fetch/archiveurl.py 2016-01-11 18:37:30 +0000
663@@ -108,7 +108,7 @@
664 install_opener(opener)
665 response = urlopen(source)
666 try:
667- with open(dest, 'w') as dest_file:
668+ with open(dest, 'wb') as dest_file:
669 dest_file.write(response.read())
670 except Exception as e:
671 if os.path.isfile(dest):
672
673=== modified file 'charmhelpers/fetch/bzrurl.py'
674--- charmhelpers/fetch/bzrurl.py 2015-02-25 00:24:56 +0000
675+++ charmhelpers/fetch/bzrurl.py 2016-01-11 18:37:30 +0000
676@@ -15,60 +15,50 @@
677 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
678
679 import os
680+from subprocess import check_call
681 from charmhelpers.fetch import (
682 BaseFetchHandler,
683- UnhandledSource
684+ UnhandledSource,
685+ filter_installed_packages,
686+ apt_install,
687 )
688 from charmhelpers.core.host import mkdir
689
690-import six
691-if six.PY3:
692- raise ImportError('bzrlib does not support Python3')
693
694-try:
695- from bzrlib.branch import Branch
696- from bzrlib import bzrdir, workingtree, errors
697-except ImportError:
698- from charmhelpers.fetch import apt_install
699- apt_install("python-bzrlib")
700- from bzrlib.branch import Branch
701- from bzrlib import bzrdir, workingtree, errors
702+if filter_installed_packages(['bzr']) != []:
703+ apt_install(['bzr'])
704+ if filter_installed_packages(['bzr']) != []:
705+ raise NotImplementedError('Unable to install bzr')
706
707
708 class BzrUrlFetchHandler(BaseFetchHandler):
709 """Handler for bazaar branches via generic and lp URLs"""
710 def can_handle(self, source):
711 url_parts = self.parse_url(source)
712- if url_parts.scheme not in ('bzr+ssh', 'lp'):
713+ if url_parts.scheme not in ('bzr+ssh', 'lp', ''):
714 return False
715+ elif not url_parts.scheme:
716+ return os.path.exists(os.path.join(source, '.bzr'))
717 else:
718 return True
719
720 def branch(self, source, dest):
721- url_parts = self.parse_url(source)
722- # If we use lp:branchname scheme we need to load plugins
723 if not self.can_handle(source):
724 raise UnhandledSource("Cannot handle {}".format(source))
725- if url_parts.scheme == "lp":
726- from bzrlib.plugin import load_plugins
727- load_plugins()
728- try:
729- local_branch = bzrdir.BzrDir.create_branch_convenience(dest)
730- except errors.AlreadyControlDirError:
731- local_branch = Branch.open(dest)
732- try:
733- remote_branch = Branch.open(source)
734- remote_branch.push(local_branch)
735- tree = workingtree.WorkingTree.open(dest)
736- tree.update()
737- except Exception as e:
738- raise e
739+ if os.path.exists(dest):
740+ check_call(['bzr', 'pull', '--overwrite', '-d', dest, source])
741+ else:
742+ check_call(['bzr', 'branch', source, dest])
743
744- def install(self, source):
745+ def install(self, source, dest=None):
746 url_parts = self.parse_url(source)
747 branch_name = url_parts.path.strip("/").split("/")[-1]
748- dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
749- branch_name)
750+ if dest:
751+ dest_dir = os.path.join(dest, branch_name)
752+ else:
753+ dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
754+ branch_name)
755+
756 if not os.path.exists(dest_dir):
757 mkdir(dest_dir, perms=0o755)
758 try:
759
760=== modified file 'charmhelpers/fetch/giturl.py'
761--- charmhelpers/fetch/giturl.py 2015-08-19 00:54:50 +0000
762+++ charmhelpers/fetch/giturl.py 2016-01-11 18:37:30 +0000
763@@ -15,24 +15,18 @@
764 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
765
766 import os
767+from subprocess import check_call
768 from charmhelpers.fetch import (
769 BaseFetchHandler,
770- UnhandledSource
771+ UnhandledSource,
772+ filter_installed_packages,
773+ apt_install,
774 )
775-from charmhelpers.core.host import mkdir
776-
777-import six
778-if six.PY3:
779- raise ImportError('GitPython does not support Python 3')
780-
781-try:
782- from git import Repo
783-except ImportError:
784- from charmhelpers.fetch import apt_install
785- apt_install("python-git")
786- from git import Repo
787-
788-from git.exc import GitCommandError # noqa E402
789+
790+if filter_installed_packages(['git']) != []:
791+ apt_install(['git'])
792+ if filter_installed_packages(['git']) != []:
793+ raise NotImplementedError('Unable to install git')
794
795
796 class GitUrlFetchHandler(BaseFetchHandler):
797@@ -40,19 +34,24 @@
798 def can_handle(self, source):
799 url_parts = self.parse_url(source)
800 # TODO (mattyw) no support for ssh git@ yet
801- if url_parts.scheme not in ('http', 'https', 'git'):
802+ if url_parts.scheme not in ('http', 'https', 'git', ''):
803 return False
804+ elif not url_parts.scheme:
805+ return os.path.exists(os.path.join(source, '.git'))
806 else:
807 return True
808
809- def clone(self, source, dest, branch, depth=None):
810+ def clone(self, source, dest, branch="master", depth=None):
811 if not self.can_handle(source):
812 raise UnhandledSource("Cannot handle {}".format(source))
813
814- if depth:
815- Repo.clone_from(source, dest, branch=branch, depth=depth)
816+ if os.path.exists(dest):
817+ cmd = ['git', '-C', dest, 'pull', source, branch]
818 else:
819- Repo.clone_from(source, dest, branch=branch)
820+ cmd = ['git', 'clone', source, dest, '--branch', branch]
821+ if depth:
822+ cmd.extend(['--depth', depth])
823+ check_call(cmd)
824
825 def install(self, source, branch="master", dest=None, depth=None):
826 url_parts = self.parse_url(source)
827@@ -62,12 +61,8 @@
828 else:
829 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
830 branch_name)
831- if not os.path.exists(dest_dir):
832- mkdir(dest_dir, perms=0o755)
833 try:
834 self.clone(source, dest_dir, branch, depth)
835- except GitCommandError as e:
836- raise UnhandledSource(e)
837 except OSError as e:
838 raise UnhandledSource(e.strerror)
839 return dest_dir

Subscribers

People subscribed via source and target branches