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
diff --git a/.gitignore b/.gitignore
0new file mode 1006440new file mode 100644
index 0000000..ba077a4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
1bin
diff --git a/charm-helpers-sync.yaml b/charm-helpers-sync.yaml
index 7fd4ca6..c6dcfa3 100644
--- a/charm-helpers-sync.yaml
+++ b/charm-helpers-sync.yaml
@@ -1,5 +1,6 @@
1branch: lp:charm-helpers1branch: lp:charm-helpers
2destination: hooks/charmhelpers2destination: hooks/charmhelpers
3include:3include:
4 - osplatform
4 - core5 - core
5 - fetch6 - fetch
diff --git a/hooks/charmhelpers/__init__.py b/hooks/charmhelpers/__init__.py
index f72e7f8..4886788 100644
--- a/hooks/charmhelpers/__init__.py
+++ b/hooks/charmhelpers/__init__.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17# Bootstrap charm-helpers, installing its dependencies if necessary using15# Bootstrap charm-helpers, installing its dependencies if necessary using
18# only standard libraries.16# only standard libraries.
diff --git a/hooks/charmhelpers/core/__init__.py b/hooks/charmhelpers/core/__init__.py
index d1400a0..d7567b8 100644
--- a/hooks/charmhelpers/core/__init__.py
+++ b/hooks/charmhelpers/core/__init__.py
@@ -1,15 +1,13 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
diff --git a/hooks/charmhelpers/core/decorators.py b/hooks/charmhelpers/core/decorators.py
index bb05620..6ad41ee 100644
--- a/hooks/charmhelpers/core/decorators.py
+++ b/hooks/charmhelpers/core/decorators.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17#15#
18# Copyright 2014 Canonical Ltd.16# Copyright 2014 Canonical Ltd.
diff --git a/hooks/charmhelpers/core/files.py b/hooks/charmhelpers/core/files.py
index 0f12d32..fdd82b7 100644
--- a/hooks/charmhelpers/core/files.py
+++ b/hooks/charmhelpers/core/files.py
@@ -3,19 +3,17 @@
33
4# Copyright 2014-2015 Canonical Limited.4# Copyright 2014-2015 Canonical Limited.
5#5#
6# This file is part of charm-helpers.6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
7#9#
8# charm-helpers is free software: you can redistribute it and/or modify10# http://www.apache.org/licenses/LICENSE-2.0
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#11#
12# charm-helpers is distributed in the hope that it will be useful,12# Unless required by applicable law or agreed to in writing, software
13# but WITHOUT ANY WARRANTY; without even the implied warranty of13# distributed under the License is distributed on an "AS IS" BASIS,
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# GNU Lesser General Public License for more details.15# See the License for the specific language governing permissions and
16#16# limitations under the License.
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1917
20__author__ = 'Jorge Niedbalski <niedbalski@ubuntu.com>'18__author__ = 'Jorge Niedbalski <niedbalski@ubuntu.com>'
2119
diff --git a/hooks/charmhelpers/core/fstab.py b/hooks/charmhelpers/core/fstab.py
index 3056fba..d9fa915 100644
--- a/hooks/charmhelpers/core/fstab.py
+++ b/hooks/charmhelpers/core/fstab.py
@@ -3,19 +3,17 @@
33
4# Copyright 2014-2015 Canonical Limited.4# Copyright 2014-2015 Canonical Limited.
5#5#
6# This file is part of charm-helpers.6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
7#9#
8# charm-helpers is free software: you can redistribute it and/or modify10# http://www.apache.org/licenses/LICENSE-2.0
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#11#
12# charm-helpers is distributed in the hope that it will be useful,12# Unless required by applicable law or agreed to in writing, software
13# but WITHOUT ANY WARRANTY; without even the implied warranty of13# distributed under the License is distributed on an "AS IS" BASIS,
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# GNU Lesser General Public License for more details.15# See the License for the specific language governing permissions and
16#16# limitations under the License.
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1917
20import io18import io
21import os19import os
diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py
index 0132129..996e81c 100644
--- a/hooks/charmhelpers/core/hookenv.py
+++ b/hooks/charmhelpers/core/hookenv.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17"Interactions with the Juju environment"15"Interactions with the Juju environment"
18# Copyright 2013 Canonical Ltd.16# Copyright 2013 Canonical Ltd.
@@ -845,6 +843,20 @@ def translate_exc(from_exc, to_exc):
845 return inner_translate_exc1843 return inner_translate_exc1
846844
847845
846def application_version_set(version):
847 """Charm authors may trigger this command from any hook to output what
848 version of the application is running. This could be a package version,
849 for instance postgres version 9.5. It could also be a build number or
850 version control revision identifier, for instance git sha 6fb7ba68. """
851
852 cmd = ['application-version-set']
853 cmd.append(version)
854 try:
855 subprocess.check_call(cmd)
856 except OSError:
857 log("Application Version: {}".format(version))
858
859
848@translate_exc(from_exc=OSError, to_exc=NotImplementedError)860@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
849def is_leader():861def is_leader():
850 """Does the current unit hold the juju leadership862 """Does the current unit hold the juju leadership
@@ -1006,4 +1018,4 @@ def network_get_primary_address(binding):
1006 :raise: NotImplementedError if run on Juju < 2.01018 :raise: NotImplementedError if run on Juju < 2.0
1007 '''1019 '''
1008 cmd = ['network-get', '--primary-address', binding]1020 cmd = ['network-get', '--primary-address', binding]
1009 return subprocess.check_output(cmd).strip()1021 return subprocess.check_output(cmd).decode('UTF-8').strip()
diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py
index bfea6a1..0f1b2f3 100644
--- a/hooks/charmhelpers/core/host.py
+++ b/hooks/charmhelpers/core/host.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17"""Tools for working with the host system"""15"""Tools for working with the host system"""
18# Copyright 2012 Canonical Ltd.16# Copyright 2012 Canonical Ltd.
@@ -32,13 +30,29 @@ import subprocess
32import hashlib30import hashlib
33import functools31import functools
34import itertools32import itertools
35from contextlib import contextmanager
36from collections import OrderedDict
37
38import six33import six
3934
35from contextlib import contextmanager
36from collections import OrderedDict
40from .hookenv import log37from .hookenv import log
41from .fstab import Fstab38from .fstab import Fstab
39from charmhelpers.osplatform import get_platform
40
41__platform__ = get_platform()
42if __platform__ == "ubuntu":
43 from charmhelpers.core.host_factory.ubuntu import (
44 service_available,
45 add_new_group,
46 lsb_release,
47 cmp_pkgrevno,
48 ) # flake8: noqa -- ignore F401 for this import
49elif __platform__ == "centos":
50 from charmhelpers.core.host_factory.centos import (
51 service_available,
52 add_new_group,
53 lsb_release,
54 cmp_pkgrevno,
55 ) # flake8: noqa -- ignore F401 for this import
4256
4357
44def service_start(service_name):58def service_start(service_name):
@@ -128,11 +142,8 @@ def service(action, service_name):
128 return subprocess.call(cmd) == 0142 return subprocess.call(cmd) == 0
129143
130144
131def systemv_services_running():145_UPSTART_CONF = "/etc/init/{}.conf"
132 output = subprocess.check_output(146_INIT_D_CONF = "/etc/init.d/{}"
133 ['service', '--status-all'],
134 stderr=subprocess.STDOUT).decode('UTF-8')
135 return [row.split()[-1] for row in output.split('\n') if '[ + ]' in row]
136147
137148
138def service_running(service_name):149def service_running(service_name):
@@ -140,34 +151,25 @@ def service_running(service_name):
140 if init_is_systemd():151 if init_is_systemd():
141 return service('is-active', service_name)152 return service('is-active', service_name)
142 else:153 else:
143 try:154 if os.path.exists(_UPSTART_CONF.format(service_name)):
144 output = subprocess.check_output(155 try:
145 ['service', service_name, 'status'],156 output = subprocess.check_output(
146 stderr=subprocess.STDOUT).decode('UTF-8')157 ['status', service_name],
147 except subprocess.CalledProcessError:158 stderr=subprocess.STDOUT).decode('UTF-8')
148 return False159 except subprocess.CalledProcessError:
149 else:160 return False
150 # This works for upstart scripts where the 'service' command161 else:
151 # returns a consistent string to represent running 'start/running'162 # This works for upstart scripts where the 'service' command
152 if ("start/running" in output or "is running" in output or163 # returns a consistent string to represent running
153 "up and running" in output):164 # 'start/running'
154 return True165 if ("start/running" in output or
166 "is running" in output or
167 "up and running" in output):
168 return True
169 elif os.path.exists(_INIT_D_CONF.format(service_name)):
155 # Check System V scripts init script return codes170 # Check System V scripts init script return codes
156 if service_name in systemv_services_running():171 return service('status', service_name)
157 return True172 return False
158 return False
159
160
161def service_available(service_name):
162 """Determine whether a system service is available"""
163 try:
164 subprocess.check_output(
165 ['service', service_name, 'status'],
166 stderr=subprocess.STDOUT).decode('UTF-8')
167 except subprocess.CalledProcessError as e:
168 return b'unrecognized service' not in e.output
169 else:
170 return True
171173
172174
173SYSTEMD_SYSTEM = '/run/systemd/system'175SYSTEMD_SYSTEM = '/run/systemd/system'
@@ -178,8 +180,9 @@ def init_is_systemd():
178 return os.path.isdir(SYSTEMD_SYSTEM)180 return os.path.isdir(SYSTEMD_SYSTEM)
179181
180182
181def adduser(username, password=None, shell='/bin/bash', system_user=False,183def adduser(username, password=None, shell='/bin/bash',
182 primary_group=None, secondary_groups=None):184 system_user=False, primary_group=None,
185 secondary_groups=None, uid=None, home_dir=None):
183 """Add a user to the system.186 """Add a user to the system.
184187
185 Will log but otherwise succeed if the user already exists.188 Will log but otherwise succeed if the user already exists.
@@ -190,15 +193,24 @@ def adduser(username, password=None, shell='/bin/bash', system_user=False,
190 :param bool system_user: Whether to create a login or system user193 :param bool system_user: Whether to create a login or system user
191 :param str primary_group: Primary group for user; defaults to username194 :param str primary_group: Primary group for user; defaults to username
192 :param list secondary_groups: Optional list of additional groups195 :param list secondary_groups: Optional list of additional groups
196 :param int uid: UID for user being created
197 :param str home_dir: Home directory for user
193198
194 :returns: The password database entry struct, as returned by `pwd.getpwnam`199 :returns: The password database entry struct, as returned by `pwd.getpwnam`
195 """200 """
196 try:201 try:
197 user_info = pwd.getpwnam(username)202 user_info = pwd.getpwnam(username)
198 log('user {0} already exists!'.format(username))203 log('user {0} already exists!'.format(username))
204 if uid:
205 user_info = pwd.getpwuid(int(uid))
206 log('user with uid {0} already exists!'.format(uid))
199 except KeyError:207 except KeyError:
200 log('creating user {0}'.format(username))208 log('creating user {0}'.format(username))
201 cmd = ['useradd']209 cmd = ['useradd']
210 if uid:
211 cmd.extend(['--uid', str(uid)])
212 if home_dir:
213 cmd.extend(['--home', str(home_dir)])
202 if system_user or password is None:214 if system_user or password is None:
203 cmd.append('--system')215 cmd.append('--system')
204 else:216 else:
@@ -233,22 +245,56 @@ def user_exists(username):
233 return user_exists245 return user_exists
234246
235247
236def add_group(group_name, system_group=False):248def uid_exists(uid):
237 """Add a group to the system"""249 """Check if a uid exists"""
250 try:
251 pwd.getpwuid(uid)
252 uid_exists = True
253 except KeyError:
254 uid_exists = False
255 return uid_exists
256
257
258def group_exists(groupname):
259 """Check if a group exists"""
260 try:
261 grp.getgrnam(groupname)
262 group_exists = True
263 except KeyError:
264 group_exists = False
265 return group_exists
266
267
268def gid_exists(gid):
269 """Check if a gid exists"""
270 try:
271 grp.getgrgid(gid)
272 gid_exists = True
273 except KeyError:
274 gid_exists = False
275 return gid_exists
276
277
278def add_group(group_name, system_group=False, gid=None):
279 """Add a group to the system
280
281 Will log but otherwise succeed if the group already exists.
282
283 :param str group_name: group to create
284 :param bool system_group: Create system group
285 :param int gid: GID for user being created
286
287 :returns: The password database entry struct, as returned by `grp.getgrnam`
288 """
238 try:289 try:
239 group_info = grp.getgrnam(group_name)290 group_info = grp.getgrnam(group_name)
240 log('group {0} already exists!'.format(group_name))291 log('group {0} already exists!'.format(group_name))
292 if gid:
293 group_info = grp.getgrgid(gid)
294 log('group with gid {0} already exists!'.format(gid))
241 except KeyError:295 except KeyError:
242 log('creating group {0}'.format(group_name))296 log('creating group {0}'.format(group_name))
243 cmd = ['addgroup']297 add_new_group(group_name, system_group, gid)
244 if system_group:
245 cmd.append('--system')
246 else:
247 cmd.extend([
248 '--group',
249 ])
250 cmd.append(group_name)
251 subprocess.check_call(cmd)
252 group_info = grp.getgrnam(group_name)298 group_info = grp.getgrnam(group_name)
253 return group_info299 return group_info
254300
@@ -493,16 +539,6 @@ def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
493 return r539 return r
494540
495541
496def lsb_release():
497 """Return /etc/lsb-release in a dict"""
498 d = {}
499 with open('/etc/lsb-release', 'r') as lsb:
500 for l in lsb:
501 k, v = l.split('=')
502 d[k.strip()] = v.strip()
503 return d
504
505
506def pwgen(length=None):542def pwgen(length=None):
507 """Generate a random pasword."""543 """Generate a random pasword."""
508 if length is None:544 if length is None:
@@ -626,25 +662,6 @@ def get_nic_hwaddr(nic):
626 return hwaddr662 return hwaddr
627663
628664
629def 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)
646
647
648@contextmanager665@contextmanager
649def chdir(directory):666def chdir(directory):
650 """Change the current working directory to a different directory for a code667 """Change the current working directory to a different directory for a code
diff --git a/hooks/charmhelpers/core/host_factory/__init__.py b/hooks/charmhelpers/core/host_factory/__init__.py
651new file mode 100644668new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/hooks/charmhelpers/core/host_factory/__init__.py
diff --git a/hooks/charmhelpers/core/host_factory/centos.py b/hooks/charmhelpers/core/host_factory/centos.py
652new file mode 100644669new file mode 100644
index 0000000..902d469
--- /dev/null
+++ b/hooks/charmhelpers/core/host_factory/centos.py
@@ -0,0 +1,56 @@
1import subprocess
2import yum
3import os
4
5
6def service_available(service_name):
7 # """Determine whether a system service is available."""
8 if os.path.isdir('/run/systemd/system'):
9 cmd = ['systemctl', 'is-enabled', service_name]
10 else:
11 cmd = ['service', service_name, 'is-enabled']
12 return subprocess.call(cmd) == 0
13
14
15def add_new_group(group_name, system_group=False, gid=None):
16 cmd = ['groupadd']
17 if gid:
18 cmd.extend(['--gid', str(gid)])
19 if system_group:
20 cmd.append('-r')
21 cmd.append(group_name)
22 subprocess.check_call(cmd)
23
24
25def lsb_release():
26 """Return /etc/os-release in a dict."""
27 d = {}
28 with open('/etc/os-release', 'r') as lsb:
29 for l in lsb:
30 s = l.split('=')
31 if len(s) != 2:
32 continue
33 d[s[0].strip()] = s[1].strip()
34 return d
35
36
37def cmp_pkgrevno(package, revno, pkgcache=None):
38 """Compare supplied revno with the revno of the installed package.
39
40 * 1 => Installed revno is greater than supplied arg
41 * 0 => Installed revno is the same as supplied arg
42 * -1 => Installed revno is less than supplied arg
43
44 This function imports YumBase function if the pkgcache argument
45 is None.
46 """
47 if not pkgcache:
48 y = yum.YumBase()
49 packages = y.doPackageLists()
50 pkgcache = {i.Name: i.version for i in packages['installed']}
51 pkg = pkgcache[package]
52 if pkg > revno:
53 return 1
54 if pkg < revno:
55 return -1
56 return 0
diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/hooks/charmhelpers/core/host_factory/ubuntu.py
0new file mode 10064457new file mode 100644
index 0000000..8c66af5
--- /dev/null
+++ b/hooks/charmhelpers/core/host_factory/ubuntu.py
@@ -0,0 +1,56 @@
1import subprocess
2
3
4def service_available(service_name):
5 """Determine whether a system service is available"""
6 try:
7 subprocess.check_output(
8 ['service', service_name, 'status'],
9 stderr=subprocess.STDOUT).decode('UTF-8')
10 except subprocess.CalledProcessError as e:
11 return b'unrecognized service' not in e.output
12 else:
13 return True
14
15
16def add_new_group(group_name, system_group=False, gid=None):
17 cmd = ['addgroup']
18 if gid:
19 cmd.extend(['--gid', str(gid)])
20 if system_group:
21 cmd.append('--system')
22 else:
23 cmd.extend([
24 '--group',
25 ])
26 cmd.append(group_name)
27 subprocess.check_call(cmd)
28
29
30def lsb_release():
31 """Return /etc/lsb-release in a dict"""
32 d = {}
33 with open('/etc/lsb-release', 'r') as lsb:
34 for l in lsb:
35 k, v = l.split('=')
36 d[k.strip()] = v.strip()
37 return d
38
39
40def cmp_pkgrevno(package, revno, pkgcache=None):
41 """Compare supplied revno with the revno of the installed package.
42
43 * 1 => Installed revno is greater than supplied arg
44 * 0 => Installed revno is the same as supplied arg
45 * -1 => Installed revno is less than supplied arg
46
47 This function imports apt_cache function from charmhelpers.fetch if
48 the pkgcache argument is None. Be sure to add charmhelpers.fetch if
49 you call this function, or pass an apt_pkg.Cache() instance.
50 """
51 import apt_pkg
52 if not pkgcache:
53 from charmhelpers.fetch import apt_cache
54 pkgcache = apt_cache()
55 pkg = pkgcache[package]
56 return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
diff --git a/hooks/charmhelpers/core/hugepage.py b/hooks/charmhelpers/core/hugepage.py
index a783ad9..54b5b5e 100644
--- a/hooks/charmhelpers/core/hugepage.py
+++ b/hooks/charmhelpers/core/hugepage.py
@@ -2,19 +2,17 @@
22
3# Copyright 2014-2015 Canonical Limited.3# Copyright 2014-2015 Canonical Limited.
4#4#
5# This file is part of charm-helpers.5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
6#8#
7# charm-helpers is free software: you can redistribute it and/or modify9# http://www.apache.org/licenses/LICENSE-2.0
8# it under the terms of the GNU Lesser General Public License version 3 as
9# published by the Free Software Foundation.
10#10#
11# charm-helpers is distributed in the hope that it will be useful,11# Unless required by applicable law or agreed to in writing, software
12# but WITHOUT ANY WARRANTY; without even the implied warranty of12# distributed under the License is distributed on an "AS IS" BASIS,
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# GNU Lesser General Public License for more details.14# See the License for the specific language governing permissions and
15#15# limitations under the License.
16# You should have received a copy of the GNU Lesser General Public License
17# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1816
19import yaml17import yaml
20from charmhelpers.core import fstab18from charmhelpers.core import fstab
diff --git a/hooks/charmhelpers/core/kernel.py b/hooks/charmhelpers/core/kernel.py
index 5dc6495..2d40452 100644
--- a/hooks/charmhelpers/core/kernel.py
+++ b/hooks/charmhelpers/core/kernel.py
@@ -3,29 +3,40 @@
33
4# Copyright 2014-2015 Canonical Limited.4# Copyright 2014-2015 Canonical Limited.
5#5#
6# This file is part of charm-helpers.6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
7#9#
8# charm-helpers is free software: you can redistribute it and/or modify10# http://www.apache.org/licenses/LICENSE-2.0
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#11#
12# charm-helpers is distributed in the hope that it will be useful,12# Unless required by applicable law or agreed to in writing, software
13# but WITHOUT ANY WARRANTY; without even the implied warranty of13# distributed under the License is distributed on an "AS IS" BASIS,
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# GNU Lesser General Public License for more details.15# See the License for the specific language governing permissions and
16#16# limitations under the License.
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1917
20__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"18import re
19import subprocess
2120
21from charmhelpers.osplatform import get_platform
22from charmhelpers.core.hookenv import (22from charmhelpers.core.hookenv import (
23 log,23 log,
24 INFO24 INFO
25)25)
2626
27from subprocess import check_call, check_output27__platform__ = get_platform()
28import re28if __platform__ == "ubuntu":
29 from charmhelpers.core.kernel_factory.ubuntu import (
30 persistent_modprobe,
31 update_initramfs,
32 ) # flake8: noqa -- ignore F401 for this import
33elif __platform__ == "centos":
34 from charmhelpers.core.kernel_factory.centos import (
35 persistent_modprobe,
36 update_initramfs,
37 ) # flake8: noqa -- ignore F401 for this import
38
39__author__ = "Jorge Niedbalski <jorge.niedbalski@canonical.com>"
2940
3041
31def modprobe(module, persist=True):42def modprobe(module, persist=True):
@@ -34,11 +45,9 @@ def modprobe(module, persist=True):
3445
35 log('Loading kernel module %s' % module, level=INFO)46 log('Loading kernel module %s' % module, level=INFO)
3647
37 check_call(cmd)48 subprocess.check_call(cmd)
38 if persist:49 if persist:
39 with open('/etc/modules', 'r+') as modules:50 persistent_modprobe(module)
40 if module not in modules.read():
41 modules.write(module)
4251
4352
44def rmmod(module, force=False):53def rmmod(module, force=False):
@@ -48,21 +57,16 @@ def rmmod(module, force=False):
48 cmd.append('-f')57 cmd.append('-f')
49 cmd.append(module)58 cmd.append(module)
50 log('Removing kernel module %s' % module, level=INFO)59 log('Removing kernel module %s' % module, level=INFO)
51 return check_call(cmd)60 return subprocess.check_call(cmd)
5261
5362
54def lsmod():63def lsmod():
55 """Shows what kernel modules are currently loaded"""64 """Shows what kernel modules are currently loaded"""
56 return check_output(['lsmod'],65 return subprocess.check_output(['lsmod'],
57 universal_newlines=True)66 universal_newlines=True)
5867
5968
60def is_module_loaded(module):69def is_module_loaded(module):
61 """Checks if a kernel module is already loaded"""70 """Checks if a kernel module is already loaded"""
62 matches = re.findall('^%s[ ]+' % module, lsmod(), re.M)71 matches = re.findall('^%s[ ]+' % module, lsmod(), re.M)
63 return len(matches) > 072 return len(matches) > 0
64
65
66def update_initramfs(version='all'):
67 """Updates an initramfs image"""
68 return check_call(["update-initramfs", "-k", version, "-u"])
diff --git a/hooks/charmhelpers/core/kernel_factory/__init__.py b/hooks/charmhelpers/core/kernel_factory/__init__.py
69new file mode 10064473new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/hooks/charmhelpers/core/kernel_factory/__init__.py
diff --git a/hooks/charmhelpers/core/kernel_factory/centos.py b/hooks/charmhelpers/core/kernel_factory/centos.py
70new file mode 10064474new file mode 100644
index 0000000..1c402c1
--- /dev/null
+++ b/hooks/charmhelpers/core/kernel_factory/centos.py
@@ -0,0 +1,17 @@
1import subprocess
2import os
3
4
5def persistent_modprobe(module):
6 """Load a kernel module and configure for auto-load on reboot."""
7 if not os.path.exists('/etc/rc.modules'):
8 open('/etc/rc.modules', 'a')
9 os.chmod('/etc/rc.modules', 111)
10 with open('/etc/rc.modules', 'r+') as modules:
11 if module not in modules.read():
12 modules.write('modprobe %s\n' % module)
13
14
15def update_initramfs(version='all'):
16 """Updates an initramfs image."""
17 return subprocess.check_call(["dracut", "-f", version])
diff --git a/hooks/charmhelpers/core/kernel_factory/ubuntu.py b/hooks/charmhelpers/core/kernel_factory/ubuntu.py
0new file mode 10064418new file mode 100644
index 0000000..2155964
--- /dev/null
+++ b/hooks/charmhelpers/core/kernel_factory/ubuntu.py
@@ -0,0 +1,13 @@
1import subprocess
2
3
4def persistent_modprobe(module):
5 """Load a kernel module and configure for auto-load on reboot."""
6 with open('/etc/modules', 'r+') as modules:
7 if module not in modules.read():
8 modules.write(module)
9
10
11def update_initramfs(version='all'):
12 """Updates an initramfs image."""
13 return subprocess.check_call(["update-initramfs", "-k", version, "-u"])
diff --git a/hooks/charmhelpers/core/services/__init__.py b/hooks/charmhelpers/core/services/__init__.py
index 0928158..61fd074 100644
--- a/hooks/charmhelpers/core/services/__init__.py
+++ b/hooks/charmhelpers/core/services/__init__.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17from .base import * # NOQA15from .base import * # NOQA
18from .helpers import * # NOQA16from .helpers import * # NOQA
diff --git a/hooks/charmhelpers/core/services/base.py b/hooks/charmhelpers/core/services/base.py
index a42660c..ca9dc99 100644
--- a/hooks/charmhelpers/core/services/base.py
+++ b/hooks/charmhelpers/core/services/base.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import os15import os
18import json16import json
diff --git a/hooks/charmhelpers/core/services/helpers.py b/hooks/charmhelpers/core/services/helpers.py
index 2423704..3e6e30d 100644
--- a/hooks/charmhelpers/core/services/helpers.py
+++ b/hooks/charmhelpers/core/services/helpers.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import os15import os
18import yaml16import yaml
diff --git a/hooks/charmhelpers/core/strutils.py b/hooks/charmhelpers/core/strutils.py
index 7e3f969..dd9b971 100644
--- a/hooks/charmhelpers/core/strutils.py
+++ b/hooks/charmhelpers/core/strutils.py
@@ -3,19 +3,17 @@
33
4# Copyright 2014-2015 Canonical Limited.4# Copyright 2014-2015 Canonical Limited.
5#5#
6# This file is part of charm-helpers.6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
7#9#
8# charm-helpers is free software: you can redistribute it and/or modify10# http://www.apache.org/licenses/LICENSE-2.0
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#11#
12# charm-helpers is distributed in the hope that it will be useful,12# Unless required by applicable law or agreed to in writing, software
13# but WITHOUT ANY WARRANTY; without even the implied warranty of13# distributed under the License is distributed on an "AS IS" BASIS,
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# GNU Lesser General Public License for more details.15# See the License for the specific language governing permissions and
16#16# limitations under the License.
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1917
20import six18import six
21import re19import re
diff --git a/hooks/charmhelpers/core/sysctl.py b/hooks/charmhelpers/core/sysctl.py
index 21cc8ab..6e413e3 100644
--- a/hooks/charmhelpers/core/sysctl.py
+++ b/hooks/charmhelpers/core/sysctl.py
@@ -3,19 +3,17 @@
33
4# Copyright 2014-2015 Canonical Limited.4# Copyright 2014-2015 Canonical Limited.
5#5#
6# This file is part of charm-helpers.6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
7#9#
8# charm-helpers is free software: you can redistribute it and/or modify10# http://www.apache.org/licenses/LICENSE-2.0
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#11#
12# charm-helpers is distributed in the hope that it will be useful,12# Unless required by applicable law or agreed to in writing, software
13# but WITHOUT ANY WARRANTY; without even the implied warranty of13# distributed under the License is distributed on an "AS IS" BASIS,
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# GNU Lesser General Public License for more details.15# See the License for the specific language governing permissions and
16#16# limitations under the License.
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1917
20import yaml18import yaml
2119
diff --git a/hooks/charmhelpers/core/templating.py b/hooks/charmhelpers/core/templating.py
index d2d8eaf..7b801a3 100644
--- a/hooks/charmhelpers/core/templating.py
+++ b/hooks/charmhelpers/core/templating.py
@@ -1,20 +1,19 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import os15import os
16import sys
1817
19from charmhelpers.core import host18from charmhelpers.core import host
20from charmhelpers.core import hookenv19from charmhelpers.core import hookenv
@@ -40,8 +39,9 @@ def render(source, target, context, owner='root', group='root',
40 The rendered template will be written to the file as well as being returned39 The rendered template will be written to the file as well as being returned
41 as a string.40 as a string.
4241
43 Note: Using this requires python-jinja2; if it is not installed, calling42 Note: Using this requires python-jinja2 or python3-jinja2; if it is not
44 this will attempt to use charmhelpers.fetch.apt_install to install it.43 installed, calling this will attempt to use charmhelpers.fetch.apt_install
44 to install it.
45 """45 """
46 try:46 try:
47 from jinja2 import FileSystemLoader, Environment, exceptions47 from jinja2 import FileSystemLoader, Environment, exceptions
@@ -53,7 +53,10 @@ def render(source, target, context, owner='root', group='root',
53 'charmhelpers.fetch to install it',53 'charmhelpers.fetch to install it',
54 level=hookenv.ERROR)54 level=hookenv.ERROR)
55 raise55 raise
56 apt_install('python-jinja2', fatal=True)56 if sys.version_info.major == 2:
57 apt_install('python-jinja2', fatal=True)
58 else:
59 apt_install('python3-jinja2', fatal=True)
57 from jinja2 import FileSystemLoader, Environment, exceptions60 from jinja2 import FileSystemLoader, Environment, exceptions
5861
59 if template_loader:62 if template_loader:
diff --git a/hooks/charmhelpers/core/unitdata.py b/hooks/charmhelpers/core/unitdata.py
index 338104e..54ec969 100644
--- a/hooks/charmhelpers/core/unitdata.py
+++ b/hooks/charmhelpers/core/unitdata.py
@@ -3,20 +3,17 @@
3#3#
4# Copyright 2014-2015 Canonical Limited.4# Copyright 2014-2015 Canonical Limited.
5#5#
6# This file is part of charm-helpers.6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
7#9#
8# charm-helpers is free software: you can redistribute it and/or modify10# http://www.apache.org/licenses/LICENSE-2.0
9# it under the terms of the GNU Lesser General Public License version 3 as
10# published by the Free Software Foundation.
11#
12# charm-helpers is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public License
18# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
19#11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
20#17#
21# Authors:18# Authors:
22# Kapil Thangavelu <kapil.foss@gmail.com>19# Kapil Thangavelu <kapil.foss@gmail.com>
diff --git a/hooks/charmhelpers/fetch/__init__.py b/hooks/charmhelpers/fetch/__init__.py
index db0d86a..ec5e0fe 100644
--- a/hooks/charmhelpers/fetch/__init__.py
+++ b/hooks/charmhelpers/fetch/__init__.py
@@ -1,32 +1,24 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import importlib15import importlib
18from tempfile import NamedTemporaryFile16from charmhelpers.osplatform import get_platform
19import time
20from yaml import safe_load17from yaml import safe_load
21from charmhelpers.core.host import (
22 lsb_release
23)
24import subprocess
25from charmhelpers.core.hookenv import (18from charmhelpers.core.hookenv import (
26 config,19 config,
27 log,20 log,
28)21)
29import os
3022
31import six23import six
32if six.PY3:24if six.PY3:
@@ -35,79 +27,6 @@ else:
35 from urlparse import urlparse, urlunparse27 from urlparse import urlparse, urlunparse
3628
3729
38CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
39deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
40"""
41PROPOSED_POCKET = """# Proposed
42deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
43"""
44CLOUD_ARCHIVE_POCKETS = {
45 # Folsom
46 'folsom': 'precise-updates/folsom',
47 'precise-folsom': 'precise-updates/folsom',
48 'precise-folsom/updates': 'precise-updates/folsom',
49 'precise-updates/folsom': 'precise-updates/folsom',
50 'folsom/proposed': 'precise-proposed/folsom',
51 'precise-folsom/proposed': 'precise-proposed/folsom',
52 'precise-proposed/folsom': 'precise-proposed/folsom',
53 # Grizzly
54 'grizzly': 'precise-updates/grizzly',
55 'precise-grizzly': 'precise-updates/grizzly',
56 'precise-grizzly/updates': 'precise-updates/grizzly',
57 'precise-updates/grizzly': 'precise-updates/grizzly',
58 'grizzly/proposed': 'precise-proposed/grizzly',
59 'precise-grizzly/proposed': 'precise-proposed/grizzly',
60 'precise-proposed/grizzly': 'precise-proposed/grizzly',
61 # Havana
62 'havana': 'precise-updates/havana',
63 'precise-havana': 'precise-updates/havana',
64 'precise-havana/updates': 'precise-updates/havana',
65 'precise-updates/havana': 'precise-updates/havana',
66 'havana/proposed': 'precise-proposed/havana',
67 'precise-havana/proposed': 'precise-proposed/havana',
68 'precise-proposed/havana': 'precise-proposed/havana',
69 # Icehouse
70 'icehouse': 'precise-updates/icehouse',
71 'precise-icehouse': 'precise-updates/icehouse',
72 'precise-icehouse/updates': 'precise-updates/icehouse',
73 'precise-updates/icehouse': 'precise-updates/icehouse',
74 'icehouse/proposed': 'precise-proposed/icehouse',
75 'precise-icehouse/proposed': 'precise-proposed/icehouse',
76 'precise-proposed/icehouse': 'precise-proposed/icehouse',
77 # Juno
78 'juno': 'trusty-updates/juno',
79 'trusty-juno': 'trusty-updates/juno',
80 'trusty-juno/updates': 'trusty-updates/juno',
81 'trusty-updates/juno': 'trusty-updates/juno',
82 'juno/proposed': 'trusty-proposed/juno',
83 'trusty-juno/proposed': 'trusty-proposed/juno',
84 'trusty-proposed/juno': 'trusty-proposed/juno',
85 # Kilo
86 'kilo': 'trusty-updates/kilo',
87 'trusty-kilo': 'trusty-updates/kilo',
88 'trusty-kilo/updates': 'trusty-updates/kilo',
89 'trusty-updates/kilo': 'trusty-updates/kilo',
90 'kilo/proposed': 'trusty-proposed/kilo',
91 'trusty-kilo/proposed': 'trusty-proposed/kilo',
92 'trusty-proposed/kilo': 'trusty-proposed/kilo',
93 # Liberty
94 'liberty': 'trusty-updates/liberty',
95 'trusty-liberty': 'trusty-updates/liberty',
96 'trusty-liberty/updates': 'trusty-updates/liberty',
97 'trusty-updates/liberty': 'trusty-updates/liberty',
98 'liberty/proposed': 'trusty-proposed/liberty',
99 'trusty-liberty/proposed': 'trusty-proposed/liberty',
100 'trusty-proposed/liberty': 'trusty-proposed/liberty',
101 # Mitaka
102 'mitaka': 'trusty-updates/mitaka',
103 'trusty-mitaka': 'trusty-updates/mitaka',
104 'trusty-mitaka/updates': 'trusty-updates/mitaka',
105 'trusty-updates/mitaka': 'trusty-updates/mitaka',
106 'mitaka/proposed': 'trusty-proposed/mitaka',
107 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
108 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
109}
110
111# The order of this list is very important. Handlers should be listed in from30# The order of this list is very important. Handlers should be listed in from
112# least- to most-specific URL matching.31# least- to most-specific URL matching.
113FETCH_HANDLERS = (32FETCH_HANDLERS = (
@@ -116,10 +35,6 @@ FETCH_HANDLERS = (
116 'charmhelpers.fetch.giturl.GitUrlFetchHandler',35 'charmhelpers.fetch.giturl.GitUrlFetchHandler',
117)36)
11837
119APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
120APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
121APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
122
12338
124class SourceConfigError(Exception):39class SourceConfigError(Exception):
125 pass40 pass
@@ -157,180 +72,38 @@ class BaseFetchHandler(object):
157 return urlunparse(parts)72 return urlunparse(parts)
15873
15974
160def filter_installed_packages(packages):75__platform__ = get_platform()
161 """Returns a list of packages that require installation"""76module = "charmhelpers.fetch.%s" % __platform__
162 cache = apt_cache()77fetch = importlib.import_module(module)
163 _pkgs = []
164 for package in packages:
165 try:
166 p = cache[package]
167 p.current_ver or _pkgs.append(package)
168 except KeyError:
169 log('Package {} has no installation candidate.'.format(package),
170 level='WARNING')
171 _pkgs.append(package)
172 return _pkgs
173
174
175def apt_cache(in_memory=True):
176 """Build and return an apt cache"""
177 from apt import apt_pkg
178 apt_pkg.init()
179 if in_memory:
180 apt_pkg.config.set("Dir::Cache::pkgcache", "")
181 apt_pkg.config.set("Dir::Cache::srcpkgcache", "")
182 return apt_pkg.Cache()
183
184
185def apt_install(packages, options=None, fatal=False):
186 """Install one or more packages"""
187 if options is None:
188 options = ['--option=Dpkg::Options::=--force-confold']
189
190 cmd = ['apt-get', '--assume-yes']
191 cmd.extend(options)
192 cmd.append('install')
193 if isinstance(packages, six.string_types):
194 cmd.append(packages)
195 else:
196 cmd.extend(packages)
197 log("Installing {} with options: {}".format(packages,
198 options))
199 _run_apt_command(cmd, fatal)
200
201
202def apt_upgrade(options=None, fatal=False, dist=False):
203 """Upgrade all packages"""
204 if options is None:
205 options = ['--option=Dpkg::Options::=--force-confold']
206
207 cmd = ['apt-get', '--assume-yes']
208 cmd.extend(options)
209 if dist:
210 cmd.append('dist-upgrade')
211 else:
212 cmd.append('upgrade')
213 log("Upgrading with options: {}".format(options))
214 _run_apt_command(cmd, fatal)
215
216
217def apt_update(fatal=False):
218 """Update local apt cache"""
219 cmd = ['apt-get', 'update']
220 _run_apt_command(cmd, fatal)
221
222
223def apt_purge(packages, fatal=False):
224 """Purge one or more packages"""
225 cmd = ['apt-get', '--assume-yes', 'purge']
226 if isinstance(packages, six.string_types):
227 cmd.append(packages)
228 else:
229 cmd.extend(packages)
230 log("Purging {}".format(packages))
231 _run_apt_command(cmd, fatal)
232
233
234def apt_mark(packages, mark, fatal=False):
235 """Flag one or more packages using apt-mark"""
236 log("Marking {} as {}".format(packages, mark))
237 cmd = ['apt-mark', mark]
238 if isinstance(packages, six.string_types):
239 cmd.append(packages)
240 else:
241 cmd.extend(packages)
242
243 if fatal:
244 subprocess.check_call(cmd, universal_newlines=True)
245 else:
246 subprocess.call(cmd, universal_newlines=True)
24778
79filter_installed_packages = fetch.filter_installed_packages
80install = fetch.install
81upgrade = fetch.upgrade
82update = fetch.update
83purge = fetch.purge
84add_source = fetch.add_source
24885
249def apt_hold(packages, fatal=False):86if __platform__ == "ubuntu":
250 return apt_mark(packages, 'hold', fatal=fatal)87 apt_cache = fetch.apt_cache
25188 apt_install = fetch.install
25289 apt_update = fetch.update
253def apt_unhold(packages, fatal=False):90 apt_upgrade = fetch.upgrade
254 return apt_mark(packages, 'unhold', fatal=fatal)91 apt_purge = fetch.purge
25592 apt_mark = fetch.apt_mark
25693 apt_hold = fetch.apt_hold
257def add_source(source, key=None):94 apt_unhold = fetch.apt_unhold
258 """Add a package source to this system.95 get_upstream_version = fetch.get_upstream_version
25996elif __platform__ == "centos":
260 @param source: a URL or sources.list entry, as supported by97 yum_search = fetch.yum_search
261 add-apt-repository(1). Examples::
262
263 ppa:charmers/example
264 deb https://stub:key@private.example.com/ubuntu trusty main
265
266 In addition:
267 'proposed:' may be used to enable the standard 'proposed'
268 pocket for the release.
269 'cloud:' may be used to activate official cloud archive pockets,
270 such as 'cloud:icehouse'
271 'distro' may be used as a noop
272
273 @param key: A key to be added to the system's APT keyring and used
274 to verify the signatures on packages. Ideally, this should be an
275 ASCII format GPG public key including the block headers. A GPG key
276 id may also be used, but be aware that only insecure protocols are
277 available to retrieve the actual public key from a public keyserver
278 placing your Juju environment at risk. ppa and cloud archive keys
279 are securely added automtically, so sould not be provided.
280 """
281 if source is None:
282 log('Source is not present. Skipping')
283 return
284
285 if (source.startswith('ppa:') or
286 source.startswith('http') or
287 source.startswith('deb ') or
288 source.startswith('cloud-archive:')):
289 subprocess.check_call(['add-apt-repository', '--yes', source])
290 elif source.startswith('cloud:'):
291 apt_install(filter_installed_packages(['ubuntu-cloud-keyring']),
292 fatal=True)
293 pocket = source.split(':')[-1]
294 if pocket not in CLOUD_ARCHIVE_POCKETS:
295 raise SourceConfigError(
296 'Unsupported cloud: source option %s' %
297 pocket)
298 actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
299 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
300 apt.write(CLOUD_ARCHIVE.format(actual_pocket))
301 elif source == 'proposed':
302 release = lsb_release()['DISTRIB_CODENAME']
303 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
304 apt.write(PROPOSED_POCKET.format(release))
305 elif source == 'distro':
306 pass
307 else:
308 log("Unknown source: {!r}".format(source))
309
310 if key:
311 if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
312 with NamedTemporaryFile('w+') as key_file:
313 key_file.write(key)
314 key_file.flush()
315 key_file.seek(0)
316 subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
317 else:
318 # Note that hkp: is in no way a secure protocol. Using a
319 # GPG key id is pointless from a security POV unless you
320 # absolutely trust your network and DNS.
321 subprocess.check_call(['apt-key', 'adv', '--keyserver',
322 'hkp://keyserver.ubuntu.com:80', '--recv',
323 key])
32498
32599
326def configure_sources(update=False,100def configure_sources(update=False,
327 sources_var='install_sources',101 sources_var='install_sources',
328 keys_var='install_keys'):102 keys_var='install_keys'):
329 """103 """Configure multiple sources from charm configuration.
330 Configure multiple sources from charm configuration.
331104
332 The lists are encoded as yaml fragments in the configuration.105 The lists are encoded as yaml fragments in the configuration.
333 The frament needs to be included as a string. Sources and their106 The fragment needs to be included as a string. Sources and their
334 corresponding keys are of the types supported by add_source().107 corresponding keys are of the types supported by add_source().
335108
336 Example config:109 Example config:
@@ -362,12 +135,11 @@ def configure_sources(update=False,
362 for source, key in zip(sources, keys):135 for source, key in zip(sources, keys):
363 add_source(source, key)136 add_source(source, key)
364 if update:137 if update:
365 apt_update(fatal=True)138 fetch.update(fatal=True)
366139
367140
368def install_remote(source, *args, **kwargs):141def install_remote(source, *args, **kwargs):
369 """142 """Install a file tree from a remote source.
370 Install a file tree from a remote source
371143
372 The specified source should be a url of the form:144 The specified source should be a url of the form:
373 scheme://[host]/path[#[option=value][&...]]145 scheme://[host]/path[#[option=value][&...]]
@@ -390,19 +162,17 @@ def install_remote(source, *args, **kwargs):
390 # We ONLY check for True here because can_handle may return a string162 # We ONLY check for True here because can_handle may return a string
391 # explaining why it can't handle a given source.163 # explaining why it can't handle a given source.
392 handlers = [h for h in plugins() if h.can_handle(source) is True]164 handlers = [h for h in plugins() if h.can_handle(source) is True]
393 installed_to = None
394 for handler in handlers:165 for handler in handlers:
395 try:166 try:
396 installed_to = handler.install(source, *args, **kwargs)167 return handler.install(source, *args, **kwargs)
397 except UnhandledSource as e:168 except UnhandledSource as e:
398 log('Install source attempt unsuccessful: {}'.format(e),169 log('Install source attempt unsuccessful: {}'.format(e),
399 level='WARNING')170 level='WARNING')
400 if not installed_to:171 raise UnhandledSource("No handler found for source {}".format(source))
401 raise UnhandledSource("No handler found for source {}".format(source))
402 return installed_to
403172
404173
405def install_from_config(config_var_name):174def install_from_config(config_var_name):
175 """Install a file from config."""
406 charm_config = config()176 charm_config = config()
407 source = charm_config[config_var_name]177 source = charm_config[config_var_name]
408 return install_remote(source)178 return install_remote(source)
@@ -425,40 +195,3 @@ def plugins(fetch_handlers=None):
425 log("FetchHandler {} not found, skipping plugin".format(195 log("FetchHandler {} not found, skipping plugin".format(
426 handler_name))196 handler_name))
427 return plugin_list197 return plugin_list
428
429
430def _run_apt_command(cmd, fatal=False):
431 """
432 Run an APT command, checking output and retrying if the fatal flag is set
433 to True.
434
435 :param: cmd: str: The apt command to run.
436 :param: fatal: bool: Whether the command's output should be checked and
437 retried.
438 """
439 env = os.environ.copy()
440
441 if 'DEBIAN_FRONTEND' not in env:
442 env['DEBIAN_FRONTEND'] = 'noninteractive'
443
444 if fatal:
445 retry_count = 0
446 result = None
447
448 # If the command is considered "fatal", we need to retry if the apt
449 # lock was not acquired.
450
451 while result is None or result == APT_NO_LOCK:
452 try:
453 result = subprocess.check_call(cmd, env=env)
454 except subprocess.CalledProcessError as e:
455 retry_count = retry_count + 1
456 if retry_count > APT_NO_LOCK_RETRY_COUNT:
457 raise
458 result = e.returncode
459 log("Couldn't acquire DPKG lock. Will retry in {} seconds."
460 "".format(APT_NO_LOCK_RETRY_DELAY))
461 time.sleep(APT_NO_LOCK_RETRY_DELAY)
462
463 else:
464 subprocess.call(cmd, env=env)
diff --git a/hooks/charmhelpers/fetch/archiveurl.py b/hooks/charmhelpers/fetch/archiveurl.py
index b8e0943..dd24f9e 100644
--- a/hooks/charmhelpers/fetch/archiveurl.py
+++ b/hooks/charmhelpers/fetch/archiveurl.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import os15import os
18import hashlib16import hashlib
diff --git a/hooks/charmhelpers/fetch/bzrurl.py b/hooks/charmhelpers/fetch/bzrurl.py
index cafd27f..07cd029 100644
--- a/hooks/charmhelpers/fetch/bzrurl.py
+++ b/hooks/charmhelpers/fetch/bzrurl.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import os15import os
18from subprocess import check_call16from subprocess import check_call
@@ -20,19 +18,20 @@ from charmhelpers.fetch import (
20 BaseFetchHandler,18 BaseFetchHandler,
21 UnhandledSource,19 UnhandledSource,
22 filter_installed_packages,20 filter_installed_packages,
23 apt_install,21 install,
24)22)
25from charmhelpers.core.host import mkdir23from charmhelpers.core.host import mkdir
2624
2725
28if filter_installed_packages(['bzr']) != []:26if filter_installed_packages(['bzr']) != []:
29 apt_install(['bzr'])27 install(['bzr'])
30 if filter_installed_packages(['bzr']) != []:28 if filter_installed_packages(['bzr']) != []:
31 raise NotImplementedError('Unable to install bzr')29 raise NotImplementedError('Unable to install bzr')
3230
3331
34class BzrUrlFetchHandler(BaseFetchHandler):32class BzrUrlFetchHandler(BaseFetchHandler):
35 """Handler for bazaar branches via generic and lp URLs"""33 """Handler for bazaar branches via generic and lp URLs."""
34
36 def can_handle(self, source):35 def can_handle(self, source):
37 url_parts = self.parse_url(source)36 url_parts = self.parse_url(source)
38 if url_parts.scheme not in ('bzr+ssh', 'lp', ''):37 if url_parts.scheme not in ('bzr+ssh', 'lp', ''):
@@ -42,15 +41,23 @@ class BzrUrlFetchHandler(BaseFetchHandler):
42 else:41 else:
43 return True42 return True
4443
45 def branch(self, source, dest):44 def branch(self, source, dest, revno=None):
46 if not self.can_handle(source):45 if not self.can_handle(source):
47 raise UnhandledSource("Cannot handle {}".format(source))46 raise UnhandledSource("Cannot handle {}".format(source))
47 cmd_opts = []
48 if revno:
49 cmd_opts += ['-r', str(revno)]
48 if os.path.exists(dest):50 if os.path.exists(dest):
49 check_call(['bzr', 'pull', '--overwrite', '-d', dest, source])51 cmd = ['bzr', 'pull']
52 cmd += cmd_opts
53 cmd += ['--overwrite', '-d', dest, source]
50 else:54 else:
51 check_call(['bzr', 'branch', source, dest])55 cmd = ['bzr', 'branch']
56 cmd += cmd_opts
57 cmd += [source, dest]
58 check_call(cmd)
5259
53 def install(self, source, dest=None):60 def install(self, source, dest=None, revno=None):
54 url_parts = self.parse_url(source)61 url_parts = self.parse_url(source)
55 branch_name = url_parts.path.strip("/").split("/")[-1]62 branch_name = url_parts.path.strip("/").split("/")[-1]
56 if dest:63 if dest:
@@ -59,10 +66,11 @@ class BzrUrlFetchHandler(BaseFetchHandler):
59 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",66 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
60 branch_name)67 branch_name)
6168
62 if not os.path.exists(dest_dir):69 if dest and not os.path.exists(dest):
63 mkdir(dest_dir, perms=0o755)70 mkdir(dest, perms=0o755)
71
64 try:72 try:
65 self.branch(source, dest_dir)73 self.branch(source, dest_dir, revno)
66 except OSError as e:74 except OSError as e:
67 raise UnhandledSource(e.strerror)75 raise UnhandledSource(e.strerror)
68 return dest_dir76 return dest_dir
diff --git a/hooks/charmhelpers/fetch/centos.py b/hooks/charmhelpers/fetch/centos.py
69new file mode 10064477new file mode 100644
index 0000000..604bbfb
--- /dev/null
+++ b/hooks/charmhelpers/fetch/centos.py
@@ -0,0 +1,171 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import subprocess
16import os
17import time
18import six
19import yum
20
21from tempfile import NamedTemporaryFile
22from charmhelpers.core.hookenv import log
23
24YUM_NO_LOCK = 1 # The return code for "couldn't acquire lock" in YUM.
25YUM_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
26YUM_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
27
28
29def filter_installed_packages(packages):
30 """Return a list of packages that require installation."""
31 yb = yum.YumBase()
32 package_list = yb.doPackageLists()
33 temp_cache = {p.base_package_name: 1 for p in package_list['installed']}
34
35 _pkgs = [p for p in packages if not temp_cache.get(p, False)]
36 return _pkgs
37
38
39def install(packages, options=None, fatal=False):
40 """Install one or more packages."""
41 cmd = ['yum', '--assumeyes']
42 if options is not None:
43 cmd.extend(options)
44 cmd.append('install')
45 if isinstance(packages, six.string_types):
46 cmd.append(packages)
47 else:
48 cmd.extend(packages)
49 log("Installing {} with options: {}".format(packages,
50 options))
51 _run_yum_command(cmd, fatal)
52
53
54def upgrade(options=None, fatal=False, dist=False):
55 """Upgrade all packages."""
56 cmd = ['yum', '--assumeyes']
57 if options is not None:
58 cmd.extend(options)
59 cmd.append('upgrade')
60 log("Upgrading with options: {}".format(options))
61 _run_yum_command(cmd, fatal)
62
63
64def update(fatal=False):
65 """Update local yum cache."""
66 cmd = ['yum', '--assumeyes', 'update']
67 log("Update with fatal: {}".format(fatal))
68 _run_yum_command(cmd, fatal)
69
70
71def purge(packages, fatal=False):
72 """Purge one or more packages."""
73 cmd = ['yum', '--assumeyes', 'remove']
74 if isinstance(packages, six.string_types):
75 cmd.append(packages)
76 else:
77 cmd.extend(packages)
78 log("Purging {}".format(packages))
79 _run_yum_command(cmd, fatal)
80
81
82def yum_search(packages):
83 """Search for a package."""
84 output = {}
85 cmd = ['yum', 'search']
86 if isinstance(packages, six.string_types):
87 cmd.append(packages)
88 else:
89 cmd.extend(packages)
90 log("Searching for {}".format(packages))
91 result = subprocess.check_output(cmd)
92 for package in list(packages):
93 output[package] = package in result
94 return output
95
96
97def add_source(source, key=None):
98 """Add a package source to this system.
99
100 @param source: a URL with a rpm package
101
102 @param key: A key to be added to the system's keyring and used
103 to verify the signatures on packages. Ideally, this should be an
104 ASCII format GPG public key including the block headers. A GPG key
105 id may also be used, but be aware that only insecure protocols are
106 available to retrieve the actual public key from a public keyserver
107 placing your Juju environment at risk.
108 """
109 if source is None:
110 log('Source is not present. Skipping')
111 return
112
113 if source.startswith('http'):
114 directory = '/etc/yum.repos.d/'
115 for filename in os.listdir(directory):
116 with open(directory + filename, 'r') as rpm_file:
117 if source in rpm_file.read():
118 break
119 else:
120 log("Add source: {!r}".format(source))
121 # write in the charms.repo
122 with open(directory + 'Charms.repo', 'a') as rpm_file:
123 rpm_file.write('[%s]\n' % source[7:].replace('/', '_'))
124 rpm_file.write('name=%s\n' % source[7:])
125 rpm_file.write('baseurl=%s\n\n' % source)
126 else:
127 log("Unknown source: {!r}".format(source))
128
129 if key:
130 if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
131 with NamedTemporaryFile('w+') as key_file:
132 key_file.write(key)
133 key_file.flush()
134 key_file.seek(0)
135 subprocess.check_call(['rpm', '--import', key_file])
136 else:
137 subprocess.check_call(['rpm', '--import', key])
138
139
140def _run_yum_command(cmd, fatal=False):
141 """Run an YUM command.
142
143 Checks the output and retry if the fatal flag is set to True.
144
145 :param: cmd: str: The yum command to run.
146 :param: fatal: bool: Whether the command's output should be checked and
147 retried.
148 """
149 env = os.environ.copy()
150
151 if fatal:
152 retry_count = 0
153 result = None
154
155 # If the command is considered "fatal", we need to retry if the yum
156 # lock was not acquired.
157
158 while result is None or result == YUM_NO_LOCK:
159 try:
160 result = subprocess.check_call(cmd, env=env)
161 except subprocess.CalledProcessError as e:
162 retry_count = retry_count + 1
163 if retry_count > YUM_NO_LOCK_RETRY_COUNT:
164 raise
165 result = e.returncode
166 log("Couldn't acquire YUM lock. Will retry in {} seconds."
167 "".format(YUM_NO_LOCK_RETRY_DELAY))
168 time.sleep(YUM_NO_LOCK_RETRY_DELAY)
169
170 else:
171 subprocess.call(cmd, env=env)
diff --git a/hooks/charmhelpers/fetch/giturl.py b/hooks/charmhelpers/fetch/giturl.py
index 65ed531..4cf21bc 100644
--- a/hooks/charmhelpers/fetch/giturl.py
+++ b/hooks/charmhelpers/fetch/giturl.py
@@ -1,18 +1,16 @@
1# Copyright 2014-2015 Canonical Limited.1# Copyright 2014-2015 Canonical Limited.
2#2#
3# This file is part of charm-helpers.3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
4#6#
5# charm-helpers is free software: you can redistribute it and/or modify7# http://www.apache.org/licenses/LICENSE-2.0
6# it under the terms of the GNU Lesser General Public License version 3 as
7# published by the Free Software Foundation.
8#8#
9# charm-helpers is distributed in the hope that it will be useful,9# Unless required by applicable law or agreed to in writing, software
10# but WITHOUT ANY WARRANTY; without even the implied warranty of10# distributed under the License is distributed on an "AS IS" BASIS,
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# GNU Lesser General Public License for more details.12# See the License for the specific language governing permissions and
13#13# limitations under the License.
14# You should have received a copy of the GNU Lesser General Public License
15# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
1614
17import os15import os
18from subprocess import check_call, CalledProcessError16from subprocess import check_call, CalledProcessError
@@ -20,17 +18,18 @@ from charmhelpers.fetch import (
20 BaseFetchHandler,18 BaseFetchHandler,
21 UnhandledSource,19 UnhandledSource,
22 filter_installed_packages,20 filter_installed_packages,
23 apt_install,21 install,
24)22)
2523
26if filter_installed_packages(['git']) != []:24if filter_installed_packages(['git']) != []:
27 apt_install(['git'])25 install(['git'])
28 if filter_installed_packages(['git']) != []:26 if filter_installed_packages(['git']) != []:
29 raise NotImplementedError('Unable to install git')27 raise NotImplementedError('Unable to install git')
3028
3129
32class GitUrlFetchHandler(BaseFetchHandler):30class GitUrlFetchHandler(BaseFetchHandler):
33 """Handler for git branches via generic and github URLs"""31 """Handler for git branches via generic and github URLs."""
32
34 def can_handle(self, source):33 def can_handle(self, source):
35 url_parts = self.parse_url(source)34 url_parts = self.parse_url(source)
36 # TODO (mattyw) no support for ssh git@ yet35 # TODO (mattyw) no support for ssh git@ yet
diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/hooks/charmhelpers/fetch/ubuntu.py
37new file mode 10064436new file mode 100644
index 0000000..fce496b
--- /dev/null
+++ b/hooks/charmhelpers/fetch/ubuntu.py
@@ -0,0 +1,336 @@
1# Copyright 2014-2015 Canonical Limited.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import os
16import six
17import time
18import subprocess
19
20from tempfile import NamedTemporaryFile
21from charmhelpers.core.host import (
22 lsb_release
23)
24from charmhelpers.core.hookenv import log
25from charmhelpers.fetch import SourceConfigError
26
27CLOUD_ARCHIVE = """# Ubuntu Cloud Archive
28deb http://ubuntu-cloud.archive.canonical.com/ubuntu {} main
29"""
30
31PROPOSED_POCKET = """# Proposed
32deb http://archive.ubuntu.com/ubuntu {}-proposed main universe multiverse restricted
33"""
34
35CLOUD_ARCHIVE_POCKETS = {
36 # Folsom
37 'folsom': 'precise-updates/folsom',
38 'precise-folsom': 'precise-updates/folsom',
39 'precise-folsom/updates': 'precise-updates/folsom',
40 'precise-updates/folsom': 'precise-updates/folsom',
41 'folsom/proposed': 'precise-proposed/folsom',
42 'precise-folsom/proposed': 'precise-proposed/folsom',
43 'precise-proposed/folsom': 'precise-proposed/folsom',
44 # Grizzly
45 'grizzly': 'precise-updates/grizzly',
46 'precise-grizzly': 'precise-updates/grizzly',
47 'precise-grizzly/updates': 'precise-updates/grizzly',
48 'precise-updates/grizzly': 'precise-updates/grizzly',
49 'grizzly/proposed': 'precise-proposed/grizzly',
50 'precise-grizzly/proposed': 'precise-proposed/grizzly',
51 'precise-proposed/grizzly': 'precise-proposed/grizzly',
52 # Havana
53 'havana': 'precise-updates/havana',
54 'precise-havana': 'precise-updates/havana',
55 'precise-havana/updates': 'precise-updates/havana',
56 'precise-updates/havana': 'precise-updates/havana',
57 'havana/proposed': 'precise-proposed/havana',
58 'precise-havana/proposed': 'precise-proposed/havana',
59 'precise-proposed/havana': 'precise-proposed/havana',
60 # Icehouse
61 'icehouse': 'precise-updates/icehouse',
62 'precise-icehouse': 'precise-updates/icehouse',
63 'precise-icehouse/updates': 'precise-updates/icehouse',
64 'precise-updates/icehouse': 'precise-updates/icehouse',
65 'icehouse/proposed': 'precise-proposed/icehouse',
66 'precise-icehouse/proposed': 'precise-proposed/icehouse',
67 'precise-proposed/icehouse': 'precise-proposed/icehouse',
68 # Juno
69 'juno': 'trusty-updates/juno',
70 'trusty-juno': 'trusty-updates/juno',
71 'trusty-juno/updates': 'trusty-updates/juno',
72 'trusty-updates/juno': 'trusty-updates/juno',
73 'juno/proposed': 'trusty-proposed/juno',
74 'trusty-juno/proposed': 'trusty-proposed/juno',
75 'trusty-proposed/juno': 'trusty-proposed/juno',
76 # Kilo
77 'kilo': 'trusty-updates/kilo',
78 'trusty-kilo': 'trusty-updates/kilo',
79 'trusty-kilo/updates': 'trusty-updates/kilo',
80 'trusty-updates/kilo': 'trusty-updates/kilo',
81 'kilo/proposed': 'trusty-proposed/kilo',
82 'trusty-kilo/proposed': 'trusty-proposed/kilo',
83 'trusty-proposed/kilo': 'trusty-proposed/kilo',
84 # Liberty
85 'liberty': 'trusty-updates/liberty',
86 'trusty-liberty': 'trusty-updates/liberty',
87 'trusty-liberty/updates': 'trusty-updates/liberty',
88 'trusty-updates/liberty': 'trusty-updates/liberty',
89 'liberty/proposed': 'trusty-proposed/liberty',
90 'trusty-liberty/proposed': 'trusty-proposed/liberty',
91 'trusty-proposed/liberty': 'trusty-proposed/liberty',
92 # Mitaka
93 'mitaka': 'trusty-updates/mitaka',
94 'trusty-mitaka': 'trusty-updates/mitaka',
95 'trusty-mitaka/updates': 'trusty-updates/mitaka',
96 'trusty-updates/mitaka': 'trusty-updates/mitaka',
97 'mitaka/proposed': 'trusty-proposed/mitaka',
98 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
99 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
100 # Newton
101 'newton': 'xenial-updates/newton',
102 'xenial-newton': 'xenial-updates/newton',
103 'xenial-newton/updates': 'xenial-updates/newton',
104 'xenial-updates/newton': 'xenial-updates/newton',
105 'newton/proposed': 'xenial-proposed/newton',
106 'xenial-newton/proposed': 'xenial-proposed/newton',
107 'xenial-proposed/newton': 'xenial-proposed/newton',
108}
109
110APT_NO_LOCK = 100 # The return code for "couldn't acquire lock" in APT.
111APT_NO_LOCK_RETRY_DELAY = 10 # Wait 10 seconds between apt lock checks.
112APT_NO_LOCK_RETRY_COUNT = 30 # Retry to acquire the lock X times.
113
114
115def filter_installed_packages(packages):
116 """Return a list of packages that require installation."""
117 cache = apt_cache()
118 _pkgs = []
119 for package in packages:
120 try:
121 p = cache[package]
122 p.current_ver or _pkgs.append(package)
123 except KeyError:
124 log('Package {} has no installation candidate.'.format(package),
125 level='WARNING')
126 _pkgs.append(package)
127 return _pkgs
128
129
130def apt_cache(in_memory=True, progress=None):
131 """Build and return an apt cache."""
132 from apt import apt_pkg
133 apt_pkg.init()
134 if in_memory:
135 apt_pkg.config.set("Dir::Cache::pkgcache", "")
136 apt_pkg.config.set("Dir::Cache::srcpkgcache", "")
137 return apt_pkg.Cache(progress)
138
139
140def install(packages, options=None, fatal=False):
141 """Install one or more packages."""
142 if options is None:
143 options = ['--option=Dpkg::Options::=--force-confold']
144
145 cmd = ['apt-get', '--assume-yes']
146 cmd.extend(options)
147 cmd.append('install')
148 if isinstance(packages, six.string_types):
149 cmd.append(packages)
150 else:
151 cmd.extend(packages)
152 log("Installing {} with options: {}".format(packages,
153 options))
154 _run_apt_command(cmd, fatal)
155
156
157def upgrade(options=None, fatal=False, dist=False):
158 """Upgrade all packages."""
159 if options is None:
160 options = ['--option=Dpkg::Options::=--force-confold']
161
162 cmd = ['apt-get', '--assume-yes']
163 cmd.extend(options)
164 if dist:
165 cmd.append('dist-upgrade')
166 else:
167 cmd.append('upgrade')
168 log("Upgrading with options: {}".format(options))
169 _run_apt_command(cmd, fatal)
170
171
172def update(fatal=False):
173 """Update local apt cache."""
174 cmd = ['apt-get', 'update']
175 _run_apt_command(cmd, fatal)
176
177
178def purge(packages, fatal=False):
179 """Purge one or more packages."""
180 cmd = ['apt-get', '--assume-yes', 'purge']
181 if isinstance(packages, six.string_types):
182 cmd.append(packages)
183 else:
184 cmd.extend(packages)
185 log("Purging {}".format(packages))
186 _run_apt_command(cmd, fatal)
187
188
189def apt_mark(packages, mark, fatal=False):
190 """Flag one or more packages using apt-mark."""
191 log("Marking {} as {}".format(packages, mark))
192 cmd = ['apt-mark', mark]
193 if isinstance(packages, six.string_types):
194 cmd.append(packages)
195 else:
196 cmd.extend(packages)
197
198 if fatal:
199 subprocess.check_call(cmd, universal_newlines=True)
200 else:
201 subprocess.call(cmd, universal_newlines=True)
202
203
204def apt_hold(packages, fatal=False):
205 return apt_mark(packages, 'hold', fatal=fatal)
206
207
208def apt_unhold(packages, fatal=False):
209 return apt_mark(packages, 'unhold', fatal=fatal)
210
211
212def add_source(source, key=None):
213 """Add a package source to this system.
214
215 @param source: a URL or sources.list entry, as supported by
216 add-apt-repository(1). Examples::
217
218 ppa:charmers/example
219 deb https://stub:key@private.example.com/ubuntu trusty main
220
221 In addition:
222 'proposed:' may be used to enable the standard 'proposed'
223 pocket for the release.
224 'cloud:' may be used to activate official cloud archive pockets,
225 such as 'cloud:icehouse'
226 'distro' may be used as a noop
227
228 @param key: A key to be added to the system's APT keyring and used
229 to verify the signatures on packages. Ideally, this should be an
230 ASCII format GPG public key including the block headers. A GPG key
231 id may also be used, but be aware that only insecure protocols are
232 available to retrieve the actual public key from a public keyserver
233 placing your Juju environment at risk. ppa and cloud archive keys
234 are securely added automtically, so sould not be provided.
235 """
236 if source is None:
237 log('Source is not present. Skipping')
238 return
239
240 if (source.startswith('ppa:') or
241 source.startswith('http') or
242 source.startswith('deb ') or
243 source.startswith('cloud-archive:')):
244 subprocess.check_call(['add-apt-repository', '--yes', source])
245 elif source.startswith('cloud:'):
246 install(filter_installed_packages(['ubuntu-cloud-keyring']),
247 fatal=True)
248 pocket = source.split(':')[-1]
249 if pocket not in CLOUD_ARCHIVE_POCKETS:
250 raise SourceConfigError(
251 'Unsupported cloud: source option %s' %
252 pocket)
253 actual_pocket = CLOUD_ARCHIVE_POCKETS[pocket]
254 with open('/etc/apt/sources.list.d/cloud-archive.list', 'w') as apt:
255 apt.write(CLOUD_ARCHIVE.format(actual_pocket))
256 elif source == 'proposed':
257 release = lsb_release()['DISTRIB_CODENAME']
258 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
259 apt.write(PROPOSED_POCKET.format(release))
260 elif source == 'distro':
261 pass
262 else:
263 log("Unknown source: {!r}".format(source))
264
265 if key:
266 if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
267 with NamedTemporaryFile('w+') as key_file:
268 key_file.write(key)
269 key_file.flush()
270 key_file.seek(0)
271 subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
272 else:
273 # Note that hkp: is in no way a secure protocol. Using a
274 # GPG key id is pointless from a security POV unless you
275 # absolutely trust your network and DNS.
276 subprocess.check_call(['apt-key', 'adv', '--keyserver',
277 'hkp://keyserver.ubuntu.com:80', '--recv',
278 key])
279
280
281def _run_apt_command(cmd, fatal=False):
282 """Run an APT command.
283
284 Checks the output and retries if the fatal flag is set
285 to True.
286
287 :param: cmd: str: The apt command to run.
288 :param: fatal: bool: Whether the command's output should be checked and
289 retried.
290 """
291 env = os.environ.copy()
292
293 if 'DEBIAN_FRONTEND' not in env:
294 env['DEBIAN_FRONTEND'] = 'noninteractive'
295
296 if fatal:
297 retry_count = 0
298 result = None
299
300 # If the command is considered "fatal", we need to retry if the apt
301 # lock was not acquired.
302
303 while result is None or result == APT_NO_LOCK:
304 try:
305 result = subprocess.check_call(cmd, env=env)
306 except subprocess.CalledProcessError as e:
307 retry_count = retry_count + 1
308 if retry_count > APT_NO_LOCK_RETRY_COUNT:
309 raise
310 result = e.returncode
311 log("Couldn't acquire DPKG lock. Will retry in {} seconds."
312 "".format(APT_NO_LOCK_RETRY_DELAY))
313 time.sleep(APT_NO_LOCK_RETRY_DELAY)
314
315 else:
316 subprocess.call(cmd, env=env)
317
318
319def get_upstream_version(package):
320 """Determine upstream version based on installed package
321
322 @returns None (if not installed) or the upstream version
323 """
324 import apt_pkg
325 cache = apt_cache()
326 try:
327 pkg = cache[package]
328 except:
329 # the package is unknown to the current apt cache.
330 return None
331
332 if not pkg.current_ver:
333 # package is known, but no version is currently installed.
334 return None
335
336 return apt_pkg.upstream_version(pkg.current_ver.ver_str)
diff --git a/hooks/charmhelpers/osplatform.py b/hooks/charmhelpers/osplatform.py
0new file mode 100644337new file mode 100644
index 0000000..ea490bb
--- /dev/null
+++ b/hooks/charmhelpers/osplatform.py
@@ -0,0 +1,19 @@
1import platform
2
3
4def get_platform():
5 """Return the current OS platform.
6
7 For example: if current os platform is Ubuntu then a string "ubuntu"
8 will be returned (which is the name of the module).
9 This string is used to decide which platform module should be imported.
10 """
11 tuple_platform = platform.linux_distribution()
12 current_platform = tuple_platform[0]
13 if "Ubuntu" in current_platform:
14 return "ubuntu"
15 elif "CentOS" in current_platform:
16 return "centos"
17 else:
18 raise RuntimeError("This module is not supported on {}."
19 .format(current_platform))
diff --git a/hooks/ntpmaster_hooks.py b/hooks/ntpmaster_hooks.py
index 979fbf0..1bbe8ce 100755
--- a/hooks/ntpmaster_hooks.py
+++ b/hooks/ntpmaster_hooks.py
@@ -65,8 +65,19 @@ def write_config():
65 shutil.copy(NTP_CONF_ORIG, NTP_CONF)65 shutil.copy(NTP_CONF_ORIG, NTP_CONF)
6666
6767
68def assess_status():
69 hookenv.application_version_set(
70 fetch.get_upstream_version('ntp')
71 )
72 if host.service_running('ntp'):
73 hookenv.status_set('active', 'Unit is ready')
74 else:
75 hookenv.status_set('blocked', 'ntp not running')
76
77
68if __name__ == '__main__':78if __name__ == '__main__':
69 try:79 try:
70 hooks.execute(sys.argv)80 hooks.execute(sys.argv)
71 except UnregisteredHookError as e:81 except UnregisteredHookError as e:
72 hookenv.log('Unknown hook {} - skipping.'.format(e))82 hookenv.log('Unknown hook {} - skipping.'.format(e))
83 assess_status()
diff --git a/hooks/update-status b/hooks/update-status
73new file mode 12000084new file mode 120000
index 0000000..ce3b60a
--- /dev/null
+++ b/hooks/update-status
@@ -0,0 +1 @@
1ntpmaster_hooks.py
0\ No newline at end of file2\ No newline at end of file

Subscribers

People subscribed via source and target branches