[libvirt] [sandbox PATCH v2 00/19] *** Virt-sandbox-image ***

virt-sandbox-image.py is a python script that lets you download and run templates from supported sources using virt-sandbox. Component-based archictecture is accomplished through Source base class. Docker image support is added through DockerSource. DockerSource is capable of downloading and running Docker images by consuming Docker Registry API. **Changes for v2** *Trailing spaces are gone forever. make syntax-check now is ok. *Python version < 2.7.9 or < 3.4.3 now gives a warring when downloading a docker image from ssl. *Dynamic resource loader has been changed. Now it uses class naming convention in order to load custom sources. In previous patch series, custom sources used to register themselves into a common area, so we can load from them * -c/--connect parameter is for providing URI to the libvirt. * Private methods now starts with a single underscore instead of double underscores * virt-sandbox-image/sources/__init__.py is added * Network params can be passed to running sandbox. * Custom volume support is added through host-bind. * Custom environment variables can be passed into virt-sandbox * Custom environment support for virt-sandbox-image Daniel P Berrange (1): Add virt-sandbox-image Eren Yagdiran (18): Fix virt-sandbox-image Image: Add Hooking Mechanism Image: Add download function Image: Refactor create function Image: Add delete function Image: Add get_command function to Source Image: Add run args Image: Add check_connect function Image: Add get_disk function to Source Image: Add run function Image: Add network support Image: Add Volume Support Image: man file for virt-sandbox-image Add configuration object for environment variables Add environment parameter to virt-sandbox Common-init: Exporting custom environment variables Add testcase for custom environment variables Image: Add custom environment support .gitignore | 1 + bin/Makefile.am | 21 +- bin/virt-sandbox-image.in | 3 + bin/virt-sandbox-image.pod | 172 +++++++++++ bin/virt-sandbox.c | 14 + configure.ac | 2 + libvirt-sandbox/Makefile.am | 2 + libvirt-sandbox/libvirt-sandbox-config-all.h | 1 + libvirt-sandbox/libvirt-sandbox-config-env.c | 199 ++++++++++++ libvirt-sandbox/libvirt-sandbox-config-env.h | 78 +++++ libvirt-sandbox/libvirt-sandbox-config.c | 187 +++++++++++- libvirt-sandbox/libvirt-sandbox-config.h | 12 + libvirt-sandbox/libvirt-sandbox-init-common.c | 30 ++ libvirt-sandbox/libvirt-sandbox.h | 1 + libvirt-sandbox/libvirt-sandbox.sym | 6 + libvirt-sandbox/tests/test-config.c | 10 + po/POTFILES.in | 1 + virt-sandbox-image/Makefile.am | 14 + virt-sandbox-image/sources/DockerSource.py | 425 ++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 59 ++++ virt-sandbox-image/sources/__init__.py | 29 ++ virt-sandbox-image/virt-sandbox-image.py | 267 ++++++++++++++++ 22 files changed, 1529 insertions(+), 5 deletions(-) create mode 100644 bin/virt-sandbox-image.in create mode 100644 bin/virt-sandbox-image.pod create mode 100644 libvirt-sandbox/libvirt-sandbox-config-env.c create mode 100644 libvirt-sandbox/libvirt-sandbox-config-env.h create mode 100644 virt-sandbox-image/Makefile.am create mode 100644 virt-sandbox-image/sources/DockerSource.py create mode 100644 virt-sandbox-image/sources/Source.py create mode 100644 virt-sandbox-image/sources/__init__.py create mode 100755 virt-sandbox-image/virt-sandbox-image.py -- 2.1.0

From: Daniel P Berrange <berrange@redhat.com> virt-sandbox-image.py is a python script that lets you download Docker images easily. It is a proof of concept code and consumes Docker Rest API. --- po/POTFILES.in | 1 + virt-sandbox-image/virt-sandbox-image.py | 394 +++++++++++++++++++++++++++++++ 2 files changed, 395 insertions(+) create mode 100644 virt-sandbox-image/virt-sandbox-image.py diff --git a/po/POTFILES.in b/po/POTFILES.in index afcb050..7204112 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -11,3 +11,4 @@ libvirt-sandbox/libvirt-sandbox-context-interactive.c libvirt-sandbox/libvirt-sandbox-init-common.c libvirt-sandbox/libvirt-sandbox-rpcpacket.c libvirt-sandbox/libvirt-sandbox-util.c +virt-sandbox-image/virt-sandbox-image.py diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py new file mode 100644 index 0000000..4f5443b --- /dev/null +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -0,0 +1,394 @@ +#!/usr/bin/python -Es +# +# Authors: Daniel P. Berrange <berrange@redhat.com> +# +# Copyright (C) 2013 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +import argparse +import gettext +import hashlib +import json +import os +import os.path +import shutil +import sys +import urllib2 +import subprocess + +default_index_server = "index.docker.io" +default_template_dir = "/var/lib/libvirt/templates" + +debug = True +verbose = True + +gettext.bindtextdomain("libvirt-sandbox", "/usr/share/locale") +gettext.textdomain("libvirt-sandbox") +try: + gettext.install("libvirt-sandbox", + localedir="/usr/share/locale", + unicode=False, + codeset = 'utf-8') +except IOError: + import __builtin__ + __builtin__.__dict__['_'] = unicode + + +def debug(msg): + sys.stderr.write(msg) + +def info(msg): + sys.stdout.write(msg) + +def get_url(server, path, headers): + url = "https://" + server + path + debug(" Fetching %s..." % url) + req = urllib2.Request(url=url) + + if json: + req.add_header("Accept", "application/json") + + for h in headers.keys(): + req.add_header(h, headers[h]) + + return urllib2.urlopen(req) + +def get_json(server, path, headers): + try: + res = get_url(server, path, headers) + data = json.loads(res.read()) + debug("OK\n") + return (data, res) + except Exception, e: + debug("FAIL %s\n" % str(e)) + raise + +def save_data(server, path, headers, dest, checksum=None, datalen=None): + try: + res = get_url(server, path, headers) + + csum = None + if checksum is not None: + csum = hashlib.sha256() + + pattern = [".", "o", "O", "o"] + patternIndex = 0 + donelen = 0 + + with open(dest, "w") as f: + while 1: + buf = res.read(1024*64) + if not buf: + break + if csum is not None: + csum.update(buf) + f.write(buf) + + if datalen is not None: + donelen = donelen + len(buf) + debug("\x1b[s%s (%5d Kb of %5d Kb)\x1b8" % ( + pattern[patternIndex], (donelen/1024), (datalen/1024) + )) + patternIndex = (patternIndex + 1) % 4 + + debug("\x1b[K") + if csum is not None: + csumstr = "sha256:" + csum.hexdigest() + if csumstr != checksum: + debug("FAIL checksum '%s' does not match '%s'" % (csumstr, checksum)) + os.remove(dest) + raise IOError("Checksum '%s' for data does not match '%s'" % (csumstr, checksum)) + debug("OK\n") + return res + except Exception, e: + debug("FAIL %s\n" % str(e)) + raise + + +def download_template(name, server, destdir): + tag = "latest" + + offset = name.find(':') + if offset != -1: + tag = name[offset + 1:] + name = name[0:offset] + + # First we must ask the index server about the image name. THe + # index server will return an auth token we can use when talking + # to the registry server. We need this token even when anonymous + try: + (data, res) = get_json(server, "/v1/repositories/" + name + "/images", + {"X-Docker-Token": "true"}) + except urllib2.HTTPError, e: + raise ValueError(["Image '%s' does not exist" % name]) + + registryserver = res.info().getheader('X-Docker-Endpoints') + token = res.info().getheader('X-Docker-Token') + checksums = {} + for layer in data: + pass + # XXX Checksums here don't appear to match the data in + # image download later. Find out what these are sums of + #checksums[layer["id"]] = layer["checksum"] + + # Now we ask the registry server for the list of tags associated + # with the image. Tags usually reflect some kind of version of + # the image, but they aren't officially "versions". There is + # always a "latest" tag which is the most recent upload + # + # We must pass in the auth token from the index server. This + # token can only be used once, and we're given a cookie back + # in return to use for later RPC calls. + (data, res) = get_json(registryserver, "/v1/repositories/" + name + "/tags", + { "Authorization": "Token " + token }) + + cookie = res.info().getheader('Set-Cookie') + + if not tag in data: + raise ValueError(["Tag '%s' does not exist for image '%s'" % (tag, name)]) + imagetagid = data[tag] + + # Only base images are self-contained, most images reference one + # or more parents, in a linear stack. Here we are getting the list + # of layers for the image with the tag we used. + (data, res) = get_json(registryserver, "/v1/images/" + imagetagid + "/ancestry", + { "Cookie": cookie }) + + if data[0] != imagetagid: + raise ValueError(["Expected first layer id '%s' to match image id '%s'", + data[0], imagetagid]) + + try: + createdFiles = [] + createdDirs = [] + + for layerid in data: + templatedir = destdir + "/" + layerid + if not os.path.exists(templatedir): + os.mkdir(templatedir) + createdDirs.append(templatedir) + + jsonfile = templatedir + "/template.json" + datafile = templatedir + "/template.tar.gz" + + if not os.path.exists(jsonfile) or not os.path.exists(datafile): + # The '/json' URL gives us some metadata about the layer + res = save_data(registryserver, "/v1/images/" + layerid + "/json", + { "Cookie": cookie }, jsonfile) + createdFiles.append(jsonfile) + layersize = int(res.info().getheader("x-docker-size")) + + datacsum = None + if layerid in checksums: + datacsum = checksums[layerid] + + # and the '/layer' URL is the actual payload, provided + # as a tar.gz archive + save_data(registryserver, "/v1/images/" + layerid + "/layer", + { "Cookie": cookie }, datafile, datacsum, layersize) + createdFiles.append(datafile) + + # Strangely the 'json' data for a layer doesn't include + # its actual name, so we save that in a json file of our own + index = { + "name": name, + } + + indexfile = destdir + "/" + imagetagid + "/index.json" + with open(indexfile, "w") as f: + f.write(json.dumps(index)) + except Exception, e: + for f in createdFiles: + try: + os.remove(f) + except: + pass + for d in createdDirs: + try: + os.rmdir(d) + except: + pass + + +def delete_template(name, destdir): + imageusage = {} + imageparent = {} + imagenames = {} + imagedirs = os.listdir(destdir) + for imagetagid in imagedirs: + indexfile = destdir + "/" + imagetagid + "/index.json" + if os.path.exists(indexfile): + with open(indexfile, "r") as f: + index = json.load(f) + imagenames[index["name"]] = imagetagid + jsonfile = destdir + "/" + imagetagid + "/template.json" + if os.path.exists(jsonfile): + with open(jsonfile, "r") as f: + template = json.load(f) + + parent = template.get("parent", None) + if parent: + if parent not in imageusage: + imageusage[parent] = [] + imageusage[parent].append(imagetagid) + imageparent[imagetagid] = parent + + if not name in imagenames: + raise ValueError(["Image %s does not exist locally" % name]) + + imagetagid = imagenames[name] + while imagetagid != None: + debug("Remove %s\n" % imagetagid) + parent = imageparent.get(imagetagid, None) + + indexfile = destdir + "/" + imagetagid + "/index.json" + if os.path.exists(indexfile): + os.remove(indexfile) + jsonfile = destdir + "/" + imagetagid + "/template.json" + if os.path.exists(jsonfile): + os.remove(jsonfile) + datafile = destdir + "/" + imagetagid + "/template.tar.gz" + if os.path.exists(datafile): + os.remove(datafile) + imagedir = destdir + "/" + imagetagid + os.rmdir(imagedir) + + if parent: + if len(imageusage[parent]) != 1: + debug("Parent %s is shared\n" % parent) + parent = None + imagetagid = parent + + +def get_image_list(name, destdir): + imageparent = {} + imagenames = {} + imagedirs = os.listdir(destdir) + for imagetagid in imagedirs: + indexfile = destdir + "/" + imagetagid + "/index.json" + if os.path.exists(indexfile): + with open(indexfile, "r") as f: + index = json.load(f) + imagenames[index["name"]] = imagetagid + jsonfile = destdir + "/" + imagetagid + "/template.json" + if os.path.exists(jsonfile): + with open(jsonfile, "r") as f: + template = json.load(f) + + parent = template.get("parent", None) + if parent: + imageparent[imagetagid] = parent + + if not name in imagenames: + raise ValueError(["Image %s does not exist locally" % name]) + + imagetagid = imagenames[name] + imagelist = [] + while imagetagid != None: + imagelist.append(imagetagid) + parent = imageparent.get(imagetagid, None) + imagetagid = parent + + return imagelist + +def create_template(name, imagepath, format, destdir): + if not format in ["qcow2"]: + raise ValueError(["Unsupported image format %s" % format]) + + imagelist = get_image_list(name, destdir) + imagelist.reverse() + + parentImage = None + for imagetagid in imagelist: + templateImage = destdir + "/" + imagetagid + "/template." + format + cmd = ["qemu-img", "create", "-f", "qcow2"] + if parentImage is not None: + cmd.append("-o") + cmd.append("backing_fmt=qcow2,backing_file=%s" % parentImage) + cmd.append(templateImage) + if parentImage is None: + cmd.append("10G") + debug("Run %s\n" % " ".join(cmd)) + subprocess.call(cmd) + parentImage = templateImage + +def download(args): + info("Downloading %s from %s to %s\n" % (args.name, default_index_server, default_template_dir)) + download_template(args.name, default_index_server, default_template_dir) + +def delete(args): + info("Deleting %s from %s\n" % (args.name, default_template_dir)) + delete_template(args.name, default_template_dir) + +def create(args): + info("Creating %s from %s in format %s\n" % (args.imagepath, args.name, args.format)) + create_template(args.name, args.imagepath, args.format, default_template_dir) + +def requires_name(parser): + parser.add_argument("name", + help=_("name of the template")) + +def gen_download_args(subparser): + parser = subparser.add_parser("download", + help=_("Download template data")) + requires_name(parser) + parser.set_defaults(func=download) + +def gen_delete_args(subparser): + parser = subparser.add_parser("delete", + help=_("Delete template data")) + requires_name(parser) + parser.set_defaults(func=delete) + +def gen_create_args(subparser): + parser = subparser.add_parser("create", + help=_("Create image from template data")) + requires_name(parser) + parser.add_argument("imagepath", + help=_("path for image")) + parser.add_argument("format", + help=_("format")) + parser.set_defaults(func=create) + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Sandbox Container Image Tool') + + subparser = parser.add_subparsers(help=_("commands")) + gen_download_args(subparser) + gen_delete_args(subparser) + gen_create_args(subparser) + + try: + args = parser.parse_args() + args.func(args) + sys.exit(0) + except KeyboardInterrupt, e: + sys.exit(0) + except ValueError, e: + for line in e: + for l in line: + sys.stderr.write("%s: %s\n" % (sys.argv[0], l)) + sys.stderr.flush() + sys.exit(1) + except IOError, e: + sys.stderr.write("%s: %s: %s\n" % (sys.argv[0], e.filename, e.reason)) + sys.stderr.flush() + sys.exit(1) + except OSError, e: + sys.stderr.write("%s: %s\n" % (sys.argv[0], e)) + sys.stderr.flush() + sys.exit(1) -- 2.1.0

Authentication fix for Docker REST API. --- virt-sandbox-image/virt-sandbox-image.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 4f5443b..a9cb0ff 100644 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -1,8 +1,10 @@ #!/usr/bin/python -Es # # Authors: Daniel P. Berrange <berrange@redhat.com> +# Eren Yagdiran <erenyagdiran@gmail.com> # # Copyright (C) 2013 Red Hat, Inc. +# Copyright (C) 2015 Universitat Polit��cnica de Catalunya. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -166,7 +168,7 @@ def download_template(name, server, destdir): # or more parents, in a linear stack. Here we are getting the list # of layers for the image with the tag we used. (data, res) = get_json(registryserver, "/v1/images/" + imagetagid + "/ancestry", - { "Cookie": cookie }) + { "Authorization": "Token " + token }) if data[0] != imagetagid: raise ValueError(["Expected first layer id '%s' to match image id '%s'", @@ -188,9 +190,9 @@ def download_template(name, server, destdir): if not os.path.exists(jsonfile) or not os.path.exists(datafile): # The '/json' URL gives us some metadata about the layer res = save_data(registryserver, "/v1/images/" + layerid + "/json", - { "Cookie": cookie }, jsonfile) + { "Authorization": "Token " + token }, jsonfile) createdFiles.append(jsonfile) - layersize = int(res.info().getheader("x-docker-size")) + layersize = int(res.info().getheader("Content-Length")) datacsum = None if layerid in checksums: @@ -199,7 +201,7 @@ def download_template(name, server, destdir): # and the '/layer' URL is the actual payload, provided # as a tar.gz archive save_data(registryserver, "/v1/images/" + layerid + "/layer", - { "Cookie": cookie }, datafile, datacsum, layersize) + { "Authorization": "Token " + token }, datafile, datacsum, layersize) createdFiles.append(datafile) # Strangely the 'json' data for a layer doesn't include -- 2.1.0

