[libvirt] [sandbox PATCH v3 00/22] *** Virt-sandbox-image ***

V3 Changes: * License syntax fixed * Source abstract method get_env fixed * Discarding byte code generation now resides into a new commit * Template_dir and storage_dir refactored and runtime resolver is added for checking permissions * -f,--format parameter is refactored. Default is qcow2. * Ssl warning is now using stderr * get_disk method in Source now adds another layer with a randomized name Daniel P Berrange (1): Add virt-sandbox-image Eren Yagdiran (21): Fix virt-sandbox-image Image: Add Hooking Mechanism Image: virt-sandbox-image default dir constants Image: Discard caching bytecode Image: Add check_writable and runtime resolver 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 | 421 ++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 55 ++++ virt-sandbox-image/sources/__init__.py | 26 ++ virt-sandbox-image/virt-sandbox-image.py | 299 ++++++++++++++++++ 22 files changed, 1550 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

On Tue, Aug 18, 2015 at 06:53:22AM +0000, Eren Yagdiran wrote:
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
ACK 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 :|

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 | 27 +++++++++++++++++++++++++++ virt-sandbox-image/sources/__init__.py | 26 ++++++++++++++++++++++++++ virt-sandbox-image/virt-sandbox-image.py | 15 +++++++++------ 8 files changed, 93 insertions(+), 10 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..508ca80 --- /dev/null +++ b/virt-sandbox-image/sources/Source.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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> + +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..df8603b --- /dev/null +++ b/virt-sandbox-image/sources/__init__.py @@ -0,0 +1,26 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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> +# + +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..fa9e1c8 --- 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> # @@ -32,11 +32,14 @@ import sys import urllib2 import subprocess -default_index_server = "index.docker.io" -default_template_dir = "/var/lib/libvirt/templates" - -debug = True -verbose = 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") -- 2.1.0

Conflicts: virt-sandbox-image/virt-sandbox-image.py --- virt-sandbox-image/virt-sandbox-image.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index fa9e1c8..55aea6a 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -32,6 +32,14 @@ import sys import urllib2 import subprocess +default_privileged_template_dir = "/var/lib/libvirt/templates" +default_home_dir = os.environ['HOME'] +default_unprivileged_template_dir = default_home_dir + "/.local/share/libvirt/templates" +default_privileged_storage_dir = default_privileged_template_dir + "/storage" +default_unprivileged_storage_dir = default_unprivileged_template_dir + "/storage" +debug = False +verbose = False + import importlib def dynamic_source_loader(name): name = name[0].upper() + name[1:] -- 2.1.0

--- virt-sandbox-image/virt-sandbox-image.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 55aea6a..9e98bf2 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -40,6 +40,8 @@ default_unprivileged_storage_dir = default_unprivileged_template_dir + "/storage debug = False verbose = False +sys.dont_write_byte_code = True + import importlib def dynamic_source_loader(name): name = name[0].upper() + name[1:] -- 2.1.0

These helper functions are for selecting right directories according to running user privileges --- virt-sandbox-image/virt-sandbox-image.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 9e98bf2..5917dd6 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -32,6 +32,8 @@ import sys import urllib2 import subprocess +template_dir = None +storage_dir = None default_privileged_template_dir = "/var/lib/libvirt/templates" default_home_dir = os.environ['HOME'] default_unprivileged_template_dir = default_home_dir + "/.local/share/libvirt/templates" @@ -40,6 +42,29 @@ default_unprivileged_storage_dir = default_unprivileged_template_dir + "/storage debug = False verbose = False +def check_dir_writable(path): + if not os.access(path,os.W_OK): + return False + return True + +def runtime_dir_resolver(): + global default_privileged_template_dir + global default_privileged_storage_dir + global default_unprivileged_template_dir + global default_unprivileged_storage_dir + global template_dir + global storage_dir + if(check_dir_writable(default_privileged_template_dir)): + template_dir = default_privileged_template_dir + storage_dir = default_privileged_storage_dir + return + template_dir = default_unprivileged_template_dir + storage_dir = default_unprivileged_storage_dir + if not os.path.exists(template_dir): + os.makedirs(template_dir) + if not os.path.exists(storage_dir): + os.makedirs(storage_dir) + sys.dont_write_byte_code = True import importlib @@ -380,8 +405,8 @@ def gen_create_args(subparser): parser.set_defaults(func=create) if __name__ == '__main__': + runtime_dir_resolver() parser = argparse.ArgumentParser(description='Sandbox Container Image Tool') - subparser = parser.add_subparsers(help=_("commands")) gen_download_args(subparser) gen_delete_args(subparser) -- 2.1.0

