Merge ~xavpaice/charm-nrpe:master into ~nrpe-charmers/charm-nrpe:master

Proposed by Xav Paice
Status: Rejected
Rejected by: Paul Gear
Proposed branch: ~xavpaice/charm-nrpe:master
Merge into: ~nrpe-charmers/charm-nrpe:master
Diff against target: 1007 lines (+851/-26)
11 files modified
charm-helpers-hooks.yaml (+1/-0)
hooks/charmhelpers/core/host.py (+168/-26)
hooks/charmhelpers/core/host_factory/__init__.py (+0/-0)
hooks/charmhelpers/core/host_factory/centos.py (+56/-0)
hooks/charmhelpers/core/host_factory/ubuntu.py (+56/-0)
hooks/charmhelpers/core/kernel_factory/__init__.py (+0/-0)
hooks/charmhelpers/core/kernel_factory/centos.py (+17/-0)
hooks/charmhelpers/core/kernel_factory/ubuntu.py (+13/-0)
hooks/charmhelpers/fetch/centos.py (+171/-0)
hooks/charmhelpers/fetch/ubuntu.py (+344/-0)
hooks/charmhelpers/osplatform.py (+25/-0)
Reviewer Review Type Date Requested Status
Paul Gear (community) Approve
Review via email: mp+317730@code.launchpad.net

Description of the change

Add osplatform to charmhelpers, and sync charmhelpers

To post a comment you must log in.
Revision history for this message
Paul Gear (paulgear) wrote :

LGTM

review: Approve
Revision history for this message
Paul Gear (paulgear) wrote :

Unmerged commits

a7a3906... by Xav Paice

