summaryrefslogtreecommitdiff
path: root/ick2/actionenvs.py
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2018-03-17 19:35:30 +0200
committerLars Wirzenius <liw@liw.fi>2018-03-30 11:52:19 +0300
commitc4841e5b3af2ebaa448a1a0b43a65941cf5c6c3e (patch)
tree5044362b227e3e92254847c40c00f02671f13106 /ick2/actionenvs.py
parent114a276e06aeb19cd9150175efa53d7267b6a6ff (diff)
downloadick2-c4841e5b3af2ebaa448a1a0b43a65941cf5c6c3e.tar.gz
Add: ActionEnvironment class for executing in various contexts
Diffstat (limited to 'ick2/actionenvs.py')
-rw-r--r--ick2/actionenvs.py167
1 files changed, 167 insertions, 0 deletions
diff --git a/ick2/actionenvs.py b/ick2/actionenvs.py
new file mode 100644
index 0000000..7a2b57a
--- /dev/null
+++ b/ick2/actionenvs.py
@@ -0,0 +1,167 @@
+# Copyright (C) 2018 Lars Wirzenius
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+
+import logging
+import os
+import subprocess
+
+
+import cliapp
+
+
+class Runner:
+
+ def __init__(self, reporter, maxbuf=128*1024):
+ self._reporter = reporter
+ self._buffers = {
+ 'stdout': b'',
+ 'stderr': b'',
+ }
+ self._maxbuf = maxbuf
+ self._timeout = 1.0
+
+ def runcmd(self, *argvs, **kwargs):
+ for argv in argvs:
+ logging.debug('Runner.runcmd: argv: %r', argv)
+ for key in kwargs:
+ logging.debug('Runner.runcmd: kwargs: %r=%r', key, kwargs[key])
+ assert all(argv is not None for argv in argvs)
+ exit_code, _, _ = cliapp.runcmd_unchecked(
+ *argvs,
+ stdout_callback=self.capture_stdout,
+ stderr=subprocess.STDOUT,
+ output_timeout=self._timeout,
+ timeout_callback=self.flush,
+ **kwargs
+ )
+ self.flush()
+ logging.debug('Runner.runcmd: finished, exit code: %d', exit_code)
+ return exit_code
+
+ def capture_stdout(self, data):
+ return self.capture('stdout', data)
+
+ def capture(self, stream_name, data):
+ self._buffers[stream_name] += data
+ if len(self._buffers[stream_name]) >= self._maxbuf:
+ self.flush()
+
+ return b''
+
+ def flush(self):
+ stdout = self._buffers['stdout']
+ stderr = self._buffers['stderr']
+ self._reporter.report(None, stdout, stderr)
+ self._buffers['stdout'] = b''
+ self._buffers['stderr'] = b''
+
+
+class Mounter: # pragma: no cover
+
+ def __init__(self, mounts, runner):
+ self._mounts = mounts
+ self._runner = runner
+
+ def __enter__(self):
+ self.mount()
+ return self
+
+ def __exit__(self, *args):
+ self.unmount()
+
+ def mount(self):
+ for dirname, mp in self._mounts:
+ if not os.path.exists(mp):
+ os.mkdir(mp)
+ self._runner.runcmd(['sudo', 'mount', '--bind', dirname, mp])
+
+ def unmount(self):
+ for dirname, mp in reversed(self._mounts):
+ try:
+ self._runner.runcmd(['sudo', 'umount', mp])
+ except BaseException as e:
+ logging.error(
+ 'Ignoring error while unmounting %s: %s', mp, str(e))
+
+
+class ActionEnvironment: # pragma: no cover
+
+ def __init__(self, systree, workspace, reporter):
+ super().__init__()
+ self._systree = systree
+ self._workspace = workspace
+ self._reporter = reporter
+
+ def get_systree_directory(self):
+ return self._systree
+
+ def get_workspace_directory(self):
+ return self._workspace
+
+ def get_mounts(self):
+ return []
+
+ def report(self, exit_code, msg):
+ self._reporter.report(exit_code, msg, None)
+
+ def runcmd(self, argv):
+ raise NotImplementedError()
+
+ def host_runcmd(self, *argvs, cwd=None):
+ env = self.get_env_vars()
+ runner = Runner(self._reporter)
+ mounts = self.get_mounts()
+ with Mounter(mounts, runner):
+ return runner.runcmd(*argvs, cwd=cwd, env=env)
+
+ def get_env_vars(self):
+ env = dict(os.environ)
+ env.update({
+ 'LC_ALL': 'C',
+ 'DEBIAN_FRONTEND': 'noninteractive',
+ })
+ return env
+
+
+class HostEnvironment(ActionEnvironment):
+
+ def runcmd(self, argv):
+ return self.host_runcmd(argv, cwd=self._workspace)
+
+
+class ChrootEnvironment(ActionEnvironment):
+
+ def get_mounts(self): # pragma: no cover
+ return [
+ ('/proc', os.path.join(self._workspace, 'proc')),
+ ('/sys', os.path.join(self._workspace, 'sys')),
+ ]
+
+ def runcmd(self, argv):
+ prefix = ['sudo', 'chroot', self._workspace]
+ return self.host_runcmd(prefix + argv)
+
+
+class ContainerEnvironment(ActionEnvironment):
+
+ def runcmd(self, argv):
+ bind = '{}:/workspace'.format(self._workspace)
+ prefix = [
+ 'sudo', 'systemd-nspawn',
+ '-D', self._systree,
+ '--bind', bind,
+ '--chdir', '/workspace',
+ ]
+ return self.host_runcmd(prefix + argv)