Merge ~james-page/ntpmaster-charm:add-update-status into ntpmaster-charm:master

Proposed by James Page
Status: Merged
Merged at revision: f26da95f35edf50c95c7598f01887e2a9d7e44ef
Proposed branch: ~james-page/ntpmaster-charm:add-update-status
Merge into: ntpmaster-charm:master
Diff against target: 2260 lines (+1055/-624)
33 files modified
.gitignore (+1/-0)
charm-helpers-sync.yaml (+1/-0)
hooks/charmhelpers/__init__.py (+9/-11)
hooks/charmhelpers/core/__init__.py (+9/-11)
hooks/charmhelpers/core/decorators.py (+9/-11)
hooks/charmhelpers/core/files.py (+9/-11)
hooks/charmhelpers/core/fstab.py (+9/-11)
hooks/charmhelpers/core/hookenv.py (+24/-12)
hooks/charmhelpers/core/host.py (+105/-88)
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/hugepage.py (+9/-11)
hooks/charmhelpers/core/kernel.py (+30/-26)
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/core/services/__init__.py (+9/-11)
hooks/charmhelpers/core/services/base.py (+9/-11)
hooks/charmhelpers/core/services/helpers.py (+9/-11)
hooks/charmhelpers/core/strutils.py (+9/-11)
hooks/charmhelpers/core/sysctl.py (+9/-11)
hooks/charmhelpers/core/templating.py (+17/-14)
hooks/charmhelpers/core/unitdata.py (+9/-12)
hooks/charmhelpers/fetch/__init__.py (+38/-305)
hooks/charmhelpers/fetch/archiveurl.py (+9/-11)
hooks/charmhelpers/fetch/bzrurl.py (+29/-21)
hooks/charmhelpers/fetch/centos.py (+171/-0)
hooks/charmhelpers/fetch/giturl.py (+13/-14)
hooks/charmhelpers/fetch/ubuntu.py (+336/-0)
hooks/charmhelpers/osplatform.py (+19/-0)
hooks/ntpmaster_hooks.py (+11/-0)
hooks/update-status (+1/-0)
Reviewer Review Type Date Requested Status
Stuart Bishop (community) Approve
Review via email: mp+308712@code.launchpad.net
To post a comment you must log in.
Revision history for this message
James Page (james-page) wrote :

For reference:

Model Controller Cloud/Region Version
default lxd-localhost lxd/localhost 2.0.0

App Version Status Scale Charm Store Rev OS Notes
ntp 4.2.6.p5+dfsg active 2 ntp local 14 ubuntu
ntpmaster 4.2.6.p5+dfsg active 1 ntpmaster local 30 ubuntu
ubuntu 14.04 active 2 ubuntu jujucharms 8 ubuntu exposed

Unit Workload Agent Machine Public address Ports Message
ntpmaster/0* active idle 0 10.20.200.76 123/udp Unit is ready
ubuntu/0 active idle 1 10.20.200.25 ready
  ntp/1 active idle 10.20.200.25 Unit is ready
ubuntu/1* active idle 2 10.20.200.38 ready
  ntp/0* active idle 10.20.200.38 Unit is ready

Machine State DNS Inst id Series AZ
0 started 10.20.200.76 juju-983066-0 trusty
1 started 10.20.200.25 juju-983066-1 trusty
2 started 10.20.200.38 juju-983066-2 trusty

Relation Provides Consumes Type
ntp-peers ntp ntp peer
master ntp ntpmaster regular
juju-info ntp ubuntu regular
peer ntpmaster ntpmaster peer
juju-info ubuntu ntp subordinate

Revision history for this message
Stuart Bishop (stub) wrote :

Almost entirely charmhelpers sync.

The new hooks looks good and should do what it says on the tin.

I'm not here this week so somebody else gets to land this :)

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

