Merge lp:~free.ekanayaka/landscape-charm/pull-python-2-in-install-hook into lp:~landscape/landscape-charm/trunk

Proposed by Free Ekanayaka
Status: Merged
Approved by: Free Ekanayaka
Approved revision: 352
Merged at revision: 349
Proposed branch: lp:~free.ekanayaka/landscape-charm/pull-python-2-in-install-hook
Merge into: lp:~landscape/landscape-charm/trunk
Diff against target: 780 lines (+227/-125)
13 files modified
Makefile (+2/-0)
charmhelpers/core/hookenv.py (+31/-0)
charmhelpers/core/host.py (+132/-56)
charmhelpers/fetch/__init__.py (+9/-1)
charmhelpers/fetch/bzrurl.py (+15/-29)
charmhelpers/fetch/giturl.py (+20/-19)
dev/ubuntu-deps (+3/-8)
hooks/install (+1/-1)
lib/apt.py (+4/-3)
lib/tests/rootdir.py (+1/-1)
lib/tests/test_apt.py (+4/-3)
lib/tests/test_install.py (+3/-3)
lib/tests/test_upgrade.py (+2/-1)
To merge this branch: bzr merge lp:~free.ekanayaka/landscape-charm/pull-python-2-in-install-hook
Reviewer Review Type Date Requested Status
Данило Шеган (community) Abstain
Benji York (community) Approve
Chris Glass (community) Approve
🤖 Landscape Builder test results Approve
Review via email: mp+294010@code.launchpad.net

Commit message

This branch modifies the landscape-server charm minimally to get it
working on xenial units.

The change ports the install hook (and related tests) to python 3, so
we can install python 2 along with our regular dependencies.

Description of the change

This branch modifies the landscape-server charm minimally to get it
working on xenial units.

The change ports the install hook (and related tests) to python 3, so
we can install python 2 along with our regular dependencies.

I've tested this modified charm both with trusty and xenial targets
and it worked okay [0].

The diff is a bit large mainly because it contains a charmhelper upgrade,
the bare diff is here:

https://pastebin.canonical.com/155935/

[0] we have no bundle for xenial yet but you can test it by using
    something like this https://pastebin.canonical.com/155933/
    (replace placeholder values as appropriate)

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Needs Fixing (test results)
350. By Free Ekanayaka

Fix tests

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Needs Fixing (test results)
351. By Free Ekanayaka

Fix import

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Needs Fixing (test results)
352. By Free Ekanayaka

Fix import for real

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :
review: Approve (test results)
Revision history for this message
Chris Glass (tribaal) wrote :

Looks good, +1.

review: Approve
Revision history for this message
Benji York (benji) wrote :

This branch looks good. I commented on a few small things inline.

review: Approve
Revision history for this message
Данило Шеган (danilo) wrote :

make lint complains:

$ make lint
flake8 --filename='*' hooks
flake8 lib tests
lib/action.py:36:27: E901 SyntaxError: invalid syntax
Makefile:64: recipe for target 'lint' failed
make: *** [lint] Error 1

I'll do a run with xenial just in case (since I've already started looking at this)

Revision history for this message
Данило Шеган (danilo) wrote :

