Merge ~xavpaice/charm-nrpe:master into ~nrpe-charmers/charm-nrpe:master
- Git
- lp:~xavpaice/charm-nrpe
- master
- Merge into 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) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Paul Gear (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
Add osplatform to charmhelpers, and sync charmhelpers
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Paul Gear (paulgear) wrote : | # |
Already fixed per https:/
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
1 | diff --git a/charm-helpers-hooks.yaml b/charm-helpers-hooks.yaml |
2 | index 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 |
10 | diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py |
11 | index 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 | |
274 | diff --git a/hooks/charmhelpers/core/host_factory/__init__.py b/hooks/charmhelpers/core/host_factory/__init__.py |
275 | new file mode 100644 |
276 | index 0000000..e69de29 |
277 | --- /dev/null |
278 | +++ b/hooks/charmhelpers/core/host_factory/__init__.py |
279 | diff --git a/hooks/charmhelpers/core/host_factory/centos.py b/hooks/charmhelpers/core/host_factory/centos.py |
280 | new file mode 100644 |
281 | index 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 |
341 | diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/hooks/charmhelpers/core/host_factory/ubuntu.py |
342 | new file mode 100644 |
343 | index 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) |
403 | diff --git a/hooks/charmhelpers/core/kernel_factory/__init__.py b/hooks/charmhelpers/core/kernel_factory/__init__.py |
404 | new file mode 100644 |
405 | index 0000000..e69de29 |
406 | --- /dev/null |
407 | +++ b/hooks/charmhelpers/core/kernel_factory/__init__.py |
408 | diff --git a/hooks/charmhelpers/core/kernel_factory/centos.py b/hooks/charmhelpers/core/kernel_factory/centos.py |
409 | new file mode 100644 |
410 | index 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]) |
431 | diff --git a/hooks/charmhelpers/core/kernel_factory/ubuntu.py b/hooks/charmhelpers/core/kernel_factory/ubuntu.py |
432 | new file mode 100644 |
433 | index 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"]) |
450 | diff --git a/hooks/charmhelpers/fetch/centos.py b/hooks/charmhelpers/fetch/centos.py |
451 | new file mode 100644 |
452 | index 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) |
627 | diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/hooks/charmhelpers/fetch/ubuntu.py |
628 | new file mode 100644 |
629 | index 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) |
977 | diff --git a/hooks/charmhelpers/osplatform.py b/hooks/charmhelpers/osplatform.py |
978 | new file mode 100644 |
979 | index 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)) |
LGTM