Merged; question about future use of status_set() in https://code.launchpad.net/~james-page/ntp-charm/+git/ntp-charm/+merge/308705 applies equally here.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/.gitignore b/.gitignore
2new file mode 100644
3index 0000000..ba077a4
4--- /dev/null
5+++ b/.gitignore
6@@ -0,0 +1 @@
7+bin
8diff --git a/charm-helpers-sync.yaml b/charm-helpers-sync.yaml
9index 7fd4ca6..c6dcfa3 100644
10--- a/charm-helpers-sync.yaml
11+++ b/charm-helpers-sync.yaml
12@@ -1,5 +1,6 @@
13 branch: lp:charm-helpers
14 destination: hooks/charmhelpers
15 include:
16+ - osplatform
17 - core
18 - fetch
19diff --git a/hooks/charmhelpers/__init__.py b/hooks/charmhelpers/__init__.py
20index f72e7f8..4886788 100644
21--- a/hooks/charmhelpers/__init__.py
22+++ b/hooks/charmhelpers/__init__.py
23@@ -1,18 +1,16 @@
24 # Copyright 2014-2015 Canonical Limited.
25 #
26-# This file is part of charm-helpers.
27+# Licensed under the Apache License, Version 2.0 (the "License");
28+# you may not use this file except in compliance with the License.
29+# You may obtain a copy of the License at
30 #
31-# charm-helpers is free software: you can redistribute it and/or modify
32-# it under the terms of the GNU Lesser General Public License version 3 as
33-# published by the Free Software Foundation.
34+# http://www.apache.org/licenses/LICENSE-2.0
35 #
36-# charm-helpers is distributed in the hope that it will be useful,
37-# but WITHOUT ANY WARRANTY; without even the implied warranty of
38-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39-# GNU Lesser General Public License for more details.
40-#
41-# You should have received a copy of the GNU Lesser General Public License
42-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
43+# Unless required by applicable law or agreed to in writing, software
44+# distributed under the License is distributed on an "AS IS" BASIS,
45+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
46+# See the License for the specific language governing permissions and
47+# limitations under the License.
48
49 # Bootstrap charm-helpers, installing its dependencies if necessary using
50 # only standard libraries.
51diff --git a/hooks/charmhelpers/core/__init__.py b/hooks/charmhelpers/core/__init__.py
52index d1400a0..d7567b8 100644
53--- a/hooks/charmhelpers/core/__init__.py
54+++ b/hooks/charmhelpers/core/__init__.py
55@@ -1,15 +1,13 @@
56 # Copyright 2014-2015 Canonical Limited.
57 #
58-# This file is part of charm-helpers.
59+# Licensed under the Apache License, Version 2.0 (the "License");
60+# you may not use this file except in compliance with the License.
61+# You may obtain a copy of the License at
62 #
63-# charm-helpers is free software: you can redistribute it and/or modify
64-# it under the terms of the GNU Lesser General Public License version 3 as
65-# published by the Free Software Foundation.
66+# http://www.apache.org/licenses/LICENSE-2.0
67 #
68-# charm-helpers is distributed in the hope that it will be useful,
69-# but WITHOUT ANY WARRANTY; without even the implied warranty of
70-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
71-# GNU Lesser General Public License for more details.
72-#
73-# You should have received a copy of the GNU Lesser General Public License
74-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
75+# Unless required by applicable law or agreed to in writing, software
76+# distributed under the License is distributed on an "AS IS" BASIS,
77+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
78+# See the License for the specific language governing permissions and
79+# limitations under the License.
80diff --git a/hooks/charmhelpers/core/decorators.py b/hooks/charmhelpers/core/decorators.py
81index bb05620..6ad41ee 100644
82--- a/hooks/charmhelpers/core/decorators.py
83+++ b/hooks/charmhelpers/core/decorators.py
84@@ -1,18 +1,16 @@
85 # Copyright 2014-2015 Canonical Limited.
86 #
87-# This file is part of charm-helpers.
88+# Licensed under the Apache License, Version 2.0 (the "License");
89+# you may not use this file except in compliance with the License.
90+# You may obtain a copy of the License at
91 #
92-# charm-helpers is free software: you can redistribute it and/or modify
93-# it under the terms of the GNU Lesser General Public License version 3 as
94-# published by the Free Software Foundation.
95+# http://www.apache.org/licenses/LICENSE-2.0
96 #
97-# charm-helpers is distributed in the hope that it will be useful,
98-# but WITHOUT ANY WARRANTY; without even the implied warranty of
99-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
100-# GNU Lesser General Public License for more details.
101-#
102-# You should have received a copy of the GNU Lesser General Public License
103-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
104+# Unless required by applicable law or agreed to in writing, software
105+# distributed under the License is distributed on an "AS IS" BASIS,
106+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
107+# See the License for the specific language governing permissions and
108+# limitations under the License.
109
110 #
111 # Copyright 2014 Canonical Ltd.
112diff --git a/hooks/charmhelpers/core/files.py b/hooks/charmhelpers/core/files.py
113index 0f12d32..fdd82b7 100644
114--- a/hooks/charmhelpers/core/files.py
115+++ b/hooks/charmhelpers/core/files.py
116@@ -3,19 +3,17 @@
117
118 # Copyright 2014-2015 Canonical Limited.
119 #
120-# This file is part of charm-helpers.
121+# Licensed under the Apache License, Version 2.0 (the "License");
122+# you may not use this file except in compliance with the License.
123+# You may obtain a copy of the License at
124 #
125-# charm-helpers is free software: you can redistribute it and/or modify
126-# it under the terms of the GNU Lesser General Public License version 3 as
127-# published by the Free Software Foundation.
128+# http://www.apache.org/licenses/LICENSE-2.0
129 #
130-# charm-helpers is distributed in the hope that it will be useful,
131-# but WITHOUT ANY WARRANTY; without even the implied warranty of
132-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
133-# GNU Lesser General Public License for more details.
134-#
135-# You should have received a copy of the GNU Lesser General Public License
136-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
137+# Unless required by applicable law or agreed to in writing, software
138+# distributed under the License is distributed on an "AS IS" BASIS,
139+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
140+# See the License for the specific language governing permissions and
141+# limitations under the License.
142
143 __author__ = 'Jorge Niedbalski <niedbalski@ubuntu.com>'
144
145diff --git a/hooks/charmhelpers/core/fstab.py b/hooks/charmhelpers/core/fstab.py
146index 3056fba..d9fa915 100644
147--- a/hooks/charmhelpers/core/fstab.py
148+++ b/hooks/charmhelpers/core/fstab.py
149@@ -3,19 +3,17 @@
150
151 # Copyright 2014-2015 Canonical Limited.
152 #
153-# This file is part of charm-helpers.
154+# Licensed under the Apache License, Version 2.0 (the "License");
155+# you may not use this file except in compliance with the License.
156+# You may obtain a copy of the License at
157 #
158-# charm-helpers is free software: you can redistribute it and/or modify
159-# it under the terms of the GNU Lesser General Public License version 3 as
160-# published by the Free Software Foundation.
161+# http://www.apache.org/licenses/LICENSE-2.0
162 #
163-# charm-helpers is distributed in the hope that it will be useful,
164-# but WITHOUT ANY WARRANTY; without even the implied warranty of
165-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
166-# GNU Lesser General Public License for more details.
167-#
168-# You should have received a copy of the GNU Lesser General Public License
169-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
170+# Unless required by applicable law or agreed to in writing, software
171+# distributed under the License is distributed on an "AS IS" BASIS,
172+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
173+# See the License for the specific language governing permissions and
174+# limitations under the License.
175
176 import io
177 import os
178diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py
179index 0132129..996e81c 100644
180--- a/hooks/charmhelpers/core/hookenv.py
181+++ b/hooks/charmhelpers/core/hookenv.py
182@@ -1,18 +1,16 @@
183 # Copyright 2014-2015 Canonical Limited.
184 #
185-# This file is part of charm-helpers.
186+# Licensed under the Apache License, Version 2.0 (the "License");
187+# you may not use this file except in compliance with the License.
188+# You may obtain a copy of the License at
189 #
190-# charm-helpers is free software: you can redistribute it and/or modify
191-# it under the terms of the GNU Lesser General Public License version 3 as
192-# published by the Free Software Foundation.
193+# http://www.apache.org/licenses/LICENSE-2.0
194 #
195-# charm-helpers is distributed in the hope that it will be useful,
196-# but WITHOUT ANY WARRANTY; without even the implied warranty of
197-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
198-# GNU Lesser General Public License for more details.
199-#
200-# You should have received a copy of the GNU Lesser General Public License
201-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
202+# Unless required by applicable law or agreed to in writing, software
203+# distributed under the License is distributed on an "AS IS" BASIS,
204+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
205+# See the License for the specific language governing permissions and
206+# limitations under the License.
207
208 "Interactions with the Juju environment"
209 # Copyright 2013 Canonical Ltd.
210@@ -845,6 +843,20 @@ def translate_exc(from_exc, to_exc):
211 return inner_translate_exc1
212
213
214+def application_version_set(version):
215+ """Charm authors may trigger this command from any hook to output what
216+ version of the application is running. This could be a package version,
217+ for instance postgres version 9.5. It could also be a build number or
218+ version control revision identifier, for instance git sha 6fb7ba68. """
219+
220+ cmd = ['application-version-set']
221+ cmd.append(version)
222+ try:
223+ subprocess.check_call(cmd)
224+ except OSError:
225+ log("Application Version: {}".format(version))
226+
227+
228 @translate_exc(from_exc=OSError, to_exc=NotImplementedError)
229 def is_leader():
230 """Does the current unit hold the juju leadership
231@@ -1006,4 +1018,4 @@ def network_get_primary_address(binding):
232 :raise: NotImplementedError if run on Juju < 2.0
233 '''
234 cmd = ['network-get', '--primary-address', binding]
235- return subprocess.check_output(cmd).strip()
236+ return subprocess.check_output(cmd).decode('UTF-8').strip()
237diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py
238index bfea6a1..0f1b2f3 100644
239--- a/hooks/charmhelpers/core/host.py
240+++ b/hooks/charmhelpers/core/host.py
241@@ -1,18 +1,16 @@
242 # Copyright 2014-2015 Canonical Limited.
243 #
244-# This file is part of charm-helpers.
245+# Licensed under the Apache License, Version 2.0 (the "License");
246+# you may not use this file except in compliance with the License.
247+# You may obtain a copy of the License at
248 #
249-# charm-helpers is free software: you can redistribute it and/or modify
250-# it under the terms of the GNU Lesser General Public License version 3 as
251-# published by the Free Software Foundation.
252+# http://www.apache.org/licenses/LICENSE-2.0
253 #
254-# charm-helpers is distributed in the hope that it will be useful,
255-# but WITHOUT ANY WARRANTY; without even the implied warranty of
256-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
257-# GNU Lesser General Public License for more details.
258-#
259-# You should have received a copy of the GNU Lesser General Public License
260-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
261+# Unless required by applicable law or agreed to in writing, software
262+# distributed under the License is distributed on an "AS IS" BASIS,
263+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
264+# See the License for the specific language governing permissions and
265+# limitations under the License.
266
267 """Tools for working with the host system"""
268 # Copyright 2012 Canonical Ltd.
269@@ -32,13 +30,29 @@ import subprocess
270 import hashlib
271 import functools
272 import itertools
273-from contextlib import contextmanager
274-from collections import OrderedDict
275-
276 import six
277
278+from contextlib import contextmanager
279+from collections import OrderedDict
280 from .hookenv import log
281 from .fstab import Fstab
282+from charmhelpers.osplatform import get_platform
283+
284+__platform__ = get_platform()
285+if __platform__ == "ubuntu":
286+ from charmhelpers.core.host_factory.ubuntu import (
287+ service_available,
288+ add_new_group,
289+ lsb_release,
290+ cmp_pkgrevno,
291+ ) # flake8: noqa -- ignore F401 for this import
292+elif __platform__ == "centos":
293+ from charmhelpers.core.host_factory.centos import (
294+ service_available,
295+ add_new_group,
296+ lsb_release,
297+ cmp_pkgrevno,
298+ ) # flake8: noqa -- ignore F401 for this import
299
300
301 def service_start(service_name):
302@@ -128,11 +142,8 @@ def service(action, service_name):
303 return subprocess.call(cmd) == 0
304
305
306-def systemv_services_running():
307- output = subprocess.check_output(
308- ['service', '--status-all'],
309- stderr=subprocess.STDOUT).decode('UTF-8')
310- return [row.split()[-1] for row in output.split('\n') if '[ + ]' in row]
311+_UPSTART_CONF = "/etc/init/{}.conf"
312+_INIT_D_CONF = "/etc/init.d/{}"
313
314
315 def service_running(service_name):
316@@ -140,34 +151,25 @@ def service_running(service_name):
317 if init_is_systemd():
318 return service('is-active', service_name)
319 else:
320- try:
321- output = subprocess.check_output(
322- ['service', service_name, 'status'],
323- stderr=subprocess.STDOUT).decode('UTF-8')
324- except subprocess.CalledProcessError:
325- return False
326- else:
327- # This works for upstart scripts where the 'service' command
328- # returns a consistent string to represent running 'start/running'
329- if ("start/running" in output or "is running" in output or
330- "up and running" in output):
331- return True
332+ if os.path.exists(_UPSTART_CONF.format(service_name)):
333+ try:
334+ output = subprocess.check_output(
335+ ['status', service_name],
336+ stderr=subprocess.STDOUT).decode('UTF-8')
337+ except subprocess.CalledProcessError:
338+ return False
339+ else:
340+ # This works for upstart scripts where the 'service' command
341+ # returns a consistent string to represent running
342+ # 'start/running'
343+ if ("start/running" in output or
344+ "is running" in output or
345+ "up and running" in output):
346+ return True
347+ elif os.path.exists(_INIT_D_CONF.format(service_name)):
348 # Check System V scripts init script return codes
349- if service_name in systemv_services_running():
350- return True
351- return False
352-
353-
354-def service_available(service_name):
355- """Determine whether a system service is available"""
356- try:
357- subprocess.check_output(
358- ['service', service_name, 'status'],
359- stderr=subprocess.STDOUT).decode('UTF-8')
360- except subprocess.CalledProcessError as e:
361- return b'unrecognized service' not in e.output
362- else:
363- return True
364+ return service('status', service_name)
365+ return False
366
367
368 SYSTEMD_SYSTEM = '/run/systemd/system'
369@@ -178,8 +180,9 @@ def init_is_systemd():
370 return os.path.isdir(SYSTEMD_SYSTEM)
371
372
373-def adduser(username, password=None, shell='/bin/bash', system_user=False,
374- primary_group=None, secondary_groups=None):
375+def adduser(username, password=None, shell='/bin/bash',
376+ system_user=False, primary_group=None,
377+ secondary_groups=None, uid=None, home_dir=None):
378 """Add a user to the system.
379
380 Will log but otherwise succeed if the user already exists.
381@@ -190,15 +193,24 @@ def adduser(username, password=None, shell='/bin/bash', system_user=False,
382 :param bool system_user: Whether to create a login or system user
383 :param str primary_group: Primary group for user; defaults to username
384 :param list secondary_groups: Optional list of additional groups
385+ :param int uid: UID for user being created
386+ :param str home_dir: Home directory for user
387
388 :returns: The password database entry struct, as returned by `pwd.getpwnam`
389 """
390 try:
391 user_info = pwd.getpwnam(username)
392 log('user {0} already exists!'.format(username))
393+ if uid:
394+ user_info = pwd.getpwuid(int(uid))
395+ log('user with uid {0} already exists!'.format(uid))
396 except KeyError:
397 log('creating user {0}'.format(username))
398 cmd = ['useradd']
399+ if uid:
400+ cmd.extend(['--uid', str(uid)])
401+ if home_dir:
402+ cmd.extend(['--home', str(home_dir)])
403 if system_user or password is None:
404 cmd.append('--system')
405 else:
406@@ -233,22 +245,56 @@ def user_exists(username):
407 return user_exists
408
409
410-def add_group(group_name, system_group=False):
411- """Add a group to the system"""
412+def uid_exists(uid):
413+ """Check if a uid exists"""
414+ try:
415+ pwd.getpwuid(uid)
416+ uid_exists = True
417+ except KeyError:
418+ uid_exists = False
419+ return uid_exists
420+
421+
422+def group_exists(groupname):
423+ """Check if a group exists"""
424+ try:
425+ grp.getgrnam(groupname)
426+ group_exists = True
427+ except KeyError:
428+ group_exists = False
429+ return group_exists
430+
431+
432+def gid_exists(gid):
433+ """Check if a gid exists"""
434+ try:
435+ grp.getgrgid(gid)
436+ gid_exists = True
437+ except KeyError:
438+ gid_exists = False
439+ return gid_exists
440+
441+
442+def add_group(group_name, system_group=False, gid=None):
443+ """Add a group to the system
444+
445+ Will log but otherwise succeed if the group already exists.
446+
447+ :param str group_name: group to create
448+ :param bool system_group: Create system group
449+ :param int gid: GID for user being created
450+
451+ :returns: The password database entry struct, as returned by `grp.getgrnam`
452+ """
453 try:
454 group_info = grp.getgrnam(group_name)
455 log('group {0} already exists!'.format(group_name))
456+ if gid:
457+ group_info = grp.getgrgid(gid)
458+ log('group with gid {0} already exists!'.format(gid))
459 except KeyError:
460 log('creating group {0}'.format(group_name))
461- cmd = ['addgroup']
462- if system_group:
463- cmd.append('--system')
464- else:
465- cmd.extend([
466- '--group',
467- ])
468- cmd.append(group_name)
469- subprocess.check_call(cmd)
470+ add_new_group(group_name, system_group, gid)
471 group_info = grp.getgrnam(group_name)
472 return group_info
473
474@@ -493,16 +539,6 @@ def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
475 return r
476
477
478-def lsb_release():
479- """Return /etc/lsb-release in a dict"""
480- d = {}
481- with open('/etc/lsb-release', 'r') as lsb:
482- for l in lsb:
483- k, v = l.split('=')
484- d[k.strip()] = v.strip()
485- return d
486-
487-
488 def pwgen(length=None):
489 """Generate a random pasword."""
490 if length is None:
491@@ -626,25 +662,6 @@ def get_nic_hwaddr(nic):
492 return hwaddr
493
494
495-def cmp_pkgrevno(package, revno, pkgcache=None):
496- """Compare supplied revno with the revno of the installed package
497-
498- * 1 => Installed revno is greater than supplied arg
499- * 0 => Installed revno is the same as supplied arg
500- * -1 => Installed revno is less than supplied arg
501-
502- This function imports apt_cache function from charmhelpers.fetch if
503- the pkgcache argument is None. Be sure to add charmhelpers.fetch if
504- you call this function, or pass an apt_pkg.Cache() instance.
505- """
506- import apt_pkg
507- if not pkgcache:
508- from charmhelpers.fetch import apt_cache
509- pkgcache = apt_cache()
510- pkg = pkgcache[package]
511- return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
512-
513-
514 @contextmanager
515 def chdir(directory):
516 """Change the current working directory to a different directory for a code
517diff --git a/hooks/charmhelpers/core/host_factory/__init__.py b/hooks/charmhelpers/core/host_factory/__init__.py
518new file mode 100644
519index 0000000..e69de29
520--- /dev/null
521+++ b/hooks/charmhelpers/core/host_factory/__init__.py
522diff --git a/hooks/charmhelpers/core/host_factory/centos.py b/hooks/charmhelpers/core/host_factory/centos.py
523new file mode 100644
524index 0000000..902d469
525--- /dev/null
526+++ b/hooks/charmhelpers/core/host_factory/centos.py
527@@ -0,0 +1,56 @@
528+import subprocess
529+import yum
530+import os
531+
532+
533+def service_available(service_name):
534+ # """Determine whether a system service is available."""
535+ if os.path.isdir('/run/systemd/system'):
536+ cmd = ['systemctl', 'is-enabled', service_name]
537+ else:
538+ cmd = ['service', service_name, 'is-enabled']
539+ return subprocess.call(cmd) == 0
540+
541+
542+def add_new_group(group_name, system_group=False, gid=None):
543+ cmd = ['groupadd']
544+ if gid:
545+ cmd.extend(['--gid', str(gid)])
546+ if system_group:
547+ cmd.append('-r')
548+ cmd.append(group_name)
549+ subprocess.check_call(cmd)
550+
551+
552+def lsb_release():
553+ """Return /etc/os-release in a dict."""
554+ d = {}
555+ with open('/etc/os-release', 'r') as lsb:
556+ for l in lsb:
557+ s = l.split('=')
558+ if len(s) != 2:
559+ continue
560+ d[s[0].strip()] = s[1].strip()
561+ return d
562+
563+
564+def cmp_pkgrevno(package, revno, pkgcache=None):
565+ """Compare supplied revno with the revno of the installed package.
566+
567+ * 1 => Installed revno is greater than supplied arg
568+ * 0 => Installed revno is the same as supplied arg
569+ * -1 => Installed revno is less than supplied arg
570+
571+ This function imports YumBase function if the pkgcache argument
572+ is None.
573+ """
574+ if not pkgcache:
575+ y = yum.YumBase()
576+ packages = y.doPackageLists()
577+ pkgcache = {i.Name: i.version for i in packages['installed']}
578+ pkg = pkgcache[package]
579+ if pkg > revno:
580+ return 1
581+ if pkg < revno:
582+ return -1
583+ return 0
584diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/hooks/charmhelpers/core/host_factory/ubuntu.py
585new file mode 100644
586index 0000000..8c66af5
587--- /dev/null
588+++ b/hooks/charmhelpers/core/host_factory/ubuntu.py
589@@ -0,0 +1,56 @@
590+import subprocess
591+
592+
593+def service_available(service_name):
594+ """Determine whether a system service is available"""
595+ try:
596+ subprocess.check_output(
597+ ['service', service_name, 'status'],
598+ stderr=subprocess.STDOUT).decode('UTF-8')
599+ except subprocess.CalledProcessError as e:
600+ return b'unrecognized service' not in e.output
601+ else:
602+ return True
603+
604+
605+def add_new_group(group_name, system_group=False, gid=None):
606+ cmd = ['addgroup']
607+ if gid:
608+ cmd.extend(['--gid', str(gid)])
609+ if system_group:
610+ cmd.append('--system')
611+ else:
612+ cmd.extend([
613+ '--group',
614+ ])
615+ cmd.append(group_name)
616+ subprocess.check_call(cmd)
617+
618+
619+def lsb_release():
620+ """Return /etc/lsb-release in a dict"""
621+ d = {}
622+ with open('/etc/lsb-release', 'r') as lsb:
623+ for l in lsb:
624+ k, v = l.split('=')
625+ d[k.strip()] = v.strip()
626+ return d
627+
628+
629+def cmp_pkgrevno(package, revno, pkgcache=None):
630+ """Compare supplied revno with the revno of the installed package.
631+
632+ * 1 => Installed revno is greater than supplied arg
633+ * 0 => Installed revno is the same as supplied arg
634+ * -1 => Installed revno is less than supplied arg
635+
636+ This function imports apt_cache function from charmhelpers.fetch if
637+ the pkgcache argument is None. Be sure to add charmhelpers.fetch if
638+ you call this function, or pass an apt_pkg.Cache() instance.
639+ """
640+ import apt_pkg
641+ if not pkgcache:
642+ from charmhelpers.fetch import apt_cache
643+ pkgcache = apt_cache()
644+ pkg = pkgcache[package]
645+ return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
646diff --git a/hooks/charmhelpers/core/hugepage.py b/hooks/charmhelpers/core/hugepage.py
647index a783ad9..54b5b5e 100644
648--- a/hooks/charmhelpers/core/hugepage.py
649+++ b/hooks/charmhelpers/core/hugepage.py
650@@ -2,19 +2,17 @@
651
652 # Copyright 2014-2015 Canonical Limited.
653 #
654-# This file is part of charm-helpers.
655+# Licensed under the Apache License, Version 2.0 (the "License");
656+# you may not use this file except in compliance with the License.
657+# You may obtain a copy of the License at
658 #
659-# charm-helpers is free software: you can redistribute it and/or modify
660-# it under the terms of the GNU Lesser General Public License version 3 as
661-# published by the Free Software Foundation.
662+# http://www.apache.org/licenses/LICENSE-2.0
663 #
664-# charm-helpers is distributed in the hope that it will be useful,
665-# but WITHOUT ANY WARRANTY; without even the implied warranty of
666-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
667-# GNU Lesser General Public License for more details.
668-#
669-# You should have received a copy of the GNU Lesser General Public License
670-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
671+# Unless required by applicable law or agreed to in writing, software
672+# distributed under the License is distributed on an "AS IS" BASIS,
673+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
674+# See the License for the specific language governing permissions and
675+# limitations under the License.
676
677 import yaml
678 from charmhelpers.core import fstab
679diff --git a/hooks/charmhelpers/core/kernel.py b/hooks/charmhelpers/core/kernel.py
680index 5dc6495..2d40452 100644
681--- a/hooks/charmhelpers/core/kernel.py
682+++ b/hooks/charmhelpers/core/kernel.py
683@@ -3,29 +3,40 @@
684
685 # Copyright 2014-2015 Canonical Limited.
686 #
687-# This file is part of charm-helpers.
688+# Licensed under the Apache License, Version 2.0 (the "License");
689+# you may not use this file except in compliance with the License.
690+# You may obtain a copy of the License at
691 #
692-# charm-helpers is free software: you can redistribute it and/or modify
693-# it under the terms of the GNU Lesser General Public License version 3 as
694-# published by the Free Software Foundation.
695+# http://www.apache.org/licenses/LICENSE-2.0
696 #
697-# charm-helpers is distributed in the hope that it will be useful,
698-# but WITHOUT ANY WARRANTY; without even the implied warranty of
699-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
700-# GNU Lesser General Public License for more details.
701-#
702-# You should have received a copy of the GNU Lesser General Public License
703-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
704+# Unless required by applicable law or agreed to in writing, software
705+# distributed under the License is distributed on an "AS IS" BASIS,
706+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
707+# See the License for the specific language governing permissions and
708+# limitations under the License.
709
710-__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
711+import re
712+import subprocess
713
714+from charmhelpers.osplatform import get_platform
715 from charmhelpers.core.hookenv import (
716 log,
717 INFO
718 )
719
720-from subprocess import check_call, check_output
721-import re
722+__platform__ = get_platform()
723+if __platform__ == "ubuntu":
724+ from charmhelpers.core.kernel_factory.ubuntu import (
725+ persistent_modprobe,
726+ update_initramfs,
727+ ) # flake8: noqa -- ignore F401 for this import
728+elif __platform__ == "centos":
729+ from charmhelpers.core.kernel_factory.centos import (
730+ persistent_modprobe,
731+ update_initramfs,
732+ ) # flake8: noqa -- ignore F401 for this import
733+
734+__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
735
736
737 def modprobe(module, persist=True):
738@@ -34,11 +45,9 @@ def modprobe(module, persist=True):
739
740 log('Loading kernel module %s' % module, level=INFO)
741
742- check_call(cmd)
743+ subprocess.check_call(cmd)
744 if persist:
745- with open('/etc/modules', 'r+') as modules:
746- if module not in modules.read():
747- modules.write(module)
748+ persistent_modprobe(module)
749
750
751 def rmmod(module, force=False):
752@@ -48,21 +57,16 @@ def rmmod(module, force=False):
753 cmd.append('-f')
754 cmd.append(module)
755 log('Removing kernel module %s' % module, level=INFO)
756- return check_call(cmd)
757+ return subprocess.check_call(cmd)
758
759
760 def lsmod():
761 """Shows what kernel modules are currently loaded"""
762- return check_output(['lsmod'],
763- universal_newlines=True)
764+ return subprocess.check_output(['lsmod'],
765+ universal_newlines=True)
766
767
768 def is_module_loaded(module):
769 """Checks if a kernel module is already loaded"""
770 matches = re.findall('^%s[ ]+' % module, lsmod(), re.M)
771 return len(matches) > 0
772-
773-
774-def update_initramfs(version='all'):
775- """Updates an initramfs image"""
776- return check_call(["update-initramfs", "-k", version, "-u"])
777diff --git a/hooks/charmhelpers/core/kernel_factory/__init__.py b/hooks/charmhelpers/core/kernel_factory/__init__.py
778new file mode 100644
779index 0000000..e69de29
780--- /dev/null
781+++ b/hooks/charmhelpers/core/kernel_factory/__init__.py
782diff --git a/hooks/charmhelpers/core/kernel_factory/centos.py b/hooks/charmhelpers/core/kernel_factory/centos.py
783new file mode 100644
784index 0000000..1c402c1
785--- /dev/null
786+++ b/hooks/charmhelpers/core/kernel_factory/centos.py
787@@ -0,0 +1,17 @@
788+import subprocess
789+import os
790+
791+
792+def persistent_modprobe(module):
793+ """Load a kernel module and configure for auto-load on reboot."""
794+ if not os.path.exists('/etc/rc.modules'):
795+ open('/etc/rc.modules', 'a')
796+ os.chmod('/etc/rc.modules', 111)
797+ with open('/etc/rc.modules', 'r+') as modules:
798+ if module not in modules.read():
799+ modules.write('modprobe %s\n' % module)
800+
801+
802+def update_initramfs(version='all'):
803+ """Updates an initramfs image."""
804+ return subprocess.check_call(["dracut", "-f", version])
805diff --git a/hooks/charmhelpers/core/kernel_factory/ubuntu.py b/hooks/charmhelpers/core/kernel_factory/ubuntu.py
806new file mode 100644
807index 0000000..2155964
808--- /dev/null
809+++ b/hooks/charmhelpers/core/kernel_factory/ubuntu.py
810@@ -0,0 +1,13 @@
811+import subprocess
812+
813+
814+def persistent_modprobe(module):
815+ """Load a kernel module and configure for auto-load on reboot."""
816+ with open('/etc/modules', 'r+') as modules:
817+ if module not in modules.read():
818+ modules.write(module)
819+
820+
821+def update_initramfs(version='all'):
822+ """Updates an initramfs image."""
823+ return subprocess.check_call(["update-initramfs", "-k", version, "-u"])
824diff --git a/hooks/charmhelpers/core/services/__init__.py b/hooks/charmhelpers/core/services/__init__.py
825index 0928158..61fd074 100644
826--- a/hooks/charmhelpers/core/services/__init__.py
827+++ b/hooks/charmhelpers/core/services/__init__.py
828@@ -1,18 +1,16 @@
829 # Copyright 2014-2015 Canonical Limited.
830 #
831-# This file is part of charm-helpers.
832+# Licensed under the Apache License, Version 2.0 (the "License");
833+# you may not use this file except in compliance with the License.
834+# You may obtain a copy of the License at
835 #
836-# charm-helpers is free software: you can redistribute it and/or modify
837-# it under the terms of the GNU Lesser General Public License version 3 as
838-# published by the Free Software Foundation.
839+# http://www.apache.org/licenses/LICENSE-2.0
840 #
841-# charm-helpers is distributed in the hope that it will be useful,
842-# but WITHOUT ANY WARRANTY; without even the implied warranty of
843-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
844-# GNU Lesser General Public License for more details.
845-#
846-# You should have received a copy of the GNU Lesser General Public License
847-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
848+# Unless required by applicable law or agreed to in writing, software
849+# distributed under the License is distributed on an "AS IS" BASIS,
850+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
851+# See the License for the specific language governing permissions and
852+# limitations under the License.
853
854 from .base import * # NOQA
855 from .helpers import * # NOQA
856diff --git a/hooks/charmhelpers/core/services/base.py b/hooks/charmhelpers/core/services/base.py
857index a42660c..ca9dc99 100644
858--- a/hooks/charmhelpers/core/services/base.py
859+++ b/hooks/charmhelpers/core/services/base.py
860@@ -1,18 +1,16 @@
861 # Copyright 2014-2015 Canonical Limited.
862 #
863-# This file is part of charm-helpers.
864+# Licensed under the Apache License, Version 2.0 (the "License");
865+# you may not use this file except in compliance with the License.
866+# You may obtain a copy of the License at
867 #
868-# charm-helpers is free software: you can redistribute it and/or modify
869-# it under the terms of the GNU Lesser General Public License version 3 as
870-# published by the Free Software Foundation.
871+# http://www.apache.org/licenses/LICENSE-2.0
872 #
873-# charm-helpers is distributed in the hope that it will be useful,
874-# but WITHOUT ANY WARRANTY; without even the implied warranty of
875-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
876-# GNU Lesser General Public License for more details.
877-#
878-# You should have received a copy of the GNU Lesser General Public License
879-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
880+# Unless required by applicable law or agreed to in writing, software
881+# distributed under the License is distributed on an "AS IS" BASIS,
882+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
883+# See the License for the specific language governing permissions and
884+# limitations under the License.
885
886 import os
887 import json
888diff --git a/hooks/charmhelpers/core/services/helpers.py b/hooks/charmhelpers/core/services/helpers.py
889index 2423704..3e6e30d 100644
890--- a/hooks/charmhelpers/core/services/helpers.py
891+++ b/hooks/charmhelpers/core/services/helpers.py
892@@ -1,18 +1,16 @@
893 # Copyright 2014-2015 Canonical Limited.
894 #
895-# This file is part of charm-helpers.
896+# Licensed under the Apache License, Version 2.0 (the "License");
897+# you may not use this file except in compliance with the License.
898+# You may obtain a copy of the License at
899 #
900-# charm-helpers is free software: you can redistribute it and/or modify
901-# it under the terms of the GNU Lesser General Public License version 3 as
902-# published by the Free Software Foundation.
903+# http://www.apache.org/licenses/LICENSE-2.0
904 #
905-# charm-helpers is distributed in the hope that it will be useful,
906-# but WITHOUT ANY WARRANTY; without even the implied warranty of
907-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
908-# GNU Lesser General Public License for more details.
909-#
910-# You should have received a copy of the GNU Lesser General Public License
911-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
912+# Unless required by applicable law or agreed to in writing, software
913+# distributed under the License is distributed on an "AS IS" BASIS,
914+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
915+# See the License for the specific language governing permissions and
916+# limitations under the License.
917
918 import os
919 import yaml
920diff --git a/hooks/charmhelpers/core/strutils.py b/hooks/charmhelpers/core/strutils.py
921index 7e3f969..dd9b971 100644
922--- a/hooks/charmhelpers/core/strutils.py
923+++ b/hooks/charmhelpers/core/strutils.py
924@@ -3,19 +3,17 @@
925
926 # Copyright 2014-2015 Canonical Limited.
927 #
928-# This file is part of charm-helpers.
929+# Licensed under the Apache License, Version 2.0 (the "License");
930+# you may not use this file except in compliance with the License.
931+# You may obtain a copy of the License at
932 #
933-# charm-helpers is free software: you can redistribute it and/or modify
934-# it under the terms of the GNU Lesser General Public License version 3 as
935-# published by the Free Software Foundation.
936+# http://www.apache.org/licenses/LICENSE-2.0
937 #
938-# charm-helpers is distributed in the hope that it will be useful,
939-# but WITHOUT ANY WARRANTY; without even the implied warranty of
940-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
941-# GNU Lesser General Public License for more details.
942-#
943-# You should have received a copy of the GNU Lesser General Public License
944-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
945+# Unless required by applicable law or agreed to in writing, software
946+# distributed under the License is distributed on an "AS IS" BASIS,
947+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
948+# See the License for the specific language governing permissions and
949+# limitations under the License.
950
951 import six
952 import re
953diff --git a/hooks/charmhelpers/core/sysctl.py b/hooks/charmhelpers/core/sysctl.py
954index 21cc8ab..6e413e3 100644
955--- a/hooks/charmhelpers/core/sysctl.py
956+++ b/hooks/charmhelpers/core/sysctl.py
957@@ -3,19 +3,17 @@
958
959 # Copyright 2014-2015 Canonical Limited.
960 #
961-# This file is part of charm-helpers.
962+# Licensed under the Apache License, Version 2.0 (the "License");
963+# you may not use this file except in compliance with the License.
964+# You may obtain a copy of the License at
965 #
966-# charm-helpers is free software: you can redistribute it and/or modify
967-# it under the terms of the GNU Lesser General Public License version 3 as
968-# published by the Free Software Foundation.
969+# http://www.apache.org/licenses/LICENSE-2.0
970 #
971-# charm-helpers is distributed in the hope that it will be useful,
972-# but WITHOUT ANY WARRANTY; without even the implied warranty of
973-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
974-# GNU Lesser General Public License for more details.
975-#
976-# You should have received a copy of the GNU Lesser General Public License
977-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
978+# Unless required by applicable law or agreed to in writing, software
979+# distributed under the License is distributed on an "AS IS" BASIS,
980+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
981+# See the License for the specific language governing permissions and
982+# limitations under the License.
983
984 import yaml
985
986diff --git a/hooks/charmhelpers/core/templating.py b/hooks/charmhelpers/core/templating.py
987index d2d8eaf..7b801a3 100644
988--- a/hooks/charmhelpers/core/templating.py
989+++ b/hooks/charmhelpers/core/templating.py
990@@ -1,20 +1,19 @@
991 # Copyright 2014-2015 Canonical Limited.
992 #
993-# This file is part of charm-helpers.
994+# Licensed under the Apache License, Version 2.0 (the "License");
995+# you may not use this file except in compliance with the License.
996+# You may obtain a copy of the License at
997 #
998-# charm-helpers is free software: you can redistribute it and/or modify
999-# it under the terms of the GNU Lesser General Public License version 3 as
1000-# published by the Free Software Foundation.
1001+# http://www.apache.org/licenses/LICENSE-2.0
1002 #
1003-# charm-helpers is distributed in the hope that it will be useful,
1004-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1005-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1006-# GNU Lesser General Public License for more details.
1007-#
1008-# You should have received a copy of the GNU Lesser General Public License
1009-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1010+# Unless required by applicable law or agreed to in writing, software
1011+# distributed under the License is distributed on an "AS IS" BASIS,
1012+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1013+# See the License for the specific language governing permissions and
1014+# limitations under the License.
1015
1016 import os
1017+import sys
1018
1019 from charmhelpers.core import host
1020 from charmhelpers.core import hookenv
1021@@ -40,8 +39,9 @@ def render(source, target, context, owner='root', group='root',
1022 The rendered template will be written to the file as well as being returned
1023 as a string.
1024
1025- Note: Using this requires python-jinja2; if it is not installed, calling
1026- this will attempt to use charmhelpers.fetch.apt_install to install it.
1027+ Note: Using this requires python-jinja2 or python3-jinja2; if it is not
1028+ installed, calling this will attempt to use charmhelpers.fetch.apt_install
1029+ to install it.
1030 """
1031 try:
1032 from jinja2 import FileSystemLoader, Environment, exceptions
1033@@ -53,7 +53,10 @@ def render(source, target, context, owner='root', group='root',
1034 'charmhelpers.fetch to install it',
1035 level=hookenv.ERROR)
1036 raise
1037- apt_install('python-jinja2', fatal=True)
1038+ if sys.version_info.major == 2:
1039+ apt_install('python-jinja2', fatal=True)
1040+ else:
1041+ apt_install('python3-jinja2', fatal=True)
1042 from jinja2 import FileSystemLoader, Environment, exceptions
1043
1044 if template_loader:
1045diff --git a/hooks/charmhelpers/core/unitdata.py b/hooks/charmhelpers/core/unitdata.py
1046index 338104e..54ec969 100644
1047--- a/hooks/charmhelpers/core/unitdata.py
1048+++ b/hooks/charmhelpers/core/unitdata.py
1049@@ -3,20 +3,17 @@
1050 #
1051 # Copyright 2014-2015 Canonical Limited.
1052 #
1053-# This file is part of charm-helpers.
1054+# Licensed under the Apache License, Version 2.0 (the "License");
1055+# you may not use this file except in compliance with the License.
1056+# You may obtain a copy of the License at
1057 #
1058-# charm-helpers is free software: you can redistribute it and/or modify
1059-# it under the terms of the GNU Lesser General Public License version 3 as
1060-# published by the Free Software Foundation.
1061-#
1062-# charm-helpers is distributed in the hope that it will be useful,
1063-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1064-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1065-# GNU Lesser General Public License for more details.
1066-#
1067-# You should have received a copy of the GNU Lesser General Public License
1068-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1069+# http://www.apache.org/licenses/LICENSE-2.0
1070 #
1071+# Unless required by applicable law or agreed to in writing, software
1072+# distributed under the License is distributed on an "AS IS" BASIS,
1073+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1074+# See the License for the specific language governing permissions and
1075+# limitations under the License.
1076 #
1077 # Authors:
1078 # Kapil Thangavelu <kapil.foss@gmail.com>
1079diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py
1080index db0d86a..ec5e0fe 100644
1081--- a/hooks/charmhelpers/fetch/__init__.py
1082+++ b/hooks/charmhelpers/fetch/__init__.py
1083@@ -1,32 +1,24 @@
1084 # Copyright 2014-2015 Canonical Limited.
1085 #
1086-# This file is part of charm-helpers.
1087+# Licensed under the Apache License, Version 2.0 (the "License");
1088+# you may not use this file except in compliance with the License.
1089+# You may obtain a copy of the License at
1090 #
1091-# charm-helpers is free software: you can redistribute it and/or modify
1092-# it under the terms of the GNU Lesser General Public License version 3 as
1093-# published by the Free Software Foundation.
1094+# http://www.apache.org/licenses/LICENSE-2.0
1095 #
1096-# charm-helpers is distributed in the hope that it will be useful,
1097-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1098-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1099-# GNU Lesser General Public License for more details.
1100-#
1101-# You should have received a copy of the GNU Lesser General Public License
1102-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1103+# Unless required by applicable law or agreed to in writing, software
1104+# distributed under the License is distributed on an "AS IS" BASIS,
1105+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1106+# See the License for the specific language governing permissions and
1107+# limitations under the License.
1108
1109 import importlib
1110-from tempfile import NamedTemporaryFile
1111-import time
1112+from charmhelpers.osplatform import get_platform
1113 from yaml import safe_load
1114-from charmhelpers.core.host import (
1115- lsb_release
1116-)
1117-import subprocess
1118 from charmhelpers.core.hookenv import (
1119 config,
1120 log,
1121 )
1122-import os
1123
1124 import six
1125 if six.PY3:
1126@@ -35,79 +27,6 @@ else:
1127 from urlparse import urlparse, urlunparse
1128
1129
1130-CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
1131-deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
1132-"""
1133-PROPOSED_POCKET = """# Proposed
1134-deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
1135-"""
1136-CLOUD_ARCHIVE_POCKETS = {
1137- # Folsom
1138- 'folsom': 'precise-updates/folsom',
1139- 'precise-folsom': 'precise-updates/folsom',
1140- 'precise-folsom/updates': 'precise-updates/folsom',
1141- 'precise-updates/folsom': 'precise-updates/folsom',
1142- 'folsom/proposed': 'precise-proposed/folsom',
1143- 'precise-folsom/proposed': 'precise-proposed/folsom',
1144- 'precise-proposed/folsom': 'precise-proposed/folsom',
1145- # Grizzly
1146- 'grizzly': 'precise-updates/grizzly',
1147- 'precise-grizzly': 'precise-updates/grizzly',
1148- 'precise-grizzly/updates': 'precise-updates/grizzly',
1149- 'precise-updates/grizzly': 'precise-updates/grizzly',
1150- 'grizzly/proposed': 'precise-proposed/grizzly',
1151- 'precise-grizzly/proposed': 'precise-proposed/grizzly',
1152- 'precise-proposed/grizzly': 'precise-proposed/grizzly',
1153- # Havana
1154- 'havana': 'precise-updates/havana',
1155- 'precise-havana': 'precise-updates/havana',
1156- 'precise-havana/updates': 'precise-updates/havana',
1157- 'precise-updates/havana': 'precise-updates/havana',
1158- 'havana/proposed': 'precise-proposed/havana',
1159- 'precise-havana/proposed': 'precise-proposed/havana',
1160- 'precise-proposed/havana': 'precise-proposed/havana',
1161- # Icehouse
1162- 'icehouse': 'precise-updates/icehouse',
1163- 'precise-icehouse': 'precise-updates/icehouse',
1164- 'precise-icehouse/updates': 'precise-updates/icehouse',
1165- 'precise-updates/icehouse': 'precise-updates/icehouse',
1166- 'icehouse/proposed': 'precise-proposed/icehouse',
1167- 'precise-icehouse/proposed': 'precise-proposed/icehouse',
1168- 'precise-proposed/icehouse': 'precise-proposed/icehouse',
1169- # Juno
1170- 'juno': 'trusty-updates/juno',
1171- 'trusty-juno': 'trusty-updates/juno',
1172- 'trusty-juno/updates': 'trusty-updates/juno',
1173- 'trusty-updates/juno': 'trusty-updates/juno',
1174- 'juno/proposed': 'trusty-proposed/juno',
1175- 'trusty-juno/proposed': 'trusty-proposed/juno',
1176- 'trusty-proposed/juno': 'trusty-proposed/juno',
1177- # Kilo
1178- 'kilo': 'trusty-updates/kilo',
1179- 'trusty-kilo': 'trusty-updates/kilo',
1180- 'trusty-kilo/updates': 'trusty-updates/kilo',
1181- 'trusty-updates/kilo': 'trusty-updates/kilo',
1182- 'kilo/proposed': 'trusty-proposed/kilo',
1183- 'trusty-kilo/proposed': 'trusty-proposed/kilo',
1184- 'trusty-proposed/kilo': 'trusty-proposed/kilo',
1185- # Liberty
1186- 'liberty': 'trusty-updates/liberty',
1187- 'trusty-liberty': 'trusty-updates/liberty',
1188- 'trusty-liberty/updates': 'trusty-updates/liberty',
1189- 'trusty-updates/liberty': 'trusty-updates/liberty',
1190- 'liberty/proposed': 'trusty-proposed/liberty',
1191- 'trusty-liberty/proposed': 'trusty-proposed/liberty',
1192- 'trusty-proposed/liberty': 'trusty-proposed/liberty',
1193- # Mitaka
1194- 'mitaka': 'trusty-updates/mitaka',
1195- 'trusty-mitaka': 'trusty-updates/mitaka',
1196- 'trusty-mitaka/updates': 'trusty-updates/mitaka',
1197- 'trusty-updates/mitaka': 'trusty-updates/mitaka',
1198- 'mitaka/proposed': 'trusty-proposed/mitaka',
1199- 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
1200- 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
1201-}
1202-
1203 # The order of this list is very important. Handlers should be listed in from
1204 # least- to most-specific URL matching.
1205 FETCH_HANDLERS = (
1206@@ -116,10 +35,6 @@ FETCH_HANDLERS = (
1207 'charmhelpers.fetch.giturl.GitUrlFetchHandler',
1208 )
1209
1210-APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
1211-APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
1212-APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
1213-
1214
1215 class SourceConfigError(Exception):
1216 pass
1217@@ -157,180 +72,38 @@ class BaseFetchHandler(object):
1218 return urlunparse(parts)
1219
1220
1221-def filter_installed_packages(packages):
1222- """Returns a list of packages that require installation"""
1223- cache = apt_cache()
1224- _pkgs = []
1225- for package in packages:
1226- try:
1227- p = cache[package]
1228- p.current_ver or _pkgs.append(package)
1229- except KeyError:
1230- log('Package {} has no installation candidate.'.format(package),
1231- level='WARNING')
1232- _pkgs.append(package)
1233- return _pkgs
1234-
1235-
1236-def apt_cache(in_memory=True):
1237- """Build and return an apt cache"""
1238- from apt import apt_pkg
1239- apt_pkg.init()
1240- if in_memory:
1241- apt_pkg.config.set("Dir::Cache::pkgcache", "")
1242- apt_pkg.config.set("Dir::Cache::srcpkgcache", "")
1243- return apt_pkg.Cache()
1244-
1245-
1246-def apt_install(packages, options=None, fatal=False):
1247- """Install one or more packages"""
1248- if options is None:
1249- options = ['--option=Dpkg::Options::=--force-confold']
1250-
1251- cmd = ['apt-get', '--assume-yes']
1252- cmd.extend(options)
1253- cmd.append('install')
1254- if isinstance(packages, six.string_types):
1255- cmd.append(packages)
1256- else:
1257- cmd.extend(packages)
1258- log("Installing {} with options: {}".format(packages,
1259- options))
1260- _run_apt_command(cmd, fatal)
1261-
1262-
1263-def apt_upgrade(options=None, fatal=False, dist=False):
1264- """Upgrade all packages"""
1265- if options is None:
1266- options = ['--option=Dpkg::Options::=--force-confold']
1267-
1268- cmd = ['apt-get', '--assume-yes']
1269- cmd.extend(options)
1270- if dist:
1271- cmd.append('dist-upgrade')
1272- else:
1273- cmd.append('upgrade')
1274- log("Upgrading with options: {}".format(options))
1275- _run_apt_command(cmd, fatal)
1276-
1277-
1278-def apt_update(fatal=False):
1279- """Update local apt cache"""
1280- cmd = ['apt-get', 'update']
1281- _run_apt_command(cmd, fatal)
1282-
1283-
1284-def apt_purge(packages, fatal=False):
1285- """Purge one or more packages"""
1286- cmd = ['apt-get', '--assume-yes', 'purge']
1287- if isinstance(packages, six.string_types):
1288- cmd.append(packages)
1289- else:
1290- cmd.extend(packages)
1291- log("Purging {}".format(packages))
1292- _run_apt_command(cmd, fatal)
1293-
1294-
1295-def apt_mark(packages, mark, fatal=False):
1296- """Flag one or more packages using apt-mark"""
1297- log("Marking {} as {}".format(packages, mark))
1298- cmd = ['apt-mark', mark]
1299- if isinstance(packages, six.string_types):
1300- cmd.append(packages)
1301- else:
1302- cmd.extend(packages)
1303-
1304- if fatal:
1305- subprocess.check_call(cmd, universal_newlines=True)
1306- else:
1307- subprocess.call(cmd, universal_newlines=True)
1308+__platform__ = get_platform()
1309+module = "charmhelpers.fetch.%s" % __platform__
1310+fetch = importlib.import_module(module)
1311
1312+filter_installed_packages = fetch.filter_installed_packages
1313+install = fetch.install
1314+upgrade = fetch.upgrade
1315+update = fetch.update
1316+purge = fetch.purge
1317+add_source = fetch.add_source
1318
1319-def apt_hold(packages, fatal=False):
1320- return apt_mark(packages, 'hold', fatal=fatal)
1321-
1322-
1323-def apt_unhold(packages, fatal=False):
1324- return apt_mark(packages, 'unhold', fatal=fatal)
1325-
1326-
1327-def add_source(source, key=None):
1328- """Add a package source to this system.
1329-
1330- @param source: a URL or sources.list entry, as supported by
1331- add-apt-repository(1). Examples::
1332-
1333- ppa:charmers/example
1334- deb https://stub:key@private.example.com/ubuntu trusty main
1335-
1336- In addition:
1337- 'proposed:' may be used to enable the standard 'proposed'
1338- pocket for the release.
1339- 'cloud:' may be used to activate official cloud archive pockets,
1340- such as 'cloud:icehouse'
1341- 'distro' may be used as a noop
1342-
1343- @param key: A key to be added to the system's APT keyring and used
1344- to verify the signatures on packages. Ideally, this should be an
1345- ASCII format GPG public key including the block headers. A GPG key
1346- id may also be used, but be aware that only insecure protocols are
1347- available to retrieve the actual public key from a public keyserver
1348- placing your Juju environment at risk. ppa and cloud archive keys
1349- are securely added automtically, so sould not be provided.
1350- """
1351- if source is None:
1352- log('Source is not present. Skipping')
1353- return
1354-
1355- if (source.startswith('ppa:') or
1356- source.startswith('http') or
1357- source.startswith('deb ') or
1358- source.startswith('cloud-archive:')):
1359- subprocess.check_call(['add-apt-repository', '--yes', source])
1360- elif source.startswith('cloud:'):
1361- apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
1362- fatal=True)
1363- pocket = source.split(':')[-1]
1364- if pocket not in CLOUD_ARCHIVE_POCKETS:
1365- raise SourceConfigError(
1366- 'Unsupported cloud: source option %s' %
1367- pocket)
1368- actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
1369- with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
1370- apt.write(CLOUD_ARCHIVE.format(actual_pocket))
1371- elif source == 'proposed':
1372- release = lsb_release()['DISTRIB_CODENAME']
1373- with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
1374- apt.write(PROPOSED_POCKET.format(release))
1375- elif source == 'distro':
1376- pass
1377- else:
1378- log("Unknown source: {!r}".format(source))
1379-
1380- if key:
1381- if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
1382- with NamedTemporaryFile('w+') as key_file:
1383- key_file.write(key)
1384- key_file.flush()
1385- key_file.seek(0)
1386- subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
1387- else:
1388- # Note that hkp: is in no way a secure protocol. Using a
1389- # GPG key id is pointless from a security POV unless you
1390- # absolutely trust your network and DNS.
1391- subprocess.check_call(['apt-key', 'adv', '--keyserver',
1392- 'hkp://keyserver.ubuntu.com:80', '--recv',
1393- key])
1394+if __platform__ == "ubuntu":
1395+ apt_cache = fetch.apt_cache
1396+ apt_install = fetch.install
1397+ apt_update = fetch.update
1398+ apt_upgrade = fetch.upgrade
1399+ apt_purge = fetch.purge
1400+ apt_mark = fetch.apt_mark
1401+ apt_hold = fetch.apt_hold
1402+ apt_unhold = fetch.apt_unhold
1403+ get_upstream_version = fetch.get_upstream_version
1404+elif __platform__ == "centos":
1405+ yum_search = fetch.yum_search
1406
1407
1408 def configure_sources(update=False,
1409 sources_var='install_sources',
1410 keys_var='install_keys'):
1411- """
1412- Configure multiple sources from charm configuration.
1413+ """Configure multiple sources from charm configuration.
1414
1415 The lists are encoded as yaml fragments in the configuration.
1416- The frament needs to be included as a string. Sources and their
1417+ The fragment needs to be included as a string. Sources and their
1418 corresponding keys are of the types supported by add_source().
1419
1420 Example config:
1421@@ -362,12 +135,11 @@ def configure_sources(update=False,
1422 for source, key in zip(sources, keys):
1423 add_source(source, key)
1424 if update:
1425- apt_update(fatal=True)
1426+ fetch.update(fatal=True)
1427
1428
1429 def install_remote(source, *args, **kwargs):
1430- """
1431- Install a file tree from a remote source
1432+ """Install a file tree from a remote source.
1433
1434 The specified source should be a url of the form:
1435 scheme://[host]/path[#[option=value][&...]]
1436@@ -390,19 +162,17 @@ def install_remote(source, *args, **kwargs):
1437 # We ONLY check for True here because can_handle may return a string
1438 # explaining why it can't handle a given source.
1439 handlers = [h for h in plugins() if h.can_handle(source) is True]
1440- installed_to = None
1441 for handler in handlers:
1442 try:
1443- installed_to = handler.install(source, *args, **kwargs)
1444+ return handler.install(source, *args, **kwargs)
1445 except UnhandledSource as e:
1446 log('Install source attempt unsuccessful: {}'.format(e),
1447 level='WARNING')
1448- if not installed_to:
1449- raise UnhandledSource("No handler found for source {}".format(source))
1450- return installed_to
1451+ raise UnhandledSource("No handler found for source {}".format(source))
1452
1453
1454 def install_from_config(config_var_name):
1455+ """Install a file from config."""
1456 charm_config = config()
1457 source = charm_config[config_var_name]
1458 return install_remote(source)
1459@@ -425,40 +195,3 @@ def plugins(fetch_handlers=None):
1460 log("FetchHandler {} not found, skipping plugin".format(
1461 handler_name))
1462 return plugin_list
1463-
1464-
1465-def _run_apt_command(cmd, fatal=False):
1466- """
1467- Run an APT command, checking output and retrying if the fatal flag is set
1468- to True.
1469-
1470- :param: cmd: str: The apt command to run.
1471- :param: fatal: bool: Whether the command's output should be checked and
1472- retried.
1473- """
1474- env = os.environ.copy()
1475-
1476- if 'DEBIAN_FRONTEND' not in env:
1477- env['DEBIAN_FRONTEND'] = 'noninteractive'
1478-
1479- if fatal:
1480- retry_count = 0
1481- result = None
1482-
1483- # If the command is considered "fatal", we need to retry if the apt
1484- # lock was not acquired.
1485-
1486- while result is None or result == APT_NO_LOCK:
1487- try:
1488- result = subprocess.check_call(cmd, env=env)
1489- except subprocess.CalledProcessError as e:
1490- retry_count = retry_count + 1
1491- if retry_count > APT_NO_LOCK_RETRY_COUNT:
1492- raise
1493- result = e.returncode
1494- log("Couldn't acquire DPKG lock. Will retry in {} seconds."
1495- "".format(APT_NO_LOCK_RETRY_DELAY))
1496- time.sleep(APT_NO_LOCK_RETRY_DELAY)
1497-
1498- else:
1499- subprocess.call(cmd, env=env)
1500diff --git a/hooks/charmhelpers/fetch/archiveurl.py b/hooks/charmhelpers/fetch/archiveurl.py
1501index b8e0943..dd24f9e 100644
1502--- a/hooks/charmhelpers/fetch/archiveurl.py
1503+++ b/hooks/charmhelpers/fetch/archiveurl.py
1504@@ -1,18 +1,16 @@
1505 # Copyright 2014-2015 Canonical Limited.
1506 #
1507-# This file is part of charm-helpers.
1508+# Licensed under the Apache License, Version 2.0 (the "License");
1509+# you may not use this file except in compliance with the License.
1510+# You may obtain a copy of the License at
1511 #
1512-# charm-helpers is free software: you can redistribute it and/or modify
1513-# it under the terms of the GNU Lesser General Public License version 3 as
1514-# published by the Free Software Foundation.
1515+# http://www.apache.org/licenses/LICENSE-2.0
1516 #
1517-# charm-helpers is distributed in the hope that it will be useful,
1518-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1519-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1520-# GNU Lesser General Public License for more details.
1521-#
1522-# You should have received a copy of the GNU Lesser General Public License
1523-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1524+# Unless required by applicable law or agreed to in writing, software
1525+# distributed under the License is distributed on an "AS IS" BASIS,
1526+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1527+# See the License for the specific language governing permissions and
1528+# limitations under the License.
1529
1530 import os
1531 import hashlib
1532diff --git a/hooks/charmhelpers/fetch/bzrurl.py b/hooks/charmhelpers/fetch/bzrurl.py
1533index cafd27f..07cd029 100644
1534--- a/hooks/charmhelpers/fetch/bzrurl.py
1535+++ b/hooks/charmhelpers/fetch/bzrurl.py
1536@@ -1,18 +1,16 @@
1537 # Copyright 2014-2015 Canonical Limited.
1538 #
1539-# This file is part of charm-helpers.
1540+# Licensed under the Apache License, Version 2.0 (the "License");
1541+# you may not use this file except in compliance with the License.
1542+# You may obtain a copy of the License at
1543 #
1544-# charm-helpers is free software: you can redistribute it and/or modify
1545-# it under the terms of the GNU Lesser General Public License version 3 as
1546-# published by the Free Software Foundation.
1547+# http://www.apache.org/licenses/LICENSE-2.0
1548 #
1549-# charm-helpers is distributed in the hope that it will be useful,
1550-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1551-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1552-# GNU Lesser General Public License for more details.
1553-#
1554-# You should have received a copy of the GNU Lesser General Public License
1555-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1556+# Unless required by applicable law or agreed to in writing, software
1557+# distributed under the License is distributed on an "AS IS" BASIS,
1558+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1559+# See the License for the specific language governing permissions and
1560+# limitations under the License.
1561
1562 import os
1563 from subprocess import check_call
1564@@ -20,19 +18,20 @@ from charmhelpers.fetch import (
1565 BaseFetchHandler,
1566 UnhandledSource,
1567 filter_installed_packages,
1568- apt_install,
1569+ install,
1570 )
1571 from charmhelpers.core.host import mkdir
1572
1573
1574 if filter_installed_packages(['bzr']) != []:
1575- apt_install(['bzr'])
1576+ install(['bzr'])
1577 if filter_installed_packages(['bzr']) != []:
1578 raise NotImplementedError('Unable to install bzr')
1579
1580
1581 class BzrUrlFetchHandler(BaseFetchHandler):
1582- """Handler for bazaar branches via generic and lp URLs"""
1583+ """Handler for bazaar branches via generic and lp URLs."""
1584+
1585 def can_handle(self, source):
1586 url_parts = self.parse_url(source)
1587 if url_parts.scheme not in ('bzr+ssh', 'lp', ''):
1588@@ -42,15 +41,23 @@ class BzrUrlFetchHandler(BaseFetchHandler):
1589 else:
1590 return True
1591
1592- def branch(self, source, dest):
1593+ def branch(self, source, dest, revno=None):
1594 if not self.can_handle(source):
1595 raise UnhandledSource("Cannot handle {}".format(source))
1596+ cmd_opts = []
1597+ if revno:
1598+ cmd_opts += ['-r', str(revno)]
1599 if os.path.exists(dest):
1600- check_call(['bzr', 'pull', '--overwrite', '-d', dest, source])
1601+ cmd = ['bzr', 'pull']
1602+ cmd += cmd_opts
1603+ cmd += ['--overwrite', '-d', dest, source]
1604 else:
1605- check_call(['bzr', 'branch', source, dest])
1606+ cmd = ['bzr', 'branch']
1607+ cmd += cmd_opts
1608+ cmd += [source, dest]
1609+ check_call(cmd)
1610
1611- def install(self, source, dest=None):
1612+ def install(self, source, dest=None, revno=None):
1613 url_parts = self.parse_url(source)
1614 branch_name = url_parts.path.strip("/").split("/")[-1]
1615 if dest:
1616@@ -59,10 +66,11 @@ class BzrUrlFetchHandler(BaseFetchHandler):
1617 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
1618 branch_name)
1619
1620- if not os.path.exists(dest_dir):
1621- mkdir(dest_dir, perms=0o755)
1622+ if dest and not os.path.exists(dest):
1623+ mkdir(dest, perms=0o755)
1624+
1625 try:
1626- self.branch(source, dest_dir)
1627+ self.branch(source, dest_dir, revno)
1628 except OSError as e:
1629 raise UnhandledSource(e.strerror)
1630 return dest_dir
1631diff --git a/hooks/charmhelpers/fetch/centos.py b/hooks/charmhelpers/fetch/centos.py
1632new file mode 100644
1633index 0000000..604bbfb
1634--- /dev/null
1635+++ b/hooks/charmhelpers/fetch/centos.py
1636@@ -0,0 +1,171 @@
1637+# Copyright 2014-2015 Canonical Limited.
1638+#
1639+# Licensed under the Apache License, Version 2.0 (the "License");
1640+# you may not use this file except in compliance with the License.
1641+# You may obtain a copy of the License at
1642+#
1643+# http://www.apache.org/licenses/LICENSE-2.0
1644+#
1645+# Unless required by applicable law or agreed to in writing, software
1646+# distributed under the License is distributed on an "AS IS" BASIS,
1647+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1648+# See the License for the specific language governing permissions and
1649+# limitations under the License.
1650+
1651+import subprocess
1652+import os
1653+import time
1654+import six
1655+import yum
1656+
1657+from tempfile import NamedTemporaryFile
1658+from charmhelpers.core.hookenv import log
1659+
1660+YUM_NO_LOCK = 1 # The return code for "couldn't acquire lock" in YUM.
1661+YUM_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
1662+YUM_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
1663+
1664+
1665+def filter_installed_packages(packages):
1666+ """Return a list of packages that require installation."""
1667+ yb = yum.YumBase()
1668+ package_list = yb.doPackageLists()
1669+ temp_cache = {p.base_package_name: 1 for p in package_list['installed']}
1670+
1671+ _pkgs = [p for p in packages if not temp_cache.get(p, False)]
1672+ return _pkgs
1673+
1674+
1675+def install(packages, options=None, fatal=False):
1676+ """Install one or more packages."""
1677+ cmd = ['yum', '--assumeyes']
1678+ if options is not None:
1679+ cmd.extend(options)
1680+ cmd.append('install')
1681+ if isinstance(packages, six.string_types):
1682+ cmd.append(packages)
1683+ else:
1684+ cmd.extend(packages)
1685+ log("Installing {} with options: {}".format(packages,
1686+ options))
1687+ _run_yum_command(cmd, fatal)
1688+
1689+
1690+def upgrade(options=None, fatal=False, dist=False):
1691+ """Upgrade all packages."""
1692+ cmd = ['yum', '--assumeyes']
1693+ if options is not None:
1694+ cmd.extend(options)
1695+ cmd.append('upgrade')
1696+ log("Upgrading with options: {}".format(options))
1697+ _run_yum_command(cmd, fatal)
1698+
1699+
1700+def update(fatal=False):
1701+ """Update local yum cache."""
1702+ cmd = ['yum', '--assumeyes', 'update']
1703+ log("Update with fatal: {}".format(fatal))
1704+ _run_yum_command(cmd, fatal)
1705+
1706+
1707+def purge(packages, fatal=False):
1708+ """Purge one or more packages."""
1709+ cmd = ['yum', '--assumeyes', 'remove']
1710+ if isinstance(packages, six.string_types):
1711+ cmd.append(packages)
1712+ else:
1713+ cmd.extend(packages)
1714+ log("Purging {}".format(packages))
1715+ _run_yum_command(cmd, fatal)
1716+
1717+
1718+def yum_search(packages):
1719+ """Search for a package."""
1720+ output = {}
1721+ cmd = ['yum', 'search']
1722+ if isinstance(packages, six.string_types):
1723+ cmd.append(packages)
1724+ else:
1725+ cmd.extend(packages)
1726+ log("Searching for {}".format(packages))
1727+ result = subprocess.check_output(cmd)
1728+ for package in list(packages):
1729+ output[package] = package in result
1730+ return output
1731+
1732+
1733+def add_source(source, key=None):
1734+ """Add a package source to this system.
1735+
1736+ @param source: a URL with a rpm package
1737+
1738+ @param key: A key to be added to the system's keyring and used
1739+ to verify the signatures on packages. Ideally, this should be an
1740+ ASCII format GPG public key including the block headers. A GPG key
1741+ id may also be used, but be aware that only insecure protocols are
1742+ available to retrieve the actual public key from a public keyserver
1743+ placing your Juju environment at risk.
1744+ """
1745+ if source is None:
1746+ log('Source is not present. Skipping')
1747+ return
1748+
1749+ if source.startswith('http'):
1750+ directory = '/etc/yum.repos.d/'
1751+ for filename in os.listdir(directory):
1752+ with open(directory + filename, 'r') as rpm_file:
1753+ if source in rpm_file.read():
1754+ break
1755+ else:
1756+ log("Add source: {!r}".format(source))
1757+ # write in the charms.repo
1758+ with open(directory + 'Charms.repo', 'a') as rpm_file:
1759+ rpm_file.write('[%s]\n' % source[7:].replace('/', '_'))
1760+ rpm_file.write('name=%s\n' % source[7:])
1761+ rpm_file.write('baseurl=%s\n\n' % source)
1762+ else:
1763+ log("Unknown source: {!r}".format(source))
1764+
1765+ if key:
1766+ if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
1767+ with NamedTemporaryFile('w+') as key_file:
1768+ key_file.write(key)
1769+ key_file.flush()
1770+ key_file.seek(0)
1771+ subprocess.check_call(['rpm', '--import', key_file])
1772+ else:
1773+ subprocess.check_call(['rpm', '--import', key])
1774+
1775+
1776+def _run_yum_command(cmd, fatal=False):
1777+ """Run an YUM command.
1778+
1779+ Checks the output and retry if the fatal flag is set to True.
1780+
1781+ :param: cmd: str: The yum command to run.
1782+ :param: fatal: bool: Whether the command's output should be checked and
1783+ retried.
1784+ """
1785+ env = os.environ.copy()
1786+
1787+ if fatal:
1788+ retry_count = 0
1789+ result = None
1790+
1791+ # If the command is considered "fatal", we need to retry if the yum
1792+ # lock was not acquired.
1793+
1794+ while result is None or result == YUM_NO_LOCK:
1795+ try:
1796+ result = subprocess.check_call(cmd, env=env)
1797+ except subprocess.CalledProcessError as e:
1798+ retry_count = retry_count + 1
1799+ if retry_count > YUM_NO_LOCK_RETRY_COUNT:
1800+ raise
1801+ result = e.returncode
1802+ log("Couldn't acquire YUM lock. Will retry in {} seconds."
1803+ "".format(YUM_NO_LOCK_RETRY_DELAY))
1804+ time.sleep(YUM_NO_LOCK_RETRY_DELAY)
1805+
1806+ else:
1807+ subprocess.call(cmd, env=env)
1808diff --git a/hooks/charmhelpers/fetch/giturl.py b/hooks/charmhelpers/fetch/giturl.py
1809index 65ed531..4cf21bc 100644
1810--- a/hooks/charmhelpers/fetch/giturl.py
1811+++ b/hooks/charmhelpers/fetch/giturl.py
1812@@ -1,18 +1,16 @@
1813 # Copyright 2014-2015 Canonical Limited.
1814 #
1815-# This file is part of charm-helpers.
1816+# Licensed under the Apache License, Version 2.0 (the "License");
1817+# you may not use this file except in compliance with the License.
1818+# You may obtain a copy of the License at
1819 #
1820-# charm-helpers is free software: you can redistribute it and/or modify
1821-# it under the terms of the GNU Lesser General Public License version 3 as
1822-# published by the Free Software Foundation.
1823+# http://www.apache.org/licenses/LICENSE-2.0
1824 #
1825-# charm-helpers is distributed in the hope that it will be useful,
1826-# but WITHOUT ANY WARRANTY; without even the implied warranty of
1827-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1828-# GNU Lesser General Public License for more details.
1829-#
1830-# You should have received a copy of the GNU Lesser General Public License
1831-# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1832+# Unless required by applicable law or agreed to in writing, software
1833+# distributed under the License is distributed on an "AS IS" BASIS,
1834+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1835+# See the License for the specific language governing permissions and
1836+# limitations under the License.
1837
1838 import os
1839 from subprocess import check_call, CalledProcessError
1840@@ -20,17 +18,18 @@ from charmhelpers.fetch import (
1841 BaseFetchHandler,
1842 UnhandledSource,
1843 filter_installed_packages,
1844- apt_install,
1845+ install,
1846 )
1847
1848 if filter_installed_packages(['git']) != []:
1849- apt_install(['git'])
1850+ install(['git'])
1851 if filter_installed_packages(['git']) != []:
1852 raise NotImplementedError('Unable to install git')
1853
1854
1855 class GitUrlFetchHandler(BaseFetchHandler):
1856- """Handler for git branches via generic and github URLs"""
1857+ """Handler for git branches via generic and github URLs."""
1858+
1859 def can_handle(self, source):
1860 url_parts = self.parse_url(source)
1861 # TODO (mattyw) no support for ssh git@ yet
1862diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/hooks/charmhelpers/fetch/ubuntu.py
1863new file mode 100644
1864index 0000000..fce496b
1865--- /dev/null
1866+++ b/hooks/charmhelpers/fetch/ubuntu.py
1867@@ -0,0 +1,336 @@
1868+# Copyright 2014-2015 Canonical Limited.
1869+#
1870+# Licensed under the Apache License, Version 2.0 (the "License");
1871+# you may not use this file except in compliance with the License.
1872+# You may obtain a copy of the License at
1873+#
1874+# http://www.apache.org/licenses/LICENSE-2.0
1875+#
1876+# Unless required by applicable law or agreed to in writing, software
1877+# distributed under the License is distributed on an "AS IS" BASIS,
1878+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1879+# See the License for the specific language governing permissions and
1880+# limitations under the License.
1881+
1882+import os
1883+import six
1884+import time
1885+import subprocess
1886+
1887+from tempfile import NamedTemporaryFile
1888+from charmhelpers.core.host import (
1889+ lsb_release
1890+)
1891+from charmhelpers.core.hookenv import log
1892+from charmhelpers.fetch import SourceConfigError
1893+
1894+CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
1895+deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
1896+"""
1897+
1898+PROPOSED_POCKET = """# Proposed
1899+deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
1900+"""
1901+
1902+CLOUD_ARCHIVE_POCKETS = {
1903+ # Folsom
1904+ 'folsom': 'precise-updates/folsom',
1905+ 'precise-folsom': 'precise-updates/folsom',
1906+ 'precise-folsom/updates': 'precise-updates/folsom',
1907+ 'precise-updates/folsom': 'precise-updates/folsom',
1908+ 'folsom/proposed': 'precise-proposed/folsom',
1909+ 'precise-folsom/proposed': 'precise-proposed/folsom',
1910+ 'precise-proposed/folsom': 'precise-proposed/folsom',
1911+ # Grizzly
1912+ 'grizzly': 'precise-updates/grizzly',
1913+ 'precise-grizzly': 'precise-updates/grizzly',
1914+ 'precise-grizzly/updates': 'precise-updates/grizzly',
1915+ 'precise-updates/grizzly': 'precise-updates/grizzly',
1916+ 'grizzly/proposed': 'precise-proposed/grizzly',
1917+ 'precise-grizzly/proposed': 'precise-proposed/grizzly',
1918+ 'precise-proposed/grizzly': 'precise-proposed/grizzly',
1919+ # Havana
1920+ 'havana': 'precise-updates/havana',
1921+ 'precise-havana': 'precise-updates/havana',
1922+ 'precise-havana/updates': 'precise-updates/havana',
1923+ 'precise-updates/havana': 'precise-updates/havana',
1924+ 'havana/proposed': 'precise-proposed/havana',
1925+ 'precise-havana/proposed': 'precise-proposed/havana',
1926+ 'precise-proposed/havana': 'precise-proposed/havana',
1927+ # Icehouse
1928+ 'icehouse': 'precise-updates/icehouse',
1929+ 'precise-icehouse': 'precise-updates/icehouse',
1930+ 'precise-icehouse/updates': 'precise-updates/icehouse',
1931+ 'precise-updates/icehouse': 'precise-updates/icehouse',
1932+ 'icehouse/proposed': 'precise-proposed/icehouse',
1933+ 'precise-icehouse/proposed': 'precise-proposed/icehouse',
1934+ 'precise-proposed/icehouse': 'precise-proposed/icehouse',
1935+ # Juno
1936+ 'juno': 'trusty-updates/juno',
1937+ 'trusty-juno': 'trusty-updates/juno',
1938+ 'trusty-juno/updates': 'trusty-updates/juno',
1939+ 'trusty-updates/juno': 'trusty-updates/juno',
1940+ 'juno/proposed': 'trusty-proposed/juno',
1941+ 'trusty-juno/proposed': 'trusty-proposed/juno',
1942+ 'trusty-proposed/juno': 'trusty-proposed/juno',
1943+ # Kilo
1944+ 'kilo': 'trusty-updates/kilo',
1945+ 'trusty-kilo': 'trusty-updates/kilo',
1946+ 'trusty-kilo/updates': 'trusty-updates/kilo',
1947+ 'trusty-updates/kilo': 'trusty-updates/kilo',
1948+ 'kilo/proposed': 'trusty-proposed/kilo',
1949+ 'trusty-kilo/proposed': 'trusty-proposed/kilo',
1950+ 'trusty-proposed/kilo': 'trusty-proposed/kilo',
1951+ # Liberty
1952+ 'liberty': 'trusty-updates/liberty',
1953+ 'trusty-liberty': 'trusty-updates/liberty',
1954+ 'trusty-liberty/updates': 'trusty-updates/liberty',
1955+ 'trusty-updates/liberty': 'trusty-updates/liberty',
1956+ 'liberty/proposed': 'trusty-proposed/liberty',
1957+ 'trusty-liberty/proposed': 'trusty-proposed/liberty',
1958+ 'trusty-proposed/liberty': 'trusty-proposed/liberty',
1959+ # Mitaka
1960+ 'mitaka': 'trusty-updates/mitaka',
1961+ 'trusty-mitaka': 'trusty-updates/mitaka',
1962+ 'trusty-mitaka/updates': 'trusty-updates/mitaka',
1963+ 'trusty-updates/mitaka': 'trusty-updates/mitaka',
1964+ 'mitaka/proposed': 'trusty-proposed/mitaka',
1965+ 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
1966+ 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
1967+ # Newton
1968+ 'newton': 'xenial-updates/newton',
1969+ 'xenial-newton': 'xenial-updates/newton',
1970+ 'xenial-newton/updates': 'xenial-updates/newton',
1971+ 'xenial-updates/newton': 'xenial-updates/newton',
1972+ 'newton/proposed': 'xenial-proposed/newton',
1973+ 'xenial-newton/proposed': 'xenial-proposed/newton',
1974+ 'xenial-proposed/newton': 'xenial-proposed/newton',
1975+}
1976+
1977+APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
1978+APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
1979+APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
1980+
1981+
1982+def filter_installed_packages(packages):
1983+ """Return a list of packages that require installation."""
1984+ cache = apt_cache()
1985+ _pkgs = []
1986+ for package in packages:
1987+ try:
1988+ p = cache[package]
1989+ p.current_ver or _pkgs.append(package)
1990+ except KeyError:
1991+ log('Package {} has no installation candidate.'.format(package),
1992+ level='WARNING')
1993+ _pkgs.append(package)
1994+ return _pkgs
1995+
1996+
1997+def apt_cache(in_memory=True, progress=None):
1998+ """Build and return an apt cache."""
1999+ from apt import apt_pkg
2000+ apt_pkg.init()
2001+ if in_memory:
2002+ apt_pkg.config.set("Dir::Cache::pkgcache", "")
2003+ apt_pkg.config.set("Dir::Cache::srcpkgcache", "")
2004+ return apt_pkg.Cache(progress)
2005+
2006+
2007+def install(packages, options=None, fatal=False):
2008+ """Install one or more packages."""
2009+ if options is None:
2010+ options = ['--option=Dpkg::Options::=--force-confold']
2011+
2012+ cmd = ['apt-get', '--assume-yes']
2013+ cmd.extend(options)
2014+ cmd.append('install')
2015+ if isinstance(packages, six.string_types):
2016+ cmd.append(packages)
2017+ else:
2018+ cmd.extend(packages)
2019+ log("Installing {} with options: {}".format(packages,
2020+ options))
2021+ _run_apt_command(cmd, fatal)
2022+
2023+
2024+def upgrade(options=None, fatal=False, dist=False):
2025+ """Upgrade all packages."""
2026+ if options is None:
2027+ options = ['--option=Dpkg::Options::=--force-confold']
2028+
2029+ cmd = ['apt-get', '--assume-yes']
2030+ cmd.extend(options)
2031+ if dist:
2032+ cmd.append('dist-upgrade')
2033+ else:
2034+ cmd.append('upgrade')
2035+ log("Upgrading with options: {}".format(options))
2036+ _run_apt_command(cmd, fatal)
2037+
2038+
2039+def update(fatal=False):
2040+ """Update local apt cache."""
2041+ cmd = ['apt-get', 'update']
2042+ _run_apt_command(cmd, fatal)
2043+
2044+
2045+def purge(packages, fatal=False):
2046+ """Purge one or more packages."""
2047+ cmd = ['apt-get', '--assume-yes', 'purge']
2048+ if isinstance(packages, six.string_types):
2049+ cmd.append(packages)
2050+ else:
2051+ cmd.extend(packages)
2052+ log("Purging {}".format(packages))
2053+ _run_apt_command(cmd, fatal)
2054+
2055+
2056+def apt_mark(packages, mark, fatal=False):
2057+ """Flag one or more packages using apt-mark."""
2058+ log("Marking {} as {}".format(packages, mark))
2059+ cmd = ['apt-mark', mark]
2060+ if isinstance(packages, six.string_types):
2061+ cmd.append(packages)
2062+ else:
2063+ cmd.extend(packages)
2064+
2065+ if fatal:
2066+ subprocess.check_call(cmd, universal_newlines=True)
2067+ else:
2068+ subprocess.call(cmd, universal_newlines=True)
2069+
2070+
2071+def apt_hold(packages, fatal=False):
2072+ return apt_mark(packages, 'hold', fatal=fatal)
2073+
2074+
2075+def apt_unhold(packages, fatal=False):
2076+ return apt_mark(packages, 'unhold', fatal=fatal)
2077+
2078+
2079+def add_source(source, key=None):
2080+ """Add a package source to this system.
2081+
2082+ @param source: a URL or sources.list entry, as supported by
2083+ add-apt-repository(1). Examples::
2084+
2085+ ppa:charmers/example
2086+ deb https://stub:key@private.example.com/ubuntu trusty main
2087+
2088+ In addition:
2089+ 'proposed:' may be used to enable the standard 'proposed'
2090+ pocket for the release.
2091+ 'cloud:' may be used to activate official cloud archive pockets,
2092+ such as 'cloud:icehouse'
2093+ 'distro' may be used as a noop
2094+
2095+ @param key: A key to be added to the system's APT keyring and used
2096+ to verify the signatures on packages. Ideally, this should be an
2097+ ASCII format GPG public key including the block headers. A GPG key
2098+ id may also be used, but be aware that only insecure protocols are
2099+ available to retrieve the actual public key from a public keyserver
2100+ placing your Juju environment at risk. ppa and cloud archive keys
2101+ are securely added automtically, so sould not be provided.
2102+ """
2103+ if source is None:
2104+ log('Source is not present. Skipping')
2105+ return
2106+
2107+ if (source.startswith('ppa:') or
2108+ source.startswith('http') or
2109+ source.startswith('deb ') or
2110+ source.startswith('cloud-archive:')):
2111+ subprocess.check_call(['add-apt-repository', '--yes', source])
2112+ elif source.startswith('cloud:'):
2113+ install(filter_installed_packages(['ubuntu-cloud-keyring']),
2114+ fatal=True)
2115+ pocket = source.split(':')[-1]
2116+ if pocket not in CLOUD_ARCHIVE_POCKETS:
2117+ raise SourceConfigError(
2118+ 'Unsupported cloud: source option %s' %
2119+ pocket)
2120+ actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
2121+ with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
2122+ apt.write(CLOUD_ARCHIVE.format(actual_pocket))
2123+ elif source == 'proposed':
2124+ release = lsb_release()['DISTRIB_CODENAME']
2125+ with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
2126+ apt.write(PROPOSED_POCKET.format(release))
2127+ elif source == 'distro':
2128+ pass
2129+ else:
2130+ log("Unknown source: {!r}".format(source))
2131+
2132+ if key:
2133+ if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
2134+ with NamedTemporaryFile('w+') as key_file:
2135+ key_file.write(key)
2136+ key_file.flush()
2137+ key_file.seek(0)
2138+ subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
2139+ else:
2140+ # Note that hkp: is in no way a secure protocol. Using a
2141+ # GPG key id is pointless from a security POV unless you
2142+ # absolutely trust your network and DNS.
2143+ subprocess.check_call(['apt-key', 'adv', '--keyserver',
2144+ 'hkp://keyserver.ubuntu.com:80', '--recv',
2145+ key])
2146+
2147+
2148+def _run_apt_command(cmd, fatal=False):
2149+ """Run an APT command.
2150+
2151+ Checks the output and retries if the fatal flag is set
2152+ to True.
2153+
2154+ :param: cmd: str: The apt command to run.
2155+ :param: fatal: bool: Whether the command's output should be checked and
2156+ retried.
2157+ """
2158+ env = os.environ.copy()
2159+
2160+ if 'DEBIAN_FRONTEND' not in env:
2161+ env['DEBIAN_FRONTEND'] = 'noninteractive'
2162+
2163+ if fatal:
2164+ retry_count = 0
2165+ result = None
2166+
2167+ # If the command is considered "fatal", we need to retry if the apt
2168+ # lock was not acquired.
2169+
2170+ while result is None or result == APT_NO_LOCK:
2171+ try:
2172+ result = subprocess.check_call(cmd, env=env)
2173+ except subprocess.CalledProcessError as e:
2174+ retry_count = retry_count + 1
2175+ if retry_count > APT_NO_LOCK_RETRY_COUNT:
2176+ raise
2177+ result = e.returncode
2178+ log("Couldn't acquire DPKG lock. Will retry in {} seconds."
2179+ "".format(APT_NO_LOCK_RETRY_DELAY))
2180+ time.sleep(APT_NO_LOCK_RETRY_DELAY)
2181+
2182+ else:
2183+ subprocess.call(cmd, env=env)
2184+
2185+
2186+def get_upstream_version(package):
2187+ """Determine upstream version based on installed package
2188+
2189+ @returns None (if not installed) or the upstream version
2190+ """
2191+ import apt_pkg
2192+ cache = apt_cache()
2193+ try:
2194+ pkg = cache[package]
2195+ except:
2196+ # the package is unknown to the current apt cache.
2197+ return None
2198+
2199+ if not pkg.current_ver:
2200+ # package is known, but no version is currently installed.
2201+ return None
2202+
2203+ return apt_pkg.upstream_version(pkg.current_ver.ver_str)
2204diff --git a/hooks/charmhelpers/osplatform.py b/hooks/charmhelpers/osplatform.py
2205new file mode 100644
2206index 0000000..ea490bb
2207--- /dev/null
2208+++ b/hooks/charmhelpers/osplatform.py
2209@@ -0,0 +1,19 @@
2210+import platform
2211+
2212+
2213+def get_platform():
2214+ """Return the current OS platform.
2215+
2216+ For example: if current os platform is Ubuntu then a string "ubuntu"
2217+ will be returned (which is the name of the module).
2218+ This string is used to decide which platform module should be imported.
2219+ """
2220+ tuple_platform = platform.linux_distribution()
2221+ current_platform = tuple_platform[0]
2222+ if "Ubuntu" in current_platform:
2223+ return "ubuntu"
2224+ elif "CentOS" in current_platform:
2225+ return "centos"
2226+ else:
2227+ raise RuntimeError("This module is not supported on {}."
2228+ .format(current_platform))
2229diff --git a/hooks/ntpmaster_hooks.py b/hooks/ntpmaster_hooks.py
2230index 979fbf0..1bbe8ce 100755
2231--- a/hooks/ntpmaster_hooks.py
2232+++ b/hooks/ntpmaster_hooks.py
2233@@ -65,8 +65,19 @@ def write_config():
2234 shutil.copy(NTP_CONF_ORIG, NTP_CONF)
2235
2236
2237+def assess_status():
2238+ hookenv.application_version_set(
2239+ fetch.get_upstream_version('ntp')
2240+ )
2241+ if host.service_running('ntp'):
2242+ hookenv.status_set('active', 'Unit is ready')
2243+ else:
2244+ hookenv.status_set('blocked', 'ntp not running')
2245+
2246+
2247 if __name__ == '__main__':
2248 try:
2249 hooks.execute(sys.argv)
2250 except UnregisteredHookError as e:
2251 hookenv.log('Unknown hook {} - skipping.'.format(e))
2252+ assess_status()
2253diff --git a/hooks/update-status b/hooks/update-status
2254new file mode 120000
2255index 0000000..ce3b60a
2256--- /dev/null
2257+++ b/hooks/update-status
2258@@ -0,0 +1 @@
2259+ntpmaster_hooks.py
2260\ No newline at end of file

Subscribers

People subscribed via source and target branches