I am having trouble with my local env and xenial, so I won't be doing a test run right now: please don't block on me (you've already got enough reviews).

review: Abstain
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

> make lint complains:
>
> $ make lint
> flake8 --filename='*' hooks
> flake8 lib tests
> lib/action.py:36:27: E901 SyntaxError: invalid syntax
> Makefile:64: recipe for target 'lint' failed
> make: *** [lint] Error 1

Fixed.

Revision history for this message
Free Ekanayaka (free.ekanayaka) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2015-11-17 04:32:24 +0000
3+++ Makefile 2016-05-06 14:04:41 +0000
4@@ -2,6 +2,8 @@
5
6 test:
7 trial lib
8+ # For now only the install hook runs against python3
9+ trial3 lib/tests/test_apt.py lib/tests/test_install.py
10
11 ci-test:
12 ./dev/ubuntu-deps
13
14=== modified file 'charmhelpers/core/hookenv.py'
15--- charmhelpers/core/hookenv.py 2015-12-11 15:23:38 +0000
16+++ charmhelpers/core/hookenv.py 2016-05-06 14:04:41 +0000
17@@ -912,6 +912,24 @@
18 subprocess.check_call(cmd)
19
20
21+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
22+def resource_get(name):
23+ """used to fetch the resource path of the given name.
24+
25+ <name> must match a name of defined resource in metadata.yaml
26+
27+ returns either a path or False if resource not available
28+ """
29+ if not name:
30+ return False
31+
32+ cmd = ['resource-get', name]
33+ try:
34+ return subprocess.check_output(cmd).decode('UTF-8')
35+ except subprocess.CalledProcessError:
36+ return False
37+
38+
39 @cached
40 def juju_version():
41 """Full version string (eg. '1.23.3.1-trusty-amd64')"""
42@@ -976,3 +994,16 @@
43 for callback, args, kwargs in reversed(_atexit):
44 callback(*args, **kwargs)
45 del _atexit[:]
46+
47+
48+@translate_exc(from_exc=OSError, to_exc=NotImplementedError)
49+def network_get_primary_address(binding):
50+ '''
51+ Retrieve the primary network address for a named binding
52+
53+ :param binding: string. The name of a relation of extra-binding
54+ :return: string. The primary IP address for the named binding
55+ :raise: NotImplementedError if run on Juju < 2.0
56+ '''
57+ cmd = ['network-get', '--primary-address', binding]
58+ return subprocess.check_output(cmd).strip()
59
60=== modified file 'charmhelpers/core/host.py'
61--- charmhelpers/core/host.py 2015-12-11 15:23:38 +0000
62+++ charmhelpers/core/host.py 2016-05-06 14:04:41 +0000
63@@ -30,6 +30,8 @@
64 import string
65 import subprocess
66 import hashlib
67+import functools
68+import itertools
69 from contextlib import contextmanager
70 from collections import OrderedDict
71
72@@ -72,7 +74,9 @@
73 stopped = service_stop(service_name)
74 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
75 sysv_file = os.path.join(initd_dir, service_name)
76- if os.path.exists(upstart_file):
77+ if init_is_systemd():
78+ service('disable', service_name)
79+ elif os.path.exists(upstart_file):
80 override_path = os.path.join(
81 init_dir, '{}.override'.format(service_name))
82 with open(override_path, 'w') as fh:
83@@ -80,9 +84,9 @@
84 elif os.path.exists(sysv_file):
85 subprocess.check_call(["update-rc.d", service_name, "disable"])
86 else:
87- # XXX: Support SystemD too
88 raise ValueError(
89- "Unable to detect {0} as either Upstart {1} or SysV {2}".format(
90+ "Unable to detect {0} as SystemD, Upstart {1} or"
91+ " SysV {2}".format(
92 service_name, upstart_file, sysv_file))
93 return stopped
94
95@@ -94,7 +98,9 @@
96 Reenable starting again at boot. Start the service"""
97 upstart_file = os.path.join(init_dir, "{}.conf".format(service_name))
98 sysv_file = os.path.join(initd_dir, service_name)
99- if os.path.exists(upstart_file):
100+ if init_is_systemd():
101+ service('enable', service_name)
102+ elif os.path.exists(upstart_file):
103 override_path = os.path.join(
104 init_dir, '{}.override'.format(service_name))
105 if os.path.exists(override_path):
106@@ -102,9 +108,9 @@
107 elif os.path.exists(sysv_file):
108 subprocess.check_call(["update-rc.d", service_name, "enable"])
109 else:
110- # XXX: Support SystemD too
111 raise ValueError(
112- "Unable to detect {0} as either Upstart {1} or SysV {2}".format(
113+ "Unable to detect {0} as SystemD, Upstart {1} or"
114+ " SysV {2}".format(
115 service_name, upstart_file, sysv_file))
116
117 started = service_running(service_name)
118@@ -115,22 +121,40 @@
119
120 def service(action, service_name):
121 """Control a system service"""
122- cmd = ['service', service_name, action]
123+ if init_is_systemd():
124+ cmd = ['systemctl', action, service_name]
125+ else:
126+ cmd = ['service', service_name, action]
127 return subprocess.call(cmd) == 0
128
129
130-def service_running(service):
131+def systemv_services_running():
132+ output = subprocess.check_output(
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]
136+
137+
138+def service_running(service_name):
139 """Determine whether a system service is running"""
140- try:
141- output = subprocess.check_output(
142- ['service', service, 'status'],
143- stderr=subprocess.STDOUT).decode('UTF-8')
144- except subprocess.CalledProcessError:
145- return False
146+ if init_is_systemd():
147+ return service('is-active', service_name)
148 else:
149- if ("start/running" in output or "is running" in output):
150- return True
151+ try:
152+ output = subprocess.check_output(
153+ ['service', service_name, 'status'],
154+ stderr=subprocess.STDOUT).decode('UTF-8')
155+ except subprocess.CalledProcessError:
156+ return False
157 else:
158+ # This works for upstart scripts where the 'service' command
159+ # returns a consistent string to represent running 'start/running'
160+ if ("start/running" in output or "is running" in output or
161+ "up and running" in output):
162+ return True
163+ # Check System V scripts init script return codes
164+ if service_name in systemv_services_running():
165+ return True
166 return False
167
168
169@@ -146,10 +170,17 @@
170 return True
171
172
173+SYSTEMD_SYSTEM = '/run/systemd/system'
174+
175+
176+def init_is_systemd():
177+ """Return True if the host system uses systemd, False otherwise."""
178+ return os.path.isdir(SYSTEMD_SYSTEM)
179+
180+
181 def adduser(username, password=None, shell='/bin/bash', system_user=False,
182 primary_group=None, secondary_groups=None):
183- """
184- Add a user to the system.
185+ """Add a user to the system.
186
187 Will log but otherwise succeed if the user already exists.
188
189@@ -157,7 +188,7 @@
190 :param str password: Password for user; if ``None``, create a system user
191 :param str shell: The default shell for the user
192 :param bool system_user: Whether to create a login or system user
193- :param str primary_group: Primary group for user; defaults to their username
194+ :param str primary_group: Primary group for user; defaults to username
195 :param list secondary_groups: Optional list of additional groups
196
197 :returns: The password database entry struct, as returned by `pwd.getpwnam`
198@@ -283,14 +314,12 @@
199
200
201 def fstab_remove(mp):
202- """Remove the given mountpoint entry from /etc/fstab
203- """
204+ """Remove the given mountpoint entry from /etc/fstab"""
205 return Fstab.remove_by_mountpoint(mp)
206
207
208 def fstab_add(dev, mp, fs, options=None):
209- """Adds the given device entry to the /etc/fstab file
210- """
211+ """Adds the given device entry to the /etc/fstab file"""
212 return Fstab.add(dev, mp, fs, options=options)
213
214
215@@ -346,8 +375,7 @@
216
217
218 def file_hash(path, hash_type='md5'):
219- """
220- Generate a hash checksum of the contents of 'path' or None if not found.
221+ """Generate a hash checksum of the contents of 'path' or None if not found.
222
223 :param str hash_type: Any hash alrgorithm supported by :mod:`hashlib`,
224 such as md5, sha1, sha256, sha512, etc.
225@@ -362,10 +390,9 @@
226
227
228 def path_hash(path):
229- """
230- Generate a hash checksum of all files matching 'path'. Standard wildcards
231- like '*' and '?' are supported, see documentation for the 'glob' module for
232- more information.
233+ """Generate a hash checksum of all files matching 'path'. Standard
234+ wildcards like '*' and '?' are supported, see documentation for the 'glob'
235+ module for more information.
236
237 :return: dict: A { filename: hash } dictionary for all matched files.
238 Empty if none found.
239@@ -377,8 +404,7 @@
240
241
242 def check_hash(path, checksum, hash_type='md5'):
243- """
244- Validate a file using a cryptographic checksum.
245+ """Validate a file using a cryptographic checksum.
246
247 :param str checksum: Value of the checksum used to validate the file.
248 :param str hash_type: Hash algorithm used to generate `checksum`.
249@@ -393,10 +419,11 @@
250
251
252 class ChecksumError(ValueError):
253+ """A class derived from Value error to indicate the checksum failed."""
254 pass
255
256
257-def restart_on_change(restart_map, stopstart=False):
258+def restart_on_change(restart_map, stopstart=False, restart_functions=None):
259 """Restart services based on configuration files changing
260
261 This function is used a decorator, for example::
262@@ -414,27 +441,58 @@
263 restarted if any file matching the pattern got changed, created
264 or removed. Standard wildcards are supported, see documentation
265 for the 'glob' module for more information.
266+
267+ @param restart_map: {path_file_name: [service_name, ...]
268+ @param stopstart: DEFAULT false; whether to stop, start OR restart
269+ @param restart_functions: nonstandard functions to use to restart services
270+ {svc: func, ...}
271+ @returns result from decorated function
272 """
273 def wrap(f):
274+ @functools.wraps(f)
275 def wrapped_f(*args, **kwargs):
276- checksums = {path: path_hash(path) for path in restart_map}
277- f(*args, **kwargs)
278- restarts = []
279- for path in restart_map:
280- if path_hash(path) != checksums[path]:
281- restarts += restart_map[path]
282- services_list = list(OrderedDict.fromkeys(restarts))
283- if not stopstart:
284- for service_name in services_list:
285- service('restart', service_name)
286- else:
287- for action in ['stop', 'start']:
288- for service_name in services_list:
289- service(action, service_name)
290+ return restart_on_change_helper(
291+ (lambda: f(*args, **kwargs)), restart_map, stopstart,
292+ restart_functions)
293 return wrapped_f
294 return wrap
295
296
297+def restart_on_change_helper(lambda_f, restart_map, stopstart=False,
298+ restart_functions=None):
299+ """Helper function to perform the restart_on_change function.
300+
301+ This is provided for decorators to restart services if files described
302+ in the restart_map have changed after an invocation of lambda_f().
303+
304+ @param lambda_f: function to call.
305+ @param restart_map: {file: [service, ...]}
306+ @param stopstart: whether to stop, start or restart a service
307+ @param restart_functions: nonstandard functions to use to restart services
308+ {svc: func, ...}
309+ @returns result of lambda_f()
310+ """
311+ if restart_functions is None:
312+ restart_functions = {}
313+ checksums = {path: path_hash(path) for path in restart_map}
314+ r = lambda_f()
315+ # create a list of lists of the services to restart
316+ restarts = [restart_map[path]
317+ for path in restart_map
318+ if path_hash(path) != checksums[path]]
319+ # create a flat list of ordered services without duplicates from lists
320+ services_list = list(OrderedDict.fromkeys(itertools.chain(*restarts)))
321+ if services_list:
322+ actions = ('stop', 'start') if stopstart else ('restart',)
323+ for service_name in services_list:
324+ if service_name in restart_functions:
325+ restart_functions[service_name](service_name)
326+ else:
327+ for action in actions:
328+ service(action, service_name)
329+ return r
330+
331+
332 def lsb_release():
333 """Return /etc/lsb-release in a dict"""
334 d = {}
335@@ -498,7 +556,7 @@
336
337
338 def list_nics(nic_type=None):
339- '''Return a list of nics of given type(s)'''
340+ """Return a list of nics of given type(s)"""
341 if isinstance(nic_type, six.string_types):
342 int_types = [nic_type]
343 else:
344@@ -540,12 +598,13 @@
345
346
347 def set_nic_mtu(nic, mtu):
348- '''Set MTU on a network interface'''
349+ """Set the Maximum Transmission Unit (MTU) on a network interface."""
350 cmd = ['ip', 'link', 'set', nic, 'mtu', mtu]
351 subprocess.check_call(cmd)
352
353
354 def get_nic_mtu(nic):
355+ """Return the Maximum Transmission Unit (MTU) for a network interface."""
356 cmd = ['ip', 'addr', 'show', nic]
357 ip_output = subprocess.check_output(cmd).decode('UTF-8').split('\n')
358 mtu = ""
359@@ -557,6 +616,7 @@
360
361
362 def get_nic_hwaddr(nic):
363+ """Return the Media Access Control (MAC) for a network interface."""
364 cmd = ['ip', '-o', '-0', 'addr', 'show', nic]
365 ip_output = subprocess.check_output(cmd).decode('UTF-8')
366 hwaddr = ""
367@@ -567,7 +627,7 @@
368
369
370 def cmp_pkgrevno(package, revno, pkgcache=None):
371- '''Compare supplied revno with the revno of the installed package
372+ """Compare supplied revno with the revno of the installed package
373
374 * 1 => Installed revno is greater than supplied arg
375 * 0 => Installed revno is the same as supplied arg
376@@ -576,7 +636,7 @@
377 This function imports apt_cache function from charmhelpers.fetch if
378 the pkgcache argument is None. Be sure to add charmhelpers.fetch if
379 you call this function, or pass an apt_pkg.Cache() instance.
380- '''
381+ """
382 import apt_pkg
383 if not pkgcache:
384 from charmhelpers.fetch import apt_cache
385@@ -586,19 +646,27 @@
386
387
388 @contextmanager
389-def chdir(d):
390+def chdir(directory):
391+ """Change the current working directory to a different directory for a code
392+ block and return the previous directory after the block exits. Useful to
393+ run commands from a specificed directory.
394+
395+ :param str directory: The directory path to change to for this context.
396+ """
397 cur = os.getcwd()
398 try:
399- yield os.chdir(d)
400+ yield os.chdir(directory)
401 finally:
402 os.chdir(cur)
403
404
405 def chownr(path, owner, group, follow_links=True, chowntopdir=False):
406- """
407- Recursively change user and group ownership of files and directories
408+ """Recursively change user and group ownership of files and directories
409 in given path. Doesn't chown path itself by default, only its children.
410
411+ :param str path: The string path to start changing ownership.
412+ :param str owner: The owner string to use when looking up the uid.
413+ :param str group: The group string to use when looking up the gid.
414 :param bool follow_links: Also Chown links if True
415 :param bool chowntopdir: Also chown path itself if True
416 """
417@@ -622,15 +690,23 @@
418
419
420 def lchownr(path, owner, group):
421+ """Recursively change user and group ownership of files and directories
422+ in a given path, not following symbolic links. See the documentation for
423+ 'os.lchown' for more information.
424+
425+ :param str path: The string path to start changing ownership.
426+ :param str owner: The owner string to use when looking up the uid.
427+ :param str group: The group string to use when looking up the gid.
428+ """
429 chownr(path, owner, group, follow_links=False)
430
431
432 def get_total_ram():
433- '''The total amount of system RAM in bytes.
434+ """The total amount of system RAM in bytes.
435
436 This is what is reported by the OS, and may be overcommitted when
437 there are multiple containers hosted on the same machine.
438- '''
439+ """
440 with open('/proc/meminfo', 'r') as f:
441 for line in f.readlines():
442 if line:
443
444=== modified file 'charmhelpers/fetch/__init__.py'
445--- charmhelpers/fetch/__init__.py 2015-12-11 15:23:38 +0000
446+++ charmhelpers/fetch/__init__.py 2016-05-06 14:04:41 +0000
447@@ -98,6 +98,14 @@
448 'liberty/proposed': 'trusty-proposed/liberty',
449 'trusty-liberty/proposed': 'trusty-proposed/liberty',
450 'trusty-proposed/liberty': 'trusty-proposed/liberty',
451+ # Mitaka
452+ 'mitaka': 'trusty-updates/mitaka',
453+ 'trusty-mitaka': 'trusty-updates/mitaka',
454+ 'trusty-mitaka/updates': 'trusty-updates/mitaka',
455+ 'trusty-updates/mitaka': 'trusty-updates/mitaka',
456+ 'mitaka/proposed': 'trusty-proposed/mitaka',
457+ 'trusty-mitaka/proposed': 'trusty-proposed/mitaka',
458+ 'trusty-proposed/mitaka': 'trusty-proposed/mitaka',
459 }
460
461 # The order of this list is very important. Handlers should be listed in from
462@@ -411,7 +419,7 @@
463 importlib.import_module(package),
464 classname)
465 plugin_list.append(handler_class())
466- except (ImportError, AttributeError):
467+ except NotImplementedError:
468 # Skip missing plugins so that they can be ommitted from
469 # installation if desired
470 log("FetchHandler {} not found, skipping plugin".format(
471
472=== modified file 'charmhelpers/fetch/bzrurl.py'
473--- charmhelpers/fetch/bzrurl.py 2015-12-11 15:23:38 +0000
474+++ charmhelpers/fetch/bzrurl.py 2016-05-06 14:04:41 +0000
475@@ -15,54 +15,40 @@
476 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
477
478 import os
479+from subprocess import check_call
480 from charmhelpers.fetch import (
481 BaseFetchHandler,
482- UnhandledSource
483+ UnhandledSource,
484+ filter_installed_packages,
485+ apt_install,
486 )
487 from charmhelpers.core.host import mkdir
488
489-import six
490-if six.PY3:
491- raise ImportError('bzrlib does not support Python3')
492
493-try:
494- from bzrlib.branch import Branch
495- from bzrlib import bzrdir, workingtree, errors
496-except ImportError:
497- from charmhelpers.fetch import apt_install
498- apt_install("python-bzrlib")
499- from bzrlib.branch import Branch
500- from bzrlib import bzrdir, workingtree, errors
501+if filter_installed_packages(['bzr']) != []:
502+ apt_install(['bzr'])
503+ if filter_installed_packages(['bzr']) != []:
504+ raise NotImplementedError('Unable to install bzr')
505
506
507 class BzrUrlFetchHandler(BaseFetchHandler):
508 """Handler for bazaar branches via generic and lp URLs"""
509 def can_handle(self, source):
510 url_parts = self.parse_url(source)
511- if url_parts.scheme not in ('bzr+ssh', 'lp'):
512+ if url_parts.scheme not in ('bzr+ssh', 'lp', ''):
513 return False
514+ elif not url_parts.scheme:
515+ return os.path.exists(os.path.join(source, '.bzr'))
516 else:
517 return True
518
519 def branch(self, source, dest):
520- url_parts = self.parse_url(source)
521- # If we use lp:branchname scheme we need to load plugins
522 if not self.can_handle(source):
523 raise UnhandledSource("Cannot handle {}".format(source))
524- if url_parts.scheme == "lp":
525- from bzrlib.plugin import load_plugins
526- load_plugins()
527- try:
528- local_branch = bzrdir.BzrDir.create_branch_convenience(dest)
529- except errors.AlreadyControlDirError:
530- local_branch = Branch.open(dest)
531- try:
532- remote_branch = Branch.open(source)
533- remote_branch.push(local_branch)
534- tree = workingtree.WorkingTree.open(dest)
535- tree.update()
536- except Exception as e:
537- raise e
538+ if os.path.exists(dest):
539+ check_call(['bzr', 'pull', '--overwrite', '-d', dest, source])
540+ else:
541+ check_call(['bzr', 'branch', source, dest])
542
543 def install(self, source, dest=None):
544 url_parts = self.parse_url(source)
545
546=== modified file 'charmhelpers/fetch/giturl.py'
547--- charmhelpers/fetch/giturl.py 2015-12-11 15:23:38 +0000
548+++ charmhelpers/fetch/giturl.py 2016-05-06 14:04:41 +0000
549@@ -15,20 +15,18 @@
550 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
551
552 import os
553+from subprocess import check_call, CalledProcessError
554 from charmhelpers.fetch import (
555 BaseFetchHandler,
556- UnhandledSource
557+ UnhandledSource,
558+ filter_installed_packages,
559+ apt_install,
560 )
561-from charmhelpers.core.host import mkdir
562-
563-try:
564- from git import Repo
565-except ImportError:
566- from charmhelpers.fetch import apt_install
567- apt_install("python-git")
568- from git import Repo
569-
570-from git.exc import GitCommandError # noqa E402
571+
572+if filter_installed_packages(['git']) != []:
573+ apt_install(['git'])
574+ if filter_installed_packages(['git']) != []:
575+ raise NotImplementedError('Unable to install git')
576
577
578 class GitUrlFetchHandler(BaseFetchHandler):
579@@ -36,19 +34,24 @@
580 def can_handle(self, source):
581 url_parts = self.parse_url(source)
582 # TODO (mattyw) no support for ssh git@ yet
583- if url_parts.scheme not in ('http', 'https', 'git'):
584+ if url_parts.scheme not in ('http', 'https', 'git', ''):
585 return False
586+ elif not url_parts.scheme:
587+ return os.path.exists(os.path.join(source, '.git'))
588 else:
589 return True
590
591- def clone(self, source, dest, branch, depth=None):
592+ def clone(self, source, dest, branch="master", depth=None):
593 if not self.can_handle(source):
594 raise UnhandledSource("Cannot handle {}".format(source))
595
596- if depth:
597- Repo.clone_from(source, dest, branch=branch, depth=depth)
598+ if os.path.exists(dest):
599+ cmd = ['git', '-C', dest, 'pull', source, branch]
600 else:
601- Repo.clone_from(source, dest, branch=branch)
602+ cmd = ['git', 'clone', source, dest, '--branch', branch]
603+ if depth:
604+ cmd.extend(['--depth', depth])
605+ check_call(cmd)
606
607 def install(self, source, branch="master", dest=None, depth=None):
608 url_parts = self.parse_url(source)
609@@ -58,11 +61,9 @@
610 else:
611 dest_dir = os.path.join(os.environ.get('CHARM_DIR'), "fetched",
612 branch_name)
613- if not os.path.exists(dest_dir):
614- mkdir(dest_dir, perms=0o755)
615 try:
616 self.clone(source, dest_dir, branch, depth)
617- except GitCommandError as e:
618+ except CalledProcessError as e:
619 raise UnhandledSource(e)
620 except OSError as e:
621 raise UnhandledSource(e.strerror)
622
623=== modified file 'dev/ubuntu-deps'
624--- dev/ubuntu-deps 2016-02-03 01:41:18 +0000
625+++ dev/ubuntu-deps 2016-05-06 14:04:41 +0000
626@@ -3,20 +3,15 @@
627 # It will ask your password a lot.
628
629 # Install add-apt-repository (packaging differs across releases).
630-lsb_release -r | grep -q 12.04 \
631- && sudo apt-get -y install python-software-properties \
632- || sudo apt-get -y install software-properties-common
633+sudo apt-get -y install python-software-properties
634
635 # Add the juju stable ppa, install charm-tools (juju-test plugin) and other deps
636 sudo add-apt-repository -y ppa:juju/stable
637 sudo apt-get update
638 sudo apt-get -y install juju-deployer juju-core charm-tools python3 \
639 python3-yaml curl python3-zope.testrunner \
640- python3-amulet python3-fixtures python3-jinja2
641-
642-# python3-flake8 was introduced after 12.04. Releases prior to that are not
643-# supported.
644-lsb_release -r | grep -q 12.04 || sudo apt-get -y install python3-flake8
645+ python3-amulet python3-fixtures python3-jinja2 python3-fixtures\
646+ python3-psutil python3-twisted python3-flake8
647
648 sudo apt-get -y install python-fixtures python-six python-yaml dpkg-dev \
649 pbuilder dh-make python-jinja2 python-psutil
650
651=== modified file 'hooks/install'
652--- hooks/install 2015-01-28 09:10:07 +0000
653+++ hooks/install 2016-05-06 14:04:41 +0000
654@@ -1,4 +1,4 @@
655-#!/usr/bin/python
656+#!/usr/bin/python3
657 import sys
658
659 from lib.install import InstallHook
660
661=== modified file 'lib/apt.py'
662--- lib/apt.py 2016-03-17 12:36:55 +0000
663+++ lib/apt.py 2016-05-06 14:04:41 +0000
664@@ -12,7 +12,7 @@
665 from lib.paths import default_paths
666
667 LANDSCAPE_PACKAGES = ("landscape-server", "landscape-hashids")
668-INSTALL_PACKAGES = LANDSCAPE_PACKAGES + ("python-psutil",)
669+INSTALL_PACKAGES = LANDSCAPE_PACKAGES + ("python-minimal", "python-psutil")
670 PACKAGES_DEV = ("dpkg-dev", "pbuilder")
671 TARBALL = "landscape-server_*.tar.gz"
672
673@@ -255,8 +255,9 @@
674
675 def _is_tarball_new(self, tarball):
676 """Check if this is a new tarball and we need to build it."""
677- with open(tarball, "r") as fd:
678- digest = hashlib.md5(fd.read()).hexdigest()
679+ with open(tarball, "rb") as fd:
680+ data = fd.read()
681+ digest = hashlib.md5(data).hexdigest()
682
683 md5sum = tarball + ".md5sum"
684 if os.path.exists(md5sum):
685
686=== modified file 'lib/tests/rootdir.py'
687--- lib/tests/rootdir.py 2015-05-28 12:25:22 +0000
688+++ lib/tests/rootdir.py 2016-05-06 14:04:41 +0000
689@@ -19,6 +19,6 @@
690 os.makedirs(self.paths.config_dir())
691 os.makedirs(os.path.dirname(self.paths.ssl_certificate()))
692 os.makedirs(self.paths.offline_dir())
693- for path in ERRORFILES_MAP.itervalues():
694+ for path in ERRORFILES_MAP.values():
695 with open(os.path.join(self.paths.offline_dir(), path), "w") as fd:
696 fd.write("Fake %s" % path)
697
698=== modified file 'lib/tests/test_apt.py'
699--- lib/tests/test_apt.py 2016-03-17 12:36:55 +0000
700+++ lib/tests/test_apt.py 2016-05-06 14:04:41 +0000
701@@ -126,7 +126,7 @@
702 """
703 self.hookenv.config()["source"] = "15.11, ppa:juju/devel"
704 self.apt.set_sources()
705- self.assertItemsEqual(
706+ self.assertEqual(
707 [("ppa:landscape/15.11", None), ("ppa:juju/devel", None)],
708 self.fetch.sources)
709
710@@ -138,7 +138,7 @@
711 self.hookenv.config()["source"] = "15.11, deb http://host/ ./"
712 self.hookenv.config()["key"] = "null, xyz"
713 self.apt.set_sources()
714- self.assertItemsEqual(
715+ self.assertEqual(
716 [("ppa:landscape/15.11", None), ("deb http://host/ ./", "xyz")],
717 self.fetch.sources)
718
719@@ -235,7 +235,8 @@
720 installed.
721 """
722 self.assertEqual(
723- ("landscape-server", "landscape-hashids", "python-psutil"),
724+ ("landscape-server", "landscape-hashids", "python-minimal",
725+ "python-psutil"),
726 INSTALL_PACKAGES)
727
728 def test_install(self):
729
730=== modified file 'lib/tests/test_install.py'
731--- lib/tests/test_install.py 2016-01-12 12:11:20 +0000
732+++ lib/tests/test_install.py 2016-05-06 14:04:41 +0000
733@@ -36,7 +36,7 @@
734 os.makedirs(os.path.dirname(hook))
735 with open(hook, "w") as fd:
736 fd.write("#!/bin/sh\ntouch %s\n" % flag)
737- os.chmod(hook, 0755)
738+ os.chmod(hook, 0o755)
739 self.hookenv.config()["source"] = "ppa:landscape/14.10"
740 self.hook()
741 self.assertTrue(os.path.exists(flag))
742@@ -50,7 +50,7 @@
743 os.makedirs(os.path.dirname(hook))
744 with open(hook, "w") as fd:
745 fd.write("#!/bin/sh\ntexit 127\n")
746- os.chmod(hook, 0755)
747+ os.chmod(hook, 0o755)
748 self.hookenv.config()["source"] = "ppa:landscape/14.10"
749 self.assertEqual(1, self.hook())
750 self.assertEqual(
751@@ -71,7 +71,7 @@
752 fd.write("#!/bin/sh\ntouch %s\n" % flag)
753 with open(hook2, "w") as fd:
754 fd.write("#!/bin/sh\ntouch %s\n" % flag)
755- os.chmod(hook2, 0755)
756+ os.chmod(hook2, 0o755)
757 self.hookenv.config()["source"] = "ppa:landscape/14.10"
758 self.hook()
759 self.assertFalse(os.path.exists(flag))
760
761=== modified file 'lib/tests/test_upgrade.py'
762--- lib/tests/test_upgrade.py 2016-01-12 12:11:20 +0000
763+++ lib/tests/test_upgrade.py 2016-05-06 14:04:41 +0000
764@@ -2,6 +2,7 @@
765 from lib.tests.rootdir import RootDir
766 from lib.tests.stubs import FetchStub, SubprocessStub
767 from lib.upgrade import UpgradeAction
768+from lib.apt import INSTALL_PACKAGES
769
770
771 class UpgradeActionTest(HookenvTest):
772@@ -33,7 +34,7 @@
773 self.assertEqual([True], self.fetch.updates)
774 # And one apt_install with appropriate options.
775 self.assertEqual(
776- [(("landscape-server", "landscape-hashids", "python-psutil"),
777+ [(INSTALL_PACKAGES,
778 ["--option=Dpkg::Options::=--force-confdef",
779 "--option=Dpkg::Options::=--force-confold"],
780 True)], self.fetch.installed)

Subscribers

People subscribed via source and target branches