Any custom source provider can be added to virt-sandbox-image as a source --- .gitignore | 1 + bin/Makefile.am | 16 ++++++++++++---- bin/virt-sandbox-image.in | 3 +++ configure.ac | 2 ++ virt-sandbox-image/Makefile.am | 13 +++++++++++++ virt-sandbox-image/sources/Source.py | 31 +++++++++++++++++++++++++++++++ virt-sandbox-image/sources/__init__.py | 29 +++++++++++++++++++++++++++++ virt-sandbox-image/virt-sandbox-image.py | 13 ++++++++++++- 8 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 bin/virt-sandbox-image.in create mode 100644 virt-sandbox-image/Makefile.am create mode 100644 virt-sandbox-image/sources/Source.py create mode 100644 virt-sandbox-image/sources/__init__.py mode change 100644 => 100755 virt-sandbox-image/virt-sandbox-image.py diff --git a/.gitignore b/.gitignore index f77ea12..ef5b5aa 100644 --- a/.gitignore +++ b/.gitignore @@ -56,3 +56,4 @@ bin/virt-sandbox bin/virt-sandbox-service-util build/ bin/*.1 +bin/virt-sandbox-image diff --git a/bin/Makefile.am b/bin/Makefile.am index 416f86f..df4c7dc 100644 --- a/bin/Makefile.am +++ b/bin/Makefile.am @@ -3,7 +3,11 @@ bin_PROGRAMS = virt-sandbox libexec_PROGRAMS = virt-sandbox-service-util -bin_SCRIPTS = virt-sandbox-service +bin_SCRIPTS = virt-sandbox-service \ + virt-sandbox-image + +virt-sandbox-image: virt-sandbox-image.in + sed -e 's,[@]pkgpythondir[@],$(pkgpythondir),g' < $< >$@ virtsandboxcompdir = $(datarootdir)/bash-completion/completions/ @@ -20,8 +24,11 @@ POD_FILES = \ virt-sandbox-service-reload.pod \ virt-sandbox-service-upgrade.pod \ $(NULL) -EXTRA_DIST = $(bin_SCRIPTS) $(POD_FILES) virt-sandbox-service-bash-completion.sh virt-sandbox-service.logrotate -EXTRA_DIST += virt-sandbox-service-bash-completion.sh +EXTRA_DIST = virt-sandbox-service \ + virt-sandbox-image.in \ + $(POD_FILES) \ + virt-sandbox-service-bash-completion.sh \ + virt-sandbox-service.logrotate man1_MANS = \ virt-sandbox.1 \ @@ -64,7 +71,8 @@ virt-sandbox-service-reload.1: virt-sandbox-service-reload.pod Makefile virt-sandbox-service-upgrade.1: virt-sandbox-service-upgrade.pod Makefile $(AM_V_GEN)$(POD2MAN) $< $(srcdir)/$@ -CLEANFILES = $(man1_MANS) +CLEANFILES = $(man1_MANS) \ + virt-sandbox-image virt_sandbox_SOURCES = virt-sandbox.c virt_sandbox_CFLAGS = \ diff --git a/bin/virt-sandbox-image.in b/bin/virt-sandbox-image.in new file mode 100644 index 0000000..732bb38 --- /dev/null +++ b/bin/virt-sandbox-image.in @@ -0,0 +1,3 @@ +#!/bin/sh + +exec "@pkgpythondir@/virt-sandbox-image.py" "$@" diff --git a/configure.ac b/configure.ac index 8f6da04..69b5870 100644 --- a/configure.ac +++ b/configure.ac @@ -124,11 +124,13 @@ dnl Should be in m4/virt-gettext.m4 but intltoolize is too dnl dumb to find it there IT_PROG_INTLTOOL([0.35.0]) +AM_PATH_PYTHON AC_OUTPUT(Makefile libvirt-sandbox/Makefile libvirt-sandbox/tests/Makefile bin/Makefile + virt-sandbox-image/Makefile examples/Makefile docs/Makefile docs/libvirt-sandbox/Makefile diff --git a/virt-sandbox-image/Makefile.am b/virt-sandbox-image/Makefile.am new file mode 100644 index 0000000..5ab4d2e --- /dev/null +++ b/virt-sandbox-image/Makefile.am @@ -0,0 +1,13 @@ + +EXTRA_DIST = \ + virt-sandbox-image.py \ + sources + +install-data-local: + $(mkinstalldirs) $(DESTDIR)/$(pkgpythondir)/sources + $(INSTALL) -m 0755 $(srcdir)/virt-sandbox-image.py $(DESTDIR)$(pkgpythondir) + $(INSTALL) -m 0644 $(srcdir)/sources/__init__.py $(DESTDIR)$(pkgpythondir)/sources + $(INSTALL) -m 0644 $(srcdir)/sources/Source.py $(DESTDIR)$(pkgpythondir)/sources + +uninstall-local: + rm -f $(DESTDIR)$(pkgpythondir) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py new file mode 100644 index 0000000..43f0720 --- /dev/null +++ b/virt-sandbox-image/sources/Source.py @@ -0,0 +1,31 @@ +''' +* +* libvirt-sandbox-config-diskaccess.h: libvirt sandbox configuration +* +* Copyright (C) 2015 Universitat Polit��cnica de Catalunya. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Author: Eren Yagdiran <erenyagdiran@gmail.com> +* +''' +#!/usr/bin/python + +from abc import ABCMeta, abstractmethod + +class Source(): + __metaclass__ = ABCMeta + def __init__(self): + pass diff --git a/virt-sandbox-image/sources/__init__.py b/virt-sandbox-image/sources/__init__.py new file mode 100644 index 0000000..4830c7a --- /dev/null +++ b/virt-sandbox-image/sources/__init__.py @@ -0,0 +1,29 @@ +''' +* +* libvirt-sandbox-config-diskaccess.h: libvirt sandbox configuration +* +* Copyright (C) 2015 Universitat Polit��cnica de Catalunya. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Author: Eren Yagdiran <erenyagdiran@gmail.com> +* +''' +#!/usr/bin/python + +import os +import glob +modules = glob.glob(os.path.dirname(__file__)+"/*.py") +__all__ = [ os.path.basename(f)[:-3] for f in modules] diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py old mode 100644 new mode 100755 index a9cb0ff..b20b240 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -1,5 +1,5 @@ #!/usr/bin/python -Es -# +# -*- coding: utf-8 -*- # Authors: Daniel P. Berrange <berrange@redhat.com> # Eren Yagdiran <erenyagdiran@gmail.com> # @@ -38,6 +38,17 @@ default_template_dir = "/var/lib/libvirt/templates" debug = True verbose = True +sys.dont_write_bytecode = True + +import importlib +def dynamic_source_loader(name): + name = name[0].upper() + name[1:] + modname = "sources." + name + "Source" + mod = importlib.import_module(modname) + classname = name + "Source" + classimpl = getattr(mod,classname) + return classimpl() + gettext.bindtextdomain("libvirt-sandbox", "/usr/share/locale") gettext.textdomain("libvirt-sandbox") try: -- 2.1.0

On Tue, Aug 04, 2015 at 08:11:09PM +0000, Eren Yagdiran wrote:
Any custom source provider can be added to virt-sandbox-image as a source --- .gitignore | 1 + bin/Makefile.am | 16 ++++++++++++---- bin/virt-sandbox-image.in | 3 +++ configure.ac | 2 ++ virt-sandbox-image/Makefile.am | 13 +++++++++++++ virt-sandbox-image/sources/Source.py | 31 +++++++++++++++++++++++++++++++ virt-sandbox-image/sources/__init__.py | 29 +++++++++++++++++++++++++++++ virt-sandbox-image/virt-sandbox-image.py | 13 ++++++++++++- 8 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 bin/virt-sandbox-image.in create mode 100644 virt-sandbox-image/Makefile.am create mode 100644 virt-sandbox-image/sources/Source.py create mode 100644 virt-sandbox-image/sources/__init__.py mode change 100644 => 100755 virt-sandbox-image/virt-sandbox-image.py
diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py new file mode 100644 index 0000000..43f0720 --- /dev/null +++ b/virt-sandbox-image/sources/Source.py @@ -0,0 +1,31 @@ +''' +* +* libvirt-sandbox-config-diskaccess.h: libvirt sandbox configuration +* +* Copyright (C) 2015 Universitat Politècnica de Catalunya. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Author: Eren Yagdiran <erenyagdiran@gmail.com> +* +'''
Python comment syntax is '# ' What you have done here is declare a string. Also since the copyright line has accents in it, you need to add #coding=utf-8 as the 2nd line in the file, otherwise python refuses to load the code at all saying it doens't know the character encoding.
+#!/usr/bin/python
THis should be the first line
+ +from abc import ABCMeta, abstractmethod + +class Source(): + __metaclass__ = ABCMeta + def __init__(self): + pass diff --git a/virt-sandbox-image/sources/__init__.py b/virt-sandbox-image/sources/__init__.py new file mode 100644 index 0000000..4830c7a --- /dev/null +++ b/virt-sandbox-image/sources/__init__.py @@ -0,0 +1,29 @@ +''' +* +* libvirt-sandbox-config-diskaccess.h: libvirt sandbox configuration +* +* Copyright (C) 2015 Universitat Politècnica de Catalunya. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Author: Eren Yagdiran <erenyagdiran@gmail.com> +* +''' +#!/usr/bin/python
Same here about comment syntax, encoding and #!/usr being the first line.
+ +import os +import glob +modules = glob.glob(os.path.dirname(__file__)+"/*.py") +__all__ = [ os.path.basename(f)[:-3] for f in modules]
I don't think any of this stuff is needed, since we are directly importing the module we want based on its name
diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py old mode 100644 new mode 100755 index a9cb0ff..b20b240 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -1,5 +1,5 @@ #!/usr/bin/python -Es -# +# -*- coding: utf-8 -*- # Authors: Daniel P. Berrange <berrange@redhat.com> # Eren Yagdiran <erenyagdiran@gmail.com> # @@ -38,6 +38,17 @@ default_template_dir = "/var/lib/libvirt/templates" debug = True verbose = True
+sys.dont_write_bytecode = True
This kind of change doesn't belong in the code. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Refactor download function from virt-sandbox-image to use the newly introduced Source abstract class. The docker-specific download code is moved to a new DockerSource class. --- virt-sandbox-image/Makefile.am | 1 + virt-sandbox-image/sources/DockerSource.py | 227 +++++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 + virt-sandbox-image/virt-sandbox-image.py | 199 ++++--------------------- 4 files changed, 259 insertions(+), 172 deletions(-) create mode 100644 virt-sandbox-image/sources/DockerSource.py diff --git a/virt-sandbox-image/Makefile.am b/virt-sandbox-image/Makefile.am index 5ab4d2e..8188c80 100644 --- a/virt-sandbox-image/Makefile.am +++ b/virt-sandbox-image/Makefile.am @@ -8,6 +8,7 @@ install-data-local: $(INSTALL) -m 0755 $(srcdir)/virt-sandbox-image.py $(DESTDIR)$(pkgpythondir) $(INSTALL) -m 0644 $(srcdir)/sources/__init__.py $(DESTDIR)$(pkgpythondir)/sources $(INSTALL) -m 0644 $(srcdir)/sources/Source.py $(DESTDIR)$(pkgpythondir)/sources + $(INSTALL) -m 0644 $(srcdir)/sources/DockerSource.py $(DESTDIR)$(pkgpythondir)/sources uninstall-local: rm -f $(DESTDIR)$(pkgpythondir) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py new file mode 100644 index 0000000..cf81ffe --- /dev/null +++ b/virt-sandbox-image/sources/DockerSource.py @@ -0,0 +1,227 @@ +''' +* +* libvirt-sandbox-config-diskaccess.h: libvirt sandbox configuration +* +* Copyright (C) 2015 Universitat Polit��cnica de Catalunya. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Author: Eren Yagdiran <erenyagdiran@gmail.com> +* +''' +#!/usr/bin/python + +from Source import Source +import urllib2 +import sys +import json +import traceback +import os +import subprocess +import shutil + +class DockerSource(Source): + default_index_server = "index.docker.io" + default_template_dir = "/var/lib/libvirt/templates" + default_image_path = "/var/lib/libvirt/templates" + default_disk_format = "qcow2" + + www_auth_username = None + www_auth_password = None + + def __init__(self,server="index.docker.io",destdir="/var/lib/libvirt/templates"): + self.default_index_server = server + self.default_template_dir = destdir + + def _check_cert_validate(self): + major = sys.version_info.major + SSL_WARNING = "SSL certificates couldn't be validated by default. You need to have 2.7.9/3.4.3 or higher" + SSL_WARNING +="\nSee https://bugs.python.org/issue22417" + py2_7_9_hexversion = 34015728 + py3_4_3_hexversion = 50594800 + if (major == 2 and sys.hexversion < py2_7_9_hexversion) or (major == 3 and sys.hexversion < py3_4_3_hexversion): + print SSL_WARNING + + def _check_dir_writable(self,path): + if not os.access(path,os.W_OK): + raise ValueError(["%s is not writable for saving template data" %path]) + + def download_template(self,**args): + name = args['name'] + registry = args['registry'] if args['registry'] is not None else self.default_index_server + username = args['username'] + password = args['password'] + templatedir = args['templatedir'] if args['templatedir'] is not None else self.default_template_dir + self._download_template(name,registry,username,password,templatedir) + + def _download_template(self,name, server,username,password,destdir): + + if username is not None: + self.www_auth_username = username + self.www_auth_password = password + + self._check_cert_validate() + self._check_dir_writable(destdir) + tag = "latest" + offset = name.find(':') + if offset != -1: + tag = name[offset + 1:] + name = name[0:offset] + try: + (data, res) = self._get_json(server, "/v1/repositories/" + name + "/images", + {"X-Docker-Token": "true"}) + except urllib2.HTTPError, e: + raise ValueError(["Image '%s' does not exist" % name]) + + registryserver = res.info().getheader('X-Docker-Endpoints') + token = res.info().getheader('X-Docker-Token') + checksums = {} + for layer in data: + pass + (data, res) = self._get_json(registryserver, "/v1/repositories/" + name + "/tags", + { "Authorization": "Token " + token }) + + cookie = res.info().getheader('Set-Cookie') + + if not tag in data: + raise ValueError(["Tag '%s' does not exist for image '%s'" % (tag, name)]) + imagetagid = data[tag] + + (data, res) = self._get_json(registryserver, "/v1/images/" + imagetagid + "/ancestry", + { "Authorization": "Token "+token }) + + if data[0] != imagetagid: + raise ValueError(["Expected first layer id '%s' to match image id '%s'", + data[0], imagetagid]) + + try: + createdFiles = [] + createdDirs = [] + + for layerid in data: + templatedir = destdir + "/" + layerid + if not os.path.exists(templatedir): + os.mkdir(templatedir) + createdDirs.append(templatedir) + + jsonfile = templatedir + "/template.json" + datafile = templatedir + "/template.tar.gz" + + if not os.path.exists(jsonfile) or not os.path.exists(datafile): + res = self._save_data(registryserver, "/v1/images/" + layerid + "/json", + { "Authorization": "Token " + token }, jsonfile) + createdFiles.append(jsonfile) + + layersize = int(res.info().getheader("Content-Length")) + + datacsum = None + if layerid in checksums: + datacsum = checksums[layerid] + + self._save_data(registryserver, "/v1/images/" + layerid + "/layer", + { "Authorization": "Token "+token }, datafile, datacsum, layersize) + createdFiles.append(datafile) + + index = { + "name": name, + } + + indexfile = destdir + "/" + imagetagid + "/index.json" + print("Index file " + indexfile) + with open(indexfile, "w") as f: + f.write(json.dumps(index)) + except Exception as e: + traceback.print_exc() + for f in createdFiles: + try: + os.remove(f) + except: + pass + for d in createdDirs: + try: + shutil.rmtree(d) + except: + pass + def _save_data(self,server, path, headers, dest, checksum=None, datalen=None): + try: + res = self._get_url(server, path, headers) + + csum = None + if checksum is not None: + csum = hashlib.sha256() + + pattern = [".", "o", "O", "o"] + patternIndex = 0 + donelen = 0 + + with open(dest, "w") as f: + while 1: + buf = res.read(1024*64) + if not buf: + break + if csum is not None: + csum.update(buf) + f.write(buf) + + if datalen is not None: + donelen = donelen + len(buf) + debug("\x1b[s%s (%5d Kb of %5d Kb)\x1b8" % ( + pattern[patternIndex], (donelen/1024), (datalen/1024) + )) + patternIndex = (patternIndex + 1) % 4 + + debug("\x1b[K") + if csum is not None: + csumstr = "sha256:" + csum.hexdigest() + if csumstr != checksum: + debug("FAIL checksum '%s' does not match '%s'" % (csumstr, checksum)) + os.remove(dest) + raise IOError("Checksum '%s' for data does not match '%s'" % (csumstr, checksum)) + debug("OK\n") + return res + except Exception, e: + debug("FAIL %s\n" % str(e)) + raise + + def _get_url(self,server, path, headers): + url = "https://" + server + path + debug("Fetching %s..." % url) + + req = urllib2.Request(url=url) + if json: + req.add_header("Accept", "application/json") + for h in headers.keys(): + req.add_header(h, headers[h]) + + #www Auth header starts + if self.www_auth_username is not None: + base64string = base64.encodestring('%s:%s' % (self.www_auth_username, self.www_auth_password)).replace('\n', '') + req.add_header("Authorization", "Basic %s" % base64string) + #www Auth header finish + + return urllib2.urlopen(req) + + def _get_json(self,server, path, headers): + try: + res = self._get_url(server, path, headers) + data = json.loads(res.read()) + debug("OK\n") + return (data, res) + except Exception, e: + debug("FAIL %s\n" % str(e)) + raise + +def debug(msg): + sys.stderr.write(msg) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index 43f0720..08bf335 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -29,3 +29,7 @@ class Source(): __metaclass__ = ABCMeta def __init__(self): pass + + @abstractmethod + def download_template(self,**args): + pass diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index b20b240..2573c29 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -67,176 +67,6 @@ def debug(msg): def info(msg): sys.stdout.write(msg) -def get_url(server, path, headers): - url = "https://" + server + path - debug(" Fetching %s..." % url) - req = urllib2.Request(url=url) - - if json: - req.add_header("Accept", "application/json") - - for h in headers.keys(): - req.add_header(h, headers[h]) - - return urllib2.urlopen(req) - -def get_json(server, path, headers): - try: - res = get_url(server, path, headers) - data = json.loads(res.read()) - debug("OK\n") - return (data, res) - except Exception, e: - debug("FAIL %s\n" % str(e)) - raise - -def save_data(server, path, headers, dest, checksum=None, datalen=None): - try: - res = get_url(server, path, headers) - - csum = None - if checksum is not None: - csum = hashlib.sha256() - - pattern = [".", "o", "O", "o"] - patternIndex = 0 - donelen = 0 - - with open(dest, "w") as f: - while 1: - buf = res.read(1024*64) - if not buf: - break - if csum is not None: - csum.update(buf) - f.write(buf) - - if datalen is not None: - donelen = donelen + len(buf) - debug("\x1b[s%s (%5d Kb of %5d Kb)\x1b8" % ( - pattern[patternIndex], (donelen/1024), (datalen/1024) - )) - patternIndex = (patternIndex + 1) % 4 - - debug("\x1b[K") - if csum is not None: - csumstr = "sha256:" + csum.hexdigest() - if csumstr != checksum: - debug("FAIL checksum '%s' does not match '%s'" % (csumstr, checksum)) - os.remove(dest) - raise IOError("Checksum '%s' for data does not match '%s'" % (csumstr, checksum)) - debug("OK\n") - return res - except Exception, e: - debug("FAIL %s\n" % str(e)) - raise - - -def download_template(name, server, destdir): - tag = "latest" - - offset = name.find(':') - if offset != -1: - tag = name[offset + 1:] - name = name[0:offset] - - # First we must ask the index server about the image name. THe - # index server will return an auth token we can use when talking - # to the registry server. We need this token even when anonymous - try: - (data, res) = get_json(server, "/v1/repositories/" + name + "/images", - {"X-Docker-Token": "true"}) - except urllib2.HTTPError, e: - raise ValueError(["Image '%s' does not exist" % name]) - - registryserver = res.info().getheader('X-Docker-Endpoints') - token = res.info().getheader('X-Docker-Token') - checksums = {} - for layer in data: - pass - # XXX Checksums here don't appear to match the data in - # image download later. Find out what these are sums of - #checksums[layer["id"]] = layer["checksum"] - - # Now we ask the registry server for the list of tags associated - # with the image. Tags usually reflect some kind of version of - # the image, but they aren't officially "versions". There is - # always a "latest" tag which is the most recent upload - # - # We must pass in the auth token from the index server. This - # token can only be used once, and we're given a cookie back - # in return to use for later RPC calls. - (data, res) = get_json(registryserver, "/v1/repositories/" + name + "/tags", - { "Authorization": "Token " + token }) - - cookie = res.info().getheader('Set-Cookie') - - if not tag in data: - raise ValueError(["Tag '%s' does not exist for image '%s'" % (tag, name)]) - imagetagid = data[tag] - - # Only base images are self-contained, most images reference one - # or more parents, in a linear stack. Here we are getting the list - # of layers for the image with the tag we used. - (data, res) = get_json(registryserver, "/v1/images/" + imagetagid + "/ancestry", - { "Authorization": "Token " + token }) - - if data[0] != imagetagid: - raise ValueError(["Expected first layer id '%s' to match image id '%s'", - data[0], imagetagid]) - - try: - createdFiles = [] - createdDirs = [] - - for layerid in data: - templatedir = destdir + "/" + layerid - if not os.path.exists(templatedir): - os.mkdir(templatedir) - createdDirs.append(templatedir) - - jsonfile = templatedir + "/template.json" - datafile = templatedir + "/template.tar.gz" - - if not os.path.exists(jsonfile) or not os.path.exists(datafile): - # The '/json' URL gives us some metadata about the layer - res = save_data(registryserver, "/v1/images/" + layerid + "/json", - { "Authorization": "Token " + token }, jsonfile) - createdFiles.append(jsonfile) - layersize = int(res.info().getheader("Content-Length")) - - datacsum = None - if layerid in checksums: - datacsum = checksums[layerid] - - # and the '/layer' URL is the actual payload, provided - # as a tar.gz archive - save_data(registryserver, "/v1/images/" + layerid + "/layer", - { "Authorization": "Token " + token }, datafile, datacsum, layersize) - createdFiles.append(datafile) - - # Strangely the 'json' data for a layer doesn't include - # its actual name, so we save that in a json file of our own - index = { - "name": name, - } - - indexfile = destdir + "/" + imagetagid + "/index.json" - with open(indexfile, "w") as f: - f.write(json.dumps(index)) - except Exception, e: - for f in createdFiles: - try: - os.remove(f) - except: - pass - for d in createdDirs: - try: - os.rmdir(d) - except: - pass - - def delete_template(name, destdir): imageusage = {} imageparent = {} @@ -340,8 +170,16 @@ def create_template(name, imagepath, format, destdir): parentImage = templateImage def download(args): - info("Downloading %s from %s to %s\n" % (args.name, default_index_server, default_template_dir)) - download_template(args.name, default_index_server, default_template_dir) + try: + dynamic_source_loader(args.source).download_template(name=args.name, + registry=args.registry, + username=args.username, + password=args.password, + templatedir=args.template_dir) + except IOError,e: + print "Source %s cannot be found in given path" %args.source + except Exception,e: + print "Download Error %s" % str(e) def delete(args): info("Deleting %s from %s\n" % (args.name, default_template_dir)) @@ -355,10 +193,27 @@ def requires_name(parser): parser.add_argument("name", help=_("name of the template")) +def requires_source(parser): + parser.add_argument("-s","--source", + default="docker", + help=_("name of the template")) + +def requires_auth_conn(parser): + parser.add_argument("-r","--registry", + help=_("Url of the custom registry")) + parser.add_argument("-u","--username", + help=_("Username for the custom registry")) + parser.add_argument("-p","--password", + help=_("Password for the custom registry")) + parser.add_argument("-t","--template-dir", + help=_("Template directory for saving templates")) + def gen_download_args(subparser): parser = subparser.add_parser("download", help=_("Download template data")) + requires_source(parser) requires_name(parser) + requires_auth_conn(parser) parser.set_defaults(func=download) def gen_delete_args(subparser): -- 2.1.0

On Tue, Aug 04, 2015 at 08:11:10PM +0000, Eren Yagdiran wrote:
Refactor download function from virt-sandbox-image to use the newly introduced Source abstract class. The docker-specific download code is moved to a new DockerSource class. --- virt-sandbox-image/Makefile.am | 1 + virt-sandbox-image/sources/DockerSource.py | 227 +++++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 + virt-sandbox-image/virt-sandbox-image.py | 199 ++++--------------------- 4 files changed, 259 insertions(+), 172 deletions(-) create mode 100644 virt-sandbox-image/sources/DockerSource.py
diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py new file mode 100644 index 0000000..cf81ffe --- /dev/null +++ b/virt-sandbox-image/sources/DockerSource.py @@ -0,0 +1,227 @@ +''' +* +* libvirt-sandbox-config-diskaccess.h: libvirt sandbox configuration +* +* Copyright (C) 2015 Universitat Politècnica de Catalunya. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Author: Eren Yagdiran <erenyagdiran@gmail.com> +* +''' +#!/usr/bin/python
Same points here about comment syntax, etc
+ +from Source import Source +import urllib2 +import sys +import json +import traceback +import os +import subprocess +import shutil + +class DockerSource(Source): + default_index_server = "index.docker.io" + default_template_dir = "/var/lib/libvirt/templates" + default_image_path = "/var/lib/libvirt/templates" + default_disk_format = "qcow2" + + www_auth_username = None + www_auth_password = None + + def __init__(self,server="index.docker.io",destdir="/var/lib/libvirt/templates"): + self.default_index_server = server + self.default_template_dir = destdir + + def _check_cert_validate(self): + major = sys.version_info.major + SSL_WARNING = "SSL certificates couldn't be validated by default. You need to have 2.7.9/3.4.3 or higher" + SSL_WARNING +="\nSee https://bugs.python.org/issue22417" + py2_7_9_hexversion = 34015728 + py3_4_3_hexversion = 50594800 + if (major == 2 and sys.hexversion < py2_7_9_hexversion) or (major == 3 and sys.hexversion < py3_4_3_hexversion): + print SSL_WARNING
Should print this to stderr, rather than stdout. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Tue, Aug 04, 2015 at 08:11:10PM +0000, Eren Yagdiran wrote:
Refactor download function from virt-sandbox-image to use the newly introduced Source abstract class. The docker-specific download code is moved to a new DockerSource class. --- virt-sandbox-image/Makefile.am | 1 + virt-sandbox-image/sources/DockerSource.py | 227 +++++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 + virt-sandbox-image/virt-sandbox-image.py | 199 ++++--------------------- 4 files changed, 259 insertions(+), 172 deletions(-) create mode 100644 virt-sandbox-image/sources/DockerSource.py
diff --git a/virt-sandbox-image/Makefile.am b/virt-sandbox-image/Makefile.am index 5ab4d2e..8188c80 100644 --- a/virt-sandbox-image/Makefile.am +++ b/virt-sandbox-image/Makefile.am @@ -8,6 +8,7 @@ install-data-local: $(INSTALL) -m 0755 $(srcdir)/virt-sandbox-image.py $(DESTDIR)$(pkgpythondir) $(INSTALL) -m 0644 $(srcdir)/sources/__init__.py $(DESTDIR)$(pkgpythondir)/sources $(INSTALL) -m 0644 $(srcdir)/sources/Source.py $(DESTDIR)$(pkgpythondir)/sources + $(INSTALL) -m 0644 $(srcdir)/sources/DockerSource.py $(DESTDIR)$(pkgpythondir)/sources
uninstall-local: rm -f $(DESTDIR)$(pkgpythondir) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py new file mode 100644 index 0000000..cf81ffe --- /dev/null +++ b/virt-sandbox-image/sources/DockerSource.py @@ -0,0 +1,227 @@ +''' +* +* libvirt-sandbox-config-diskaccess.h: libvirt sandbox configuration +* +* Copyright (C) 2015 Universitat Politècnica de Catalunya. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Author: Eren Yagdiran <erenyagdiran@gmail.com> +* +''' +#!/usr/bin/python + +from Source import Source +import urllib2 +import sys +import json +import traceback +import os +import subprocess +import shutil + +class DockerSource(Source): + default_index_server = "index.docker.io" + default_template_dir = "/var/lib/libvirt/templates" + default_image_path = "/var/lib/libvirt/templates" + default_disk_format = "qcow2"
These are class level variables
+ def __init__(self,server="index.docker.io",destdir="/var/lib/libvirt/templates"): + self.default_index_server = server + self.default_template_dir = destdir
And these are object level variables with the same name. Generally object level variables should have a leading _ to indicate that they are private. I'd suggest we remove the default directories from this bit of code though.
def download(args): - info("Downloading %s from %s to %s\n" % (args.name, default_index_server, default_template_dir)) - download_template(args.name, default_index_server, default_template_dir) + try: + dynamic_source_loader(args.source).download_template(name=args.name, + registry=args.registry, + username=args.username, + password=args.password, + templatedir=args.template_dir) + except IOError,e: + print "Source %s cannot be found in given path" %args.source + except Exception,e: + print "Download Error %s" % str(e)
def delete(args): info("Deleting %s from %s\n" % (args.name, default_template_dir)) @@ -355,10 +193,27 @@ def requires_name(parser): parser.add_argument("name", help=_("name of the template"))
+def requires_source(parser): + parser.add_argument("-s","--source", + default="docker", + help=_("name of the template")) + +def requires_auth_conn(parser): + parser.add_argument("-r","--registry", + help=_("Url of the custom registry")) + parser.add_argument("-u","--username", + help=_("Username for the custom registry")) + parser.add_argument("-p","--password", + help=_("Password for the custom registry")) + parser.add_argument("-t","--template-dir", + help=_("Template directory for saving templates"))
And register a default here instead. Also, we will want to have a different default directory for people running as non-root, as /var/lib/libvirt/templates won't be accessible for people using qemu:///session
+ def gen_download_args(subparser): parser = subparser.add_parser("download", help=_("Download template data")) + requires_source(parser) requires_name(parser) + requires_auth_conn(parser) parser.set_defaults(func=download)
def gen_delete_args(subparser):
Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Mon, Aug 10, 2015 at 05:30:52PM +0100, Daniel P. Berrange wrote:
On Tue, Aug 04, 2015 at 08:11:10PM +0000, Eren Yagdiran wrote:
Refactor download function from virt-sandbox-image to use the newly introduced Source abstract class. The docker-specific download code is moved to a new DockerSource class. --- virt-sandbox-image/Makefile.am | 1 + virt-sandbox-image/sources/DockerSource.py | 227 +++++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 + virt-sandbox-image/virt-sandbox-image.py | 199 ++++--------------------- 4 files changed, 259 insertions(+), 172 deletions(-) create mode 100644 virt-sandbox-image/sources/DockerSource.py
diff --git a/virt-sandbox-image/Makefile.am b/virt-sandbox-image/Makefile.am index 5ab4d2e..8188c80 100644 --- a/virt-sandbox-image/Makefile.am +++ b/virt-sandbox-image/Makefile.am @@ -8,6 +8,7 @@ install-data-local: $(INSTALL) -m 0755 $(srcdir)/virt-sandbox-image.py $(DESTDIR)$(pkgpythondir) $(INSTALL) -m 0644 $(srcdir)/sources/__init__.py $(DESTDIR)$(pkgpythondir)/sources $(INSTALL) -m 0644 $(srcdir)/sources/Source.py $(DESTDIR)$(pkgpythondir)/sources + $(INSTALL) -m 0644 $(srcdir)/sources/DockerSource.py $(DESTDIR)$(pkgpythondir)/sources
uninstall-local: rm -f $(DESTDIR)$(pkgpythondir) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py new file mode 100644 index 0000000..cf81ffe --- /dev/null +++ b/virt-sandbox-image/sources/DockerSource.py @@ -0,0 +1,227 @@ +''' +* +* libvirt-sandbox-config-diskaccess.h: libvirt sandbox configuration +* +* Copyright (C) 2015 Universitat Politècnica de Catalunya. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; either +* version 2.1 of the License, or (at your option) any later version. +* +* This library 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 +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Author: Eren Yagdiran <erenyagdiran@gmail.com> +* +''' +#!/usr/bin/python + +from Source import Source +import urllib2 +import sys +import json +import traceback +import os +import subprocess +import shutil + +class DockerSource(Source): + default_index_server = "index.docker.io" + default_template_dir = "/var/lib/libvirt/templates" + default_image_path = "/var/lib/libvirt/templates" + default_disk_format = "qcow2"
These are class level variables
+ def __init__(self,server="index.docker.io",destdir="/var/lib/libvirt/templates"): + self.default_index_server = server + self.default_template_dir = destdir
And these are object level variables with the same name. Generally object level variables should have a leading _ to indicate that they are private.
I'd suggest we remove the default directories from this bit of code though.
def download(args): - info("Downloading %s from %s to %s\n" % (args.name, default_index_server, default_template_dir)) - download_template(args.name, default_index_server, default_template_dir) + try: + dynamic_source_loader(args.source).download_template(name=args.name, + registry=args.registry, + username=args.username, + password=args.password, + templatedir=args.template_dir) + except IOError,e: + print "Source %s cannot be found in given path" %args.source + except Exception,e: + print "Download Error %s" % str(e)
def delete(args): info("Deleting %s from %s\n" % (args.name, default_template_dir)) @@ -355,10 +193,27 @@ def requires_name(parser): parser.add_argument("name", help=_("name of the template"))
+def requires_source(parser): + parser.add_argument("-s","--source", + default="docker", + help=_("name of the template")) + +def requires_auth_conn(parser): + parser.add_argument("-r","--registry", + help=_("Url of the custom registry")) + parser.add_argument("-u","--username", + help=_("Username for the custom registry")) + parser.add_argument("-p","--password", + help=_("Password for the custom registry")) + parser.add_argument("-t","--template-dir", + help=_("Template directory for saving templates"))
And register a default here instead.
Also, we will want to have a different default directory for people running as non-root, as /var/lib/libvirt/templates won't be accessible for people using qemu:///session
We should use $HOME/.local/share/libvirt/templates for unprivileged users Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Move the docker-related code to the DockerSource and use the Source mechanism --- virt-sandbox-image/sources/DockerSource.py | 100 +++++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++ virt-sandbox-image/virt-sandbox-image.py | 70 ++++---------------- 3 files changed, 118 insertions(+), 56 deletions(-) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index cf81ffe..9cd0080 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -223,5 +223,105 @@ class DockerSource(Source): debug("FAIL %s\n" % str(e)) raise + def create_template(self,**args): + name = args['name'] + connect = args['connect'] + path = args['imagepath'] + path = path if path is not None else self.default_image_path + format = args['format'] + format = format if format is not None else self.default_disk_format + + self._create_template(name, + connect, + path, + format, + path) + + def _create_template(self,name,connect,image_path,format,destdir): + self._check_disk_format(format) + imagelist = self._get_image_list(name,destdir) + imagelist.reverse() + + parentImage = None + for imagetagid in imagelist: + templateImage = destdir + "/" + imagetagid + "/template." + format + cmd = ["qemu-img","create","-f","qcow2"] + if parentImage is not None: + cmd.append("-o") + cmd.append("backing_fmt=qcow2,backing_file=%s" % parentImage) + cmd.append(templateImage) + if parentImage is None: + cmd.append("10G") + subprocess.call(cmd) + + if parentImage is None: + self._format_disk(templateImage,format,connect) + + self._extract_tarballs(destdir + "/" + imagetagid + "/template.",format,connect) + parentImage = templateImage + + + def _check_disk_format(self,format): + supportedFormats = ['qcow2'] + if not format in supportedFormats: + raise ValueError(["Unsupported image format %s" % format]) + + def _get_image_list(self,name,destdir): + imageparent = {} + imagenames = {} + imagedirs = os.listdir(destdir) + for imagetagid in imagedirs: + indexfile = destdir + "/" + imagetagid + "/index.json" + if os.path.exists(indexfile): + with open(indexfile,"r") as f: + index = json.load(f) + imagenames[index["name"]] = imagetagid + jsonfile = destdir + "/" + imagetagid + "/template.json" + if os.path.exists(jsonfile): + with open(jsonfile,"r") as f: + template = json.load(f) + parent = template.get("parent",None) + if parent: + imageparent[imagetagid] = parent + if not name in imagenames: + raise ValueError(["Image %s does not exist locally" %name]) + imagetagid = imagenames[name] + imagelist = [] + while imagetagid != None: + imagelist.append(imagetagid) + parent = imageparent.get(imagetagid,None) + imagetagid = parent + return imagelist + + def _format_disk(self,disk,format,connect): + cmd = ['virt-sandbox'] + if connect is not None: + cmd.append("-c") + cmd.append(connect) + params = ['--disk=file:disk_image=%s,format=%s' %(disk,format), + '/sbin/mkfs.ext3', + '/dev/disk/by-tag/disk_image'] + cmd = cmd + params + subprocess.call(cmd) + + def _extract_tarballs(self,directory,format,connect): + tempdir = "/mnt" + tarfile = directory + "tar.gz" + diskfile = directory + "qcow2" + cmd = ['virt-sandbox'] + if connect is not None: + cmd.append("-c") + cmd.append(connect) + params = ['-m', + 'host-image:/mnt=%s,format=%s' %(diskfile,format), + '--', + '/bin/tar', + 'zxvf', + '%s' %tarfile, + '-C', + '/mnt'] + cmd = cmd + params + subprocess.call(cmd) + def debug(msg): sys.stderr.write(msg) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index 08bf335..1a63428 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -33,3 +33,7 @@ class Source(): @abstractmethod def download_template(self,**args): pass + + @abstractmethod + def create_template(self,**args): + pass diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 2573c29..6f65445 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -116,59 +116,6 @@ def delete_template(name, destdir): parent = None imagetagid = parent - -def get_image_list(name, destdir): - imageparent = {} - imagenames = {} - imagedirs = os.listdir(destdir) - for imagetagid in imagedirs: - indexfile = destdir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile, "r") as f: - index = json.load(f) - imagenames[index["name"]] = imagetagid - jsonfile = destdir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - with open(jsonfile, "r") as f: - template = json.load(f) - - parent = template.get("parent", None) - if parent: - imageparent[imagetagid] = parent - - if not name in imagenames: - raise ValueError(["Image %s does not exist locally" % name]) - - imagetagid = imagenames[name] - imagelist = [] - while imagetagid != None: - imagelist.append(imagetagid) - parent = imageparent.get(imagetagid, None) - imagetagid = parent - - return imagelist - -def create_template(name, imagepath, format, destdir): - if not format in ["qcow2"]: - raise ValueError(["Unsupported image format %s" % format]) - - imagelist = get_image_list(name, destdir) - imagelist.reverse() - - parentImage = None - for imagetagid in imagelist: - templateImage = destdir + "/" + imagetagid + "/template." + format - cmd = ["qemu-img", "create", "-f", "qcow2"] - if parentImage is not None: - cmd.append("-o") - cmd.append("backing_fmt=qcow2,backing_file=%s" % parentImage) - cmd.append(templateImage) - if parentImage is None: - cmd.append("10G") - debug("Run %s\n" % " ".join(cmd)) - subprocess.call(cmd) - parentImage = templateImage - def download(args): try: dynamic_source_loader(args.source).download_template(name=args.name, @@ -186,8 +133,13 @@ def delete(args): delete_template(args.name, default_template_dir) def create(args): - info("Creating %s from %s in format %s\n" % (args.imagepath, args.name, args.format)) - create_template(args.name, args.imagepath, args.format, default_template_dir) + try: + dynamic_source_loader(args.source).create_template(name=args.name, + connect=args.connect, + imagepath=args.imagepath, + format=args.format) + except Exception,e: + print "Create Error %s" % str(e) def requires_name(parser): parser.add_argument("name", @@ -198,6 +150,10 @@ def requires_source(parser): default="docker", help=_("name of the template")) +def requires_connect(parser): + parser.add_argument("-c","--connect", + help=_("Connect string for libvirt")) + def requires_auth_conn(parser): parser.add_argument("-r","--registry", help=_("Url of the custom registry")) @@ -226,10 +182,12 @@ def gen_create_args(subparser): parser = subparser.add_parser("create", help=_("Create image from template data")) requires_name(parser) + requires_source(parser) + requires_connect(parser) parser.add_argument("imagepath", help=_("path for image")) parser.add_argument("format", - help=_("format")) + help=_("format format for image")) parser.set_defaults(func=create) if __name__ == '__main__': -- 2.1.0

On Tue, Aug 04, 2015 at 08:11:11PM +0000, Eren Yagdiran wrote:
Move the docker-related code to the DockerSource and use the Source mechanism --- virt-sandbox-image/sources/DockerSource.py | 100 +++++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++ virt-sandbox-image/virt-sandbox-image.py | 70 ++++---------------- 3 files changed, 118 insertions(+), 56 deletions(-)
diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index cf81ffe..9cd0080 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -223,5 +223,105 @@ class DockerSource(Source):
+ def _format_disk(self,disk,format,connect): + cmd = ['virt-sandbox'] + if connect is not None: + cmd.append("-c") + cmd.append(connect) + params = ['--disk=file:disk_image=%s,format=%s' %(disk,format), + '/sbin/mkfs.ext3', + '/dev/disk/by-tag/disk_image'] + cmd = cmd + params + subprocess.call(cmd)
We need to include the '-p' argument, because if running with qemu:///session you'll be unprivileged by default, so need -p to become root in the sandbox.
+ def _extract_tarballs(self,directory,format,connect): + tempdir = "/mnt" + tarfile = directory + "tar.gz" + diskfile = directory + "qcow2" + cmd = ['virt-sandbox'] + if connect is not None: + cmd.append("-c") + cmd.append(connect) + params = ['-m', + 'host-image:/mnt=%s,format=%s' %(diskfile,format), + '--', + '/bin/tar', + 'zxvf',
Could probably leave out the 'v' flag, since I don't think we need to display a list of every file. Or make it conditional on some --debug flag to virt-sandbox-image.
+ '%s' %tarfile, + '-C', + '/mnt'] + cmd = cmd + params + subprocess.call(cmd)
Same here about -p flag needed Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Tue, Aug 04, 2015 at 08:11:11PM +0000, Eren Yagdiran wrote:
Move the docker-related code to the DockerSource and use the Source mechanism --- virt-sandbox-image/sources/DockerSource.py | 100 +++++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++ virt-sandbox-image/virt-sandbox-image.py | 70 ++++---------------- 3 files changed, 118 insertions(+), 56 deletions(-)
diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 2573c29..6f65445 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -116,59 +116,6 @@ def delete_template(name, destdir): parent = None imagetagid = parent
- -def get_image_list(name, destdir): - imageparent = {} - imagenames = {} - imagedirs = os.listdir(destdir) - for imagetagid in imagedirs: - indexfile = destdir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile, "r") as f: - index = json.load(f) - imagenames[index["name"]] = imagetagid - jsonfile = destdir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - with open(jsonfile, "r") as f: - template = json.load(f) - - parent = template.get("parent", None) - if parent: - imageparent[imagetagid] = parent - - if not name in imagenames: - raise ValueError(["Image %s does not exist locally" % name]) - - imagetagid = imagenames[name] - imagelist = [] - while imagetagid != None: - imagelist.append(imagetagid) - parent = imageparent.get(imagetagid, None) - imagetagid = parent - - return imagelist - -def create_template(name, imagepath, format, destdir): - if not format in ["qcow2"]: - raise ValueError(["Unsupported image format %s" % format]) - - imagelist = get_image_list(name, destdir) - imagelist.reverse() - - parentImage = None - for imagetagid in imagelist: - templateImage = destdir + "/" + imagetagid + "/template." + format - cmd = ["qemu-img", "create", "-f", "qcow2"] - if parentImage is not None: - cmd.append("-o") - cmd.append("backing_fmt=qcow2,backing_file=%s" % parentImage) - cmd.append(templateImage) - if parentImage is None: - cmd.append("10G") - debug("Run %s\n" % " ".join(cmd)) - subprocess.call(cmd) - parentImage = templateImage - def download(args): try: dynamic_source_loader(args.source).download_template(name=args.name, @@ -186,8 +133,13 @@ def delete(args): delete_template(args.name, default_template_dir)
def create(args): - info("Creating %s from %s in format %s\n" % (args.imagepath, args.name, args.format)) - create_template(args.name, args.imagepath, args.format, default_template_dir) + try: + dynamic_source_loader(args.source).create_template(name=args.name, + connect=args.connect, + imagepath=args.imagepath, + format=args.format) + except Exception,e: + print "Create Error %s" % str(e)
def requires_name(parser): parser.add_argument("name", @@ -198,6 +150,10 @@ def requires_source(parser): default="docker", help=_("name of the template"))
+def requires_connect(parser): + parser.add_argument("-c","--connect", + help=_("Connect string for libvirt")) + def requires_auth_conn(parser): parser.add_argument("-r","--registry", help=_("Url of the custom registry")) @@ -226,10 +182,12 @@ def gen_create_args(subparser): parser = subparser.add_parser("create", help=_("Create image from template data")) requires_name(parser) + requires_source(parser) + requires_connect(parser) parser.add_argument("imagepath", help=_("path for image")) parser.add_argument("format", - help=_("format")) + help=_("format format for image"))
For the previous 'download' command we called the directory 'templatedir' but for 'create' we are calling it 'imagepath'. We should be consistent about what we do and so call it 'templatedir' for all commands, and also make sure it uses '--templatedir' and not a positional argument. The same applies to the 'run' command. Also we should really use '--format=qcow2' too, so people don't have to pass it by default Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Refactoring delete function from virt-sandbox-image to DockerSource. Delete function can delete templates by name. --- virt-sandbox-image/sources/DockerSource.py | 53 +++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++ virt-sandbox-image/virt-sandbox-image.py | 59 ++++-------------------------- 3 files changed, 65 insertions(+), 51 deletions(-) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 9cd0080..03f50aa 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -323,5 +323,58 @@ class DockerSource(Source): cmd = cmd + params subprocess.call(cmd) + def delete_template(self,**args): + imageusage = {} + imageparent = {} + imagenames = {} + name = args['name'] + destdir = args['imagepath'] + destdir = destdir if destdir is not None else default_template_dir + imagedirs = os.listdir(destdir) + for imagetagid in imagedirs: + indexfile = destdir + "/" + imagetagid + "/index.json" + if os.path.exists(indexfile): + with open(indexfile,"r") as f: + index = json.load(f) + imagenames[index["name"]] = imagetagid + jsonfile = destdir + "/" + imagetagid + "/template.json" + if os.path.exists(jsonfile): + with open(jsonfile,"r") as f: + template = json.load(f) + + parent = template.get("parent",None) + if parent: + if parent not in imageusage: + imageusage[parent] = [] + imageusage[parent].append(imagetagid) + imageparent[imagetagid] = parent + + + if not name in imagenames: + raise ValueError(["Image %s does not exist locally" %name]) + + imagetagid = imagenames[name] + while imagetagid != None: + debug("Remove %s\n" % imagetagid) + parent = imageparent.get(imagetagid,None) + + indexfile = destdir + "/" + imagetagid + "/index.json" + if os.path.exists(indexfile): + os.remove(indexfile) + jsonfile = destdir + "/" + imagetagid + "/template.json" + if os.path.exists(jsonfile): + os.remove(jsonfile) + datafile = destdir + "/" + imagetagid + "/template.tar.gz" + if os.path.exists(datafile): + os.remove(datafile) + imagedir = destdir + "/" + imagetagid + shutil.rmtree(imagedir) + + if parent: + if len(imageusage[parent]) != 1: + debug("Parent %s is shared\n" % parent) + parent = None + imagetagid = parent + def debug(msg): sys.stderr.write(msg) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index 1a63428..ceda58f 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -37,3 +37,7 @@ class Source(): @abstractmethod def create_template(self,**args): pass + + @abstractmethod + def delete_template(self,**args): + pass diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 6f65445..ea7ab02 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -67,55 +67,6 @@ def debug(msg): def info(msg): sys.stdout.write(msg) -def delete_template(name, destdir): - imageusage = {} - imageparent = {} - imagenames = {} - imagedirs = os.listdir(destdir) - for imagetagid in imagedirs: - indexfile = destdir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile, "r") as f: - index = json.load(f) - imagenames[index["name"]] = imagetagid - jsonfile = destdir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - with open(jsonfile, "r") as f: - template = json.load(f) - - parent = template.get("parent", None) - if parent: - if parent not in imageusage: - imageusage[parent] = [] - imageusage[parent].append(imagetagid) - imageparent[imagetagid] = parent - - if not name in imagenames: - raise ValueError(["Image %s does not exist locally" % name]) - - imagetagid = imagenames[name] - while imagetagid != None: - debug("Remove %s\n" % imagetagid) - parent = imageparent.get(imagetagid, None) - - indexfile = destdir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - os.remove(indexfile) - jsonfile = destdir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - os.remove(jsonfile) - datafile = destdir + "/" + imagetagid + "/template.tar.gz" - if os.path.exists(datafile): - os.remove(datafile) - imagedir = destdir + "/" + imagetagid - os.rmdir(imagedir) - - if parent: - if len(imageusage[parent]) != 1: - debug("Parent %s is shared\n" % parent) - parent = None - imagetagid = parent - def download(args): try: dynamic_source_loader(args.source).download_template(name=args.name, @@ -129,8 +80,11 @@ def download(args): print "Download Error %s" % str(e) def delete(args): - info("Deleting %s from %s\n" % (args.name, default_template_dir)) - delete_template(args.name, default_template_dir) + try: + dynamic_source_loader(args.source).delete_template(name=args.name, + imagepath=args.imagepath) + except Exception,e: + print "Delete Error %s", str(e) def create(args): try: @@ -176,6 +130,9 @@ def gen_delete_args(subparser): parser = subparser.add_parser("delete", help=_("Delete template data")) requires_name(parser) + requires_source(parser) + parser.add_argument("imagepath", + help=_("Path for image")) parser.set_defaults(func=delete) def gen_create_args(subparser): -- 2.1.0

Provide a way to know how a template can be started depending on the used source DockerSource will need to parse the topmost config file in order to find the igniter command --- virt-sandbox-image/sources/DockerSource.py | 14 ++++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 03f50aa..2f4646f 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -32,6 +32,15 @@ import os import subprocess import shutil +class DockerConfParser(): + + def __init__(self,jsonfile): + with open(jsonfile) as json_file: + self.json_data = json.load(json_file) + def getRunCommand(self): + cmd = self.json_data['container_config']['Cmd'][2] + return cmd[cmd.index('"') + 1:cmd.rindex('"')] + class DockerSource(Source): default_index_server = "index.docker.io" default_template_dir = "/var/lib/libvirt/templates" @@ -376,5 +385,10 @@ class DockerSource(Source): parent = None imagetagid = parent + def get_command(self,configfile): + configParser = DockerConfParser(configfile) + commandToRun = configParser.getRunCommand() + return commandToRun + def debug(msg): sys.stderr.write(msg) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index ceda58f..01f8560 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -41,3 +41,7 @@ class Source(): @abstractmethod def delete_template(self,**args): pass + + @abstractmethod + def get_command(self,**args): + pass -- 2.1.0

Commandline parameters for running a template --- virt-sandbox-image/virt-sandbox-image.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index ea7ab02..feee849 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -147,6 +147,18 @@ def gen_create_args(subparser): help=_("format format for image")) parser.set_defaults(func=create) +def gen_run_args(subparser): + parser = subparser.add_parser("run", + help=_("Run a already built image")) + requires_name(parser) + requires_source(parser) + requires_connect(parser) + parser.add_argument("imagepath", + help=_("path for image")) + parser.add_argument("-i","--igniter", + help=_("Igniter command for image")) + parser.set_defaults(func=run) + if __name__ == '__main__': parser = argparse.ArgumentParser(description='Sandbox Container Image Tool') @@ -154,6 +166,7 @@ if __name__ == '__main__': gen_download_args(subparser) gen_delete_args(subparser) gen_create_args(subparser) + gen_run_args(subparser) try: args = parser.parse_args() -- 2.1.0

Check if user-specified connect argument is valid --- virt-sandbox-image/virt-sandbox-image.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index feee849..4c19fa8 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -95,6 +95,12 @@ def create(args): except Exception,e: print "Create Error %s" % str(e) +def check_connect(connectstr): + supportedDrivers = ['lxc:///','qemu:///session','qemu:///system'] + if not connectstr in supportedDrivers: + raise ValueError("%s is not supported by Virt-sandbox" %connectstr) + return True + def requires_name(parser): parser.add_argument("name", help=_("name of the template")) -- 2.1.0

Provide a way to know which disk image to use for the sandbox depending on the used source DockerSource will need to locate the topmost disk image among all the layers images --- virt-sandbox-image/sources/DockerSource.py | 9 +++++++++ virt-sandbox-image/sources/Source.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 2f4646f..74feb3e 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -385,6 +385,15 @@ class DockerSource(Source): parent = None imagetagid = parent + def get_disk(self,**args): + name = args['name'] + destdir = args['path'] + imageList = self._get_image_list(name,destdir) + toplayer = imageList[0] + diskfile = destdir + "/" + toplayer + "/template.qcow2" + configfile = destdir + "/" + toplayer + "/template.json" + return (diskfile,configfile) + def get_command(self,configfile): configParser = DockerConfParser(configfile) commandToRun = configParser.getRunCommand() diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index 01f8560..6e2f5fb 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -45,3 +45,7 @@ class Source(): @abstractmethod def get_command(self,**args): pass + + @abstractmethod + def get_disk(self,**args): + pass -- 2.1.0

Run an already-built template If there is no execution command specified by user, source.get_command will find the command to invoke --- virt-sandbox-image/virt-sandbox-image.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 4c19fa8..e20ce22 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -101,6 +101,30 @@ def check_connect(connectstr): raise ValueError("%s is not supported by Virt-sandbox" %connectstr) return True +def run(args): + try: + if args.connect is not None: + check_connect(args.connect) + source = dynamic_source_loader(args.source) + diskfile,configfile = source.get_disk(name=args.name,path=args.imagepath) + + format = "qcow2" + commandToRun = args.igniter + if commandToRun is None: + commandToRun = source.get_command(configfile) + cmd = ['virt-sandbox'] + if args.connect is not None: + cmd.append("-c") + cmd.append(args.connect) + params = ['-m','host-image:/=%s,format=%s' %(diskfile,format), + '--', + commandToRun] + cmd = cmd + params + subprocess.call(cmd) + + except Exception,e: + print "Run Error %s" % str(e) + def requires_name(parser): parser.add_argument("name", help=_("name of the template")) -- 2.1.0

On Tue, Aug 04, 2015 at 08:11:17PM +0000, Eren Yagdiran wrote:
Run an already-built template If there is no execution command specified by user, source.get_command will find the command to invoke --- virt-sandbox-image/virt-sandbox-image.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 4c19fa8..e20ce22 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -101,6 +101,30 @@ def check_connect(connectstr): raise ValueError("%s is not supported by Virt-sandbox" %connectstr) return True
+def run(args): + try: + if args.connect is not None: + check_connect(args.connect) + source = dynamic_source_loader(args.source) + diskfile,configfile = source.get_disk(name=args.name,path=args.imagepath) + + format = "qcow2" + commandToRun = args.igniter + if commandToRun is None: + commandToRun = source.get_command(configfile) + cmd = ['virt-sandbox'] + if args.connect is not None: + cmd.append("-c") + cmd.append(args.connect) + params = ['-m','host-image:/=%s,format=%s' %(diskfile,format), + '--', + commandToRun] + cmd = cmd + params + subprocess.call(cmd) + + except Exception,e: + print "Run Error %s" % str(e)
This code actually ends up launching a sandbox using the template file as the root disk image. This is not good, because we need to be able to run multiple instances of the sandbox, all using the same template file. As such we need to be able to create a new temporary disk image for each sandbox instance, that is an overlay on the main template, an then delete this temporary disk at shutdown. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Mon, Aug 10, 2015 at 05:16:19PM +0100, Daniel P. Berrange wrote:
On Tue, Aug 04, 2015 at 08:11:17PM +0000, Eren Yagdiran wrote:
Run an already-built template If there is no execution command specified by user, source.get_command will find the command to invoke --- virt-sandbox-image/virt-sandbox-image.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 4c19fa8..e20ce22 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -101,6 +101,30 @@ def check_connect(connectstr): raise ValueError("%s is not supported by Virt-sandbox" %connectstr) return True
+def run(args): + try: + if args.connect is not None: + check_connect(args.connect) + source = dynamic_source_loader(args.source) + diskfile,configfile = source.get_disk(name=args.name,path=args.imagepath) + + format = "qcow2" + commandToRun = args.igniter + if commandToRun is None: + commandToRun = source.get_command(configfile) + cmd = ['virt-sandbox'] + if args.connect is not None: + cmd.append("-c") + cmd.append(args.connect) + params = ['-m','host-image:/=%s,format=%s' %(diskfile,format), + '--', + commandToRun] + cmd = cmd + params + subprocess.call(cmd) + + except Exception,e: + print "Run Error %s" % str(e)
This code actually ends up launching a sandbox using the template file as the root disk image. This is not good, because we need to be able to run multiple instances of the sandbox, all using the same template file. As such we need to be able to create a new temporary disk image for each sandbox instance, that is an overlay on the main template, an then delete this temporary disk at shutdown.
Or if we make sure / is read-only that would avoid the problem I mention here. With normal docker toolchain is / read-only by default, or do they make it writable with a throw-away snapshot ? Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Mon, 2015-08-10 at 17:19 +0100, Daniel P. Berrange wrote:
On Mon, Aug 10, 2015 at 05:16:19PM +0100, Daniel P. Berrange wrote:
On Tue, Aug 04, 2015 at 08:11:17PM +0000, Eren Yagdiran wrote:
Run an already-built template If there is no execution command specified by user, source.get_command will find the command to invoke --- virt-sandbox-image/virt-sandbox-image.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 4c19fa8..e20ce22 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -101,6 +101,30 @@ def check_connect(connectstr): raise ValueError("%s is not supported by Virt-sandbox" %connectstr) return True
+def run(args): + try: + if args.connect is not None: + check_connect(args.connect) + source = dynamic_source_loader(args.source) + diskfile,configfile = source.get_disk(name=args.name,path=args.imagepath) + + format = "qcow2" + commandToRun = args.igniter + if commandToRun is None: + commandToRun = source.get_command(configfile) + cmd = ['virt-sandbox'] + if args.connect is not None: + cmd.append("-c") + cmd.append(args.connect) + params = ['-m','host-image:/=%s,format=%s' %(diskfile,format), + '--', + commandToRun] + cmd = cmd + params + subprocess.call(cmd) + + except Exception,e: + print "Run Error %s" % str(e)
This code actually ends up launching a sandbox using the template file as the root disk image. This is not good, because we need to be able to run multiple instances of the sandbox, all using the same template file. As such we need to be able to create a new temporary disk image for each sandbox instance, that is an overlay on the main template, an then delete this temporary disk at shutdown.
Or if we make sure / is read-only that would avoid the problem I mention here. With normal docker toolchain is / read-only by default, or do they make it writable with a throw-away snapshot ?
They have a layer for the container instance, but it isn't thrown-away when the container stops as users can commit the changes in that container's layer to the image template. I think we need to go the separate temporary layer as you mentioned, and have either an automatically computed container name or one defined by the user: with that we should have clean separation for all instances. -- Cedric

On Mon, Aug 17, 2015 at 11:28:37AM +0200, Cedric Bosdonnat wrote:
On Mon, 2015-08-10 at 17:19 +0100, Daniel P. Berrange wrote:
On Mon, Aug 10, 2015 at 05:16:19PM +0100, Daniel P. Berrange wrote:
On Tue, Aug 04, 2015 at 08:11:17PM +0000, Eren Yagdiran wrote:
Run an already-built template If there is no execution command specified by user, source.get_command will find the command to invoke --- virt-sandbox-image/virt-sandbox-image.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 4c19fa8..e20ce22 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -101,6 +101,30 @@ def check_connect(connectstr): raise ValueError("%s is not supported by Virt-sandbox" %connectstr) return True
+def run(args): + try: + if args.connect is not None: + check_connect(args.connect) + source = dynamic_source_loader(args.source) + diskfile,configfile = source.get_disk(name=args.name,path=args.imagepath) + + format = "qcow2" + commandToRun = args.igniter + if commandToRun is None: + commandToRun = source.get_command(configfile) + cmd = ['virt-sandbox'] + if args.connect is not None: + cmd.append("-c") + cmd.append(args.connect) + params = ['-m','host-image:/=%s,format=%s' %(diskfile,format), + '--', + commandToRun] + cmd = cmd + params + subprocess.call(cmd) + + except Exception,e: + print "Run Error %s" % str(e)
This code actually ends up launching a sandbox using the template file as the root disk image. This is not good, because we need to be able to run multiple instances of the sandbox, all using the same template file. As such we need to be able to create a new temporary disk image for each sandbox instance, that is an overlay on the main template, an then delete this temporary disk at shutdown.
Or if we make sure / is read-only that would avoid the problem I mention here. With normal docker toolchain is / read-only by default, or do they make it writable with a throw-away snapshot ?
They have a layer for the container instance, but it isn't thrown-away when the container stops as users can commit the changes in that container's layer to the image template. I think we need to go the separate temporary layer as you mentioned, and have either an automatically computed container name or one defined by the user: with that we should have clean separation for all instances.
Thanks for investigating that - I agree we should use a layer per container and support the --name argument, or auto-generate a name if it is not provided Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Virt-sandbox-image will pass exact network arguments to virt-sandbox --- virt-sandbox-image/virt-sandbox-image.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index e20ce22..5fc7f44 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -116,9 +116,13 @@ def run(args): if args.connect is not None: cmd.append("-c") cmd.append(args.connect) - params = ['-m','host-image:/=%s,format=%s' %(diskfile,format), - '--', - commandToRun] + params = ['-m','host-image:/=%s,format=%s' %(diskfile,format)] + networkArgs = args.network + if networkArgs is not None: + params.append('-N') + params.append(networkArgs) + params.append('--') + params.append(commandToRun) cmd = cmd + params subprocess.call(cmd) @@ -187,6 +191,8 @@ def gen_run_args(subparser): help=_("path for image")) parser.add_argument("-i","--igniter", help=_("Igniter command for image")) + parser.add_argument("-n","--network", + help=_("Network params for running template")) parser.set_defaults(func=run) if __name__ == '__main__': -- 2.1.0

Volumes let user to map host-paths into guest. Docker containers need volumes because its filesystem read-only by default. --- virt-sandbox-image/sources/DockerSource.py | 12 ++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++++ virt-sandbox-image/virt-sandbox-image.py | 22 ++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 74feb3e..44bc238 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -31,6 +31,7 @@ import traceback import os import subprocess import shutil +import collections class DockerConfParser(): @@ -40,6 +41,13 @@ class DockerConfParser(): def getRunCommand(self): cmd = self.json_data['container_config']['Cmd'][2] return cmd[cmd.index('"') + 1:cmd.rindex('"')] + def getVolumes(self): + volumes = self.json_data['container_config']['Volumes'] + volumelist = [] + if isinstance(volumes,collections.Iterable): + for key,value in volumes.iteritems(): + volumelist.append(key) + return volumelist class DockerSource(Source): default_index_server = "index.docker.io" @@ -399,5 +407,9 @@ class DockerSource(Source): commandToRun = configParser.getRunCommand() return commandToRun + def get_volume(self,configfile): + configParser = DockerConfParser(configfile) + return configParser.getVolumes() + def debug(msg): sys.stderr.write(msg) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index 6e2f5fb..6898c15 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -49,3 +49,7 @@ class Source(): @abstractmethod def get_disk(self,**args): pass + + @abstractmethod + def get_volume(self,**args): + pass diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 5fc7f44..b12b99b 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -103,6 +103,7 @@ def check_connect(connectstr): def run(args): try: + default_dir = "/var/lib/libvirt/storage" if args.connect is not None: check_connect(args.connect) source = dynamic_source_loader(args.source) @@ -121,6 +122,25 @@ def run(args): if networkArgs is not None: params.append('-N') params.append(networkArgs) + allVolumes = source.get_volume(configfile) + volumeArgs = args.volume + if volumeArgs is not None: + allVolumes = allVolumes + volumeArgs + for volume in allVolumes: + volumeSplit = volume.split(":") + volumelen = len(volumeSplit) + if volumelen == 2: + hostPath = volumeSplit[0] + guestPath = volumeSplit[1] + elif volumelen == 1: + guestPath = volumeSplit[0] + hostPath = default_dir + guestPath + if not os.path.exists(hostPath): + os.makedirs(hostPath) + else: + pass + params.append("--mount") + params.append("host-bind:%s=%s" %(guestPath,hostPath)) params.append('--') params.append(commandToRun) cmd = cmd + params @@ -193,6 +213,8 @@ def gen_run_args(subparser): help=_("Igniter command for image")) parser.add_argument("-n","--network", help=_("Network params for running template")) + parser.add_argument("-v","--volume",action="append", + help=_("Volume params for running template")) parser.set_defaults(func=run) if __name__ == '__main__': -- 2.1.0

On Tue, Aug 04, 2015 at 08:11:19PM +0000, Eren Yagdiran wrote:
Volumes let user to map host-paths into guest. Docker containers need volumes because its filesystem read-only by default.
Hmm, it seems our filesystem is not read-only. Normally we would have / as rread-only, but because we remappe it using -m host-image:/=/path/to/template we end up with / being read-write. We should add option to --mount to request a read-only mount, so we can force / to be read-only againt. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On Tue, 2015-08-04 at 20:11 +0000, Eren Yagdiran wrote:
Volumes let user to map host-paths into guest. Docker containers need volumes because its filesystem read-only by default.
Docker doesn't set / read-only by default. Docker volumes are only a way to share data with the host or other containers. See here: http://docs.docker.com/userguide/dockervolumes/#volume -- Cedric
--- virt-sandbox-image/sources/DockerSource.py | 12 ++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++++ virt-sandbox-image/virt-sandbox-image.py | 22 ++++++++++++++++++++++ 3 files changed, 38 insertions(+)
diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 74feb3e..44bc238 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -31,6 +31,7 @@ import traceback import os import subprocess import shutil +import collections
class DockerConfParser():
@@ -40,6 +41,13 @@ class DockerConfParser(): def getRunCommand(self): cmd = self.json_data['container_config']['Cmd'][2] return cmd[cmd.index('"') + 1:cmd.rindex('"')] + def getVolumes(self): + volumes = self.json_data['container_config']['Volumes'] + volumelist = [] + if isinstance(volumes,collections.Iterable): + for key,value in volumes.iteritems(): + volumelist.append(key) + return volumelist
class DockerSource(Source): default_index_server = "index.docker.io" @@ -399,5 +407,9 @@ class DockerSource(Source): commandToRun = configParser.getRunCommand() return commandToRun
+ def get_volume(self,configfile): + configParser = DockerConfParser(configfile) + return configParser.getVolumes() + def debug(msg): sys.stderr.write(msg) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index 6e2f5fb..6898c15 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -49,3 +49,7 @@ class Source(): @abstractmethod def get_disk(self,**args): pass + + @abstractmethod + def get_volume(self,**args): + pass diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 5fc7f44..b12b99b 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -103,6 +103,7 @@ def check_connect(connectstr):
def run(args): try: + default_dir = "/var/lib/libvirt/storage" if args.connect is not None: check_connect(args.connect) source = dynamic_source_loader(args.source) @@ -121,6 +122,25 @@ def run(args): if networkArgs is not None: params.append('-N') params.append(networkArgs) + allVolumes = source.get_volume(configfile) + volumeArgs = args.volume + if volumeArgs is not None: + allVolumes = allVolumes + volumeArgs + for volume in allVolumes: + volumeSplit = volume.split(":") + volumelen = len(volumeSplit) + if volumelen == 2: + hostPath = volumeSplit[0] + guestPath = volumeSplit[1] + elif volumelen == 1: + guestPath = volumeSplit[0] + hostPath = default_dir + guestPath + if not os.path.exists(hostPath): + os.makedirs(hostPath) + else: + pass + params.append("--mount") + params.append("host-bind:%s=%s" %(guestPath,hostPath)) params.append('--') params.append(commandToRun) cmd = cmd + params @@ -193,6 +213,8 @@ def gen_run_args(subparser): help=_("Igniter command for image")) parser.add_argument("-n","--network", help=_("Network params for running template")) + parser.add_argument("-v","--volume",action="append", + help=_("Volume params for running template")) parser.set_defaults(func=run)
if __name__ == '__main__':

--- bin/Makefile.am | 5 ++ bin/virt-sandbox-image.pod | 172 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 bin/virt-sandbox-image.pod diff --git a/bin/Makefile.am b/bin/Makefile.am index df4c7dc..5d7ff8a 100644 --- a/bin/Makefile.am +++ b/bin/Makefile.am @@ -23,6 +23,7 @@ POD_FILES = \ virt-sandbox-service-delete.pod \ virt-sandbox-service-reload.pod \ virt-sandbox-service-upgrade.pod \ + virt-sandbox-image.pod \ $(NULL) EXTRA_DIST = virt-sandbox-service \ virt-sandbox-image.in \ @@ -40,6 +41,7 @@ man1_MANS = \ virt-sandbox-service-delete.1 \ virt-sandbox-service-reload.1 \ virt-sandbox-service-upgrade.1 \ + virt-sandbox-image.1 \ $(NULL) POD2MAN = pod2man -c "Virtualization Support" -r "$(PACKAGE)-$(VERSION)" @@ -71,6 +73,9 @@ virt-sandbox-service-reload.1: virt-sandbox-service-reload.pod Makefile virt-sandbox-service-upgrade.1: virt-sandbox-service-upgrade.pod Makefile $(AM_V_GEN)$(POD2MAN) $< $(srcdir)/$@ +virt-sandbox-image.1: virt-sandbox-image.pod Makefile + $(AM_V_GEN)$(POD2MAN) $< $(srcdir)/$@ + CLEANFILES = $(man1_MANS) \ virt-sandbox-image diff --git a/bin/virt-sandbox-image.pod b/bin/virt-sandbox-image.pod new file mode 100644 index 0000000..a85fcd9 --- /dev/null +++ b/bin/virt-sandbox-image.pod @@ -0,0 +1,172 @@ +=head1 NAME + +virt-sandbox-image - Sandbox Container Image Tool + +=head1 SYNOPSIS + + {download,create,run,delete} + + commands: + + download Download template data + + create Create image from template data + + run Run an already built image + + delete Delete template data + +=head1 DESCRIPTION + +virt-sandbox-image.py is a sandbox container image tool developed in python. +This tool can download,create,run and delete templates which are provided by +different sources. This tool comes with Docker source by default. Other sources +can be implemented by extending source class + +=head1 OPTIONS + +=over 4 + +=item B<download name -s source -r registry -u username -p password -t template_directory> + +Download a template by given name with a specified source. + +=over 6 + +=item B<name> + +Template name to download + +=item B<-s or --source> + +Source parameter will try load source module under sources/ directory. Each source has to implement Source.py base class and register itself with a proper name +Default source is Docker. + +=item B<-r or --registry> + +Custom registry url for downloading data. This might need privileged credentials which can be specified by --username and --password parameters. + +=item B<-u or --username> + +Username for custom registry authentication + +=item B<-p or --password> + +Password for custom registry authentication + +=item B<-t or --template-dir> + +Custom directory for downloading template data + +=back + +=item B<create name imagepath format -s source -d driver> + +Create already downloaded template into image with given format. + +=over 5 + +=item B<name> + +Template name to download. + +=item B<imagepath> + +Image path where template image will be stored. + +=item B<format> + +Image format e.g qcow2 + +=item B<-s or --source> + +Source parameter will try load source module under sources/ directory. Each source has to implement Source.py base class and register itself with a proper name +Default source is Docker. + +=item B<-d or --driver> + +Driver parameter can be specified with only supported driver by libvirt-sandbox. These are lxc:///, qemu:///session, qemu:///system. + +=back + +=item B<run name imagepath format -c command -n network -v volume -s source -d driver> + +Run already built image. + +=over 6 + +=item B<name> + +Template name to download. + +=item B<imagepath> + +Image path where template image will be stored. + +=item B<-c or --command> + +Command for running a image. If it is not specified, virt-sandbox-image will try to load command params from specified source. E.g /bin/bash + +=item B<-n or --network> + +Network params will be passed directly to the virt-sandbox. More information about network params, See C<virt-sandbox(8)> + +=item B<-v or --volume> + +Volume params are for binding host-paths to the guest. E.g -v /home:/home will map /home directory from host to the guest. + +=item B<-d or --driver> + +Driver parameter can be specified with only supported driver by libvirt-sandbox. These are lxc:///, qemu:///session, qemu:///system. + +=back + +=item B<delete name imagepath -s source > + +Delete downloaded template data and its built image. + +=over 3 + +=item B<name> + +Template name to delete. + +=item B<imagepath> + +Image path where template data or image stays. + +=item B<-s or --source> + +Source parameter will try load source module under sources/ directory. Each source has to implement Source.py base class and register itself with a proper name +Default source is Docker. + +=back + +=back + +=head1 SEE ALSO + +C<virt-sandbox(8)> + +=head1 FILES + +Container content will be stored in subdirectories of +/var/lib/libvirt/templates, by default. + +=head1 AUTHORS + +Daniel P. Berrange <dan@berrange.com> + +Eren Yagdiran <erenyagdiran@gmail.com> + +=head1 COPYRIGHT + +Copyright (C) 2013 Red Hat, Inc. +Copyright (C) 2015 Universitat Politecnica de Catalunya. + +=head1 LICENSE + +virt-sandbox-image is distributed under the terms of the GNU LGPL v2+. +This is free software; see the source for copying conditions. +There is NO warranty; not even for MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE -- 2.1.0

Add the config gobject to store custom environment variables. This will allow creating custom environment variables on a sandbox with a parameter formatted like --env key1=val1 --- libvirt-sandbox/Makefile.am | 2 + libvirt-sandbox/libvirt-sandbox-config-all.h | 1 + libvirt-sandbox/libvirt-sandbox-config-env.c | 199 +++++++++++++++++++++++++++ libvirt-sandbox/libvirt-sandbox-config-env.h | 78 +++++++++++ libvirt-sandbox/libvirt-sandbox-config.c | 187 ++++++++++++++++++++++++- libvirt-sandbox/libvirt-sandbox-config.h | 12 ++ libvirt-sandbox/libvirt-sandbox.h | 1 + libvirt-sandbox/libvirt-sandbox.sym | 6 + 8 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 libvirt-sandbox/libvirt-sandbox-config-env.c create mode 100644 libvirt-sandbox/libvirt-sandbox-config-env.h diff --git a/libvirt-sandbox/Makefile.am b/libvirt-sandbox/Makefile.am index 597803e..5383b0d 100644 --- a/libvirt-sandbox/Makefile.am +++ b/libvirt-sandbox/Makefile.am @@ -53,6 +53,7 @@ SANDBOX_RPC_FILES = \ SANDBOX_CONFIG_HEADER_FILES = \ libvirt-sandbox-config.h \ libvirt-sandbox-config-disk.h \ + libvirt-sandbox-config-env.h \ libvirt-sandbox-config-network.h \ libvirt-sandbox-config-network-address.h \ libvirt-sandbox-config-network-filterref-parameter.h \ @@ -92,6 +93,7 @@ SANDBOX_CONFIG_SOURCE_FILES = \ libvirt-sandbox-util.c \ libvirt-sandbox-config.c \ libvirt-sandbox-config-disk.c \ + libvirt-sandbox-config-env.c \ libvirt-sandbox-config-network.c \ libvirt-sandbox-config-network-address.c \ libvirt-sandbox-config-network-filterref.c \ diff --git a/libvirt-sandbox/libvirt-sandbox-config-all.h b/libvirt-sandbox/libvirt-sandbox-config-all.h index 8cb25c4..756bb3e 100644 --- a/libvirt-sandbox/libvirt-sandbox-config-all.h +++ b/libvirt-sandbox/libvirt-sandbox-config-all.h @@ -32,6 +32,7 @@ /* Local includes */ #include <libvirt-sandbox/libvirt-sandbox-util.h> #include <libvirt-sandbox/libvirt-sandbox-config-disk.h> +#include <libvirt-sandbox/libvirt-sandbox-config-env.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount-file.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount-host-bind.h> diff --git a/libvirt-sandbox/libvirt-sandbox-config-env.c b/libvirt-sandbox/libvirt-sandbox-config-env.c new file mode 100644 index 0000000..eaf0fb2 --- /dev/null +++ b/libvirt-sandbox/libvirt-sandbox-config-env.c @@ -0,0 +1,199 @@ +/* + * libvirt-sandbox-config-env.c: libvirt sandbox configuration + * + * Copyright (C) 2015 Universitat Polit��cnica de Catalunya. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Eren Yagdiran <erenyagdiran@gmail.com> + */ + +#include <config.h> +#include <string.h> + +#include "libvirt-sandbox/libvirt-sandbox-config-all.h" + +/** + * SECTION: libvirt-sandbox-config-env + * @short_description: Disk attachment configuration details + * @include: libvirt-sandbox/libvirt-sandbox.h + * @see_aloso: #GVirSandboxConfig + * + * Provides an object to store information about a environment variable in the sandbox + * + */ + +#define GVIR_SANDBOX_CONFIG_ENV_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), GVIR_SANDBOX_TYPE_CONFIG_ENV, GVirSandboxConfigEnvPrivate)) + + +struct _GVirSandboxConfigEnvPrivate +{ + gchar *key; + gchar *value; +}; + +G_DEFINE_TYPE(GVirSandboxConfigEnv, gvir_sandbox_config_env, G_TYPE_OBJECT); + + +enum { + PROP_0, + PROP_KEY, + PROP_VALUE +}; + +enum { + LAST_SIGNAL +}; + + + +static void gvir_sandbox_config_env_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GVirSandboxConfigEnv *config = GVIR_SANDBOX_CONFIG_ENV(object); + GVirSandboxConfigEnvPrivate *priv = config->priv; + + switch (prop_id) { + case PROP_KEY: + g_value_set_string(value, priv->key); + break; + case PROP_VALUE: + g_value_set_string(value, priv->value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + + +static void gvir_sandbox_config_env_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GVirSandboxConfigEnv *config = GVIR_SANDBOX_CONFIG_ENV(object); + GVirSandboxConfigEnvPrivate *priv = config->priv; + + switch (prop_id) { + case PROP_KEY: + g_free(priv->key); + priv->key = g_value_dup_string(value); + break; + case PROP_VALUE: + g_free(priv->value); + priv->value = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + + +static void gvir_sandbox_config_env_finalize(GObject *object) +{ + GVirSandboxConfigEnv *config = GVIR_SANDBOX_CONFIG_ENV(object); + GVirSandboxConfigEnvPrivate *priv = config->priv; + + g_free(priv->key); + g_free(priv->value); + + G_OBJECT_CLASS(gvir_sandbox_config_env_parent_class)->finalize(object); +} + + +static void gvir_sandbox_config_env_class_init(GVirSandboxConfigEnvClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gvir_sandbox_config_env_finalize; + object_class->get_property = gvir_sandbox_config_env_get_property; + object_class->set_property = gvir_sandbox_config_env_set_property; + + g_object_class_install_property(object_class, + PROP_KEY, + g_param_spec_string("key", + "Key", + "The sandbox key property", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_object_class_install_property(object_class, + PROP_VALUE, + g_param_spec_string("value", + "Value", + "The sandbox value property", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_type_class_add_private(klass, sizeof(GVirSandboxConfigEnvPrivate)); +} + + +static void gvir_sandbox_config_env_init(GVirSandboxConfigEnv *config) +{ + config->priv = GVIR_SANDBOX_CONFIG_ENV_GET_PRIVATE(config); +} + + +/** + * gvir_sandbox_config_env_get_key: + * @config: (transfer none): the sandbox env config + * + * Retrieves the key property for the environment variable + * + * Returns: (transfer none): the key property + */ +const gchar *gvir_sandbox_config_env_get_key(GVirSandboxConfigEnv *config) +{ + GVirSandboxConfigEnvPrivate *priv = config->priv; + return priv->key; +} + + +/** + * gvir_sandbox_config_disk_get_value: + * @config: (transfer none): the sandbox env config + * + * Retrieves the value property for the environment variable + * + * Returns: (transfer none): the value property + */ +const gchar *gvir_sandbox_config_env_get_value(GVirSandboxConfigEnv *config) +{ + GVirSandboxConfigEnvPrivate *priv = config->priv; + return priv->value; +} + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/libvirt-sandbox/libvirt-sandbox-config-env.h b/libvirt-sandbox/libvirt-sandbox-config-env.h new file mode 100644 index 0000000..1386f10 --- /dev/null +++ b/libvirt-sandbox/libvirt-sandbox-config-env.h @@ -0,0 +1,78 @@ +/* + * libvirt-sandbox-config-env.h: libvirt sandbox configuration + * + * Copyright (C) 2015 Universitat Polit��cnica de Catalunya. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Eren Yagdiran <erenyagdiran@gmail.com> + */ + +#if !defined(__LIBVIRT_SANDBOX_H__) && !defined(LIBVIRT_SANDBOX_BUILD) +#error "Only <libvirt-sandbox/libvirt-sandbox.h> can be included directly." +#endif + +#ifndef __LIBVIRT_SANDBOX_CONFIG_ENV_H__ +#define __LIBVIRT_SANDBOX_CONFIG_ENV_H__ + +G_BEGIN_DECLS + +#define GVIR_SANDBOX_TYPE_CONFIG_ENV (gvir_sandbox_config_env_get_type ()) +#define GVIR_SANDBOX_CONFIG_ENV(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GVIR_SANDBOX_TYPE_CONFIG_ENV, GVirSandboxConfigEnv)) +#define GVIR_SANDBOX_CONFIG_ENV_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GVIR_SANDBOX_TYPE_CONFIG_ENV, GVirSandboxConfigEnvClass)) +#define GVIR_SANDBOX_IS_CONFIG_ENV(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GVIR_SANDBOX_TYPE_CONFIG_ENV)) +#define GVIR_SANDBOX_IS_CONFIG_ENV_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GVIR_SANDBOX_TYPE_CONFIG_ENV)) +#define GVIR_SANDBOX_CONFIG_ENV_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GVIR_SANDBOX_TYPE_CONFIG_ENV, GVirSandboxConfigEnvClass)) + +#define GVIR_SANDBOX_TYPE_CONFIG_ENV_HANDLE (gvir_sandbox_config_env_handle_get_type ()) + +typedef struct _GVirSandboxConfigEnv GVirSandboxConfigEnv; +typedef struct _GVirSandboxConfigEnvPrivate GVirSandboxConfigEnvPrivate; +typedef struct _GVirSandboxConfigEnvClass GVirSandboxConfigEnvClass; + +struct _GVirSandboxConfigEnv +{ + GObject parent; + + GVirSandboxConfigEnvPrivate *priv; + + /* Do not add fields to this struct */ +}; + +struct _GVirSandboxConfigEnvClass +{ + GObjectClass parent_class; + + gpointer padding[LIBVIRT_SANDBOX_CLASS_PADDING]; +}; + +GType gvir_sandbox_config_env_get_type(void); + +const gchar *gvir_sandbox_config_env_get_key(GVirSandboxConfigEnv *config); + +const gchar *gvir_sandbox_config_env_get_value(GVirSandboxConfigEnv *config); + +G_END_DECLS + +#endif /* __LIBVIRT_SANDBOX_CONFIG_DISK_H__ */ + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/libvirt-sandbox/libvirt-sandbox-config.c b/libvirt-sandbox/libvirt-sandbox-config.c index 2506072..980a50b 100644 --- a/libvirt-sandbox/libvirt-sandbox-config.c +++ b/libvirt-sandbox/libvirt-sandbox-config.c @@ -64,6 +64,7 @@ struct _GVirSandboxConfigPrivate GList *networks; GList *mounts; GList *disks; + GList *envs; gchar *secLabel; gboolean secDynamic; @@ -276,6 +277,9 @@ static void gvir_sandbox_config_finalize(GObject *object) g_list_foreach(priv->networks, (GFunc)g_object_unref, NULL); g_list_free(priv->networks); + g_list_foreach(priv->envs, (GFunc)g_object_unref, NULL); + g_list_free(priv->envs); + g_list_foreach(priv->disks, (GFunc)g_object_unref, NULL); g_list_free(priv->disks); @@ -1144,6 +1148,114 @@ gboolean gvir_sandbox_config_has_networks(GVirSandboxConfig *config) return priv->networks ? TRUE : FALSE; } +/** + * gvir_sandbox_config_add_env: + * @config: (transfer none): the sandbox config + * @dsk: (transfer none): the env configuration + * + * Adds a new environment variable to the sandbox + * + */ +void gvir_sandbox_config_add_env(GVirSandboxConfig *config, + GVirSandboxConfigEnv *env) +{ + GVirSandboxConfigPrivate *priv = config->priv; + + g_object_ref(env); + + priv->envs = g_list_append(priv->envs, env); +} + +/** + * gvir_sandbox_config_get_envs: + * @config: (transfer none): the sandbox config + * + * Retrieves the list of custom environment list in the sandbox + * + * Returns: (transfer full) (element-type GVirSandboxConfigMount): the list of environment variables + */ +GList *gvir_sandbox_config_get_envs(GVirSandboxConfig *config) +{ + GVirSandboxConfigPrivate *priv = config->priv; + g_list_foreach(priv->envs, (GFunc)g_object_ref, NULL); + return g_list_copy(priv->envs); +} + +/** + * gvir_sandbox_config_add_env_strv: + * @config: (transfer none): the sandbox config + * @envs: (transfer none)(array zero-terminated=1): the list of environment variables + * + * Parses @envs whose elements are in the format KEY=VALUE + * + * --env KEY=VALUE + */ +gboolean gvir_sandbox_config_add_env_strv(GVirSandboxConfig *config, + gchar **envs, + GError **error) +{ + gsize i = 0; + while (envs && envs[i]) { + if (!gvir_sandbox_config_add_env_opts(config, + envs[i], + error)) + return FALSE; + i++; + } + return TRUE; +} + +/** + * gvir_sandbox_config_add_env_opts: + * @config: (transfer none): the sandbox config + * @disk: (transfer none): the env config + * + * Parses @env in the format KEY=VALUE + * creating #GVirSandboxConfigEnv instances for each element. For + * example + * + * --env KEY=VALUE + */ + +gboolean gvir_sandbox_config_add_env_opts(GVirSandboxConfig *config, + const char *opt, + GError **error) +{ + gchar *tmp = NULL; + gchar *key = NULL; + gchar *value = NULL; + GVirSandboxConfigEnv *envConfig; + + if (!(tmp = g_strdup(opt))) + return FALSE; + + key = strchr(tmp, '='); + + if (!key) { + g_set_error(error, GVIR_SANDBOX_CONFIG_ERROR, 0, + _("Wrong environment format on %s"), opt); + return FALSE; + } + + *key = '\0'; + value = key + 1; + envConfig = GVIR_SANDBOX_CONFIG_ENV(g_object_new(GVIR_SANDBOX_TYPE_CONFIG_ENV, + "key", tmp, + "value", value, + NULL)); + + gvir_sandbox_config_add_env(config, envConfig); + + g_object_unref(envConfig); + g_free(tmp); + return TRUE; +} + +gboolean gvir_sandbox_config_has_envs(GVirSandboxConfig *config) +{ + GVirSandboxConfigPrivate *priv = config->priv; + return priv->envs != NULL; +} /** * gvir_sandbox_config_add_disk: @@ -1163,7 +1275,6 @@ void gvir_sandbox_config_add_disk(GVirSandboxConfig *config, priv->disks = g_list_append(priv->disks, dsk); } - /** * gvir_sandbox_config_get_disks: * @config: (transfer none): the sandbox config @@ -1949,6 +2060,44 @@ static GVirSandboxConfigMount *gvir_sandbox_config_load_config_mount(GKeyFile *f goto cleanup; } +static GVirSandboxConfigEnv *gvir_sandbox_config_load_config_env(GKeyFile *file, + guint i, + GError **error) +{ + GVirSandboxConfigEnv *config = NULL; + gchar *index = NULL; + gchar *key = NULL; + gchar *value = NULL; + GError *e = NULL; + + index = g_strdup_printf("env.%u", i); + if ((key = g_key_file_get_string(file, index, "key", &e)) == NULL) { + if (e->code == G_KEY_FILE_ERROR_GROUP_NOT_FOUND) { + g_error_free(e); + return NULL; + } + g_error_free(e); + g_set_error(error, GVIR_SANDBOX_CONFIG_ERROR, 0, + "%s", _("Missing environment key in config file")); + goto cleanup; + } + if ((value = g_key_file_get_string(file, index, "value", NULL)) == NULL) { + g_set_error(error, GVIR_SANDBOX_CONFIG_ERROR, 0, + "%s", _("Missing environment value in config file")); + goto cleanup; + } + + config = GVIR_SANDBOX_CONFIG_ENV(g_object_new(GVIR_SANDBOX_TYPE_CONFIG_ENV, + "key", key, + "value", value, + NULL)); + + cleanup: + g_free(key); + g_free(value); + g_free(index); + return config; +} static GVirSandboxConfigDisk *gvir_sandbox_config_load_config_disk(GKeyFile *file, guint i, @@ -2244,6 +2393,14 @@ static gboolean gvir_sandbox_config_load_config(GVirSandboxConfig *config, priv->mounts = g_list_append(priv->mounts, mount); } + for (i = 0 ; i < 1024 ; i++) { + GVirSandboxConfigEnv *env; + if (!(env = gvir_sandbox_config_load_config_env(file, i, error)) && + *error) + goto cleanup; + if (env) + priv->envs = g_list_append(priv->envs, env); + } for (i = 0 ; i < 1024 ; i++) { GVirSandboxConfigDisk *disk; @@ -2274,6 +2431,24 @@ static gboolean gvir_sandbox_config_load_config(GVirSandboxConfig *config, return ret; } +static void gvir_sandbox_config_save_config_env(GVirSandboxConfigEnv *config, + GKeyFile *file, + guint i) +{ + gchar *index = NULL; + gchar *key = NULL; + gchar *value = NULL; + + index = g_strdup_printf("env.%u", i); + key = g_strdup(gvir_sandbox_config_env_get_key(config)); + value = g_strdup(gvir_sandbox_config_env_get_value(config)); + g_key_file_set_string(file, index, "key",key); + g_key_file_set_string(file, index, "value",value); + + g_free(index); + g_free(key); + g_free(value); +} static void gvir_sandbox_config_save_config_disk(GVirSandboxConfigDisk *config, GKeyFile *file, @@ -2485,6 +2660,16 @@ static void gvir_sandbox_config_save_config(GVirSandboxConfig *config, } i = 0; + tmp = priv->envs; + while (tmp) { + gvir_sandbox_config_save_config_env(tmp->data, + file, + i); + tmp = tmp->next; + i++; + } + + i = 0; tmp = priv->disks; while (tmp) { gvir_sandbox_config_save_config_disk(tmp->data, diff --git a/libvirt-sandbox/libvirt-sandbox-config.h b/libvirt-sandbox/libvirt-sandbox-config.h index 2c5f0a6..d39bb2b 100644 --- a/libvirt-sandbox/libvirt-sandbox-config.h +++ b/libvirt-sandbox/libvirt-sandbox-config.h @@ -122,6 +122,18 @@ gboolean gvir_sandbox_config_add_network_strv(GVirSandboxConfig *config, GError **error); gboolean gvir_sandbox_config_has_networks(GVirSandboxConfig *config); +void gvir_sandbox_config_add_env(GVirSandboxConfig *config, + GVirSandboxConfigEnv *env); +GList *gvir_sandbox_config_get_envs(GVirSandboxConfig *config); +gboolean gvir_sandbox_config_add_env_strv(GVirSandboxConfig *config, + gchar **envs, + GError **error); +gboolean gvir_sandbox_config_add_env_opts(GVirSandboxConfig *config, + const char *env, + GError **error); +gboolean gvir_sandbox_config_has_envs(GVirSandboxConfig *config); + + void gvir_sandbox_config_add_disk(GVirSandboxConfig *config, GVirSandboxConfigDisk *dsk); GList *gvir_sandbox_config_get_disks(GVirSandboxConfig *config); diff --git a/libvirt-sandbox/libvirt-sandbox.h b/libvirt-sandbox/libvirt-sandbox.h index 20dc871..05d7485 100644 --- a/libvirt-sandbox/libvirt-sandbox.h +++ b/libvirt-sandbox/libvirt-sandbox.h @@ -32,6 +32,7 @@ #include <libvirt-sandbox/libvirt-sandbox-util.h> #include <libvirt-sandbox/libvirt-sandbox-enum-types.h> #include <libvirt-sandbox/libvirt-sandbox-config-disk.h> +#include <libvirt-sandbox/libvirt-sandbox-config-env.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount-file.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount-host-bind.h> diff --git a/libvirt-sandbox/libvirt-sandbox.sym b/libvirt-sandbox/libvirt-sandbox.sym index 6f8097a..a09a6c2 100644 --- a/libvirt-sandbox/libvirt-sandbox.sym +++ b/libvirt-sandbox/libvirt-sandbox.sym @@ -26,6 +26,12 @@ LIBVIRT_SANDBOX_0.6.0 { gvir_sandbox_config_disk_get_type; gvir_sandbox_config_has_disks; + gvir_sandbox_config_add_env; + gvir_sandbox_config_add_env_strv; + gvir_sandbox_config_add_env_opts; + gvir_sandbox_config_env_get_type; + gvir_sandbox_config_has_envs; + gvir_sandbox_config_mount_add_include; gvir_sandbox_config_mount_get_includes; gvir_sandbox_config_mount_get_target; -- 2.1.0

