From 13930c94ed984b9f5f057641c5cadfe030e7adb6 Mon Sep 17 00:00:00 2001 From: Gilles Grandou Date: Sun, 25 Apr 2021 18:09:29 +0200 Subject: [PATCH] Initial commit --- runon | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++ runon.conf | 53 ++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100755 runon create mode 100644 runon.conf diff --git a/runon b/runon new file mode 100755 index 0000000..b67f81f --- /dev/null +++ b/runon @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 + +import sys +import argparse +import docker +import dockerpty +import io +import json +import getpass +import platform +import os +import re +import xdg.BaseDirectory +import configparser +import subprocess +from pprint import pprint + + +def load_config(user_confname, osname): + ini_list = [ 'runon.conf', '.runon.conf', os.path.join(xdg.BaseDirectory.xdg_config_home, 'runon', 'runon.conf'), '/etc/runon/runon.conf' ] + defaults = { + 'osname': osname, + 'user': getpass.getuser() + } + if user_confname: + ini_list.insert(0, user_confname) + ini = configparser.ConfigParser(defaults=defaults, interpolation=configparser.ExtendedInterpolation()) + ini.read(ini_list) + if not ini.has_section(osname): + print('ERROR: cannot find configuration for distribution "{}"'.format(osname)) + sys.exit(1) + conf = {} + fields = [ 'dockerfile', 'pkginstall', 'packages', 'environment', 'binds', 'user', 'osname' ] + try: + for f in fields: + conf[f] = ini.get(osname, f) + except configparser.NoOptionError as e: + print('ERROR: {}'.format(e)) + sys.exit(1) + for f in [ 'dockerfile', 'environment', 'binds' ]: + conf[f] = [ i for i in conf[f].split('\n') if i ] + for f in [ 'packages' ]: + conf[f] = conf[f].split() + return conf + + +def make_image_name(osname): + user = getpass.getuser() + name = 'runon-{}-{}'.format(osname, user) + return name + + +def build_image(client, conf, update, verbose): + packages = conf['packages'] + dockerfile = conf['dockerfile'] + pkginstall = conf['pkginstall'] + for p in packages: + dockerfile.append(pkginstall.format(p)) + tag = make_image_name(conf['osname']) + try: + if verbose: + # fallback to external command 'docker build' as there is + # no way to follow the build progress with API. + print('Building image {} ...'.format(tag)) + cmd = ['docker', 'build'] + if update: + cmd.append('--no-cache') + cmd += ['-t', tag, '-'] + ret = subprocess.run(cmd, + input='\n'.join(dockerfile).encode('utf-8'), + stderr=subprocess.STDOUT, check=True) + image = client.images.get(tag) + else: + with io.BytesIO('\n'.join(dockerfile).encode('utf-8')) as fd: + image, logs = client.images.build(tag=tag, fileobj=fd, rm=True, nocache=update) + if verbose: + print('Built image {} / {}'.format(image.tags[0], image.short_id)) + for l in logs: + print(l.get('stream', '').strip('\n')) + except (docker.errors.BuildError, KeyboardInterrupt, subprocess.CalledProcessError, docker.errors.ImageNotFound) as e: + print('Build Error: {}'.format(e)) + print() + print('with dockerfile:') + for line in dockerfile: + print(' {}'.format(line)) + sys.exit(1) + return image + + +def create_container(client, image, conf, command, verbose): + volumes = {} + environment = {} + for mnt in conf['binds']: + mnt = mnt.split(':') + if mnt[-1] in ['ro','rw']: + mode = mnt[-1] + del mnt[-1] + else: + mode = 'rw' + mnt = mnt[:2] + bind = mnt[-1] + vol = mnt[0] + volumes[vol] = { 'bind': bind, 'mode': mode } + hostname = platform.node() + for v in conf['environment']: + e = v.split('=') + if len(e) == 1: + e.append(os.getenv(e[0])) + environment[e[0]] = e[1] + #environment['debian_chroot']=conf['osname'] + user='{}:{}'.format(os.getuid(), os.getgid()) + pwd=os.getcwd() + + container = client.containers.create(image, command, + detach=False, stdin_open=True, tty=True, + auto_remove=True, + hostname=hostname, + volumes=volumes, + environment=environment, + user=user, + network_mode='host', + working_dir=pwd + ) + return container + + +def run_container(client, container): + dockerpty.start(client.api, container.id) + container.reload() # to update attrs fields + ret = container.attrs['State']['ExitCode'] + return ret + + +def main(): + osname = None + run_name = os.path.basename(os.sys.argv[0]) + if run_name == 'runos': + pass + elif run_name.startswith('runon'): + m = re.match('runon[-_]?(.*)$', run_name) + if m: + osname = m[1] + else: + osname = run_name + + parser = argparse.ArgumentParser() + if osname: + parser.description = 'run commands on "{}" distribution'.format(osname) + else: + parser.description = 'run commands on any distribution' + parser.add_argument('osname', + help = 'distribution name to run on') + + parser.add_argument('-v', '--verbose', action='store_true', + help='verbose output') + parser.add_argument('-c', '--config', + help='specify config file') + parser.add_argument('-u', '--update', action='store_true', + help='force image update') + parser.add_argument('command', nargs='*', default=None, + help = 'command to execute') + + args = parser.parse_args() + if osname: + args.osname = osname + + client = docker.from_env() + conf = load_config(args.config, args.osname) + image = build_image(client, conf, args.update, args.verbose) + container = create_container(client, image, conf, args.command, args.verbose) + ret = run_container(client, container) + return ret + + +if __name__ == '__main__': + ret = main() + sys.exit(ret) diff --git a/runon.conf b/runon.conf new file mode 100644 index 0000000..43dcc65 --- /dev/null +++ b/runon.conf @@ -0,0 +1,53 @@ +[DEFAULT] +environment = + HOME + USER + DISPLAY + debian_chroot=${osname} + + +binds = + /etc/passwd:ro + /etc/group:ro + /etc/shadow:ro + /tmp/.X11-unix:ro + /home/${user} + +[centos7] +dockerfile = + FROM centos:7 + RUN yum install sudo -y + RUN echo "Defaults lecture = never" >> /etc/sudoers + RUN echo "ALL ALL=(ALL) ALL" >> /etc/sudoers +pkginstall = RUN yum install {} -y +packages = ksh csh xterm xorg-x11-apps xkeyboard-config git + +[centos8] +dockerfile = + FROM centos:8 + RUN yum install dnf-plugins-core -y + RUN yum config-manager --set-enabled powertools -y + RUN yum install sudo -y + RUN echo "Defaults lecture = never" >> /etc/sudoers + RUN echo "ALL ALL=(ALL) ALL" >> /etc/sudoers +pkginstall = RUN yum install {} -y +packages = ksh csh xterm xorg-x11-apps xkeyboard-config git + +[debian9] +dockerfile = + FROM debian:9 + RUN apt-get update + RUN apt-get -y install sudo + RUN echo "Defaults lecture = never" >> /etc/sudoers +pkginstall = RUN apt-get -y install {} +packages = ksh csh xterm x11-apps libgtk-3-0 build-essential git + +[ubuntu20.04] +dockerfile = + FROM ubuntu:20.04 + RUN apt-get update + RUN apt-get -y install sudo +pkginstall = RUN apt-get -y install {} +packages = ksh csh xterm x11-apps build-essential git + +