On Tue, 2015-08-18 at 06:53 +0000, Eren Yagdiran wrote:
These helper functions are for selecting right directories according to running user privileges --- virt-sandbox-image/virt-sandbox-image.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-)
diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index 9e98bf2..5917dd6 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -32,6 +32,8 @@ import sys import urllib2 import subprocess
+template_dir = None +storage_dir = None default_privileged_template_dir = "/var/lib/libvirt/templates" default_home_dir = os.environ['HOME'] default_unprivileged_template_dir = default_home_dir + "/.local/share/libvirt/templates" @@ -40,6 +42,29 @@ default_unprivileged_storage_dir = default_unprivileged_template_dir + "/storage debug = False verbose = False
+def check_dir_writable(path): + if not os.access(path,os.W_OK): + return False + return True
Is that function really useful? Can't you just call os.access instead of it?
+def runtime_dir_resolver(): + global default_privileged_template_dir + global default_privileged_storage_dir + global default_unprivileged_template_dir + global default_unprivileged_storage_dir + global template_dir + global storage_dir + if(check_dir_writable(default_privileged_template_dir)):
Better use os.access(default_privileged_template_dir, os.W_OK) here -- Cedric
+ template_dir = default_privileged_template_dir + storage_dir = default_privileged_storage_dir + return + template_dir = default_unprivileged_template_dir + storage_dir = default_unprivileged_storage_dir + if not os.path.exists(template_dir): + os.makedirs(template_dir) + if not os.path.exists(storage_dir): + os.makedirs(storage_dir) + sys.dont_write_byte_code = True
import importlib @@ -380,8 +405,8 @@ def gen_create_args(subparser): parser.set_defaults(func=create)
if __name__ == '__main__': + runtime_dir_resolver() parser = argparse.ArgumentParser(description='Sandbox Container Image Tool') - subparser = parser.add_subparsers(help=_("commands")) gen_download_args(subparser) gen_delete_args(subparser)

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 | 214 +++++++++++++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 + virt-sandbox-image/virt-sandbox-image.py | 204 +++++---------------------- 4 files changed, 251 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..f3cf5f3 --- /dev/null +++ b/virt-sandbox-image/sources/DockerSource.py @@ -0,0 +1,214 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# 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> +# + +from Source import Source +import urllib2 +import sys +import json +import traceback +import os +import subprocess +import shutil + +class DockerSource(Source): + + www_auth_username = None + www_auth_password = None + + def __init__(self): + self.default_index_server = "index.docker.io" + + 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\n" + 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): + sys.stderr.write(SSL_WARNING) + + 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'] + 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() + 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 508ca80..8751689 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -25,3 +25,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 5917dd6..4f371d7 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -94,176 +94,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 = {} @@ -367,8 +197,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)) @@ -382,10 +220,32 @@ 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")) + +def requires_template_dir(parser): + global template_dir + parser.add_argument("-t","--template-dir", + default=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) + requires_template_dir(parser) parser.set_defaults(func=download) def gen_delete_args(subparser): -- 2.1.0

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 | 76 +++++----------------- 3 files changed, 121 insertions(+), 59 deletions(-) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index f3cf5f3..09eea85 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -210,5 +210,105 @@ class DockerSource(Source): debug("FAIL %s\n" % str(e)) raise + def create_template(self,**args): + name = args['name'] + connect = args['connect'] + templatedir = args['templatedir'] + format = args['format'] + format = format if format is not None else self.default_disk_format + + self._create_template(name, + connect, + templatedir, + format) + + def _create_template(self,name,connect,templatedir,format): + self._check_disk_format(format) + imagelist = self._get_image_list(name,templatedir) + imagelist.reverse() + + parentImage = None + for imagetagid in imagelist: + templateImage = templatedir + "/" + 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(templatedir + "/" + 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) + cmd.append("-p") + 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) + cmd.append("-p") + params = ['-m', + 'host-image:/mnt=%s,format=%s' %(diskfile,format), + '--', + '/bin/tar', + 'zxf', + '%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 8751689..6ac08dc 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -29,3 +29,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 4f371d7..745401c 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -143,59 +143,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, @@ -213,8 +160,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, + templatedir=args.template_dir, + format=args.format) + except Exception,e: + print "Create Error %s" % str(e) def requires_name(parser): parser.add_argument("name", @@ -225,6 +177,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")) @@ -258,10 +214,12 @@ 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")) + requires_source(parser) + requires_connect(parser) + requires_template_dir(parser) + parser.add_argument("-f","--format", + default="qcow2", + help=_("format format for image")) parser.set_defaults(func=create) if __name__ == '__main__': -- 2.1.0

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 | 58 ++++-------------------------- 3 files changed, 64 insertions(+), 51 deletions(-) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 09eea85..760ba6c 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -310,5 +310,58 @@ class DockerSource(Source): cmd = cmd + params subprocess.call(cmd) + def delete_template(self,**args): + imageusage = {} + imageparent = {} + imagenames = {} + name = args['name'] + destdir = args['templatedir'] + 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 6ac08dc..d66e61e 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -33,3 +33,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 745401c..1da5150 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -94,55 +94,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, @@ -156,8 +107,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, + templatedir=args.template_dir) + except Exception,e: + print "Delete Error %s", str(e) def create(args): try: @@ -208,6 +162,8 @@ def gen_delete_args(subparser): parser = subparser.add_parser("delete", help=_("Delete template data")) requires_name(parser) + requires_source(parser) + requires_template_dir(parser) 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 760ba6c..3e0362b 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -29,6 +29,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): www_auth_username = None @@ -363,5 +372,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 d66e61e..9daf62d 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -37,3 +37,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 1da5150..d6b682f 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -178,6 +178,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("-t","--template-dir", + help=_("Template directory for saving templates")) + parser.add_argument("-i","--igniter", + help=_("Igniter command for image")) + parser.set_defaults(func=run) + if __name__ == '__main__': runtime_dir_resolver() parser = argparse.ArgumentParser(description='Sandbox Container Image Tool') @@ -185,6 +197,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 d6b682f..c46abd4 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -122,6 +122,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 | 18 ++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 3e0362b..1e7f633 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -28,6 +28,8 @@ import traceback import os import subprocess import shutil +import random +import string class DockerConfParser(): @@ -372,6 +374,22 @@ class DockerSource(Source): parent = None imagetagid = parent + def get_disk(self,**args): + name = args['name'] + destdir = args['templatedir'] + imageList = self._get_image_list(name,destdir) + toplayer = imageList[0] + diskfile = destdir + "/" + toplayer + "/template.qcow2" + configfile = destdir + "/" + toplayer + "/template.json" + tempfile = ''.join(random.choice(string.lowercase) for i in range(10)) + tempfile = destdir + "/" + toplayer + "/" + tempfile + ".qcow2" + cmd = ["qemu-img","create","-q","-f","qcow2"] + cmd.append("-o") + cmd.append("backing_fmt=qcow2,backing_file=%s" % diskfile) + cmd.append(tempfile) + subprocess.call(cmd) + return (tempfile,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 9daf62d..9a3da59 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -41,3 +41,7 @@ class Source(): @abstractmethod def get_command(self,**args): pass + + @abstractmethod + def get_disk(self,**args): + pass -- 2.1.0

On Tue, 2015-08-18 at 06:53 +0000, Eren Yagdiran wrote:
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 | 18 ++++++++++++++++++ virt-sandbox-image/sources/Source.py | 4 ++++ 2 files changed, 22 insertions(+)
diff --git a/virt-sandbox-image/sources/DockerSource.py b/virt-sandbox-image/sources/DockerSource.py index 3e0362b..1e7f633 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -28,6 +28,8 @@ import traceback import os import subprocess import shutil +import random +import string
class DockerConfParser():
@@ -372,6 +374,22 @@ class DockerSource(Source): parent = None imagetagid = parent
+ def get_disk(self,**args): + name = args['name'] + destdir = args['templatedir'] + imageList = self._get_image_list(name,destdir) + toplayer = imageList[0] + diskfile = destdir + "/" + toplayer + "/template.qcow2" + configfile = destdir + "/" + toplayer + "/template.json" + tempfile = ''.join(random.choice(string.lowercase) for i in range(10))
This isn't really where the temporary disk should be created. I would let the get_disk function return the top-most image, and have the temporary disk created in the run function of virt-sandbox-image.py. More over the temporary disk image shouldn't be created next to the templates, but in the sandbox state directory. I wouldn't have it named randomly, but rather <sandbox-name>.qcow, where the <sandbox-name> would be computed based on the template name and an incremented counter. -- Cedric
+ tempfile = destdir + "/" + toplayer + "/" + tempfile + ".qcow2" + cmd = ["qemu-img","create","-q","-f","qcow2"] + cmd.append("-o") + cmd.append("backing_fmt=qcow2,backing_file=%s" % diskfile) + cmd.append(tempfile) + subprocess.call(cmd) + return (tempfile,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 9daf62d..9a3da59 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -41,3 +41,7 @@ class Source(): @abstractmethod def get_command(self,**args): pass + + @abstractmethod + def get_disk(self,**args): + pass

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 | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index c46abd4..278d19a 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -128,6 +128,31 @@ 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,templatedir=args.template_dir) + + 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) + subprocess.call(["rm", "-rf", diskfile]) + + 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, 2015-08-18 at 06:53 +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 | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+)
diff --git a/virt-sandbox-image/virt-sandbox-image.py b/virt-sandbox-image/virt-sandbox-image.py index c46abd4..278d19a 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -128,6 +128,31 @@ 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,templatedir=args.template_dir)
This is the place where the temporary disk file would be created. Don't forget it's a per-sandbox disk layer. Compute the sandbox name here, use it to create the disk layer in the sandbox state directory...
+ + format = "qcow2" + commandToRun = args.igniter + if commandToRun is None: + commandToRun = source.get_command(configfile) + cmd = ['virt-sandbox']
...and add --name parameter to virt-sandbox to define that name. -- Cedric
+ 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) + subprocess.call(["rm", "-rf", diskfile])
Remove the temporary disk layer and the sandbox state directory here. -- Cedric
+ except Exception,e: + print "Run Error %s" % str(e) + def requires_name(parser): parser.add_argument("name", help=_("name of the template"))

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 278d19a..3ce3a8b 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -143,9 +143,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) subprocess.call(["rm", "-rf", diskfile]) @@ -219,6 +223,8 @@ def gen_run_args(subparser): help=_("Template directory for saving templates")) 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 1e7f633..67bcf6b 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -30,6 +30,7 @@ import subprocess import shutil import random import string +import collections class DockerConfParser(): @@ -39,6 +40,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): @@ -395,5 +403,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 9a3da59..8cc508e 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -45,3 +45,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 3ce3a8b..e554d8a 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -130,6 +130,7 @@ def check_connect(connectstr): def run(args): try: + global storage_dir if args.connect is not None: check_connect(args.connect) source = dynamic_source_loader(args.source) @@ -148,6 +149,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 = storage_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 @@ -225,6 +245,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, 2015-08-18 at 06:53 +0000, Eren Yagdiran wrote:
Volumes let user to map host-paths into guest. Docker containers need volumes because its filesystem read-only by default.
Please reword that wrong commit message... docker file system isn't read-only by default. -- 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 1e7f633..67bcf6b 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -30,6 +30,7 @@ import subprocess import shutil import random import string +import collections
class DockerConfParser():
@@ -39,6 +40,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):
@@ -395,5 +403,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 9a3da59..8cc508e 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -45,3 +45,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 3ce3a8b..e554d8a 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -130,6 +130,7 @@ def check_connect(connectstr):
def run(args): try: + global storage_dir if args.connect is not None: check_connect(args.connect) source = dynamic_source_loader(args.source) @@ -148,6 +149,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 = storage_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 @@ -225,6 +245,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-18 at 06:53 +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; +};
Any reason for not having removed that class and used a GHashMap instead? -- Cedric
+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; -- 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 67bcf6b..c84b84e 100644 --- a/virt-sandbox-image/sources/DockerSource.py +++ b/virt-sandbox-image/sources/DockerSource.py @@ -47,6 +47,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): @@ -407,5 +413,9 @@ class DockerSource(Source): configParser = DockerConfParser(configfile) return configParser.getVolumes() + def get_env(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 8cc508e..c467ce2 100644 --- a/virt-sandbox-image/sources/Source.py +++ b/virt-sandbox-image/sources/Source.py @@ -49,3 +49,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 e554d8a..dacfa0c 100755 --- a/virt-sandbox-image/virt-sandbox-image.py +++ b/virt-sandbox-image/virt-sandbox-image.py @@ -145,10 +145,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: @@ -168,6 +170,20 @@ def run(args): pass params.append("--mount") params.append("host-bind:%s=%s" %(guestPath,hostPath)) + + allEnvs = source.get_env(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 @@ -247,6 +263,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
participants (3)
-
Cedric Bosdonnat
-
Daniel P. Berrange
-
Eren Yagdiran