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
=== modified file '.bzrignore'
--- .bzrignore 2016-10-25 17:27:50 +0000
+++ .bzrignore 2017-01-05 20:19:05 +0000
@@ -2,3 +2,11 @@
2po/Makefile.in.in2po/Makefile.in.in
3__pycache__3__pycache__
4tests/unit/.cache4tests/unit/.cache
5*.click-hook
6
7prime/
8stage/
9*.snap
10parts/*
11!parts/plugins
12parts/plugins/__pycache__
513
=== modified file 'CMakeLists.txt'
--- CMakeLists.txt 2016-11-03 18:28:54 +0000
+++ CMakeLists.txt 2017-01-05 20:19:05 +0000
@@ -2,7 +2,7 @@
2cmake_policy(SET CMP0048 NEW)2cmake_policy(SET CMP0048 NEW)
33
4project(libertine4project(libertine
5 VERSION 1.4.3)5 VERSION 1.5)
66
7set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")7set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake" "${CMAKE_MODULE_PATH}")
88
99
=== modified file 'common/ContainerConfigList.h'
--- common/ContainerConfigList.h 2016-09-26 18:17:07 +0000
+++ common/ContainerConfigList.h 2017-01-05 20:19:05 +0000
@@ -55,11 +55,10 @@
55 * Display roles for a container config.55 * Display roles for a container config.
56 */56 */
57 enum class DataRole57 enum class DataRole
58 : int
59 {58 {
60 ContainerId = Qt::UserRole + 1, /**< The container ID */59 ContainerId = Qt::UserRole + 1, /**< The container ID */
61 ContainerName, /**< The container name */60 ContainerName, /**< The container name */
62 ContainerType, /**< The type of container - lxc or chroot */61 ContainerType, /**< The type of container */
63 DistroSeries, /**< The distro from which the container was built */62 DistroSeries, /**< The distro from which the container was built */
64 InstallStatus, /**< Current container install status */63 InstallStatus, /**< Current container install status */
65 Error /**< last role (error) */64 Error /**< last role (error) */
6665
=== modified file 'data/CMakeLists.txt'
--- data/CMakeLists.txt 2016-11-01 20:10:58 +0000
+++ data/CMakeLists.txt 2017-01-05 20:19:05 +0000
@@ -6,11 +6,9 @@
6 DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME})6 DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME})
7install(FILES libertine-xmir.conf7install(FILES libertine-xmir.conf
8 DESTINATION ${CMAKE_INSTALL_DATADIR}/upstart/sessions)8 DESTINATION ${CMAKE_INSTALL_DATADIR}/upstart/sessions)
9install(FILES libertine-lxc-sudo9install(FILES libertine-lxc-sudo libertine-lxd-sudo
10 DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sudoers.d)10 DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/sudoers.d)
11install(FILES com.canonical.libertine.LxcManager.service com.canonical.libertine.ContainerManager.service11install(FILES com.canonical.libertine.ContainerManager.service
12 com.canonical.libertine.LxcManager.service
13 com.canonical.libertine.LxdManager.service
12 DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services)14 DESTINATION ${CMAKE_INSTALL_DATADIR}/dbus-1/services)
13
14configure_file("python3-libertine-chroot.click-hook.in"
15 "${CMAKE_SOURCE_DIR}/debian/python3-libertine-chroot.click-hook"
16 @ONLY)
1715
=== added file 'data/com.canonical.libertine.LxdManager.service'
--- data/com.canonical.libertine.LxdManager.service 1970-01-01 00:00:00 +0000
+++ data/com.canonical.libertine.LxdManager.service 2017-01-05 20:19:05 +0000
@@ -0,0 +1,3 @@
1[D-BUS Service]
2Name=com.canonical.libertine.LxdManager
3Exec=/usr/bin/libertine-lxd-manager
04
=== added file 'data/libertine-lxd-sudo'
--- data/libertine-lxd-sudo 1970-01-01 00:00:00 +0000
+++ data/libertine-lxd-sudo 2017-01-05 20:19:05 +0000
@@ -0,0 +1,1 @@
1ALL ALL=(ALL) NOPASSWD:/usr/bin/libertine-lxd-setup
02
=== removed file 'data/python3-libertine-chroot.click-hook.in'
--- data/python3-libertine-chroot.click-hook.in 2016-09-06 12:36:47 +0000
+++ data/python3-libertine-chroot.click-hook.in 1970-01-01 00:00:00 +0000
@@ -1,4 +0,0 @@
1Pattern: ${home}/.cache/libertine/puritine/${id}
2Exec: @CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_LIBDIR@/libertine/update-puritine-containers
3User-Level: yes
4Hook-Name: puritine
50
=== added file 'data/snap-runner.wrapper'
--- data/snap-runner.wrapper 1970-01-01 00:00:00 +0000
+++ data/snap-runner.wrapper 2017-01-05 20:19:05 +0000
@@ -0,0 +1,11 @@
1#!/bin/bash
2# additional env modifications on top of desktop-launch
3
4export LD_LIBRARY_PATH=$SNAP/usr/lib/$ARCH/fakechroot:$LD_LIBRARY_PATH
5export DEBOOTSTRAP_DIR=$SNAP/usr/share/debootstrap
6
7# Useful debug variables
8# export FAKECHROOT_DEBUG=1
9# export LIBERTINE_DEBUG=1
10
11exec $SNAP/bin/desktop-launch $@
012
=== modified file 'debian/changelog'
--- debian/changelog 2016-12-05 21:10:03 +0000
+++ debian/changelog 2017-01-05 20:19:05 +0000
@@ -1,3 +1,23 @@
1libertine (1.5-0ubuntu1) UNRELEASED; urgency=medium
2
3 [ Chris Townsend ]
4 * Drop support for the Puritine click package as that is Vivid only
5 and a Vivid Libertine branch exists for any future fixes.
6 * Only set the lxc log when a container is defined during class init.
7 (LP: #1653973)
8 * Bump version to 1.5 for new upstream release.
9
10 [ Larry Price ]
11 * Logic for bundling libertine as a snap built from source.
12 * Catch exceptions raised during container creation.
13 * Initial implementation of lxd backend. (LP: #1580612)
14 * Use the libertine logger and LIBERTINE_DEBUG variable everywhere.
15 * Update configure bind-mount logic given new lxd backend.
16 * Use dpkg to find package name when installing local deb.
17 * Create d-bus service for lxd container management.
18
19 -- Chris Townsend <christopher.townsend@canonical.com> Thu, 05 Jan 2017 11:46:42 -0500
20
1libertine (1.4.4+17.04.20161205-0ubuntu1) zesty; urgency=medium21libertine (1.4.4+17.04.20161205-0ubuntu1) zesty; urgency=medium
222
3 [ Chris Townsend ]23 [ Chris Townsend ]
424
=== modified file 'debian/control'
--- debian/control 2016-11-22 17:47:05 +0000
+++ debian/control 2017-01-05 20:19:05 +0000
@@ -2,8 +2,7 @@
2Section: utils2Section: utils
3Priority: extra3Priority: extra
4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
5Build-Depends: click-dev,5Build-Depends: cmake,
6 cmake,
7 cmake-extras,6 cmake-extras,
8 debhelper (>= 9),7 debhelper (>= 9),
9 dh-translations,8 dh-translations,
@@ -61,8 +60,7 @@
61Depends: libertine-qt-common,60Depends: libertine-qt-common,
62 libertine-tools,61 libertine-tools,
63 python3-libertine-lxc,62 python3-libertine-lxc,
64 qml-module-qtquick2,63 libsystemsettings1,
65 qtdeclarative5-ubuntu-ui-toolkit-plugin,
66 ${misc:Depends},64 ${misc:Depends},
67 ${shlibs:Depends}65 ${shlibs:Depends}
68Enhances: ubuntu-system-settings66Enhances: ubuntu-system-settings
@@ -184,6 +182,21 @@
184 Libertine sandbox. It requires support for unprivileged LXC containers in the182 Libertine sandbox. It requires support for unprivileged LXC containers in the
185 Linux kernel.183 Linux kernel.
186184
185Package: python3-libertine-lxd
186Architecture: any
187Section: python
188Multi-Arch: allowed
189Depends: lxd,
190 python3-libertine,
191 python3-pexpect,
192 python3-pylxd,
193 ${misc:Depends},
194 ${python3:Depends}
195Description: Python3 scripts for the Libertine application sandbox
196 This package provides the LXD-based container back end module for the
197 Libertine sandbox. It requires support for unprivileged LXD containers in the
198 Linux kernel.
199
187Package: python3-libertine-chroot200Package: python3-libertine-chroot
188Architecture: any201Architecture: any
189Section: python202Section: python
190203
=== modified file 'debian/python3-libertine-chroot.install'
--- debian/python3-libertine-chroot.install 2016-10-07 21:09:41 +0000
+++ debian/python3-libertine-chroot.install 2017-01-05 20:19:05 +0000
@@ -1,2 +1,1 @@
1usr/lib/*/libertine/update-puritine-containers
2usr/lib/python*/*/libertine/ChrootContainer.py1usr/lib/python*/*/libertine/ChrootContainer.py
32
=== added file 'debian/python3-libertine-lxd.install'
--- debian/python3-libertine-lxd.install 1970-01-01 00:00:00 +0000
+++ debian/python3-libertine-lxd.install 2017-01-05 20:19:05 +0000
@@ -0,0 +1,5 @@
1etc/sudoers.d/libertine-lxd-sudo
2usr/bin/libertine-lxd-setup
3usr/bin/libertine-lxd-manager
4usr/lib/python*/*/libertine/LxdContainer.py
5usr/share/dbus-1/services/com.canonical.libertine.LxdManager.service
06
=== modified file 'debian/python3-libertine.install'
--- debian/python3-libertine.install 2016-10-28 19:37:32 +0000
+++ debian/python3-libertine.install 2017-01-05 20:19:05 +0000
@@ -4,5 +4,6 @@
4usr/lib/python*/*/libertine/Libertine.py4usr/lib/python*/*/libertine/Libertine.py
5usr/lib/python*/*/libertine/__init__.py5usr/lib/python*/*/libertine/__init__.py
6usr/lib/python*/*/libertine/launcher6usr/lib/python*/*/libertine/launcher
7usr/lib/python*/*/libertine/lifecycle
7usr/lib/python*/*/libertine/service8usr/lib/python*/*/libertine/service
8usr/lib/python*/*/libertine/utils.py9usr/lib/python*/*/libertine/utils.py
910
=== modified file 'debian/rules'
--- debian/rules 2016-12-05 15:32:46 +0000
+++ debian/rules 2017-01-05 20:19:05 +0000
@@ -1,7 +1,7 @@
1#!/usr/bin/make -f1#!/usr/bin/make -f
22
3%:3%:
4 dh $@ --with python3,gir,click4 dh $@ --with python3,gir
55
6override_dh_auto_test:6override_dh_auto_test:
7 dbus-run-session -- dh_auto_test7 dbus-run-session -- dh_auto_test
88
=== modified file 'liblibertine/libertine.cpp'
--- liblibertine/libertine.cpp 2016-10-26 13:58:19 +0000
+++ liblibertine/libertine.cpp 2017-01-05 20:19:05 +0000
@@ -67,8 +67,16 @@
67 g_dir_close(dir);67 g_dir_close(dir);
68 return nullptr;68 return nullptr;
69}69}
70}70
7171
72gchar*
73id_from_list_index(const ContainerConfigList& container_list, guint index)
74{
75 return (gchar*)container_list.data(container_list.index(index, 0),
76 (int)ContainerConfigList::DataRole::ContainerId)
77 .toString().toStdString().c_str();
78}
79}
7280
73gchar**81gchar**
74libertine_list_apps_for_container(const gchar* container_id)82libertine_list_apps_for_container(const gchar* container_id)
@@ -137,61 +145,68 @@
137gchar *145gchar *
138libertine_container_path(const gchar * container_id)146libertine_container_path(const gchar * container_id)
139{147{
148 g_return_val_if_fail(container_id != nullptr, nullptr);
149 LibertineConfig config;
150 ContainerConfigList container_list(&config);
140 gchar * path = nullptr;151 gchar * path = nullptr;
141 g_return_val_if_fail(container_id != nullptr, nullptr);
142152
143 path = g_build_filename(g_get_user_cache_dir(), "libertine-container", container_id, "rootfs", nullptr);153 if (g_strcmp0((gchar*)container_list.getContainerType(container_id).toStdString().c_str(), "lxd") == 0)
154 {
155 path = g_build_filename("/", "var", "lib", "lxd", "containers", container_id, "rootfs", nullptr);
156 }
157 else
158 {
159 path = g_build_filename(g_get_user_cache_dir(), "libertine-container", container_id, "rootfs", nullptr);
160 }
144161
145 if (g_file_test(path, G_FILE_TEST_EXISTS))162 if (g_file_test(path, G_FILE_TEST_EXISTS))
146 {163 {
147 return path;164 return path;
148 }165 }
149 else166
150 {167 g_free(path);
151 g_free(path);168 return nullptr;
152 return nullptr;
153 }
154}169}
155170
156171
157gchar *172gchar *
158libertine_container_home_path(const gchar * container_id)173libertine_container_home_path(const gchar * container_id)
159{174{
175 g_return_val_if_fail(container_id != nullptr, nullptr);
176 LibertineConfig config;
177 ContainerConfigList container_list(&config);
160 gchar * path = nullptr;178 gchar * path = nullptr;
161 g_return_val_if_fail(container_id != nullptr, nullptr);
162179
163 path = g_build_filename(g_get_user_data_dir(), "libertine-container", "user-data", container_id, nullptr);180 if (g_strcmp0((gchar*)container_list.getContainerType(container_id).toStdString().c_str(), "lxd") == 0)
181 {
182 path = g_build_filename("/", "var", "lib", "lxd", "containers", container_id, "rootfs", "home", g_get_user_name(), nullptr);
183 }
184 else
185 {
186 path = g_build_filename(g_get_user_data_dir(), "libertine-container", "user-data", container_id, nullptr);
187 }
164188
165 if (g_file_test(path, G_FILE_TEST_EXISTS))189 if (g_file_test(path, G_FILE_TEST_EXISTS))
166 {190 {
167 return path;191 return path;
168 }192 }
169 else
170 {
171 g_free(path);
172 return nullptr;
173 }
174193
194 g_free(path);
195 return nullptr;
175}196}
176197
177198
178gchar *199gchar *
179libertine_container_name(const gchar * container_id)200libertine_container_name(const gchar * container_id)
180{201{
181 guint container_count;
182 guint i;
183 gchar * container_name = nullptr;202 gchar * container_name = nullptr;
184 LibertineConfig config;203 LibertineConfig config;
185 ContainerConfigList container_list(&config);204 ContainerConfigList container_list(&config);
186 QVariant id;205 guint container_count = (guint)container_list.size();
187206
188 container_count = (guint)container_list.size();207 for (guint i = 0; i < container_count; ++i)
189
190 for (i = 0; i < container_count; ++i)
191 {208 {
192 id = container_list.data(container_list.index(i, 0), (int)ContainerConfigList::DataRole::ContainerId);209 if (g_strcmp0(id_from_list_index(container_list, i), container_id) == 0)
193
194 if (g_strcmp0((gchar *)id.toString().toStdString().c_str(), container_id) == 0)
195 {210 {
196 QVariant name = container_list.data(container_list.index(i, 0), (int)ContainerConfigList::DataRole::ContainerName);211 QVariant name = container_list.data(container_list.index(i, 0), (int)ContainerConfigList::DataRole::ContainerName);
197 container_name = g_strdup(name.toString().toStdString().c_str());212 container_name = g_strdup(name.toString().toStdString().c_str());
198213
=== added directory 'parts'
=== added directory 'parts/plugins'
=== added file 'parts/plugins/utils.py'
--- parts/plugins/utils.py 1970-01-01 00:00:00 +0000
+++ parts/plugins/utils.py 2017-01-05 20:19:05 +0000
@@ -0,0 +1,63 @@
1# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
2#
3# Copyright (C) 2016 Canonical Ltd
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17import re
18from snapcraft import file_utils
19
20
21def fix_shebangs(path):
22 file_utils.replace_in_file(path, re.compile(r''),
23 re.compile(r'^#!.*python'),
24 r'#!/usr/bin/env python')
25
26
27def _sanitize(dep):
28 return re.sub(r"\(.*\)", "", re.sub(r"\[.*\]", "", dep)).replace(',', '').strip()
29
30
31class DependsParser(object):
32 def __init__(self):
33 self.keyword = 'Depends:'
34 self._parsing = False
35 self._deps = []
36 self._packages = []
37
38 @property
39 def deps(self):
40 return [dep for dep in self._deps if dep not in self._packages]
41
42 def parse(self, line):
43 if self._parsing:
44 if ':' in line:
45 if not line.strip().startswith('${'):
46 self._parsing = False
47 else:
48 self._deps.append(_sanitize(line))
49
50 if line.startswith(self.keyword):
51 self._parsing = True
52 possible_dep = line.lstrip(self.keyword)
53 if not possible_dep.isspace() and not possible_dep.strip().startswith('${'):
54 self._deps.append(_sanitize(possible_dep))
55
56 if line.startswith('Package:'):
57 self._packages.append(line.lstrip('Package:').strip())
58
59
60class BuildDependsParser(DependsParser):
61 def __init__(self):
62 super().__init__()
63 self.keyword = 'Build-Depends:'
064
=== added file 'parts/plugins/x-libertine-deps.py'
--- parts/plugins/x-libertine-deps.py 1970-01-01 00:00:00 +0000
+++ parts/plugins/x-libertine-deps.py 2017-01-05 20:19:05 +0000
@@ -0,0 +1,155 @@
1# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
2#
3# Copyright (C) 2016 Canonical Ltd
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
18import fileinput
19import os
20import re
21import shlex
22import snapcraft.plugins.nil
23import sys
24import subprocess
25import utils # local
26
27
28def _arch():
29 cmd = subprocess.Popen(shlex.split('dpkg-architecture -qDEB_HOST_ARCH_CPU'), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
30 out, err = cmd.communicate()
31 arch = str(out, 'utf-8').strip()
32 if arch == 'amd64':
33 return 'x86_64-linux-gnu'
34 elif arch == 'armhf':
35 return 'arm-linux-gnueabihf'
36 elif arch == 'arm64':
37 return 'aarch64-linux-gnu'
38
39 return '{}-linux-gnu'.format(arch)
40
41
42class LibertineDependenciesPlugin(snapcraft.plugins.nil.NilPlugin):
43 def __init__(self, name, options, project):
44 super().__init__(name, options, project)
45 self._arch = _arch()
46
47 deps_parser = utils.DependsParser()
48 with open('debian/control') as control:
49 for line in control.readlines():
50 deps_parser.parse(line)
51 self.stage_packages.extend(deps_parser.deps)
52
53 self._ignore_duplicate_files()
54
55 @classmethod
56 def schema(cls):
57 return super().schema()
58
59 def enable_cross_compilation(self):
60 return super().enable_cross_compilation()
61
62 def env(self, root):
63 return super().env(root) + ['ARCH={}'.format(self._arch)]
64
65 # By design, snapcraft ignores all pre- and post-inst scripts which
66 # result in a bunch of packages that just don't work.
67 def _run_preinst_postinst(self):
68 # We need to create the lxc-usernet
69 if not os.path.exists(self.installdir + '/etc/lxc/lxc-usernet'):
70 with open(self.installdir + '/etc/lxc/lxc-usernet', 'w') as f:
71 f.write('# USERNAME TYPE BRIDGE COUNT')
72
73 # We need to run update-alternatives to create /usr/bin/fakeroot
74 admindir = self.installdir + '/var/lib/dpkg/alternatives'
75 altdir = self.installdir + '/etc/alternatives'
76 os.makedirs(admindir, exist_ok=True)
77 os.makedirs(altdir, exist_ok=True)
78 subprocess.Popen(shlex.split('\
79 update-alternatives --admindir {0} --altdir {1} --install \
80 {2}/usr/bin/fakeroot fakeroot {2}/usr/bin/fakeroot-sysv 50 \
81 --slave {2}/usr/share/man/man1/fakeroot.1.gz \
82 fakeroot.1.gz {2}/usr/share/man/man1/fakeroot-sysv.1.gz \
83 --slave {2}/usr/share/man/man1/faked.1.gz \
84 faked.1.gz {2}/usr/share/man/man1/faked-sysv.1.gz \
85 --slave {2}/usr/share/man/es/man1/fakeroot.1.gz \
86 fakeroot.es.1.gz {2}/usr/share/man/es/man1/fakeroot-sysv.1.gz \
87 --slave {2}/usr/share/man/es/man1/faked.1.gz \
88 faked.es.1.gz {2}/usr/share/man/es/man1/faked-sysv.1.gz \
89 --slave {2}/usr/share/man/fr/man1/fakeroot.1.gz \
90 fakeroot.fr.1.gz {2}/usr/share/man/fr/man1/fakeroot-sysv.1.gz \
91 --slave {2}/usr/share/man/fr/man1/faked.1.gz \
92 faked.fr.1.gz {2}/usr/share/man/fr/man1/faked-sysv.1.gz \
93 --slave {2}/usr/share/man/sv/man1/fakeroot.1.gz \
94 fakeroot.sv.1.gz {2}/usr/share/man/sv/man1/fakeroot-sysv.1.gz \
95 --slave {2}/usr/share/man/sv/man1/faked.1.gz \
96 faked.sv.1.gz {2}/usr/share/man/sv/man1/faked-sysv.1.gz\
97 '.format(admindir, altdir, self.installdir))).wait()
98 subprocess.Popen(shlex.split('\
99 update-alternatives --admindir {0} --altdir {1} --install \
100 {2}/usr/bin/fakeroot fakeroot {2}/usr/bin/fakeroot-tcp 30 \
101 --slave {2}/usr/share/man/man1/fakeroot.1.gz \
102 fakeroot.1.gz {2}/usr/share/man/man1/fakeroot-tcp.1.gz \
103 --slave {2}/usr/share/man/man1/faked.1.gz \
104 faked.1.gz {2}/usr/share/man/man1/faked-tcp.1.gz \
105 --slave {2}/usr/share/man/es/man1/fakeroot.1.gz \
106 fakeroot.es.1.gz {2}/usr/share/man/es/man1/fakeroot-tcp.1.gz \
107 --slave {2}/usr/share/man/es/man1/faked.1.gz \
108 faked.es.1.gz {2}/usr/share/man/es/man1/faked-tcp.1.gz \
109 --slave {2}/usr/share/man/fr/man1/fakeroot.1.gz \
110 fakeroot.fr.1.gz {2}/usr/share/man/fr/man1/fakeroot-tcp.1.gz \
111 --slave {2}/usr/share/man/fr/man1/faked.1.gz \
112 faked.fr.1.gz {2}/usr/share/man/fr/man1/faked-tcp.1.gz \
113 --slave {2}/usr/share/man/sv/man1/fakeroot.1.gz \
114 fakeroot.sv.1.gz {2}/usr/share/man/sv/man1/fakeroot-tcp.1.gz \
115 --slave {2}/usr/share/man/sv/man1/faked.1.gz \
116 faked.sv.1.gz {2}/usr/share/man/sv/man1/faked-tcp.1.gz\
117 '.format(admindir, altdir, self.installdir))).wait()
118
119 def _fix_symlinks(self):
120 for root, dirs, files in os.walk(self.installdir):
121 for f in files:
122 path = '{}/{}'.format(root, f)
123 if os.path.islink(path):
124 link = os.readlink(path)
125 if link.startswith('/'): # needs fixing
126 os.remove(path)
127 os.symlink(os.path.relpath(link, root), path)
128
129 def _fix_fakeroot(self):
130 for line in fileinput.FileInput('{}/usr/bin/fakeroot'.format(self.installdir), inplace=True):
131 if line.startswith('FAKEROOT_PREFIX='):
132 sys.stdout.write('FAKEROOT_PREFIX=${SNAP}/usr # Updated by x-libertine.py\n')
133 elif line.startswith('FAKEROOT_BINDIR='):
134 sys.stdout.write('FAKEROOT_BINDIR=${SNAP}/usr/bin # Updated by x-libertine.py\n')
135 elif line.startswith('PATHS='):
136 sys.stdout.write('PATHS=${FAKEROOT_PREFIX}/lib/%s/libfakeroot:' \
137 '${FAKEROOT_PREFIX}/lib64/libfakeroot:' \
138 '${FAKEROOT_PREFIX}/lib32/libfakeroot' \
139 ' # Updated by x-libertine.py\n' % self._arch)
140 else:
141 sys.stdout.write(line)
142
143 def _ignore_duplicate_files(self):
144 self.options.stage.extend([
145 '-usr/lib/{}/liblibertine.so*'.format(self._arch)
146 ])
147
148 def build(self):
149 super().build()
150
151 utils.fix_shebangs(self.installdir + '/usr/bin')
152
153 self._run_preinst_postinst()
154 self._fix_symlinks()
155 self._fix_fakeroot()
0156
=== added file 'parts/plugins/x-libertine.py'
--- parts/plugins/x-libertine.py 1970-01-01 00:00:00 +0000
+++ parts/plugins/x-libertine.py 2017-01-05 20:19:05 +0000
@@ -0,0 +1,45 @@
1# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*-
2#
3# Copyright (C) 2016 Canonical Ltd
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 3 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17
18import snapcraft.plugins.cmake
19import utils # local
20
21class LibertinePlugin(snapcraft.plugins.cmake.CMakePlugin):
22 def __init__(self, name, options, project):
23 super().__init__(name, options, project)
24
25 deps = utils.BuildDependsParser()
26
27 with open('debian/control') as control:
28 for line in control.readlines():
29 deps.parse(line)
30
31 self.build_packages.extend(deps.deps)
32 self.options.configflags.extend(['-DCMAKE_INSTALL_PREFIX=/usr'])
33
34 @classmethod
35 def schema(cls):
36 return super().schema()
37
38 def enable_cross_compilation(self):
39 return super().enable_cross_compilation()
40
41 def build(self):
42 super().build()
43
44 # Fix all shebangs to use the in-snap python.
45 utils.fix_shebangs(self.installdir + '/usr/bin')
046
=== modified file 'python/CMakeLists.txt'
--- python/CMakeLists.txt 2015-07-20 15:00:27 +0000
+++ python/CMakeLists.txt 2017-01-05 20:19:05 +0000
@@ -1,4 +1,4 @@
1execute_process(COMMAND python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"1execute_process(COMMAND /usr/bin/python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())"
2 OUTPUT_VARIABLE python_site_packages2 OUTPUT_VARIABLE python_site_packages
3 OUTPUT_STRIP_TRAILING_WHITESPACE)3 OUTPUT_STRIP_TRAILING_WHITESPACE)
44
55
=== modified file 'python/libertine/AppDiscovery.py'
--- python/libertine/AppDiscovery.py 2016-01-04 17:41:08 +0000
+++ python/libertine/AppDiscovery.py 2017-01-05 20:19:05 +0000
@@ -188,7 +188,7 @@
188 if desktop_file_is_showable(desktop_entry):188 if desktop_file_is_showable(desktop_entry):
189 yield AppInfo(desktop_file_name, desktop_entry, icon_cache)189 yield AppInfo(desktop_file_name, desktop_entry, icon_cache)
190 except Exception as ex:190 except Exception as ex:
191 print("error processing {}: {}".format(desktop_file_name, ex), file=sys.stderr)191 utils.get_logger().error("error processing {}: {}".format(desktop_file_name, ex))
192192
193193
194class AppLauncherCache(object):194class AppLauncherCache(object):
@@ -218,5 +218,3 @@
218 default=lambda o: o.__dict__,218 default=lambda o: o.__dict__,
219 sort_keys=True,219 sort_keys=True,
220 indent=4)220 indent=4)
221
222
223221
=== modified file 'python/libertine/ChrootContainer.py'
--- python/libertine/ChrootContainer.py 2016-12-02 17:46:47 +0000
+++ python/libertine/ChrootContainer.py 2017-01-05 20:19:05 +0000
@@ -71,10 +71,10 @@
71 shutil.rmtree(container_root)71 shutil.rmtree(container_root)
72 return True72 return True
73 except Exception as e:73 except Exception as e:
74 print("%s" % e)74 utils.get_logger().error("%s" % e)
75 return False75 return False
7676
77 def create_libertine_container(self, password=None, multiarch=False, verbosity=1):77 def create_libertine_container(self, password=None, multiarch=False):
78 # Create the actual chroot78 # Create the actual chroot
79 command_line = "{} fakeroot debootstrap --verbose --variant=fakechroot {} {}".format(79 command_line = "{} fakeroot debootstrap --verbose --variant=fakechroot {} {}".format(
80 self._build_fakechroot_command(), self.installed_release, self.root_path)80 self._build_fakechroot_command(), self.installed_release, self.root_path)
@@ -83,12 +83,12 @@
83 cmd.wait()83 cmd.wait()
8484
85 if cmd.returncode != 0:85 if cmd.returncode != 0:
86 print("Failed to create container")86 utils.get_logger().error("Failed to create container")
87 self.destroy_libertine_container()87 self.destroy_libertine_container()
88 return False88 return False
8989
90 # Remove symlinks as they can cause ill-behaved recursive behavior in the chroot90 # Remove symlinks as they can cause ill-behaved recursive behavior in the chroot
91 print("Fixing chroot symlinks...")91 utils.get_logger().info("Fixing chroot symlinks...")
92 os.remove(os.path.join(self.root_path, 'dev'))92 os.remove(os.path.join(self.root_path, 'dev'))
93 os.remove(os.path.join(self.root_path, 'proc'))93 os.remove(os.path.join(self.root_path, 'proc'))
9494
@@ -109,8 +109,8 @@
109 else:109 else:
110 archive = "deb http://archive.ubuntu.com/ubuntu "110 archive = "deb http://archive.ubuntu.com/ubuntu "
111111
112 if verbosity == 1:112 utils.get_logger().info("Updating chroot's sources.list entries...")
113 print("Updating chroot's sources.list entries...")113
114 with open(os.path.join(self.root_path, 'etc', 'apt', 'sources.list'), 'a') as fd:114 with open(os.path.join(self.root_path, 'etc', 'apt', 'sources.list'), 'a') as fd:
115 fd.write(archive + self.installed_release + "-updates main\n")115 fd.write(archive + self.installed_release + "-updates main\n")
116 fd.write(archive + self.installed_release + " universe\n")116 fd.write(archive + self.installed_release + " universe\n")
@@ -121,46 +121,43 @@
121 utils.create_libertine_user_data_dir(self.container_id)121 utils.create_libertine_user_data_dir(self.container_id)
122122
123 if multiarch and self.architecture == 'amd64':123 if multiarch and self.architecture == 'amd64':
124 if verbosity == 1:124 utils.get_logger().info("Adding i386 multiarch support...")
125 print("Adding i386 multiarch support...")
126 self.run_in_container("dpkg --add-architecture i386")125 self.run_in_container("dpkg --add-architecture i386")
127126
128 if verbosity == 1:127 utils.get_logger().info("Updating the contents of the container after creation...")
129 print("Updating the contents of the container after creation...")128 self.update_packages()
130 self.update_packages(verbosity)
131129
132 for package in self.default_packages:130 for package in self.default_packages:
133 if not self.install_package(package, verbosity, update_cache=False):131 if not self.install_package(package, update_cache=False):
134 print("Failure installing %s during container creation" % package)132 utils.get_logger().error("Failure installing %s during container creation" % package)
135 self.destroy_libertine_container()133 self.destroy_libertine_container()
136 return False134 return False
137135
138 if self.installed_release == "vivid" or self.installed_release == "xenial":136 if self.installed_release == "vivid" or self.installed_release == "xenial":
139 if verbosity == 1:137 utils.get_logger().info("Installing the Stable Overlay PPA...")
140 print("Installing the Stable Overlay PPA...")138 if not self.install_package("software-properties-common", update_cache=False):
141 if not self.install_package("software-properties-common", verbosity, update_cache=False):139 utils.get_logger().error("Failure installing software-properties-common during container creation")
142 print("Failure installing software-properties-common during container creation")
143 self.destroy_libertine_container()140 self.destroy_libertine_container()
144 return False141 return False
145142
146 self.run_in_container("add-apt-repository ppa:ci-train-ppa-service/stable-phone-overlay -y")143 self.run_in_container("add-apt-repository ppa:ci-train-ppa-service/stable-phone-overlay -y")
147 self.update_packages(verbosity)144 self.update_packages()
148145
149 # Check if the container was created as root and chown the user directories as necessary146 # Check if the container was created as root and chown the user directories as necessary
150 chown_recursive_dirs(utils.get_libertine_container_userdata_dir_path(self.container_id))147 chown_recursive_dirs(utils.get_libertine_container_userdata_dir_path(self.container_id))
151148
152 return True149 return True
153150
154 def update_packages(self, verbosity=1):151 def update_packages(self):
155 retcode = super().update_packages(verbosity)152 retcode = super().update_packages()
156 self._run_ldconfig(verbosity)153 self._run_ldconfig()
157 return retcode == 0154 return retcode == 0
158155
159 def install_package(self, package_name, verbosity=1, no_dialog=False, update_cache=True):156 def install_package(self, package_name, no_dialog=False, update_cache=True):
160 returncode = super().install_package(package_name, verbosity, no_dialog, update_cache)157 returncode = super().install_package(package_name, no_dialog, update_cache)
161158
162 if returncode:159 if returncode:
163 self._run_ldconfig(verbosity)160 self._run_ldconfig()
164161
165 return returncode162 return returncode
166163
@@ -197,8 +194,12 @@
197 mounts = self._sanitize_bind_mounts(utils.get_common_xdg_user_directories() + \194 mounts = self._sanitize_bind_mounts(utils.get_common_xdg_user_directories() + \
198 ContainersConfig().get_container_bind_mounts(self.container_id))195 ContainersConfig().get_container_bind_mounts(self.container_id))
199 for user_dir in utils.generate_binding_directories(mounts, home_path):196 for user_dir in utils.generate_binding_directories(mounts, home_path):
200 user_dir_path = os.path.join(home_path, user_dir[1])197 if os.path.isabs(user_dir[1]):
201 bind_mounts += " -b \"%s:%s\"" % (user_dir[0], user_dir_path)198 path = user_dir[1]
199 else:
200 path = os.path.join(home_path, user_dir[1])
201
202 bind_mounts += " -b \"%s:%s\"" % (user_dir[0], path)
202203
203 proot_cmd += bind_mounts204 proot_cmd += bind_mounts
204205
@@ -233,16 +234,14 @@
233234
234 args = shlex.split(proot_cmd)235 args = shlex.split(proot_cmd)
235 args.extend(app_exec_line)236 args.extend(app_exec_line)
236 app = psutil.Popen(args, env=environ)237 return psutil.Popen(args, env=environ)
237 return app
238238
239 def finish_application(self, app):239 def finish_application(self, app):
240 utils.terminate_window_manager(self._window_manager)240 utils.terminate_window_manager(self._window_manager)
241 app.wait()241 app.wait()
242242
243 def _run_ldconfig(self, verbosity=1):243 def _run_ldconfig(self):
244 if verbosity == 1:244 utils.get_logger().info("Refreshing the container's dynamic linker run-time bindings...")
245 print("Refreshing the container's dynamic linker run-time bindings...")
246245
247 command_line = self._build_privileged_proot_cmd() + " ldconfig.REAL"246 command_line = self._build_privileged_proot_cmd() + " ldconfig.REAL"
248247
249248
=== modified file 'python/libertine/ContainersConfig.py'
--- python/libertine/ContainersConfig.py 2016-11-16 17:58:38 +0000
+++ python/libertine/ContainersConfig.py 2017-01-05 20:19:05 +0000
@@ -214,12 +214,12 @@
214214
215 def check_container_id(self, container_id):215 def check_container_id(self, container_id):
216 if container_id and not self.container_exists(container_id):216 if container_id and not self.container_exists(container_id):
217 print("Container id \'%s\' does not exist." % container_id, file=sys.stderr)217 libertine.utils.get_logger().error("Container id \'%s\' does not exist." % container_id)
218 sys.exit(1)218 sys.exit(1)
219 elif not container_id:219 elif not container_id:
220 container_id = self.get_default_container_id()220 container_id = self.get_default_container_id()
221 if container_id is None:221 if container_id is None:
222 print("No default container available.")222 libertine.utils.get_logger().error("No default container available.")
223 sys.exit(1)223 sys.exit(1)
224224
225 return container_id225 return container_id
@@ -260,7 +260,7 @@
260260
261 def delete_container(self, container_id):261 def delete_container(self, container_id):
262 if not self.container_list:262 if not self.container_list:
263 print("Unable to delete container. No containers defined.")263 libertine.utils.get_logger().error("Unable to delete container. No containers defined.")
264 sys.exit(1)264 sys.exit(1)
265265
266 container = self._get_container_entry(container_id)266 container = self._get_container_entry(container_id)
267267
=== modified file 'python/libertine/HostInfo.py'
--- python/libertine/HostInfo.py 2016-10-28 18:11:12 +0000
+++ python/libertine/HostInfo.py 2017-01-05 20:19:05 +0000
@@ -13,6 +13,7 @@
13# with this program. If not, see <http://www.gnu.org/licenses/>.13# with this program. If not, see <http://www.gnu.org/licenses/>.
1414
15import lsb_release15import lsb_release
16import os
16import platform17import platform
17import subprocess18import subprocess
1819
@@ -69,3 +70,7 @@
69 parser.error("Failed to determine the local architecture.")70 parser.error("Failed to determine the local architecture.")
7071
71 return dpkg.stdout.read().strip()72 return dpkg.stdout.read().strip()
73
74 def get_host_timezone(self):
75 with open(os.path.join('/', 'etc', 'timezone'), 'r') as fd:
76 return fd.read().strip('\n')
7277
=== modified file 'python/libertine/Libertine.py'
--- python/libertine/Libertine.py 2016-11-16 17:58:38 +0000
+++ python/libertine/Libertine.py 2017-01-05 20:19:05 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015-2106 Canonical Ltd.1# Copyright 2015-2016 Canonical Ltd.
2#2#
3# This program is free software: you can redistribute it and/or modify it3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3, as published4# under the terms of the GNU General Public License version 3, as published
@@ -20,31 +20,32 @@
20import os20import os
21import shutil21import shutil
2222
23from . import utils
23from libertine.ContainersConfig import ContainersConfig24from libertine.ContainersConfig import ContainersConfig
24from libertine.HostInfo import HostInfo25from libertine.HostInfo import HostInfo
2526
2627
27def apt_args_for_verbosity_level(verbosity):28def _apt_args_for_verbosity_level():
28 """29 """
29 Maps numeric verbosity levels onto APT command-line arguments.30 Maps debug levels to APT command-line arguments.
3031 """
31 :param verbosity: 0 is quiet, 1 is normal, 2 is incontinent32 if 'LIBERTINE_DEBUG' not in os.environ or os.environ['LIBERTINE_DEBUG'] == '0':
32 """33 return '--quiet=2'
33 return {34
34 0: '--quiet=2',35 if os.environ['LIBERTINE_DEBUG'] == '1':
35 1: '--assume-yes',36 return '--quiet=1 --assume-yes'
36 2: '--quiet=1 --assume-yes --option APT::Status-Fd=1'37
37 }.get(verbosity, '')38 return '--assume-yes --option APT::Status-Fd=1'
3839
3940
40def apt_command_prefix(verbosity):41def _apt_command_prefix():
41 return '/usr/bin/apt-get ' + apt_args_for_verbosity_level(verbosity) + \42 return '/usr/bin/apt-get ' + _apt_args_for_verbosity_level() + \
42 ' --option Apt::Cmd::Disable-Script-Warning=true --option Dpkg::Progress-Fancy=1' + \43 ' --option Apt::Cmd::Disable-Script-Warning=true --option Dpkg::Progress-Fancy=1' + \
43 ' --option Apt::Color=1 '44 ' --option Apt::Color=1 '
4445
4546
46def handle_runtime_error(error):47def handle_runtime_error(error):
47 print("%s" % error)48 utils.get_logger().error("%s" % error)
48 return False49 return False
4950
5051
@@ -92,41 +93,33 @@
92 'maliit-inputcontext-gtk3',93 'maliit-inputcontext-gtk3',
93 'maliit-framework']94 'maliit-framework']
9495
95 def create_libertine_container(self, password=None, multiarch=False, verbosity=1):96 def create_libertine_container(self, password=None, multiarch=False):
96 pass97 pass
9798
98 def destroy_libertine_container(self, verbosity=1):99 def destroy_libertine_container(self):
99 pass100 pass
100101
101 def copy_package_to_container(self, package_path):102 def copy_file_to_container(self, source, dest):
102 """103 """
103 Copies a Debian package from the host to a pre-determined place104 Copies a file from the host to the given path in the container.
104 in a container.105
105106 :param source: The full path to the file on the host.
106 :param package_path: The full path to the Debian package located107 :param dest: The relative path to the file in the container without
107 on the host.108 the root path.
108 """109 """
109 if os.path.exists(os.path.join(self.root_path, 'tmp', package_path.split('/')[-1])):110 if os.path.exists(os.path.join(self.root_path, dest)):
110 return False111 return False
111112
112 shutil.copy2(package_path, os.path.join(self.root_path, 'tmp'))113 shutil.copy2(source, os.path.join(self.root_path, dest.lstrip('/')))
113 return True114 return True
114115
115 def delete_package_in_container(self, package_path):116 def delete_file_in_container(self, path):
116 """117 """
117 Deletes a previously copied in Debian package in a container.118 Deletes a file within the container.
118119
119 :param package_path: The full path to the Debian package located120 :param path: The path to the file without the container root path.
120 on the host.121 """
121 """122 os.remove(os.path.join(self.root_path, path.lstrip('/')))
122 os.remove(os.path.join(self.root_path, 'tmp', package_path.split('/')[-1]))
123
124 def is_running(self):
125 """
126 Indicates if the container is 'running'. The definition of 'running'
127 depends on the type of the container.
128 """
129 return False
130123
131 def start_container(self):124 def start_container(self):
132 """125 """
@@ -151,91 +144,85 @@
151 """144 """
152 pass145 pass
153146
154 def update_apt_cache(self, verbosity=1):147 def update_apt_cache(self):
155 """148 """
156 Updates the apt cache in the container.149 Updates the apt cache in the container.
157
158 :param verbosity: the chattiness of the output on a range from 0 to 2
159 """150 """
160 return self.run_in_container(apt_command_prefix(verbosity) + 'update')151 return self.run_in_container(_apt_command_prefix() + 'update')
161152
162 def update_packages(self, verbosity=1):153 def update_packages(self):
163 """154 """
164 Updates all packages installed in the container.155 Updates all packages installed in the container.
165
166 :param verbosity: the chattiness of the output on a range from 0 to 2
167 """156 """
168 self.update_apt_cache(verbosity)157 self.update_apt_cache()
169 return self.run_in_container(apt_command_prefix(verbosity) + '--force-yes dist-upgrade') == 0158 return self.run_in_container(_apt_command_prefix() + '--force-yes dist-upgrade') == 0
170159
171 def install_package(self, package_name, verbosity=1, no_dialog=False, update_cache=True):160 def install_package(self, package_name, no_dialog=False, update_cache=True):
172 """161 """
173 Installs a named package in the container.162 Installs a named package in the container.
174163
175 :param package_name: The name of the package as APT understands it or164 :param package_name: The name of the package as APT understands it or
176 a full path to a Debian package on the host.165 a full path to a Debian package on the host.
177 :param verbosity: the chattiness of the output on a range from 0 to 2
178 """166 """
179 if update_cache:167 if update_cache:
180 self.update_apt_cache(verbosity)168 self.update_apt_cache()
181169
182 if package_name.endswith('.deb'):170 if package_name.endswith('.deb'):
183 delete_package = self.copy_package_to_container(package_name)171 if not os.path.exists(package_name):
184172 utils.get_logger().error("File {} does not exist.".format(package_name))
185 self.run_in_container('ls -la ' + os.environ['HOME'])173 return False
186 self.run_in_container('dpkg -i ' +174
187 os.path.join('/', 'tmp', package_name.split('/')[-1]))175 dest = os.path.join('/', 'tmp', package_name.split('/')[-1])
188176 file_created = self.copy_file_to_container(package_name, dest)
189 ret = self.run_in_container(apt_command_prefix(verbosity) + " install -f") == 0177
190178 self.run_in_container('dpkg -i {}'.format(dest))
191 if delete_package:179 ret = self.run_in_container(_apt_command_prefix() + " install -f") == 0
192 self.delete_package_in_container(package_name)180
181 if file_created:
182 self.delete_file_in_container(dest)
193183
194 return ret184 return ret
195 else:185 else:
196 if no_dialog:186 if no_dialog:
197 os.environ['DEBIAN_FRONTEND'] = 'teletype'187 os.environ['DEBIAN_FRONTEND'] = 'teletype'
198 return self.run_in_container(apt_command_prefix(verbosity) + " install '" + package_name + "'") == 0188 return self.run_in_container(_apt_command_prefix() + " install '" + package_name + "'") == 0
199189
200 def remove_package(self, package_name, verbosity=1):190 def remove_package(self, package_name):
201 """191 """
202 Removes a package from the container.192 Removes a package from the container.
203193
204 :param package_name: The name of the package to be removed.194 :param package_name: The name of the package to be removed.
205 :param verbosity: The verbosity level of the progress reporting.
206 """195 """
207 if self.run_in_container(apt_command_prefix(verbosity) + " purge '" + package_name + "'") != 0:196 if self.run_in_container(_apt_command_prefix() + " purge '" + package_name + "'") != 0:
208 return False197 return False
209 return self.run_in_container(apt_command_prefix(verbosity) + "autoremove --purge") == 0198 return self.run_in_container(_apt_command_prefix() + "autoremove --purge") == 0
210199
211 def configure_multiarch(self, should_enable, verbosity=1):200 def configure_multiarch(self, should_enable):
212 """201 """
213 Enables or disables multiarch repositories.202 Enables or disables multiarch repositories.
214203
215 :param should_enable: Whether or not to enable multiarch support.204 :param should_enable: Whether or not to enable multiarch support.
216 :param verbosity: the chattiness of the output on a range from 0 to 2
217 """205 """
218 if should_enable:206 if should_enable:
219 ret = self.run_in_container("dpkg --add-architecture i386")207 ret = self.run_in_container("dpkg --add-architecture i386")
220 if ret or ret == 0:208 if ret or ret == 0:
221 self.update_apt_cache(verbosity)209 self.update_apt_cache()
222 return ret210 return ret
223 else:211 else:
224 self.run_in_container(apt_command_prefix(verbosity) + "purge \".*:i386\"")212 self.run_in_container(_apt_command_prefix() + "purge \".*:i386\"")
225 return self.run_in_container("dpkg --remove-architecture i386")213 return self.run_in_container("dpkg --remove-architecture i386")
226214
227 def configure_add_archive(self, archive, public_key_file, verbosity=1):215 def configure_add_archive(self, archive, public_key_file):
228 """216 """
229 Adds the given archive. If this archive requires a key, prompt user.217 Adds the given archive. If this archive requires a key, prompt user.
230218
231 :param archive: The configuration command to run.219 :param archive: The configuration command to run.
232 :param public_key_file: file containing the public key used to sign this archive220 :param public_key_file: file containing the public key used to sign this archive
233 :param verbosity: the chattiness of the output on a range from 0 to 2
234 """221 """
235 if not os.path.exists(os.path.join(self.root_path, 'usr', 'bin', 'add-apt-repository')):222 if not os.path.exists(os.path.join(self.root_path, 'usr', 'bin', 'add-apt-repository')):
236 self.install_package("software-properties-common", verbosity)223 self.install_package("software-properties-common")
237 if 'https://' in archive and not os.path.exists(os.path.join(self.root_path, 'usr', 'lib', 'apt', 'methods', 'https')):224 if 'https://' in archive and not os.path.exists(os.path.join(self.root_path, 'usr', 'lib', 'apt', 'methods', 'https')):
238 self.install_package("apt-transport-https", verbosity)225 self.install_package("apt-transport-https")
239226
240 retcode = self.run_in_container("add-apt-repository -y " + archive)227 retcode = self.run_in_container("add-apt-repository -y " + archive)
241 if retcode is 0 and public_key_file is not None:228 if retcode is 0 and public_key_file is not None:
@@ -244,12 +231,11 @@
244231
245 return retcode232 return retcode
246233
247 def configure_remove_archive(self, archive, verbosity=1):234 def configure_remove_archive(self, archive):
248 """235 """
249 Removes the given archive.236 Removes the given archive.
250237
251 :param archive: The configuration command to run.238 :param archive: The configuration command to run.
252 :param verbosity: the chattiness of the output on a range from 0 to 2
253 """239 """
254 return self.run_in_container("add-apt-repository -y -r " + archive)240 return self.run_in_container("add-apt-repository -y -r " + archive)
255241
@@ -272,19 +258,19 @@
272 super().__init__(container_id)258 super().__init__(container_id)
273 self.container_type = "mock"259 self.container_type = "mock"
274260
275 def create_libertine_container(self, password=None, multiarch=False, verbosity=1):261 def create_libertine_container(self, password=None, multiarch=False):
276 return True262 return True
277263
278 def destroy_libertine_container(self, verbosity=1):264 def destroy_libertine_container(self):
279 return True265 return True
280266
281 def update_packages(self, verbosity=1):267 def update_packages(self):
282 return True268 return True
283269
284 def install_package(self, package_name, verbosity=1, no_dialog=False, update_cache=True):270 def install_package(self, package_name, no_dialog=False, update_cache=True):
285 return True271 return True
286272
287 def remove_package(self, package_name, verbosity=1, no_dialog=False):273 def remove_package(self, package_name, no_dialog=False):
288 return True274 return True
289275
290 def run_in_container(self, command_string):276 def run_in_container(self, command_string):
@@ -335,6 +321,9 @@
335 if container_type == "lxc":321 if container_type == "lxc":
336 from libertine.LxcContainer import LibertineLXC322 from libertine.LxcContainer import LibertineLXC
337 self.container = LibertineLXC(container_id)323 self.container = LibertineLXC(container_id)
324 elif container_type == "lxd":
325 from libertine.LxdContainer import LibertineLXD
326 self.container = LibertineLXD(container_id, self.containers_config)
338 elif container_type == "chroot":327 elif container_type == "chroot":
339 from libertine.ChrootContainer import LibertineChroot328 from libertine.ChrootContainer import LibertineChroot
340 self.container = LibertineChroot(container_id)329 self.container = LibertineChroot(container_id)
@@ -365,48 +354,47 @@
365 """354 """
366 return self.container.destroy_libertine_container()355 return self.container.destroy_libertine_container()
367356
368 def create_libertine_container(self, password=None, multiarch=False, verbosity=1):357 def create_libertine_container(self, password=None, multiarch=False):
369 """358 """
370 Creates the container.359 Creates the container.
371 """360 """
372 self.container.architecture = HostInfo().get_host_architecture()361 self.container.architecture = HostInfo().get_host_architecture()
373 self.container.installed_release = self.containers_config.get_container_distro(self.container_id)362 self.container.installed_release = self.containers_config.get_container_distro(self.container_id)
374363
375 return self.container.create_libertine_container(password, multiarch, verbosity)364 return self.container.create_libertine_container(password, multiarch)
376365
377 def update_libertine_container(self, verbosity=1):366 def update_libertine_container(self):
378 """367 """
379 Updates the contents of the container.368 Updates the contents of the container.
380 """369 """
381 try:370 try:
382 with ContainerRunning(self.container):371 with ContainerRunning(self.container):
383 return self.container.update_packages(verbosity)372 return self.container.update_packages()
384 except RuntimeError as e:373 except RuntimeError as e:
385 return handle_runtime_error(e)374 return handle_runtime_error(e)
386375
387 def install_package(self, package_name, verbosity=1, no_dialog=False, update_cache=True):376 def install_package(self, package_name, no_dialog=False, update_cache=True):
388 """377 """
389 Installs a package in the container.378 Installs a package in the container.
390 """379 """
391 try:380 try:
392 with ContainerRunning(self.container):381 with ContainerRunning(self.container):
393 return self.container.install_package(package_name, verbosity, no_dialog, update_cache)382 return self.container.install_package(package_name, no_dialog, update_cache)
394 except RuntimeError as e:383 except RuntimeError as e:
395 return handle_runtime_error(e)384 return handle_runtime_error(e)
396385
397 def remove_package(self, package_name, verbosity=1, no_dialog=False):386 def remove_package(self, package_name, no_dialog=False):
398 """387 """
399 Removes a package from the container.388 Removes a package from the container.
400389
401 :param package_name: The name of the package to be removed.390 :param package_name: The name of the package to be removed.
402 :param verbosity: The verbosity level of the progress reporting.
403 """391 """
404 try:392 try:
405 with ContainerRunning(self.container):393 with ContainerRunning(self.container):
406 if no_dialog:394 if no_dialog:
407 os.environ['DEBIAN_FRONTEND'] = 'teletype'395 os.environ['DEBIAN_FRONTEND'] = 'teletype'
408396
409 return self.container.remove_package(package_name, verbosity)397 return self.container.remove_package(package_name)
410 except RuntimeError as e:398 except RuntimeError as e:
411 return handle_runtime_error(e)399 return handle_runtime_error(e)
412400
@@ -484,23 +472,23 @@
484 except RuntimeError as e:472 except RuntimeError as e:
485 return handle_runtime_error(e)473 return handle_runtime_error(e)
486474
487 def configure_multiarch(self, should_enable, verbosity=1):475 def configure_multiarch(self, should_enable):
488 try:476 try:
489 with ContainerRunning(self.container):477 with ContainerRunning(self.container):
490 return self.container.configure_multiarch(should_enable, verbosity)478 return self.container.configure_multiarch(should_enable)
491 except RuntimeError as e:479 except RuntimeError as e:
492 return handle_runtime_error(e)480 return handle_runtime_error(e)
493481
494 def configure_add_archive(self, archive, key, verbosity):482 def configure_add_archive(self, archive, key):
495 try:483 try:
496 with ContainerRunning(self.container):484 with ContainerRunning(self.container):
497 return self.container.configure_add_archive(archive, key, verbosity)485 return self.container.configure_add_archive(archive, key)
498 except RuntimeError as e:486 except RuntimeError as e:
499 return handle_runtime_error(e)487 return handle_runtime_error(e)
500488
501 def configure_remove_archive(self, archive, verbosity):489 def configure_remove_archive(self, archive):
502 try:490 try:
503 with ContainerRunning(self.container):491 with ContainerRunning(self.container):
504 return self.container.configure_remove_archive(archive, verbosity)492 return self.container.configure_remove_archive(archive)
505 except RuntimeError as e:493 except RuntimeError as e:
506 return handle_runtime_error(e)494 return handle_runtime_error(e)
507495
=== modified file 'python/libertine/LxcContainer.py'
--- python/libertine/LxcContainer.py 2016-11-16 17:33:42 +0000
+++ python/libertine/LxcContainer.py 2017-01-05 20:19:05 +0000
@@ -23,8 +23,9 @@
23import sys23import sys
24import tempfile24import tempfile
2525
26from .lifecycle import LifecycleResult
26from .Libertine import BaseContainer27from .Libertine import BaseContainer
27from . import utils28from . import utils, HostInfo
2829
2930
30home_path = os.environ['HOME']31home_path = os.environ['HOME']
@@ -33,7 +34,7 @@
33LIBERTINE_LXC_MANAGER_PATH = "/LxcManager"34LIBERTINE_LXC_MANAGER_PATH = "/LxcManager"
3435
3536
36def check_lxc_net_entry(entry):37def _check_lxc_net_entry(entry):
37 lxc_net_file = open('/etc/lxc/lxc-usernet')38 lxc_net_file = open('/etc/lxc/lxc-usernet')
38 found = False39 found = False
3940
@@ -45,14 +46,14 @@
45 return found46 return found
4647
4748
48def setup_host_environment(username):49def _setup_host_environment(username):
49 lxc_net_entry = "%s veth lxcbr0 10" % str(username)50 lxc_net_entry = "%s veth lxcbr0 10" % str(username)
5051
51 if not check_lxc_net_entry(lxc_net_entry):52 if not _check_lxc_net_entry(lxc_net_entry):
52 subprocess.Popen(["sudo", "libertine-lxc-setup", str(username)]).wait()53 subprocess.Popen(["sudo", "libertine-lxc-setup", str(username)]).wait()
5354
5455
55def get_lxc_default_config_path():56def _get_lxc_default_config_path():
56 return os.path.join(home_path, '.config', 'lxc')57 return os.path.join(home_path, '.config', 'lxc')
5758
5859
@@ -74,21 +75,43 @@
74 return container75 return container
7576
7677
77def lxc_start(container, lxc_log_file):78def _dump_lxc_log(logfile):
78 container.append_config_item("lxc.logfile", lxc_log_file)79 if os.path.exists(logfile):
79 container.append_config_item("lxc.logpriority", "3")80 try:
81 with open(logfile, 'r') as fd:
82 for line in fd:
83 print(line.lstrip())
84 except Exception as ex:
85 utils.get_logger().error("Could not open LXC log file: %s" % ex)
86
87
88def get_logfile(container):
89 try:
90 logfile = container.get_config_item('lxc.logfile')
91 except:
92 logfile = None
93
94 if not container.running or logfile is None or not os.path.exists(logfile):
95 logfile = os.path.join(tempfile.mkdtemp(), 'lxc-start.log')
96 container.append_config_item("lxc.logfile", logfile)
97 container.append_config_item("lxc.logpriority", "3")
98
99 return logfile
100
101def lxc_start(container):
102 lxc_log_file = get_logfile(container)
80103
81 if not container.start():104 if not container.start():
82 return (False, "Container failed to start")105 return LifecycleResult("Container failed to start.")
83106
84 if not container.wait("RUNNING", 10):107 if not container.wait("RUNNING", 10):
85 return (False, "Container failed to enter the RUNNING state")108 return LifecycleResult("Container failed to enter the RUNNING state.")
86109
87 if not container.get_ips(timeout=30):110 if not container.get_ips(timeout=30):
88 lxc_stop(container)111 lxc_stop(container)
89 return (False, "Not able to connect to the network.")112 return LifecycleResult("Not able to connect to the network.")
90113
91 return (True, "")114 return LifecycleResult()
92115
93116
94def lxc_stop(container):117def lxc_stop(container):
@@ -96,13 +119,6 @@
96 container.stop()119 container.stop()
97120
98121
99def get_host_timezone():
100 with open(os.path.join('/', 'etc', 'timezone'), 'r') as fd:
101 host_timezone = fd.read().strip('\n')
102
103 return host_timezone
104
105
106class EnvLxcSettings(contextlib.ExitStack):122class EnvLxcSettings(contextlib.ExitStack):
107 """123 """
108 Helper object providing a way to set the proxies for testing124 Helper object providing a way to set the proxies for testing
@@ -137,42 +153,31 @@
137 super().__init__(container_id)153 super().__init__(container_id)
138 self.container_type = "lxc"154 self.container_type = "lxc"
139 self.container = lxc_container(container_id)155 self.container = lxc_container(container_id)
140 self._set_lxc_log()
141 self.lxc_manager_interface = None156 self.lxc_manager_interface = None
142 self.window_manager = None157 self.window_manager = None
158 self.host_info = HostInfo.HostInfo()
143159
144 utils.set_session_dbus_env_var()160 utils.set_session_dbus_env_var()
145161
146 try:162 try:
147 bus = dbus.SessionBus()163 bus = dbus.SessionBus()
148 lxc_mgr_service = bus.get_object(get_lxc_manager_dbus_name(), get_lxc_manager_dbus_path())164 self.lxc_manager_interface = bus.get_object(get_lxc_manager_dbus_name(), get_lxc_manager_dbus_path())
149 self.lxc_manager_interface = dbus.Interface(lxc_mgr_service, get_lxc_manager_dbus_name())
150 except dbus.exceptions.DBusException:165 except dbus.exceptions.DBusException:
151 pass166 pass
152167
153 def is_running(self):
154 return self.container.running
155
156 def timezone_needs_update(self):168 def timezone_needs_update(self):
157 host_timezone = get_host_timezone()
158
159 with open(os.path.join(self.root_path, 'etc', 'timezone'), 'r') as fd:169 with open(os.path.join(self.root_path, 'etc', 'timezone'), 'r') as fd:
160 container_timezone = fd.read().strip('\n')170 return fd.read().strip('\n') != self.host_info.get_host_timezone()
161
162 if host_timezone == container_timezone:
163 return False
164 else:
165 return True
166171
167 def start_container(self):172 def start_container(self):
168 if self.lxc_manager_interface:173 if self.lxc_manager_interface:
169 (result, error) = self.lxc_manager_interface.operation_start(self.container_id, self.lxc_log_file)174 result = LifecycleResult.from_dict(self.lxc_manager_interface.operation_start(self.container_id))
170 else:175 else:
171 (result, error) = lxc_start(self.container, self.lxc_log_file)176 result = lxc_start(self.container)
172177
173 if not result:178 if not result.success:
174 self._dump_lxc_log()179 _dump_lxc_log(result.logfile)
175 raise RuntimeError(error)180 raise RuntimeError(result.error)
176181
177 if self.run_in_container("mountpoint -q /tmp/.X11-unix") == 0:182 if self.run_in_container("mountpoint -q /tmp/.X11-unix") == 0:
178 self.run_in_container("umount /tmp/.X11-unix")183 self.run_in_container("umount /tmp/.X11-unix")
@@ -189,14 +194,14 @@
189 cmd_args = shlex.split(command_string)194 cmd_args = shlex.split(command_string)
190 return self.container.attach_wait(lxc.attach_run_command, cmd_args)195 return self.container.attach_wait(lxc.attach_run_command, cmd_args)
191196
192 def update_packages(self, verbosity=1):197 def update_packages(self):
193 if self.timezone_needs_update():198 if self.timezone_needs_update():
194 self.run_in_container("bash -c \'echo \"{}\" >/etc/timezone\'".format(199 self.run_in_container("bash -c \'echo \"{}\" >/etc/timezone\'".format(
195 get_host_timezone()))200 self.host_info.get_host_timezone()))
196 self.run_in_container("rm -f /etc/localtime")201 self.run_in_container("rm -f /etc/localtime")
197 self.run_in_container("dpkg-reconfigure -f noninteractive tzdata")202 self.run_in_container("dpkg-reconfigure -f noninteractive tzdata")
198203
199 return super().update_packages(verbosity)204 return super().update_packages()
200205
201 def destroy_libertine_container(self):206 def destroy_libertine_container(self):
202 if not self.container.defined:207 if not self.container.defined:
@@ -206,7 +211,7 @@
206 self.container.destroy()211 self.container.destroy()
207 return True212 return True
208213
209 def create_libertine_container(self, password=None, multiarch=False, verbosity=1):214 def create_libertine_container(self, password=None, multiarch=False):
210 if password is None:215 if password is None:
211 return False216 return False
212217
@@ -214,10 +219,10 @@
214 user_id = os.getuid()219 user_id = os.getuid()
215 group_id = os.getgid()220 group_id = os.getgid()
216221
217 setup_host_environment(username)222 _setup_host_environment(username)
218223
219 # Generate the default lxc default config, if it doesn't exist224 # Generate the default lxc default config, if it doesn't exist
220 config_path = get_lxc_default_config_path()225 config_path = _get_lxc_default_config_path()
221 config_file = "%s/default.conf" % config_path226 config_file = "%s/default.conf" % config_path
222227
223 if not os.path.exists(config_path):228 if not os.path.exists(config_path):
@@ -236,25 +241,27 @@
236 fd.write("lxc.id_map = u %s %s %s\n" % (user_id + 1, (user_id + 1) + 100000, 65536 - (user_id + 1)))241 fd.write("lxc.id_map = u %s %s %s\n" % (user_id + 1, (user_id + 1) + 100000, 65536 - (user_id + 1)))
237 fd.write("lxc.id_map = g %s %s %s\n" % (group_id + 1, (group_id + 1) + 100000, 65536 - (user_id + 1)))242 fd.write("lxc.id_map = g %s %s %s\n" % (group_id + 1, (group_id + 1) + 100000, 65536 - (user_id + 1)))
238243
244 self.container.load_config(config_file)
245
239 utils.create_libertine_user_data_dir(self.container_id)246 utils.create_libertine_user_data_dir(self.container_id)
240247
241 with EnvLxcSettings():248 with EnvLxcSettings():
249 lxc_logfile = get_logfile(self.container)
242 if not self.container.create("download", 0,250 if not self.container.create("download", 0,
243 {"dist": "ubuntu",251 {"dist": "ubuntu",
244 "release": self.installed_release,252 "release": self.installed_release,
245 "arch": self.architecture}):253 "arch": self.architecture}):
246 print("Failed to create container")254 utils.get_logger().error("Failed to create container")
247 self._dump_lxc_log()255 _dump_lxc_log(lxc_logfile)
248 return False256 return False
249257
250 self.create_libertine_config()258 self.create_libertine_config()
251259
252 if verbosity == 1:260 utils.get_logger().info("starting container ...")
253 print("starting container ...")
254 try:261 try:
255 self.start_container()262 self.start_container()
256 except RuntimeError as e:263 except RuntimeError as e:
257 print("Container failed to start: %s" % e)264 utils.get_logger().error("Container failed to start: %s" % e)
258 self.destroy_libertine_container()265 self.destroy_libertine_container()
259 return False266 return False
260267
@@ -263,22 +270,19 @@
263 str(user_id), crypt.crypt(password), str(username)))270 str(user_id), crypt.crypt(password), str(username)))
264271
265 if multiarch and self.architecture == 'amd64':272 if multiarch and self.architecture == 'amd64':
266 if verbosity == 1:273 utils.get_logger().info("Adding i386 multiarch support...")
267 print("Adding i386 multiarch support...")
268 self.run_in_container("dpkg --add-architecture i386")274 self.run_in_container("dpkg --add-architecture i386")
269275
270 if verbosity == 1:276 utils.get_logger().info("Updating the contents of the container after creation...")
271 print("Updating the contents of the container after creation...")277 self.update_packages()
272 self.update_packages(verbosity)
273278
274 for package in self.default_packages:279 for package in self.default_packages:
275 if not self.install_package(package, verbosity, update_cache=False):280 if not self.install_package(package, update_cache=False):
276 print("Failure installing %s during container creation" % package)281 utils.get_logger().error("Failure installing %s during container creation" % package)
277 self.destroy_libertine_container()282 self.destroy_libertine_container()
278 return False283 return False
279284
280 if verbosity == 1:285 utils.get_logger().info("stopping container ...")
281 print("stopping container ...")
282 self.stop_container()286 self.stop_container()
283287
284 return True288 return True
@@ -316,17 +320,17 @@
316320
317 def start_application(self, app_exec_line, environ):321 def start_application(self, app_exec_line, environ):
318 if self.lxc_manager_interface == None:322 if self.lxc_manager_interface == None:
319 print("No interface to libertine-lxc-manager. Failing application launch.")323 utils.get_logger().error("No interface to libertine-lxc-manager. Failing application launch.")
320 return324 return
321325
322 os.environ.clear()326 os.environ.clear()
323 os.environ.update(environ)327 os.environ.update(environ)
324328
325 (result, error) = self.lxc_manager_interface.app_start(self.container_id, self.lxc_log_file)329 result = LifecycleResult.from_dict(self.lxc_manager_interface.app_start(self.container_id))
326330
327 if not result:331 if not result.success:
328 self._dump_lxc_log()332 _dump_lxc_log(get_logfile(self.container))
329 print("%s" % error)333 utils.get_logger().error("%s" % result.error)
330 return334 return
331335
332 self.window_manager = self.container.attach(lxc.attach_run_command,336 self.window_manager = self.container.attach(lxc.attach_run_command,
@@ -348,17 +352,3 @@
348352
349 # Tell libertine-lxc-manager that the app has stopped.353 # Tell libertine-lxc-manager that the app has stopped.
350 self.lxc_manager_interface.app_stop(self.container_id)354 self.lxc_manager_interface.app_stop(self.container_id)
351
352 def _set_lxc_log(self):
353 self.lxc_log_file = os.path.join(tempfile.mkdtemp(), 'lxc-start.log')
354 self.container.append_config_item("lxc.logfile", self.lxc_log_file)
355 self.container.append_config_item("lxc.logpriority", "3")
356
357 def _dump_lxc_log(self):
358 if os.path.exists(self.lxc_log_file):
359 try:
360 with open(self.lxc_log_file, 'r') as fd:
361 for line in fd:
362 print(line.lstrip())
363 except Exception as ex:
364 print("Could not open LXC log file: %s" % ex, file=sys.stderr)
365355
=== added file 'python/libertine/LxdContainer.py'
--- python/libertine/LxdContainer.py 1970-01-01 00:00:00 +0000
+++ python/libertine/LxdContainer.py 2017-01-05 20:19:05 +0000
@@ -0,0 +1,455 @@
1# Copyright 2016 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3, as published
5# by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful, but
8# WITHOUT ANY WARRANTY; without even the implied warranties of
9# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10# PURPOSE. See the GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License along
13# with this program. If not, see <http://www.gnu.org/licenses/>.
14
15import crypt
16import dbus
17import os
18import psutil
19import pylxd
20import shlex
21import subprocess
22import time
23
24from .lifecycle import LifecycleResult
25from libertine import Libertine, utils, HostInfo
26
27
28LIBERTINE_LXC_MANAGER_NAME = "com.canonical.libertine.LxdManager"
29LIBERTINE_LXC_MANAGER_PATH = "/LxdManager"
30
31
32def get_lxd_manager_dbus_name():
33 return LIBERTINE_LXC_MANAGER_NAME
34
35
36def get_lxd_manager_dbus_path():
37 return LIBERTINE_LXC_MANAGER_PATH
38
39
40def _get_devices_map():
41 devices = {
42 '/dev/tty0': {'path': '/dev/tty0', 'type': 'unix-char'},
43 '/dev/tty7': {'path': '/dev/tty7', 'type': 'unix-char'},
44 '/dev/tty8': {'path': '/dev/tty8', 'type': 'unix-char'},
45 '/dev/fb0': {'path': '/dev/fb0', 'type': 'unix-char'},
46 'x11-socket': {'source': '/tmp/.X11-unix', 'path': '/tmp/.X11-unix', 'type': 'disk', 'optional': 'true'},
47 }
48
49 if os.path.exists('/dev/video0'):
50 devices['/dev/video0'] = {'path': '/dev/video0', 'type': 'unix-char'}
51
52 # every regular file in /dev/snd
53 for f in ['/dev/snd/{}'.format(f) for f in os.listdir('/dev/snd') if not os.path.isdir('/dev/snd/{}'.format(f))]:
54 devices[f] = {'path': f, 'type': 'unix-char'}
55
56 # every regular file in /dev/dri
57 for f in ['/dev/dri/{}'.format(f) for f in os.listdir('/dev/dri') if not os.path.isdir('/dev/dri/{}'.format(f))]:
58 devices[f] = {'path': f, 'type': 'unix-char'}
59
60 # Some devices require special mappings for snap
61 if utils.is_snap_environment():
62 devices['x11-socket']['source'] = '/var/lib/snapd/hostfs/tmp/.X11-unix'
63
64 return devices
65
66def _readlink(source):
67 while os.path.islink(source):
68 new_source = os.readlink(source)
69 if not os.path.isabs(new_source):
70 new_source = os.path.join(os.path.dirname(source), new_source)
71 source = new_source
72
73 return source
74
75def _setup_lxd():
76 utils.get_logger().info("Running LXD setup.")
77 import pexpect
78 child = pexpect.spawnu('sudo libertine-lxd-setup {}'.format(os.environ['USER']), env={'TERM': 'dumb'})
79
80 while True:
81 index = child.expect(['.+\[default=.+\].*', pexpect.EOF, pexpect.TIMEOUT,
82 # The following are required for lxd=2.0.x
83 '.+\[yes/no\].*',
84 '.+\(e.g. (?P<example>[a-z0-9\.:]+)\).+'])
85 if index == 0:
86 child.sendline()
87 elif index == 1:
88 return True
89 elif index == 2:
90 return False
91 elif index == 3:
92 child.sendline('yes')
93 elif index == 4:
94 child.sendline(child.match.group('example'))
95
96 if child.exitstatus is not None:
97 return child.exitstatus == 0
98
99
100def _setup_bind_mount_service(container, uid, username):
101 utils.get_logger().info("Creating systemd mount override service")
102 service = '''
103[Unit]
104Description=Fix system mounts for libertine
105
106[Service]
107ExecStart=/usr/bin/libertine-lxd-mount-update
108
109[Install]
110WantedBy=multi-user.target
111'''[1:-1]
112 container.files.put('/etc/systemd/system/libertine-lxd-mount-update.service', service.encode('utf-8'))
113 container.execute(shlex.split('chmod 644 /etc/systemd/system/libertine-lxd-mount-update.service'))
114
115 utils.get_logger().info("Creating mount update shell script")
116 script = '''
117#!/bin/sh
118
119mkdir -p /run/user/{uid}
120chown {username}:{username} /run/user/{uid}
121chmod 755 /run/user/{uid}
122mount -o bind /var/tmp/run/user/{uid} /run/user/{uid}
123
124chgrp audio /dev/snd/*
125chgrp video /dev/dri/*
126[ -n /dev/video0 ] && chgrp video /dev/video0
127'''[1:-1]
128 container.files.put('/usr/bin/libertine-lxd-mount-update', script.format(uid=uid, username=username).encode('utf-8'))
129 container.execute(shlex.split('chmod 755 /usr/bin/libertine-lxd-mount-update'))
130
131 utils.get_logger().info("Enabling systemd mount update service")
132 container.execute(shlex.split('systemctl enable libertine-lxd-mount-update.service'))
133
134
135def lxd_container(client, container_id):
136 try:
137 return client.containers.get(container_id)
138 except pylxd.exceptions.LXDAPIException:
139 return None
140
141
142def _wait_for_network(container):
143 for retries in range(0, 10):
144 out, err = container.execute(shlex.split('ping -c 1 ubuntu.com'))
145 if out:
146 utils.get_logger().info("Network connection active")
147 return True
148 time.sleep(1)
149 return False
150
151
152def lxd_start(container):
153 if container.status != 'Running':
154 container.start(wait=True)
155
156 container.sync(rollback=True) # required for pylxd=2.0.x
157
158 if container.status != 'Running':
159 return LifecycleResult("Container {} failed to start".format(container.name))
160
161 return LifecycleResult()
162
163
164def lxd_stop(container, wait):
165 if container.status == 'Stopped':
166 return LifecycleResult()
167
168 container.stop(wait=wait)
169 container.sync(rollback=True) # required for pylxd=2.0.x
170
171 if wait and container.status != 'Stopped':
172 return LifecycleResult("Container {} failed to stop".format(container.name))
173
174 return LifecycleResult()
175
176
177def update_bind_mounts(container, config):
178 home_path = os.environ['HOME']
179
180 container.devices.clear()
181 container.devices['root'] = {'type': 'disk', 'path': '/'}
182
183 if os.path.exists(os.path.join(home_path, '.config', 'dconf')):
184 container.devices['dconf'] = {
185 'type': 'disk',
186 'source': os.path.join(home_path, '.config', 'dconf'),
187 'path': os.path.join(home_path, '.config', 'dconf')
188 }
189
190 run_user = '/run/user/{}'.format(os.getuid())
191 container.devices[run_user] = {'source': run_user, 'path': '/var/tmp{}'.format(run_user), 'type': 'disk'}
192
193 mounts = utils.get_common_xdg_user_directories() + \
194 config.get_container_bind_mounts(container.name)
195 for user_dir in utils.generate_binding_directories(mounts, home_path):
196 if not os.path.exists(user_dir[0]):
197 utils.get_logger().warning('Bind-mount path \'{}\' does not exist.'.format(user_dir[0]))
198 continue
199
200 if os.path.isabs(user_dir[1]):
201 path = user_dir[1]
202 else:
203 path = os.path.join(home_path, user_dir[1])
204
205 utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, container.name))
206
207 container.devices[user_dir[1]] = {
208 'source': _readlink(user_dir[0]),
209 'path': path,
210 'optional': 'true',
211 'type': 'disk'
212 }
213
214 try:
215 container.save(wait=True)
216 except pylxd.exceptions.LXDAPIException as e:
217 utils.get_logger().warning('Saving bind mounts for container \'{}\' raised: {}'.format(container.name, str(e)))
218 # This is most likely the result of the container currently running
219
220
221def update_libertine_profile(client):
222 try:
223 profile = client.profiles.get('libertine')
224
225 utils.get_logger().info('Updating existing lxd profile.')
226 profile.devices = _get_devices_map()
227 profile.config['raw.idmap'] = 'both 1000 1000'
228
229 try:
230 profile.save()
231 except pylxd.exceptions.LXDAPIException as e:
232 utils.get_logger().warning('Saving libertine lxd profile raised: {}'.format(str(e)))
233 # This is most likely the result of an older container currently
234 # running and/or containing a conflicting device entry
235 except pylxd.exceptions.LXDAPIException:
236 utils.get_logger().info('Creating libertine lxd profile.')
237 client.profiles.create('libertine', config={'raw.idmap': 'both 1000 1000'}, devices=_get_devices_map())
238
239
240class LibertineLXD(Libertine.BaseContainer):
241 def __init__(self, name, config):
242 super().__init__(name)
243 self._id = name
244 self._config = config
245 self._host_info = HostInfo.HostInfo()
246 self._container = None
247 self._matchbox_pid = None
248
249 if not _setup_lxd():
250 raise Exception("Failed to setup lxd.")
251
252 self._client = pylxd.Client()
253 self._window_manager = None
254 self.root_path = '{}/containers/{}/rootfs'.format(os.getenv('LXD_DIR', '/var/lib/lxd'), name)
255
256 utils.set_session_dbus_env_var()
257 try:
258 bus = dbus.SessionBus()
259 self._manager = bus.get_object(get_lxd_manager_dbus_name(), get_lxd_manager_dbus_path())
260 except dbus.exceptions.DBusException:
261 utils.get_logger().warning("D-Bus Service not found.")
262 self._manager = None
263
264
265 def create_libertine_container(self, password=None, multiarch=False):
266 if self._try_get_container():
267 utils.get_logger().error("Container already exists")
268 return False
269
270 update_libertine_profile(self._client)
271
272 utils.get_logger().info("Creating container '%s' with distro '%s'" % (self._id, self.installed_release))
273 create = subprocess.Popen(shlex.split('lxc launch ubuntu-daily:{distro} {id} --profile '
274 'default --profile libertine'.format(
275 distro=self.installed_release, id=self._id)))
276 if create.wait() is not 0:
277 utils.get_logger().error("Creating container '{}' failed with code '{}'".format(self._id, create.returncode))
278 return False
279
280 if not self.start_container():
281 utils.get_logger().error("Failed to start container '{}'".format(self._id))
282 self.destroy_libertine_container()
283 return False
284
285 username = os.environ['USER']
286 uid = str(os.getuid())
287 self.run_in_container("userdel -r ubuntu")
288 self.run_in_container("useradd -u {} -U -p {} -G sudo,audio,video {}".format(
289 uid, crypt.crypt(''), username))
290 self.run_in_container("mkdir -p /home/{}".format(username))
291 self.run_in_container("chown {0}:{0} /home/{0}".format(username))
292
293 _setup_bind_mount_service(self._container, uid, username)
294
295 if multiarch and self.architecture == 'amd64':
296 utils.get_logger().info("Adding i386 multiarch support to container '{}'".format(self._id))
297 self.run_in_container("dpkg --add-architecture i386")
298
299 self.update_packages()
300
301 for package in self.default_packages:
302 utils.get_logger().info("Installing package '%s' in container '%s'" % (package, self._id))
303 if not self.install_package(package, no_dialog=True, update_cache=False):
304 utils.get_logger().error("Failure installing '%s' during container creation" % package)
305 self.destroy_libertine_container()
306 return False
307
308 return True
309
310 def update_packages(self):
311 if not self._timezone_in_sync():
312 utils.get_logger().info("Re-syncing timezones")
313 self.run_in_container("bash -c 'echo \"%s\" > /etc/timezone'" % self._host_info.get_host_timezone())
314 self.run_in_container("rm -f /etc/localtime")
315 self.run_in_container("dpkg-reconfigure -f noninteractive tzdata")
316
317 return super().update_packages()
318
319 def destroy_libertine_container(self):
320 if not self._try_get_container():
321 utils.get_logger().error("No such container '%s'" % self._id)
322 return False
323
324 self.stop_container(wait=True)
325 self._container.delete()
326 return True
327
328 def _timezone_in_sync(self):
329 proc = subprocess.Popen(self._lxc_args('cat /etc/timezone'), stdout=subprocess.PIPE)
330 out, err = proc.communicate()
331 return out.decode('UTF-8').strip('\n') == self._host_info.get_host_timezone()
332
333 def _lxc_args(self, command, environ={}):
334 env_as_args = ' '
335 for k, v in environ.items():
336 env_as_args += '--env {}="{}" '.format(k, v)
337
338 return shlex.split('lxc exec {}{}-- {}'.format(self._id,
339 env_as_args,
340 command))
341
342 def run_in_container(self, command):
343 proc = subprocess.Popen(self._lxc_args(command))
344 return proc.wait()
345
346 def start_container(self):
347 if not self._try_get_container():
348 return False
349
350 if self._manager:
351 result = LifecycleResult.from_dict(self._manager.operation_start(self._id))
352 else:
353 result = lxd_start(self._container)
354
355 if not result.success:
356 utils.get_logger().error(result.error)
357 return False
358
359 if not _wait_for_network(self._container):
360 utils.get_logger().warning("Network unavailable in container '{}'".format(self._id))
361
362 return result.success
363
364 def stop_container(self, wait=False):
365 if not self._try_get_container():
366 return False
367
368 if self._manager:
369 result = LifecycleResult.from_dict(self._manager.operation_stop(self._id))
370 else:
371 result = lxd_stop(self._container, wait)
372
373 if not result.success:
374 utils.get_logger().error(result.error)
375
376 return result.success
377
378 def _get_matchbox_pids(self):
379 p = subprocess.Popen(self._lxc_args('pgrep matchbox'), stdout=subprocess.PIPE)
380 out, err = p.communicate()
381 return out.decode('utf-8').split('\n')
382
383 # FIXME: Remove once window management logic has been moved to the host
384 def _start_window_manager(self, args):
385 args.extend(utils.setup_window_manager(self._id))
386
387 if 'matchbox-window-manager' in args:
388 pids = self._get_matchbox_pids()
389
390 self._window_manager = psutil.Popen(args)
391
392 time.sleep(.25) # Give matchbox a moment to start in the container
393 if self._window_manager.poll() is not None:
394 utils.get_logger().warning("Window manager terminated prematurely with exit code '{}'".format(
395 self._window_manager.returncode))
396 self._window_manager = None
397 elif 'matchbox-window-manager' in args:
398 after_pids = self._get_matchbox_pids()
399 if len(after_pids) > len(pids):
400 self._matchbox_pid = (set(after_pids) - set(pids)).pop()
401
402 def start_application(self, app_exec_line, environ):
403 if not self._try_get_container():
404 utils.get_logger().error("Could not get container '{}'".format(self._id))
405 return None
406
407 if self._manager:
408 result = LifecycleResult.from_dict(self._manager.app_start(self._id))
409 else:
410 update_libertine_profile(self._client)
411 update_bind_mounts(self._container, self._config)
412 result = lxd_start(self._container)
413
414 if not result.success:
415 utils.get_logger().error(result.error)
416 return False
417
418 args = self._lxc_args("sudo -E -u {} env PATH={}".format(os.environ['USER'], environ['PATH']), environ)
419
420 self._start_window_manager(args.copy())
421
422 args.extend(app_exec_line)
423 return psutil.Popen(args)
424
425 def finish_application(self, app):
426 if self._window_manager is not None:
427 utils.terminate_window_manager(self._window_manager)
428 self._window_manager = None
429
430 # This is a workaround for an issue on xenial where the process
431 # running the window manager is not killed with the application
432 if self._matchbox_pid is not None:
433 utils.get_logger().debug("Manually killing matchbox-window-manager")
434 self.run_in_container("kill -9 {}".format(self._matchbox_pid))
435 self._matchbox_pid = None
436
437 app.wait()
438
439 if self._manager:
440 self._manager.app_stop(self.container_id)
441 else:
442 lxd_stop(self._container, False)
443
444 def copy_file_to_container(self, source, dest):
445 with open(source, 'rb') as f:
446 return self._container.files.put(dest, f.read())
447
448 def delete_file_in_container(self, path):
449 return self.run_in_container('rm {}'.format(path))
450
451 def _try_get_container(self):
452 if self._container is None:
453 self._container = lxd_container(self._client, self._id)
454
455 return self._container is not None
0456
=== modified file 'python/libertine/launcher/session.py'
--- python/libertine/launcher/session.py 2016-10-27 16:57:03 +0000
+++ python/libertine/launcher/session.py 2017-01-05 20:19:05 +0000
@@ -29,9 +29,6 @@
29from .task import LaunchServiceTask, TaskType29from .task import LaunchServiceTask, TaskType
3030
3131
32log = utils.get_logger()
33
34
35def translate_to_real_address(abstract_address):32def translate_to_real_address(abstract_address):
36 """Translate the notional text address to a real UNIX-domain address string.33 """Translate the notional text address to a real UNIX-domain address string.
3734
@@ -101,12 +98,12 @@
101 b = from_socket.recv(4096)98 b = from_socket.recv(4096)
102 if len(b) > 0:99 if len(b) > 0:
103 to_socket.sendall(b)100 to_socket.sendall(b)
104 log.debug('copied {} bytes from fd to {}'.format(len(b), from_socket, to_socket))101 utils.get_logger().debug('copied {} bytes from fd to {}'.format(len(b), from_socket, to_socket))
105 else:102 else:
106 log.info('close detected on {}'.format(from_socket))103 utils.get_logger().info('close detected on {}'.format(from_socket))
107 return len(b)104 return len(b)
108 except Exception as e:105 except Exception as e:
109 log.debug(e)106 utils.get_logger().debug(e)
110 return 0107 return 0
111108
112 def _close_up_shop(self, session):109 def _close_up_shop(self, session):
@@ -172,7 +169,7 @@
172 with suppress(AttributeError):169 with suppress(AttributeError):
173 for task_config in self._config.prelaunch_tasks:170 for task_config in self._config.prelaunch_tasks:
174 if task_config.task_type == TaskType.LAUNCH_SERVICE:171 if task_config.task_type == TaskType.LAUNCH_SERVICE:
175 log.info("launching {}".format(task_config.datum[0]))172 utils.get_logger().info("launching {}".format(task_config.datum[0]))
176 task = LaunchServiceTask(task_config)173 task = LaunchServiceTask(task_config)
177 self._child_processes.append(task)174 self._child_processes.append(task)
178 task.start(self._config.host_environ)175 task.start(self._config.host_environ)
@@ -267,7 +264,7 @@
267 create a socket bridge to the host when a connection from the session is264 create a socket bridge to the host when a connection from the session is
268 made.265 made.
269 """266 """
270 log.debug('creating bridge listener for {} on {}'.267 utils.get_logger().debug('creating bridge listener for {} on {}'.
271 format(bridge_config.env_var, bridge_config.session_address))268 format(bridge_config.env_var, bridge_config.session_address))
272 sock = socket(AF_UNIX, SOCK_STREAM)269 sock = socket(AF_UNIX, SOCK_STREAM)
273 sock.bind(translate_to_real_address(bridge_config.session_address))270 sock.bind(translate_to_real_address(bridge_config.session_address))
@@ -286,7 +283,7 @@
286 """283 """
287 (bridge_config, sock) = datum284 (bridge_config, sock) = datum
288 conn = sock.accept()285 conn = sock.accept()
289 log.debug('connection of session socket {} accepted'.format(bridge_config.session_address))286 utils.get_logger().debug('connection of session socket {} accepted'.format(bridge_config.session_address))
290 self.add_bridge_pair(BridgePair(conn[0], bridge_config.host_address))287 self.add_bridge_pair(BridgePair(conn[0], bridge_config.host_address))
291288
292 def _ensure_paths_exist(self):289 def _ensure_paths_exist(self):
@@ -316,17 +313,17 @@
316 data = os.read(fd, 4)313 data = os.read(fd, 4)
317 sig = struct.unpack('%uB' % len(data), data)314 sig = struct.unpack('%uB' % len(data), data)
318 if sig[0] == signal.SIGCHLD:315 if sig[0] == signal.SIGCHLD:
319 log.info('SIGCHLD received')316 utils.get_logger().info('SIGCHLD received')
320 if self._handle_child_died():317 if self._handle_child_died():
321 raise StopIteration('launched program exited')318 raise StopIteration('launched program exited')
322 elif sig[0] == signal.SIGINT:319 elif sig[0] == signal.SIGINT:
323 log.info('SIGINT received')320 utils.get_logger().info('SIGINT received')
324 raise StopIteration('keyboard interrupt')321 raise StopIteration('keyboard interrupt')
325 elif sig[0] == signal.SIGTERM:322 elif sig[0] == signal.SIGTERM:
326 log.info('SIGTERM received')323 utils.get_logger().info('SIGTERM received')
327 raise StopIteration('terminate')324 raise StopIteration('terminate')
328 else:325 else:
329 log.warning('unknown signal {} received'.format(sig[0]))326 utils.get_logger().warning('unknown signal {} received'.format(sig[0]))
330327
331 def _set_signal_handlers(self):328 def _set_signal_handlers(self):
332 """Set the signal handlers."""329 """Set the signal handlers."""
333330
=== modified file 'python/libertine/launcher/task.py'
--- python/libertine/launcher/task.py 2016-10-27 17:25:08 +0000
+++ python/libertine/launcher/task.py 2017-01-05 20:19:05 +0000
@@ -44,7 +44,7 @@
4444
45 def __init__(self, config):45 def __init__(self, config):
46 """46 """
47 :param config: The task configuraiton.47 :param config: The task configuration.
48 :type config: TaskConfig48 :type config: TaskConfig
4949
50 The constructor unpacks the service commandline from the config datum.50 The constructor unpacks the service commandline from the config datum.
@@ -54,13 +54,13 @@
5454
55 def start(self, environ=None):55 def start(self, environ=None):
56 """Start the service.56 """Start the service.
57 57
58 :param env: An alternate environment dictionary.58 :param env: An alternate environment dictionary.
59 """59 """
60 self._process = Popen(self._command_line, env=environ)60 self._process = Popen(self._command_line, env=environ)
6161
62 def stop(self):62 def stop(self):
63 """Shuts the service down. """63 """Shuts the service down."""
64 try:64 try:
65 self._process.terminate()65 self._process.terminate()
66 except ProcessLookupError:66 except ProcessLookupError:
6767
=== added directory 'python/libertine/lifecycle'
=== added file 'python/libertine/lifecycle/ContainerLifecycleService.py'
--- python/libertine/lifecycle/ContainerLifecycleService.py 1970-01-01 00:00:00 +0000
+++ python/libertine/lifecycle/ContainerLifecycleService.py 2017-01-05 20:19:05 +0000
@@ -0,0 +1,108 @@
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2016 Canonical Ltd.
5
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; version 3 of the License.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18
19import dbus.exceptions
20import dbus.service
21
22from .LifecycleResult import LifecycleResult
23from collections import Counter
24from dbus.mainloop.glib import DBusGMainLoop
25from libertine import utils
26
27
28LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE = 'com.canonical.libertine.ContainerLifecycle'
29
30
31class ContainerLifecycleService(dbus.service.Object):
32 def __init__(self, service_name, service_path):
33 self._apps = Counter()
34 self._operations = Counter()
35
36 DBusGMainLoop(set_as_default=True)
37 try:
38 bus_name = dbus.service.BusName(service_name,
39 bus=dbus.SessionBus(),
40 do_not_queue=True)
41 except dbus.exceptions.NameExistsException:
42 utils.get_logger().error("service is already running")
43 raise
44 super().__init__(bus_name, service_path)
45
46 def start(self, container):
47 raise NotImplementedError("Subclasses must implement start(container)")
48
49 def stop(self, container):
50 raise NotImplementedError("Subclasses must implement stop(container)")
51
52 @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE,
53 in_signature='s',
54 out_signature='a{ss}')
55 def app_start(self, container):
56 utils.get_logger().debug("app_start({})".format(container))
57 if self._operations[container] != 0:
58 return LifecycleResult("Libertine container operation already running: cannot launch application.")
59
60 result = self.start(container, True)
61
62 if result.success:
63 self._apps[container] += 1
64
65 return result.to_dict()
66
67 @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE,
68 in_signature='s',
69 out_signature='a{ss}')
70 def app_stop(self, container):
71 utils.get_logger().debug("app_stop({})".format(container))
72 self._apps[container] -= 1
73 result = LifecycleResult()
74
75 if self._apps[container] == 0:
76 result = self.stop(container)
77 del self._apps[container]
78
79 return result.to_dict()
80
81 @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE,
82 in_signature='s',
83 out_signature='a{ss}')
84 def operation_start(self, container):
85 utils.get_logger().debug("operation_start({})".format(container))
86 if self._apps[container] != 0:
87 return LifecycleResult("Application already running in container: cannot run operation.")
88
89 result = self.start(container, False)
90
91 if result.success:
92 self._operations[container] += 1
93
94 return result.to_dict()
95
96 @dbus.service.method(LIBERTINE_CONTAINER_LIFECYCLE_INTERFACE,
97 in_signature='s',
98 out_signature='a{ss}')
99 def operation_stop(self, container):
100 utils.get_logger().debug("operation_stop({})".format(container))
101 self._operations[container] -= 1
102 result = LifecycleResult()
103
104 if self._operations[container] == 0:
105 result = self.stop(container)
106 del self._operations[container]
107
108 return result.to_dict()
0109
=== added file 'python/libertine/lifecycle/ContainerLifecycleServiceRunner.py'
--- python/libertine/lifecycle/ContainerLifecycleServiceRunner.py 1970-01-01 00:00:00 +0000
+++ python/libertine/lifecycle/ContainerLifecycleServiceRunner.py 2017-01-05 20:19:05 +0000
@@ -0,0 +1,46 @@
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2016 Canonical Ltd.
5
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; version 3 of the License.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import signal
19
20from gi.repository import GLib
21from libertine import utils
22
23
24class ContainerLifecycleServiceRunner(object):
25 def __init__(self, service):
26 self._service = service
27
28 def _sigterm(self, sig):
29 utils.get_logger().warning("Received SIGTERM")
30 self._shutdown()
31
32 def _shutdown(self):
33 utils.get_logger().info("Shutting down")
34 GLib.MainLoop().quit()
35
36 def run(self):
37 GLib.unix_signal_add(GLib.PRIORITY_HIGH,
38 signal.SIGTERM,
39 self._sigterm,
40 None)
41
42 try:
43 utils.get_logger().info("Starting main loop")
44 GLib.MainLoop().run()
45 except KeyboardInterrupt:
46 self._shutdown()
047
=== added file 'python/libertine/lifecycle/LifecycleResult.py'
--- python/libertine/lifecycle/LifecycleResult.py 1970-01-01 00:00:00 +0000
+++ python/libertine/lifecycle/LifecycleResult.py 2017-01-05 20:19:05 +0000
@@ -0,0 +1,37 @@
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2016 Canonical Ltd.
5
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; version 3 of the License.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18class LifecycleResult(object):
19 def __init__(self, error=''):
20 self._error = error
21
22 @property
23 def error(self):
24 return self._error or ''
25
26 @property
27 def success(self):
28 return self.error == ''
29
30 @classmethod
31 def from_dict(kls, d):
32 return LifecycleResult(d.get('error', None))
33
34 def to_dict(self):
35 return {
36 'error': self.error
37 }
038
=== added file 'python/libertine/lifecycle/__init__.py'
--- python/libertine/lifecycle/__init__.py 1970-01-01 00:00:00 +0000
+++ python/libertine/lifecycle/__init__.py 2017-01-05 20:19:05 +0000
@@ -0,0 +1,23 @@
1# Copyright 2016 Canonical Ltd.
2#
3# This program is free software: you can redistribute it and/or modify it
4# under the terms of the GNU General Public License version 3, as published
5# by the Free Software Foundation.
6#
7# This program is distributed in the hope that it will be useful, but
8# WITHOUT ANY WARRANTY; without even the implied warranties of
9# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10# PURPOSE. See the GNU General Public License for more details.
11#
12# You should have received a copy of the GNU General Public License along
13# with this program. If not, see <http://www.gnu.org/licenses/>.
14
15from .ContainerLifecycleServiceRunner import ContainerLifecycleServiceRunner
16from .ContainerLifecycleService import ContainerLifecycleService
17from .LifecycleResult import LifecycleResult
18
19__all__ = [
20 'ContainerLifecycleServiceRunner',
21 'ContainerLifecycleService',
22 'LifecycleResult'
23 ]
024
=== modified file 'python/libertine/utils.py'
--- python/libertine/utils.py 2016-12-02 16:38:11 +0000
+++ python/libertine/utils.py 2017-01-05 20:19:05 +0000
@@ -25,6 +25,7 @@
25require_version('Libertine', '1')25require_version('Libertine', '1')
26from gi.repository import Libertine26from gi.repository import Libertine
2727
28
28def get_logger():29def get_logger():
29 logger = logging.getLogger('__libertine_logger__')30 logger = logging.getLogger('__libertine_logger__')
3031
@@ -40,13 +41,27 @@
40 logger.addHandler(stream_handler)41 logger.addHandler(stream_handler)
4142
42 if 'LIBERTINE_DEBUG' in os.environ:43 if 'LIBERTINE_DEBUG' in os.environ:
43 logger.setLevel(logging.DEBUG)44 if os.environ['LIBERTINE_DEBUG'] == '0':
45 logger.setLevel(logging.WARNING)
46 elif os.environ['LIBERTINE_DEBUG'] == '1':
47 logger.setLevel(logging.INFO)
48 else:
49 logger.setLevel(logging.DEBUG)
44 else:50 else:
45 logger.setLevel(logging.WARNING)51 logger.setLevel(logging.WARNING)
4652
47 return logger53 return logger
4854
4955
56def set_environmental_verbosity(verbosity):
57 # Set debug levels if not overridden in environment
58 if 'LIBERTINE_DEBUG' not in os.environ:
59 if verbosity is None:
60 os.environ['LIBERTINE_DEBUG'] = '1'
61 else:
62 os.environ['LIBERTINE_DEBUG'] = str(verbosity)
63
64
50def get_libertine_container_rootfs_path(container_id):65def get_libertine_container_rootfs_path(container_id):
51 path = Libertine.container_path(container_id)66 path = Libertine.container_path(container_id)
5267
@@ -112,8 +127,8 @@
112127
113 name = dir128 name = dir
114 if name.startswith(prefix):129 if name.startswith(prefix):
115 name = name.replace(prefix, '', 1)130 name = name.replace(prefix, '', 1).lstrip('/')
116 name = name.lstrip('/')131
117 if name in names:132 if name in names:
118 binding_dirs.append((dir, "%s (%i)" % (name, names.count(name))))133 binding_dirs.append((dir, "%s (%i)" % (name, names.count(name))))
119 else:134 else:
@@ -201,3 +216,9 @@
201 return True216 return True
202 else:217 else:
203 return False218 return False
219
220
221def get_deb_package_name(package):
222 pkg_name_proc = subprocess.Popen(shlex.split('dpkg --field {} Package'.format(package)), stdout=subprocess.PIPE)
223 out, err = pkg_name_proc.communicate()
224 return out.decode('utf-8').strip()
204225
=== added file 'snapcraft.yaml'
--- snapcraft.yaml 1970-01-01 00:00:00 +0000
+++ snapcraft.yaml 2017-01-05 20:19:05 +0000
@@ -0,0 +1,32 @@
1name: libertine
2version: "1.5"
3summary: Libertine suite
4description: |
5 Suite for maintaining deb-based applications in a non-deb environment
6confinement: devmode
7grade: devel
8
9apps:
10 libertine-container-manager:
11 command: usr/bin/snap-runner.wrapper libertine-container-manager
12 libertine-manager-app:
13 command: usr/bin/snap-runner.wrapper libertine-manager-app
14 libertine-launch:
15 command: usr/bin/snap-runner.wrapper libertine-launch
16
17 # For debugging purposes
18 bash:
19 command: usr/bin/snap-runner.wrapper bash
20
21parts:
22 libertine-source:
23 plugin: libertine
24 source: .
25 after:
26 - desktop-qt5
27 libertine-deps:
28 plugin: libertine-deps
29 env:
30 plugin: dump
31 organize:
32 data/snap-runner.wrapper: usr/bin/snap-runner.wrapper
033
=== modified file 'tests/unit/test_libertine_gir.py'
--- tests/unit/test_libertine_gir.py 2016-08-28 00:17:54 +0000
+++ tests/unit/test_libertine_gir.py 2017-01-05 20:19:05 +0000
@@ -49,14 +49,12 @@
49 self.assertThat(container_home_path, Equals(self.cmake_source_dir + '/libertine-home/libertine-container/user-data/wily'))49 self.assertThat(container_home_path, Equals(self.cmake_source_dir + '/libertine-home/libertine-container/user-data/wily'))
5050
51 def test_container_name(self):51 def test_container_name(self):
52 container_id = 'wily'
53 with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}):52 with patch.dict('os.environ', {'XDG_DATA_HOME': self.cmake_source_dir + '/libertine-config'}):
54 container_name = Libertine.container_name(container_id)53 container_name = Libertine.container_name('wily')
5554
56 self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf'"))55 self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf'"))
5756
58 container_id = 'wily-2'57 container_name = Libertine.container_name('wily-2')
59 container_name = Libertine.container_name(container_id)
6058
61 self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf' (2)"))59 self.assertThat(container_name, Equals("Ubuntu 'Wily Werewolf' (2)"))
6260
6361
=== modified file 'tests/unit/test_logger.py'
--- tests/unit/test_logger.py 2016-08-25 01:49:07 +0000
+++ tests/unit/test_logger.py 2017-01-05 20:19:05 +0000
@@ -34,6 +34,11 @@
34 def test_logger_on_with_env_var(self):34 def test_logger_on_with_env_var(self):
35 with patch.dict('os.environ', {'LIBERTINE_DEBUG': '1'}):35 with patch.dict('os.environ', {'LIBERTINE_DEBUG': '1'}):
36 l = libertine.utils.get_logger()36 l = libertine.utils.get_logger()
37 self.assertThat(l.getEffectiveLevel(), Equals(logging.INFO))
38
39 def test_logger_on_with_env_var(self):
40 with patch.dict('os.environ', {'LIBERTINE_DEBUG': '2'}):
41 l = libertine.utils.get_logger()
37 self.assertThat(l.getEffectiveLevel(), Equals(logging.DEBUG))42 self.assertThat(l.getEffectiveLevel(), Equals(logging.DEBUG))
3843
39 def test_logger_only_inits_once(self):44 def test_logger_only_inits_once(self):
@@ -42,4 +47,3 @@
42 l2 = libertine.utils.get_logger()47 l2 = libertine.utils.get_logger()
43 l3 = libertine.utils.get_logger()48 l3 = libertine.utils.get_logger()
44 self.assertThat(len(l3.handlers), Equals(1))49 self.assertThat(len(l3.handlers), Equals(1))
45
4650
=== modified file 'tools/CMakeLists.txt'
--- tools/CMakeLists.txt 2016-10-28 19:39:13 +0000
+++ tools/CMakeLists.txt 2017-01-05 20:19:05 +0000
@@ -1,10 +1,9 @@
1install(PROGRAMS libertine-container-manager libertine-launch libertine-lxc-manager libertine-xmir libertine-lxc-setup libertined1install(PROGRAMS libertine-container-manager libertine-launch libertine-lxc-manager libertine-lxd-manager
2 libertine-xmir libertine-lxc-setup libertine-lxd-setup libertined
2 DESTINATION ${CMAKE_INSTALL_BINDIR})3 DESTINATION ${CMAKE_INSTALL_BINDIR})
3install(FILES libertine-launch.1 libertine-container-manager.1 libertine-lxc-manager.1 libertine-xmir.14install(FILES libertine-launch.1 libertine-container-manager.1 libertine-lxc-manager.1
5 libertine-lxd-manager.1 libertine-xmir.1
4 DESTINATION ${CMAKE_INSTALL_MANDIR}/man16 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
5 COMPONENT doc)7 COMPONENT doc)
6install(PROGRAMS update-puritine-containers
7 DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}/libertine)
8
9install(FILES completions/libertine-container-manager8install(FILES completions/libertine-container-manager
10 DESTINATION ${DESTDIR}/usr/share/bash-completion/completions/)9 DESTINATION ${DESTDIR}/usr/share/bash-completion/completions/)
1110
=== modified file 'tools/libertine-container-manager'
--- tools/libertine-container-manager 2016-11-16 17:33:42 +0000
+++ tools/libertine-container-manager 2017-01-05 20:19:05 +0000
@@ -23,7 +23,6 @@
23import sys23import sys
24import re24import re
2525
26from apt.debfile import DebPackage
27from libertine import LibertineContainer26from libertine import LibertineContainer
28from libertine.ContainersConfig import ContainersConfig27from libertine.ContainersConfig import ContainersConfig
29from libertine.HostInfo import HostInfo28from libertine.HostInfo import HostInfo
@@ -39,22 +38,22 @@
39 password = None38 password = None
4039
41 if args.distro and not self.host_info.is_distro_valid(args.distro, args.force):40 if args.distro and not self.host_info.is_distro_valid(args.distro, args.force):
42 print("Invalid distro %s" % args.distro, file=sys.stderr)41 libertine.utils.get_logger().error("Invalid distro %s" % args.distro)
43 sys.exit(1)42 sys.exit(1)
4443
45 if self.containers_config.container_exists(args.id):44 if self.containers_config.container_exists(args.id):
46 print("Container id '%s' is already used." % args.id, file=sys.stderr)45 libertine.utils.get_logger().error("Container id '%s' is already used." % args.id)
47 sys.exit(1)46 sys.exit(1)
48 elif re.match("^[a-z0-9][a-z0-9+.-]+$", args.id) is None:47 elif re.match("^[a-z0-9][a-z0-9+.-]+$", args.id) is None:
49 print("Container id '%s' invalid. ID must be of form ([a-z0-9][a-z0-9+.-]+)." % args.id, file=sys.stderr)48 libertine.utils.get_logger().error("Container id '%s' invalid. ID must be of form ([a-z0-9][a-z0-9+.-]+)." % args.id)
50 sys.exit(1)49 sys.exit(1)
5150
52 if not args.type:51 if not args.type:
53 container_type = self.host_info.select_container_type_by_kernel()52 container_type = self.host_info.select_container_type_by_kernel()
54 else:53 else:
55 if args.type == 'lxc' and not self.host_info.has_lxc_support():54 if (args.type == 'lxc' or args.type == 'lxd') and not self.host_info.has_lxc_support():
56 print("System kernel does not support lxc type containers. "55 libertine.utils.get_logger().error("System kernel does not support lxc type containers. "
57 "Please either use chroot or omit the -t option.")56 "Please either use chroot or omit the -t option.")
58 sys.exit(1)57 sys.exit(1)
59 container_type = args.type58 container_type = args.type
6059
@@ -64,8 +63,8 @@
64 host_distro = self.host_info.get_host_distro_release()63 host_distro = self.host_info.get_host_distro_release()
6564
66 if args.distro != host_distro:65 if args.distro != host_distro:
67 print("The container distribution needs to match the host ditribution for chroot"66 libertine.utils.get_logger().error("The container distribution needs to match the host ditribution for chroot"
68 " based containers. Please either use \'%s\' or omit the -d/--distro option."67 " based containers. Please either use \'%s\' or omit the -d/--distro option."
69 % host_distro)68 % host_distro)
70 sys.exit(1)69 sys.exit(1)
7170
@@ -88,11 +87,18 @@
88 multiarch = 'enabled'87 multiarch = 'enabled'
89 self.containers_config.update_container_multiarch_support(args.id, multiarch)88 self.containers_config.update_container_multiarch_support(args.id, multiarch)
9089
91 container = LibertineContainer(args.id)90 try:
92 self.containers_config.update_container_install_status(args.id, "installing")91 container = LibertineContainer(args.id)
93 if not container.create_libertine_container(password, args.multiarch, args.verbosity):92 self.containers_config.update_container_install_status(args.id, "installing")
93 if not container.create_libertine_container(password, args.multiarch):
94 libertine.utils.get_logger().error("Failed to create container")
95 self.containers_config.delete_container(args.id)
96 sys.exit(1)
97 except Exception as e:
98 libertine.utils.get_logger().error("Failed to create container: '{}'".format(str(e)))
94 self.containers_config.delete_container(args.id)99 self.containers_config.delete_container(args.id)
95 sys.exit(1)100 sys.exit(1)
101
96 self.containers_config.update_container_install_status(args.id, "ready")102 self.containers_config.update_container_install_status(args.id, "ready")
97103
98 libertine.utils.refresh_libertine_scope()104 libertine.utils.refresh_libertine_scope()
@@ -119,16 +125,16 @@
119125
120 if is_debian_package:126 if is_debian_package:
121 if os.path.exists(args.package):127 if os.path.exists(args.package):
122 package = DebPackage(args.package).pkgname128 package = libertine.utils.get_deb_package_name(args.package)
123 else:129 else:
124 print("%s does not exist." % args.package)130 libertine.utils.get_logger().error("%s does not exist." % args.package)
125 sys.exit(1)131 sys.exit(1)
126 else:132 else:
127 package = args.package133 package = args.package
128134
129 if self.containers_config.package_exists(container_id, package):135 if self.containers_config.package_exists(container_id, package):
130 if not is_debian_package:136 if not is_debian_package:
131 print("Package \'%s\' is already installed." % package)137 libertine.utils.get_logger().error("Package '%s' is already installed." % package)
132 sys.exit(1)138 sys.exit(1)
133 else:139 else:
134 self.containers_config.add_new_package(container_id, package)140 self.containers_config.add_new_package(container_id, package)
@@ -136,7 +142,9 @@
136 container = LibertineContainer(container_id)142 container = LibertineContainer(container_id)
137143
138 self.containers_config.update_package_install_status(container_id, package, "installing")144 self.containers_config.update_package_install_status(container_id, package, "installing")
139 if not container.install_package(args.package, args.verbosity, args.no_dialog):145 if not container.install_package(args.package, args.no_dialog):
146 libertine.utils.get_logger().error("Package '{}' failed to install in container '{}'"
147 .format(package, container_id))
140 self.containers_config.delete_package(container_id, package)148 self.containers_config.delete_package(container_id, package)
141 sys.exit(1)149 sys.exit(1)
142150
@@ -144,12 +152,12 @@
144152
145 libertine.utils.refresh_libertine_scope()153 libertine.utils.refresh_libertine_scope()
146154
147 def remove_package_by_name(self, container_id, package_name, verbosity=1, no_dialog=False):155 def remove_package_by_name(self, container_id, package_name, no_dialog=False):
148 fallback_status = self.containers_config.get_package_install_status(container_id, package_name)156 fallback_status = self.containers_config.get_package_install_status(container_id, package_name)
149 self.containers_config.update_package_install_status(container_id, package_name, "removing")157 self.containers_config.update_package_install_status(container_id, package_name, "removing")
150158
151 container = LibertineContainer(container_id)159 container = LibertineContainer(container_id)
152 if not container.remove_package(package_name, verbosity, no_dialog) and fallback_status == 'installed':160 if not container.remove_package(package_name, no_dialog) and fallback_status == 'installed':
153 self.containers_config.update_package_install_status(container_id, package_name, fallback_status)161 self.containers_config.update_package_install_status(container_id, package_name, fallback_status)
154 return False162 return False
155163
@@ -162,10 +170,12 @@
162 container_id = self.containers_config.check_container_id(args.id)170 container_id = self.containers_config.check_container_id(args.id)
163171
164 if self.containers_config.get_package_install_status(container_id, args.package) != 'installed':172 if self.containers_config.get_package_install_status(container_id, args.package) != 'installed':
165 print("Package \'%s\' is not installed." % args.package)173 libertine.utils.get_logger().error("Package \'%s\' is not installed." % args.package)
166 sys.exit(1)174 sys.exit(1)
167175
168 if not self.remove_package_by_name(container_id, args.package, args.verbosity, args.no_dialog):176 if not self.remove_package_by_name(container_id, args.package, args.no_dialog):
177 libertine.utils.get_logger().error("Package '{}' failed to be removed from container '{}'"
178 .format(args.package, container_id))
169 sys.exit(1)179 sys.exit(1)
170180
171 libertine.utils.refresh_libertine_scope()181 libertine.utils.refresh_libertine_scope()
@@ -175,6 +185,8 @@
175185
176 container = LibertineContainer(container_id)186 container = LibertineContainer(container_id)
177 if container.search_package_cache(args.search_string) is not 0:187 if container.search_package_cache(args.search_string) is not 0:
188 libertine.utils.get_logger().error("Search for '{}' in container '{}' exited with non-zero status"
189 .format(args.id, args.search_string))
178 sys.exit(1)190 sys.exit(1)
179191
180 def update(self, args):192 def update(self, args):
@@ -183,7 +195,7 @@
183 container = LibertineContainer(container_id)195 container = LibertineContainer(container_id)
184196
185 self.containers_config.update_container_install_status(container_id, "updating")197 self.containers_config.update_container_install_status(container_id, "updating")
186 if not container.update_libertine_container(args.verbosity):198 if not container.update_libertine_container():
187 self.containers_config.update_container_install_status(container_id, "ready")199 self.containers_config.update_container_install_status(container_id, "ready")
188 sys.exit(1)200 sys.exit(1)
189201
@@ -208,10 +220,10 @@
208 if not container.exec_command(args.command):220 if not container.exec_command(args.command):
209 sys.exit(1)221 sys.exit(1)
210222
211 def delete_archive_by_name(self, container_id, archive_name, verbosity=1):223 def delete_archive_by_name(self, container_id, archive_name):
212 if self.containers_config.get_archive_install_status(container_id, archive_name) == 'installed':224 if self.containers_config.get_archive_install_status(container_id, archive_name) == 'installed':
213 self.containers_config.update_archive_install_status(container_id, archive_name, 'removing')225 self.containers_config.update_archive_install_status(container_id, archive_name, 'removing')
214 if LibertineContainer(container_id).configure_remove_archive("\"" + archive_name + "\"", verbosity) is not 0:226 if LibertineContainer(container_id).configure_remove_archive("\"" + archive_name + "\"") is not 0:
215 self.containers_config.update_archive_install_status(container_id, archive_name, 'installed')227 self.containers_config.update_archive_install_status(container_id, archive_name, 'installed')
216 return False228 return False
217229
@@ -230,17 +242,17 @@
230242
231 current_multiarch = self.containers_config.get_container_multiarch_support(container_id)243 current_multiarch = self.containers_config.get_container_multiarch_support(container_id)
232 if current_multiarch == multiarch:244 if current_multiarch == multiarch:
233 print("i386 multiarch support is already %s" % multiarch)245 libertine.utils.get_logger().error("i386 multiarch support is already %s" % multiarch)
234 sys.exit(1)246 sys.exit(1)
235247
236 if container.configure_multiarch(args.multiarch, args.verbosity) is not 0:248 if container.configure_multiarch(args.multiarch) is not 0:
237 sys.exit(1)249 sys.exit(1)
238250
239 self.containers_config.update_container_multiarch_support(container_id, multiarch)251 self.containers_config.update_container_multiarch_support(container_id, multiarch)
240252
241 elif args.archive is not None:253 elif args.archive is not None:
242 if args.archive_name is None:254 if args.archive_name is None:
243 print("Configure archive called with no archive name. See configure --help for usage.")255 libertine.utils.get_logger().error("Configure archive called with no archive name. See configure --help for usage.")
244 sys.exit(1)256 sys.exit(1)
245257
246 archive_name = args.archive_name.strip("\'\"")258 archive_name = args.archive_name.strip("\'\"")
@@ -248,12 +260,12 @@
248260
249 if args.archive == 'add':261 if args.archive == 'add':
250 if self.containers_config.archive_exists(container_id, archive_name):262 if self.containers_config.archive_exists(container_id, archive_name):
251 print("%s already added in container." % archive_name)263 libertine.utils.get_logger().error("%s already added in container." % archive_name)
252 sys.exit(1)264 sys.exit(1)
253265
254 self.containers_config.add_container_archive(container_id, archive_name)266 self.containers_config.add_container_archive(container_id, archive_name)
255 self.containers_config.update_archive_install_status(container_id, archive_name, 'installing')267 self.containers_config.update_archive_install_status(container_id, archive_name, 'installing')
256 if container.configure_add_archive(archive_name_esc, args.public_key_file, args.verbosity) is not 0:268 if container.configure_add_archive(archive_name_esc, args.public_key_file) is not 0:
257 self.containers_config.delete_container_archive(container_id, archive_name)269 self.containers_config.delete_container_archive(container_id, archive_name)
258 sys.exit(1)270 sys.exit(1)
259271
@@ -261,48 +273,47 @@
261273
262 elif args.archive == 'remove':274 elif args.archive == 'remove':
263 if not self.containers_config.archive_exists(container_id, archive_name):275 if not self.containers_config.archive_exists(container_id, archive_name):
264 print("%s is not added in container." % archive_name)276 libertine.utils.get_logger().error("%s is not added in container." % archive_name)
265 sys.exit(1)277 sys.exit(1)
266278
267 if not self.delete_archive_by_name(container_id, archive_name):279 if not self.delete_archive_by_name(container_id, archive_name):
268 print("%s was not properly deleted." % archive_name)280 libertine.utils.get_logger().error("%s was not properly deleted." % archive_name)
269 sys.exit(1)281 sys.exit(1)
270282
271 elif args.bind_mount is not None:283 elif args.bind_mount is not None:
272 if args.mount_path is None:284 if args.mount_path is None:
273 print("Configure bind-mounts called without mount path. See configure --help for usage")285 libertine.utils.get_logger().error("Configure bind-mounts called without mount path. See configure --help for usage")
274 sys.exit(1)286 sys.exit(1)
275287
276 mount_path = args.mount_path.rstrip('/')288 mount_path = args.mount_path.rstrip('/')
277289
278 # validate bind-mount290 # validate bind-mount
279 if not mount_path.startswith(os.environ['HOME']) and not mount_path.startswith('/media/%s' % os.environ['USER']):291 if not mount_path.startswith(os.environ['HOME']) and not mount_path.startswith('/media/%s' % os.environ['USER']):
280 print("Cannot mount '%s', mount path must be in $HOME or /media/$USER." % mount_path)292 libertine.utils.get_logger().error("Cannot mount {}, mount path must be in {} or /media/{}.".format(mount_path, os.environ['HOME'], os.environ['USER']))
281 sys.exit(1)293 sys.exit(1)
282 if mount_path.startswith('/media/%s' % os.environ['USER']) and \294 if mount_path.startswith('/media/%s' % os.environ['USER']) and \
283 self.containers_config.get_container_type(container_id) == 'lxc':295 self.containers_config.get_container_type(container_id) == 'lxc':
284 print("/media mounts not currently supported in lxc.")296 libertine.utils.get_logger().error("/media mounts not currently supported in lxc.")
285 sys.exit(1)297 sys.exit(1)
286 if not os.path.isdir(mount_path):298 if not os.path.isdir(mount_path):
287 print("Cannot mount '%s', mount path must be an existing directory." % mount_path)299 libertine.utils.get_logger().error("Cannot mount '%s', mount path must be an existing directory." % mount_path)
288 sys.exit(1)300 sys.exit(1)
289301
290 # update database with new bind-mount302 # update database with new bind-mount
291 container_bind_mounts = self.containers_config.get_container_bind_mounts(container_id)303 container_bind_mounts = self.containers_config.get_container_bind_mounts(container_id)
292 if args.bind_mount == 'add':304 if args.bind_mount == 'add':
293 if mount_path in container_bind_mounts:305 if mount_path in container_bind_mounts:
294 print("Cannot add mount '%s', bind-mount already exists." % mount_path)306 libertine.utils.get_logger().error("Cannot add mount '%s', bind-mount already exists." % mount_path)
295 sys.exit(1)307 sys.exit(1)
296 self.containers_config.add_new_bind_mount(container_id, mount_path)308 self.containers_config.add_new_bind_mount(container_id, mount_path)
297 elif args.bind_mount == 'remove':309 elif args.bind_mount == 'remove':
298 if mount_path not in container_bind_mounts:310 if mount_path not in container_bind_mounts:
299 print("Cannot remove mount '%s', bind-mount does not exist." % mount_path)311 libertine.utils.get_logger().error("Cannot remove mount '%s', bind-mount does not exist." % mount_path)
300 sys.exit(1)312 sys.exit(1)
301 self.containers_config.delete_bind_mount(container_id, mount_path)313 self.containers_config.delete_bind_mount(container_id, mount_path)
302314
303
304 else:315 else:
305 print("Configure called with no subcommand. See configure --help for usage.")316 libertine.utils.get_logger().error("Configure called with no subcommand. See configure --help for usage.")
306 sys.exit(1)317 sys.exit(1)
307318
308319
@@ -340,17 +351,17 @@
340 parser = argparse.ArgumentParser(description="Legacy X application support for Unity 8")351 parser = argparse.ArgumentParser(description="Legacy X application support for Unity 8")
341352
342 if not os.geteuid():353 if not os.geteuid():
343 print("Please do not run %s using sudo" % parser.prog)354 libertine.utils.get_logger().error("Please do not run %s using sudo" % parser.prog)
344 sys.exit(1)355 sys.exit(1)
345356
346 container_manager = LibertineContainerManager()357 container_manager = LibertineContainerManager()
347358
348 parser.add_argument('-q', '--quiet',359 parser.add_argument('-q', '--quiet',
349 action='store_const', dest='verbosity', const=0,360 action='store_const', dest='verbosity', const=0,
350 help=('do not print status updates on stdout'))361 help=('disables all non-vital output'))
351 parser.add_argument('-v', '--verbose',362 parser.add_argument('-v', '--verbosity',
352 action='store_const', dest='verbosity', const=2,363 action='store_const', dest='verbosity', const=2,
353 help=('extra verbose output'))364 help=('enables debug output'))
354 subparsers = parser.add_subparsers(dest="subparser_name",365 subparsers = parser.add_subparsers(dest="subparser_name",
355 title="subcommands",366 title="subcommands",
356 metavar='create, destroy, install-package, remove-package, search-cache, update, list, list-apps, configure')367 metavar='create, destroy, install-package, remove-package, search-cache, update, list, list-apps, configure')
@@ -556,8 +567,8 @@
556567
557 # Actually parse the args568 # Actually parse the args
558 args = parser.parse_args()569 args = parser.parse_args()
559 if args.verbosity is None:570
560 args.verbosity = 1571 libertine.utils.set_environmental_verbosity(args.verbosity)
561572
562 if args.subparser_name == None:573 if args.subparser_name == None:
563 parser.print_help()574 parser.print_help()
564575
=== modified file 'tools/libertine-container-manager.1'
--- tools/libertine-container-manager.1 2016-10-14 14:44:17 +0000
+++ tools/libertine-container-manager.1 2017-01-05 20:19:05 +0000
@@ -26,10 +26,10 @@
26.SH OPTIONS26.SH OPTIONS
27.TP27.TP
28.BR \-q ", " \-\-quiet ""28.BR \-q ", " \-\-quiet ""
29do not print status updates on stdout29disable all non-vital output
30.TP30.TP
31.BR \-v ", " \-\-verbose ""31.BR \-v ", " \-\-verbose ""
32extra verbose output32enable debug output
33.TP33.TP
34.BR \-h ", " \-\-help ""34.BR \-h ", " \-\-help ""
35Print a summary of the command line options and exit.35Print a summary of the command line options and exit.
@@ -319,6 +319,12 @@
319Clear default container.319Clear default container.
320.RE320.RE
321.TP321.TP
322.BR
323
324.SH ENVIRONMENT VARIABLES
325.TP
326.BR LIBERTINE_DEBUG
327Overrides verbosity arguments. 0 for quiet, 1 for standard, 2 for debug.
322328
323.SH SEE ALSO329.SH SEE ALSO
324.UR https://launchpad.net/libertine330.UR https://launchpad.net/libertine
325331
=== modified file 'tools/libertine-lxc-manager'
--- tools/libertine-lxc-manager 2016-11-09 20:59:27 +0000
+++ tools/libertine-lxc-manager 2017-01-05 20:19:05 +0000
@@ -16,90 +16,50 @@
16# You should have received a copy of the GNU General Public License16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.17# along with this program. If not, see <http://www.gnu.org/licenses/>.
1818
19import dbus
20import dbus.service
21import libertine.LxcContainer19import libertine.LxcContainer
22import libertine.utils20import libertine.utils
23import os21import os
24import shlex22import shlex
25import signal
26import subprocess23import subprocess
2724
28from collections import Counter
29from dbus.mainloop.glib import DBusGMainLoop
30from gi.repository import GLib
31from libertine.ContainersConfig import ContainersConfig25from libertine.ContainersConfig import ContainersConfig
3226from libertine.lifecycle import *
3327
34home_path = os.environ['HOME']
3528
36LIBERTINE_LXC_MANAGER_NAME = libertine.LxcContainer.get_lxc_manager_dbus_name()29LIBERTINE_LXC_MANAGER_NAME = libertine.LxcContainer.get_lxc_manager_dbus_name()
37LIBERTINE_LXC_MANAGER_PATH = libertine.LxcContainer.get_lxc_manager_dbus_path()30LIBERTINE_LXC_MANAGER_PATH = libertine.LxcContainer.get_lxc_manager_dbus_path()
3831
3932
40class Service(dbus.service.Object):33class Service(ContainerLifecycleService):
4134
42 def __init__(self):35 def __init__(self):
43 self.is_pulse_setup = False36 super().__init__(LIBERTINE_LXC_MANAGER_NAME, LIBERTINE_LXC_MANAGER_PATH)
44 self.app_counter = Counter()37 self._home = os.environ['HOME']
45 self.containers_config = ContainersConfig()38 self._containers_config = ContainersConfig()
46 self.operation_counter = Counter()39 self._is_pulse_setup = False
4740
48 DBusGMainLoop(set_as_default=True)41 def start(self, container_id, launchable):
49 try:42 container = libertine.LxcContainer.lxc_container(container_id)
50 bus_name = dbus.service.BusName(LIBERTINE_LXC_MANAGER_NAME,43
51 bus=dbus.SessionBus(),44 if not container.defined:
52 do_not_queue=True)45 return LifecycleResult("Container {} is not valid".format(container_id))
53 except dbus.exceptions.NameExistsException:46
54 print("service is already running")47 if launchable and not self._is_pulse_setup:
55 raise48 self._setup_pulse()
56 super().__init__(bus_name, LIBERTINE_LXC_MANAGER_PATH)49
5750 if not container.running:
58 @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,51 if launchable:
59 in_signature='ss',52 self._dynamic_bind_mounts(container, container_id)
60 out_signature='(bs)')53
61 def app_start(self, container_id, lxc_logfile):54 return libertine.LxcContainer.lxc_start(container)
62 if self.operation_counter[container_id] != 0:55
63 return (False, "Libertine container operation already running: cannot launch application.")56 return LifecycleResult()
6457
65 (started, reason) = self._launch_lxc_container(container_id, lxc_logfile)58 def stop(self, container_id):
6659 container = libertine.LxcContainer.lxc_container(container_id)
67 if started:60 libertine.LxcContainer.lxc_stop(container)
68 self.app_counter[container_id] += 161
6962 return LifecycleResult() # no error case
70 return (started, reason)
71
72 @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
73 in_signature='s')
74 def app_stop(self, container_id):
75 self.app_counter[container_id] -= 1
76
77 if self.app_counter[container_id] == 0:
78 self._stop_lxc_container(container_id)
79 del self.app_counter[container_id]
80
81 @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
82 in_signature='ss',
83 out_signature='(bs)')
84 def operation_start(self, container_id, lxc_log_file):
85 if self.app_counter[container_id] != 0:
86 return (False, "Application already running in container: cannot run operation.")
87
88 (started, reason) = self._launch_lxc_container(container_id, lxc_log_file, launchable=False)
89
90 if started:
91 self.operation_counter[container_id] += 1
92
93 return (started, reason)
94
95 @dbus.service.method(LIBERTINE_LXC_MANAGER_NAME,
96 in_signature='s')
97 def operation_stop(self, container_id):
98 self.operation_counter[container_id] -= 1
99
100 if self.operation_counter[container_id] == 0:
101 self._stop_lxc_container(container_id)
102 del self.operation_counter[container_id]
10363
104 def _setup_pulse(self):64 def _setup_pulse(self):
105 pulse_socket_path = os.path.join(libertine.utils.get_libertine_runtime_dir(), 'pulse_socket')65 pulse_socket_path = os.path.join(libertine.utils.get_libertine_runtime_dir(), 'pulse_socket')
@@ -118,42 +78,26 @@
11878
119 self.is_pulse_setup = True79 self.is_pulse_setup = True
12080
121 def _launch_lxc_container(self, container_id, lxc_log_file, launchable=True):
122 container = libertine.LxcContainer.lxc_container(container_id)
123
124 if not container.defined:
125 return (False, "Container {} is not valid".format(container_id))
126
127 if launchable and not self.is_pulse_setup:
128 self._setup_pulse()
129
130 if not container.running:
131 if launchable:
132 self._dynamic_bind_mounts(container, container_id)
133
134 return libertine.LxcContainer.lxc_start(container, lxc_log_file)
135
136 return (True, "")
137
138 def _stop_lxc_container(self, container_id):
139 container = libertine.LxcContainer.lxc_container(container_id)
140
141 libertine.LxcContainer.lxc_stop(container)
142
143 def _dynamic_bind_mounts(self, container, container_id):81 def _dynamic_bind_mounts(self, container, container_id):
144 self.containers_config.refresh_database()82 self._containers_config.refresh_database()
145 mounts = self._sanitize_bind_mounts(libertine.utils.get_common_xdg_user_directories() + \83 mounts = self._sanitize_bind_mounts(libertine.utils.get_common_xdg_user_directories() + \
146 self.containers_config.get_container_bind_mounts(container_id))84 self._containers_config.get_container_bind_mounts(container_id))
14785
148 data_dir = libertine.utils.get_libertine_container_userdata_dir_path(container_id)86 data_dir = libertine.utils.get_libertine_container_userdata_dir_path(container_id)
149 for user_dir in libertine.utils.generate_binding_directories(mounts, home_path):87 for user_dir in libertine.utils.generate_binding_directories(mounts, self._home):
150 user_dir_fullpath = os.path.join(data_dir, user_dir[1])88 if os.path.isabs(user_dir[1]):
151 if not os.path.exists(user_dir_fullpath):89 path = user_dir[1].strip('/')
152 os.makedirs(user_dir_fullpath, exist_ok=True)90 fullpath = os.path.join(libertine.utils.get_libertine_container_rootfs_path(container_id), path)
15391 else:
92 path = "{}/{}".format(self._home.strip('/'), user_dir[1])
93 fullpath = os.path.join(data_dir, user_dir[1])
94
95 os.makedirs(fullpath, exist_ok=True)
96
97 libertine.utils.get_logger().debug("Mounting {}:{} in container {}".format(user_dir[0], path, container_id))
154 xdg_user_dir_entry = (98 xdg_user_dir_entry = (
155 "%s %s/%s none bind,create=dir,optional"99 "%s %s none bind,create=dir,optional"
156 % (user_dir[0], home_path.strip('/'), user_dir[1])100 % (user_dir[0], path)
157 )101 )
158 container.append_config_item("lxc.mount.entry", xdg_user_dir_entry)102 container.append_config_item("lxc.mount.entry", xdg_user_dir_entry)
159103
@@ -161,26 +105,5 @@
161 return [mount.replace(" ", "\\040") for mount in mounts]105 return [mount.replace(" ", "\\040") for mount in mounts]
162106
163107
164def sigterm(self):
165 shutdown()
166
167
168def shutdown():
169 GLib.MainLoop().quit()
170
171
172def main():
173 service = Service()
174 GLib.unix_signal_add(GLib.PRIORITY_HIGH,
175 signal.SIGTERM,
176 sigterm,
177 None)
178
179 try:
180 GLib.MainLoop().run()
181 except KeyboardInterrupt:
182 shutdown()
183
184
185if __name__ == '__main__':108if __name__ == '__main__':
186 main()109 ContainerLifecycleServiceRunner(Service()).run()
187110
=== added file 'tools/libertine-lxd-manager'
--- tools/libertine-lxd-manager 1970-01-01 00:00:00 +0000
+++ tools/libertine-lxd-manager 2017-01-05 20:19:05 +0000
@@ -0,0 +1,57 @@
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2016 Canonical Ltd.
5
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; version 3 of the License.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18import libertine.LxdContainer
19import libertine.utils
20import pylxd
21
22from libertine.ContainersConfig import ContainersConfig
23from libertine.lifecycle import *
24
25
26LIBERTINE_LXD_MANAGER_NAME = libertine.LxdContainer.get_lxd_manager_dbus_name()
27LIBERTINE_LXD_MANAGER_PATH = libertine.LxdContainer.get_lxd_manager_dbus_path()
28
29
30class Service(ContainerLifecycleService):
31
32 def __init__(self):
33 super().__init__(LIBERTINE_LXD_MANAGER_NAME, LIBERTINE_LXD_MANAGER_PATH)
34 self._config = ContainersConfig()
35 self._client = pylxd.Client()
36
37 def start(self, container_id, launchable):
38 container = libertine.LxdContainer.lxd_container(self._client, container_id)
39
40 if not container:
41 return LifecycleResult("Container {} is not valid".format(container_id))
42
43 if container.status != 'Running':
44 libertine.LxdContainer.update_libertine_profile(self._client)
45 if launchable:
46 libertine.LxdContainer.update_bind_mounts(container, self._config)
47
48 return libertine.LxdContainer.lxd_start(container)
49
50 return LifecycleResult()
51
52 def stop(self, container_id):
53 return libertine.LxdContainer.lxd_stop(libertine.LxdContainer.lxd_container(self._client, container_id), False)
54
55
56if __name__ == '__main__':
57 ContainerLifecycleServiceRunner(Service()).run()
058
=== added file 'tools/libertine-lxd-manager.1'
--- tools/libertine-lxd-manager.1 1970-01-01 00:00:00 +0000
+++ tools/libertine-lxd-manager.1 2017-01-05 20:19:05 +0000
@@ -0,0 +1,9 @@
1.TH libertine-lxd-manager "1" "Dec 2016" "libertine-lxd-manager 0.99" "User Commands"
2
3.SH NAME
4libertine-lxd-manager \- monitors LXD activity performed by libertine
5
6.SH DESCRIPTION
7usage: libertine\-lxd\-manager
8.PP
9monitors LXD activity performed by libertine
010
=== added file 'tools/libertine-lxd-setup'
--- tools/libertine-lxd-setup 1970-01-01 00:00:00 +0000
+++ tools/libertine-lxd-setup 2017-01-05 20:19:05 +0000
@@ -0,0 +1,40 @@
1#!/bin/sh
2
3if [ -z $1 ]; then
4 echo "Usage: $0 username"
5 exit 1
6fi
7
8USERNAME=$1
9
10# Create the lxd group and add given user
11if [ -z "`groups ${USERNAME} | grep lxd`" ]; then
12 groupadd --force --system lxd
13 usermod -G lxd -a $USERNAME
14fi
15
16# Map the given user to the container root user
17uid=`id --user ${USERNAME}`
18idmap="root:$uid:1"
19if [ -z "`grep ${idmap} /etc/subuid`" ]; then
20 echo ${idmap} | tee -a /etc/subuid /etc/subgid
21fi
22
23# find the right lxc command
24lxc=`which lxc`
25if [ -z "${lxc}" ]; then
26 if [ -n `which lxd.lxc` ]; then
27 lxc=`which lxd.lxc`
28 else
29 echo "No lxc command found on this system."
30 exit 1
31 fi
32fi
33
34# Run lxd init if there are no containers already on the system
35if [ 3 -ge `${lxc} list | wc -l` ]; then
36 lxd init
37fi
38
39mkdir -p /home/$USERNAME/.config/lxc
40chown -R $USERNAME:$USERNAME /home/$USERNAME/.config/lxc
041
=== modified file 'tools/libertined'
--- tools/libertined 2016-10-26 17:05:34 +0000
+++ tools/libertined 2017-01-05 20:19:05 +0000
@@ -62,11 +62,10 @@
62 os.rename(self.cache_file, '%s.1' % self.cache_file)62 os.rename(self.cache_file, '%s.1' % self.cache_file)
6363
64 def __enter__(self):64 def __enter__(self):
65 if self.config.debug:65 utils.set_environmental_verbosity(self.config.verbosity)
66 os.environ['LIBERTINE_DEBUG'] = '1'66
67 else:67 if not self.config.debug:
68 if self.config.cache_output:68 if self.config.cache_output:
69 os.environ['LIBERTINE_DEBUG'] = '1'
70 self._rotate_logs()69 self._rotate_logs()
71 self.output_file = open(self.cache_file, 'w')70 self.output_file = open(self.cache_file, 'w')
72 else:71 else:
@@ -94,7 +93,13 @@
94 self._arg_parser.add_argument(u'-d', u"--debug",93 self._arg_parser.add_argument(u'-d', u"--debug",
95 action='store_true',94 action='store_true',
96 default=False,95 default=False,
97 help=u"allow all output on stdout")96 help=u"print output to stdout")
97 self._arg_parser.add_argument('-q', '--quiet', action='store_const',
98 dest='verbosity', const=0,
99 help=('disables all non-vital output'))
100 self._arg_parser.add_argument('-v', '--verbosity', action='store_const',
101 dest='verbosity', const=2,
102 help=('enables debug output'))
98 self._arg_parser.add_argument(u'-c', u"--cache-output",103 self._arg_parser.add_argument(u'-c', u"--cache-output",
99 action='store_true',104 action='store_true',
100 default=False,105 default=False,
@@ -111,7 +116,7 @@
111 self.loop = GLib.MainLoop()116 self.loop = GLib.MainLoop()
112117
113 def sigterm(self, code):118 def sigterm(self, code):
114 utils.get_logger().info("terminate ('%s') signal received" % code)119 utils.get_logger().debug("terminate ('%s') signal received" % code)
115 self.shutdown()120 self.shutdown()
116121
117 def shutdown(self):122 def shutdown(self):
118123
=== removed file 'tools/update-puritine-containers'
--- tools/update-puritine-containers 2016-10-25 15:24:08 +0000
+++ tools/update-puritine-containers 1970-01-01 00:00:00 +0000
@@ -1,183 +0,0 @@
1#!/usr/bin/python3
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2016 Canonical Ltd.
5# Author: Christopher Townsend <christopher.townsend@canonical.com>
6
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; version 3 of the License.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19import json
20import libertine.utils
21import os
22import shlex
23import shutil
24import subprocess
25
26from gi.repository import GLib, Gio
27
28puritine_hook_dir = os.path.join(os.environ['HOME'], '.cache', 'libertine', 'puritine')
29puritine_symlink_farm_file = os.path.join(puritine_hook_dir, 'PuritineSymlinkFarm.json')
30puritine_click_config_file = os.path.join('libertine-config', 'libertine', 'ContainersConfig.json')
31
32
33def symlink_farm_entries_count():
34 if (puritine_symlink_farm_list and
35 puritine_symlink_farm_list['customContainers']):
36 return len(puritine_symlink_farm_list['customContainers'])
37
38 return 0
39
40
41def puritine_symlink_exists(symlink):
42 for file in os.listdir(puritine_hook_dir):
43 if file == symlink:
44 return True
45
46 return False
47
48
49def get_symlink_name(puritine_symlink_farm_list, container_id):
50 if puritine_symlink_farm_list:
51 for container in puritine_symlink_farm_list['customContainers']:
52 if container['id'] == container_id:
53 return (puritine_symlink_farm_list['customContainers'].index(container), container['symlinkName'])
54
55 return (0, None)
56
57
58def find_removed_puritine_symlinks(puritine_symlink_farm_list):
59 for container in puritine_symlink_farm_list['customContainers']:
60 if puritine_symlink_exists(container['symlinkName']):
61 continue
62 else:
63 manager_cmd = "libertine-container-manager destroy -i " + container['id']
64 cmd = shlex.split(manager_cmd)
65 subprocess.Popen(cmd).wait()
66 puritine_symlink_farm_list['customContainers'].remove(container)
67
68 return puritine_symlink_farm_list
69
70
71def find_new_or_updated_puritine_symlinks(puritine_symlink_farm_list):
72 symlinks = os.listdir(puritine_hook_dir)
73
74 for symlink in symlinks:
75 puritine_click_path = os.path.join(puritine_hook_dir, symlink)
76 if not os.path.islink(puritine_click_path):
77 continue
78
79 config_file_path = os.path.join(puritine_click_path, puritine_click_config_file)
80
81 with open(config_file_path, 'r') as fd:
82 container_list = json.load(fd)
83
84 container_id = container_list['containerList'][0]['id']
85
86 (index, symlink_name) = get_symlink_name(puritine_symlink_farm_list, container_id)
87
88 if symlink_name:
89 # Package exists and symlink name is the same- no update
90 if symlink_name == symlink:
91 continue
92 else:
93 puritine_symlink_farm_list['customContainers'][index]['symlinkName'] = symlink
94 else:
95 symlink_obj = {'symlinkName': symlink, 'id': container_id}
96 if 'customContainers' not in puritine_symlink_farm_list:
97 puritine_symlink_farm_list['customContainers'] = [symlink_obj]
98 else:
99 puritine_symlink_farm_list['customContainers'].append(symlink_obj)
100
101 puritine_click_rootfs_path = os.path.join(puritine_click_path, 'libertine-data', 'libertine-container',
102 container_id, 'rootfs')
103
104 libertine_containers_path = libertine.utils.get_libertine_containers_dir_path()
105 puritine_container_path = os.path.join(libertine_containers_path, container_id)
106 puritine_container_rootfs_path = os.path.join(puritine_container_path, 'rootfs')
107
108 if not os.path.exists(puritine_container_path):
109 os.makedirs(puritine_container_path)
110 else:
111 os.remove(puritine_container_rootfs_path)
112
113 # Link to the click packages container
114 os.symlink(puritine_click_rootfs_path, puritine_container_rootfs_path)
115
116 # Copy any user data that does not exist
117 libertine_user_data_path = libertine.utils.get_libertine_container_userdata_dir_path(container_id)
118 if not os.path.exists(libertine_user_data_path):
119 puritine_click_user_data_path = os.path.join(puritine_click_path, 'libertine-config', 'libertine-container',
120 'user-data', container_id)
121 shutil.copytree(puritine_click_user_data_path, libertine_user_data_path)
122
123 # Update the main ContainerConfig.json
124 manager_cmd = "libertine-container-manager merge-configs -f " + config_file_path
125 cmd = shlex.split(manager_cmd)
126 subprocess.Popen(cmd).wait()
127
128 return puritine_symlink_farm_list
129
130
131def favorite_libertine_scope():
132 libertine_scope_value = 'scope://libertine-scope.ubuntu_libertine-scope'
133 click_scope_value = 'scope://clickscope'
134 schema = 'com.canonical.Unity.Dash'
135 schema_key = 'favorite-scopes'
136
137 settings = Gio.Settings.new(schema)
138
139 favorites = settings.get_value(schema_key)
140
141 array = favorites.dup_strv()
142
143 if not libertine_scope_value in array:
144 array.insert(array.index(click_scope_value) + 1, libertine_scope_value)
145 new_favorites = GLib.Variant.new_strv(array)
146 settings.set_value(schema_key, new_favorites)
147 del new_favorites
148
149 del array
150 del favorites
151 del settings
152
153
154if __name__ == '__main__':
155 puritine_symlink_farm_list = {}
156 update_libertine_scope = libertine.utils.set_session_dbus_env_var()
157
158 if not os.path.exists(puritine_hook_dir):
159 os.makedirs(puritine_hook_dir)
160
161 if (os.path.exists(puritine_symlink_farm_file) and
162 os.path.getsize(puritine_symlink_farm_file) != 0):
163 with open(puritine_symlink_farm_file, 'r') as fd:
164 puritine_symlink_farm_list = json.load(fd)
165
166 count_at_start = symlink_farm_entries_count()
167
168 puritine_symlink_farm_list = find_new_or_updated_puritine_symlinks(puritine_symlink_farm_list)
169
170 if puritine_symlink_farm_list:
171 puritine_symlink_farm_list = find_removed_puritine_symlinks(puritine_symlink_farm_list)
172
173 count_at_finish = symlink_farm_entries_count()
174
175 with open(puritine_symlink_farm_file, 'w') as fd:
176 json.dump(puritine_symlink_farm_list, fd, sort_keys=True, indent=4)
177 fd.write('\n')
178
179 if count_at_start == 0 and count_at_finish > 0 and update_libertine_scope:
180 favorite_libertine_scope()
181
182 if update_libertine_scope:
183 libertine.utils.refresh_libertine_scope()

Subscribers

People subscribed via source and target branches