Merge lp:~townsend/libertine/release-1.5 into lp:libertine/trunk

Proposed by Christopher Townsend
Status: Merged
Approved by: Larry Price
Approved revision: 182
Merged at revision: 179
Proposed branch: lp:~townsend/libertine/release-1.5
Merge into: lp:libertine/trunk
Diff against target: 3383 lines (+1572/-672)
45 files modified
.bzrignore (+8/-0)
CMakeLists.txt (+1/-1)
common/ContainerConfigList.h (+1/-2)
data/CMakeLists.txt (+4/-6)
data/com.canonical.libertine.LxdManager.service (+3/-0)
data/libertine-lxd-sudo (+1/-0)
data/python3-libertine-chroot.click-hook.in (+0/-4)
data/snap-runner.wrapper (+11/-0)
debian/changelog (+20/-0)
debian/control (+17/-4)
debian/python3-libertine-chroot.install (+0/-1)
debian/python3-libertine-lxd.install (+5/-0)
debian/python3-libertine.install (+1/-0)
debian/rules (+1/-1)
liblibertine/libertine.cpp (+41/-26)
parts/plugins/utils.py (+63/-0)
parts/plugins/x-libertine-deps.py (+155/-0)
parts/plugins/x-libertine.py (+45/-0)
python/CMakeLists.txt (+1/-1)
python/libertine/AppDiscovery.py (+1/-3)
python/libertine/ChrootContainer.py (+30/-31)
python/libertine/ContainersConfig.py (+3/-3)
python/libertine/HostInfo.py (+5/-0)
python/libertine/Libertine.py (+113/-125)
python/libertine/LxcContainer.py (+67/-77)
python/libertine/LxdContainer.py (+455/-0)
python/libertine/launcher/session.py (+10/-13)
python/libertine/launcher/task.py (+3/-3)
python/libertine/lifecycle/ContainerLifecycleService.py (+108/-0)
python/libertine/lifecycle/ContainerLifecycleServiceRunner.py (+46/-0)
python/libertine/lifecycle/LifecycleResult.py (+37/-0)
python/libertine/lifecycle/__init__.py (+23/-0)
python/libertine/utils.py (+24/-3)
snapcraft.yaml (+32/-0)
tests/unit/test_libertine_gir.py (+2/-4)
tests/unit/test_logger.py (+5/-1)
tools/CMakeLists.txt (+4/-5)
tools/libertine-container-manager (+55/-44)
tools/libertine-container-manager.1 (+8/-2)
tools/libertine-lxc-manager (+46/-123)
tools/libertine-lxd-manager (+57/-0)
tools/libertine-lxd-manager.1 (+9/-0)
tools/libertine-lxd-setup (+40/-0)
tools/libertined (+11/-6)
tools/update-puritine-containers (+0/-183)
To merge this branch: bzr merge lp:~townsend/libertine/release-1.5
Reviewer Review Type Date Requested Status
Larry Price Approve
Review via email: mp+314179@code.launchpad.net

Commit message