On Tue, 2015-08-04 at 20:11 +0000, Eren Yagdiran wrote:
Add the config gobject to store custom environment variables. This will allow creating custom environment variables on a sandbox with a parameter formatted like --env key1=val1 --- libvirt-sandbox/Makefile.am | 2 + libvirt-sandbox/libvirt-sandbox-config-all.h | 1 + libvirt-sandbox/libvirt-sandbox-config-env.c | 199 +++++++++++++++++++++++++++ libvirt-sandbox/libvirt-sandbox-config-env.h | 78 +++++++++++ libvirt-sandbox/libvirt-sandbox-config.c | 187 ++++++++++++++++++++++++- libvirt-sandbox/libvirt-sandbox-config.h | 12 ++ libvirt-sandbox/libvirt-sandbox.h | 1 + libvirt-sandbox/libvirt-sandbox.sym | 6 + 8 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 libvirt-sandbox/libvirt-sandbox-config-env.c create mode 100644 libvirt-sandbox/libvirt-sandbox-config-env.h
diff --git a/libvirt-sandbox/Makefile.am b/libvirt-sandbox/Makefile.am index 597803e..5383b0d 100644 --- a/libvirt-sandbox/Makefile.am +++ b/libvirt-sandbox/Makefile.am @@ -53,6 +53,7 @@ SANDBOX_RPC_FILES = \ SANDBOX_CONFIG_HEADER_FILES = \ libvirt-sandbox-config.h \ libvirt-sandbox-config-disk.h \ + libvirt-sandbox-config-env.h \ libvirt-sandbox-config-network.h \ libvirt-sandbox-config-network-address.h \ libvirt-sandbox-config-network-filterref-parameter.h \ @@ -92,6 +93,7 @@ SANDBOX_CONFIG_SOURCE_FILES = \ libvirt-sandbox-util.c \ libvirt-sandbox-config.c \ libvirt-sandbox-config-disk.c \ + libvirt-sandbox-config-env.c \ libvirt-sandbox-config-network.c \ libvirt-sandbox-config-network-address.c \ libvirt-sandbox-config-network-filterref.c \ diff --git a/libvirt-sandbox/libvirt-sandbox-config-all.h b/libvirt-sandbox/libvirt-sandbox-config-all.h index 8cb25c4..756bb3e 100644 --- a/libvirt-sandbox/libvirt-sandbox-config-all.h +++ b/libvirt-sandbox/libvirt-sandbox-config-all.h @@ -32,6 +32,7 @@ /* Local includes */ #include <libvirt-sandbox/libvirt-sandbox-util.h> #include <libvirt-sandbox/libvirt-sandbox-config-disk.h> +#include <libvirt-sandbox/libvirt-sandbox-config-env.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount-file.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount-host-bind.h> diff --git a/libvirt-sandbox/libvirt-sandbox-config-env.c b/libvirt-sandbox/libvirt-sandbox-config-env.c new file mode 100644 index 0000000..eaf0fb2 --- /dev/null +++ b/libvirt-sandbox/libvirt-sandbox-config-env.c @@ -0,0 +1,199 @@ +/* + * libvirt-sandbox-config-env.c: libvirt sandbox configuration + * + * Copyright (C) 2015 Universitat Politècnica de Catalunya. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Eren Yagdiran <erenyagdiran@gmail.com> + */ + +#include <config.h> +#include <string.h> + +#include "libvirt-sandbox/libvirt-sandbox-config-all.h" + +/** + * SECTION: libvirt-sandbox-config-env + * @short_description: Disk attachment configuration details + * @include: libvirt-sandbox/libvirt-sandbox.h + * @see_aloso: #GVirSandboxConfig + * + * Provides an object to store information about a environment variable in the sandbox + * + */ + +#define GVIR_SANDBOX_CONFIG_ENV_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE((obj), GVIR_SANDBOX_TYPE_CONFIG_ENV, GVirSandboxConfigEnvPrivate)) + + +struct _GVirSandboxConfigEnvPrivate +{ + gchar *key; + gchar *value; +}; + +G_DEFINE_TYPE(GVirSandboxConfigEnv, gvir_sandbox_config_env, G_TYPE_OBJECT); + + +enum { + PROP_0, + PROP_KEY, + PROP_VALUE +}; + +enum { + LAST_SIGNAL +}; + + + +static void gvir_sandbox_config_env_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GVirSandboxConfigEnv *config = GVIR_SANDBOX_CONFIG_ENV(object); + GVirSandboxConfigEnvPrivate *priv = config->priv; + + switch (prop_id) { + case PROP_KEY: + g_value_set_string(value, priv->key); + break; + case PROP_VALUE: + g_value_set_string(value, priv->value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + + +static void gvir_sandbox_config_env_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GVirSandboxConfigEnv *config = GVIR_SANDBOX_CONFIG_ENV(object); + GVirSandboxConfigEnvPrivate *priv = config->priv; + + switch (prop_id) { + case PROP_KEY: + g_free(priv->key); + priv->key = g_value_dup_string(value); + break; + case PROP_VALUE: + g_free(priv->value); + priv->value = g_value_dup_string(value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + + +static void gvir_sandbox_config_env_finalize(GObject *object) +{ + GVirSandboxConfigEnv *config = GVIR_SANDBOX_CONFIG_ENV(object); + GVirSandboxConfigEnvPrivate *priv = config->priv; + + g_free(priv->key); + g_free(priv->value); + + G_OBJECT_CLASS(gvir_sandbox_config_env_parent_class)->finalize(object); +} + + +static void gvir_sandbox_config_env_class_init(GVirSandboxConfigEnvClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->finalize = gvir_sandbox_config_env_finalize; + object_class->get_property = gvir_sandbox_config_env_get_property; + object_class->set_property = gvir_sandbox_config_env_set_property; + + g_object_class_install_property(object_class, + PROP_KEY, + g_param_spec_string("key", + "Key", + "The sandbox key property", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_object_class_install_property(object_class, + PROP_VALUE, + g_param_spec_string("value", + "Value", + "The sandbox value property", + NULL, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_NAME | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB)); + + g_type_class_add_private(klass, sizeof(GVirSandboxConfigEnvPrivate)); +} + + +static void gvir_sandbox_config_env_init(GVirSandboxConfigEnv *config) +{ + config->priv = GVIR_SANDBOX_CONFIG_ENV_GET_PRIVATE(config); +} + + +/** + * gvir_sandbox_config_env_get_key: + * @config: (transfer none): the sandbox env config + * + * Retrieves the key property for the environment variable + * + * Returns: (transfer none): the key property + */ +const gchar *gvir_sandbox_config_env_get_key(GVirSandboxConfigEnv *config) +{ + GVirSandboxConfigEnvPrivate *priv = config->priv; + return priv->key; +} + + +/** + * gvir_sandbox_config_disk_get_value: + * @config: (transfer none): the sandbox env config + * + * Retrieves the value property for the environment variable + * + * Returns: (transfer none): the value property + */ +const gchar *gvir_sandbox_config_env_get_value(GVirSandboxConfigEnv *config) +{ + GVirSandboxConfigEnvPrivate *priv = config->priv; + return priv->value; +} + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/libvirt-sandbox/libvirt-sandbox-config-env.h b/libvirt-sandbox/libvirt-sandbox-config-env.h new file mode 100644 index 0000000..1386f10 --- /dev/null +++ b/libvirt-sandbox/libvirt-sandbox-config-env.h @@ -0,0 +1,78 @@ +/* + * libvirt-sandbox-config-env.h: libvirt sandbox configuration + * + * Copyright (C) 2015 Universitat Politècnica de Catalunya. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Author: Eren Yagdiran <erenyagdiran@gmail.com> + */ + +#if !defined(__LIBVIRT_SANDBOX_H__) && !defined(LIBVIRT_SANDBOX_BUILD) +#error "Only <libvirt-sandbox/libvirt-sandbox.h> can be included directly." +#endif + +#ifndef __LIBVIRT_SANDBOX_CONFIG_ENV_H__ +#define __LIBVIRT_SANDBOX_CONFIG_ENV_H__ + +G_BEGIN_DECLS + +#define GVIR_SANDBOX_TYPE_CONFIG_ENV (gvir_sandbox_config_env_get_type ()) +#define GVIR_SANDBOX_CONFIG_ENV(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GVIR_SANDBOX_TYPE_CONFIG_ENV, GVirSandboxConfigEnv)) +#define GVIR_SANDBOX_CONFIG_ENV_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GVIR_SANDBOX_TYPE_CONFIG_ENV, GVirSandboxConfigEnvClass)) +#define GVIR_SANDBOX_IS_CONFIG_ENV(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GVIR_SANDBOX_TYPE_CONFIG_ENV)) +#define GVIR_SANDBOX_IS_CONFIG_ENV_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GVIR_SANDBOX_TYPE_CONFIG_ENV)) +#define GVIR_SANDBOX_CONFIG_ENV_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GVIR_SANDBOX_TYPE_CONFIG_ENV, GVirSandboxConfigEnvClass)) + +#define GVIR_SANDBOX_TYPE_CONFIG_ENV_HANDLE (gvir_sandbox_config_env_handle_get_type ()) + +typedef struct _GVirSandboxConfigEnv GVirSandboxConfigEnv; +typedef struct _GVirSandboxConfigEnvPrivate GVirSandboxConfigEnvPrivate; +typedef struct _GVirSandboxConfigEnvClass GVirSandboxConfigEnvClass; + +struct _GVirSandboxConfigEnv +{ + GObject parent; + + GVirSandboxConfigEnvPrivate *priv; + + /* Do not add fields to this struct */ +}; + +struct _GVirSandboxConfigEnvClass +{ + GObjectClass parent_class; + + gpointer padding[LIBVIRT_SANDBOX_CLASS_PADDING]; +}; + +GType gvir_sandbox_config_env_get_type(void); + +const gchar *gvir_sandbox_config_env_get_key(GVirSandboxConfigEnv *config); + +const gchar *gvir_sandbox_config_env_get_value(GVirSandboxConfigEnv *config); + +G_END_DECLS + +#endif /* __LIBVIRT_SANDBOX_CONFIG_DISK_H__ */ + +/* + * Local variables: + * c-indent-level: 4 + * c-basic-offset: 4 + * indent-tabs-mode: nil + * tab-width: 8 + * End: + */ diff --git a/libvirt-sandbox/libvirt-sandbox-config.c b/libvirt-sandbox/libvirt-sandbox-config.c index 2506072..980a50b 100644 --- a/libvirt-sandbox/libvirt-sandbox-config.c +++ b/libvirt-sandbox/libvirt-sandbox-config.c @@ -64,6 +64,7 @@ struct _GVirSandboxConfigPrivate GList *networks; GList *mounts; GList *disks; + GList *envs;
Can't we just use a GHashTable here? See here for the doc: https://developer.gnome.org/glib/stable/glib-Hash-Tables.html No need to reinvent the wheel, or the hash table ;) -- Cedric
gchar *secLabel; gboolean secDynamic; @@ -276,6 +277,9 @@ static void gvir_sandbox_config_finalize(GObject *object) g_list_foreach(priv->networks, (GFunc)g_object_unref, NULL); g_list_free(priv->networks);
+ g_list_foreach(priv->envs, (GFunc)g_object_unref, NULL); + g_list_free(priv->envs); + g_list_foreach(priv->disks, (GFunc)g_object_unref, NULL); g_list_free(priv->disks);
@@ -1144,6 +1148,114 @@ gboolean gvir_sandbox_config_has_networks(GVirSandboxConfig *config) return priv->networks ? TRUE : FALSE; }
+/** + * gvir_sandbox_config_add_env: + * @config: (transfer none): the sandbox config + * @dsk: (transfer none): the env configuration + * + * Adds a new environment variable to the sandbox + * + */ +void gvir_sandbox_config_add_env(GVirSandboxConfig *config, + GVirSandboxConfigEnv *env) +{ + GVirSandboxConfigPrivate *priv = config->priv; + + g_object_ref(env); + + priv->envs = g_list_append(priv->envs, env); +} + +/** + * gvir_sandbox_config_get_envs: + * @config: (transfer none): the sandbox config + * + * Retrieves the list of custom environment list in the sandbox + * + * Returns: (transfer full) (element-type GVirSandboxConfigMount): the list of environment variables + */ +GList *gvir_sandbox_config_get_envs(GVirSandboxConfig *config) +{ + GVirSandboxConfigPrivate *priv = config->priv; + g_list_foreach(priv->envs, (GFunc)g_object_ref, NULL); + return g_list_copy(priv->envs); +} + +/** + * gvir_sandbox_config_add_env_strv: + * @config: (transfer none): the sandbox config + * @envs: (transfer none)(array zero-terminated=1): the list of environment variables + * + * Parses @envs whose elements are in the format KEY=VALUE + * + * --env KEY=VALUE + */ +gboolean gvir_sandbox_config_add_env_strv(GVirSandboxConfig *config, + gchar **envs, + GError **error) +{ + gsize i = 0; + while (envs && envs[i]) { + if (!gvir_sandbox_config_add_env_opts(config, + envs[i], + error)) + return FALSE; + i++; + } + return TRUE; +} + +/** + * gvir_sandbox_config_add_env_opts: + * @config: (transfer none): the sandbox config + * @disk: (transfer none): the env config + * + * Parses @env in the format KEY=VALUE + * creating #GVirSandboxConfigEnv instances for each element. For + * example + * + * --env KEY=VALUE + */ + +gboolean gvir_sandbox_config_add_env_opts(GVirSandboxConfig *config, + const char *opt, + GError **error) +{ + gchar *tmp = NULL; + gchar *key = NULL; + gchar *value = NULL; + GVirSandboxConfigEnv *envConfig; + + if (!(tmp = g_strdup(opt))) + return FALSE; + + key = strchr(tmp, '='); + + if (!key) { + g_set_error(error, GVIR_SANDBOX_CONFIG_ERROR, 0, + _("Wrong environment format on %s"), opt); + return FALSE; + } + + *key = '\0'; + value = key + 1; + envConfig = GVIR_SANDBOX_CONFIG_ENV(g_object_new(GVIR_SANDBOX_TYPE_CONFIG_ENV, + "key", tmp, + "value", value, + NULL)); + + gvir_sandbox_config_add_env(config, envConfig); + + g_object_unref(envConfig); + g_free(tmp); + return TRUE; +} + +gboolean gvir_sandbox_config_has_envs(GVirSandboxConfig *config) +{ + GVirSandboxConfigPrivate *priv = config->priv; + return priv->envs != NULL; +}
/** * gvir_sandbox_config_add_disk: @@ -1163,7 +1275,6 @@ void gvir_sandbox_config_add_disk(GVirSandboxConfig *config, priv->disks = g_list_append(priv->disks, dsk); }
- /** * gvir_sandbox_config_get_disks: * @config: (transfer none): the sandbox config @@ -1949,6 +2060,44 @@ static GVirSandboxConfigMount *gvir_sandbox_config_load_config_mount(GKeyFile *f goto cleanup; }
+static GVirSandboxConfigEnv *gvir_sandbox_config_load_config_env(GKeyFile *file, + guint i, + GError **error) +{ + GVirSandboxConfigEnv *config = NULL; + gchar *index = NULL; + gchar *key = NULL; + gchar *value = NULL; + GError *e = NULL; + + index = g_strdup_printf("env.%u", i); + if ((key = g_key_file_get_string(file, index, "key", &e)) == NULL) { + if (e->code == G_KEY_FILE_ERROR_GROUP_NOT_FOUND) { + g_error_free(e); + return NULL; + } + g_error_free(e); + g_set_error(error, GVIR_SANDBOX_CONFIG_ERROR, 0, + "%s", _("Missing environment key in config file")); + goto cleanup; + } + if ((value = g_key_file_get_string(file, index, "value", NULL)) == NULL) { + g_set_error(error, GVIR_SANDBOX_CONFIG_ERROR, 0, + "%s", _("Missing environment value in config file")); + goto cleanup; + } + + config = GVIR_SANDBOX_CONFIG_ENV(g_object_new(GVIR_SANDBOX_TYPE_CONFIG_ENV, + "key", key, + "value", value, + NULL)); + + cleanup: + g_free(key); + g_free(value); + g_free(index); + return config; +}
static GVirSandboxConfigDisk *gvir_sandbox_config_load_config_disk(GKeyFile *file, guint i, @@ -2244,6 +2393,14 @@ static gboolean gvir_sandbox_config_load_config(GVirSandboxConfig *config, priv->mounts = g_list_append(priv->mounts, mount); }
+ for (i = 0 ; i < 1024 ; i++) { + GVirSandboxConfigEnv *env; + if (!(env = gvir_sandbox_config_load_config_env(file, i, error)) && + *error) + goto cleanup; + if (env) + priv->envs = g_list_append(priv->envs, env); + }
for (i = 0 ; i < 1024 ; i++) { GVirSandboxConfigDisk *disk; @@ -2274,6 +2431,24 @@ static gboolean gvir_sandbox_config_load_config(GVirSandboxConfig *config, return ret; }
+static void gvir_sandbox_config_save_config_env(GVirSandboxConfigEnv *config, + GKeyFile *file, + guint i) +{ + gchar *index = NULL; + gchar *key = NULL; + gchar *value = NULL; + + index = g_strdup_printf("env.%u", i); + key = g_strdup(gvir_sandbox_config_env_get_key(config)); + value = g_strdup(gvir_sandbox_config_env_get_value(config)); + g_key_file_set_string(file, index, "key",key); + g_key_file_set_string(file, index, "value",value); + + g_free(index); + g_free(key); + g_free(value); +}
static void gvir_sandbox_config_save_config_disk(GVirSandboxConfigDisk *config, GKeyFile *file, @@ -2485,6 +2660,16 @@ static void gvir_sandbox_config_save_config(GVirSandboxConfig *config, }
i = 0; + tmp = priv->envs; + while (tmp) { + gvir_sandbox_config_save_config_env(tmp->data, + file, + i); + tmp = tmp->next; + i++; + } + + i = 0; tmp = priv->disks; while (tmp) { gvir_sandbox_config_save_config_disk(tmp->data, diff --git a/libvirt-sandbox/libvirt-sandbox-config.h b/libvirt-sandbox/libvirt-sandbox-config.h index 2c5f0a6..d39bb2b 100644 --- a/libvirt-sandbox/libvirt-sandbox-config.h +++ b/libvirt-sandbox/libvirt-sandbox-config.h @@ -122,6 +122,18 @@ gboolean gvir_sandbox_config_add_network_strv(GVirSandboxConfig *config, GError **error); gboolean gvir_sandbox_config_has_networks(GVirSandboxConfig *config);
+void gvir_sandbox_config_add_env(GVirSandboxConfig *config, + GVirSandboxConfigEnv *env); +GList *gvir_sandbox_config_get_envs(GVirSandboxConfig *config); +gboolean gvir_sandbox_config_add_env_strv(GVirSandboxConfig *config, + gchar **envs, + GError **error); +gboolean gvir_sandbox_config_add_env_opts(GVirSandboxConfig *config, + const char *env, + GError **error); +gboolean gvir_sandbox_config_has_envs(GVirSandboxConfig *config); + + void gvir_sandbox_config_add_disk(GVirSandboxConfig *config, GVirSandboxConfigDisk *dsk); GList *gvir_sandbox_config_get_disks(GVirSandboxConfig *config); diff --git a/libvirt-sandbox/libvirt-sandbox.h b/libvirt-sandbox/libvirt-sandbox.h index 20dc871..05d7485 100644 --- a/libvirt-sandbox/libvirt-sandbox.h +++ b/libvirt-sandbox/libvirt-sandbox.h @@ -32,6 +32,7 @@ #include <libvirt-sandbox/libvirt-sandbox-util.h> #include <libvirt-sandbox/libvirt-sandbox-enum-types.h> #include <libvirt-sandbox/libvirt-sandbox-config-disk.h> +#include <libvirt-sandbox/libvirt-sandbox-config-env.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount-file.h> #include <libvirt-sandbox/libvirt-sandbox-config-mount-host-bind.h> diff --git a/libvirt-sandbox/libvirt-sandbox.sym b/libvirt-sandbox/libvirt-sandbox.sym index 6f8097a..a09a6c2 100644 --- a/libvirt-sandbox/libvirt-sandbox.sym +++ b/libvirt-sandbox/libvirt-sandbox.sym @@ -26,6 +26,12 @@ LIBVIRT_SANDBOX_0.6.0 { gvir_sandbox_config_disk_get_type; gvir_sandbox_config_has_disks;
+ gvir_sandbox_config_add_env; + gvir_sandbox_config_add_env_strv; + gvir_sandbox_config_add_env_opts; + gvir_sandbox_config_env_get_type; + gvir_sandbox_config_has_envs; + gvir_sandbox_config_mount_add_include; gvir_sandbox_config_mount_get_includes; gvir_sandbox_config_mount_get_target; -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

Allow users to add custom environment variables to their sandbox. --- bin/virt-sandbox.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/bin/virt-sandbox.c b/bin/virt-sandbox.c index 195515f..e90b698 100644 --- a/bin/virt-sandbox.c +++ b/bin/virt-sandbox.c @@ -64,6 +64,7 @@ int main(int argc, char **argv) { GError *error = NULL; gchar *name = NULL; gchar **disks = NULL; + gchar **envs = NULL; gchar **mounts = NULL; gchar **includes = NULL; gchar *includefile = NULL; @@ -95,6 +96,8 @@ int main(int argc, char **argv) { N_("root directory of the sandbox"), "DIR" }, { "disk", ' ', 0, G_OPTION_ARG_STRING_ARRAY, &disks, N_("add a disk in the guest"), "TYPE:TAGNAME=SOURCE,format=FORMAT" }, + { "env", 'e', 0, G_OPTION_ARG_STRING_ARRAY, &envs, + N_("add a environment variable for the sandbox"), "KEY=VALUE" }, { "mount", 'm', 0, G_OPTION_ARG_STRING_ARRAY, &mounts, N_("mount a filesystem in the guest"), "TYPE:TARGET=SOURCE" }, { "include", 'i', 0, G_OPTION_ARG_STRING_ARRAY, &includes, @@ -185,6 +188,13 @@ int main(int argc, char **argv) { gvir_sandbox_config_set_username(cfg, "root"); } + if (envs && + !gvir_sandbox_config_add_env_strv(cfg, envs, &error)) { + g_printerr(_("Unable to parse custom environment variables: %s\n"), + error && error->message ? error->message : _("Unknown failure")); + goto cleanup; + } + if (disks && !gvir_sandbox_config_add_disk_strv(cfg, disks, &error)) { g_printerr(_("Unable to parse disks: %s\n"), @@ -329,6 +339,10 @@ inheriting the host's root filesystem. NB. C<DIR> must contain a matching install of the libvirt-sandbox package. This restriction may be lifted in a future version. +=item B<--env key=value> + +Sets up a custom environment variable on a running sandbox. + =item B<--disk TYPE:TAGNAME=SOURCE,format=FORMAT> Sets up a disk inside the sandbox by using B<SOURCE> with a symlink named as B<TAGNAME> -- 2.1.0

Common-init reads config file and export custom environment variables from config file and apply them to the running sandbox. --- libvirt-sandbox/libvirt-sandbox-init-common.c | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/libvirt-sandbox/libvirt-sandbox-init-common.c b/libvirt-sandbox/libvirt-sandbox-init-common.c index d35f760..0b0aa98 100644 --- a/libvirt-sandbox/libvirt-sandbox-init-common.c +++ b/libvirt-sandbox/libvirt-sandbox-init-common.c @@ -336,6 +336,33 @@ static gboolean setup_network(GVirSandboxConfig *config, GError **error) } +static gboolean setup_custom_env(GVirSandboxConfig *config, GError **error) +{ + GList *envs, *tmp; + gboolean ret = FALSE; + gchar *key = NULL; + gchar *value = NULL; + + envs = tmp = gvir_sandbox_config_get_envs(config); + + while (tmp) { + GVirSandboxConfigEnv *env = GVIR_SANDBOX_CONFIG_ENV(tmp->data); + key = g_strdup(gvir_sandbox_config_env_get_key(env)); + value = g_strdup(gvir_sandbox_config_env_get_value(env)); + if(setenv(key,value,1)!=0) + goto cleanup; + g_free(key); + g_free(value); + tmp = tmp->next; + } + + ret = TRUE; + cleanup: + g_list_foreach(envs, (GFunc)g_object_unref, NULL); + g_list_free(envs); + return ret; +} + static int change_user(const gchar *user, uid_t uid, gid_t gid, @@ -1262,6 +1289,9 @@ int main(int argc, char **argv) { if (!setup_disk_tags()) exit(EXIT_FAILURE); + if (!setup_custom_env(config, &error)) + goto error; + if (!setup_network(config, &error)) goto error; -- 2.1.0

"make check" now includes testcase for environment variables --- libvirt-sandbox/tests/test-config.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libvirt-sandbox/tests/test-config.c b/libvirt-sandbox/tests/test-config.c index da05187..ac10bab 100644 --- a/libvirt-sandbox/tests/test-config.c +++ b/libvirt-sandbox/tests/test-config.c @@ -58,6 +58,13 @@ int main(int argc, char **argv) "host-bind:/tmp=", NULL }; + + const gchar *envs[] = { + "key1=val1", + "key2=val2", + NULL + }; + const gchar *disks[] = { "file:dbdata=/tmp/img.blah,format=qcow2", "file:cache=/tmp/img.qcow2", @@ -103,6 +110,9 @@ int main(int argc, char **argv) if (!gvir_sandbox_config_add_mount_strv(cfg1, (gchar**)mounts, &err)) goto cleanup; + if (!gvir_sandbox_config_add_env_strv(cfg1, (gchar**)envs, &err)) + goto cleanup; + if (!gvir_sandbox_config_add_disk_strv(cfg1, (gchar**)disks, &err)) goto cleanup; -- 2.1.0

Any custom key=value pair can be used as a custom environment variable in virt-sandbox-image. e.g virt-sandbox-image run ubuntu /var/lib/libvirt/templates -c lxc:/// -i /bin/bash -e key1=val1 --- virt-sandbox-image/sources/DockerSource.py | 10 ++++++++++ virt-sandbox-image/sources/Source.py | 4 ++++ virt-sandbox-image/virt-sandbox-image.py | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 44bc238..54b68b9 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -48,6 +48,12 @@ class DockerConfParser(): for key,value in volumes.iteritems(): volumelist.append(key) return volumelist + def getEnvs(self): + lst = self.json_data['container_config']['Env'] + if lst is not None and isinstance(lst,list): + return lst + else: + return [] class DockerSource(Source): default_index_server = "index.docker.io" @@ -411,5 +417,9 @@ class DockerSource(Source): configParser = DockerConfParser(configfile) return configParser.getVolumes() + def get_environment(self,configfile): + configParser = DockerConfParser(configfile) + return configParser.getEnvs() + def debug(msg): sys.stderr.write(msg) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index 6898c15..ad82986 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -53,3 +53,7 @@ class Source(): @abstractmethod def get_volume(self,**args): pass + + @abstractmethod + def get_env(self,**args): + pass diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index b12b99b..25c8dfa 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -118,10 +118,12 @@ def run(args): cmd.append("-c") cmd.append(args.connect) params = ['-m','host-image:/=%s,format=%s' %(diskfile,format)] + networkArgs = args.network if networkArgs is not None: params.append('-N') params.append(networkArgs) + allVolumes = source.get_volume(configfile) volumeArgs = args.volume if volumeArgs is not None: @@ -141,6 +143,20 @@ def run(args): pass params.append("--mount") params.append("host-bind:%s=%s" %(guestPath,hostPath)) + + allEnvs = source.get_environment(configfile) + envArgs = args.env + if envArgs is not None: + allEnvs = allEnvs + envArgs + for env in allEnvs: + envsplit = env.split("=") + envlen = len(envsplit) + if envlen == 2: + params.append("--env") + params.append(env) + else: + pass + params.append('--') params.append(commandToRun) cmd = cmd + params @@ -215,6 +231,9 @@ def gen_run_args(subparser): help=_("Network params for running template")) parser.add_argument("-v","--volume",action="append", help=_("Volume params for running template")) + parser.add_argument("-e","--env",action="append", + help=_("Environment params for running template")) + parser.set_defaults(func=run) if __name__ == '__main__': -- 2.1.0

On Tue, Aug 04, 2015 at 08:11:25PM +0000, Eren Yagdiran wrote:
Any custom key=value pair can be used as a custom environment variable in virt-sandbox-image. e.g virt-sandbox-image run ubuntu /var/lib/libvirt/templates -c lxc:/// -i /bin/bash -e key1=val1 --- virt-sandbox-image/sources/DockerSource.py | 10 ++++++++++ virt-sandbox-image/sources/Source.py | 4 ++++ virt-sandbox-image/virt-sandbox-image.py | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+)
diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 44bc238..54b68b9 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -48,6 +48,12 @@ class DockerConfParser(): for key,value in volumes.iteritems(): volumelist.append(key) return volumelist + def getEnvs(self): + lst = self.json_data['container_config']['Env'] + if lst is not None and isinstance(lst,list): + return lst + else: + return []
class DockerSource(Source): default_index_server = "index.docker.io" @@ -411,5 +417,9 @@ class DockerSource(Source): configParser = DockerConfParser(configfile) return configParser.getVolumes()
+ def get_environment(self,configfile): + configParser = DockerConfParser(configfile) + return configParser.getEnvs() + def debug(msg): sys.stderr.write(msg) diff --git a/virt-sandbox-image/sources/Source.py b/virt-sandbox-image/sources/Source.py index 6898c15..ad82986 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -53,3 +53,7 @@ class Source(): @abstractmethod def get_volume(self,**args): pass + + @abstractmethod + def get_env(self,**args):
Here you call it 'get_env' but in the DockerSource you call it 'get_environment'. This causes python to immediately exit saying that DockerSource doens't implement the API defined by Source. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (3)
-
Cedric Bosdonnat
-
Daniel P. Berrange
-
Eren Yagdiran