Merge lp:~rcj/charms/trusty/ntp/lp1366880 into lp:charms/trusty/ntp

Proposed by Robert C Jennings
Status: Merged
Merged at revision: 17
Proposed branch: lp:~rcj/charms/trusty/ntp/lp1366880
Merge into: lp:charms/trusty/ntp
Diff against target: 334 lines (+140/-35)
4 files modified
hooks/charmhelpers/contrib/templating/jinja.py (+23/-0)
hooks/charmhelpers/core/hookenv.py (+30/-16)
hooks/charmhelpers/core/host.py (+36/-7)
hooks/charmhelpers/fetch/__init__.py (+51/-12)
To merge this branch: bzr merge lp:~rcj/charms/trusty/ntp/lp1366880
Reviewer Review Type Date Requested Status
Matt Bruzek (community) Approve
Review via email: mp+233759@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Matt Bruzek (mbruzek) wrote :

+1 LGTM I was able to deploy this successfully thanks to the automated tests!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added directory 'hooks/charmhelpers/contrib'
2=== added file 'hooks/charmhelpers/contrib/__init__.py'
3=== added directory 'hooks/charmhelpers/contrib/templating'
4=== added file 'hooks/charmhelpers/contrib/templating/__init__.py'
5=== added file 'hooks/charmhelpers/contrib/templating/jinja.py'
6--- hooks/charmhelpers/contrib/templating/jinja.py 1970-01-01 00:00:00 +0000
7+++ hooks/charmhelpers/contrib/templating/jinja.py 2014-09-08 16:44:10 +0000
8@@ -0,0 +1,23 @@
9+"""
10+Templating using the python-jinja2 package.
11+"""
12+from charmhelpers.fetch import (
13+ apt_install,
14+)
15+
16+
17+DEFAULT_TEMPLATES_DIR = 'templates'
18+
19+
20+try:
21+ import jinja2
22+except ImportError:
23+ apt_install(["python-jinja2"])
24+ import jinja2
25+
26+
27+def render(template_name, context, template_dir=DEFAULT_TEMPLATES_DIR):
28+ templates = jinja2.Environment(
29+ loader=jinja2.FileSystemLoader(template_dir))
30+ template = templates.get_template(template_name)
31+ return template.render(context)
32
33=== modified file 'hooks/charmhelpers/core/hookenv.py'
34--- hooks/charmhelpers/core/hookenv.py 2014-07-16 05:40:55 +0000
35+++ hooks/charmhelpers/core/hookenv.py 2014-09-08 16:44:10 +0000
36@@ -156,12 +156,15 @@
37
38
39 class Config(dict):
40- """A Juju charm config dictionary that can write itself to
41- disk (as json) and track which values have changed since
42- the previous hook invocation.
43-
44- Do not instantiate this object directly - instead call
45- ``hookenv.config()``
46+ """A dictionary representation of the charm's config.yaml, with some
47+ extra features:
48+
49+ - See which values in the dictionary have changed since the previous hook.
50+ - For values that have changed, see what the previous value was.
51+ - Store arbitrary data for use in a later hook.
52+
53+ NOTE: Do not instantiate this object directly - instead call
54+ ``hookenv.config()``, which will return an instance of :class:`Config`.
55
56 Example usage::
57
58@@ -170,8 +173,8 @@
59 >>> config = hookenv.config()
60 >>> config['foo']
61 'bar'
62+ >>> # store a new key/value for later use
63 >>> config['mykey'] = 'myval'
64- >>> config.save()
65
66
67 >>> # user runs `juju set mycharm foo=baz`
68@@ -188,22 +191,23 @@
69 >>> # keys/values that we add are preserved across hooks
70 >>> config['mykey']
71 'myval'
72- >>> # don't forget to save at the end of hook!
73- >>> config.save()
74
75 """
76 CONFIG_FILE_NAME = '.juju-persistent-config'
77
78 def __init__(self, *args, **kw):
79 super(Config, self).__init__(*args, **kw)
80+ self.implicit_save = True
81 self._prev_dict = None
82 self.path = os.path.join(charm_dir(), Config.CONFIG_FILE_NAME)
83 if os.path.exists(self.path):
84 self.load_previous()
85
86 def load_previous(self, path=None):
87- """Load previous copy of config from disk so that current values
88- can be compared to previous values.
89+ """Load previous copy of config from disk.
90+
91+ In normal usage you don't need to call this method directly - it
92+ is called automatically at object initialization.
93
94 :param path:
95
96@@ -218,8 +222,8 @@
97 self._prev_dict = json.load(f)
98
99 def changed(self, key):
100- """Return true if the value for this key has changed since
101- the last save.
102+ """Return True if the current value for this key is different from
103+ the previous value.
104
105 """
106 if self._prev_dict is None:
107@@ -228,7 +232,7 @@
108
109 def previous(self, key):
110 """Return previous value for this key, or None if there
111- is no "previous" value.
112+ is no previous value.
113
114 """
115 if self._prev_dict:
116@@ -238,7 +242,13 @@
117 def save(self):
118 """Save this config to disk.
119
120- Preserves items in _prev_dict that do not exist in self.
121+ If the charm is using the :mod:`Services Framework <services.base>`
122+ or :meth:'@hook <Hooks.hook>' decorator, this
123+ is called automatically at the end of successful hook execution.
124+ Otherwise, it should be called directly by user code.
125+
126+ To disable automatic saves, set ``implicit_save=False`` on this
127+ instance.
128
129 """
130 if self._prev_dict:
131@@ -285,8 +295,9 @@
132 raise
133
134
135-def relation_set(relation_id=None, relation_settings={}, **kwargs):
136+def relation_set(relation_id=None, relation_settings=None, **kwargs):
137 """Set relation information for the current unit"""
138+ relation_settings = relation_settings if relation_settings else {}
139 relation_cmd_line = ['relation-set']
140 if relation_id is not None:
141 relation_cmd_line.extend(('-r', relation_id))
142@@ -477,6 +488,9 @@
143 hook_name = os.path.basename(args[0])
144 if hook_name in self._hooks:
145 self._hooks[hook_name]()
146+ cfg = config()
147+ if cfg.implicit_save:
148+ cfg.save()
149 else:
150 raise UnregisteredHookError(hook_name)
151
152
153=== modified file 'hooks/charmhelpers/core/host.py'
154--- hooks/charmhelpers/core/host.py 2014-08-05 05:12:15 +0000
155+++ hooks/charmhelpers/core/host.py 2014-09-08 16:44:10 +0000
156@@ -12,6 +12,8 @@
157 import string
158 import subprocess
159 import hashlib
160+import shutil
161+from contextlib import contextmanager
162
163 from collections import OrderedDict
164
165@@ -52,7 +54,7 @@
166 def service_running(service):
167 """Determine whether a system service is running"""
168 try:
169- output = subprocess.check_output(['service', service, 'status'])
170+ output = subprocess.check_output(['service', service, 'status'], stderr=subprocess.STDOUT)
171 except subprocess.CalledProcessError:
172 return False
173 else:
174@@ -62,6 +64,16 @@
175 return False
176
177
178+def service_available(service_name):
179+ """Determine whether a system service is available"""
180+ try:
181+ subprocess.check_output(['service', service_name, 'status'], stderr=subprocess.STDOUT)
182+ except subprocess.CalledProcessError:
183+ return False
184+ else:
185+ return True
186+
187+
188 def adduser(username, password=None, shell='/bin/bash', system_user=False):
189 """Add a user to the system"""
190 try:
191@@ -320,12 +332,29 @@
192
193 '''
194 import apt_pkg
195+ from charmhelpers.fetch import apt_cache
196 if not pkgcache:
197- apt_pkg.init()
198- # Force Apt to build its cache in memory. That way we avoid race
199- # conditions with other applications building the cache in the same
200- # place.
201- apt_pkg.config.set("Dir::Cache::pkgcache", "")
202- pkgcache = apt_pkg.Cache()
203+ pkgcache = apt_cache()
204 pkg = pkgcache[package]
205 return apt_pkg.version_compare(pkg.current_ver.ver_str, revno)
206+
207+
208+@contextmanager
209+def chdir(d):
210+ cur = os.getcwd()
211+ try:
212+ yield os.chdir(d)
213+ finally:
214+ os.chdir(cur)
215+
216+
217+def chownr(path, owner, group):
218+ uid = pwd.getpwnam(owner).pw_uid
219+ gid = grp.getgrnam(group).gr_gid
220+
221+ for root, dirs, files in os.walk(path):
222+ for name in dirs + files:
223+ full = os.path.join(root, name)
224+ broken_symlink = os.path.lexists(full) and not os.path.exists(full)
225+ if not broken_symlink:
226+ os.chown(full, uid, gid)
227
228=== modified file 'hooks/charmhelpers/fetch/__init__.py'
229--- hooks/charmhelpers/fetch/__init__.py 2014-07-16 05:40:55 +0000
230+++ hooks/charmhelpers/fetch/__init__.py 2014-09-08 16:44:10 +0000
231@@ -1,4 +1,5 @@
232 import importlib
233+from tempfile import NamedTemporaryFile
234 import time
235 from yaml import safe_load
236 from charmhelpers.core.host import (
237@@ -116,14 +117,7 @@
238
239 def filter_installed_packages(packages):
240 """Returns a list of packages that require installation"""
241- import apt_pkg
242- apt_pkg.init()
243-
244- # Tell apt to build an in-memory cache to prevent race conditions (if
245- # another process is already building the cache).
246- apt_pkg.config.set("Dir::Cache::pkgcache", "")
247-
248- cache = apt_pkg.Cache()
249+ cache = apt_cache()
250 _pkgs = []
251 for package in packages:
252 try:
253@@ -136,6 +130,16 @@
254 return _pkgs
255
256
257+def apt_cache(in_memory=True):
258+ """Build and return an apt cache"""
259+ import apt_pkg
260+ apt_pkg.init()
261+ if in_memory:
262+ apt_pkg.config.set("Dir::Cache::pkgcache", "")
263+ apt_pkg.config.set("Dir::Cache::srcpkgcache", "")
264+ return apt_pkg.Cache()
265+
266+
267 def apt_install(packages, options=None, fatal=False):
268 """Install one or more packages"""
269 if options is None:
270@@ -201,6 +205,27 @@
271
272
273 def add_source(source, key=None):
274+ """Add a package source to this system.
275+
276+ @param source: a URL or sources.list entry, as supported by
277+ add-apt-repository(1). Examples:
278+ ppa:charmers/example
279+ deb https://stub:key@private.example.com/ubuntu trusty main
280+
281+ In addition:
282+ 'proposed:' may be used to enable the standard 'proposed'
283+ pocket for the release.
284+ 'cloud:' may be used to activate official cloud archive pockets,
285+ such as 'cloud:icehouse'
286+
287+ @param key: A key to be added to the system's APT keyring and used
288+ to verify the signatures on packages. Ideally, this should be an
289+ ASCII format GPG public key including the block headers. A GPG key
290+ id may also be used, but be aware that only insecure protocols are
291+ available to retrieve the actual public key from a public keyserver
292+ placing your Juju environment at risk. ppa and cloud archive keys
293+ are securely added automtically, so sould not be provided.
294+ """
295 if source is None:
296 log('Source is not present. Skipping')
297 return
298@@ -225,10 +250,23 @@
299 release = lsb_release()['DISTRIB_CODENAME']
300 with open('/etc/apt/sources.list.d/proposed.list', 'w') as apt:
301 apt.write(PROPOSED_POCKET.format(release))
302+ else:
303+ raise SourceConfigError("Unknown source: {!r}".format(source))
304+
305 if key:
306- subprocess.check_call(['apt-key', 'adv', '--keyserver',
307- 'hkp://keyserver.ubuntu.com:80', '--recv',
308- key])
309+ if '-----BEGIN PGP PUBLIC KEY BLOCK-----' in key:
310+ with NamedTemporaryFile() as key_file:
311+ key_file.write(key)
312+ key_file.flush()
313+ key_file.seek(0)
314+ subprocess.check_call(['apt-key', 'add', '-'], stdin=key_file)
315+ else:
316+ # Note that hkp: is in no way a secure protocol. Using a
317+ # GPG key id is pointless from a security POV unless you
318+ # absolutely trust your network and DNS.
319+ subprocess.check_call(['apt-key', 'adv', '--keyserver',
320+ 'hkp://keyserver.ubuntu.com:80', '--recv',
321+ key])
322
323
324 def configure_sources(update=False,
325@@ -238,7 +276,8 @@
326 Configure multiple sources from charm configuration.
327
328 The lists are encoded as yaml fragments in the configuration.
329- The frament needs to be included as a string.
330+ The frament needs to be included as a string. Sources and their
331+ corresponding keys are of the types supported by add_source().
332
333 Example config:
334 install_sources: |

Subscribers

People subscribed via source and target branches