* Drop support for the Puritine click package as that is Vivid only and a Vivid Libertine branch exists for any future fixes.
* Only set the lxc log when a container is defined during class init. (LP: #1653973)
* Bump version to 1.5 for new upstream release.
* Logic for bundling libertine as a snap built from source.
* Catch exceptions raised during container creation.
* Initial implementation of lxd backend. (LP: #1580612)
* Use the libertine logger and LIBERTINE_DEBUG variable everywhere.
* Update configure bind-mount logic given new lxd backend.
* Use dpkg to find package name when installing local deb.
* Create d-bus service for lxd container management.

To post a comment you must log in.
lp:~townsend/libertine/release-1.5 updated
181. By Christopher Townsend

Update the version for the snap package as well.

182. By Larry Price

Merge from lp:libertine for fixes during testing and review.

Revision history for this message
Larry Price (larryprice) wrote :

okok let's do this

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2016-10-25 17:27:50 +0000
3+++ .bzrignore 2017-01-05 20:19:05 +0000
4@@ -2,3 +2,11 @@
5 po/Makefile.in.in
6 __pycache__
7 tests/unit/.cache
8+*.click-hook
9+
10+prime/
11+stage/
12+*.snap
13+parts/*
14+!parts/plugins
15+parts/plugins/__pycache__
16
17=== modified file 'CMakeLists.txt'
18--- CMakeLists.txt 2016-11-03 18:28:54 +0000
19+++ CMakeLists.txt 2017-01-05 20:19:05 +0000
20@@ -2,7 +2,7 @@
21 cmake_policy(SET CMP0048 NEW)
22
23 project(libertine
24- VERSION 1.4.3)
25+ VERSION 1.5)
26
27 set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")
28
29
30=== modified file 'common/ContainerConfigList.h'
31--- common/ContainerConfigList.h 2016-09-26 18:17:07 +0000
32+++ common/ContainerConfigList.h 2017-01-05 20:19:05 +0000
33@@ -55,11 +55,10 @@
34 * Display roles for a container config.
35 */
36 enum class DataRole
37- : int
38 {
39 ContainerId = Qt::UserRole + 1, /**< The container ID */
40 ContainerName, /**< The container name */
41- ContainerType, /**< The type of container - lxc or chroot */
42+ ContainerType, /**< The type of container */
43 DistroSeries, /**< The distro from which the container was built */
44 InstallStatus, /**< Current container install status */
45 Error /**< last role (error) */
46
47=== modified file 'data/CMakeLists.txt'
48--- data/CMakeLists.txt 2016-11-01 20:10:58 +0000
49+++ data/CMakeLists.txt 2017-01-05 20:19:05 +0000
50@@ -6,11 +6,9 @@
51 DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME})
52 install(FILES libertine-xmir.conf
53 DESTINATION ${CMAKE_INSTALL_DATADIR}/upstart/sessions)
54-install(FILES libertine-lxc-sudo
55+install(FILES libertine-lxc-sudo libertine-lxd-sudo
56 DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sudoers.d)
57-install(FILES com.canonical.libertine.LxcManager.service com.canonical.libertine.ContainerManager.service
58+install(FILES com.canonical.libertine.ContainerManager.service
59+ com.canonical.libertine.LxcManager.service
60+ com.canonical.libertine.LxdManager.service
61 DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services)
62-
63-configure_file("python3-libertine-chroot.click-hook.in"
64- "${CMAKE_SOURCE_DIR}/debian/python3-libertine-chroot.click-hook"
65- @ONLY)
66
67=== added file 'data/com.canonical.libertine.LxdManager.service'
68--- data/com.canonical.libertine.LxdManager.service 1970-01-01 00:00:00 +0000
69+++ data/com.canonical.libertine.LxdManager.service 2017-01-05 20:19:05 +0000
70@@ -0,0 +1,3 @@
71+[D-BUS Service]
72+Name=com.canonical.libertine.LxdManager
73+Exec=/usr/bin/libertine-lxd-manager
74
75=== added file 'data/libertine-lxd-sudo'
76--- data/libertine-lxd-sudo 1970-01-01 00:00:00 +0000
77+++ data/libertine-lxd-sudo 2017-01-05 20:19:05 +0000
78@@ -0,0 +1,1 @@
79+ALL ALL=(ALL) NOPASSWD:/usr/bin/libertine-lxd-setup
80
81=== removed file 'data/python3-libertine-chroot.click-hook.in'
82--- data/python3-libertine-chroot.click-hook.in 2016-09-06 12:36:47 +0000
83+++ data/python3-libertine-chroot.click-hook.in 1970-01-01 00:00:00 +0000
84@@ -1,4 +0,0 @@
85-Pattern: ${home}/.cache/libertine/puritine/${id}
86-Exec: @CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@/libertine/update-puritine-containers
87-User-Level: yes
88-Hook-Name: puritine
89
90=== added file 'data/snap-runner.wrapper'
91--- data/snap-runner.wrapper 1970-01-01 00:00:00 +0000
92+++ data/snap-runner.wrapper 2017-01-05 20:19:05 +0000
93@@ -0,0 +1,11 @@
94+#!/bin/bash
95+# additional env modifications on top of desktop-launch
96+
97+export LD_LIBRARY_PATH=$SNAP/usr/lib/$ARCH/fakechroot:$LD_LIBRARY_PATH
98+export DEBOOTSTRAP_DIR=$SNAP/usr/share/debootstrap
99+
100+# Useful debug variables
101+# export FAKECHROOT_DEBUG=1
102+# export LIBERTINE_DEBUG=1
103+
104+exec $SNAP/bin/desktop-launch $@
105
106=== modified file 'debian/changelog'
107--- debian/changelog 2016-12-05 21:10:03 +0000
108+++ debian/changelog 2017-01-05 20:19:05 +0000
109@@ -1,3 +1,23 @@
110+libertine (1.5-0ubuntu1) UNRELEASED; urgency=medium
111+
112+ [ Chris Townsend ]
113+ * Drop support for the Puritine click package as that is Vivid only
114+ and a Vivid Libertine branch exists for any future fixes.
115+ * Only set the lxc log when a container is defined during class init.
116+ (LP: #1653973)
117+ * Bump version to 1.5 for new upstream release.
118+
119+ [ Larry Price ]
120+ * Logic for bundling libertine as a snap built from source.
121+ * Catch exceptions raised during container creation.
122+ * Initial implementation of lxd backend. (LP: #1580612)
123+ * Use the libertine logger and LIBERTINE_DEBUG variable everywhere.
124+ * Update configure bind-mount logic given new lxd backend.
125+ * Use dpkg to find package name when installing local deb.
126+ * Create d-bus service for lxd container management.
127+
128+ -- Chris Townsend <christopher.townsend@canonical.com> Thu, 05 Jan 2017 11:46:42 -0500
129+
130 libertine (1.4.4+17.04.20161205-0ubuntu1) zesty; urgency=medium
131
132 [ Chris Townsend ]
133
134=== modified file 'debian/control'
135--- debian/control 2016-11-22 17:47:05 +0000
136+++ debian/control 2017-01-05 20:19:05 +0000
137@@ -2,8 +2,7 @@
138 Section: utils
139 Priority: extra
140 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
141-Build-Depends: click-dev,
142- cmake,
143+Build-Depends: cmake,
144 cmake-extras,
145 debhelper (>= 9),
146 dh-translations,
147@@ -61,8 +60,7 @@
148 Depends: libertine-qt-common,
149 libertine-tools,
150 python3-libertine-lxc,
151- qml-module-qtquick2,
152- qtdeclarative5-ubuntu-ui-toolkit-plugin,
153+ libsystemsettings1,
154 ${misc:Depends},
155 ${shlibs:Depends}
156 Enhances: ubuntu-system-settings
157@@ -184,6 +182,21 @@
158 Libertine sandbox. It requires support for unprivileged LXC containers in the
159 Linux kernel.
160
161+Package: python3-libertine-lxd
162+Architecture: any
163+Section: python
164+Multi-Arch: allowed
165+Depends: lxd,
166+ python3-libertine,
167+ python3-pexpect,
168+ python3-pylxd,
169+ ${misc:Depends},
170+ ${python3:Depends}
171+Description: Python3 scripts for the Libertine application sandbox
172+ This package provides the LXD-based container back end module for the
173+ Libertine sandbox. It requires support for unprivileged LXD containers in the
174+ Linux kernel.
175+
176 Package: python3-libertine-chroot
177 Architecture: any
178 Section: python
179
180=== modified file 'debian/python3-libertine-chroot.install'
181--- debian/python3-libertine-chroot.install 2016-10-07 21:09:41 +0000
182+++ debian/python3-libertine-chroot.install 2017-01-05 20:19:05 +0000
183@@ -1,2 +1,1 @@
184-usr/lib/*/libertine/update-puritine-containers
185 usr/lib/python*/*/libertine/ChrootContainer.py
186
187=== added file 'debian/python3-libertine-lxd.install'
188--- debian/python3-libertine-lxd.install 1970-01-01 00:00:00 +0000
189+++ debian/python3-libertine-lxd.install 2017-01-05 20:19:05 +0000
190@@ -0,0 +1,5 @@
191+etc/sudoers.d/libertine-lxd-sudo
192+usr/bin/libertine-lxd-setup
193+usr/bin/libertine-lxd-manager
194+usr/lib/python*/*/libertine/LxdContainer.py
195+usr/share/dbus-1/services/com.canonical.libertine.LxdManager.service
196
197=== modified file 'debian/python3-libertine.install'
198--- debian/python3-libertine.install 2016-10-28 19:37:32 +0000
199+++ debian/python3-libertine.install 2017-01-05 20:19:05 +0000
200@@ -4,5 +4,6 @@
201 usr/lib/python*/*/libertine/Libertine.py
202 usr/lib/python*/*/libertine/__init__.py
203 usr/lib/python*/*/libertine/launcher
204+usr/lib/python*/*/libertine/lifecycle
205 usr/lib/python*/*/libertine/service
206 usr/lib/python*/*/libertine/utils.py
207
208=== modified file 'debian/rules'
209--- debian/rules 2016-12-05 15:32:46 +0000
210+++ debian/rules 2017-01-05 20:19:05 +0000
211@@ -1,7 +1,7 @@
212 #!/usr/bin/make -f
213
214 %:
215- dh $@ --with python3,gir,click
216+ dh $@ --with python3,gir
217
218 override_dh_auto_test:
219 dbus-run-session -- dh_auto_test
220
221=== modified file 'liblibertine/libertine.cpp'
222--- liblibertine/libertine.cpp 2016-10-26 13:58:19 +0000
223+++ liblibertine/libertine.cpp 2017-01-05 20:19:05 +0000
224@@ -67,8 +67,16 @@
225 g_dir_close(dir);
226 return nullptr;
227 }
228-}
229-
230+
231+
232+gchar*
233+id_from_list_index(const ContainerConfigList& container_list, guint index)
234+{
235+ return (gchar*)container_list.data(container_list.index(index, 0),
236+ (int)ContainerConfigList::DataRole::ContainerId)
237+ .toString().toStdString().c_str();
238+}
239+}
240
241 gchar**
242 libertine_list_apps_for_container(const gchar* container_id)
243@@ -137,61 +145,68 @@
244 gchar *
245 libertine_container_path(const gchar * container_id)
246 {
247+ g_return_val_if_fail(container_id != nullptr, nullptr);
248+ LibertineConfig config;
249+ ContainerConfigList container_list(&config);
250 gchar * path = nullptr;
251- g_return_val_if_fail(container_id != nullptr, nullptr);
252
253- path = g_build_filename(g_get_user_cache_dir(), "libertine-container", container_id, "rootfs", nullptr);
254+ if (g_strcmp0((gchar*)container_list.getContainerType(container_id).toStdString().c_str(), "lxd") == 0)
255+ {
256+ path = g_build_filename("/", "var", "lib", "lxd", "containers", container_id, "rootfs", nullptr);
257+ }
258+ else
259+ {
260+ path = g_build_filename(g_get_user_cache_dir(), "libertine-container", container_id, "rootfs", nullptr);
261+ }
262
263 if (g_file_test(path, G_FILE_TEST_EXISTS))
264 {
265 return path;
266 }
267- else
268- {
269- g_free(path);
270- return nullptr;
271- }
272+
273+ g_free(path);
274+ return nullptr;
275 }
276
277
278 gchar *
279 libertine_container_home_path(const gchar * container_id)
280 {
281+ g_return_val_if_fail(container_id != nullptr, nullptr);
282+ LibertineConfig config;
283+ ContainerConfigList container_list(&config);
284 gchar * path = nullptr;
285- g_return_val_if_fail(container_id != nullptr, nullptr);
286
287- path = g_build_filename(g_get_user_data_dir(), "libertine-container", "user-data", container_id, nullptr);
288+ if (g_strcmp0((gchar*)container_list.getContainerType(container_id).toStdString().c_str(), "lxd") == 0)
289+ {
290+ path = g_build_filename("/", "var", "lib", "lxd", "containers", container_id, "rootfs", "home", g_get_user_name(), nullptr);
291+ }
292+ else
293+ {
294+ path = g_build_filename(g_get_user_data_dir(), "libertine-container", "user-data", container_id, nullptr);
295+ }
296
297 if (g_file_test(path, G_FILE_TEST_EXISTS))
298 {
299 return path;
300 }
301- else
302- {
303- g_free(path);
304- return nullptr;
305- }
306
307+ g_free(path);
308+ return nullptr;
309 }
310
311
312 gchar *
313 libertine_container_name(const gchar * container_id)
314 {
315- guint container_count;
316- guint i;
317 gchar * container_name = nullptr;
318 LibertineConfig config;
319 ContainerConfigList container_list(&config);
320- QVariant id;
321-
322- container_count = (guint)container_list.size();
323-
324- for (i = 0; i < container_count; ++i)
325+ guint container_count = (guint)container_list.size();
326+
327+ for (guint i = 0; i < container_count; ++i)
328 {
329- id = container_list.data(container_list.index(i, 0), (int)ContainerConfigList::DataRole::ContainerId);
330-
331- if (g_strcmp0((gchar *)id.toString().toStdString().c_str(), container_id) == 0)
332+ if (g_strcmp0(id_from_list_index(container_list, i), container_id) == 0)
333 {
334 QVariant name = container_list.data(container_list.index(i, 0), (int)ContainerConfigList::DataRole::ContainerName);
335 container_name = g_strdup(name.toString().toStdString().c_str());
336
337=== added directory 'parts'
338=== added directory 'parts/plugins'
339=== added file 'parts/plugins/utils.py'
340--- parts/plugins/utils.py 1970-01-01 00:00:00 +0000
341+++ parts/plugins/utils.py 2017-01-05 20:19:05 +0000
342@@ -0,0 +1,63 @@
343+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
344+#
345+# Copyright (C) 2016 Canonical Ltd
346+#
347+# This program is free software: you can redistribute it and/or modify
348+# it under the terms of the GNU General Public License version 3 as
349+# published by the Free Software Foundation.
350+#
351+# This program is distributed in the hope that it will be useful,
352+# but WITHOUT ANY WARRANTY; without even the implied warranty of
353+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
354+# GNU General Public License for more details.
355+#
356+# You should have received a copy of the GNU General Public License
357+# along with this program. If not, see <http://www.gnu.org/licenses/>.
358+
359+import re
360+from snapcraft import file_utils
361+
362+
363+def fix_shebangs(path):
364+ file_utils.replace_in_file(path, re.compile(r''),
365+ re.compile(r'^#!.*python'),
366+ r'#!/usr/bin/env python')
367+
368+
369+def _sanitize(dep):
370+ return re.sub(r"\(.*\)", "", re.sub(r"\[.*\]", "", dep)).replace(',', '').strip()
371+
372+
373+class DependsParser(object):
374+ def __init__(self):
375+ self.keyword = 'Depends:'
376+ self._parsing = False
377+ self._deps = []
378+ self._packages = []
379+
380+ @property
381+ def deps(self):
382+ return [dep for dep in self._deps if dep not in self._packages]
383+
384+ def parse(self, line):
385+ if self._parsing:
386+ if ':' in line:
387+ if not line.strip().startswith('${'):
388+ self._parsing = False
389+ else:
390+ self._deps.append(_sanitize(line))
391+
392+ if line.startswith(self.keyword):
393+ self._parsing = True
394+ possible_dep = line.lstrip(self.keyword)
395+ if not possible_dep.isspace() and not possible_dep.strip().startswith('${'):
396+ self._deps.append(_sanitize(possible_dep))
397+
398+ if line.startswith('Package:'):
399+ self._packages.append(line.lstrip('Package:').strip())
400+
401+
402+class BuildDependsParser(DependsParser):
403+ def __init__(self):
404+ super().__init__()
405+ self.keyword = 'Build-Depends:'
406
407=== added file 'parts/plugins/x-libertine-deps.py'
408--- parts/plugins/x-libertine-deps.py 1970-01-01 00:00:00 +0000
409+++ parts/plugins/x-libertine-deps.py 2017-01-05 20:19:05 +0000
410@@ -0,0 +1,155 @@
411+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
412+#
413+# Copyright (C) 2016 Canonical Ltd
414+#
415+# This program is free software: you can redistribute it and/or modify
416+# it under the terms of the GNU General Public License version 3 as
417+# published by the Free Software Foundation.
418+#
419+# This program is distributed in the hope that it will be useful,
420+# but WITHOUT ANY WARRANTY; without even the implied warranty of
421+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
422+# GNU General Public License for more details.
423+#
424+# You should have received a copy of the GNU General Public License
425+# along with this program. If not, see <http://www.gnu.org/licenses/>.
426+
427+
428+import fileinput
429+import os
430+import re
431+import shlex
432+import snapcraft.plugins.nil
433+import sys
434+import subprocess
435+import utils # local
436+
437+
438+def _arch():
439+ cmd = subprocess.Popen(shlex.split('dpkg-architecture -qDEB_HOST_ARCH_CPU'), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
440+ out, err = cmd.communicate()
441+ arch = str(out, 'utf-8').strip()
442+ if arch == 'amd64':
443+ return 'x86_64-linux-gnu'
444+ elif arch == 'armhf':
445+ return 'arm-linux-gnueabihf'
446+ elif arch == 'arm64':
447+ return 'aarch64-linux-gnu'
448+
449+ return '{}-linux-gnu'.format(arch)
450+
451+
452+class LibertineDependenciesPlugin(snapcraft.plugins.nil.NilPlugin):
453+ def __init__(self, name, options, project):
454+ super().__init__(name, options, project)
455+ self._arch = _arch()
456+
457+ deps_parser = utils.DependsParser()
458+ with open('debian/control') as control:
459+ for line in control.readlines():
460+ deps_parser.parse(line)
461+ self.stage_packages.extend(deps_parser.deps)
462+
463+ self._ignore_duplicate_files()
464+
465+ @classmethod
466+ def schema(cls):
467+ return super().schema()
468+
469+ def enable_cross_compilation(self):
470+ return super().enable_cross_compilation()
471+
472+ def env(self, root):
473+ return super().env(root) + ['ARCH={}'.format(self._arch)]
474+
475+ # By design, snapcraft ignores all pre- and post-inst scripts which
476+ # result in a bunch of packages that just don't work.
477+ def _run_preinst_postinst(self):
478+ # We need to create the lxc-usernet
479+ if not os.path.exists(self.installdir + '/etc/lxc/lxc-usernet'):
480+ with open(self.installdir + '/etc/lxc/lxc-usernet', 'w') as f:
481+ f.write('# USERNAME TYPE BRIDGE COUNT')
482+
483+ # We need to run update-alternatives to create /usr/bin/fakeroot
484+ admindir = self.installdir + '/var/lib/dpkg/alternatives'
485+ altdir = self.installdir + '/etc/alternatives'
486+ os.makedirs(admindir, exist_ok=True)
487+ os.makedirs(altdir, exist_ok=True)
488+ subprocess.Popen(shlex.split('\
489+ update-alternatives --admindir {0} --altdir {1} --install \
490+ {2}/usr/bin/fakeroot fakeroot {2}/usr/bin/fakeroot-sysv 50 \
491+ --slave {2}/usr/share/man/man1/fakeroot.1.gz \
492+ fakeroot.1.gz {2}/usr/share/man/man1/fakeroot-sysv.1.gz \
493+ --slave {2}/usr/share/man/man1/faked.1.gz \
494+ faked.1.gz {2}/usr/share/man/man1/faked-sysv.1.gz \
495+ --slave {2}/usr/share/man/es/man1/fakeroot.1.gz \
496+ fakeroot.es.1.gz {2}/usr/share/man/es/man1/fakeroot-sysv.1.gz \
497+ --slave {2}/usr/share/man/es/man1/faked.1.gz \
498+ faked.es.1.gz {2}/usr/share/man/es/man1/faked-sysv.1.gz \
499+ --slave {2}/usr/share/man/fr/man1/fakeroot.1.gz \
500+ fakeroot.fr.1.gz {2}/usr/share/man/fr/man1/fakeroot-sysv.1.gz \
501+ --slave {2}/usr/share/man/fr/man1/faked.1.gz \
502+ faked.fr.1.gz {2}/usr/share/man/fr/man1/faked-sysv.1.gz \
503+ --slave {2}/usr/share/man/sv/man1/fakeroot.1.gz \
504+ fakeroot.sv.1.gz {2}/usr/share/man/sv/man1/fakeroot-sysv.1.gz \
505+ --slave {2}/usr/share/man/sv/man1/faked.1.gz \
506+ faked.sv.1.gz {2}/usr/share/man/sv/man1/faked-sysv.1.gz\
507+ '.format(admindir, altdir, self.installdir))).wait()
508+ subprocess.Popen(shlex.split('\
509+ update-alternatives --admindir {0} --altdir {1} --install \
510+ {2}/usr/bin/fakeroot fakeroot {2}/usr/bin/fakeroot-tcp 30 \
511+ --slave {2}/usr/share/man/man1/fakeroot.1.gz \
512+ fakeroot.1.gz {2}/usr/share/man/man1/fakeroot-tcp.1.gz \
513+ --slave {2}/usr/share/man/man1/faked.1.gz \
514+ faked.1.gz {2}/usr/share/man/man1/faked-tcp.1.gz \
515+ --slave {2}/usr/share/man/es/man1/fakeroot.1.gz \
516+ fakeroot.es.1.gz {2}/usr/share/man/es/man1/fakeroot-tcp.1.gz \
517+ --slave {2}/usr/share/man/es/man1/faked.1.gz \
518+ faked.es.1.gz {2}/usr/share/man/es/man1/faked-tcp.1.gz \
519+ --slave {2}/usr/share/man/fr/man1/fakeroot.1.gz \
520+ fakeroot.fr.1.gz {2}/usr/share/man/fr/man1/fakeroot-tcp.1.gz \
521+ --slave {2}/usr/share/man/fr/man1/faked.1.gz \
522+ faked.fr.1.gz {2}/usr/share/man/fr/man1/faked-tcp.1.gz \
523+ --slave {2}/usr/share/man/sv/man1/fakeroot.1.gz \
524+ fakeroot.sv.1.gz {2}/usr/share/man/sv/man1/fakeroot-tcp.1.gz \
525+ --slave {2}/usr/share/man/sv/man1/faked.1.gz \
526+ faked.sv.1.gz {2}/usr/share/man/sv/man1/faked-tcp.1.gz\
527+ '.format(admindir, altdir, self.installdir))).wait()
528+
529+ def _fix_symlinks(self):
530+ for root, dirs, files in os.walk(self.installdir):
531+ for f in files:
532+ path = '{}/{}'.format(root, f)
533+ if os.path.islink(path):
534+ link = os.readlink(path)
535+ if link.startswith('/'): # needs fixing
536+ os.remove(path)
537+ os.symlink(os.path.relpath(link, root), path)
538+
539+ def _fix_fakeroot(self):
540+ for line in fileinput.FileInput('{}/usr/bin/fakeroot'.format(self.installdir), inplace=True):
541+ if line.startswith('FAKEROOT_PREFIX='):
542+ sys.stdout.write('FAKEROOT_PREFIX=${SNAP}/usr # Updated by x-libertine.py\n')
543+ elif line.startswith('FAKEROOT_BINDIR='):
544+ sys.stdout.write('FAKEROOT_BINDIR=${SNAP}/usr/bin # Updated by x-libertine.py\n')
545+ elif line.startswith('PATHS='):
546+ sys.stdout.write('PATHS=${FAKEROOT_PREFIX}/lib/%s/libfakeroot:' \
547+ '${FAKEROOT_PREFIX}/lib64/libfakeroot:' \
548+ '${FAKEROOT_PREFIX}/lib32/libfakeroot' \
549+ ' # Updated by x-libertine.py\n' % self._arch)
550+ else:
551+ sys.stdout.write(line)
552+
553+ def _ignore_duplicate_files(self):
554+ self.options.stage.extend([
555+ '-usr/lib/{}/liblibertine.so*'.format(self._arch)
556+ ])
557+
558+ def build(self):
559+ super().build()
560+
561+ utils.fix_shebangs(self.installdir + '/usr/bin')
562+
563+ self._run_preinst_postinst()
564+ self._fix_symlinks()
565+ self._fix_fakeroot()
566
567=== added file 'parts/plugins/x-libertine.py'
568--- parts/plugins/x-libertine.py 1970-01-01 00:00:00 +0000
569+++ parts/plugins/x-libertine.py 2017-01-05 20:19:05 +0000
570@@ -0,0 +1,45 @@
571+# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
572+#
573+# Copyright (C) 2016 Canonical Ltd
574+#
575+# This program is free software: you can redistribute it and/or modify
576+# it under the terms of the GNU General Public License version 3 as
577+# published by the Free Software Foundation.
578+#
579+# This program is distributed in the hope that it will be useful,
580+# but WITHOUT ANY WARRANTY; without even the implied warranty of
581+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
582+# GNU General Public License for more details.
583+#
584+# You should have received a copy of the GNU General Public License
585+# along with this program. If not, see <http://www.gnu.org/licenses/>.
586+
587+
588+import snapcraft.plugins.cmake
589+import utils # local
590+
591+class LibertinePlugin(snapcraft.plugins.cmake.CMakePlugin):
592+ def __init__(self, name, options, project):
593+ super().__init__(name, options, project)
594+
595+ deps = utils.BuildDependsParser()
596+
597+ with open('debian/control') as control:
598+ for line in control.readlines():
599+ deps.parse(line)
600+
601+ self.build_packages.extend(deps.deps)
602+ self.options.configflags.extend(['-DCMAKE_INSTALL_PREFIX=/usr'])
603+
604+ @classmethod
605+ def schema(cls):
606+ return super().schema()
607+
608+ def enable_cross_compilation(self):
609+ return super().enable_cross_compilation()
610+
611+ def build(self):
612+ super().build()
613+
614+ # Fix all shebangs to use the in-snap python.
615+ utils.fix_shebangs(self.installdir + '/usr/bin')
616
617=== modified file 'python/CMakeLists.txt'
618--- python/CMakeLists.txt 2015-07-20 15:00:27 +0000
619+++ python/CMakeLists.txt 2017-01-05 20:19:05 +0000
620@@ -1,4 +1,4 @@
621-execute_process(COMMAND python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
622+execute_process(COMMAND /usr/bin/python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
623 OUTPUT_VARIABLE python_site_packages
624 OUTPUT_STRIP_TRAILING_WHITESPACE)
625
626
627=== modified file 'python/libertine/AppDiscovery.py'
628--- python/libertine/AppDiscovery.py 2016-01-04 17:41:08 +0000
629+++ python/libertine/AppDiscovery.py 2017-01-05 20:19:05 +0000
630@@ -188,7 +188,7 @@
631 if desktop_file_is_showable(desktop_entry):
632 yield AppInfo(desktop_file_name, desktop_entry, icon_cache)
633 except Exception as ex:
634- print("error processing {}: {}".format(desktop_file_name, ex), file=sys.stderr)
635+ utils.get_logger().error("error processing {}: {}".format(desktop_file_name, ex))
636
637
638 class AppLauncherCache(object):
639@@ -218,5 +218,3 @@
640 default=lambda o: o.__dict__,
641 sort_keys=True,
642 indent=4)
643-
644-
645
646=== modified file 'python/libertine/ChrootContainer.py'
647--- python/libertine/ChrootContainer.py 2016-12-02 17:46:47 +0000
648+++ python/libertine/ChrootContainer.py 2017-01-05 20:19:05 +0000
649@@ -71,10 +71,10 @@
650 shutil.rmtree(container_root)
651 return True
652 except Exception as e:
653- print("%s" % e)
654+ utils.get_logger().error("%s" % e)
655 return False
656
657- def create_libertine_container(self, password=None, multiarch=False, verbosity=1):
658+ def create_libertine_container(self, password=None, multiarch=False):
659 # Create the actual chroot
660 command_line = "{} fakeroot debootstrap --verbose --variant=fakechroot {} {}".format(
661 self._build_fakechroot_command(), self.installed_release, self.root_path)
662@@ -83,12 +83,12 @@
663 cmd.wait()
664
665 if cmd.returncode != 0:
666- print("Failed to create container")
667+ utils.get_logger().error("Failed to create container")
668 self.destroy_libertine_container()
669 return False
670
671 # Remove symlinks as they can cause ill-behaved recursive behavior in the chroot
672- print("Fixing chroot symlinks...")
673+ utils.get_logger().info("Fixing chroot symlinks...")
674 os.remove(os.path.join(self.root_path, 'dev'))
675 os.remove(os.path.join(self.root_path, 'proc'))
676
677@@ -109,8 +109,8 @@
678 else:
679 archive = "deb http://archive.ubuntu.com/ubuntu "
680
681- if verbosity == 1:
682- print("Updating chroot's sources.list entries...")
683+ utils.get_logger().info("Updating chroot's sources.list entries...")
684+
685 with open(os.path.join(self.root_path, 'etc', 'apt', 'sources.list'), 'a') as fd:
686 fd.write(archive + self.installed_release + "-updates main\n")
687 fd.write(archive + self.installed_release + " universe\n")
688@@ -121,46 +121,43 @@
689 utils.create_libertine_user_data_dir(self.container_id)
690
691 if multiarch and self.architecture == 'amd64':
692- if verbosity == 1:
693- print("Adding i386 multiarch support...")
694+ utils.get_logger().info("Adding i386 multiarch support...")
695 self.run_in_container("dpkg --add-architecture i386")
696
697- if verbosity == 1:
698- print("Updating the contents of the container after creation...")
699- self.update_packages(verbosity)
700+ utils.get_logger().info("Updating the contents of the container after creation...")
701+ self.update_packages()
702
703 for package in self.default_packages:
704- if not self.install_package(package, verbosity, update_cache=False):
705- print("Failure installing %s during container creation" % package)
706+ if not self.install_package(package, update_cache=False):
707+ utils.get_logger().error("Failure installing %s during container creation" % package)
708 self.destroy_libertine_container()
709 return False
710
711 if self.installed_release == "vivid" or self.installed_release == "xenial":
712- if verbosity == 1:
713- print("Installing the Stable Overlay PPA...")
714- if not self.install_package("software-properties-common", verbosity, update_cache=False):
715- print("Failure installing software-properties-common during container creation")
716+ utils.get_logger().info("Installing the Stable Overlay PPA...")
717+ if not self.install_package("software-properties-common", update_cache=False):
718+ utils.get_logger().error("Failure installing software-properties-common during container creation")
719 self.destroy_libertine_container()
720 return False
721
722 self.run_in_container("add-apt-repository ppa:ci-train-ppa-service/stable-phone-overlay -y")
723- self.update_packages(verbosity)
724+ self.update_packages()
725
726 # Check if the container was created as root and chown the user directories as necessary
727 chown_recursive_dirs(utils.get_libertine_container_userdata_dir_path(self.container_id))
728
729 return True
730
731- def update_packages(self, verbosity=1):
732- retcode = super().update_packages(verbosity)
733- self._run_ldconfig(verbosity)
734+ def update_packages(self):
735+ retcode = super().update_packages()
736+ self._run_ldconfig()
737 return retcode == 0
738
739- def install_package(self, package_name, verbosity=1, no_dialog=False, update_cache=True):
740- returncode = super().install_package(package_name, verbosity, no_dialog, update_cache)
741+ def install_package(self, package_name, no_dialog=False, update_cache=True):
742+ returncode = super().install_package(package_name, no_dialog, update_cache)
743
744 if returncode:
745- self._run_ldconfig(verbosity)
746+ self._run_ldconfig()
747
748 return returncode
749
750@@ -197,8 +194,12 @@
751 mounts = self._sanitize_bind_mounts(utils.get_common_xdg_user_directories() + \
752 ContainersConfig().get_container_bind_mounts(self.container_id))
753 for user_dir in utils.generate_binding_directories(mounts, home_path):
754- user_dir_path = os.path.join(home_path, user_dir[1])
755- bind_mounts += " -b \"%s:%s\"" % (user_dir[0], user_dir_path)
756+ if os.path.isabs(user_dir[1]):
757+ path = user_dir[1]
758+ else:
759+ path = os.path.join(home_path, user_dir[1])
760+
761+ bind_mounts += " -b \"%s:%s\"" % (user_dir[0], path)
762
763 proot_cmd += bind_mounts
764
765@@ -233,16 +234,14 @@
766
767 args = shlex.split(proot_cmd)
768 args.extend(app_exec_line)
769- app = psutil.Popen(args, env=environ)
770- return app
771+ return psutil.Popen(args, env=environ)
772
773 def finish_application(self, app):
774 utils.terminate_window_manager(self._window_manager)
775 app.wait()
776
777- def _run_ldconfig(self, verbosity=1):
778- if verbosity == 1:
779- print("Refreshing the container's dynamic linker run-time bindings...")
780+ def _run_ldconfig(self):
781+ utils.get_logger().info("Refreshing the container's dynamic linker run-time bindings...")
782
783 command_line = self._build_privileged_proot_cmd() + " ldconfig.REAL"
784
785
786=== modified file 'python/libertine/ContainersConfig.py'
787--- python/libertine/ContainersConfig.py 2016-11-16 17:58:38 +0000
788+++ python/libertine/ContainersConfig.py 2017-01-05 20:19:05 +0000
789@@ -214,12 +214,12 @@
790
791 def check_container_id(self, container_id):
792 if container_id and not self.container_exists(container_id):
793- print("Container id \'%s\' does not exist." % container_id, file=sys.stderr)
794+ libertine.utils.get_logger().error("Container id \'%s\' does not exist." % container_id)
795 sys.exit(1)
796 elif not container_id:
797 container_id = self.get_default_container_id()
798 if container_id is None:
799- print("No default container available.")
800+ libertine.utils.get_logger().error("No default container available.")
801 sys.exit(1)
802
803 return container_id
804@@ -260,7 +260,7 @@
805
806 def delete_container(self, container_id):
807 if not self.container_list:
808- print("Unable to delete container. No containers defined.")
809+ libertine.utils.get_logger().error("Unable to delete container. No containers defined.")
810 sys.exit(1)
811
812 container = self._get_container_entry(container_id)
813
814=== modified file 'python/libertine/HostInfo.py'
815--- python/libertine/HostInfo.py 2016-10-28 18:11:12 +0000
816+++ python/libertine/HostInfo.py 2017-01-05 20:19:05 +0000
817@@ -13,6 +13,7 @@
818 # with this program. If not, see <http://www.gnu.org/licenses/>.
819
820 import lsb_release
821+import os
822 import platform
823 import subprocess
824
825@@ -69,3 +70,7 @@
826 parser.error("Failed to determine the local architecture.")
827
828 return dpkg.stdout.read().strip()
829+
830+ def get_host_timezone(self):
831+ with open(os.path.join('/', 'etc', 'timezone'), 'r') as fd:
832+ return fd.read().strip('\n')
833
834=== modified file 'python/libertine/Libertine.py'
835--- python/libertine/Libertine.py 2016-11-16 17:58:38 +0000
836+++ python/libertine/Libertine.py 2017-01-05 20:19:05 +0000
837@@ -1,4 +1,4 @@
838-# Copyright 2015-2106 Canonical Ltd.
839+# Copyright 2015-2016 Canonical Ltd.
840 #
841 # This program is free software: you can redistribute it and/or modify it
842 # under the terms of the GNU General Public License version 3, as published
843@@ -20,31 +20,32 @@
844 import os
845 import shutil
846
847+from . import utils
848 from libertine.ContainersConfig import ContainersConfig
849 from libertine.HostInfo import HostInfo
850
851
852-def apt_args_for_verbosity_level(verbosity):
853- """
854- Maps numeric verbosity levels onto APT command-line arguments.
855-
856- :param verbosity: 0 is quiet, 1 is normal, 2 is incontinent
857- """
858- return {
859- 0: '--quiet=2',
860- 1: '--assume-yes',
861- 2: '--quiet=1 --assume-yes --option APT::Status-Fd=1'
862- }.get(verbosity, '')
863-
864-
865-def apt_command_prefix(verbosity):
866- return '/usr/bin/apt-get ' + apt_args_for_verbosity_level(verbosity) + \
867+def _apt_args_for_verbosity_level():
868+ """
869+ Maps debug levels to APT command-line arguments.
870+ """
871+ if 'LIBERTINE_DEBUG' not in os.environ or os.environ['LIBERTINE_DEBUG'] == '0':
872+ return '--quiet=2'
873+
874+ if os.environ['LIBERTINE_DEBUG'] == '1':
875+ return '--quiet=1 --assume-yes'
876+
877+ return '--assume-yes --option APT::Status-Fd=1'
878+
879+
880+def _apt_command_prefix():
881+ return '/usr/bin/apt-get ' + _apt_args_for_verbosity_level() + \
882 ' --option Apt::Cmd::Disable-Script-Warning=true --option Dpkg::Progress-Fancy=1' + \
883 ' --option Apt::Color=1 '
884
885
886 def handle_runtime_error(error):
887- print("%s" % error)
888+ utils.get_logger().error("%s" % error)
889 return False
890
891
892@@ -92,41 +93,33 @@
893 'maliit-inputcontext-gtk3',
894 'maliit-framework']
895
896- def create_libertine_container(self, password=None, multiarch=False, verbosity=1):
897- pass
898-
899- def destroy_libertine_container(self, verbosity=1):
900- pass
901-
902- def copy_package_to_container(self, package_path):
903- """
904- Copies a Debian package from the host to a pre-determined place
905- in a container.
906-
907- :param package_path: The full path to the Debian package located
908- on the host.
909- """
910- if os.path.exists(os.path.join(self.root_path, 'tmp', package_path.split('/')[-1])):
911+ def create_libertine_container(self, password=None, multiarch=False):
912+ pass
913+
914+ def destroy_libertine_container(self):
915+ pass
916+
917+ def copy_file_to_container(self, source, dest):
918+ """
919+ Copies a file from the host to the given path in the container.
920+
921+ :param source: The full path to the file on the host.
922+ :param dest: The relative path to the file in the container without
923+ the root path.
924+ """
925+ if os.path.exists(os.path.join(self.root_path, dest)):
926 return False
927
928- shutil.copy2(package_path, os.path.join(self.root_path, 'tmp'))
929+ shutil.copy2(source, os.path.join(self.root_path, dest.lstrip('/')))
930 return True
931
932- def delete_package_in_container(self, package_path):
933- """
934- Deletes a previously copied in Debian package in a container.
935-
936- :param package_path: The full path to the Debian package located
937- on the host.
938- """
939- os.remove(os.path.join(self.root_path, 'tmp', package_path.split('/')[-1]))
940-
941- def is_running(self):
942- """
943- Indicates if the container is 'running'. The definition of 'running'
944- depends on the type of the container.
945- """
946- return False
947+ def delete_file_in_container(self, path):
948+ """
949+ Deletes a file within the container.
950+
951+ :param path: The path to the file without the container root path.
952+ """
953+ os.remove(os.path.join(self.root_path, path.lstrip('/')))
954
955 def start_container(self):
956 """
957@@ -151,91 +144,85 @@
958 """
959 pass
960
961- def update_apt_cache(self, verbosity=1):
962+ def update_apt_cache(self):
963 """
964 Updates the apt cache in the container.
965-
966- :param verbosity: the chattiness of the output on a range from 0 to 2
967 """
968- return self.run_in_container(apt_command_prefix(verbosity) + 'update')
969+ return self.run_in_container(_apt_command_prefix() + 'update')
970
971- def update_packages(self, verbosity=1):
972+ def update_packages(self):
973 """
974 Updates all packages installed in the container.
975-
976- :param verbosity: the chattiness of the output on a range from 0 to 2
977 """
978- self.update_apt_cache(verbosity)
979- return self.run_in_container(apt_command_prefix(verbosity) + '--force-yes dist-upgrade') == 0
980+ self.update_apt_cache()
981+ return self.run_in_container(_apt_command_prefix() + '--force-yes dist-upgrade') == 0
982
983- def install_package(self, package_name, verbosity=1, no_dialog=False, update_cache=True):
984+ def install_package(self, package_name, no_dialog=False, update_cache=True):
985 """
986 Installs a named package in the container.
987
988 :param package_name: The name of the package as APT understands it or
989 a full path to a Debian package on the host.
990- :param verbosity: the chattiness of the output on a range from 0 to 2
991 """
992 if update_cache:
993- self.update_apt_cache(verbosity)
994+ self.update_apt_cache()
995
996 if package_name.endswith('.deb'):
997- delete_package = self.copy_package_to_container(package_name)
998-
999- self.run_in_container('ls -la ' + os.environ['HOME'])
1000- self.run_in_container('dpkg -i ' +
1001- os.path.join('/', 'tmp', package_name.split('/')[-1]))
1002-
1003- ret = self.run_in_container(apt_command_prefix(verbosity) + " install -f") == 0
1004-
1005- if delete_package:
1006- self.delete_package_in_container(package_name)
1007+ if not os.path.exists(package_name):
1008+ utils.get_logger().error("File {} does not exist.".format(package_name))
1009+ return False
1010+
1011+ dest = os.path.join('/', 'tmp', package_name.split('/')[-1])
1012+ file_created = self.copy_file_to_container(package_name, dest)
1013+
1014+ self.run_in_container('dpkg -i {}'.format(dest))
1015+ ret = self.run_in_container(_apt_command_prefix() + " install -f") == 0
1016+
1017+ if file_created:
1018+ self.delete_file_in_container(dest)
1019
1020 return ret
1021 else:
1022 if no_dialog:
1023 os.environ['DEBIAN_FRONTEND'] = 'teletype'
1024- return self.run_in_container(apt_command_prefix(verbosity) + " install '" + package_name + "'") == 0
1025+ return self.run_in_container(_apt_command_prefix() + " install '" + package_name + "'") == 0
1026
1027- def remove_package(self, package_name, verbosity=1):
1028+ def remove_package(self, package_name):
1029 """
1030 Removes a package from the container.
1031
1032 :param package_name: The name of the package to be removed.
1033- :param verbosity: The verbosity level of the progress reporting.
1034 """
1035- if self.run_in_container(apt_command_prefix(verbosity) + " purge '" + package_name + "'") != 0:
1036+ if self.run_in_container(_apt_command_prefix() + " purge '" + package_name + "'") != 0:
1037 return False
1038- return self.run_in_container(apt_command_prefix(verbosity) + "autoremove --purge") == 0
1039+ return self.run_in_container(_apt_command_prefix() + "autoremove --purge") == 0
1040
1041- def configure_multiarch(self, should_enable, verbosity=1):
1042+ def configure_multiarch(self, should_enable):
1043 """
1044 Enables or disables multiarch repositories.
1045
1046 :param should_enable: Whether or not to enable multiarch support.
1047- :param verbosity: the chattiness of the output on a range from 0 to 2
1048 """
1049 if should_enable:
1050 ret = self.run_in_container("dpkg --add-architecture i386")
1051 if ret or ret == 0:
1052- self.update_apt_cache(verbosity)
1053+ self.update_apt_cache()
1054 return ret
1055 else:
1056- self.run_in_container(apt_command_prefix(verbosity) + "purge \".*:i386\"")
1057+ self.run_in_container(_apt_command_prefix() + "purge \".*:i386\"")
1058 return self.run_in_container("dpkg --remove-architecture i386")
1059
1060- def configure_add_archive(self, archive, public_key_file, verbosity=1):
1061+ def configure_add_archive(self, archive, public_key_file):
1062 """
1063 Adds the given archive. If this archive requires a key, prompt user.
1064
1065 :param archive: The configuration command to run.
1066 :param public_key_file: file containing the public key used to sign this archive
1067- :param verbosity: the chattiness of the output on a range from 0 to 2
1068 """
1069 if not os.path.exists(os.path.join(self.root_path, 'usr', 'bin', 'add-apt-repository')):
1070- self.install_package("software-properties-common", verbosity)
1071+ self.install_package("software-properties-common")
1072 if 'https://' in archive and not os.path.exists(os.path.join(self.root_path, 'usr', 'lib', 'apt', 'methods', 'https')):
1073- self.install_package("apt-transport-https", verbosity)
1074+ self.install_package("apt-transport-https")
1075
1076 retcode = self.run_in_container("add-apt-repository -y " + archive)
1077 if retcode is 0 and public_key_file is not None:
1078@@ -244,12 +231,11 @@
1079
1080 return retcode
1081
1082- def configure_remove_archive(self, archive, verbosity=1):
1083+ def configure_remove_archive(self, archive):
1084 """
1085 Removes the given archive.
1086
1087 :param archive: The configuration command to run.
1088- :param verbosity: the chattiness of the output on a range from 0 to 2
1089 """
1090 return self.run_in_container("add-apt-repository -y -r " + archive)
1091
1092@@ -272,19 +258,19 @@
1093 super().__init__(container_id)
1094 self.container_type = "mock"
1095
1096- def create_libertine_container(self, password=None, multiarch=False, verbosity=1):
1097- return True
1098-
1099- def destroy_libertine_container(self, verbosity=1):
1100- return True
1101-
1102- def update_packages(self, verbosity=1):
1103- return True
1104-
1105- def install_package(self, package_name, verbosity=1, no_dialog=False, update_cache=True):
1106- return True
1107-
1108- def remove_package(self, package_name, verbosity=1, no_dialog=False):
1109+ def create_libertine_container(self, password=None, multiarch=False):
1110+ return True
1111+
1112+ def destroy_libertine_container(self):
1113+ return True
1114+
1115+ def update_packages(self):
1116+ return True
1117+
1118+ def install_package(self, package_name, no_dialog=False, update_cache=True):
1119+ return True
1120+
1121+ def remove_package(self, package_name, no_dialog=False):
1122 return True
1123
1124 def run_in_container(self, command_string):
1125@@ -335,6 +321,9 @@
1126 if container_type == "lxc":
1127 from libertine.LxcContainer import LibertineLXC
1128 self.container = LibertineLXC(container_id)
1129+ elif container_type == "lxd":
1130+ from libertine.LxdContainer import LibertineLXD
1131+ self.container = LibertineLXD(container_id, self.containers_config)
1132 elif container_type == "chroot":
1133 from libertine.ChrootContainer import LibertineChroot
1134 self.container = LibertineChroot(container_id)
1135@@ -365,48 +354,47 @@
1136 """
1137 return self.container.destroy_libertine_container()
1138
1139- def create_libertine_container(self, password=None, multiarch=False, verbosity=1):
1140+ def create_libertine_container(self, password=None, multiarch=False):
1141 """
1142 Creates the container.
1143 """
1144 self.container.architecture = HostInfo().get_host_architecture()
1145 self.container.installed_release = self.containers_config.get_container_distro(self.container_id)
1146
1147- return self.container.create_libertine_container(password, multiarch, verbosity)
1148+ return self.container.create_libertine_container(password, multiarch)
1149
1150- def update_libertine_container(self, verbosity=1):
1151+ def update_libertine_container(self):
1152 """
1153 Updates the contents of the container.
1154 """
1155 try:
1156 with ContainerRunning(self.container):
1157- return self.container.update_packages(verbosity)
1158+ return self.container.update_packages()
1159 except RuntimeError as e:
1160 return handle_runtime_error(e)
1161
1162- def install_package(self, package_name, verbosity=1, no_dialog=False, update_cache=True):
1163+ def install_package(self, package_name, no_dialog=False, update_cache=True):
1164 """
1165 Installs a package in the container.
1166 """
1167 try:
1168 with ContainerRunning(self.container):
1169- return self.container.install_package(package_name, verbosity, no_dialog, update_cache)
1170+ return self.container.install_package(package_name, no_dialog, update_cache)
1171 except RuntimeError as e:
1172 return handle_runtime_error(e)
1173
1174- def remove_package(self, package_name, verbosity=1, no_dialog=False):
1175+ def remove_package(self, package_name, no_dialog=False):
1176 """
1177 Removes a package from the container.
1178
1179 :param package_name: The name of the package to be removed.
1180- :param verbosity: The verbosity level of the progress reporting.
1181 """
1182 try:
1183 with ContainerRunning(self.container):
1184 if no_dialog:
1185 os.environ['DEBIAN_FRONTEND'] = 'teletype'
1186
1187- return self.container.remove_package(package_name, verbosity)
1188+ return self.container.remove_package(package_name)
1189 except RuntimeError as e:
1190 return handle_runtime_error(e)
1191
1192@@ -484,23 +472,23 @@
1193 except RuntimeError as e:
1194 return handle_runtime_error(e)
1195
1196- def configure_multiarch(self, should_enable, verbosity=1):
1197- try:
1198- with ContainerRunning(self.container):
1199- return self.container.configure_multiarch(should_enable, verbosity)
1200- except RuntimeError as e:
1201- return handle_runtime_error(e)
1202-
1203- def configure_add_archive(self, archive, key, verbosity):
1204- try:
1205- with ContainerRunning(self.container):
1206- return self.container.configure_add_archive(archive, key, verbosity)
1207- except RuntimeError as e:
1208- return handle_runtime_error(e)
1209-
1210- def configure_remove_archive(self, archive, verbosity):
1211- try:
1212- with ContainerRunning(self.container):
1213- return self.container.configure_remove_archive(archive, verbosity)
1214+ def configure_multiarch(self, should_enable):
1215+ try:
1216+ with ContainerRunning(self.container):
1217+ return self.container.configure_multiarch(should_enable)
1218+ except RuntimeError as e:
1219+ return handle_runtime_error(e)
1220+
1221+ def configure_add_archive(self, archive, key):
1222+ try:
1223+ with ContainerRunning(self.container):
1224+ return self.container.configure_add_archive(archive, key)
1225+ except RuntimeError as e:
1226+ return handle_runtime_error(e)
1227+
1228+ def configure_remove_archive(self, archive):
1229+ try:
1230+ with ContainerRunning(self.container):
1231+ return self.container.configure_remove_archive(archive)
1232 except RuntimeError as e:
1233 return handle_runtime_error(e)
1234
1235=== modified file 'python/libertine/LxcContainer.py'
1236--- python/libertine/LxcContainer.py 2016-11-16 17:33:42 +0000
1237+++ python/libertine/LxcContainer.py 2017-01-05 20:19:05 +0000
1238@@ -23,8 +23,9 @@
1239 import sys
1240 import tempfile
1241
1242+from .lifecycle import LifecycleResult
1243 from .Libertine import BaseContainer
1244-from . import utils
1245+from . import utils, HostInfo
1246
1247
1248 home_path = os.environ['HOME']
1249@@ -33,7 +34,7 @@
1250 LIBERTINE_LXC_MANAGER_PATH = "/LxcManager"
1251
1252
1253-def check_lxc_net_entry(entry):
1254+def _check_lxc_net_entry(entry):
1255 lxc_net_file = open('/etc/lxc/lxc-usernet')
1256 found = False
1257
1258@@ -45,14 +46,14 @@
1259 return found
1260
1261
1262-def setup_host_environment(username):
1263+def _setup_host_environment(username):
1264 lxc_net_entry = "%s veth lxcbr0 10" % str(username)
1265
1266- if not check_lxc_net_entry(lxc_net_entry):
1267+ if not _check_lxc_net_entry(lxc_net_entry):
1268 subprocess.Popen(["sudo", "libertine-lxc-setup", str(username)]).wait()
1269
1270
1271-def get_lxc_default_config_path():
1272+def _get_lxc_default_config_path():
1273 return os.path.join(home_path, '.config', 'lxc')
1274
1275
1276@@ -74,21 +75,43 @@
1277 return container
1278
1279
1280-def lxc_start(container, lxc_log_file):
1281- container.append_config_item("lxc.logfile", lxc_log_file)
1282- container.append_config_item("lxc.logpriority", "3")
1283+def _dump_lxc_log(logfile):
1284+ if os.path.exists(logfile):
1285+ try:
1286+ with open(logfile, 'r') as fd:
1287+ for line in fd:
1288+ print(line.lstrip())
1289+ except Exception as ex:
1290+ utils.get_logger().error("Could not open LXC log file: %s" % ex)
1291+
1292+
1293+def get_logfile(container):
1294+ try:
1295+ logfile = container.get_config_item('lxc.logfile')
1296+ except:
1297+ logfile = None
1298+
1299+ if not container.running or logfile is None or not os.path.exists(logfile):
1300+ logfile = os.path.join(tempfile.mkdtemp(), 'lxc-start.log')
1301+ container.append_config_item("lxc.logfile", logfile)
1302+ container.append_config_item("lxc.logpriority", "3")
1303+
1304+ return logfile
1305+
1306+def lxc_start(container):
1307+ lxc_log_file = get_logfile(container)
1308
1309 if not container.start():
1310- return (False, "Container failed to start")
1311+ return LifecycleResult("Container failed to start.")
1312
1313 if not container.wait("RUNNING", 10):
1314- return (False, "Container failed to enter the RUNNING state")
1315+ return LifecycleResult("Container failed to enter the RUNNING state.")
1316
1317 if not container.get_ips(timeout=30):
1318 lxc_stop(container)
1319- return (False, "Not able to connect to the network.")
1320+ return LifecycleResult("Not able to connect to the network.")
1321
1322- return (True, "")
1323+ return LifecycleResult()
1324
1325
1326 def lxc_stop(container):
1327@@ -96,13 +119,6 @@
1328 container.stop()
1329
1330
1331-def get_host_timezone():
1332- with open(os.path.join('/', 'etc', 'timezone'), 'r') as fd:
1333- host_timezone = fd.read().strip('\n')
1334-
1335- return host_timezone
1336-
1337-
1338 class EnvLxcSettings(contextlib.ExitStack):
1339 """
1340 Helper object providing a way to set the proxies for testing
1341@@ -137,42 +153,31 @@
1342 super().__init__(container_id)
1343 self.container_type = "lxc"
1344 self.container = lxc_container(container_id)
1345- self._set_lxc_log()
1346 self.lxc_manager_interface = None
1347 self.window_manager = None
1348+ self.host_info = HostInfo.HostInfo()
1349
1350 utils.set_session_dbus_env_var()
1351
1352 try:
1353 bus = dbus.SessionBus()
1354- lxc_mgr_service = bus.get_object(get_lxc_manager_dbus_name(), get_lxc_manager_dbus_path())
1355- self.lxc_manager_interface = dbus.Interface(lxc_mgr_service, get_lxc_manager_dbus_name())
1356+ self.lxc_manager_interface = bus.get_object(get_lxc_manager_dbus_name(), get_lxc_manager_dbus_path())
1357 except dbus.exceptions.DBusException:
1358 pass
1359
1360- def is_running(self):
1361- return self.container.running
1362-
1363 def timezone_needs_update(self):
1364- host_timezone = get_host_timezone()
1365-
1366 with open(os.path.join(self.root_path, 'etc', 'timezone'), 'r') as fd:
1367- container_timezone = fd.read().strip('\n')
1368-
1369- if host_timezone == container_timezone:
1370- return False
1371- else:
1372- return True
1373+ return fd.read().strip('\n') != self.host_info.get_host_timezone()
1374
1375 def start_container(self):
1376 if self.lxc_manager_interface:
1377- (result, error) = self.lxc_manager_interface.operation_start(self.container_id, self.lxc_log_file)
1378+ result = LifecycleResult.from_dict(self.lxc_manager_interface.operation_start(self.container_id))
1379 else:
1380- (result, error) = lxc_start(self.container, self.lxc_log_file)
1381+ result = lxc_start(self.container)
1382
1383- if not result:
1384- self._dump_lxc_log()
1385- raise RuntimeError(error)
1386+ if not result.success:
1387+ _dump_lxc_log(result.logfile)
1388+ raise RuntimeError(result.error)
1389
1390 if self.run_in_container("mountpoint -q /tmp/.X11-unix") == 0:
1391 self.run_in_container("umount /tmp/.X11-unix")
1392@@ -189,14 +194,14 @@
1393 cmd_args = shlex.split(command_string)
1394 return self.container.attach_wait(lxc.attach_run_command, cmd_args)
1395
1396- def update_packages(self, verbosity=1):
1397+ def update_packages(self):
1398 if self.timezone_needs_update():
1399 self.run_in_container("bash -c \'echo \"{}\" >/etc/timezone\'".format(
1400- get_host_timezone()))
1401+ self.host_info.get_host_timezone()))
1402 self.run_in_container("rm -f /etc/localtime")
1403 self.run_in_container("dpkg-reconfigure -f noninteractive tzdata")
1404
1405- return super().update_packages(verbosity)
1406+ return super().update_packages()
1407
1408 def destroy_libertine_container(self):
1409 if not self.container.defined:
1410@@ -206,7 +211,7 @@
1411 self.container.destroy()
1412 return True
1413
1414- def create_libertine_container(self, password=None, multiarch=False, verbosity=1):
1415+ def create_libertine_container(self, password=None, multiarch=False):
1416 if password is None:
1417 return False
1418
1419@@ -214,10 +219,10 @@
1420 user_id = os.getuid()
1421 group_id = os.getgid()
1422
1423- setup_host_environment(username)
1424+ _setup_host_environment(username)
1425
1426 # Generate the default lxc default config, if it doesn't exist
1427- config_path = get_lxc_default_config_path()
1428+ config_path = _get_lxc_default_config_path()
1429 config_file = "%s/default.conf" % config_path
1430
1431 if not os.path.exists(config_path):
1432@@ -236,25 +241,27 @@
1433 fd.write("lxc.id_map = u %s %s %s\n" % (user_id + 1, (user_id + 1) + 100000, 65536 - (user_id + 1)))
1434 fd.write("lxc.id_map = g %s %s %s\n" % (group_id + 1, (group_id + 1) + 100000, 65536 - (user_id + 1)))
1435
1436+ self.container.load_config(config_file)
1437+
1438 utils.create_libertine_user_data_dir(self.container_id)
1439
1440 with EnvLxcSettings():
1441+ lxc_logfile = get_logfile(self.container)
1442 if not self.container.create("download", 0,
1443 {"dist": "ubuntu",
1444 "release": self.installed_release,
1445 "arch": self.architecture}):
1446- print("Failed to create container")
1447- self._dump_lxc_log()
1448+ utils.get_logger().error("Failed to create container")
1449+ _dump_lxc_log(lxc_logfile)
1450 return False
1451
1452 self.create_libertine_config()
1453
1454- if verbosity == 1:
1455- print("starting container ...")
1456+ utils.get_logger().info("starting container ...")
1457 try:
1458 self.start_container()
1459 except RuntimeError as e:
1460- print("Container failed to start: %s" % e)
1461+ utils.get_logger().error("Container failed to start: %s" % e)
1462 self.destroy_libertine_container()
1463 return False
1464
1465@@ -263,22 +270,19 @@
1466 str(user_id), crypt.crypt(password), str(username)))
1467
1468 if multiarch and self.architecture == 'amd64':
1469- if verbosity == 1:
1470- print("Adding i386 multiarch support...")
1471+ utils.get_logger().info("Adding i386 multiarch support...")
1472 self.run_in_container("dpkg --add-architecture i386")
1473
1474- if verbosity == 1:
1475- print("Updating the contents of the container after creation...")
1476- self.update_packages(verbosity)
1477+ utils.get_logger().info("Updating the contents of the container after creation...")
1478+ self.update_packages()
1479
1480 for package in self.default_packages:
1481- if not self.install_package(package, verbosity, update_cache=False):
1482- print("Failure installing %s during container creation" % package)
1483+ if not self.install_package(package, update_cache=False):
1484+ utils.get_logger().error("Failure installing %s during container creation" % package)
1485 self.destroy_libertine_container()
1486 return False
1487
1488- if verbosity == 1:
1489- print("stopping container ...")
1490+ utils.get_logger().info("stopping container ...")
1491 self.stop_container()
1492
1493 return True
1494@@ -316,17 +320,17 @@
1495
1496 def start_application(self, app_exec_line, environ):
1497 if self.lxc_manager_interface == None:
1498- print("No interface to libertine-lxc-manager. Failing application launch.")
1499+ utils.get_logger().error("No interface to libertine-lxc-manager. Failing application launch.")
1500 return
1501
1502 os.environ.clear()
1503 os.environ.update(environ)
1504
1505- (result, error) = self.lxc_manager_interface.app_start(self.container_id, self.lxc_log_file)
1506+ result = LifecycleResult.from_dict(self.lxc_manager_interface.app_start(self.container_id))
1507
1508- if not result:
1509- self._dump_lxc_log()
1510- print("%s" % error)
1511+ if not result.success:
1512+ _dump_lxc_log(get_logfile(self.container))
1513+ utils.get_logger().error("%s" % result.error)
1514 return
1515
1516 self.window_manager = self.container.attach(lxc.attach_run_command,
1517@@ -348,17 +352,3 @@
1518
1519 # Tell libertine-lxc-manager that the app has stopped.
1520 self.lxc_manager_interface.app_stop(self.container_id)
1521-
1522- def _set_lxc_log(self):
1523- self.lxc_log_file = os.path.join(tempfile.mkdtemp(), 'lxc-start.log')
1524- self.container.append_config_item("lxc.logfile", self.lxc_log_file)
1525- self.container.append_config_item("lxc.logpriority", "3")
1526-
1527- def _dump_lxc_log(self):
1528- if os.path.exists(self.lxc_log_file):
1529- try:
1530- with open(self.lxc_log_file, 'r') as fd:
1531- for line in fd:
1532- print(line.lstrip())
1533- except Exception as ex:
1534- print("Could not open LXC log file: %s" % ex, file=sys.stderr)
1535
1536=== added file 'python/libertine/LxdContainer.py'
1537--- python/libertine/LxdContainer.py 1970-01-01 00:00:00 +0000
1538+++ python/libertine/LxdContainer.py 2017-01-05 20:19:05 +0000
1539@@ -0,0 +1,455 @@
1540+# Copyright 2016 Canonical Ltd.
1541+#
1542+# This program is free software: you can redistribute it and/or modify it
1543+# under the terms of the GNU General Public License version 3, as published
1544+# by the Free Software Foundation.
1545+#
1546+# This program is distributed in the hope that it will be useful, but
1547+# WITHOUT ANY WARRANTY; without even the implied warranties of
1548+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1549+# PURPOSE. See the GNU General Public License for more details.
1550+#
1551+# You should have received a copy of the GNU General Public License along
1552+# with this program. If not, see <http://www.gnu.org/licenses/>.
1553+
1554+import crypt
1555+import dbus
1556+import os
1557+import psutil
1558+import pylxd
1559+import shlex
1560+import subprocess
1561+import time
1562+
1563+from .lifecycle import LifecycleResult
1564+from libertine import Libertine, utils, HostInfo
1565+
1566+
1567+LIBERTINE_LXC_MANAGER_NAME = "com.canonical.libertine.LxdManager"
1568+LIBERTINE_LXC_MANAGER_PATH = "/LxdManager"
1569+
1570+
1571+def get_lxd_manager_dbus_name():
1572+ return LIBERTINE_LXC_MANAGER_NAME
1573+
1574+
1575+def get_lxd_manager_dbus_path():
1576+ return LIBERTINE_LXC_MANAGER_PATH
1577+
1578+
1579+def _get_devices_map():
1580+ devices = {
1581+ '/dev/tty0': {'path': '/dev/tty0', 'type': 'unix-char'},
1582+ '/dev/tty7': {'path': '/dev/tty7', 'type': 'unix-char'},
1583+ '/dev/tty8': {'path': '/dev/tty8', 'type': 'unix-char'},
1584+ '/dev/fb0': {'path': '/dev/fb0', 'type': 'unix-char'},
1585+ 'x11-socket': {'source': '/tmp/.X11-unix', 'path': '/tmp/.X11-unix', 'type': 'disk', 'optional': 'true'},
1586+ }
1587+
1588+ if os.path.exists('/dev/video0'):
1589+ devices['/dev/video0'] = {'path': '/dev/video0', 'type': 'unix-char'}
1590+
1591+ # every regular file in /dev/snd
1592+ for f in ['/dev/snd/{}'.format(f) for f in os.listdir('/dev/snd') if not os.path.isdir('/dev/snd/{}'.format(f))]:
1593+ devices[f] = {'path': f, 'type': 'unix-char'}
1594+
1595+ # every regular file in /dev/dri
1596+ for f in ['/dev/dri/{}'.format(f) for f in os.listdir('/dev/dri') if not os.path.isdir('/dev/dri/{}'.format(f))]:
1597+ devices[f] = {'path': f, 'type': 'unix-char'}
1598+
1599+ # Some devices require special mappings for snap
1600+ if utils.is_snap_environment():
1601+ devices['x11-socket']['source'] = '/var/lib/snapd/hostfs/tmp/.X11-unix'
1602+
1603+ return devices
1604+
1605+def _readlink(source):
1606+ while os.path.islink(source):
1607+ new_source = os.readlink(source)
1608+ if not os.path.isabs(new_source):
1609+ new_source = os.path.join(os.path.dirname(source), new_source)
1610+ source = new_source
1611+
1612+ return source
1613+
1614+def _setup_lxd():
1615+ utils.get_logger().info("Running LXD setup.")
1616+ import pexpect
1617+ child = pexpect.spawnu('sudo libertine-lxd-setup {}'.format(os.environ['USER']), env={'TERM': 'dumb'})
1618+
1619+ while True:
1620+ index = child.expect(['.+\[default=.+\].*', pexpect.EOF, pexpect.TIMEOUT,
1621+ # The following are required for lxd=2.0.x
1622+ '.+\[yes/no\].*',
1623+ '.+\(e.g. (?P<example>[a-z0-9\.:]+)\).+'])
1624+ if index == 0:
1625+ child.sendline()
1626+ elif index == 1:
1627+ return True
1628+ elif index == 2:
1629+ return False
1630+ elif index == 3:
1631+ child.sendline('yes')
1632+ elif index == 4:
1633+ child.sendline(child.match.group('example'))
1634+
1635+ if child.exitstatus is not None:
1636+ return child.exitstatus == 0
1637+
1638+
1639+def _setup_bind_mount_service(container, uid, username):
1640+ utils.get_logger().info("Creating systemd mount override service")
1641+ service = '''
1642+[Unit]
1643+Description=Fix system mounts for libertine
1644+
1645+[Service]
1646+ExecStart=/usr/bin/libertine-lxd-mount-update
1647+
1648+[Install]
1649+WantedBy=multi-user.target
1650+'''[1:-1]
1651+ container.files.put('/etc/systemd/system/libertine-lxd-mount-update.service', service.encode('utf-8'))
1652+ container.execute(shlex.split('chmod 644 /etc/systemd/system/libertine-lxd-mount-update.service'))
1653+
1654+ utils.get_logger().info("Creating mount update shell script")
1655+ script = '''
1656+#!/bin/sh
1657+
1658+mkdir -p /run/user/{uid}
1659+chown {username}:{username} /run/user/{uid}
1660+chmod 755 /run/user/{uid}
1661+mount -o bind /var/tmp/run/user/{uid} /run/user/{uid}
1662+
1663+chgrp audio /dev/snd/*
1664+chgrp video /dev/dri/*
1665+[ -n /dev/video0 ] && chgrp video /dev/video0
1666+'''[1:-1]
1667+ container.files.put('/usr/bin/libertine-lxd-mount-update', script.format(uid=uid, username=username).encode('utf-8'))
1668+ container.execute(shlex.split('chmod 755 /usr/bin/libertine-lxd-mount-update'))
1669+
1670+ utils.get_logger().info("Enabling systemd mount update service")
1671+ container.execute(shlex.split('systemctl enable libertine-lxd-mount-update.service'))
1672+
1673+
1674+def lxd_container(client, container_id):
1675+ try:
1676+ return client.containers.get(container_id)
1677+ except pylxd.exceptions.LXDAPIException:
1678+ return None
1679+
1680+
1681+def _wait_for_network(container):
1682+ for retries in range(0, 10):
1683+ out, err = container.execute(shlex.split('ping -c 1 ubuntu.com'))
1684+ if out:
1685+ utils.get_logger().info("Network connection active")
1686+ return True
1687+ time.sleep(1)
1688+ return False
1689+
1690+
1691+def lxd_start(container):
1692+ if container.status != 'Running':
1693+ container.start(wait=True)
1694+
1695+ container.sync(rollback=True) # required for pylxd=2.0.x
1696+
1697+ if container.status != 'Running':
1698+ return LifecycleResult("Container {} failed to start".format(container.name))
1699+
1700+ return LifecycleResult()
1701+
1702+
1703+def lxd_stop(container, wait):
1704+ if container.status == 'Stopped':
1705+ return LifecycleResult()
1706+
1707+ container.stop(wait=wait)
1708+ container.sync(rollback=True) # required for pylxd=2.0.x
1709+
1710+ if wait and container.status != 'Stopped':
1711+ return LifecycleResult("Container {} failed to stop".format(container.name))
1712+
1713+ return LifecycleResult()
1714+
1715+
1716+def update_bind_mounts(container, config):
1717+ home_path = os.environ['HOME']
1718+
1719+ container.devices.clear()
1720+ container.devices['root'] = {'type': 'disk', 'path': '/'}
1721+
1722+ if os.path.exists(os.path.join(home_path, '.config', 'dconf')):
1723+ container.devices['dconf'] = {
1724+ 'type': 'disk',
1725+ 'source': os.path.join(home_path, '.config', 'dconf'),
1726+ 'path': os.path.join(home_path, '.config', 'dconf')
1727+ }
1728+
1729+ run_user = '/run/user/{}'.format(os.getuid())
1730+ container.devices[run_user] = {'source': run_user, 'path': '/var/tmp{}'.format(run_user), 'type': 'disk'}
1731+
1732+ mounts = utils.get_common_xdg_user_directories() + \
1733+ config.get_container_bind_mounts(container.name)
1734+ for user_dir in utils.generate_binding_directories(mounts, home_path):
1735+ if not os.path.exists(user_dir[0]):
1736+ utils.get_logger().warning('Bind-mount path \'{}\' does not exist.'.format(user_dir[0]))
1737+ continue
1738+
1739+ if os.path.isabs(user_dir[1]):
1740+ path = user_dir[1]
1741+ else:
1742+ path = os.path.join(home_path, user_dir[1])
1743+
1744+ utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, container.name))
1745+
1746+ container.devices[user_dir[1]] = {
1747+ 'source': _readlink(user_dir[0]),
1748+ 'path': path,
1749+ 'optional': 'true',
1750+ 'type': 'disk'
1751+ }
1752+
1753+ try:
1754+ container.save(wait=True)
1755+ except pylxd.exceptions.LXDAPIException as e:
1756+ utils.get_logger().warning('Saving bind mounts for container \'{}\' raised: {}'.format(container.name, str(e)))
1757+ # This is most likely the result of the container currently running
1758+
1759+
1760+def update_libertine_profile(client):
1761+ try:
1762+ profile = client.profiles.get('libertine')
1763+
1764+ utils.get_logger().info('Updating existing lxd profile.')
1765+ profile.devices = _get_devices_map()
1766+ profile.config['raw.idmap'] = 'both 1000 1000'
1767+
1768+ try:
1769+ profile.save()
1770+ except pylxd.exceptions.LXDAPIException as e:
1771+ utils.get_logger().warning('Saving libertine lxd profile raised: {}'.format(str(e)))
1772+ # This is most likely the result of an older container currently
1773+ # running and/or containing a conflicting device entry
1774+ except pylxd.exceptions.LXDAPIException:
1775+ utils.get_logger().info('Creating libertine lxd profile.')
1776+ client.profiles.create('libertine', config={'raw.idmap': 'both 1000 1000'}, devices=_get_devices_map())
1777+
1778+
1779+class LibertineLXD(Libertine.BaseContainer):
1780+ def __init__(self, name, config):
1781+ super().__init__(name)
1782+ self._id = name
1783+ self._config = config
1784+ self._host_info = HostInfo.HostInfo()
1785+ self._container = None
1786+ self._matchbox_pid = None
1787+
1788+ if not _setup_lxd():
1789+ raise Exception("Failed to setup lxd.")
1790+
1791+ self._client = pylxd.Client()
1792+ self._window_manager = None
1793+ self.root_path = '{}/containers/{}/rootfs'.format(os.getenv('LXD_DIR', '/var/lib/lxd'), name)
1794+
1795+ utils.set_session_dbus_env_var()
1796+ try:
1797+ bus = dbus.SessionBus()
1798+ self._manager = bus.get_object(get_lxd_manager_dbus_name(), get_lxd_manager_dbus_path())
1799+ except dbus.exceptions.DBusException:
1800+ utils.get_logger().warning("D-Bus Service not found.")
1801+ self._manager = None
1802+
1803+
1804+ def create_libertine_container(self, password=None, multiarch=False):
1805+ if self._try_get_container():
1806+ utils.get_logger().error("Container already exists")
1807+ return False
1808+
1809+ update_libertine_profile(self._client)
1810+
1811+ utils.get_logger().info("Creating container '%s' with distro '%s'" % (self._id, self.installed_release))
1812+ create = subprocess.Popen(shlex.split('lxc launch ubuntu-daily:{distro} {id} --profile '
1813+ 'default --profile libertine'.format(
1814+ distro=self.installed_release, id=self._id)))
1815+ if create.wait() is not 0:
1816+ utils.get_logger().error("Creating container '{}' failed with code '{}'".format(self._id, create.returncode))
1817+ return False
1818+
1819+ if not self.start_container():
1820+ utils.get_logger().error("Failed to start container '{}'".format(self._id))
1821+ self.destroy_libertine_container()
1822+ return False
1823+
1824+ username = os.environ['USER']
1825+ uid = str(os.getuid())
1826+ self.run_in_container("userdel -r ubuntu")
1827+ self.run_in_container("useradd -u {} -U -p {} -G sudo,audio,video {}".format(
1828+ uid, crypt.crypt(''), username))
1829+ self.run_in_container("mkdir -p /home/{}".format(username))
1830+ self.run_in_container("chown {0}:{0} /home/{0}".format(username))
1831+
1832+ _setup_bind_mount_service(self._container, uid, username)
1833+
1834+ if multiarch and self.architecture == 'amd64':
1835+ utils.get_logger().info("Adding i386 multiarch support to container '{}'".format(self._id))
1836+ self.run_in_container("dpkg --add-architecture i386")
1837+
1838+ self.update_packages()
1839+
1840+ for package in self.default_packages:
1841+ utils.get_logger().info("Installing package '%s' in container '%s'" % (package, self._id))
1842+ if not self.install_package(package, no_dialog=True, update_cache=False):
1843+ utils.get_logger().error("Failure installing '%s' during container creation" % package)
1844+ self.destroy_libertine_container()
1845+ return False
1846+
1847+ return True
1848+
1849+ def update_packages(self):
1850+ if not self._timezone_in_sync():
1851+ utils.get_logger().info("Re-syncing timezones")
1852+ self.run_in_container("bash -c 'echo \"%s\" > /etc/timezone'" % self._host_info.get_host_timezone())
1853+ self.run_in_container("rm -f /etc/localtime")
1854+ self.run_in_container("dpkg-reconfigure -f noninteractive tzdata")
1855+
1856+ return super().update_packages()
1857+
1858+ def destroy_libertine_container(self):
1859+ if not self._try_get_container():
1860+ utils.get_logger().error("No such container '%s'" % self._id)
1861+ return False
1862+
1863+ self.stop_container(wait=True)
1864+ self._container.delete()
1865+ return True
1866+
1867+ def _timezone_in_sync(self):
1868+ proc = subprocess.Popen(self._lxc_args('cat /etc/timezone'), stdout=subprocess.PIPE)
1869+ out, err = proc.communicate()
1870+ return out.decode('UTF-8').strip('\n') == self._host_info.get_host_timezone()
1871+
1872+ def _lxc_args(self, command, environ={}):
1873+ env_as_args = ' '
1874+ for k, v in environ.items():
1875+ env_as_args += '--env {}="{}" '.format(k, v)
1876+
1877+ return shlex.split('lxc exec {}{}-- {}'.format(self._id,
1878+ env_as_args,
1879+ command))
1880+
1881+ def run_in_container(self, command):
1882+ proc = subprocess.Popen(self._lxc_args(command))
1883+ return proc.wait()
1884+
1885+ def start_container(self):
1886+ if not self._try_get_container():
1887+ return False
1888+
1889+ if self._manager:
1890+ result = LifecycleResult.from_dict(self._manager.operation_start(self._id))
1891+ else:
1892+ result = lxd_start(self._container)
1893+
1894+ if not result.success:
1895+ utils.get_logger().error(result.error)
1896+ return False
1897+
1898+ if not _wait_for_network(self._container):
1899+ utils.get_logger().warning("Network unavailable in container '{}'".format(self._id))
1900+
1901+ return result.success
1902+
1903+ def stop_container(self, wait=False):
1904+ if not self._try_get_container():
1905+ return False
1906+
1907+ if self._manager:
1908+ result = LifecycleResult.from_dict(self._manager.operation_stop(self._id))
1909+ else:
1910+ result = lxd_stop(self._container, wait)
1911+
1912+ if not result.success:
1913+ utils.get_logger().error(result.error)
1914+
1915+ return result.success
1916+
1917+ def _get_matchbox_pids(self):
1918+ p = subprocess.Popen(self._lxc_args('pgrep matchbox'), stdout=subprocess.PIPE)
1919+ out, err = p.communicate()
1920+ return out.decode('utf-8').split('\n')
1921+
1922+ # FIXME: Remove once window management logic has been moved to the host
1923+ def _start_window_manager(self, args):
1924+ args.extend(utils.setup_window_manager(self._id))
1925+
1926+ if 'matchbox-window-manager' in args:
1927+ pids = self._get_matchbox_pids()
1928+
1929+ self._window_manager = psutil.Popen(args)
1930+
1931+ time.sleep(.25) # Give matchbox a moment to start in the container
1932+ if self._window_manager.poll() is not None:
1933+ utils.get_logger().warning("Window manager terminated prematurely with exit code '{}'".format(
1934+ self._window_manager.returncode))
1935+ self._window_manager = None
1936+ elif 'matchbox-window-manager' in args:
1937+ after_pids = self._get_matchbox_pids()
1938+ if len(after_pids) > len(pids):
1939+ self._matchbox_pid = (set(after_pids) - set(pids)).pop()
1940+
1941+ def start_application(self, app_exec_line, environ):
1942+ if not self._try_get_container():
1943+ utils.get_logger().error("Could not get container '{}'".format(self._id))
1944+ return None
1945+
1946+ if self._manager:
1947+ result = LifecycleResult.from_dict(self._manager.app_start(self._id))
1948+ else:
1949+ update_libertine_profile(self._client)
1950+ update_bind_mounts(self._container, self._config)
1951+ result = lxd_start(self._container)
1952+
1953+ if not result.success:
1954+ utils.get_logger().error(result.error)
1955+ return False
1956+
1957+ args = self._lxc_args("sudo -E -u {} env PATH={}".format(os.environ['USER'], environ['PATH']), environ)
1958+
1959+ self._start_window_manager(args.copy())
1960+
1961+ args.extend(app_exec_line)
1962+ return psutil.Popen(args)
1963+
1964+ def finish_application(self, app):
1965+ if self._window_manager is not None:
1966+ utils.terminate_window_manager(self._window_manager)
1967+ self._window_manager = None
1968+
1969+ # This is a workaround for an issue on xenial where the process
1970+ # running the window manager is not killed with the application
1971+ if self._matchbox_pid is not None:
1972+ utils.get_logger().debug("Manually killing matchbox-window-manager")
1973+ self.run_in_container("kill -9 {}".format(self._matchbox_pid))
1974+ self._matchbox_pid = None
1975+
1976+ app.wait()
1977+
1978+ if self._manager:
1979+ self._manager.app_stop(self.container_id)
1980+ else:
1981+ lxd_stop(self._container, False)
1982+
1983+ def copy_file_to_container(self, source, dest):
1984+ with open(source, 'rb') as f:
1985+ return self._container.files.put(dest, f.read())
1986+
1987+ def delete_file_in_container(self, path):
1988+ return self.run_in_container('rm {}'.format(path))
1989+
1990+ def _try_get_container(self):
1991+ if self._container is None:
1992+ self._container = lxd_container(self._client, self._id)
1993+
1994+ return self._container is not None
1995
1996=== modified file 'python/libertine/launcher/session.py'
1997--- python/libertine/launcher/session.py 2016-10-27 16:57:03 +0000
1998+++ python/libertine/launcher/session.py 2017-01-05 20:19:05 +0000
1999@@ -29,9 +29,6 @@
2000 from .task import LaunchServiceTask, TaskType
2001
2002
2003-log = utils.get_logger()
2004-
2005-
2006 def translate_to_real_address(abstract_address):
2007 """Translate the notional text address to a real UNIX-domain address string.
2008
2009@@ -101,12 +98,12 @@
2010 b = from_socket.recv(4096)
2011 if len(b) > 0:
2012 to_socket.sendall(b)
2013- log.debug('copied {} bytes from fd to {}'.format(len(b), from_socket, to_socket))
2014+ utils.get_logger().debug('copied {} bytes from fd to {}'.format(len(b), from_socket, to_socket))
2015 else:
2016- log.info('close detected on {}'.format(from_socket))
2017+ utils.get_logger().info('close detected on {}'.format(from_socket))
2018 return len(b)
2019 except Exception as e:
2020- log.debug(e)
2021+ utils.get_logger().debug(e)
2022 return 0
2023
2024 def _close_up_shop(self, session):
2025@@ -172,7 +169,7 @@
2026 with suppress(AttributeError):
2027 for task_config in self._config.prelaunch_tasks:
2028 if task_config.task_type == TaskType.LAUNCH_SERVICE:
2029- log.info("launching {}".format(task_config.datum[0]))
2030+ utils.get_logger().info("launching {}".format(task_config.datum[0]))
2031 task = LaunchServiceTask(task_config)
2032 self._child_processes.append(task)
2033 task.start(self._config.host_environ)
2034@@ -267,7 +264,7 @@
2035 create a socket bridge to the host when a connection from the session is
2036 made.
2037 """
2038- log.debug('creating bridge listener for {} on {}'.
2039+ utils.get_logger().debug('creating bridge listener for {} on {}'.
2040 format(bridge_config.env_var, bridge_config.session_address))
2041 sock = socket(AF_UNIX, SOCK_STREAM)
2042 sock.bind(translate_to_real_address(bridge_config.session_address))
2043@@ -286,7 +283,7 @@
2044 """
2045 (bridge_config, sock) = datum
2046 conn = sock.accept()
2047- log.debug('connection of session socket {} accepted'.format(bridge_config.session_address))
2048+ utils.get_logger().debug('connection of session socket {} accepted'.format(bridge_config.session_address))
2049 self.add_bridge_pair(BridgePair(conn[0], bridge_config.host_address))
2050
2051 def _ensure_paths_exist(self):
2052@@ -316,17 +313,17 @@
2053 data = os.read(fd, 4)
2054 sig = struct.unpack('%uB' % len(data), data)
2055 if sig[0] == signal.SIGCHLD:
2056- log.info('SIGCHLD received')
2057+ utils.get_logger().info('SIGCHLD received')
2058 if self._handle_child_died():
2059 raise StopIteration('launched program exited')
2060 elif sig[0] == signal.SIGINT:
2061- log.info('SIGINT received')
2062+ utils.get_logger().info('SIGINT received')
2063 raise StopIteration('keyboard interrupt')
2064 elif sig[0] == signal.SIGTERM:
2065- log.info('SIGTERM received')
2066+ utils.get_logger().info('SIGTERM received')
2067 raise StopIteration('terminate')
2068 else:
2069- log.warning('unknown signal {} received'.format(sig[0]))
2070+ utils.get_logger().warning('unknown signal {} received'.format(sig[0]))
2071
2072 def _set_signal_handlers(self):
2073 """Set the signal handlers."""
2074
2075=== modified file 'python/libertine/launcher/task.py'
2076--- python/libertine/launcher/task.py 2016-10-27 17:25:08 +0000
2077+++ python/libertine/launcher/task.py 2017-01-05 20:19:05 +0000
2078@@ -44,7 +44,7 @@
2079
2080 def __init__(self, config):
2081 """
2082- :param config: The task configuraiton.
2083+ :param config: The task configuration.
2084 :type config: TaskConfig
2085
2086 The constructor unpacks the service commandline from the config datum.
2087@@ -54,13 +54,13 @@
2088
2089 def start(self, environ=None):
2090 """Start the service.
2091-
2092+
2093 :param env: An alternate environment dictionary.
2094 """
2095 self._process = Popen(self._command_line, env=environ)
2096
2097 def stop(self):
2098- """Shuts the service down. """
2099+ """Shuts the service down."""
2100 try:
2101 self._process.terminate()
2102 except ProcessLookupError:
2103
2104=== added directory 'python/libertine/lifecycle'
2105=== added file 'python/libertine/lifecycle/ContainerLifecycleService.py'
2106--- python/libertine/lifecycle/ContainerLifecycleService.py 1970-01-01 00:00:00 +0000
2107+++ python/libertine/lifecycle/ContainerLifecycleService.py 2017-01-05 20:19:05 +0000
2108@@ -0,0 +1,108 @@
2109+#!/usr/bin/python3
2110+# -*- coding: utf-8 -*-
2111+
2112+# Copyright (C) 2016 Canonical Ltd.
2113+
2114+# This program is free software: you can redistribute it and/or modify
2115+# it under the terms of the GNU General Public License as published by
2116+# the Free Software Foundation; version 3 of the License.
2117+#
2118+# This program is distributed in the hope that it will be useful,
2119+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2120+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2121+# GNU General Public License for more details.
2122+#
2123+# You should have received a copy of the GNU General Public License
2124+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2125+
2126+
2127+import dbus.exceptions
2128+import dbus.service
2129+
2130+from .LifecycleResult import LifecycleResult
2131+from collections import Counter
2132+from dbus.mainloop.glib import DBusGMainLoop
2133+from libertine import utils
2134+
2135+
2136+LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE = 'com.canonical.libertine.ContainerLifecycle'
2137+
2138+
2139+class ContainerLifecycleService(dbus.service.Object):
2140+ def __init__(self, service_name, service_path):
2141+ self._apps = Counter()
2142+ self._operations = Counter()
2143+
2144+ DBusGMainLoop(set_as_default=True)
2145+ try:
2146+ bus_name = dbus.service.BusName(service_name,
2147+ bus=dbus.SessionBus(),
2148+ do_not_queue=True)
2149+ except dbus.exceptions.NameExistsException:
2150+ utils.get_logger().error("service is already running")
2151+ raise
2152+ super().__init__(bus_name, service_path)
2153+
2154+ def start(self, container):
2155+ raise NotImplementedError("Subclasses must implement start(container)")
2156+
2157+ def stop(self, container):
2158+ raise NotImplementedError("Subclasses must implement stop(container)")
2159+
2160+ @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE,
2161+ in_signature='s',
2162+ out_signature='a{ss}')
2163+ def app_start(self, container):
2164+ utils.get_logger().debug("app_start({})".format(container))
2165+ if self._operations[container] != 0:
2166+ return LifecycleResult("Libertine container operation already running: cannot launch application.")
2167+
2168+ result = self.start(container, True)
2169+
2170+ if result.success:
2171+ self._apps[container] += 1
2172+
2173+ return result.to_dict()
2174+
2175+ @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE,
2176+ in_signature='s',
2177+ out_signature='a{ss}')
2178+ def app_stop(self, container):
2179+ utils.get_logger().debug("app_stop({})".format(container))
2180+ self._apps[container] -= 1
2181+ result = LifecycleResult()
2182+
2183+ if self._apps[container] == 0:
2184+ result = self.stop(container)
2185+ del self._apps[container]
2186+
2187+ return result.to_dict()
2188+
2189+ @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE,
2190+ in_signature='s',
2191+ out_signature='a{ss}')
2192+ def operation_start(self, container):
2193+ utils.get_logger().debug("operation_start({})".format(container))
2194+ if self._apps[container] != 0:
2195+ return LifecycleResult("Application already running in container: cannot run operation.")
2196+
2197+ result = self.start(container, False)
2198+
2199+ if result.success:
2200+ self._operations[container] += 1
2201+
2202+ return result.to_dict()
2203+
2204+ @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE,
2205+ in_signature='s',
2206+ out_signature='a{ss}')
2207+ def operation_stop(self, container):
2208+ utils.get_logger().debug("operation_stop({})".format(container))
2209+ self._operations[container] -= 1
2210+ result = LifecycleResult()
2211+
2212+ if self._operations[container] == 0:
2213+ result = self.stop(container)
2214+ del self._operations[container]
2215+
2216+ return result.to_dict()
2217
2218=== added file 'python/libertine/lifecycle/ContainerLifecycleServiceRunner.py'
2219--- python/libertine/lifecycle/ContainerLifecycleServiceRunner.py 1970-01-01 00:00:00 +0000
2220+++ python/libertine/lifecycle/ContainerLifecycleServiceRunner.py 2017-01-05 20:19:05 +0000
2221@@ -0,0 +1,46 @@
2222+#!/usr/bin/python3
2223+# -*- coding: utf-8 -*-
2224+
2225+# Copyright (C) 2016 Canonical Ltd.
2226+
2227+# This program is free software: you can redistribute it and/or modify
2228+# it under the terms of the GNU General Public License as published by
2229+# the Free Software Foundation; version 3 of the License.
2230+#
2231+# This program is distributed in the hope that it will be useful,
2232+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2233+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2234+# GNU General Public License for more details.
2235+#
2236+# You should have received a copy of the GNU General Public License
2237+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2238+
2239+import signal
2240+
2241+from gi.repository import GLib
2242+from libertine import utils
2243+
2244+
2245+class ContainerLifecycleServiceRunner(object):
2246+ def __init__(self, service):
2247+ self._service = service
2248+
2249+ def _sigterm(self, sig):
2250+ utils.get_logger().warning("Received SIGTERM")
2251+ self._shutdown()
2252+
2253+ def _shutdown(self):
2254+ utils.get_logger().info("Shutting down")
2255+ GLib.MainLoop().quit()
2256+
2257+ def run(self):
2258+ GLib.unix_signal_add(GLib.PRIORITY_HIGH,
2259+ signal.SIGTERM,
2260+ self._sigterm,
2261+ None)
2262+
2263+ try:
2264+ utils.get_logger().info("Starting main loop")
2265+ GLib.MainLoop().run()
2266+ except KeyboardInterrupt:
2267+ self._shutdown()
2268
2269=== added file 'python/libertine/lifecycle/LifecycleResult.py'
2270--- python/libertine/lifecycle/LifecycleResult.py 1970-01-01 00:00:00 +0000
2271+++ python/libertine/lifecycle/LifecycleResult.py 2017-01-05 20:19:05 +0000
2272@@ -0,0 +1,37 @@
2273+#!/usr/bin/python3
2274+# -*- coding: utf-8 -*-
2275+
2276+# Copyright (C) 2016 Canonical Ltd.
2277+
2278+# This program is free software: you can redistribute it and/or modify
2279+# it under the terms of the GNU General Public License as published by
2280+# the Free Software Foundation; version 3 of the License.
2281+#
2282+# This program is distributed in the hope that it will be useful,
2283+# but WITHOUT ANY WARRANTY; without even the implied warranty of
2284+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2285+# GNU General Public License for more details.
2286+#
2287+# You should have received a copy of the GNU General Public License
2288+# along with this program. If not, see <http://www.gnu.org/licenses/>.
2289+
2290+class LifecycleResult(object):
2291+ def __init__(self, error=''):
2292+ self._error = error
2293+
2294+ @property
2295+ def error(self):
2296+ return self._error or ''
2297+
2298+ @property
2299+ def success(self):
2300+ return self.error == ''
2301+
2302+ @classmethod
2303+ def from_dict(kls, d):
2304+ return LifecycleResult(d.get('error', None))
2305+
2306+ def to_dict(self):
2307+ return {
2308+ 'error': self.error
2309+ }
2310
2311=== added file 'python/libertine/lifecycle/__init__.py'
2312--- python/libertine/lifecycle/__init__.py 1970-01-01 00:00:00 +0000
2313+++ python/libertine/lifecycle/__init__.py 2017-01-05 20:19:05 +0000
2314@@ -0,0 +1,23 @@
2315+# Copyright 2016 Canonical Ltd.
2316+#
2317+# This program is free software: you can redistribute it and/or modify it
2318+# under the terms of the GNU General Public License version 3, as published
2319+# by the Free Software Foundation.
2320+#
2321+# This program is distributed in the hope that it will be useful, but
2322+# WITHOUT ANY WARRANTY; without even the implied warranties of
2323+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2324+# PURPOSE. See the GNU General Public License for more details.
2325+#
2326+# You should have received a copy of the GNU General Public License along
2327+# with this program. If not, see <http://www.gnu.org/licenses/>.
2328+
2329+from .ContainerLifecycleServiceRunner import ContainerLifecycleServiceRunner
2330+from .ContainerLifecycleService import ContainerLifecycleService
2331+from .LifecycleResult import LifecycleResult
2332+
2333+__all__ = [
2334+ 'ContainerLifecycleServiceRunner',
2335+ 'ContainerLifecycleService',
2336+ 'LifecycleResult'
2337+ ]
2338
2339=== modified file 'python/libertine/utils.py'
2340--- python/libertine/utils.py 2016-12-02 16:38:11 +0000
2341+++ python/libertine/utils.py 2017-01-05 20:19:05 +0000
2342@@ -25,6 +25,7 @@
2343 require_version('Libertine', '1')
2344 from gi.repository import Libertine
2345
2346+
2347 def get_logger():
2348 logger = logging.getLogger('__libertine_logger__')
2349
2350@@ -40,13 +41,27 @@
2351 logger.addHandler(stream_handler)
2352
2353 if 'LIBERTINE_DEBUG' in os.environ:
2354- logger.setLevel(logging.DEBUG)
2355+ if os.environ['LIBERTINE_DEBUG'] == '0':
2356+ logger.setLevel(logging.WARNING)
2357+ elif os.environ['LIBERTINE_DEBUG'] == '1':
2358+ logger.setLevel(logging.INFO)
2359+ else:
2360+ logger.setLevel(logging.DEBUG)
2361 else:
2362 logger.setLevel(logging.WARNING)
2363
2364 return logger
2365
2366
2367+def set_environmental_verbosity(verbosity):
2368+ # Set debug levels if not overridden in environment
2369+ if 'LIBERTINE_DEBUG' not in os.environ:
2370+ if verbosity is None:
2371+ os.environ['LIBERTINE_DEBUG'] = '1'
2372+ else:
2373+ os.environ['LIBERTINE_DEBUG'] = str(verbosity)
2374+
2375+
2376 def get_libertine_container_rootfs_path(container_id):
2377 path = Libertine.container_path(container_id)
2378
2379@@ -112,8 +127,8 @@
2380
2381 name = dir
2382 if name.startswith(prefix):
2383- name = name.replace(prefix, '', 1)
2384- name = name.lstrip('/')
2385+ name = name.replace(prefix, '', 1).lstrip('/')
2386+
2387 if name in names:
2388 binding_dirs.append((dir, "%s (%i)" % (name, names.count(name))))
2389 else:
2390@@ -201,3 +216,9 @@
2391 return True
2392 else:
2393 return False
2394+
2395+
2396+def get_deb_package_name(package):
2397+ pkg_name_proc = subprocess.Popen(shlex.split('dpkg --field {} Package'.format(package)), stdout=subprocess.PIPE)
2398+ out, err = pkg_name_proc.communicate()
2399+ return out.decode('utf-8').strip()
2400
2401=== added file 'snapcraft.yaml'
2402--- snapcraft.yaml 1970-01-01 00:00:00 +0000
2403+++ snapcraft.yaml 2017-01-05 20:19:05 +0000
2404@@ -0,0 +1,32 @@
2405+name: libertine
2406+version: "1.5"
2407+summary: Libertine suite
2408+description: |
2409+ Suite for maintaining deb-based applications in a non-deb environment
2410+confinement: devmode
2411+grade: devel
2412+
2413+apps:
2414+ libertine-container-manager:
2415+ command: usr/bin/snap-runner.wrapper libertine-container-manager
2416+ libertine-manager-app:
2417+ command: usr/bin/snap-runner.wrapper libertine-manager-app
2418+ libertine-launch:
2419+ command: usr/bin/snap-runner.wrapper libertine-launch
2420+
2421+ # For debugging purposes
2422+ bash:
2423+ command: usr/bin/snap-runner.wrapper bash
2424+
2425+parts:
2426+ libertine-source:
2427+ plugin: libertine
2428+ source: .
2429+ after:
2430+ - desktop-qt5
2431+ libertine-deps:
2432+ plugin: libertine-deps
2433+ env:
2434+ plugin: dump
2435+ organize:
2436+ data/snap-runner.wrapper: usr/bin/snap-runner.wrapper
2437
2438=== modified file 'tests/unit/test_libertine_gir.py'
2439--- tests/unit/test_libertine_gir.py 2016-08-28 00:17:54 +0000
2440+++ tests/unit/test_libertine_gir.py 2017-01-05 20:19:05 +0000
2441@@ -49,14 +49,12 @@
2442 self.assertThat(container_home_path, Equals(self.cmake_source_dir + '/libertine-home/libertine-container/user-data/wily'))
2443
2444 def test_container_name(self):
2445- container_id = 'wily'
2446 with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}):
2447- container_name = Libertine.container_name(container_id)
2448+ container_name = Libertine.container_name('wily')
2449
2450 self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf'"))
2451
2452- container_id = 'wily-2'
2453- container_name = Libertine.container_name(container_id)
2454+ container_name = Libertine.container_name('wily-2')
2455
2456 self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf' (2)"))
2457
2458
2459=== modified file 'tests/unit/test_logger.py'
2460--- tests/unit/test_logger.py 2016-08-25 01:49:07 +0000
2461+++ tests/unit/test_logger.py 2017-01-05 20:19:05 +0000
2462@@ -34,6 +34,11 @@
2463 def test_logger_on_with_env_var(self):
2464 with patch.dict('os.environ', {'LIBERTINE_DEBUG': '1'}):
2465 l = libertine.utils.get_logger()
2466+ self.assertThat(l.getEffectiveLevel(), Equals(logging.INFO))
2467+
2468+ def test_logger_on_with_env_var(self):
2469+ with patch.dict('os.environ', {'LIBERTINE_DEBUG': '2'}):
2470+ l = libertine.utils.get_logger()
2471 self.assertThat(l.getEffectiveLevel(), Equals(logging.DEBUG))
2472
2473 def test_logger_only_inits_once(self):
2474@@ -42,4 +47,3 @@
2475 l2 = libertine.utils.get_logger()
2476 l3 = libertine.utils.get_logger()
2477 self.assertThat(len(l3.handlers), Equals(1))
2478-
2479
2480=== modified file 'tools/CMakeLists.txt'
2481--- tools/CMakeLists.txt 2016-10-28 19:39:13 +0000
2482+++ tools/CMakeLists.txt 2017-01-05 20:19:05 +0000
2483@@ -1,10 +1,9 @@
2484-install(PROGRAMS libertine-container-manager libertine-launch libertine-lxc-manager libertine-xmir libertine-lxc-setup libertined
2485+install(PROGRAMS libertine-container-manager libertine-launch libertine-lxc-manager libertine-lxd-manager
2486+ libertine-xmir libertine-lxc-setup libertine-lxd-setup libertined
2487 DESTINATION ${CMAKE_INSTALL_BINDIR})
2488-install(FILES libertine-launch.1 libertine-container-manager.1 libertine-lxc-manager.1 libertine-xmir.1
2489+install(FILES libertine-launch.1 libertine-container-manager.1 libertine-lxc-manager.1
2490+ libertine-lxd-manager.1 libertine-xmir.1
2491 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
2492 COMPONENT doc)
2493-install(PROGRAMS update-puritine-containers
2494- DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/libertine)
2495-
2496 install(FILES completions/libertine-container-manager
2497 DESTINATION ${DESTDIR}/usr/share/bash-completion/completions/)
2498
2499=== modified file 'tools/libertine-container-manager'
2500--- tools/libertine-container-manager 2016-11-16 17:33:42 +0000
2501+++ tools/libertine-container-manager 2017-01-05 20:19:05 +0000
2502@@ -23,7 +23,6 @@
2503 import sys
2504 import re
2505
2506-from apt.debfile import DebPackage
2507 from libertine import LibertineContainer
2508 from libertine.ContainersConfig import ContainersConfig
2509 from libertine.HostInfo import HostInfo
2510@@ -39,22 +38,22 @@
2511 password = None
2512
2513 if args.distro and not self.host_info.is_distro_valid(args.distro, args.force):
2514- print("Invalid distro %s" % args.distro, file=sys.stderr)
2515+ libertine.utils.get_logger().error("Invalid distro %s" % args.distro)
2516 sys.exit(1)
2517
2518 if self.containers_config.container_exists(args.id):
2519- print("Container id '%s' is already used." % args.id, file=sys.stderr)
2520+ libertine.utils.get_logger().error("Container id '%s' is already used." % args.id)
2521 sys.exit(1)
2522 elif re.match("^[a-z0-9][a-z0-9+.-]+$", args.id) is None:
2523- print("Container id '%s' invalid. ID must be of form ([a-z0-9][a-z0-9+.-]+)." % args.id, file=sys.stderr)
2524+ libertine.utils.get_logger().error("Container id '%s' invalid. ID must be of form ([a-z0-9][a-z0-9+.-]+)." % args.id)
2525 sys.exit(1)
2526
2527 if not args.type:
2528 container_type = self.host_info.select_container_type_by_kernel()
2529 else:
2530- if args.type == 'lxc' and not self.host_info.has_lxc_support():
2531- print("System kernel does not support lxc type containers. "
2532- "Please either use chroot or omit the -t option.")
2533+ if (args.type == 'lxc' or args.type == 'lxd') and not self.host_info.has_lxc_support():
2534+ libertine.utils.get_logger().error("System kernel does not support lxc type containers. "
2535+ "Please either use chroot or omit the -t option.")
2536 sys.exit(1)
2537 container_type = args.type
2538
2539@@ -64,8 +63,8 @@
2540 host_distro = self.host_info.get_host_distro_release()
2541
2542 if args.distro != host_distro:
2543- print("The container distribution needs to match the host ditribution for chroot"
2544- " based containers. Please either use \'%s\' or omit the -d/--distro option."
2545+ libertine.utils.get_logger().error("The container distribution needs to match the host ditribution for chroot"
2546+ " based containers. Please either use \'%s\' or omit the -d/--distro option."
2547 % host_distro)
2548 sys.exit(1)
2549
2550@@ -88,11 +87,18 @@
2551 multiarch = 'enabled'
2552 self.containers_config.update_container_multiarch_support(args.id, multiarch)
2553
2554- container = LibertineContainer(args.id)
2555- self.containers_config.update_container_install_status(args.id, "installing")
2556- if not container.create_libertine_container(password, args.multiarch, args.verbosity):
2557+ try:
2558+ container = LibertineContainer(args.id)
2559+ self.containers_config.update_container_install_status(args.id, "installing")
2560+ if not container.create_libertine_container(password, args.multiarch):
2561+ libertine.utils.get_logger().error("Failed to create container")
2562+ self.containers_config.delete_container(args.id)
2563+ sys.exit(1)
2564+ except Exception as e:
2565+ libertine.utils.get_logger().error("Failed to create container: '{}'".format(str(e)))
2566 self.containers_config.delete_container(args.id)
2567 sys.exit(1)
2568+
2569 self.containers_config.update_container_install_status(args.id, "ready")
2570
2571 libertine.utils.refresh_libertine_scope()
2572@@ -119,16 +125,16 @@
2573
2574 if is_debian_package:
2575 if os.path.exists(args.package):
2576- package = DebPackage(args.package).pkgname
2577+ package = libertine.utils.get_deb_package_name(args.package)
2578 else:
2579- print("%s does not exist." % args.package)
2580+ libertine.utils.get_logger().error("%s does not exist." % args.package)
2581 sys.exit(1)
2582 else:
2583 package = args.package
2584
2585 if self.containers_config.package_exists(container_id, package):
2586 if not is_debian_package:
2587- print("Package \'%s\' is already installed." % package)
2588+ libertine.utils.get_logger().error("Package '%s' is already installed." % package)
2589 sys.exit(1)
2590 else:
2591 self.containers_config.add_new_package(container_id, package)
2592@@ -136,7 +142,9 @@
2593 container = LibertineContainer(container_id)
2594
2595 self.containers_config.update_package_install_status(container_id, package, "installing")
2596- if not container.install_package(args.package, args.verbosity, args.no_dialog):
2597+ if not container.install_package(args.package, args.no_dialog):
2598+ libertine.utils.get_logger().error("Package '{}' failed to install in container '{}'"
2599+ .format(package, container_id))
2600 self.containers_config.delete_package(container_id, package)
2601 sys.exit(1)
2602
2603@@ -144,12 +152,12 @@
2604
2605 libertine.utils.refresh_libertine_scope()
2606
2607- def remove_package_by_name(self, container_id, package_name, verbosity=1, no_dialog=False):
2608+ def remove_package_by_name(self, container_id, package_name, no_dialog=False):
2609 fallback_status = self.containers_config.get_package_install_status(container_id, package_name)
2610 self.containers_config.update_package_install_status(container_id, package_name, "removing")
2611
2612 container = LibertineContainer(container_id)
2613- if not container.remove_package(package_name, verbosity, no_dialog) and fallback_status == 'installed':
2614+ if not container.remove_package(package_name, no_dialog) and fallback_status == 'installed':
2615 self.containers_config.update_package_install_status(container_id, package_name, fallback_status)
2616 return False
2617
2618@@ -162,10 +170,12 @@
2619 container_id = self.containers_config.check_container_id(args.id)
2620
2621 if self.containers_config.get_package_install_status(container_id, args.package) != 'installed':
2622- print("Package \'%s\' is not installed." % args.package)
2623+ libertine.utils.get_logger().error("Package \'%s\' is not installed." % args.package)
2624 sys.exit(1)
2625
2626- if not self.remove_package_by_name(container_id, args.package, args.verbosity, args.no_dialog):
2627+ if not self.remove_package_by_name(container_id, args.package, args.no_dialog):
2628+ libertine.utils.get_logger().error("Package '{}' failed to be removed from container '{}'"
2629+ .format(args.package, container_id))
2630 sys.exit(1)
2631
2632 libertine.utils.refresh_libertine_scope()
2633@@ -175,6 +185,8 @@
2634
2635 container = LibertineContainer(container_id)
2636 if container.search_package_cache(args.search_string) is not 0:
2637+ libertine.utils.get_logger().error("Search for '{}' in container '{}' exited with non-zero status"
2638+ .format(args.id, args.search_string))
2639 sys.exit(1)
2640
2641 def update(self, args):
2642@@ -183,7 +195,7 @@
2643 container = LibertineContainer(container_id)
2644
2645 self.containers_config.update_container_install_status(container_id, "updating")
2646- if not container.update_libertine_container(args.verbosity):
2647+ if not container.update_libertine_container():
2648 self.containers_config.update_container_install_status(container_id, "ready")
2649 sys.exit(1)
2650
2651@@ -208,10 +220,10 @@
2652 if not container.exec_command(args.command):
2653 sys.exit(1)
2654
2655- def delete_archive_by_name(self, container_id, archive_name, verbosity=1):
2656+ def delete_archive_by_name(self, container_id, archive_name):
2657 if self.containers_config.get_archive_install_status(container_id, archive_name) == 'installed':
2658 self.containers_config.update_archive_install_status(container_id, archive_name, 'removing')
2659- if LibertineContainer(container_id).configure_remove_archive("\"" + archive_name + "\"", verbosity) is not 0:
2660+ if LibertineContainer(container_id).configure_remove_archive("\"" + archive_name + "\"") is not 0:
2661 self.containers_config.update_archive_install_status(container_id, archive_name, 'installed')
2662 return False
2663
2664@@ -230,17 +242,17 @@
2665
2666 current_multiarch = self.containers_config.get_container_multiarch_support(container_id)
2667 if current_multiarch == multiarch:
2668- print("i386 multiarch support is already %s" % multiarch)
2669+ libertine.utils.get_logger().error("i386 multiarch support is already %s" % multiarch)
2670 sys.exit(1)
2671
2672- if container.configure_multiarch(args.multiarch, args.verbosity) is not 0:
2673+ if container.configure_multiarch(args.multiarch) is not 0:
2674 sys.exit(1)
2675
2676 self.containers_config.update_container_multiarch_support(container_id, multiarch)
2677
2678 elif args.archive is not None:
2679 if args.archive_name is None:
2680- print("Configure archive called with no archive name. See configure --help for usage.")
2681+ libertine.utils.get_logger().error("Configure archive called with no archive name. See configure --help for usage.")
2682 sys.exit(1)
2683
2684 archive_name = args.archive_name.strip("\'\"")
2685@@ -248,12 +260,12 @@
2686
2687 if args.archive == 'add':
2688 if self.containers_config.archive_exists(container_id, archive_name):
2689- print("%s already added in container." % archive_name)
2690+ libertine.utils.get_logger().error("%s already added in container." % archive_name)
2691 sys.exit(1)
2692
2693 self.containers_config.add_container_archive(container_id, archive_name)
2694 self.containers_config.update_archive_install_status(container_id, archive_name, 'installing')
2695- if container.configure_add_archive(archive_name_esc, args.public_key_file, args.verbosity) is not 0:
2696+ if container.configure_add_archive(archive_name_esc, args.public_key_file) is not 0:
2697 self.containers_config.delete_container_archive(container_id, archive_name)
2698 sys.exit(1)
2699
2700@@ -261,48 +273,47 @@
2701
2702 elif args.archive == 'remove':
2703 if not self.containers_config.archive_exists(container_id, archive_name):
2704- print("%s is not added in container." % archive_name)
2705+ libertine.utils.get_logger().error("%s is not added in container." % archive_name)
2706 sys.exit(1)
2707
2708 if not self.delete_archive_by_name(container_id, archive_name):
2709- print("%s was not properly deleted." % archive_name)
2710+ libertine.utils.get_logger().error("%s was not properly deleted." % archive_name)
2711 sys.exit(1)
2712
2713 elif args.bind_mount is not None:
2714 if args.mount_path is None:
2715- print("Configure bind-mounts called without mount path. See configure --help for usage")
2716+ libertine.utils.get_logger().error("Configure bind-mounts called without mount path. See configure --help for usage")
2717 sys.exit(1)
2718
2719 mount_path = args.mount_path.rstrip('/')
2720
2721 # validate bind-mount
2722 if not mount_path.startswith(os.environ['HOME']) and not mount_path.startswith('/media/%s' % os.environ['USER']):
2723- print("Cannot mount '%s', mount path must be in $HOME or /media/$USER." % mount_path)
2724+ libertine.utils.get_logger().error("Cannot mount {}, mount path must be in {} or /media/{}.".format(mount_path, os.environ['HOME'], os.environ['USER']))
2725 sys.exit(1)
2726 if mount_path.startswith('/media/%s' % os.environ['USER']) and \
2727 self.containers_config.get_container_type(container_id) == 'lxc':
2728- print("/media mounts not currently supported in lxc.")
2729+ libertine.utils.get_logger().error("/media mounts not currently supported in lxc.")
2730 sys.exit(1)
2731 if not os.path.isdir(mount_path):
2732- print("Cannot mount '%s', mount path must be an existing directory." % mount_path)
2733+ libertine.utils.get_logger().error("Cannot mount '%s', mount path must be an existing directory." % mount_path)
2734 sys.exit(1)
2735
2736 # update database with new bind-mount
2737 container_bind_mounts = self.containers_config.get_container_bind_mounts(container_id)
2738 if args.bind_mount == 'add':
2739 if mount_path in container_bind_mounts:
2740- print("Cannot add mount '%s', bind-mount already exists." % mount_path)
2741+ libertine.utils.get_logger().error("Cannot add mount '%s', bind-mount already exists." % mount_path)
2742 sys.exit(1)
2743 self.containers_config.add_new_bind_mount(container_id, mount_path)
2744 elif args.bind_mount == 'remove':
2745 if mount_path not in container_bind_mounts:
2746- print("Cannot remove mount '%s', bind-mount does not exist." % mount_path)
2747+ libertine.utils.get_logger().error("Cannot remove mount '%s', bind-mount does not exist." % mount_path)
2748 sys.exit(1)
2749 self.containers_config.delete_bind_mount(container_id, mount_path)
2750
2751-
2752 else:
2753- print("Configure called with no subcommand. See configure --help for usage.")
2754+ libertine.utils.get_logger().error("Configure called with no subcommand. See configure --help for usage.")
2755 sys.exit(1)
2756
2757
2758@@ -340,17 +351,17 @@
2759 parser = argparse.ArgumentParser(description="Legacy X application support for Unity 8")
2760
2761 if not os.geteuid():
2762- print("Please do not run %s using sudo" % parser.prog)
2763+ libertine.utils.get_logger().error("Please do not run %s using sudo" % parser.prog)
2764 sys.exit(1)
2765
2766 container_manager = LibertineContainerManager()
2767
2768 parser.add_argument('-q', '--quiet',
2769 action='store_const', dest='verbosity', const=0,
2770- help=('do not print status updates on stdout'))
2771- parser.add_argument('-v', '--verbose',
2772+ help=('disables all non-vital output'))
2773+ parser.add_argument('-v', '--verbosity',
2774 action='store_const', dest='verbosity', const=2,
2775- help=('extra verbose output'))
2776+ help=('enables debug output'))
2777 subparsers = parser.add_subparsers(dest="subparser_name",
2778 title="subcommands",
2779 metavar='create, destroy, install-package, remove-package, search-cache, update, list, list-apps, configure')
2780@@ -556,8 +567,8 @@
2781
2782 # Actually parse the args
2783 args = parser.parse_args()
2784- if args.verbosity is None:
2785- args.verbosity = 1
2786+
2787+ libertine.utils.set_environmental_verbosity(args.verbosity)
2788
2789 if args.subparser_name == None:
2790 parser.print_help()
2791
2792=== modified file 'tools/libertine-container-manager.1'
2793--- tools/libertine-container-manager.1 2016-10-14 14:44:17 +0000
2794+++ tools/libertine-container-manager.1 2017-01-05 20:19:05 +0000
2795@@ -26,10 +26,10 @@
2796 .SH OPTIONS
2797 .TP
2798 .BR \-q ", " \-\-quiet ""
2799-do not print status updates on stdout
2800+disable all non-vital output
2801 .TP
2802 .BR \-v ", " \-\-verbose ""
2803-extra verbose output
2804+enable debug output
2805 .TP
2806 .BR \-h ", " \-\-help ""
2807 Print a summary of the command line options and exit.
2808@@ -319,6 +319,12 @@
2809 Clear default container.
2810 .RE
2811 .TP
2812+.BR
2813+
2814+.SH ENVIRONMENT VARIABLES
2815+.TP
2816+.BR LIBERTINE_DEBUG
2817+Overrides verbosity arguments. 0 for quiet, 1 for standard, 2 for debug.
2818
2819 .SH SEE ALSO
2820 .UR https://launchpad.net/libertine
2821
2822=== modified file 'tools/libertine-lxc-manager'
2823--- tools/libertine-lxc-manager 2016-11-09 20:59:27 +0000
2824+++ tools/libertine-lxc-manager 2017-01-05 20:19:05 +0000
2825@@ -16,90 +16,50 @@
2826 # You should have received a copy of the GNU General Public License
2827 # along with this program. If not, see <http://www.gnu.org/licenses/>.
2828
2829-import dbus
2830-import dbus.service
2831 import libertine.LxcContainer
2832 import libertine.utils
2833 import os
2834 import shlex
2835-import signal
2836 import subprocess
2837
2838-from collections import Counter
2839-from dbus.mainloop.glib import DBusGMainLoop
2840-from gi.repository import GLib
2841 from libertine.ContainersConfig import ContainersConfig
2842-
2843-
2844-home_path = os.environ['HOME']
2845+from libertine.lifecycle import *
2846+
2847
2848 LIBERTINE_LXC_MANAGER_NAME = libertine.LxcContainer.get_lxc_manager_dbus_name()
2849 LIBERTINE_LXC_MANAGER_PATH = libertine.LxcContainer.get_lxc_manager_dbus_path()
2850
2851
2852-class Service(dbus.service.Object):
2853+class Service(ContainerLifecycleService):
2854
2855 def __init__(self):
2856- self.is_pulse_setup = False
2857- self.app_counter = Counter()
2858- self.containers_config = ContainersConfig()
2859- self.operation_counter = Counter()
2860-
2861- DBusGMainLoop(set_as_default=True)
2862- try:
2863- bus_name = dbus.service.BusName(LIBERTINE_LXC_MANAGER_NAME,
2864- bus=dbus.SessionBus(),
2865- do_not_queue=True)
2866- except dbus.exceptions.NameExistsException:
2867- print("service is already running")
2868- raise
2869- super().__init__(bus_name, LIBERTINE_LXC_MANAGER_PATH)
2870-
2871- @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
2872- in_signature='ss',
2873- out_signature='(bs)')
2874- def app_start(self, container_id, lxc_logfile):
2875- if self.operation_counter[container_id] != 0:
2876- return (False, "Libertine container operation already running: cannot launch application.")
2877-
2878- (started, reason) = self._launch_lxc_container(container_id, lxc_logfile)
2879-
2880- if started:
2881- self.app_counter[container_id] += 1
2882-
2883- return (started, reason)
2884-
2885- @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
2886- in_signature='s')
2887- def app_stop(self, container_id):
2888- self.app_counter[container_id] -= 1
2889-
2890- if self.app_counter[container_id] == 0:
2891- self._stop_lxc_container(container_id)
2892- del self.app_counter[container_id]
2893-
2894- @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
2895- in_signature='ss',
2896- out_signature='(bs)')
2897- def operation_start(self, container_id, lxc_log_file):
2898- if self.app_counter[container_id] != 0:
2899- return (False, "Application already running in container: cannot run operation.")
2900-
2901- (started, reason) = self._launch_lxc_container(container_id, lxc_log_file, launchable=False)
2902-
2903- if started:
2904- self.operation_counter[container_id] += 1
2905-
2906- return (started, reason)
2907-
2908- @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
2909- in_signature='s')
2910- def operation_stop(self, container_id):
2911- self.operation_counter[container_id] -= 1
2912-
2913- if self.operation_counter[container_id] == 0:
2914- self._stop_lxc_container(container_id)
2915- del self.operation_counter[container_id]
2916+ super().__init__(LIBERTINE_LXC_MANAGER_NAME, LIBERTINE_LXC_MANAGER_PATH)
2917+ self._home = os.environ['HOME']
2918+ self._containers_config = ContainersConfig()
2919+ self._is_pulse_setup = False
2920+
2921+ def start(self, container_id, launchable):
2922+ container = libertine.LxcContainer.lxc_container(container_id)
2923+
2924+ if not container.defined:
2925+ return LifecycleResult("Container {} is not valid".format(container_id))
2926+
2927+ if launchable and not self._is_pulse_setup:
2928+ self._setup_pulse()
2929+
2930+ if not container.running:
2931+ if launchable:
2932+ self._dynamic_bind_mounts(container, container_id)
2933+
2934+ return libertine.LxcContainer.lxc_start(container)
2935+
2936+ return LifecycleResult()
2937+
2938+ def stop(self, container_id):
2939+ container = libertine.LxcContainer.lxc_container(container_id)
2940+ libertine.LxcContainer.lxc_stop(container)
2941+
2942+ return LifecycleResult() # no error case
2943
2944 def _setup_pulse(self):
2945 pulse_socket_path = os.path.join(libertine.utils.get_libertine_runtime_dir(), 'pulse_socket')
2946@@ -118,42 +78,26 @@
2947
2948 self.is_pulse_setup = True
2949
2950- def _launch_lxc_container(self, container_id, lxc_log_file, launchable=True):
2951- container = libertine.LxcContainer.lxc_container(container_id)
2952-
2953- if not container.defined:
2954- return (False, "Container {} is not valid".format(container_id))
2955-
2956- if launchable and not self.is_pulse_setup:
2957- self._setup_pulse()
2958-
2959- if not container.running:
2960- if launchable:
2961- self._dynamic_bind_mounts(container, container_id)
2962-
2963- return libertine.LxcContainer.lxc_start(container, lxc_log_file)
2964-
2965- return (True, "")
2966-
2967- def _stop_lxc_container(self, container_id):
2968- container = libertine.LxcContainer.lxc_container(container_id)
2969-
2970- libertine.LxcContainer.lxc_stop(container)
2971-
2972 def _dynamic_bind_mounts(self, container, container_id):
2973- self.containers_config.refresh_database()
2974+ self._containers_config.refresh_database()
2975 mounts = self._sanitize_bind_mounts(libertine.utils.get_common_xdg_user_directories() + \
2976- self.containers_config.get_container_bind_mounts(container_id))
2977+ self._containers_config.get_container_bind_mounts(container_id))
2978
2979 data_dir = libertine.utils.get_libertine_container_userdata_dir_path(container_id)
2980- for user_dir in libertine.utils.generate_binding_directories(mounts, home_path):
2981- user_dir_fullpath = os.path.join(data_dir, user_dir[1])
2982- if not os.path.exists(user_dir_fullpath):
2983- os.makedirs(user_dir_fullpath, exist_ok=True)
2984-
2985+ for user_dir in libertine.utils.generate_binding_directories(mounts, self._home):
2986+ if os.path.isabs(user_dir[1]):
2987+ path = user_dir[1].strip('/')
2988+ fullpath = os.path.join(libertine.utils.get_libertine_container_rootfs_path(container_id), path)
2989+ else:
2990+ path = "{}/{}".format(self._home.strip('/'), user_dir[1])
2991+ fullpath = os.path.join(data_dir, user_dir[1])
2992+
2993+ os.makedirs(fullpath, exist_ok=True)
2994+
2995+ libertine.utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, container_id))
2996 xdg_user_dir_entry = (
2997- "%s %s/%s none bind,create=dir,optional"
2998- % (user_dir[0], home_path.strip('/'), user_dir[1])
2999+ "%s %s none bind,create=dir,optional"
3000+ % (user_dir[0], path)
3001 )
3002 container.append_config_item("lxc.mount.entry", xdg_user_dir_entry)
3003
3004@@ -161,26 +105,5 @@
3005 return [mount.replace(" ", "\\040") for mount in mounts]
3006
3007
3008-def sigterm(self):
3009- shutdown()
3010-
3011-
3012-def shutdown():
3013- GLib.MainLoop().quit()
3014-
3015-
3016-def main():
3017- service = Service()
3018- GLib.unix_signal_add(GLib.PRIORITY_HIGH,
3019- signal.SIGTERM,
3020- sigterm,
3021- None)
3022-
3023- try:
3024- GLib.MainLoop().run()
3025- except KeyboardInterrupt:
3026- shutdown()
3027-
3028-
3029 if __name__ == '__main__':
3030- main()
3031+ ContainerLifecycleServiceRunner(Service()).run()
3032
3033=== added file 'tools/libertine-lxd-manager'
3034--- tools/libertine-lxd-manager 1970-01-01 00:00:00 +0000
3035+++ tools/libertine-lxd-manager 2017-01-05 20:19:05 +0000
3036@@ -0,0 +1,57 @@
3037+#!/usr/bin/python3
3038+# -*- coding: utf-8 -*-
3039+
3040+# Copyright (C) 2016 Canonical Ltd.
3041+
3042+# This program is free software: you can redistribute it and/or modify
3043+# it under the terms of the GNU General Public License as published by
3044+# the Free Software Foundation; version 3 of the License.
3045+#
3046+# This program is distributed in the hope that it will be useful,
3047+# but WITHOUT ANY WARRANTY; without even the implied warranty of
3048+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3049+# GNU General Public License for more details.
3050+#
3051+# You should have received a copy of the GNU General Public License
3052+# along with this program. If not, see <http://www.gnu.org/licenses/>.
3053+
3054+import libertine.LxdContainer
3055+import libertine.utils
3056+import pylxd
3057+
3058+from libertine.ContainersConfig import ContainersConfig
3059+from libertine.lifecycle import *
3060+
3061+
3062+LIBERTINE_LXD_MANAGER_NAME = libertine.LxdContainer.get_lxd_manager_dbus_name()
3063+LIBERTINE_LXD_MANAGER_PATH = libertine.LxdContainer.get_lxd_manager_dbus_path()
3064+
3065+
3066+class Service(ContainerLifecycleService):
3067+
3068+ def __init__(self):
3069+ super().__init__(LIBERTINE_LXD_MANAGER_NAME, LIBERTINE_LXD_MANAGER_PATH)
3070+ self._config = ContainersConfig()
3071+ self._client = pylxd.Client()
3072+
3073+ def start(self, container_id, launchable):
3074+ container = libertine.LxdContainer.lxd_container(self._client, container_id)
3075+
3076+ if not container:
3077+ return LifecycleResult("Container {} is not valid".format(container_id))
3078+
3079+ if container.status != 'Running':
3080+ libertine.LxdContainer.update_libertine_profile(self._client)
3081+ if launchable:
3082+ libertine.LxdContainer.update_bind_mounts(container, self._config)
3083+
3084+ return libertine.LxdContainer.lxd_start(container)
3085+
3086+ return LifecycleResult()
3087+
3088+ def stop(self, container_id):
3089+ return libertine.LxdContainer.lxd_stop(libertine.LxdContainer.lxd_container(self._client, container_id), False)
3090+
3091+
3092+if __name__ == '__main__':
3093+ ContainerLifecycleServiceRunner(Service()).run()
3094
3095=== added file 'tools/libertine-lxd-manager.1'
3096--- tools/libertine-lxd-manager.1 1970-01-01 00:00:00 +0000
3097+++ tools/libertine-lxd-manager.1 2017-01-05 20:19:05 +0000
3098@@ -0,0 +1,9 @@
3099+.TH libertine-lxd-manager "1" "Dec 2016" "libertine-lxd-manager 0.99" "User Commands"
3100+
3101+.SH NAME
3102+libertine-lxd-manager \- monitors LXD activity performed by libertine
3103+
3104+.SH DESCRIPTION
3105+usage: libertine\-lxd\-manager
3106+.PP
3107+monitors LXD activity performed by libertine
3108
3109=== added file 'tools/libertine-lxd-setup'
3110--- tools/libertine-lxd-setup 1970-01-01 00:00:00 +0000
3111+++ tools/libertine-lxd-setup 2017-01-05 20:19:05 +0000
3112@@ -0,0 +1,40 @@
3113+#!/bin/sh
3114+
3115+if [ -z $1 ]; then
3116+ echo "Usage: $0 username"
3117+ exit 1
3118+fi
3119+
3120+USERNAME=$1
3121+
3122+# Create the lxd group and add given user
3123+if [ -z "`groups ${USERNAME} | grep lxd`" ]; then
3124+ groupadd --force --system lxd
3125+ usermod -G lxd -a $USERNAME
3126+fi
3127+
3128+# Map the given user to the container root user
3129+uid=`id --user ${USERNAME}`
3130+idmap="root:$uid:1"
3131+if [ -z "`grep ${idmap} /etc/subuid`" ]; then
3132+ echo ${idmap} | tee -a /etc/subuid /etc/subgid
3133+fi
3134+
3135+# find the right lxc command
3136+lxc=`which lxc`
3137+if [ -z "${lxc}" ]; then
3138+ if [ -n `which lxd.lxc` ]; then
3139+ lxc=`which lxd.lxc`
3140+ else
3141+ echo "No lxc command found on this system."
3142+ exit 1
3143+ fi
3144+fi
3145+
3146+# Run lxd init if there are no containers already on the system
3147+if [ 3 -ge `${lxc} list | wc -l` ]; then
3148+ lxd init
3149+fi
3150+
3151+mkdir -p /home/$USERNAME/.config/lxc
3152+chown -R $USERNAME:$USERNAME /home/$USERNAME/.config/lxc
3153
3154=== modified file 'tools/libertined'
3155--- tools/libertined 2016-10-26 17:05:34 +0000
3156+++ tools/libertined 2017-01-05 20:19:05 +0000
3157@@ -62,11 +62,10 @@
3158 os.rename(self.cache_file, '%s.1' % self.cache_file)
3159
3160 def __enter__(self):
3161- if self.config.debug:
3162- os.environ['LIBERTINE_DEBUG'] = '1'
3163- else:
3164+ utils.set_environmental_verbosity(self.config.verbosity)
3165+
3166+ if not self.config.debug:
3167 if self.config.cache_output:
3168- os.environ['LIBERTINE_DEBUG'] = '1'
3169 self._rotate_logs()
3170 self.output_file = open(self.cache_file, 'w')
3171 else:
3172@@ -94,7 +93,13 @@
3173 self._arg_parser.add_argument(u'-d', u"--debug",
3174 action='store_true',
3175 default=False,
3176- help=u"allow all output on stdout")
3177+ help=u"print output to stdout")
3178+ self._arg_parser.add_argument('-q', '--quiet', action='store_const',
3179+ dest='verbosity', const=0,
3180+ help=('disables all non-vital output'))
3181+ self._arg_parser.add_argument('-v', '--verbosity', action='store_const',
3182+ dest='verbosity', const=2,
3183+ help=('enables debug output'))
3184 self._arg_parser.add_argument(u'-c', u"--cache-output",
3185 action='store_true',
3186 default=False,
3187@@ -111,7 +116,7 @@
3188 self.loop = GLib.MainLoop()
3189
3190 def sigterm(self, code):
3191- utils.get_logger().info("terminate ('%s') signal received" % code)
3192+ utils.get_logger().debug("terminate ('%s') signal received" % code)
3193 self.shutdown()
3194
3195 def shutdown(self):
3196
3197=== removed file 'tools/update-puritine-containers'
3198--- tools/update-puritine-containers 2016-10-25 15:24:08 +0000
3199+++ tools/update-puritine-containers 1970-01-01 00:00:00 +0000
3200@@ -1,183 +0,0 @@
3201-#!/usr/bin/python3
3202-# -*- coding: utf-8 -*-
3203-
3204-# Copyright (C) 2016 Canonical Ltd.
3205-# Author: Christopher Townsend <christopher.townsend@canonical.com>
3206-
3207-# This program is free software: you can redistribute it and/or modify
3208-# it under the terms of the GNU General Public License as published by
3209-# the Free Software Foundation; version 3 of the License.
3210-#
3211-# This program is distributed in the hope that it will be useful,
3212-# but WITHOUT ANY WARRANTY; without even the implied warranty of
3213-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3214-# GNU General Public License for more details.
3215-#
3216-# You should have received a copy of the GNU General Public License
3217-# along with this program. If not, see <http://www.gnu.org/licenses/>.
3218-
3219-import json
3220-import libertine.utils
3221-import os
3222-import shlex
3223-import shutil
3224-import subprocess
3225-
3226-from gi.repository import GLib, Gio
3227-
3228-puritine_hook_dir = os.path.join(os.environ['HOME'], '.cache', 'libertine', 'puritine')
3229-puritine_symlink_farm_file = os.path.join(puritine_hook_dir, 'PuritineSymlinkFarm.json')
3230-puritine_click_config_file = os.path.join('libertine-config', 'libertine', 'ContainersConfig.json')
3231-
3232-
3233-def symlink_farm_entries_count():
3234- if (puritine_symlink_farm_list and
3235- puritine_symlink_farm_list['customContainers']):
3236- return len(puritine_symlink_farm_list['customContainers'])
3237-
3238- return 0
3239-
3240-
3241-def puritine_symlink_exists(symlink):
3242- for file in os.listdir(puritine_hook_dir):
3243- if file == symlink:
3244- return True
3245-
3246- return False
3247-
3248-
3249-def get_symlink_name(puritine_symlink_farm_list, container_id):
3250- if puritine_symlink_farm_list:
3251- for container in puritine_symlink_farm_list['customContainers']:
3252- if container['id'] == container_id:
3253- return (puritine_symlink_farm_list['customContainers'].index(container), container['symlinkName'])
3254-
3255- return (0, None)
3256-
3257-
3258-def find_removed_puritine_symlinks(puritine_symlink_farm_list):
3259- for container in puritine_symlink_farm_list['customContainers']:
3260- if puritine_symlink_exists(container['symlinkName']):
3261- continue
3262- else:
3263- manager_cmd = "libertine-container-manager destroy -i " + container['id']
3264- cmd = shlex.split(manager_cmd)
3265- subprocess.Popen(cmd).wait()
3266- puritine_symlink_farm_list['customContainers'].remove(container)
3267-
3268- return puritine_symlink_farm_list
3269-
3270-
3271-def find_new_or_updated_puritine_symlinks(puritine_symlink_farm_list):
3272- symlinks = os.listdir(puritine_hook_dir)
3273-
3274- for symlink in symlinks:
3275- puritine_click_path = os.path.join(puritine_hook_dir, symlink)
3276- if not os.path.islink(puritine_click_path):
3277- continue
3278-
3279- config_file_path = os.path.join(puritine_click_path, puritine_click_config_file)
3280-
3281- with open(config_file_path, 'r') as fd:
3282- container_list = json.load(fd)
3283-
3284- container_id = container_list['containerList'][0]['id']
3285-
3286- (index, symlink_name) = get_symlink_name(puritine_symlink_farm_list, container_id)
3287-
3288- if symlink_name:
3289- # Package exists and symlink name is the same- no update
3290- if symlink_name == symlink:
3291- continue
3292- else:
3293- puritine_symlink_farm_list['customContainers'][index]['symlinkName'] = symlink
3294- else:
3295- symlink_obj = {'symlinkName': symlink, 'id': container_id}
3296- if 'customContainers' not in puritine_symlink_farm_list:
3297- puritine_symlink_farm_list['customContainers'] = [symlink_obj]
3298- else:
3299- puritine_symlink_farm_list['customContainers'].append(symlink_obj)
3300-
3301- puritine_click_rootfs_path = os.path.join(puritine_click_path, 'libertine-data', 'libertine-container',
3302- container_id, 'rootfs')
3303-
3304- libertine_containers_path = libertine.utils.get_libertine_containers_dir_path()
3305- puritine_container_path = os.path.join(libertine_containers_path, container_id)
3306- puritine_container_rootfs_path = os.path.join(puritine_container_path, 'rootfs')
3307-
3308- if not os.path.exists(puritine_container_path):
3309- os.makedirs(puritine_container_path)
3310- else:
3311- os.remove(puritine_container_rootfs_path)
3312-
3313- # Link to the click packages container
3314- os.symlink(puritine_click_rootfs_path, puritine_container_rootfs_path)
3315-
3316- # Copy any user data that does not exist
3317- libertine_user_data_path = libertine.utils.get_libertine_container_userdata_dir_path(container_id)
3318- if not os.path.exists(libertine_user_data_path):
3319- puritine_click_user_data_path = os.path.join(puritine_click_path, 'libertine-config', 'libertine-container',
3320- 'user-data', container_id)
3321- shutil.copytree(puritine_click_user_data_path, libertine_user_data_path)
3322-
3323- # Update the main ContainerConfig.json
3324- manager_cmd = "libertine-container-manager merge-configs -f " + config_file_path
3325- cmd = shlex.split(manager_cmd)
3326- subprocess.Popen(cmd).wait()
3327-
3328- return puritine_symlink_farm_list
3329-
3330-
3331-def favorite_libertine_scope():
3332- libertine_scope_value = 'scope://libertine-scope.ubuntu_libertine-scope'
3333- click_scope_value = 'scope://clickscope'
3334- schema = 'com.canonical.Unity.Dash'
3335- schema_key = 'favorite-scopes'
3336-
3337- settings = Gio.Settings.new(schema)
3338-
3339- favorites = settings.get_value(schema_key)
3340-
3341- array = favorites.dup_strv()
3342-
3343- if not libertine_scope_value in array:
3344- array.insert(array.index(click_scope_value) + 1, libertine_scope_value)
3345- new_favorites = GLib.Variant.new_strv(array)
3346- settings.set_value(schema_key, new_favorites)
3347- del new_favorites
3348-
3349- del array
3350- del favorites
3351- del settings
3352-
3353-
3354-if __name__ == '__main__':
3355- puritine_symlink_farm_list = {}
3356- update_libertine_scope = libertine.utils.set_session_dbus_env_var()
3357-
3358- if not os.path.exists(puritine_hook_dir):
3359- os.makedirs(puritine_hook_dir)
3360-
3361- if (os.path.exists(puritine_symlink_farm_file) and
3362- os.path.getsize(puritine_symlink_farm_file) != 0):
3363- with open(puritine_symlink_farm_file, 'r') as fd:
3364- puritine_symlink_farm_list = json.load(fd)
3365-
3366- count_at_start = symlink_farm_entries_count()
3367-
3368- puritine_symlink_farm_list = find_new_or_updated_puritine_symlinks(puritine_symlink_farm_list)
3369-
3370- if puritine_symlink_farm_list:
3371- puritine_symlink_farm_list = find_removed_puritine_symlinks(puritine_symlink_farm_list)
3372-
3373- count_at_finish = symlink_farm_entries_count()
3374-
3375- with open(puritine_symlink_farm_file, 'w') as fd:
3376- json.dump(puritine_symlink_farm_list, fd, sort_keys=True, indent=4)
3377- fd.write('\n')
3378-
3379- if count_at_start == 0 and count_at_finish > 0 and update_libertine_scope:
3380- favorite_libertine_scope()
3381-
3382- if update_libertine_scope:
3383- libertine.utils.refresh_libertine_scope()

Subscribers

People subscribed via source and target branches