Merge lp:~junaidali/charms/trusty/neutron-api-plumgrid/resynced into lp:~plumgrid-team/charms/trusty/neutron-api-plumgrid/trunk

Proposed by Junaid Ali
Status: Merged
Merged at revision: 22
Proposed branch: lp:~junaidali/charms/trusty/neutron-api-plumgrid/resynced
Merge into: lp:~plumgrid-team/charms/trusty/neutron-api-plumgrid/trunk
Diff against target: 740 lines (+336/-132)
7 files modified
hooks/charmhelpers/contrib/network/ip.py (+22/-5)
hooks/charmhelpers/contrib/openstack/context.py (+5/-83)
hooks/charmhelpers/contrib/openstack/exceptions.py (+6/-0)
hooks/charmhelpers/contrib/openstack/utils.py (+184/-21)
hooks/charmhelpers/contrib/storage/linux/ceph.py (+41/-0)
hooks/charmhelpers/core/host.py (+70/-23)
hooks/charmhelpers/fetch/__init__.py (+8/-0)
To merge this branch: bzr merge lp:~junaidali/charms/trusty/neutron-api-plumgrid/resynced
Reviewer Review Type Date Requested Status
Bilal Baqar Pending
Review via email: mp+296483@code.launchpad.net
To post a comment you must log in.
22. By Junaid Ali

