Merge lp:~bregma/libertine/track-apt-progress into lp:libertine
- track-apt-progress
- Merge into devel
Proposed by
Stephen M. Webb
Status: | Merged |
---|---|
Approved by: | Christopher Townsend |
Approved revision: | 139 |
Merged at revision: | 135 |
Proposed branch: | lp:~bregma/libertine/track-apt-progress |
Merge into: | lp:libertine |
Diff against target: |
584 lines (+241/-204) 2 files modified
python/libertine/Libertine.py (+228/-200) tools/libertine-container-manager (+13/-4) |
To merge this branch: | bzr merge lp:~bregma/libertine/track-apt-progress |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Christopher Townsend | Approve | ||
Review via email: mp+278984@code.launchpad.net |
Commit message
refactor container code to deduplicate lx/chroot code and add enhanced progress reporting to package management
Description of the change
(1) Refactor some of the code in the Python3 libertine package to deduplicate code common to both.
(2) Add an (optional) enahnced verbose output from APT commands that can be used by a front end to give ongoing package installation/
This MP is mostly foundational prep work for a couple of later MPs that do actual visible changes to te software.
To post a comment you must log in.
Revision history for this message
Stephen M. Webb (bregma) wrote : | # |
Revision history for this message
Christopher Townsend (townsend) wrote : | # |
Looks good. A couple of nitpicks inline.
- 138. By Stephen M. Webb
-
libertine-
container- manager: fixed help message for --quiet option - 139. By Stephen M. Webb
-
libertine-
container- manager: fixed a pep8 nit
Revision history for this message
Christopher Townsend (townsend) wrote : | # |
Ok, LGTM now.
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'python/libertine/Libertine.py' |
2 | --- python/libertine/Libertine.py 2015-11-23 20:04:30 +0000 |
3 | +++ python/libertine/Libertine.py 2015-12-01 12:40:06 +0000 |
4 | @@ -12,6 +12,8 @@ |
5 | # You should have received a copy of the GNU General Public License along |
6 | # with this program. If not, see <http://www.gnu.org/licenses/>. |
7 | |
8 | +import abc |
9 | +import contextlib |
10 | import crypt |
11 | import json |
12 | import lxc |
13 | @@ -124,29 +126,6 @@ |
14 | fd.write("s0_mode = 3") |
15 | |
16 | |
17 | -def start_container_for_update(container): |
18 | - if not container.running: |
19 | - print("Starting the container...") |
20 | - if not container.start(): |
21 | - print("Container failed to start") |
22 | - return False |
23 | - if not container.wait("RUNNING", 10): |
24 | - print("Container failed to enter the RUNNING state") |
25 | - return False |
26 | - |
27 | - if not container.get_ips(timeout=30): |
28 | - print("Not able to connect to the network.") |
29 | - return False |
30 | - |
31 | - container.attach_wait(lxc.attach_run_command, |
32 | - ["umount", "/tmp/.X11-unix"]) |
33 | - |
34 | - container.attach_wait(lxc.attach_run_command, |
35 | - ["apt-get", "update"]) |
36 | - |
37 | - return True |
38 | - |
39 | - |
40 | def lxc_container(container_id): |
41 | config_path = libertine.utils.get_libertine_containers_dir_path() |
42 | container = lxc.Container(container_id, config_path) |
43 | @@ -154,18 +133,126 @@ |
44 | return container |
45 | |
46 | |
47 | -class LibertineLXC(object): |
48 | +def apt_args_for_verbosity_level(verbosity): |
49 | + """ |
50 | + Maps numeric verbosity levels onto APT command-line arguments. |
51 | + |
52 | + :param verbosity: 0 is quiet, 1 is normal, 2 is incontinent |
53 | + """ |
54 | + return { |
55 | + 0: '--quiet=2', |
56 | + 1: '--assume-yes', |
57 | + 2: '--quiet=1 --assume-yes --option APT::Status-Fd=1' |
58 | + }.get(verbosity, '') |
59 | + |
60 | + |
61 | +def apt_command_prefix(verbosity): |
62 | + return '/usr/bin/apt ' + apt_args_for_verbosity_level(verbosity) + ' ' |
63 | + |
64 | + |
65 | +class BaseContainer(metaclass=abc.ABCMeta): |
66 | + """ |
67 | + An abstract base container to provide common functionality for all |
68 | + concrete container types. |
69 | + |
70 | + :param container_id: The machine-readable container name. |
71 | + """ |
72 | def __init__(self, container_id): |
73 | self.container_id = container_id |
74 | + |
75 | + def create_libertine_container(self, password=None, verbosity=1): |
76 | + pass |
77 | + |
78 | + def destroy_libertine_container(self, verbosity=1): |
79 | + pass |
80 | + |
81 | + def is_running(self): |
82 | + """ |
83 | + Indicates if the container is 'running'. The definition of 'running' |
84 | + depends on the type of the container. |
85 | + """ |
86 | + return False |
87 | + |
88 | + def start_container(self): |
89 | + """ |
90 | + Starts the container. To start the container means to put it into a |
91 | + 'running' state, the meaning of which depends on the type of the |
92 | + container. |
93 | + """ |
94 | + pass |
95 | + |
96 | + def stop_container(self): |
97 | + """ |
98 | + Stops the container. The opposite of start_container(). |
99 | + """ |
100 | + pass |
101 | + |
102 | + @abc.abstractmethod |
103 | + def run_in_container(self, command_string): |
104 | + """ |
105 | + Runs a command inside the container context. |
106 | + |
107 | + :param command_string: The command line to execute in the container context. |
108 | + """ |
109 | + pass |
110 | + |
111 | + def update_packages(self, verbosity=1): |
112 | + """ |
113 | + Updates all packages installed in the container. |
114 | + |
115 | + :param verbosity: the chattiness of the output on a range from 0 to 2 |
116 | + """ |
117 | + self.run_in_container(apt_command_prefix(verbosity) + 'update') |
118 | + self.run_in_container(apt_command_prefix(verbosity) + 'upgrade') |
119 | + |
120 | + def install_package(self, package_name, verbosity=1): |
121 | + """ |
122 | + Installs a named package in the container. |
123 | + |
124 | + :param package_name: The name of the package as APT understands it. |
125 | + :param verbosity: the chattiness of the output on a range from 0 to 2 |
126 | + """ |
127 | + return self.run_in_container(apt_command_prefix(verbosity) + "install --no-install-recommends '" + package_name + "'") == 0 |
128 | + |
129 | + |
130 | +class LibertineLXC(BaseContainer): |
131 | + """ |
132 | + A concrete container type implemented using an LXC container. |
133 | + """ |
134 | + |
135 | + def __init__(self, container_id): |
136 | + super().__init__(container_id) |
137 | self.container = lxc_container(container_id) |
138 | self.series = get_container_distro(container_id) |
139 | |
140 | + def is_running(self): |
141 | + return self.container.running |
142 | + |
143 | + def start_container(self): |
144 | + if not self.container.running: |
145 | + if not self.container.start(): |
146 | + raise RuntimeError("Container failed to start") |
147 | + if not self.container.wait("RUNNING", 10): |
148 | + raise RuntimeError("Container failed to enter the RUNNING state") |
149 | + |
150 | + if not self.container.get_ips(timeout=30): |
151 | + raise RuntimeError("Not able to connect to the network.") |
152 | + |
153 | + self.run_in_container("umount /tmp/.X11-unix") |
154 | + |
155 | + def stop_container(self): |
156 | + self.container.stop() |
157 | + |
158 | + def run_in_container(self, command_string): |
159 | + cmd_args = shlex.split(command_string) |
160 | + return self.container.attach_wait(lxc.attach_run_command, cmd_args) |
161 | + |
162 | def destroy_libertine_container(self): |
163 | if self.container.defined: |
164 | self.container.stop() |
165 | self.container.destroy() |
166 | |
167 | - def create_libertine_container(self, password=None): |
168 | + def create_libertine_container(self, password=None, verbosity=1): |
169 | if password is None: |
170 | return |
171 | |
172 | @@ -211,23 +298,21 @@ |
173 | self.create_libertine_config() |
174 | |
175 | if self.container.start(): |
176 | - self.container.attach_wait(lxc.attach_run_command, |
177 | - ["userdel", "-r", "ubuntu"]) |
178 | - |
179 | - self.container.attach_wait(lxc.attach_run_command, |
180 | - ["useradd", "-u", str(user_id), "-p", crypt.crypt(password), |
181 | - "-G", "sudo", str(username)]) |
182 | + self.run_in_container("userdel-r ubuntu") |
183 | + self.run_in_container("useradd -u {} -p {} -G sudo {}".format(str(user_id), crypt.crypt(password), str(username))) |
184 | + |
185 | + if verbosity == 1: |
186 | + print("Updating the contents of the container after creation...") |
187 | + self.update_packages(verbosity) |
188 | + |
189 | + if verbosity == 1: |
190 | + print("Installing Compiz as the Xmir window manager...") |
191 | + self.install_package('compiz', verbosity) |
192 | + create_compiz_config(self.container.name) |
193 | + |
194 | + self.container.stop() |
195 | else: |
196 | - print("Container failed to start.") |
197 | - |
198 | - print("Updating the contents of the container after creation...") |
199 | - self.update_libertine_container() |
200 | - |
201 | - print("Installing Compiz as the Xmir window manager...") |
202 | - self.install_package('compiz') |
203 | - create_compiz_config(self.container_id) |
204 | - |
205 | - self.container.stop() |
206 | + raise RuntimeError("Container failed to start.") |
207 | |
208 | def create_libertine_config(self): |
209 | user_id = os.getuid() |
210 | @@ -261,82 +346,35 @@ |
211 | # Dump it all to disk |
212 | self.container.save_config() |
213 | |
214 | - def update_libertine_container(self): |
215 | - # Update packages inside the LXC |
216 | - stop_container = not self.container.running |
217 | - |
218 | - if not start_container_for_update(self.container): |
219 | - return (False, "Container did not start") |
220 | - |
221 | - print("Updating packages inside the LXC...") |
222 | - |
223 | - self.container.attach_wait(lxc.attach_run_command, |
224 | - ["apt-get", "dist-upgrade", "-y"]) |
225 | - |
226 | - # Stopping the container |
227 | - if stop_container: |
228 | - self.container.stop() |
229 | - |
230 | - def install_package(self, package_name): |
231 | - stop_container = False |
232 | - |
233 | - if not self.container.running: |
234 | - if not start_container_for_update(self.container): |
235 | - return (False, "Container did not start") |
236 | - stop_container = True |
237 | - |
238 | - retval = self.container.attach_wait(lxc.attach_run_command, |
239 | - ["apt-get", "install", "-y", package_name]) |
240 | - |
241 | - if stop_container: |
242 | - self.container.stop() |
243 | - |
244 | - if retval != 0: |
245 | - return False |
246 | - |
247 | - return True |
248 | - |
249 | - def remove_package(self, package_name): |
250 | - stop_container = False |
251 | - |
252 | - if not self.container.running: |
253 | - if not start_container_for_update(self.container): |
254 | - return (False, "Container did not start") |
255 | - stop_container = True |
256 | - |
257 | - retval = self.container.attach_wait(lxc.attach_run_command, |
258 | - ["apt-get", "remove", "-y", package_name]) |
259 | - |
260 | - if stop_container: |
261 | - self.container.stop() |
262 | - |
263 | - def search_package_cache(self, search_string): |
264 | - stop_container = False |
265 | - |
266 | - if not self.container.running: |
267 | - if not start_container_for_update(self.container): |
268 | - return (False, "Container did not start") |
269 | - stop_container = True |
270 | - |
271 | - retval = self.container.attach_wait(lxc.attach_run_command, |
272 | - ["apt-cache", "search", search_string]) |
273 | - |
274 | - if stop_container: |
275 | - self.container.stop() |
276 | - |
277 | - |
278 | -class LibertineChroot(object): |
279 | + |
280 | +class LibertineChroot(BaseContainer): |
281 | + """ |
282 | + A concrete container type implemented using a plain old chroot. |
283 | + """ |
284 | + |
285 | def __init__(self, container_id): |
286 | - self.container_id = container_id |
287 | + super().__init__(container_id) |
288 | self.series = get_container_distro(container_id) |
289 | self.chroot_path = libertine.utils.get_libertine_container_rootfs_path(container_id) |
290 | os.environ['FAKECHROOT_CMD_SUBST'] = '$FAKECHROOT_CMD_SUBST:/usr/bin/chfn=/bin/true' |
291 | os.environ['DEBIAN_FRONTEND'] = 'noninteractive' |
292 | |
293 | + def run_in_container(self, command_string): |
294 | + cmd_args = shlex.split(command_string) |
295 | + if self.series == "trusty": |
296 | + proot_cmd = '/usr/bin/proot' |
297 | + if not os.path.isfile(proot_cmd) or not os.access(proot_cmd, os.X_OK): |
298 | + raise RuntimeError('executable proot not found') |
299 | + command_prefix = proot_cmd + " -b /usr/lib/locale -S " + self.chroot_path |
300 | + else: |
301 | + command_prefix = "fakechroot fakeroot chroot " + self.chroot_path |
302 | + args = shlex.split(command_prefix + ' ' + command_string) |
303 | + cmd = subprocess.Popen(args).wait() |
304 | + |
305 | def destroy_libertine_container(self): |
306 | shutil.rmtree(self.chroot_path) |
307 | |
308 | - def create_libertine_container(self, password=None): |
309 | + def create_libertine_container(self, password=None, verbosity=1): |
310 | installed_release = self.series |
311 | |
312 | # Create the actual chroot |
313 | @@ -370,7 +408,8 @@ |
314 | else: |
315 | archive = "deb http://archive.ubuntu.com/ubuntu " |
316 | |
317 | - print("Updating chroot's sources.list entries...") |
318 | + if verbosity == 1: |
319 | + print("Updating chroot's sources.list entries...") |
320 | with open(os.path.join(self.chroot_path, 'etc', 'apt', 'sources.list'), 'a') as fd: |
321 | fd.write(archive + installed_release + " universe\n") |
322 | fd.write(archive + installed_release + "-updates main\n") |
323 | @@ -418,97 +457,58 @@ |
324 | args = shlex.split(command_line) |
325 | cmd = subprocess.Popen(args).wait() |
326 | |
327 | - print("Updating the contents of the container after creation...") |
328 | - self.update_libertine_container() |
329 | - |
330 | - self.install_package("libnss-extrausers") |
331 | - |
332 | - print("Installing Compiz as the Xmir window manager...") |
333 | - self.install_package("compiz") |
334 | + if verbosity == 1: |
335 | + print("Updating the contents of the container after creation...") |
336 | + self.update_packages(verbosity) |
337 | + self.install_package("libnss-extrausers", verbosity) |
338 | + |
339 | + if verbosity == 1: |
340 | + print("Installing Compiz as the Xmir window manager...") |
341 | + self.install_package("compiz", verbosity) |
342 | create_compiz_config(self.container_id) |
343 | |
344 | # Check if the container was created as root and chown the user directories as necessary |
345 | chown_recursive_dirs(libertine.utils.get_libertine_container_userdata_dir_path(self.container_id)) |
346 | |
347 | - def update_libertine_container(self): |
348 | - if self.series == "trusty": |
349 | - proot_cmd = '/usr/bin/proot' |
350 | - if not os.path.isfile(proot_cmd) or not os.access(proot_cmd, os.X_OK): |
351 | - raise RuntimeError('executable proot not found') |
352 | - command_line = proot_cmd + " -b /usr/lib/locale -S " + self.chroot_path + " apt-get update" |
353 | - else: |
354 | - command_line = "fakechroot fakeroot chroot " + self.chroot_path + " /usr/bin/apt-get update" |
355 | - args = shlex.split(command_line) |
356 | - cmd = subprocess.Popen(args).wait() |
357 | - |
358 | - if self.series == "trusty": |
359 | - proot_cmd = '/usr/bin/proot' |
360 | - if not os.path.isfile(proot_cmd) or not os.access(proot_cmd, os.X_OK): |
361 | - raise RuntimeError('executable proot not found') |
362 | - command_line = proot_cmd + " -b /usr/lib/locale -S " + self.chroot_path + " apt-get dist-upgrade -y" |
363 | - else: |
364 | - command_line = "fakechroot fakeroot chroot " + self.chroot_path + " /usr/bin/apt-get dist-upgrade -y" |
365 | - args = shlex.split(command_line) |
366 | - cmd = subprocess.Popen(args).wait() |
367 | - |
368 | - def install_package(self, package_name): |
369 | - if self.series == "trusty": |
370 | - proot_cmd = '/usr/bin/proot' |
371 | - if not os.path.isfile(proot_cmd) or not os.access(proot_cmd, os.X_OK): |
372 | - raise RuntimeError('executable proot not found') |
373 | - command_line = proot_cmd + " -b /usr/lib/locale -S " + self.chroot_path + " apt-get install -y " + package_name |
374 | - else: |
375 | - command_line = "fakechroot fakeroot chroot " + self.chroot_path + " /usr/bin/apt-get install -y " + package_name |
376 | - args = shlex.split(command_line) |
377 | - cmd = subprocess.Popen(args) |
378 | - cmd.wait() |
379 | - |
380 | - if cmd.returncode != 0: |
381 | - return False |
382 | - else: |
383 | - return True |
384 | - |
385 | - def remove_package(self, package_name): |
386 | - command_line = "fakechroot fakeroot chroot " + self.chroot_path + " /usr/bin/apt-get remove -y " + package_name |
387 | - args = shlex.split(command_line) |
388 | - cmd = subprocess.Popen(args) |
389 | - cmd.wait() |
390 | - |
391 | - def search_package_cache(self, search_string): |
392 | - command_line = "fakechroot fakeroot chroot " + self.chroot_path + " /usr/bin/apt-cache search " + search_string |
393 | - args = shlex.split(command_line) |
394 | - cmd = subprocess.Popen(args) |
395 | - cmd.wait() |
396 | - |
397 | - |
398 | -class LibertineMock(object): |
399 | + |
400 | +class LibertineMock(BaseContainer): |
401 | + """ |
402 | + A concrete mock container type. Used for unit testing. |
403 | + """ |
404 | def __init__(self, container_id): |
405 | - self.container_id = container_id |
406 | - |
407 | - def destroy_libertine_container(self): |
408 | - return True |
409 | - |
410 | - def create_libertine_container(self, password=None): |
411 | - return True |
412 | - |
413 | - def update_libertine_container(self): |
414 | - return True |
415 | - |
416 | - def install_package(self, package_name): |
417 | - return True |
418 | - |
419 | - def remove_package(self, package_name): |
420 | - return True |
421 | - |
422 | - def search_package_cache(self, search_string): |
423 | - return True |
424 | + super().__init__(container_id) |
425 | + |
426 | + def run_in_container(self, command_string): |
427 | + return 0 |
428 | + |
429 | + |
430 | +class ContainerRunning(contextlib.ExitStack): |
431 | + """ |
432 | + Helper object providing a running container context. |
433 | + |
434 | + Starts the container running if it's not already running, and shuts it down |
435 | + when the context is destroyed if it was not running at context creation. |
436 | + """ |
437 | + def __init__(self, container): |
438 | + super().__init__() |
439 | + if not container.is_running(): |
440 | + container.start_container() |
441 | + self.callback(lambda: container.stop_container()) |
442 | |
443 | |
444 | class LibertineContainer(object): |
445 | """ |
446 | A sandbox for DEB-packaged X11-based applications. |
447 | """ |
448 | + |
449 | def __init__(self, container_id, container_type="lxc"): |
450 | + """ |
451 | + Initializes the container object. |
452 | + |
453 | + :param container_id: The machine-readable container name. |
454 | + :param container_type: One of the supported container types (lxc, |
455 | + chroot, mock). |
456 | + """ |
457 | super().__init__() |
458 | if container_type == "lxc": |
459 | self.container = LibertineLXC(container_id) |
460 | @@ -517,22 +517,50 @@ |
461 | elif container_type == "mock": |
462 | self.container = LibertineMock(container_id) |
463 | else: |
464 | - print("Unsupported container type %s" % container_type) |
465 | + raise RuntimeError("Unsupported container type %s" % container_type) |
466 | |
467 | def destroy_libertine_container(self): |
468 | + """ |
469 | + Destroys the container and releases all its system resources. |
470 | + """ |
471 | self.container.destroy_libertine_container() |
472 | |
473 | - def create_libertine_container(self, password=None): |
474 | - self.container.create_libertine_container(password) |
475 | - |
476 | - def update_libertine_container(self): |
477 | - self.container.update_libertine_container() |
478 | - |
479 | - def install_package(self, package_name): |
480 | - return self.container.install_package(package_name) |
481 | - |
482 | - def remove_package(self, package_name): |
483 | - self.container.remove_package(package_name) |
484 | + def create_libertine_container(self, password=None, verbosity=1): |
485 | + """ |
486 | + Creates the container. |
487 | + """ |
488 | + self.container.create_libertine_container(password, verbosity) |
489 | + |
490 | + def update_libertine_container(self, verbosity=1): |
491 | + """ |
492 | + Updates the contents of the container. |
493 | + """ |
494 | + with ContainerRunning(self.container): |
495 | + self.container.update_packages(verbosity) |
496 | + |
497 | + def install_package(self, package_name, verbosity=1): |
498 | + """ |
499 | + Installs a package in the container. |
500 | + """ |
501 | + with ContainerRunning(self.container): |
502 | + return self.container.install_package(package_name, verbosity) |
503 | + |
504 | + def remove_package(self, package_name, verbosity=1): |
505 | + """ |
506 | + Removes a package from the container. |
507 | + |
508 | + :param package_name: The name of the package to be removed. |
509 | + :param verbosity: The verbosity level of the progress reporting. |
510 | + """ |
511 | + with ContainerRunning(self.container): |
512 | + self.container.run_in_container(apt_command_prefix(verbosity) + "remove '" + package_name + "'") == 0 |
513 | |
514 | def search_package_cache(self, search_string): |
515 | - self.container.search_package_cache(search_string) |
516 | + """ |
517 | + Searches the container's package cache for a named package. |
518 | + |
519 | + :param search_string: the (regex) to use to search the package cache. |
520 | + The regex is quoted to sanitize it. |
521 | + """ |
522 | + with ContainerRunning(self.container): |
523 | + self.container.run_in_container("/usr/bin/apt-cache search '" + search_string + "'") |
524 | |
525 | === modified file 'tools/libertine-container-manager' |
526 | --- tools/libertine-container-manager 2015-11-23 20:04:30 +0000 |
527 | +++ tools/libertine-container-manager 2015-12-01 12:40:06 +0000 |
528 | @@ -263,7 +263,7 @@ |
529 | |
530 | container = LibertineContainer(args.id, args.type) |
531 | update_container_install_status(args.id, "installing") |
532 | - container.create_libertine_container(password) |
533 | + container.create_libertine_container(password, args.verbosity) |
534 | update_container_install_status(args.id, "ready") |
535 | |
536 | |
537 | @@ -295,7 +295,7 @@ |
538 | container = LibertineContainer(args.id, get_container_type(args.id)) |
539 | |
540 | update_package_install_status(args.id, args.package, "installing") |
541 | - if not container.install_package(args.package): |
542 | + if not container.install_package(args.package, args.verbosity): |
543 | delete_package(args.id, args.package) |
544 | sys.exit(1) |
545 | |
546 | @@ -314,7 +314,7 @@ |
547 | sys.exit(1) |
548 | |
549 | container = LibertineContainer(args.id, get_container_type(args.id)) |
550 | - container.remove_package(args.package) |
551 | + container.remove_package(args.package, args.verbosity) |
552 | |
553 | delete_package(args.id, args.package) |
554 | |
555 | @@ -338,7 +338,7 @@ |
556 | args.id = get_default_container_id() |
557 | |
558 | container = LibertineContainer(args.id, get_container_type(args.id)) |
559 | - container.update_libertine_container() |
560 | + container.update_libertine_container(args.verbosity) |
561 | |
562 | |
563 | def list(args): |
564 | @@ -349,6 +349,12 @@ |
565 | |
566 | if __name__ == '__main__': |
567 | parser = argparse.ArgumentParser(description="Legacy X application support for Unity 8") |
568 | + parser.add_argument('-q', '--quiet', |
569 | + action='store_const', dest='verbosity', const=0, |
570 | + help=('do not print status updates on stdout')) |
571 | + parser.add_argument('-v', '--verbose', |
572 | + action='store_const', dest='verbosity', const=2, |
573 | + help=('extra verbose output')) |
574 | subparsers = parser.add_subparsers(dest="subparser_name") |
575 | |
576 | # Handle the create command and its options |
577 | @@ -434,4 +440,7 @@ |
578 | parser_list.set_defaults(func=list) |
579 | |
580 | args = parser.parse_args() |
581 | + if args.verbosity is None: |
582 | + args.verbosity = 1 |
583 | + |
584 | args.func(args) |
May as well mention this MP contains a couple of bugs that were fixed in the follow-on MP https:/ /code.launchpad .net/~bregma/ libertine/ refactor- container- back-ends/ +merge/ 278985 -- in particular, a missing space in the userdel command in the LXC back end and a missing wait while starting the LXC container.