[xavpaice] charmhelpers sync incl osplatform

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/charm-helpers-hooks.yaml b/charm-helpers-hooks.yaml
2index 7fd4ca6..a697bd9 100644
3--- a/charm-helpers-hooks.yaml
4+++ b/charm-helpers-hooks.yaml
5@@ -3,3 +3,4 @@ destination: hooks/charmhelpers
6 include:
7 - core
8 - fetch
9+ - osplatform
10diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py
11index 3638e65..edbb72f 100644
12--- a/hooks/charmhelpers/core/host.py
13+++ b/hooks/charmhelpers/core/host.py
14@@ -56,37 +56,136 @@ elif __platform__ == "centos":
15
16 UPDATEDB_PATH = '/etc/updatedb.conf'
17
18-def service_start(service_name):
19- """Start a system service"""
20- return service('start', service_name)
21+def service_start(service_name, **kwargs):
22+ """Start a system service.
23+
24+ The specified service name is managed via the system level init system.
25+ Some init systems (e.g. upstart) require that additional arguments be
26+ provided in order to directly control service instances whereas other init
27+ systems allow for addressing instances of a service directly by name (e.g.
28+ systemd).
29+
30+ The kwargs allow for the additional parameters to be passed to underlying
31+ init systems for those systems which require/allow for them. For example,
32+ the ceph-osd upstart script requires the id parameter to be passed along
33+ in order to identify which running daemon should be reloaded. The follow-
34+ ing example stops the ceph-osd service for instance id=4:
35+
36+ service_stop('ceph-osd', id=4)
37+
38+ :param service_name: the name of the service to stop
39+ :param **kwargs: additional parameters to pass to the init system when
40+ managing services. These will be passed as key=value
41+ parameters to the init system's commandline. kwargs
42+ are ignored for systemd enabled systems.
43+ """
44+ return service('start', service_name, **kwargs)
45+
46+
47+def service_stop(service_name, **kwargs):
48+ """Stop a system service.
49+
50+ The specified service name is managed via the system level init system.
51+ Some init systems (e.g. upstart) require that additional arguments be
52+ provided in order to directly control service instances whereas other init
53+ systems allow for addressing instances of a service directly by name (e.g.
54+ systemd).
55+
56+ The kwargs allow for the additional parameters to be passed to underlying
57+ init systems for those systems which require/allow for them. For example,
58+ the ceph-osd upstart script requires the id parameter to be passed along
59+ in order to identify which running daemon should be reloaded. The follow-
60+ ing example stops the ceph-osd service for instance id=4:
61+
62+ service_stop('ceph-osd', id=4)
63
64+ :param service_name: the name of the service to stop
65+ :param **kwargs: additional parameters to pass to the init system when
66+ managing services. These will be passed as key=value
67+ parameters to the init system's commandline. kwargs
68+ are ignored for systemd enabled systems.
69+ """
70+ return service('stop', service_name, **kwargs)
71+
72+
73+def service_restart(service_name, **kwargs):
74+ """Restart a system service.
75
76-def service_stop(service_name):
77- """Stop a system service"""
78- return service('stop', service_name)
79+ The specified service name is managed via the system level init system.
80+ Some init systems (e.g. upstart) require that additional arguments be
81+ provided in order to directly control service instances whereas other init
82+ systems allow for addressing instances of a service directly by name (e.g.
83+ systemd).
84
85+ The kwargs allow for the additional parameters to be passed to underlying
86+ init systems for those systems which require/allow for them. For example,
87+ the ceph-osd upstart script requires the id parameter to be passed along
88+ in order to identify which running daemon should be restarted. The follow-
89+ ing example restarts the ceph-osd service for instance id=4:
90
91-def service_restart(service_name):
92- """Restart a system service"""
93+ service_restart('ceph-osd', id=4)
94+
95+ :param service_name: the name of the service to restart
96+ :param **kwargs: additional parameters to pass to the init system when
97+ managing services. These will be passed as key=value
98+ parameters to the init system's commandline. kwargs
99+ are ignored for init systems not allowing additional
100+ parameters via the commandline (systemd).
101+ """
102 return service('restart', service_name)
103
104
105-def service_reload(service_name, restart_on_failure=False):
106+def service_reload(service_name, restart_on_failure=False, **kwargs):
107 """Reload a system service, optionally falling back to restart if
108- reload fails"""
109- service_result = service('reload', service_name)
110+ reload fails.
111+
112+ The specified service name is managed via the system level init system.
113+ Some init systems (e.g. upstart) require that additional arguments be
114+ provided in order to directly control service instances whereas other init
115+ systems allow for addressing instances of a service directly by name (e.g.
116+ systemd).
117+
118+ The kwargs allow for the additional parameters to be passed to underlying
119+ init systems for those systems which require/allow for them. For example,
120+ the ceph-osd upstart script requires the id parameter to be passed along
121+ in order to identify which running daemon should be reloaded. The follow-
122+ ing example restarts the ceph-osd service for instance id=4:
123+
124+ service_reload('ceph-osd', id=4)
125+
126+ :param service_name: the name of the service to reload
127+ :param restart_on_failure: boolean indicating whether to fallback to a
128+ restart if the reload fails.
129+ :param **kwargs: additional parameters to pass to the init system when
130+ managing services. These will be passed as key=value
131+ parameters to the init system's commandline. kwargs
132+ are ignored for init systems not allowing additional
133+ parameters via the commandline (systemd).
134+ """
135+ service_result = service('reload', service_name, **kwargs)
136 if not service_result and restart_on_failure:
137- service_result = service('restart', service_name)
138+ service_result = service('restart', service_name, **kwargs)
139 return service_result
140
141
142-def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"):
143+def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d",
144+ **kwargs):
145 """Pause a system service.
146
147- Stop it, and prevent it from starting again at boot."""
148+ Stop it, and prevent it from starting again at boot.
149+
150+ :param service_name: the name of the service to pause
151+ :param init_dir: path to the upstart init directory
152+ :param initd_dir: path to the sysv init directory
153+ :param **kwargs: additional parameters to pass to the init system when
154+ managing services. These will be passed as key=value
155+ parameters to the init system's commandline. kwargs
156+ are ignored for init systems which do not support
157+ key=value arguments via the commandline.
158+ """
159 stopped = True
160- if service_running(service_name):
161- stopped = service_stop(service_name)
162+ if service_running(service_name, **kwargs):
163+ stopped = service_stop(service_name, **kwargs)
164 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
165 sysv_file = os.path.join(initd_dir, service_name)
166 if init_is_systemd():
167@@ -107,10 +206,19 @@ def service_pause(service_name, init_dir="/etc/init", initd_dir="/etc/init.d"):
168
169
170 def service_resume(service_name, init_dir="/etc/init",
171- initd_dir="/etc/init.d"):
172+ initd_dir="/etc/init.d", **kwargs):
173 """Resume a system service.
174
175- Reenable starting again at boot. Start the service"""
176+ Reenable starting again at boot. Start the service.
177+
178+ :param service_name: the name of the service to resume
179+ :param init_dir: the path to the init dir
180+ :param initd dir: the path to the initd dir
181+ :param **kwargs: additional parameters to pass to the init system when
182+ managing services. These will be passed as key=value
183+ parameters to the init system's commandline. kwargs
184+ are ignored for systemd enabled systems.
185+ """
186 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
187 sysv_file = os.path.join(initd_dir, service_name)
188 if init_is_systemd():
189@@ -127,19 +235,28 @@ def service_resume(service_name, init_dir="/etc/init",
190 "Unable to detect {0} as SystemD, Upstart {1} or"
191 " SysV {2}".format(
192 service_name, upstart_file, sysv_file))
193+ started = service_running(service_name, **kwargs)
194
195- started = service_running(service_name)
196 if not started:
197- started = service_start(service_name)
198+ started = service_start(service_name, **kwargs)
199 return started
200
201
202-def service(action, service_name):
203- """Control a system service"""
204+def service(action, service_name, **kwargs):
205+ """Control a system service.
206+
207+ :param action: the action to take on the service
208+ :param service_name: the name of the service to perform th action on
209+ :param **kwargs: additional params to be passed to the service command in
210+ the form of key=value.
211+ """
212 if init_is_systemd():
213 cmd = ['systemctl', action, service_name]
214 else:
215 cmd = ['service', service_name, action]
216+ for key, value in six.iteritems(kwargs):
217+ parameter = '%s=%s' % (key, value)
218+ cmd.append(parameter)
219 return subprocess.call(cmd) == 0
220
221
222@@ -147,15 +264,26 @@ _UPSTART_CONF = "/etc/init/{}.conf"
223 _INIT_D_CONF = "/etc/init.d/{}"
224
225
226-def service_running(service_name):
227- """Determine whether a system service is running"""
228+def service_running(service_name, **kwargs):
229+ """Determine whether a system service is running.
230+
231+ :param service_name: the name of the service
232+ :param **kwargs: additional args to pass to the service command. This is
233+ used to pass additional key=value arguments to the
234+ service command line for managing specific instance
235+ units (e.g. service ceph-osd status id=2). The kwargs
236+ are ignored in systemd services.
237+ """
238 if init_is_systemd():
239 return service('is-active', service_name)
240 else:
241 if os.path.exists(_UPSTART_CONF.format(service_name)):
242 try:
243- output = subprocess.check_output(
244- ['status', service_name],
245+ cmd = ['status', service_name]
246+ for key, value in six.iteritems(kwargs):
247+ parameter = '%s=%s' % (key, value)
248+ cmd.append(parameter)
249+ output = subprocess.check_output(cmd,
250 stderr=subprocess.STDOUT).decode('UTF-8')
251 except subprocess.CalledProcessError:
252 return False
253@@ -721,6 +849,20 @@ def lchownr(path, owner, group):
254 chownr(path, owner, group, follow_links=False)
255
256
257+def owner(path):
258+ """Returns a tuple containing the username & groupname owning the path.
259+
260+ :param str path: the string path to retrieve the ownership
261+ :return tuple(str, str): A (username, groupname) tuple containing the
262+ name of the user and group owning the path.
263+ :raises OSError: if the specified path does not exist
264+ """
265+ stat = os.stat(path)
266+ username = pwd.getpwuid(stat.st_uid)[0]
267+ groupname = grp.getgrgid(stat.st_gid)[0]
268+ return username, groupname
269+
270+
271 def get_total_ram():
272 """The total amount of system RAM in bytes.
273
274diff --git a/hooks/charmhelpers/core/host_factory/__init__.py b/hooks/charmhelpers/core/host_factory/__init__.py
275new file mode 100644
276index 0000000..e69de29
277--- /dev/null
278+++ b/hooks/charmhelpers/core/host_factory/__init__.py
279diff --git a/hooks/charmhelpers/core/host_factory/centos.py b/hooks/charmhelpers/core/host_factory/centos.py
280new file mode 100644
281index 0000000..902d469
282--- /dev/null
283+++ b/hooks/charmhelpers/core/host_factory/centos.py
284@@ -0,0 +1,56 @@
285+import subprocess
286+import yum
287+import os
288+
289+
290+def service_available(service_name):
291+ # """Determine whether a system service is available."""
292+ if os.path.isdir('/run/systemd/system'):
293+ cmd = ['systemctl', 'is-enabled', service_name]
294+ else:
295+ cmd = ['service', service_name, 'is-enabled']
296+ return subprocess.call(cmd) == 0
297+
298+
299+def add_new_group(group_name, system_group=False, gid=None):
300+ cmd = ['groupadd']
301+ if gid:
302+ cmd.extend(['--gid', str(gid)])
303+ if system_group:
304+ cmd.append('-r')
305+ cmd.append(group_name)
306+ subprocess.check_call(cmd)
307+
308+
309+def lsb_release():
310+ """Return /etc/os-release in a dict."""
311+ d = {}
312+ with open('/etc/os-release', 'r') as lsb:
313+ for l in lsb:
314+ s = l.split('=')
315+ if len(s) != 2:
316+ continue
317+ d[s[0].strip()] = s[1].strip()
318+ return d
319+
320+
321+def cmp_pkgrevno(package, revno, pkgcache=None):
322+ """Compare supplied revno with the revno of the installed package.
323+
324+ * 1 => Installed revno is greater than supplied arg
325+ * 0 => Installed revno is the same as supplied arg
326+ * -1 => Installed revno is less than supplied arg
327+
328+ This function imports YumBase function if the pkgcache argument
329+ is None.
330+ """
331+ if not pkgcache:
332+ y = yum.YumBase()
333+ packages = y.doPackageLists()
334+ pkgcache = {i.Name: i.version for i in packages['installed']}
335+ pkg = pkgcache[package]
336+ if pkg > revno:
337+ return 1
338+ if pkg < revno:
339+ return -1
340+ return 0
341diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/hooks/charmhelpers/core/host_factory/ubuntu.py
342new file mode 100644
343index 0000000..8c66af5
344--- /dev/null
345+++ b/hooks/charmhelpers/core/host_factory/ubuntu.py
346@@ -0,0 +1,56 @@
347+import subprocess
348+
349+
350+def service_available(service_name):
351+ """Determine whether a system service is available"""
352+ try:
353+ subprocess.check_output(
354+ ['service', service_name, 'status'],
355+ stderr=subprocess.STDOUT).decode('UTF-8')
356+ except subprocess.CalledProcessError as e:
357+ return b'unrecognized service' not in e.output
358+ else:
359+ return True
360+
361+
362+def add_new_group(group_name, system_group=False, gid=None):
363+ cmd = ['addgroup']
364+ if gid:
365+ cmd.extend(['--gid', str(gid)])
366+ if system_group:
367+ cmd.append('--system')
368+ else:
369+ cmd.extend([
370+ '--group',
371+ ])
372+ cmd.append(group_name)
373+ subprocess.check_call(cmd)
374+
375+
376+def lsb_release():
377+ """Return /etc/lsb-release in a dict"""
378+ d = {}
379+ with open('/etc/lsb-release', 'r') as lsb:
380+ for l in lsb:
381+ k, v = l.split('=')
382+ d[k.strip()] = v.strip()
383+ return d
384+
385+
386+def cmp_pkgrevno(package, revno, pkgcache=None):
387+ """Compare supplied revno with the revno of the installed package.
388+
389+ * 1 => Installed revno is greater than supplied arg
390+ * 0 => Installed revno is the same as supplied arg
391+ * -1 => Installed revno is less than supplied arg
392+
393+ This function imports apt_cache function from charmhelpers.fetch if
394+ the pkgcache argument is None. Be sure to add charmhelpers.fetch if
395+ you call this function, or pass an apt_pkg.Cache() instance.
396+ """
397+ import apt_pkg
398+ if not pkgcache:
399+ from charmhelpers.fetch import apt_cache
400+ pkgcache = apt_cache()
401+ pkg = pkgcache[package]
402+ return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
403diff --git a/hooks/charmhelpers/core/kernel_factory/__init__.py b/hooks/charmhelpers/core/kernel_factory/__init__.py
404new file mode 100644
405index 0000000..e69de29
406--- /dev/null
407+++ b/hooks/charmhelpers/core/kernel_factory/__init__.py
408diff --git a/hooks/charmhelpers/core/kernel_factory/centos.py b/hooks/charmhelpers/core/kernel_factory/centos.py
409new file mode 100644
410index 0000000..1c402c1
411--- /dev/null
412+++ b/hooks/charmhelpers/core/kernel_factory/centos.py
413@@ -0,0 +1,17 @@
414+import subprocess
415+import os
416+
417+
418+def persistent_modprobe(module):
419+ """Load a kernel module and configure for auto-load on reboot."""
420+ if not os.path.exists('/etc/rc.modules'):
421+ open('/etc/rc.modules', 'a')
422+ os.chmod('/etc/rc.modules', 111)
423+ with open('/etc/rc.modules', 'r+') as modules:
424+ if module not in modules.read():
425+ modules.write('modprobe %s\n' % module)
426+
427+
428+def update_initramfs(version='all'):
429+ """Updates an initramfs image."""
430+ return subprocess.check_call(["dracut", "-f", version])
431diff --git a/hooks/charmhelpers/core/kernel_factory/ubuntu.py b/hooks/charmhelpers/core/kernel_factory/ubuntu.py
432new file mode 100644
433index 0000000..3de372f
434--- /dev/null
435+++ b/hooks/charmhelpers/core/kernel_factory/ubuntu.py
436@@ -0,0 +1,13 @@
437+import subprocess
438+
439+
440+def persistent_modprobe(module):
441+ """Load a kernel module and configure for auto-load on reboot."""
442+ with open('/etc/modules', 'r+') as modules:
443+ if module not in modules.read():
444+ modules.write(module + "\n")
445+
446+
447+def update_initramfs(version='all'):
448+ """Updates an initramfs image."""
449+ return subprocess.check_call(["update-initramfs", "-k", version, "-u"])
450diff --git a/hooks/charmhelpers/fetch/centos.py b/hooks/charmhelpers/fetch/centos.py
451new file mode 100644
452index 0000000..604bbfb
453--- /dev/null
454+++ b/hooks/charmhelpers/fetch/centos.py
455@@ -0,0 +1,171 @@
456+# Copyright 2014-2015 Canonical Limited.
457+#
458+# Licensed under the Apache License, Version 2.0 (the "License");
459+# you may not use this file except in compliance with the License.
460+# You may obtain a copy of the License at
461+#
462+# http://www.apache.org/licenses/LICENSE-2.0
463+#
464+# Unless required by applicable law or agreed to in writing, software
465+# distributed under the License is distributed on an "AS IS" BASIS,
466+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
467+# See the License for the specific language governing permissions and
468+# limitations under the License.
469+
470+import subprocess
471+import os
472+import time
473+import six
474+import yum
475+
476+from tempfile import NamedTemporaryFile
477+from charmhelpers.core.hookenv import log
478+
479+YUM_NO_LOCK = 1 # The return code for "couldn't acquire lock" in YUM.
480+YUM_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
481+YUM_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
482+
483+
484+def filter_installed_packages(packages):
485+ """Return a list of packages that require installation."""
486+ yb = yum.YumBase()
487+ package_list = yb.doPackageLists()
488+ temp_cache = {p.base_package_name: 1 for p in package_list['installed']}
489+
490+ _pkgs = [p for p in packages if not temp_cache.get(p, False)]
491+ return _pkgs
492+
493+
494+def install(packages, options=None, fatal=False):
495+ """Install one or more packages."""
496+ cmd = ['yum', '--assumeyes']
497+ if options is not None:
498+ cmd.extend(options)
499+ cmd.append('install')
500+ if isinstance(packages, six.string_types):
501+ cmd.append(packages)
502+ else:
503+ cmd.extend(packages)
504+ log("Installing {} with options: {}".format(packages,
505+ options))
506+ _run_yum_command(cmd, fatal)
507+
508+
509+def upgrade(options=None, fatal=False, dist=False):
510+ """Upgrade all packages."""
511+ cmd = ['yum', '--assumeyes']
512+ if options is not None:
513+ cmd.extend(options)
514+ cmd.append('upgrade')
515+ log("Upgrading with options: {}".format(options))
516+ _run_yum_command(cmd, fatal)
517+
518+
519+def update(fatal=False):
520+ """Update local yum cache."""
521+ cmd = ['yum', '--assumeyes', 'update']
522+ log("Update with fatal: {}".format(fatal))
523+ _run_yum_command(cmd, fatal)
524+
525+
526+def purge(packages, fatal=False):
527+ """Purge one or more packages."""
528+ cmd = ['yum', '--assumeyes', 'remove']
529+ if isinstance(packages, six.string_types):
530+ cmd.append(packages)
531+ else:
532+ cmd.extend(packages)
533+ log("Purging {}".format(packages))
534+ _run_yum_command(cmd, fatal)
535+
536+
537+def yum_search(packages):
538+ """Search for a package."""
539+ output = {}
540+ cmd = ['yum', 'search']
541+ if isinstance(packages, six.string_types):
542+ cmd.append(packages)
543+ else:
544+ cmd.extend(packages)
545+ log("Searching for {}".format(packages))
546+ result = subprocess.check_output(cmd)
547+ for package in list(packages):
548+ output[package] = package in result
549+ return output
550+
551+
552+def add_source(source, key=None):
553+ """Add a package source to this system.
554+
555+ @param source: a URL with a rpm package
556+
557+ @param key: A key to be added to the system's keyring and used
558+ to verify the signatures on packages. Ideally, this should be an
559+ ASCII format GPG public key including the block headers. A GPG key
560+ id may also be used, but be aware that only insecure protocols are
561+ available to retrieve the actual public key from a public keyserver
562+ placing your Juju environment at risk.
563+ """
564+ if source is None:
565+ log('Source is not present. Skipping')
566+ return
567+
568+ if source.startswith('http'):
569+ directory = '/etc/yum.repos.d/'
570+ for filename in os.listdir(directory):
571+ with open(directory + filename, 'r') as rpm_file:
572+ if source in rpm_file.read():
573+ break
574+ else:
575+ log("Add source: {!r}".format(source))
576+ # write in the charms.repo
577+ with open(directory + 'Charms.repo', 'a') as rpm_file:
578+ rpm_file.write('[%s]\n' % source[7:].replace('/', '_'))
579+ rpm_file.write('name=%s\n' % source[7:])
580+ rpm_file.write('baseurl=%s\n\n' % source)
581+ else:
582+ log("Unknown source: {!r}".format(source))
583+
584+ if key:
585+ if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
586+ with NamedTemporaryFile('w+') as key_file:
587+ key_file.write(key)
588+ key_file.flush()
589+ key_file.seek(0)
590+ subprocess.check_call(['rpm', '--import', key_file])
591+ else:
592+ subprocess.check_call(['rpm', '--import', key])
593+
594+
595+def _run_yum_command(cmd, fatal=False):
596+ """Run an YUM command.
597+
598+ Checks the output and retry if the fatal flag is set to True.
599+
600+ :param: cmd: str: The yum command to run.
601+ :param: fatal: bool: Whether the command's output should be checked and
602+ retried.
603+ """
604+ env = os.environ.copy()
605+
606+ if fatal:
607+ retry_count = 0
608+ result = None
609+
610+ # If the command is considered "fatal", we need to retry if the yum
611+ # lock was not acquired.
612+
613+ while result is None or result == YUM_NO_LOCK:
614+ try:
615+ result = subprocess.check_call(cmd, env=env)
616+ except subprocess.CalledProcessError as e:
617+ retry_count = retry_count + 1
618+ if retry_count > YUM_NO_LOCK_RETRY_COUNT:
619+ raise
620+ result = e.returncode
621+ log("Couldn't acquire YUM lock. Will retry in {} seconds."
622+ "".format(YUM_NO_LOCK_RETRY_DELAY))
623+ time.sleep(YUM_NO_LOCK_RETRY_DELAY)
624+
625+ else:
626+ subprocess.call(cmd, env=env)
627diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/hooks/charmhelpers/fetch/ubuntu.py
628new file mode 100644
629index 0000000..39b9b80
630--- /dev/null
631+++ b/hooks/charmhelpers/fetch/ubuntu.py
632@@ -0,0 +1,344 @@
633+# Copyright 2014-2015 Canonical Limited.
634+#
635+# Licensed under the Apache License, Version 2.0 (the "License");
636+# you may not use this file except in compliance with the License.
637+# You may obtain a copy of the License at
638+#
639+# http://www.apache.org/licenses/LICENSE-2.0
640+#
641+# Unless required by applicable law or agreed to in writing, software
642+# distributed under the License is distributed on an "AS IS" BASIS,
643+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
644+# See the License for the specific language governing permissions and
645+# limitations under the License.
646+
647+import os
648+import six
649+import time
650+import subprocess
651+
652+from tempfile import NamedTemporaryFile
653+from charmhelpers.core.host import (
654+ lsb_release
655+)
656+from charmhelpers.core.hookenv import log
657+from charmhelpers.fetch import SourceConfigError
658+
659+CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
660+deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
661+"""
662+
663+PROPOSED_POCKET = """# Proposed
664+deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
665+"""
666+
667+CLOUD_ARCHIVE_POCKETS = {
668+ # Folsom
669+ 'folsom': 'precise-updates/folsom',
670+ 'precise-folsom': 'precise-updates/folsom',
671+ 'precise-folsom/updates': 'precise-updates/folsom',
672+ 'precise-updates/folsom': 'precise-updates/folsom',
673+ 'folsom/proposed': 'precise-proposed/folsom',
674+ 'precise-folsom/proposed': 'precise-proposed/folsom',
675+ 'precise-proposed/folsom': 'precise-proposed/folsom',
676+ # Grizzly
677+ 'grizzly': 'precise-updates/grizzly',
678+ 'precise-grizzly': 'precise-updates/grizzly',
679+ 'precise-grizzly/updates': 'precise-updates/grizzly',
680+ 'precise-updates/grizzly': 'precise-updates/grizzly',
681+ 'grizzly/proposed': 'precise-proposed/grizzly',
682+ 'precise-grizzly/proposed': 'precise-proposed/grizzly',
683+ 'precise-proposed/grizzly': 'precise-proposed/grizzly',
684+ # Havana
685+ 'havana': 'precise-updates/havana',
686+ 'precise-havana': 'precise-updates/havana',
687+ 'precise-havana/updates': 'precise-updates/havana',
688+ 'precise-updates/havana': 'precise-updates/havana',
689+ 'havana/proposed': 'precise-proposed/havana',
690+ 'precise-havana/proposed': 'precise-proposed/havana',
691+ 'precise-proposed/havana': 'precise-proposed/havana',
692+ # Icehouse
693+ 'icehouse': 'precise-updates/icehouse',
694+ 'precise-icehouse': 'precise-updates/icehouse',
695+ 'precise-icehouse/updates': 'precise-updates/icehouse',
696+ 'precise-updates/icehouse': 'precise-updates/icehouse',
697+ 'icehouse/proposed': 'precise-proposed/icehouse',
698+ 'precise-icehouse/proposed': 'precise-proposed/icehouse',
699+ 'precise-proposed/icehouse': 'precise-proposed/icehouse',
700+ # Juno
701+ 'juno': 'trusty-updates/juno',
702+ 'trusty-juno': 'trusty-updates/juno',
703+ 'trusty-juno/updates': 'trusty-updates/juno',
704+ 'trusty-updates/juno': 'trusty-updates/juno',
705+ 'juno/proposed': 'trusty-proposed/juno',
706+ 'trusty-juno/proposed': 'trusty-proposed/juno',
707+ 'trusty-proposed/juno': 'trusty-proposed/juno',
708+ # Kilo
709+ 'kilo': 'trusty-updates/kilo',
710+ 'trusty-kilo': 'trusty-updates/kilo',
711+ 'trusty-kilo/updates': 'trusty-updates/kilo',
712+ 'trusty-updates/kilo': 'trusty-updates/kilo',
713+ 'kilo/proposed': 'trusty-proposed/kilo',
714+ 'trusty-kilo/proposed': 'trusty-proposed/kilo',
715+ 'trusty-proposed/kilo': 'trusty-proposed/kilo',
716+ # Liberty
717+ 'liberty': 'trusty-updates/liberty',
718+ 'trusty-liberty': 'trusty-updates/liberty',
719+ 'trusty-liberty/updates': 'trusty-updates/liberty',
720+ 'trusty-updates/liberty': 'trusty-updates/liberty',
721+ 'liberty/proposed': 'trusty-proposed/liberty',
722+ 'trusty-liberty/proposed': 'trusty-proposed/liberty',
723+ 'trusty-proposed/liberty': 'trusty-proposed/liberty',
724+ # Mitaka
725+ 'mitaka': 'trusty-updates/mitaka',
726+ 'trusty-mitaka': 'trusty-updates/mitaka',
727+ 'trusty-mitaka/updates': 'trusty-updates/mitaka',
728+ 'trusty-updates/mitaka': 'trusty-updates/mitaka',
729+ 'mitaka/proposed': 'trusty-proposed/mitaka',
730+ 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
731+ 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
732+ # Newton
733+ 'newton': 'xenial-updates/newton',
734+ 'xenial-newton': 'xenial-updates/newton',
735+ 'xenial-newton/updates': 'xenial-updates/newton',
736+ 'xenial-updates/newton': 'xenial-updates/newton',
737+ 'newton/proposed': 'xenial-proposed/newton',
738+ 'xenial-newton/proposed': 'xenial-proposed/newton',
739+ 'xenial-proposed/newton': 'xenial-proposed/newton',
740+ # Ocata
741+ 'ocata': 'xenial-updates/ocata',
742+ 'xenial-ocata': 'xenial-updates/ocata',
743+ 'xenial-ocata/updates': 'xenial-updates/ocata',
744+ 'xenial-updates/ocata': 'xenial-updates/ocata',
745+ 'ocata/proposed': 'xenial-proposed/ocata',
746+ 'xenial-ocata/proposed': 'xenial-proposed/ocata',
747+ 'xenial-ocata/newton': 'xenial-proposed/ocata',
748+}
749+
750+APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
751+APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
752+APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
753+
754+
755+def filter_installed_packages(packages):
756+ """Return a list of packages that require installation."""
757+ cache = apt_cache()
758+ _pkgs = []
759+ for package in packages:
760+ try:
761+ p = cache[package]
762+ p.current_ver or _pkgs.append(package)
763+ except KeyError:
764+ log('Package {} has no installation candidate.'.format(package),
765+ level='WARNING')
766+ _pkgs.append(package)
767+ return _pkgs
768+
769+
770+def apt_cache(in_memory=True, progress=None):
771+ """Build and return an apt cache."""
772+ from apt import apt_pkg
773+ apt_pkg.init()
774+ if in_memory:
775+ apt_pkg.config.set("Dir::Cache::pkgcache", "")
776+ apt_pkg.config.set("Dir::Cache::srcpkgcache", "")
777+ return apt_pkg.Cache(progress)
778+
779+
780+def install(packages, options=None, fatal=False):
781+ """Install one or more packages."""
782+ if options is None:
783+ options = ['--option=Dpkg::Options::=--force-confold']
784+
785+ cmd = ['apt-get', '--assume-yes']
786+ cmd.extend(options)
787+ cmd.append('install')
788+ if isinstance(packages, six.string_types):
789+ cmd.append(packages)
790+ else:
791+ cmd.extend(packages)
792+ log("Installing {} with options: {}".format(packages,
793+ options))
794+ _run_apt_command(cmd, fatal)
795+
796+
797+def upgrade(options=None, fatal=False, dist=False):
798+ """Upgrade all packages."""
799+ if options is None:
800+ options = ['--option=Dpkg::Options::=--force-confold']
801+
802+ cmd = ['apt-get', '--assume-yes']
803+ cmd.extend(options)
804+ if dist:
805+ cmd.append('dist-upgrade')
806+ else:
807+ cmd.append('upgrade')
808+ log("Upgrading with options: {}".format(options))
809+ _run_apt_command(cmd, fatal)
810+
811+
812+def update(fatal=False):
813+ """Update local apt cache."""
814+ cmd = ['apt-get', 'update']
815+ _run_apt_command(cmd, fatal)
816+
817+
818+def purge(packages, fatal=False):
819+ """Purge one or more packages."""
820+ cmd = ['apt-get', '--assume-yes', 'purge']
821+ if isinstance(packages, six.string_types):
822+ cmd.append(packages)
823+ else:
824+ cmd.extend(packages)
825+ log("Purging {}".format(packages))
826+ _run_apt_command(cmd, fatal)
827+
828+
829+def apt_mark(packages, mark, fatal=False):
830+ """Flag one or more packages using apt-mark."""
831+ log("Marking {} as {}".format(packages, mark))
832+ cmd = ['apt-mark', mark]
833+ if isinstance(packages, six.string_types):
834+ cmd.append(packages)
835+ else:
836+ cmd.extend(packages)
837+
838+ if fatal:
839+ subprocess.check_call(cmd, universal_newlines=True)
840+ else:
841+ subprocess.call(cmd, universal_newlines=True)
842+
843+
844+def apt_hold(packages, fatal=False):
845+ return apt_mark(packages, 'hold', fatal=fatal)
846+
847+
848+def apt_unhold(packages, fatal=False):
849+ return apt_mark(packages, 'unhold', fatal=fatal)
850+
851+
852+def add_source(source, key=None):
853+ """Add a package source to this system.
854+
855+ @param source: a URL or sources.list entry, as supported by
856+ add-apt-repository(1). Examples::
857+
858+ ppa:charmers/example
859+ deb https://stub:key@private.example.com/ubuntu trusty main
860+
861+ In addition:
862+ 'proposed:' may be used to enable the standard 'proposed'
863+ pocket for the release.
864+ 'cloud:' may be used to activate official cloud archive pockets,
865+ such as 'cloud:icehouse'
866+ 'distro' may be used as a noop
867+
868+ @param key: A key to be added to the system's APT keyring and used
869+ to verify the signatures on packages. Ideally, this should be an
870+ ASCII format GPG public key including the block headers. A GPG key
871+ id may also be used, but be aware that only insecure protocols are
872+ available to retrieve the actual public key from a public keyserver
873+ placing your Juju environment at risk. ppa and cloud archive keys
874+ are securely added automtically, so sould not be provided.
875+ """
876+ if source is None:
877+ log('Source is not present. Skipping')
878+ return
879+
880+ if (source.startswith('ppa:') or
881+ source.startswith('http') or
882+ source.startswith('deb ') or
883+ source.startswith('cloud-archive:')):
884+ subprocess.check_call(['add-apt-repository', '--yes', source])
885+ elif source.startswith('cloud:'):
886+ install(filter_installed_packages(['ubuntu-cloud-keyring']),
887+ fatal=True)
888+ pocket = source.split(':')[-1]
889+ if pocket not in CLOUD_ARCHIVE_POCKETS:
890+ raise SourceConfigError(
891+ 'Unsupported cloud: source option %s' %
892+ pocket)
893+ actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
894+ with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
895+ apt.write(CLOUD_ARCHIVE.format(actual_pocket))
896+ elif source == 'proposed':
897+ release = lsb_release()['DISTRIB_CODENAME']
898+ with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
899+ apt.write(PROPOSED_POCKET.format(release))
900+ elif source == 'distro':
901+ pass
902+ else:
903+ log("Unknown source: {!r}".format(source))
904+
905+ if key:
906+ if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
907+ with NamedTemporaryFile('w+') as key_file:
908+ key_file.write(key)
909+ key_file.flush()
910+ key_file.seek(0)
911+ subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
912+ else:
913+ # Note that hkp: is in no way a secure protocol. Using a
914+ # GPG key id is pointless from a security POV unless you
915+ # absolutely trust your network and DNS.
916+ subprocess.check_call(['apt-key', 'adv', '--keyserver',
917+ 'hkp://keyserver.ubuntu.com:80', '--recv',
918+ key])
919+
920+
921+def _run_apt_command(cmd, fatal=False):
922+ """Run an APT command.
923+
924+ Checks the output and retries if the fatal flag is set
925+ to True.
926+
927+ :param: cmd: str: The apt command to run.
928+ :param: fatal: bool: Whether the command's output should be checked and
929+ retried.
930+ """
931+ env = os.environ.copy()
932+
933+ if 'DEBIAN_FRONTEND' not in env:
934+ env['DEBIAN_FRONTEND'] = 'noninteractive'
935+
936+ if fatal:
937+ retry_count = 0
938+ result = None
939+
940+ # If the command is considered "fatal", we need to retry if the apt
941+ # lock was not acquired.
942+
943+ while result is None or result == APT_NO_LOCK:
944+ try:
945+ result = subprocess.check_call(cmd, env=env)
946+ except subprocess.CalledProcessError as e:
947+ retry_count = retry_count + 1
948+ if retry_count > APT_NO_LOCK_RETRY_COUNT:
949+ raise
950+ result = e.returncode
951+ log("Couldn't acquire DPKG lock. Will retry in {} seconds."
952+ "".format(APT_NO_LOCK_RETRY_DELAY))
953+ time.sleep(APT_NO_LOCK_RETRY_DELAY)
954+
955+ else:
956+ subprocess.call(cmd, env=env)
957+
958+
959+def get_upstream_version(package):
960+ """Determine upstream version based on installed package
961+
962+ @returns None (if not installed) or the upstream version
963+ """
964+ import apt_pkg
965+ cache = apt_cache()
966+ try:
967+ pkg = cache[package]
968+ except:
969+ # the package is unknown to the current apt cache.
970+ return None
971+
972+ if not pkg.current_ver:
973+ # package is known, but no version is currently installed.
974+ return None
975+
976+ return apt_pkg.upstream_version(pkg.current_ver.ver_str)
977diff --git a/hooks/charmhelpers/osplatform.py b/hooks/charmhelpers/osplatform.py
978new file mode 100644
979index 0000000..d9a4d5c
980--- /dev/null
981+++ b/hooks/charmhelpers/osplatform.py
982@@ -0,0 +1,25 @@
983+import platform
984+
985+
986+def get_platform():
987+ """Return the current OS platform.
988+
989+ For example: if current os platform is Ubuntu then a string "ubuntu"
990+ will be returned (which is the name of the module).
991+ This string is used to decide which platform module should be imported.
992+ """
993+ # linux_distribution is deprecated and will be removed in Python 3.7
994+ # Warings *not* disabled, as we certainly need to fix this.
995+ tuple_platform = platform.linux_distribution()
996+ current_platform = tuple_platform[0]
997+ if "Ubuntu" in current_platform:
998+ return "ubuntu"
999+ elif "CentOS" in current_platform:
1000+ return "centos"
1001+ elif "debian" in current_platform:
1002+ # Stock Python does not detect Ubuntu and instead returns debian.
1003+ # Or at least it does in some build environments like Travis CI
1004+ return "ubuntu"
1005+ else:
1006+ raise RuntimeError("This module is not supported on {}."
1007+ .format(current_platform))

Subscribers

People subscribed via source and target branches