Merge ~barryprice/charm-nrpe/+git/nrpe-charm:master into ~nrpe-charmers/charm-nrpe:master
- Git
- lp:~barryprice/charm-nrpe/+git/nrpe-charm
- master
- Merge into master
Proposed by
Barry Price
Status: | Superseded |
---|---|
Proposed branch: | ~barryprice/charm-nrpe/+git/nrpe-charm:master |
Merge into: | ~nrpe-charmers/charm-nrpe:master |
Diff against target: |
544 lines (+227/-58) 11 files modified
bin/charm_helpers_sync.py (+27/-22) hooks/charmhelpers/core/hookenv.py (+69/-2) hooks/charmhelpers/core/host.py (+74/-1) hooks/charmhelpers/core/host_factory/ubuntu.py (+1/-0) hooks/charmhelpers/core/services/base.py (+6/-15) hooks/charmhelpers/core/strutils.py (+11/-5) hooks/charmhelpers/core/templating.py (+18/-9) hooks/charmhelpers/core/unitdata.py (+3/-1) hooks/charmhelpers/fetch/snap.py (+16/-0) hooks/charmhelpers/fetch/ubuntu.py (+1/-1) metadata.yaml (+1/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Junien F | Approve | ||
Review via email: mp+337038@code.launchpad.net |
Commit message
Freshen charm_helpers_
Description of the change
To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : | # |
Change successfully merged at revision f2747e0ca46b6ac
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/bin/charm_helpers_sync.py b/bin/charm_helpers_sync.py |
2 | index f67fdb9..e3f0e74 100644 |
3 | --- a/bin/charm_helpers_sync.py |
4 | +++ b/bin/charm_helpers_sync.py |
5 | @@ -2,19 +2,17 @@ |
6 | |
7 | # Copyright 2014-2015 Canonical Limited. |
8 | # |
9 | -# This file is part of charm-helpers. |
10 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
11 | +# you may not use this file except in compliance with the License. |
12 | +# You may obtain a copy of the License at |
13 | # |
14 | -# charm-helpers is free software: you can redistribute it and/or modify |
15 | -# it under the terms of the GNU Lesser General Public License version 3 as |
16 | -# published by the Free Software Foundation. |
17 | +# http://www.apache.org/licenses/LICENSE-2.0 |
18 | # |
19 | -# charm-helpers is distributed in the hope that it will be useful, |
20 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
21 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
22 | -# GNU Lesser General Public License for more details. |
23 | -# |
24 | -# You should have received a copy of the GNU Lesser General Public License |
25 | -# along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
26 | +# Unless required by applicable law or agreed to in writing, software |
27 | +# distributed under the License is distributed on an "AS IS" BASIS, |
28 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
29 | +# See the License for the specific language governing permissions and |
30 | +# limitations under the License. |
31 | |
32 | # Authors: |
33 | # Adam Gandelman <adamg@ubuntu.com> |
34 | @@ -31,7 +29,7 @@ from fnmatch import fnmatch |
35 | |
36 | import six |
37 | |
38 | -CHARM_HELPERS_BRANCH = 'lp:charm-helpers' |
39 | +CHARM_HELPERS_REPO = 'https://github.com/juju/charm-helpers' |
40 | |
41 | |
42 | def parse_config(conf_file): |
43 | @@ -41,10 +39,16 @@ def parse_config(conf_file): |
44 | return yaml.load(open(conf_file).read()) |
45 | |
46 | |
47 | -def clone_helpers(work_dir, branch): |
48 | +def clone_helpers(work_dir, repo): |
49 | dest = os.path.join(work_dir, 'charm-helpers') |
50 | - logging.info('Checking out %s to %s.' % (branch, dest)) |
51 | - cmd = ['bzr', 'checkout', '--lightweight', branch, dest] |
52 | + logging.info('Cloning out %s to %s.' % (repo, dest)) |
53 | + branch = None |
54 | + if '@' in repo: |
55 | + repo, branch = repo.split('@', 1) |
56 | + cmd = ['git', 'clone', '--depth=1'] |
57 | + if branch is not None: |
58 | + cmd += ['--branch', branch] |
59 | + cmd += [repo, dest] |
60 | subprocess.check_call(cmd) |
61 | return dest |
62 | |
63 | @@ -193,14 +197,15 @@ def sync_helpers(include, src, dest, options=None): |
64 | inc, opts = extract_options(m, global_options) |
65 | sync(src, dest, '%s.%s' % (k, inc), opts) |
66 | |
67 | + |
68 | if __name__ == '__main__': |
69 | parser = optparse.OptionParser() |
70 | parser.add_option('-c', '--config', action='store', dest='config', |
71 | default=None, help='helper config file') |
72 | parser.add_option('-D', '--debug', action='store_true', dest='debug', |
73 | default=False, help='debug') |
74 | - parser.add_option('-b', '--branch', action='store', dest='branch', |
75 | - help='charm-helpers bzr branch (overrides config)') |
76 | + parser.add_option('-r', '--repository', action='store', dest='repo', |
77 | + help='charm-helpers git repository (overrides config)') |
78 | parser.add_option('-d', '--destination', action='store', dest='dest_dir', |
79 | help='sync destination dir (overrides config)') |
80 | (opts, args) = parser.parse_args() |
81 | @@ -219,10 +224,10 @@ if __name__ == '__main__': |
82 | else: |
83 | config = {} |
84 | |
85 | - if 'branch' not in config: |
86 | - config['branch'] = CHARM_HELPERS_BRANCH |
87 | - if opts.branch: |
88 | - config['branch'] = opts.branch |
89 | + if 'repo' not in config: |
90 | + config['repo'] = CHARM_HELPERS_REPO |
91 | + if opts.repo: |
92 | + config['repo'] = opts.repo |
93 | if opts.dest_dir: |
94 | config['destination'] = opts.dest_dir |
95 | |
96 | @@ -242,7 +247,7 @@ if __name__ == '__main__': |
97 | sync_options = config['options'] |
98 | tmpd = tempfile.mkdtemp() |
99 | try: |
100 | - checkout = clone_helpers(tmpd, config['branch']) |
101 | + checkout = clone_helpers(tmpd, config['repo']) |
102 | sync_helpers(config['include'], checkout, config['destination'], |
103 | options=sync_options) |
104 | except Exception as e: |
105 | diff --git a/hooks/charmhelpers/core/hookenv.py b/hooks/charmhelpers/core/hookenv.py |
106 | index c7feeaf..7ed1cc4 100644 |
107 | --- a/hooks/charmhelpers/core/hookenv.py |
108 | +++ b/hooks/charmhelpers/core/hookenv.py |
109 | @@ -22,6 +22,7 @@ from __future__ import print_function |
110 | import copy |
111 | from distutils.version import LooseVersion |
112 | from functools import wraps |
113 | +from collections import namedtuple |
114 | import glob |
115 | import os |
116 | import json |
117 | @@ -38,6 +39,7 @@ if not six.PY3: |
118 | else: |
119 | from collections import UserDict |
120 | |
121 | + |
122 | CRITICAL = "CRITICAL" |
123 | ERROR = "ERROR" |
124 | WARNING = "WARNING" |
125 | @@ -343,6 +345,7 @@ class Config(dict): |
126 | |
127 | """ |
128 | with open(self.path, 'w') as f: |
129 | + os.fchmod(f.fileno(), 0o600) |
130 | json.dump(self, f) |
131 | |
132 | def _implicit_save(self): |
133 | @@ -654,7 +657,7 @@ def _port_op(op_name, port, protocol="TCP"): |
134 | _args.append('{}/{}'.format(port, protocol)) |
135 | try: |
136 | subprocess.check_call(_args) |
137 | - except: |
138 | + except subprocess.CalledProcessError: |
139 | # Older Juju pre 2.3 doesn't support ICMP |
140 | # so treat it as a no-op if it fails. |
141 | if not icmp: |
142 | @@ -685,6 +688,17 @@ def close_ports(start, end, protocol="TCP"): |
143 | subprocess.check_call(_args) |
144 | |
145 | |
146 | +def opened_ports(): |
147 | + """Get the opened ports |
148 | + |
149 | + *Note that this will only show ports opened in a previous hook* |
150 | + |
151 | + :returns: Opened ports as a list of strings: ``['8080/tcp', '8081-8083/tcp']`` |
152 | + """ |
153 | + _args = ['opened-ports', '--format=json'] |
154 | + return json.loads(subprocess.check_output(_args).decode('UTF-8')) |
155 | + |
156 | + |
157 | @cached |
158 | def unit_get(attribute): |
159 | """Get the unit ID for the remote unit""" |
160 | @@ -806,6 +820,10 @@ class Hooks(object): |
161 | return wrapper |
162 | |
163 | |
164 | +class NoNetworkBinding(Exception): |
165 | + pass |
166 | + |
167 | + |
168 | def charm_dir(): |
169 | """Return the root directory of the current charm""" |
170 | d = os.environ.get('JUJU_CHARM_DIR') |
171 | @@ -1092,7 +1110,17 @@ def network_get_primary_address(binding): |
172 | :raise: NotImplementedError if run on Juju < 2.0 |
173 | ''' |
174 | cmd = ['network-get', '--primary-address', binding] |
175 | - return subprocess.check_output(cmd).decode('UTF-8').strip() |
176 | + try: |
177 | + response = subprocess.check_output( |
178 | + cmd, |
179 | + stderr=subprocess.STDOUT).decode('UTF-8').strip() |
180 | + except CalledProcessError as e: |
181 | + if 'no network config found for binding' in e.output.decode('UTF-8'): |
182 | + raise NoNetworkBinding("No network binding for {}" |
183 | + .format(binding)) |
184 | + else: |
185 | + raise |
186 | + return response |
187 | |
188 | |
189 | @translate_exc(from_exc=OSError, to_exc=NotImplementedError) |
190 | @@ -1153,3 +1181,42 @@ def meter_info(): |
191 | """Get the meter status information, if running in the meter-status-changed |
192 | hook.""" |
193 | return os.environ.get('JUJU_METER_INFO') |
194 | + |
195 | + |
196 | +def iter_units_for_relation_name(relation_name): |
197 | + """Iterate through all units in a relation |
198 | + |
199 | + Generator that iterates through all the units in a relation and yields |
200 | + a named tuple with rid and unit field names. |
201 | + |
202 | + Usage: |
203 | + data = [(u.rid, u.unit) |
204 | + for u in iter_units_for_relation_name(relation_name)] |
205 | + |
206 | + :param relation_name: string relation name |
207 | + :yield: Named Tuple with rid and unit field names |
208 | + """ |
209 | + RelatedUnit = namedtuple('RelatedUnit', 'rid, unit') |
210 | + for rid in relation_ids(relation_name): |
211 | + for unit in related_units(rid): |
212 | + yield RelatedUnit(rid, unit) |
213 | + |
214 | + |
215 | +def ingress_address(rid=None, unit=None): |
216 | + """ |
217 | + Retrieve the ingress-address from a relation when available. Otherwise, |
218 | + return the private-address. This function is to be used on the consuming |
219 | + side of the relation. |
220 | + |
221 | + Usage: |
222 | + addresses = [ingress_address(rid=u.rid, unit=u.unit) |
223 | + for u in iter_units_for_relation_name(relation_name)] |
224 | + |
225 | + :param rid: string relation id |
226 | + :param unit: string unit name |
227 | + :side effect: calls relation_get |
228 | + :return: string IP address |
229 | + """ |
230 | + settings = relation_get(rid=rid, unit=unit) |
231 | + return (settings.get('ingress-address') or |
232 | + settings.get('private-address')) |
233 | diff --git a/hooks/charmhelpers/core/host.py b/hooks/charmhelpers/core/host.py |
234 | index 5656e2f..fd14d60 100644 |
235 | --- a/hooks/charmhelpers/core/host.py |
236 | +++ b/hooks/charmhelpers/core/host.py |
237 | @@ -34,7 +34,7 @@ import six |
238 | |
239 | from contextlib import contextmanager |
240 | from collections import OrderedDict |
241 | -from .hookenv import log, DEBUG |
242 | +from .hookenv import log, DEBUG, local_unit |
243 | from .fstab import Fstab |
244 | from charmhelpers.osplatform import get_platform |
245 | |
246 | @@ -441,6 +441,49 @@ def add_user_to_group(username, group): |
247 | subprocess.check_call(cmd) |
248 | |
249 | |
250 | +def chage(username, lastday=None, expiredate=None, inactive=None, |
251 | + mindays=None, maxdays=None, root=None, warndays=None): |
252 | + """Change user password expiry information |
253 | + |
254 | + :param str username: User to update |
255 | + :param str lastday: Set when password was changed in YYYY-MM-DD format |
256 | + :param str expiredate: Set when user's account will no longer be |
257 | + accessible in YYYY-MM-DD format. |
258 | + -1 will remove an account expiration date. |
259 | + :param str inactive: Set the number of days of inactivity after a password |
260 | + has expired before the account is locked. |
261 | + -1 will remove an account's inactivity. |
262 | + :param str mindays: Set the minimum number of days between password |
263 | + changes to MIN_DAYS. |
264 | + 0 indicates the password can be changed anytime. |
265 | + :param str maxdays: Set the maximum number of days during which a |
266 | + password is valid. |
267 | + -1 as MAX_DAYS will remove checking maxdays |
268 | + :param str root: Apply changes in the CHROOT_DIR directory |
269 | + :param str warndays: Set the number of days of warning before a password |
270 | + change is required |
271 | + :raises subprocess.CalledProcessError: if call to chage fails |
272 | + """ |
273 | + cmd = ['chage'] |
274 | + if root: |
275 | + cmd.extend(['--root', root]) |
276 | + if lastday: |
277 | + cmd.extend(['--lastday', lastday]) |
278 | + if expiredate: |
279 | + cmd.extend(['--expiredate', expiredate]) |
280 | + if inactive: |
281 | + cmd.extend(['--inactive', inactive]) |
282 | + if mindays: |
283 | + cmd.extend(['--mindays', mindays]) |
284 | + if maxdays: |
285 | + cmd.extend(['--maxdays', maxdays]) |
286 | + if warndays: |
287 | + cmd.extend(['--warndays', warndays]) |
288 | + cmd.append(username) |
289 | + subprocess.check_call(cmd) |
290 | + |
291 | +remove_password_expiry = functools.partial(chage, expiredate='-1', inactive='-1', mindays='0', maxdays='-1') |
292 | + |
293 | def rsync(from_path, to_path, flags='-r', options=None, timeout=None): |
294 | """Replicate the contents of a path""" |
295 | options = options or ['--delete', '--executability'] |
296 | @@ -506,6 +549,8 @@ def write_file(path, content, owner='root', group='root', perms=0o444): |
297 | with open(path, 'wb') as target: |
298 | os.fchown(target.fileno(), uid, gid) |
299 | os.fchmod(target.fileno(), perms) |
300 | + if six.PY3 and isinstance(content, six.string_types): |
301 | + content = content.encode('UTF-8') |
302 | target.write(content) |
303 | return |
304 | # the contents were the same, but we might still need to change the |
305 | @@ -946,3 +991,31 @@ def updatedb(updatedb_text, new_path): |
306 | lines[i] = 'PRUNEPATHS="{}"'.format(' '.join(paths)) |
307 | output = "\n".join(lines) |
308 | return output |
309 | + |
310 | + |
311 | +def modulo_distribution(modulo=3, wait=30): |
312 | + """ Modulo distribution |
313 | + |
314 | + This helper uses the unit number, a modulo value and a constant wait time |
315 | + to produce a calculated wait time distribution. This is useful in large |
316 | + scale deployments to distribute load during an expensive operation such as |
317 | + service restarts. |
318 | + |
319 | + If you have 1000 nodes that need to restart 100 at a time 1 minute at a |
320 | + time: |
321 | + |
322 | + time.wait(modulo_distribution(modulo=100, wait=60)) |
323 | + restart() |
324 | + |
325 | + If you need restarts to happen serially set modulo to the exact number of |
326 | + nodes and set a high constant wait time: |
327 | + |
328 | + time.wait(modulo_distribution(modulo=10, wait=120)) |
329 | + restart() |
330 | + |
331 | + @param modulo: int The modulo number creates the group distribution |
332 | + @param wait: int The constant time wait value |
333 | + @return: int Calculated time to wait for unit operation |
334 | + """ |
335 | + unit_number = int(local_unit().split('/')[1]) |
336 | + return (unit_number % modulo) * wait |
337 | diff --git a/hooks/charmhelpers/core/host_factory/ubuntu.py b/hooks/charmhelpers/core/host_factory/ubuntu.py |
338 | index d8dc378..99451b5 100644 |
339 | --- a/hooks/charmhelpers/core/host_factory/ubuntu.py |
340 | +++ b/hooks/charmhelpers/core/host_factory/ubuntu.py |
341 | @@ -20,6 +20,7 @@ UBUNTU_RELEASES = ( |
342 | 'yakkety', |
343 | 'zesty', |
344 | 'artful', |
345 | + 'bionic', |
346 | ) |
347 | |
348 | |
349 | diff --git a/hooks/charmhelpers/core/services/base.py b/hooks/charmhelpers/core/services/base.py |
350 | index 345b60d..ca9dc99 100644 |
351 | --- a/hooks/charmhelpers/core/services/base.py |
352 | +++ b/hooks/charmhelpers/core/services/base.py |
353 | @@ -313,26 +313,17 @@ class PortManagerCallback(ManagerCallback): |
354 | with open(port_file) as fp: |
355 | old_ports = fp.read().split(',') |
356 | for old_port in old_ports: |
357 | - if bool(old_port) and not self.ports_contains(old_port, new_ports): |
358 | - hookenv.close_port(old_port) |
359 | + if bool(old_port): |
360 | + old_port = int(old_port) |
361 | + if old_port not in new_ports: |
362 | + hookenv.close_port(old_port) |
363 | with open(port_file, 'w') as fp: |
364 | fp.write(','.join(str(port) for port in new_ports)) |
365 | for port in new_ports: |
366 | - # A port is either a number or 'ICMP' |
367 | - protocol = 'TCP' |
368 | - if str(port).upper() == 'ICMP': |
369 | - protocol = 'ICMP' |
370 | if event_name == 'start': |
371 | - hookenv.open_port(port, protocol) |
372 | + hookenv.open_port(port) |
373 | elif event_name == 'stop': |
374 | - hookenv.close_port(port, protocol) |
375 | - |
376 | - def ports_contains(self, port, ports): |
377 | - if not bool(port): |
378 | - return False |
379 | - if str(port).upper() != 'ICMP': |
380 | - port = int(port) |
381 | - return port in ports |
382 | + hookenv.close_port(port) |
383 | |
384 | |
385 | def service_stop(service_name): |
386 | diff --git a/hooks/charmhelpers/core/strutils.py b/hooks/charmhelpers/core/strutils.py |
387 | index 685dabd..e8df045 100644 |
388 | --- a/hooks/charmhelpers/core/strutils.py |
389 | +++ b/hooks/charmhelpers/core/strutils.py |
390 | @@ -61,13 +61,19 @@ def bytes_from_string(value): |
391 | if isinstance(value, six.string_types): |
392 | value = six.text_type(value) |
393 | else: |
394 | - msg = "Unable to interpret non-string value '%s' as boolean" % (value) |
395 | + msg = "Unable to interpret non-string value '%s' as bytes" % (value) |
396 | raise ValueError(msg) |
397 | matches = re.match("([0-9]+)([a-zA-Z]+)", value) |
398 | - if not matches: |
399 | - msg = "Unable to interpret string value '%s' as bytes" % (value) |
400 | - raise ValueError(msg) |
401 | - return int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) |
402 | + if matches: |
403 | + size = int(matches.group(1)) * (1024 ** BYTE_POWER[matches.group(2)]) |
404 | + else: |
405 | + # Assume that value passed in is bytes |
406 | + try: |
407 | + size = int(value) |
408 | + except ValueError: |
409 | + msg = "Unable to interpret string value '%s' as bytes" % (value) |
410 | + raise ValueError(msg) |
411 | + return size |
412 | |
413 | |
414 | class BasicStringComparator(object): |
415 | diff --git a/hooks/charmhelpers/core/templating.py b/hooks/charmhelpers/core/templating.py |
416 | index 7b801a3..9014015 100644 |
417 | --- a/hooks/charmhelpers/core/templating.py |
418 | +++ b/hooks/charmhelpers/core/templating.py |
419 | @@ -20,7 +20,8 @@ from charmhelpers.core import hookenv |
420 | |
421 | |
422 | def render(source, target, context, owner='root', group='root', |
423 | - perms=0o444, templates_dir=None, encoding='UTF-8', template_loader=None): |
424 | + perms=0o444, templates_dir=None, encoding='UTF-8', |
425 | + template_loader=None, config_template=None): |
426 | """ |
427 | Render a template. |
428 | |
429 | @@ -32,6 +33,9 @@ def render(source, target, context, owner='root', group='root', |
430 | The context should be a dict containing the values to be replaced in the |
431 | template. |
432 | |
433 | + config_template may be provided to render from a provided template instead |
434 | + of loading from a file. |
435 | + |
436 | The `owner`, `group`, and `perms` options will be passed to `write_file`. |
437 | |
438 | If omitted, `templates_dir` defaults to the `templates` folder in the charm. |
439 | @@ -65,14 +69,19 @@ def render(source, target, context, owner='root', group='root', |
440 | if templates_dir is None: |
441 | templates_dir = os.path.join(hookenv.charm_dir(), 'templates') |
442 | template_env = Environment(loader=FileSystemLoader(templates_dir)) |
443 | - try: |
444 | - source = source |
445 | - template = template_env.get_template(source) |
446 | - except exceptions.TemplateNotFound as e: |
447 | - hookenv.log('Could not load template %s from %s.' % |
448 | - (source, templates_dir), |
449 | - level=hookenv.ERROR) |
450 | - raise e |
451 | + |
452 | + # load from a string if provided explicitly |
453 | + if config_template is not None: |
454 | + template = template_env.from_string(config_template) |
455 | + else: |
456 | + try: |
457 | + source = source |
458 | + template = template_env.get_template(source) |
459 | + except exceptions.TemplateNotFound as e: |
460 | + hookenv.log('Could not load template %s from %s.' % |
461 | + (source, templates_dir), |
462 | + level=hookenv.ERROR) |
463 | + raise e |
464 | content = template.render(context) |
465 | if target is not None: |
466 | target_dir = os.path.dirname(target) |
467 | diff --git a/hooks/charmhelpers/core/unitdata.py b/hooks/charmhelpers/core/unitdata.py |
468 | index 54ec969..6d7b494 100644 |
469 | --- a/hooks/charmhelpers/core/unitdata.py |
470 | +++ b/hooks/charmhelpers/core/unitdata.py |
471 | @@ -175,6 +175,8 @@ class Storage(object): |
472 | else: |
473 | self.db_path = os.path.join( |
474 | os.environ.get('CHARM_DIR', ''), '.unit-state.db') |
475 | + with open(self.db_path, 'a') as f: |
476 | + os.fchmod(f.fileno(), 0o600) |
477 | self.conn = sqlite3.connect('%s' % self.db_path) |
478 | self.cursor = self.conn.cursor() |
479 | self.revision = None |
480 | @@ -358,7 +360,7 @@ class Storage(object): |
481 | try: |
482 | yield self.revision |
483 | self.revision = None |
484 | - except: |
485 | + except Exception: |
486 | self.flush(False) |
487 | self.revision = None |
488 | raise |
489 | diff --git a/hooks/charmhelpers/fetch/snap.py b/hooks/charmhelpers/fetch/snap.py |
490 | index 112a54c..395836c 100644 |
491 | --- a/hooks/charmhelpers/fetch/snap.py |
492 | +++ b/hooks/charmhelpers/fetch/snap.py |
493 | @@ -41,6 +41,10 @@ class CouldNotAcquireLockException(Exception): |
494 | pass |
495 | |
496 | |
497 | +class InvalidSnapChannel(Exception): |
498 | + pass |
499 | + |
500 | + |
501 | def _snap_exec(commands): |
502 | """ |
503 | Execute snap commands. |
504 | @@ -132,3 +136,15 @@ def snap_refresh(packages, *flags): |
505 | |
506 | log(message, level='INFO') |
507 | return _snap_exec(['refresh'] + flags + packages) |
508 | + |
509 | + |
510 | +def valid_snap_channel(channel): |
511 | + """ Validate snap channel exists |
512 | + |
513 | + :raises InvalidSnapChannel: When channel does not exist |
514 | + :return: Boolean |
515 | + """ |
516 | + if channel.lower() in SNAP_CHANNELS: |
517 | + return True |
518 | + else: |
519 | + raise InvalidSnapChannel("Invalid Snap Channel: {}".format(channel)) |
520 | diff --git a/hooks/charmhelpers/fetch/ubuntu.py b/hooks/charmhelpers/fetch/ubuntu.py |
521 | index 40e1cb5..910e96a 100644 |
522 | --- a/hooks/charmhelpers/fetch/ubuntu.py |
523 | +++ b/hooks/charmhelpers/fetch/ubuntu.py |
524 | @@ -572,7 +572,7 @@ def get_upstream_version(package): |
525 | cache = apt_cache() |
526 | try: |
527 | pkg = cache[package] |
528 | - except: |
529 | + except Exception: |
530 | # the package is unknown to the current apt cache. |
531 | return None |
532 | |
533 | diff --git a/metadata.yaml b/metadata.yaml |
534 | index d0f196f..ace705f 100644 |
535 | --- a/metadata.yaml |
536 | +++ b/metadata.yaml |
537 | @@ -29,6 +29,5 @@ requires: |
538 | series: |
539 | - xenial |
540 | - trusty |
541 | - - zesty |
542 | - - yakkety |
543 | - artful |
544 | + - bionic |
+1