Resynced charm-helpers

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'hooks/charmhelpers/contrib/network/ip.py'
2--- hooks/charmhelpers/contrib/network/ip.py 2016-04-22 04:35:32 +0000
3+++ hooks/charmhelpers/contrib/network/ip.py 2016-06-04 02:32:00 +0000
4@@ -214,7 +214,16 @@
5
6 def get_iface_addr(iface='eth0', inet_type='AF_INET', inc_aliases=False,
7 fatal=True, exc_list=None):
8- """Return the assigned IP address for a given interface, if any."""
9+ """Return the assigned IP address for a given interface, if any.
10+
11+ :param iface: network interface on which address(es) are expected to
12+ be found.
13+ :param inet_type: inet address family
14+ :param inc_aliases: include alias interfaces in search
15+ :param fatal: if True, raise exception if address not found
16+ :param exc_list: list of addresses to ignore
17+ :return: list of ip addresses
18+ """
19 # Extract nic if passed /dev/ethX
20 if '/' in iface:
21 iface = iface.split('/')[-1]
22@@ -315,6 +324,14 @@
23 We currently only support scope global IPv6 addresses i.e. non-temporary
24 addresses. If no global IPv6 address is found, return the first one found
25 in the ipv6 address list.
26+
27+ :param iface: network interface on which ipv6 address(es) are expected to
28+ be found.
29+ :param inc_aliases: include alias interfaces in search
30+ :param fatal: if True, raise exception if address not found
31+ :param exc_list: list of addresses to ignore
32+ :param dynamic_only: only recognise dynamic addresses
33+ :return: list of ipv6 addresses
34 """
35 addresses = get_iface_addr(iface=iface, inet_type='AF_INET6',
36 inc_aliases=inc_aliases, fatal=fatal,
37@@ -336,7 +353,7 @@
38 cmd = ['ip', 'addr', 'show', iface]
39 out = subprocess.check_output(cmd).decode('UTF-8')
40 if dynamic_only:
41- key = re.compile("inet6 (.+)/[0-9]+ scope global dynamic.*")
42+ key = re.compile("inet6 (.+)/[0-9]+ scope global.* dynamic.*")
43 else:
44 key = re.compile("inet6 (.+)/[0-9]+ scope global.*")
45
46@@ -388,10 +405,10 @@
47 Returns True if address is a valid IP address.
48 """
49 try:
50- # Test to see if already an IPv4 address
51- socket.inet_aton(address)
52+ # Test to see if already an IPv4/IPv6 address
53+ address = netaddr.IPAddress(address)
54 return True
55- except socket.error:
56+ except netaddr.AddrFormatError:
57 return False
58
59
60
61=== modified file 'hooks/charmhelpers/contrib/openstack/context.py'
62--- hooks/charmhelpers/contrib/openstack/context.py 2016-04-22 04:35:32 +0000
63+++ hooks/charmhelpers/contrib/openstack/context.py 2016-06-04 02:32:00 +0000
64@@ -23,7 +23,6 @@
65 from subprocess import check_call, CalledProcessError
66
67 import six
68-import yaml
69
70 from charmhelpers.fetch import (
71 apt_install,
72@@ -50,6 +49,7 @@
73
74 from charmhelpers.core.sysctl import create as sysctl_create
75 from charmhelpers.core.strutils import bool_from_string
76+from charmhelpers.contrib.openstack.exceptions import OSContextError
77
78 from charmhelpers.core.host import (
79 get_bond_master,
80@@ -88,7 +88,10 @@
81 is_address_in_network,
82 is_bridge_member,
83 )
84-from charmhelpers.contrib.openstack.utils import get_host_ip
85+from charmhelpers.contrib.openstack.utils import (
86+ config_flags_parser,
87+ get_host_ip,
88+)
89 from charmhelpers.core.unitdata import kv
90
91 try:
92@@ -101,10 +104,6 @@
93 ADDRESS_TYPES = ['admin', 'internal', 'public']
94
95
96-class OSContextError(Exception):
97- pass
98-
99-
100 def ensure_packages(packages):
101 """Install but do not upgrade required plugin packages."""
102 required = filter_installed_packages(packages)
103@@ -125,83 +124,6 @@
104 return True
105
106
107-def config_flags_parser(config_flags):
108- """Parses config flags string into dict.
109-
110- This parsing method supports a few different formats for the config
111- flag values to be parsed:
112-
113- 1. A string in the simple format of key=value pairs, with the possibility
114- of specifying multiple key value pairs within the same string. For
115- example, a string in the format of 'key1=value1, key2=value2' will
116- return a dict of:
117-
118- {'key1': 'value1',
119- 'key2': 'value2'}.
120-
121- 2. A string in the above format, but supporting a comma-delimited list
122- of values for the same key. For example, a string in the format of
123- 'key1=value1, key2=value3,value4,value5' will return a dict of:
124-
125- {'key1', 'value1',
126- 'key2', 'value2,value3,value4'}
127-
128- 3. A string containing a colon character (:) prior to an equal
129- character (=) will be treated as yaml and parsed as such. This can be
130- used to specify more complex key value pairs. For example,
131- a string in the format of 'key1: subkey1=value1, subkey2=value2' will
132- return a dict of:
133-
134- {'key1', 'subkey1=value1, subkey2=value2'}
135-
136- The provided config_flags string may be a list of comma-separated values
137- which themselves may be comma-separated list of values.
138- """
139- # If we find a colon before an equals sign then treat it as yaml.
140- # Note: limit it to finding the colon first since this indicates assignment
141- # for inline yaml.
142- colon = config_flags.find(':')
143- equals = config_flags.find('=')
144- if colon > 0:
145- if colon < equals or equals < 0:
146- return yaml.safe_load(config_flags)
147-
148- if config_flags.find('==') >= 0:
149- log("config_flags is not in expected format (key=value)", level=ERROR)
150- raise OSContextError
151-
152- # strip the following from each value.
153- post_strippers = ' ,'
154- # we strip any leading/trailing '=' or ' ' from the string then
155- # split on '='.
156- split = config_flags.strip(' =').split('=')
157- limit = len(split)
158- flags = {}
159- for i in range(0, limit - 1):
160- current = split[i]
161- next = split[i + 1]
162- vindex = next.rfind(',')
163- if (i == limit - 2) or (vindex < 0):
164- value = next
165- else:
166- value = next[:vindex]
167-
168- if i == 0:
169- key = current
170- else:
171- # if this not the first entry, expect an embedded key.
172- index = current.rfind(',')
173- if index < 0:
174- log("Invalid config value(s) at index %s" % (i), level=ERROR)
175- raise OSContextError
176- key = current[index + 1:]
177-
178- # Add to collection.
179- flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
180-
181- return flags
182-
183-
184 class OSContextGenerator(object):
185 """Base class for all context generators."""
186 interfaces = []
187
188=== added file 'hooks/charmhelpers/contrib/openstack/exceptions.py'
189--- hooks/charmhelpers/contrib/openstack/exceptions.py 1970-01-01 00:00:00 +0000
190+++ hooks/charmhelpers/contrib/openstack/exceptions.py 2016-06-04 02:32:00 +0000
191@@ -0,0 +1,6 @@
192+class OSContextError(Exception):
193+ """Raised when an error occurs during context generation.
194+
195+ This exception is principally used in contrib.openstack.context
196+ """
197+ pass
198
199=== modified file 'hooks/charmhelpers/contrib/openstack/utils.py'
200--- hooks/charmhelpers/contrib/openstack/utils.py 2016-04-22 04:35:32 +0000
201+++ hooks/charmhelpers/contrib/openstack/utils.py 2016-06-04 02:32:00 +0000
202@@ -25,6 +25,7 @@
203 import re
204 import itertools
205 import functools
206+import shutil
207
208 import six
209 import tempfile
210@@ -46,6 +47,7 @@
211 charm_dir,
212 DEBUG,
213 INFO,
214+ ERROR,
215 related_units,
216 relation_ids,
217 relation_set,
218@@ -82,6 +84,7 @@
219 from charmhelpers.fetch import apt_install, apt_cache, install_remote
220 from charmhelpers.contrib.storage.linux.utils import is_block_device, zap_disk
221 from charmhelpers.contrib.storage.linux.loopback import ensure_loopback_device
222+from charmhelpers.contrib.openstack.exceptions import OSContextError
223
224 CLOUD_ARCHIVE_URL = "http://ubuntu-cloud.archive.canonical.com/ubuntu"
225 CLOUD_ARCHIVE_KEY_ID = '5EDB1B62EC4926EA'
226@@ -100,6 +103,8 @@
227 ('vivid', 'kilo'),
228 ('wily', 'liberty'),
229 ('xenial', 'mitaka'),
230+ ('yakkety', 'newton'),
231+ ('zebra', 'ocata'), # TODO: upload with real Z name
232 ])
233
234
235@@ -114,6 +119,8 @@
236 ('2015.1', 'kilo'),
237 ('2015.2', 'liberty'),
238 ('2016.1', 'mitaka'),
239+ ('2016.2', 'newton'),
240+ ('2017.1', 'ocata'),
241 ])
242
243 # The ugly duckling - must list releases oldest to newest
244@@ -138,46 +145,65 @@
245 ['2.3.0', '2.4.0', '2.5.0']),
246 ('mitaka',
247 ['2.5.0', '2.6.0', '2.7.0']),
248+ ('newton',
249+ ['2.8.0']),
250 ])
251
252 # >= Liberty version->codename mapping
253 PACKAGE_CODENAMES = {
254 'nova-common': OrderedDict([
255- ('12.0', 'liberty'),
256- ('13.0', 'mitaka'),
257+ ('12', 'liberty'),
258+ ('13', 'mitaka'),
259+ ('14', 'newton'),
260+ ('15', 'ocata'),
261 ]),
262 'neutron-common': OrderedDict([
263- ('7.0', 'liberty'),
264- ('8.0', 'mitaka'),
265+ ('7', 'liberty'),
266+ ('8', 'mitaka'),
267+ ('9', 'newton'),
268+ ('10', 'ocata'),
269 ]),
270 'cinder-common': OrderedDict([
271- ('7.0', 'liberty'),
272- ('8.0', 'mitaka'),
273+ ('7', 'liberty'),
274+ ('8', 'mitaka'),
275+ ('9', 'newton'),
276+ ('10', 'ocata'),
277 ]),
278 'keystone': OrderedDict([
279- ('8.0', 'liberty'),
280- ('8.1', 'liberty'),
281- ('9.0', 'mitaka'),
282+ ('8', 'liberty'),
283+ ('9', 'mitaka'),
284+ ('10', 'newton'),
285+ ('11', 'ocata'),
286 ]),
287 'horizon-common': OrderedDict([
288- ('8.0', 'liberty'),
289- ('9.0', 'mitaka'),
290+ ('8', 'liberty'),
291+ ('9', 'mitaka'),
292+ ('10', 'newton'),
293+ ('11', 'ocata'),
294 ]),
295 'ceilometer-common': OrderedDict([
296- ('5.0', 'liberty'),
297- ('6.0', 'mitaka'),
298+ ('5', 'liberty'),
299+ ('6', 'mitaka'),
300+ ('7', 'newton'),
301+ ('8', 'ocata'),
302 ]),
303 'heat-common': OrderedDict([
304- ('5.0', 'liberty'),
305- ('6.0', 'mitaka'),
306+ ('5', 'liberty'),
307+ ('6', 'mitaka'),
308+ ('7', 'newton'),
309+ ('8', 'ocata'),
310 ]),
311 'glance-common': OrderedDict([
312- ('11.0', 'liberty'),
313- ('12.0', 'mitaka'),
314+ ('11', 'liberty'),
315+ ('12', 'mitaka'),
316+ ('13', 'newton'),
317+ ('14', 'ocata'),
318 ]),
319 'openstack-dashboard': OrderedDict([
320- ('8.0', 'liberty'),
321- ('9.0', 'mitaka'),
322+ ('8', 'liberty'),
323+ ('9', 'mitaka'),
324+ ('10', 'newton'),
325+ ('11', 'ocata'),
326 ]),
327 }
328
329@@ -253,6 +279,7 @@
330 def get_swift_codename(version):
331 '''Determine OpenStack codename that corresponds to swift version.'''
332 codenames = [k for k, v in six.iteritems(SWIFT_CODENAMES) if version in v]
333+
334 if len(codenames) > 1:
335 # If more than one release codename contains this version we determine
336 # the actual codename based on the highest available install source.
337@@ -264,6 +291,16 @@
338 return codename
339 elif len(codenames) == 1:
340 return codenames[0]
341+
342+ # NOTE: fallback - attempt to match with just major.minor version
343+ match = re.match('^(\d+)\.(\d+)', version)
344+ if match:
345+ major_minor_version = match.group(0)
346+ for codename, versions in six.iteritems(SWIFT_CODENAMES):
347+ for release_version in versions:
348+ if release_version.startswith(major_minor_version):
349+ return codename
350+
351 return None
352
353
354@@ -302,10 +339,13 @@
355 if match:
356 vers = match.group(0)
357
358+ # Generate a major version number for newer semantic
359+ # versions of openstack projects
360+ major_vers = vers.split('.')[0]
361 # >= Liberty independent project versions
362 if (package in PACKAGE_CODENAMES and
363- vers in PACKAGE_CODENAMES[package]):
364- return PACKAGE_CODENAMES[package][vers]
365+ major_vers in PACKAGE_CODENAMES[package]):
366+ return PACKAGE_CODENAMES[package][major_vers]
367 else:
368 # < Liberty co-ordinated project versions
369 try:
370@@ -465,6 +505,9 @@
371 'mitaka': 'trusty-updates/mitaka',
372 'mitaka/updates': 'trusty-updates/mitaka',
373 'mitaka/proposed': 'trusty-proposed/mitaka',
374+ 'newton': 'xenial-updates/newton',
375+ 'newton/updates': 'xenial-updates/newton',
376+ 'newton/proposed': 'xenial-proposed/newton',
377 }
378
379 try:
380@@ -857,6 +900,47 @@
381 return None
382
383
384+def git_generate_systemd_init_files(templates_dir):
385+ """
386+ Generate systemd init files.
387+
388+ Generates and installs systemd init units and script files based on the
389+ *.init.in files contained in the templates_dir directory.
390+
391+ This code is based on the openstack-pkg-tools package and its init
392+ script generation, which is used by the OpenStack packages.
393+ """
394+ for f in os.listdir(templates_dir):
395+ if f.endswith(".init.in"):
396+ init_in_file = f
397+ init_file = f[:-8]
398+ service_file = "{}.service".format(init_file)
399+
400+ init_in_source = os.path.join(templates_dir, init_in_file)
401+ init_source = os.path.join(templates_dir, init_file)
402+ service_source = os.path.join(templates_dir, service_file)
403+
404+ init_dest = os.path.join('/etc/init.d', init_file)
405+ service_dest = os.path.join('/lib/systemd/system', service_file)
406+
407+ shutil.copyfile(init_in_source, init_source)
408+ with open(init_source, 'a') as outfile:
409+ template = '/usr/share/openstack-pkg-tools/init-script-template'
410+ with open(template) as infile:
411+ outfile.write('\n\n{}'.format(infile.read()))
412+
413+ cmd = ['pkgos-gen-systemd-unit', init_in_source]
414+ subprocess.check_call(cmd)
415+
416+ if os.path.exists(init_dest):
417+ os.remove(init_dest)
418+ if os.path.exists(service_dest):
419+ os.remove(service_dest)
420+ shutil.move(init_source, init_dest)
421+ shutil.move(service_source, service_dest)
422+ os.chmod(init_dest, 0o755)
423+
424+
425 def os_workload_status(configs, required_interfaces, charm_func=None):
426 """
427 Decorator to set workload status based on complete contexts
428@@ -1573,3 +1657,82 @@
429 restart_functions)
430 return wrapped_f
431 return wrap
432+
433+
434+def config_flags_parser(config_flags):
435+ """Parses config flags string into dict.
436+
437+ This parsing method supports a few different formats for the config
438+ flag values to be parsed:
439+
440+ 1. A string in the simple format of key=value pairs, with the possibility
441+ of specifying multiple key value pairs within the same string. For
442+ example, a string in the format of 'key1=value1, key2=value2' will
443+ return a dict of:
444+
445+ {'key1': 'value1',
446+ 'key2': 'value2'}.
447+
448+ 2. A string in the above format, but supporting a comma-delimited list
449+ of values for the same key. For example, a string in the format of
450+ 'key1=value1, key2=value3,value4,value5' will return a dict of:
451+
452+ {'key1', 'value1',
453+ 'key2', 'value2,value3,value4'}
454+
455+ 3. A string containing a colon character (:) prior to an equal
456+ character (=) will be treated as yaml and parsed as such. This can be
457+ used to specify more complex key value pairs. For example,
458+ a string in the format of 'key1: subkey1=value1, subkey2=value2' will
459+ return a dict of:
460+
461+ {'key1', 'subkey1=value1, subkey2=value2'}
462+
463+ The provided config_flags string may be a list of comma-separated values
464+ which themselves may be comma-separated list of values.
465+ """
466+ # If we find a colon before an equals sign then treat it as yaml.
467+ # Note: limit it to finding the colon first since this indicates assignment
468+ # for inline yaml.
469+ colon = config_flags.find(':')
470+ equals = config_flags.find('=')
471+ if colon > 0:
472+ if colon < equals or equals < 0:
473+ return yaml.safe_load(config_flags)
474+
475+ if config_flags.find('==') >= 0:
476+ juju_log("config_flags is not in expected format (key=value)",
477+ level=ERROR)
478+ raise OSContextError
479+
480+ # strip the following from each value.
481+ post_strippers = ' ,'
482+ # we strip any leading/trailing '=' or ' ' from the string then
483+ # split on '='.
484+ split = config_flags.strip(' =').split('=')
485+ limit = len(split)
486+ flags = {}
487+ for i in range(0, limit - 1):
488+ current = split[i]
489+ next = split[i + 1]
490+ vindex = next.rfind(',')
491+ if (i == limit - 2) or (vindex < 0):
492+ value = next
493+ else:
494+ value = next[:vindex]
495+
496+ if i == 0:
497+ key = current
498+ else:
499+ # if this not the first entry, expect an embedded key.
500+ index = current.rfind(',')
501+ if index < 0:
502+ juju_log("Invalid config value(s) at index %s" % (i),
503+ level=ERROR)
504+ raise OSContextError
505+ key = current[index + 1:]
506+
507+ # Add to collection.
508+ flags[key.strip(post_strippers)] = value.rstrip(post_strippers)
509+
510+ return flags
511
512=== modified file 'hooks/charmhelpers/contrib/storage/linux/ceph.py'
513--- hooks/charmhelpers/contrib/storage/linux/ceph.py 2016-04-22 04:35:32 +0000
514+++ hooks/charmhelpers/contrib/storage/linux/ceph.py 2016-06-04 02:32:00 +0000
515@@ -40,6 +40,7 @@
516 CalledProcessError,
517 )
518 from charmhelpers.core.hookenv import (
519+ config,
520 local_unit,
521 relation_get,
522 relation_ids,
523@@ -64,6 +65,7 @@
524 )
525
526 from charmhelpers.core.kernel import modprobe
527+from charmhelpers.contrib.openstack.utils import config_flags_parser
528
529 KEYRING = '/etc/ceph/ceph.client.{}.keyring'
530 KEYFILE = '/etc/ceph/ceph.client.{}.key'
531@@ -1204,3 +1206,42 @@
532 for rid in relation_ids(relation):
533 log('Sending request {}'.format(request.request_id), level=DEBUG)
534 relation_set(relation_id=rid, broker_req=request.request)
535+
536+
537+class CephConfContext(object):
538+ """Ceph config (ceph.conf) context.
539+
540+ Supports user-provided Ceph configuration settings. Use can provide a
541+ dictionary as the value for the config-flags charm option containing
542+ Ceph configuration settings keyede by their section in ceph.conf.
543+ """
544+ def __init__(self, permitted_sections=None):
545+ self.permitted_sections = permitted_sections or []
546+
547+ def __call__(self):
548+ conf = config('config-flags')
549+ if not conf:
550+ return {}
551+
552+ conf = config_flags_parser(conf)
553+ if type(conf) != dict:
554+ log("Provided config-flags is not a dictionary - ignoring",
555+ level=WARNING)
556+ return {}
557+
558+ permitted = self.permitted_sections
559+ if permitted:
560+ diff = set(conf.keys()).difference(set(permitted))
561+ if diff:
562+ log("Config-flags contains invalid keys '%s' - they will be "
563+ "ignored" % (', '.join(diff)), level=WARNING)
564+
565+ ceph_conf = {}
566+ for key in conf:
567+ if permitted and key not in permitted:
568+ log("Ignoring key '%s'" % key, level=WARNING)
569+ continue
570+
571+ ceph_conf[key] = conf[key]
572+
573+ return ceph_conf
574
575=== modified file 'hooks/charmhelpers/core/host.py'
576--- hooks/charmhelpers/core/host.py 2016-04-22 04:35:32 +0000
577+++ hooks/charmhelpers/core/host.py 2016-06-04 02:32:00 +0000
578@@ -128,11 +128,8 @@
579 return subprocess.call(cmd) == 0
580
581
582-def systemv_services_running():
583- output = subprocess.check_output(
584- ['service', '--status-all'],
585- stderr=subprocess.STDOUT).decode('UTF-8')
586- return [row.split()[-1] for row in output.split('\n') if '[ + ]' in row]
587+_UPSTART_CONF = "/etc/init/{}.conf"
588+_INIT_D_CONF = "/etc/init.d/{}"
589
590
591 def service_running(service_name):
592@@ -140,22 +137,22 @@
593 if init_is_systemd():
594 return service('is-active', service_name)
595 else:
596- try:
597- output = subprocess.check_output(
598- ['service', service_name, 'status'],
599- stderr=subprocess.STDOUT).decode('UTF-8')
600- except subprocess.CalledProcessError:
601- return False
602- else:
603- # This works for upstart scripts where the 'service' command
604- # returns a consistent string to represent running 'start/running'
605- if ("start/running" in output or "is running" in output or
606- "up and running" in output):
607- return True
608+ if os.path.exists(_UPSTART_CONF.format(service_name)):
609+ try:
610+ output = subprocess.check_output(
611+ ['status', service_name],
612+ stderr=subprocess.STDOUT).decode('UTF-8')
613+ except subprocess.CalledProcessError:
614+ return False
615+ else:
616+ # This works for upstart scripts where the 'service' command
617+ # returns a consistent string to represent running 'start/running'
618+ if "start/running" in output:
619+ return True
620+ elif os.path.exists(_INIT_D_CONF.format(service_name)):
621 # Check System V scripts init script return codes
622- if service_name in systemv_services_running():
623- return True
624- return False
625+ return service('status', service_name)
626+ return False
627
628
629 def service_available(service_name):
630@@ -179,7 +176,7 @@
631
632
633 def adduser(username, password=None, shell='/bin/bash', system_user=False,
634- primary_group=None, secondary_groups=None):
635+ primary_group=None, secondary_groups=None, uid=None):
636 """Add a user to the system.
637
638 Will log but otherwise succeed if the user already exists.
639@@ -190,15 +187,21 @@
640 :param bool system_user: Whether to create a login or system user
641 :param str primary_group: Primary group for user; defaults to username
642 :param list secondary_groups: Optional list of additional groups
643+ :param int uid: UID for user being created
644
645 :returns: The password database entry struct, as returned by `pwd.getpwnam`
646 """
647 try:
648 user_info = pwd.getpwnam(username)
649 log('user {0} already exists!'.format(username))
650+ if uid:
651+ user_info = pwd.getpwuid(int(uid))
652+ log('user with uid {0} already exists!'.format(uid))
653 except KeyError:
654 log('creating user {0}'.format(username))
655 cmd = ['useradd']
656+ if uid:
657+ cmd.extend(['--uid', str(uid)])
658 if system_user or password is None:
659 cmd.append('--system')
660 else:
661@@ -233,14 +236,58 @@
662 return user_exists
663
664
665-def add_group(group_name, system_group=False):
666- """Add a group to the system"""
667+def uid_exists(uid):
668+ """Check if a uid exists"""
669+ try:
670+ pwd.getpwuid(uid)
671+ uid_exists = True
672+ except KeyError:
673+ uid_exists = False
674+ return uid_exists
675+
676+
677+def group_exists(groupname):
678+ """Check if a group exists"""
679+ try:
680+ grp.getgrnam(groupname)
681+ group_exists = True
682+ except KeyError:
683+ group_exists = False
684+ return group_exists
685+
686+
687+def gid_exists(gid):
688+ """Check if a gid exists"""
689+ try:
690+ grp.getgrgid(gid)
691+ gid_exists = True
692+ except KeyError:
693+ gid_exists = False
694+ return gid_exists
695+
696+
697+def add_group(group_name, system_group=False, gid=None):
698+ """Add a group to the system
699+
700+ Will log but otherwise succeed if the group already exists.
701+
702+ :param str group_name: group to create
703+ :param bool system_group: Create system group
704+ :param int gid: GID for user being created
705+
706+ :returns: The password database entry struct, as returned by `grp.getgrnam`
707+ """
708 try:
709 group_info = grp.getgrnam(group_name)
710 log('group {0} already exists!'.format(group_name))
711+ if gid:
712+ group_info = grp.getgrgid(gid)
713+ log('group with gid {0} already exists!'.format(gid))
714 except KeyError:
715 log('creating group {0}'.format(group_name))
716 cmd = ['addgroup']
717+ if gid:
718+ cmd.extend(['--gid', str(gid)])
719 if system_group:
720 cmd.append('--system')
721 else:
722
723=== modified file 'hooks/charmhelpers/fetch/__init__.py'
724--- hooks/charmhelpers/fetch/__init__.py 2016-02-27 19:51:32 +0000
725+++ hooks/charmhelpers/fetch/__init__.py 2016-06-04 02:32:00 +0000
726@@ -106,6 +106,14 @@
727 'mitaka/proposed': 'trusty-proposed/mitaka',
728 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
729 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
730+ # Newton
731+ 'newton': 'xenial-updates/newton',
732+ 'xenial-newton': 'xenial-updates/newton',
733+ 'xenial-newton/updates': 'xenial-updates/newton',
734+ 'xenial-updates/newton': 'xenial-updates/newton',
735+ 'newton/proposed': 'xenial-proposed/newton',
736+ 'xenial-newton/proposed': 'xenial-proposed/newton',
737+ 'xenial-proposed/newton': 'xenial-proposed/newton',
738 }
739
740 # The order of this list is very important. Handlers should be listed in from

Subscribers

People subscribed via source and target branches

to all changes: