Merge lp:~free.ekanayaka/landscape-charm/pull-python-2-in-install-hook into lp:~landscape/landscape-charm/trunk
- pull-python-2-in-install-hook
- Merge into trunk
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 |
Related bugs: |
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:/
[0] we have no bundle for xenial yet but you can test it by using
something like this https:/
(replace placeholder values as appropriate)
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
- 350. By Free Ekanayaka
-
Fix tests
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Fail
Revno: 350
Branch: lp:~free.ekanayaka/landscape-charm/pull-python-2-in-install-hook
Jenkins: https:/
- 351. By Free Ekanayaka
-
Fix import
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Fail
Revno: 351
Branch: lp:~free.ekanayaka/landscape-charm/pull-python-2-in-install-hook
Jenkins: https:/
- 352. By Free Ekanayaka
-
Fix import for real
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: make ci-test
Result: Success
Revno: 352
Branch: lp:~free.ekanayaka/landscape-charm/pull-python-2-in-install-hook
Jenkins: https:/
Benji York (benji) wrote : | # |
This branch looks good. I commented on a few small things inline.
Данило Шеган (danilo) wrote : | # |
make lint complains:
$ make lint
flake8 --filename='*' hooks
flake8 lib tests
lib/action.
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)
Данило Шеган (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).
Free Ekanayaka (free.ekanayaka) wrote : | # |
> make lint complains:
>
> $ make lint
> flake8 --filename='*' hooks
> flake8 lib tests
> lib/action.
> Makefile:64: recipe for target 'lint' failed
> make: *** [lint] Error 1
Fixed.
Free Ekanayaka (free.ekanayaka) : | # |
Preview Diff
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) |
Command: make ci-test /ci.lscape. net/job/ latch-test/ 4531/
Result: Fail
Revno: 349
Branch: lp:~free.ekanayaka/landscape-charm/pull-python-2-in-install-hook
Jenkins: https:/