[libvirt] [PATCH sandbox 0/1] split libvirt-sandbox project

I've been preparing todo a long overdue release of the libvirt-sandbox project. First of all I've already killed the virt-sandbox-service tool, since that is essentially a failed concept. It had bitrotted such that it didn't even work on a simple "httpd" systemd unit, let alone anything complex. Docker and/or systemd do this better themselves. I'm then also not entirely comfortable with having the virt-sandbox-image tool as a core part of the libvirt-sandbox repo. It is written entirely in python, and there is a simple one-way dependancy from virt-sandbox-image onto libvirt-sandbox, so there's no compelling reason to force them into the same repo. Separating them will let us release them at independant times. It will also let us use native python tools for build instead of automake which is never a very nice fit for python. The new repository is created using git filter-branch, so it has preserved all git history for the virt-sandbox-image source still. Daniel P. Berrangé (1): Delete virt-sandbox-image tool now in separate repo autobuild.sh | 3 +- bin/virt-sandbox-image | 8 - configure.ac | 4 - libvirt-sandbox.spec.in | 9 - libvirt-sandbox/image/Makefile.am | 10 - libvirt-sandbox/image/__init__.py | 0 libvirt-sandbox/image/cli.py | 282 ----------- libvirt-sandbox/image/sources/Makefile.am | 10 - libvirt-sandbox/image/sources/__init__.py | 0 libvirt-sandbox/image/sources/base.py | 160 ------- libvirt-sandbox/image/sources/docker.py | 690 --------------------------- libvirt-sandbox/image/sources/virtbuilder.py | 109 ----- libvirt-sandbox/image/template.py | 133 ------ m4/virt-win32.m4 | 5 - 14 files changed, 1 insertion(+), 1422 deletions(-) delete mode 100755 bin/virt-sandbox-image delete mode 100644 libvirt-sandbox/image/Makefile.am delete mode 100644 libvirt-sandbox/image/__init__.py delete mode 100644 libvirt-sandbox/image/cli.py delete mode 100644 libvirt-sandbox/image/sources/Makefile.am delete mode 100644 libvirt-sandbox/image/sources/__init__.py delete mode 100644 libvirt-sandbox/image/sources/base.py delete mode 100755 libvirt-sandbox/image/sources/docker.py delete mode 100755 libvirt-sandbox/image/sources/virtbuilder.py delete mode 100644 libvirt-sandbox/image/template.py -- 2.14.3

The virt-sandbox-image tool and its supporting code has been split into a separate libvirt-sandbox-image GIT repository. This allows its build system and distribution to work in the normal python way, and have a release lifecycle independent of the main libvirt-sandbox package. https://libvirt.org/git/?p=libvirt-sandbox-image.git;a=summary Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- autobuild.sh | 3 +- bin/virt-sandbox-image | 8 - configure.ac | 4 - libvirt-sandbox.spec.in | 9 - libvirt-sandbox/image/Makefile.am | 10 - libvirt-sandbox/image/__init__.py | 0 libvirt-sandbox/image/cli.py | 282 ----------- libvirt-sandbox/image/sources/Makefile.am | 10 - libvirt-sandbox/image/sources/__init__.py | 0 libvirt-sandbox/image/sources/base.py | 160 ------- libvirt-sandbox/image/sources/docker.py | 690 --------------------------- libvirt-sandbox/image/sources/virtbuilder.py | 109 ----- libvirt-sandbox/image/template.py | 133 ------ m4/virt-win32.m4 | 5 - 14 files changed, 1 insertion(+), 1422 deletions(-) delete mode 100755 bin/virt-sandbox-image delete mode 100644 libvirt-sandbox/image/Makefile.am delete mode 100644 libvirt-sandbox/image/__init__.py delete mode 100644 libvirt-sandbox/image/cli.py delete mode 100644 libvirt-sandbox/image/sources/Makefile.am delete mode 100644 libvirt-sandbox/image/sources/__init__.py delete mode 100644 libvirt-sandbox/image/sources/base.py delete mode 100755 libvirt-sandbox/image/sources/docker.py delete mode 100755 libvirt-sandbox/image/sources/virtbuilder.py delete mode 100644 libvirt-sandbox/image/template.py diff --git a/autobuild.sh b/autobuild.sh index 9cd4ca2..c176fea 100755 --- a/autobuild.sh +++ b/autobuild.sh @@ -54,8 +54,7 @@ if [ -x /usr/bin/i686-pc-mingw32-gcc ]; then ../configure \ --build=$(uname -m)-pc-linux \ --host=i686-pc-mingw32 \ - --prefix="$AUTOBUILD_INSTALL_ROOT/i686-pc-mingw32/sys-root/mingw" \ - --without-python + --prefix="$AUTOBUILD_INSTALL_ROOT/i686-pc-mingw32/sys-root/mingw" make make install diff --git a/bin/virt-sandbox-image b/bin/virt-sandbox-image deleted file mode 100755 index 61346ef..0000000 --- a/bin/virt-sandbox-image +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from libvirt_sandbox.image import cli -import sys - -if __name__ == '__main__': - sys.exit(cli.main()) diff --git a/configure.ac b/configure.ac index 28305d2..19d4523 100644 --- a/configure.ac +++ b/configure.ac @@ -130,13 +130,9 @@ 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([3]) - AC_OUTPUT(Makefile libvirt-sandbox/Makefile libvirt-sandbox/tests/Makefile - libvirt-sandbox/image/Makefile - libvirt-sandbox/image/sources/Makefile bin/Makefile examples/Makefile docs/Makefile diff --git a/libvirt-sandbox.spec.in b/libvirt-sandbox.spec.in index 125a361..0374d9e 100644 --- a/libvirt-sandbox.spec.in +++ b/libvirt-sandbox.spec.in @@ -36,14 +36,7 @@ BuildRequires: zlib-devel >= 1.2.0, zlib-static BuildRequires: libtirpc-devel BuildRequires: rpcgen %endif -Requires: python3-rpm -# For virsh lxc-enter-namespace command -Requires: libvirt-client >= %{libvirt_version} -Requires: systemd >= 198 -Requires: pygobject3-base -Requires: libselinux-python3 Requires: %{name}-libs = %{version}-%{release} -Requires: %{_bindir}/virt-builder %package libs Group: Development/Libraries @@ -102,8 +95,6 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_bindir}/virt-sandbox -%{_bindir}/virt-sandbox-image -%{python3_sitelib}/libvirt_sandbox %{_mandir}/man1/virt-sandbox.1* %files libs -f %{name}.lang diff --git a/libvirt-sandbox/image/Makefile.am b/libvirt-sandbox/image/Makefile.am deleted file mode 100644 index 60afbf3..0000000 --- a/libvirt-sandbox/image/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ - -SUBDIRS = sources - -pythonsandboxdir = $(pythondir)/libvirt_sandbox -pythonsandbox_DATA = __init__.py - -pythonimagedir = $(pythondir)/libvirt_sandbox/image -pythonimage_DATA = __init__.py cli.py template.py - -EXTRA_DIST = $(pythonimage_DATA) diff --git a/libvirt-sandbox/image/__init__.py b/libvirt-sandbox/image/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/libvirt-sandbox/image/cli.py b/libvirt-sandbox/image/cli.py deleted file mode 100644 index 605183c..0000000 --- a/libvirt-sandbox/image/cli.py +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# Authors: Daniel P. Berrange <berrange@redhat.com> -# Eren Yagdiran <erenyagdiran@gmail.com> -# -# Copyright (C) 2013-2015 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 -# 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 re -import shutil -import sys -import subprocess -import random -import string - -from libvirt_sandbox.image import template - -if os.geteuid() == 0: - default_template_dir = "/var/lib/libvirt/templates" - default_image_dir = "/var/lib/libvirt/images" -else: - default_template_dir = os.environ['HOME'] + "/.local/share/libvirt/templates" - default_image_dir = os.environ['HOME'] + "/.local/share/libvirt/images" - -debug = False -verbose = False - -gettext.bindtextdomain("libvirt-sandbox", "/usr/share/locale") -gettext.textdomain("libvirt-sandbox") -try: - gettext.install("libvirt-sandbox", - localedir="/usr/share/locale", - 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_template_dir(args): - tmpl = template.Template.from_uri(args.template) - return "%s/%s" % (args.template_dir, tmpl.source) - -def purge(args): - tmpl = template.Template.from_uri(args.template) - source = tmpl.get_source_impl() - source.delete_template(template=tmpl, - templatedir=get_template_dir(args)) - -def prepare(args): - tmpl = template.Template.from_uri(args.template) - source = tmpl.get_source_impl() - source.create_template(template=tmpl, - templatedir=get_template_dir(args), - connect=args.connect) - -def random_domain_name(tmpl): - randomid = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) - return re.sub('[^a-z0-9-]', '_', tmpl.path[1:], re.I) + ":" + randomid - -def run(args): - if args.connect is not None: - check_connect(args.connect) - - tmpl = template.Template.from_uri(args.template) - source = tmpl.get_source_impl() - template_dir = get_template_dir(args) - - # Create the template image if needed - if not source.has_template(tmpl, template_dir): - prepare(args) - - name = args.name - if name is None: - name = random_domain_name(tmpl) - - diskfile = source.get_disk(template=tmpl, - templatedir=template_dir, - imagedir=args.image_dir, - sandboxname=name) - - commandToRun = source.get_command(tmpl, template_dir, args.args) - if len(commandToRun) == 0: - commandToRun = ["/bin/sh"] - cmd = ['virt-sandbox', '--name', name] - if args.connect is not None: - cmd.append("-c") - cmd.append(args.connect) - params = ['-m','host-image:/=%s,format=qcow2' % diskfile] - - networkArgs = args.network - if networkArgs is not None: - params.append('-N') - params.append(networkArgs) - - allEnvs = source.get_env(tmpl, template_dir) - 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 - - cmd = cmd + params + ['--'] + commandToRun - subprocess.call(cmd) - os.unlink(diskfile) - source.post_run(tmpl, template_dir, name) - -def list_cached(args): - tmpls = [] - if args.source is not None: - tmpls.extend(template.Template.get_all(args.source, - "%s/%s" % (args.template_dir, args.source))) - else: - for source in ["docker", "virt-builder"]: - tmpls.extend(template.Template.get_all(source, - "%s/%s" % (args.template_dir, source))) - for tmpl in tmpls: - print (tmpl) - -def requires_template(parser): - parser.add_argument("template", - help=_("URI of the template")) - -def requires_name(parser): - parser.add_argument("-n","--name", - help=_("Name of the running sandbox")) - -def requires_debug(parser): - parser.add_argument("-d","--debug", - default=False, action="store_true", - help=_("Run in debug mode")) - -def check_connect(connectstr): - supportedDrivers = ['lxc:///','qemu:///session','qemu:///system'] - if not connectstr in supportedDrivers: - raise ValueError("URI '%s' is not supported by virt-sandbox-image" % connectstr) - return True - -def requires_connect(parser): - parser.add_argument("-c","--connect", - help=_("Connect string for libvirt")) - -def requires_template_dir(parser): - global default_template_dir - parser.add_argument("-t","--template-dir", - default=default_template_dir, - help=_("Template directory for saving templates")) - -def requires_image_dir(parser): - global default_image_dir - parser.add_argument("-I","--image-dir", - default=default_image_dir, - help=_("Image directory for saving images")) - -def gen_command_parser(subparser, name, helptext): - parser = subparser.add_parser( - name, help=helptext, - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" - -Example supported URI formats: - - docker:///ubuntu?tag=15.04 - docker://username:password@index.docker.io/private/image - docker://registry.access.redhat.com/rhel6 - virt-builder:///fedora-20 -""") - return parser - -def gen_purge_args(subparser): - parser = gen_command_parser(subparser, "purge", - _("Purge cached template")) - requires_template(parser) - requires_template_dir(parser) - parser.set_defaults(func=purge) - -def gen_prepare_args(subparser): - parser = gen_command_parser(subparser, "prepare", - _("Prepare local template")) - requires_template(parser) - requires_connect(parser) - requires_template_dir(parser) - parser.set_defaults(func=prepare) - -def gen_run_args(subparser): - parser = gen_command_parser(subparser, "run", - _("Run an instance of a template")) - requires_name(parser) - requires_template(parser) - requires_connect(parser) - requires_template_dir(parser) - requires_image_dir(parser) - parser.add_argument("args", - nargs=argparse.REMAINDER, - help=_("command arguments to run")) - parser.add_argument("-N","--network", - help=_("Network params for running template")) - parser.add_argument("-e","--env",action="append", - help=_("Environment params for running template")) - - parser.set_defaults(func=run) - -def gen_list_args(subparser): - parser = gen_command_parser(subparser, "list", - _("List locally cached images")) - requires_template_dir(parser) - - parser.add_argument("-s","--source", - help=_("Name of the template source")) - - parser.set_defaults(func=list_cached) - -def main(): - parser = argparse.ArgumentParser(description="Sandbox Container Image Tool") - - requires_debug(parser) - - subparser = parser.add_subparsers(help=_("commands")) - subparser.required = True - subparser.dest = "command" - gen_purge_args(subparser) - gen_prepare_args(subparser) - gen_run_args(subparser) - gen_list_args(subparser) - - args = parser.parse_args() - if args.debug: - args.func(args) - sys.exit(0) - else: - try: - args.func(args) - sys.exit(0) - except KeyboardInterrupt as e: - sys.exit(0) - except ValueError as e: - sys.stderr.write("%s: %s\n" % (sys.argv[0], e)) - sys.stderr.flush() - sys.exit(1) - except IOError as e: - sys.stderr.write("%s: %s\n" % (sys.argv[0], e.filename)) - sys.stderr.flush() - sys.exit(1) - except OSError as e: - sys.stderr.write("%s: %s\n" % (sys.argv[0], e)) - sys.stderr.flush() - sys.exit(1) - except Exception as e: - print (e) - sys.exit(1) diff --git a/libvirt-sandbox/image/sources/Makefile.am b/libvirt-sandbox/image/sources/Makefile.am deleted file mode 100644 index 817baa0..0000000 --- a/libvirt-sandbox/image/sources/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ - -pythonimagedir = $(pythondir)/libvirt_sandbox/image/sources -pythonimage_DATA = \ - __init__.py \ - base.py \ - docker.py \ - virtbuilder.py \ - $(NULL) - -EXTRA_DIST = $(pythonimage_DATA) diff --git a/libvirt-sandbox/image/sources/__init__.py b/libvirt-sandbox/image/sources/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/libvirt-sandbox/image/sources/base.py b/libvirt-sandbox/image/sources/base.py deleted file mode 100644 index 0fc9243..0000000 --- a/libvirt-sandbox/image/sources/base.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 Universitat Politècnica de Catalunya. -# Copyright (C) 2015 Red Hat, Inc. -# -# 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 -import subprocess - -class Source(): - '''The Source class defines the base interface for - all image provider source implementations. An image - provide source is able to download templates from - a repository, convert them to a host specific image - format and report commands used to invoke them.''' - - __metaclass__ = ABCMeta - def __init__(self): - pass - - @abstractmethod - def list_templates(self, templatedir): - """ - :param templatedir: local directory path in which to store the template - - Get a list of all templates that are locally cached - - :returns: a list of libvirt_sandbox.template.Template objects - """ - pass - - @abstractmethod - def has_template(self, template, templatedir): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to store the template - - Check if a template has already been created. - """ - pass - - @abstractmethod - def create_template(self, template, templatedir, - connect=None): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to store the template - :param connect: libvirt connection URI - - Create a set of local disk images populated with the content - of a template. The images creation process will be isolated - inside a sandbox using the requested libvirt connection URI. - """ - pass - - @abstractmethod - def delete_template(self, template, templatedir): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path from which to delete template - - Delete all local files associated with the template - """ - pass - - @abstractmethod - def get_command(self, template, templatedir, userargs): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which templates are stored - :param userargs: user specified arguments to run - - Get the command line to invoke in the container. If userargs - is specified, then this should override the default args in - the image""" - pass - - @abstractmethod - def get_disk(self, template, templatedir, imagedir, sandboxname): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to find template - :param imagedir: local directory in which to storage disk image - - Creates an instance private disk image, backed by the content - of a template. - """ - pass - - @abstractmethod - def get_env(self, template, templatedir): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to find template - - Get the dict of environment variables to set - """ - pass - - def post_run(self, template, templatedir, imagename): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to find template - :param imagename: name of the image that just stopped running - - Hook called after the image has been stopped. By default is doesn't - do anything, subclasses can override this to do some additional - cleanup. - """ - pass - - - # Utility functions to share between the sources. - - 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.check_call(cmd) - - def extract_tarball(self, diskfile, format, tarfile, connect): - cmd = ['virt-sandbox'] - if connect is not None: - cmd.append("-c") - cmd.append(connect) - cmd.append("-p") - compression = "" - if tarfile.endswith(".gz"): - compression = "z" - params = ['-m', - 'host-image:/mnt=%s,format=%s' % (diskfile, format), - '--', - '/bin/tar', - 'xf%s' % compression, - '%s' % tarfile, - '-C', - '/mnt'] - cmd = cmd + params - subprocess.check_call(cmd) diff --git a/libvirt-sandbox/image/sources/docker.py b/libvirt-sandbox/image/sources/docker.py deleted file mode 100755 index eaf41fc..0000000 --- a/libvirt-sandbox/image/sources/docker.py +++ /dev/null @@ -1,690 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 Universitat Politècnica de Catalunya. -# Copyright (C) 2015 Red Hat, Inc. -# -# 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 sys -import json -import traceback -import os -import subprocess -import shutil -import urllib.error -import urllib.parse -import urllib.request -import hashlib -from abc import ABCMeta, abstractmethod -import copy -from libvirt_sandbox.image.template import Template - -from . import base - -class DockerConfParser(): - - def __init__(self,jsonfile): - with open(jsonfile) as json_file: - self.json_data = json.load(json_file) - def getCommand(self): - return self.json_data['config']['Cmd'] - def getEntrypoint(self): - return self.json_data['config']['Entrypoint'] - def getEnvs(self): - lst = self.json_data['config']['Env'] - if lst is not None and isinstance(lst,list): - return lst - else: - return [] - -class DockerImage(): - - def __init__(self, repo, name, tag=None): - - self.repo = repo - self.name = name - self.tag = tag - - if self.tag is None: - self.tag = "latest" - - if self.repo is None: - self.repo = "library" - - def __repr__(self): - return "%s/%s,tag=%s" % (self.repo, self.name, self.tag) - - @classmethod - def from_template(cls, template): - bits = template.path[1:].split("/") - if len(bits) == 1: - return cls(repo=None, - name=bits[0], - tag=template.params.get("tag")) - elif len(bits) == 2: - return cls(repo=bits[0], - name=bits[1], - tag=template.params.get("tag")) - else: - raise Exception("Expected image name, or repo & image name for path, not '%s'", - template.path) - - -class DockerAuth(): - - __metaclass__ = ABCMeta - def __init__(self): - pass - - @abstractmethod - def prepare_req(self, req): - pass - - @abstractmethod - def process_res(self, res): - pass - - @abstractmethod - def process_err(self, err): - return False - - -class DockerAuthNop(DockerAuth): - - def prepare_req(self, req): - pass - - def process_res(self, res): - pass - - def process_err(self, err): - return False - - -class DockerAuthBasic(DockerAuth): - - def __init__(self, username, password): - self.username = username - self.password = password - self.token = None - - def prepare_req(self, req): - if self.username is not None: - auth = base64.encodestring( - '%s:%s' % (self.username, self.password)).replace('\n', '') - - req.add_header("Authorization", "Basic %s" % auth) - - req.add_header("X-Docker-Token", "true") - - def process_res(self, res): - self.token = res.info().get('X-Docker-Token') - - def process_err(self, err): - return False - - -class DockerAuthToken(DockerAuth): - - def __init__(self, token): - self.token = token - - def prepare_req(self, req): - req.add_header("Authorization", "Token %s" % self.token) - - def process_res(self, res): - pass - - def process_err(self, err): - return False - - -class DockerAuthBearer(DockerAuth): - - def __init__(self): - self.token = None - - def prepare_req(self, req): - if self.token is not None: - req.add_header("Authorization", "Bearer %s" % self.token) - - def process_res(self, res): - pass - - def process_err(self, err): - method = err.headers.get("WWW-Authenticate", None) - if method is None: - return False - - if not method.startswith("Bearer "): - return False - - challenge = method[7:] - - bits = challenge.split(",") - attrs = {} - for bit in bits: - subbit = bit.split("=") - attrs[subbit[0]] = subbit[1][1:-1] - - url = attrs["realm"] - del attrs["realm"] - if "error" in attrs: - del attrs["error"] - - params = "&".join([ - "%s=%s" % (attr, attrs[attr]) - for attr in attrs.keys() - ]) - if params != "": - url = url + "?" + params - - req = urllib.request.Request(url=url) - req.add_header("Accept", "application/json") - - res = urllib.request.urlopen(req) - data = json.loads(res.read()) - self.token = data["token"] - return True - - -class DockerRegistry(): - - def __init__(self, uri_base): - - self.uri_base = list(urllib.parse.urlparse(uri_base)) - self.auth_handler = DockerAuthNop() - - def set_auth_handler(self, auth_handler): - self.auth_handler = auth_handler - - def supports_v2(self): - try: - (data, res) = self.get_json("/v2/") - ver = res.info().get("Docker-Distribution-Api-Version") - except urllib.error.HTTPError as e: - ver = e.headers.get("Docker-Distribution-Api-Version", None) - - if ver is None: - return False - return ver.startswith("registry/2") - - def set_server(self, server): - self.uri_base[1] = server - - @classmethod - def from_template(cls, template): - protocol = template.protocol - hostname = template.hostname - port = template.port - - if protocol is None: - protocol = "https" - if hostname is None: - hostname = "index.docker.io" - - if port is None: - server = hostname - else: - server = "%s:%s" % (hostname, port) - - url = urllib.parse.urlunparse((protocol, server, "", None, None, None)) - - return cls(url) - - def get_url(self, path, headers=None): - url_bits = copy.copy(self.uri_base) - url_bits[2] = path - url = urllib.parse.urlunparse(url_bits) - debug("Fetching %s..." % url) - - req = urllib.request.Request(url=url) - - if headers is not None: - for h in headers.keys(): - req.add_header(h, headers[h]) - - self.auth_handler.prepare_req(req) - - try: - res = urllib.request.urlopen(req) - self.auth_handler.process_res(res) - return res - except urllib.error.HTTPError as e: - if e.code == 401: - retry = self.auth_handler.process_err(e) - if retry: - debug("Re-Fetching %s..." % url) - self.auth_handler.prepare_req(req) - res = urllib.request.urlopen(req) - self.auth_handler.process_res(res) - return res - else: - debug("Not re-fetching") - raise - else: - raise - - def save_data(self, path, dest, checksum=None): - try: - res = self.get_url(path) - - datalen = res.info().get("Content-Length") - if datalen is not None: - datalen = int(datalen) - - csum = None - if checksum is not None: - csum = hashlib.sha256() - - pattern = [".", "o", "O", "o"] - patternIndex = 0 - donelen = 0 - - with open(dest, "wb") 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 as e: - debug("FAIL %s\n" % str(e)) - raise - - def get_json(self, path): - try: - headers = {} - headers["Accept"] = "application/json" - res = self.get_url(path, headers) - data = json.loads(res.read()) - debug("OK\n") - return (data, res) - except Exception as e: - debug("FAIL %s\n" % str(e)) - raise - - -class DockerSource(base.Source): - - 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 _was_downloaded(self, image, templatedir): - try: - self._get_image_list(image, templatedir) - return True - except Exception: - return False - - def list_templates(self, templatedir): - indexes = [] - try: - imagedirs = os.listdir(templatedir) - except OSError: - return [] - - for imagetagid in imagedirs: - indexfile = templatedir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile,"r") as f: - index = json.load(f) - indexes.append(index) - - return [ - Template(source="docker", - protocol=None, - hostname=None, - port=None, - username=None, - password=None, - path="/%s/%s" % (index.get("repo", "library"), index["name"]), - params={ - "tag": index.get("tag", "latest"), - }) for index in indexes] - - def has_template(self, template, templatedir): - try: - image = DockerImage.from_template(template) - configfile, diskfile = self._get_template_data(image, templatedir) - return os.path.exists(diskfile) - except Exception: - return False - - def download_template(self, image, template, templatedir): - try: - createdFiles = [] - createdDirs = [] - - self._download_template_impl(image, template, templatedir, createdFiles, createdDirs) - except Exception as e: - for f in createdFiles: - try: - os.remove(f) - except: - pass - for d in createdDirs: - try: - shutil.rmtree(d) - except: - pass - raise - - def _download_template_impl(self, image, template, templatedir, createdFiles, createdDirs): - self._check_cert_validate() - - registry = DockerRegistry.from_template(template) - registry.set_auth_handler(DockerAuthBearer()) - if registry.supports_v2(): - self._download_template_impl_v2(registry, image, template, templatedir, createdFiles, createdDirs) - else: - self._download_template_impl_v1(registry, image, template, templatedir, createdFiles, createdDirs) - - def _download_template_impl_v1(self, registry, image, template, templatedir, createdFiles, createdDirs): - basicauth = DockerAuthBasic(template.username, template.password) - registry.set_auth_handler(basicauth) - try: - (data, res) = registry.get_json("/v1/repositories/%s/%s/images" % ( - image.repo, image.name, - )) - except urllib.error.HTTPError as e: - raise ValueError(["Image '%s' does not exist" % template]) - - registryendpoint = res.info().get('X-Docker-Endpoints') - - if basicauth.token is not None: - registry.set_auth_handler(DockerAuthToken(basicauth.token)) - else: - registry.set_auth_handler(DockerAuthNop()) - - (data, res) = registry.get_json("/v1/repositories/%s/%s/tags" %( - image.repo, image.name - )) - - if image.tag not in data: - raise ValueError(["Tag '%s' does not exist for image '%s'" % - (image.tag, template)]) - imagetagid = data[image.tag] - - (data, res) = registry.get_json("/v1/images/" + imagetagid + "/ancestry") - - if data[0] != imagetagid: - raise ValueError(["Expected first layer id '%s' to match image id '%s'", - data[0], imagetagid]) - - for layerid in data: - layerdir = templatedir + "/" + layerid - if not os.path.exists(layerdir): - os.makedirs(layerdir) - createdDirs.append(layerdir) - - jsonfile = layerdir + "/template.json" - datafile = layerdir + "/template.tar.gz" - - if not os.path.exists(jsonfile) or not os.path.exists(datafile): - res = registry.save_data("/v1/images/" + layerid + "/json", - jsonfile) - createdFiles.append(jsonfile) - - registry.save_data("/v1/images/" + layerid + "/layer", - datafile) - createdFiles.append(datafile) - - index = { - "repo": image.repo, - "name": image.name, - "tag": image.tag, - } - - indexfile = templatedir + "/" + imagetagid + "/index.json" - with open(indexfile, "w") as f: - f.write(json.dumps(index)) - - - def _download_template_impl_v2(self, registry, image, template, templatedir, createdFiles, createdDirs): - (manifest, res) = registry.get_json( "/v2/%s/%s/manifests/%s" % ( - image.repo, image.name, image.tag)) - - layerChecksums = [ - layer["blobSum"] for layer in manifest["fsLayers"] - ] - layers = [ - json.loads(entry["v1Compatibility"]) for entry in manifest["history"] - ] - - for i in range(len(layerChecksums)): - layerChecksum = layerChecksums[i] - config = layers[i] - - layerdir = templatedir + "/" + config["id"] - if not os.path.exists(layerdir): - os.makedirs(layerdir) - createdDirs.append(layerdir) - - jsonfile = layerdir + "/template.json" - datafile = layerdir + "/template.tar.gz" - - with open(jsonfile, "w") as fh: - fh.write(json.dumps(config)) - - registry.save_data("/v2/%s/%s/blobs/%s" % ( - image.repo, image.name, layerChecksum), - datafile, checksum=layerChecksum) - - - index = { - "repo": image.repo, - "name": image.name, - "tag": image.tag, - } - - indexfile = templatedir + "/" + layers[0]["id"] + "/index.json" - with open(indexfile, "w") as f: - f.write(json.dumps(index)) - - - def create_template(self, template, templatedir, connect=None): - image = DockerImage.from_template(template) - - if not self._was_downloaded(image, templatedir): - self.download_template(image, template, templatedir) - - imagelist = self._get_image_list(image, templatedir) - imagelist.reverse() - - parentImage = None - for imagetagid in imagelist: - templateImage = templatedir + "/" + imagetagid + "/template.qcow2" - 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.check_call(cmd) - - if parentImage is None: - self.format_disk(templateImage, "qcow2", connect) - - path = templatedir + "/" + imagetagid + "/template." - self.extract_tarball(path + "qcow2", - "qcow2", - path + "tar.gz", - connect) - parentImage = templateImage - - def _get_image_list(self, image, templatedir): - imageparent = {} - imagenames = {} - imagedirs = [] - try: - imagedirs = os.listdir(templatedir) - except OSError: - pass - for imagetagid in imagedirs: - indexfile = templatedir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile,"r") as f: - index = json.load(f) - thisimage = DockerImage(index.get("repo"), - index["name"], - index.get("tag")) - imagenames[str(thisimage)] = imagetagid - jsonfile = templatedir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - with open(jsonfile,"r") as f: - data = json.load(f) - parent = data.get("parent",None) - if parent: - imageparent[imagetagid] = parent - if str(image) not in imagenames: - raise ValueError(["Image %s does not exist locally" % image]) - imagetagid = imagenames[str(image)] - imagelist = [] - while imagetagid != None: - imagelist.append(imagetagid) - parent = imageparent.get(imagetagid,None) - imagetagid = parent - return imagelist - - def delete_template(self, template, templatedir): - image = DockerImage.from_template(template) - - imageusage = {} - imageparent = {} - imagenames = {} - imagedirs = [] - try: - imagedirs = os.listdir(templatedir) - except OSError: - pass - for imagetagid in imagedirs: - indexfile = templatedir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile,"r") as f: - index = json.load(f) - thisimage = DockerImage(index.get("repo"), - index["name"], - index.get("tag")) - imagenames[str(thisimage)] = imagetagid - jsonfile = templatedir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - with open(jsonfile,"r") as f: - data = json.load(f) - - parent = data.get("parent",None) - if parent: - if parent not in imageusage: - imageusage[parent] = [] - imageusage[parent].append(imagetagid) - imageparent[imagetagid] = parent - - - if str(image) not in imagenames: - raise ValueError(["Image %s does not exist locally" % image]) - imagetagid = imagenames[str(image)] - while imagetagid != None: - debug("Remove %s\n" % imagetagid) - parent = imageparent.get(imagetagid,None) - - indexfile = templatedir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - os.remove(indexfile) - jsonfile = templatedir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - os.remove(jsonfile) - datafile = templatedir + "/" + imagetagid + "/template.tar.gz" - if os.path.exists(datafile): - os.remove(datafile) - imagedir = templatedir + "/" + imagetagid - shutil.rmtree(imagedir) - - if parent: - if len(imageusage[parent]) != 1: - debug("Parent %s is shared\n" % parent) - parent = None - imagetagid = parent - - def _get_template_data(self, image, templatedir): - imageList = self._get_image_list(image, templatedir) - toplayer = imageList[0] - diskfile = templatedir + "/" + toplayer + "/template.qcow2" - configfile = templatedir + "/" + toplayer + "/template.json" - return configfile, diskfile - - def get_disk(self, template, templatedir, imagedir, sandboxname): - image = DockerImage.from_template(template) - configfile, diskfile = self._get_template_data(image, templatedir) - tempfile = imagedir + "/" + sandboxname.split('/')[-1] + ".qcow2" - if not os.path.exists(imagedir): - os.makedirs(imagedir) - cmd = ["qemu-img","create","-q","-f","qcow2"] - cmd.append("-o") - cmd.append("backing_fmt=qcow2,backing_file=%s" % diskfile) - cmd.append(tempfile) - subprocess.check_call(cmd) - return tempfile - - def get_command(self, template, templatedir, userargs): - image = DockerImage.from_template(template) - configfile, diskfile = self._get_template_data(image, templatedir) - configParser = DockerConfParser(configfile) - cmd = configParser.getCommand() - entrypoint = configParser.getEntrypoint() - if entrypoint is None: - entrypoint = [] - if cmd is None: - cmd = [] - if userargs is not None and len(userargs) > 0: - return entrypoint + userargs - else: - return entrypoint + cmd - - def get_env(self, template, templatedir): - image = DockerImage.from_template(template) - configfile, diskfile = self._get_template_data(image, templatedir) - configParser = DockerConfParser(configfile) - return configParser.getEnvs() - -def debug(msg): - sys.stderr.write(msg) diff --git a/libvirt-sandbox/image/sources/virtbuilder.py b/libvirt-sandbox/image/sources/virtbuilder.py deleted file mode 100755 index 1b32083..0000000 --- a/libvirt-sandbox/image/sources/virtbuilder.py +++ /dev/null @@ -1,109 +0,0 @@ -# -# Copyright (C) 2015 SUSE LLC -# -# 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: Cedric Bosdonnat <cbosdonnat@suse.com> -# - -import os -import os.path -import subprocess - -from . import base -from libvirt_sandbox.image.template import Template - - -class VirtBuilderSource(base.Source): - - def _get_template_name(self, template): - # We shouldn't have '/' in the names, but let's make sure - # nobody can try to alter the folders structure later. - return template.path[1:].replace('/', '_') - - def has_template(self, template, templatedir): - imagepath = "%s/%s.qcow2" % (templatedir, template.path) - return os.path.exists(imagepath) - - def create_template(self, template, templatedir, connect=None): - if not os.path.exists(templatedir): - os.makedirs(templatedir) - - # Get the image using virt-builder - templatename = self._get_template_name(template) - imagepath_original = "%s/%s-original.qcow2" % (templatedir, templatename) - imagepath = "%s/%s.qcow2" % (templatedir, templatename) - cmd = ["virt-builder", templatename, "--no-network", - "-o", imagepath_original, "--format", "qcow2"] - subprocess.check_call(cmd) - - try: - # We need to convert this image into a single partition one. - tarfile = "%s/%s.tar" % (templatedir, templatename) - cmd = ["virt-tar-out", "-a", imagepath_original, "/", tarfile] - subprocess.check_call(cmd) - - cmd = ["qemu-img", "create", "-q", "-f", "qcow2", imagepath, "10G"] - subprocess.check_call(cmd) - - self.format_disk(imagepath, "qcow2", connect) - self.extract_tarball(imagepath, "qcow2", tarfile, connect) - - finally: - os.unlink(imagepath_original) - os.unlink(tarfile) - - def list_templates(self, templatedir): - files = [] - try: - imagefiles = os.listdir(templatedir) - except OSError: - return [] - - for filename in imagefiles: - if not filename.endswith(".qcow2"): - continue - files.append(filename[0:-6]) - - return [ - Template(source="virt-builder", - protocol=None, - hostname=None, - port=None, - username=None, - password=None, - path="/%s" % filename, - params={}) for filename in files] - - def delete_template(self, template, templatedir): - os.unlink("%s/%s.qcow2" % (templatedir, self._get_template_name(template))) - - def get_command(self, template, templatedir, userargs): - return userargs - - def get_disk(self,template, templatedir, imagedir, sandboxname): - diskfile = "%s/%s.qcow2" % (templatedir, self._get_template_name(template)) - tempfile = imagedir + "/" + sandboxname + ".qcow2" - if not os.path.exists(imagedir): - os.makedirs(imagedir) - cmd = ["qemu-img", "create", "-q", - "-f", "qcow2", - "-o", "backing_fmt=qcow2,backing_file=%s" % diskfile, - tempfile] - subprocess.check_call(cmd) - return tempfile - - def get_env(self,template, templatedir): - return [] diff --git a/libvirt-sandbox/image/template.py b/libvirt-sandbox/image/template.py deleted file mode 100644 index ab2ea29..0000000 --- a/libvirt-sandbox/image/template.py +++ /dev/null @@ -1,133 +0,0 @@ -# -# -*- coding: utf-8 -*- -# Authors: Daniel P. Berrange <berrange@redhat.com> -# -# Copyright (C) 2015 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 urllib.parse -import importlib -import re - -class Template(object): - - def __init__(self, - source, protocol, - hostname, port, - username, password, - path, params): - """ - :param source: template source name - :param protocol: network transport protocol or None - :param hostname: registry hostname or None - :param port: registry port or None - :param username: username or None - :param password: password or None - :param path: template path identifier - :param params: template parameters - - docker:///ubuntu - - docker+https://index.docker.io/ubuntu?tag=latest - - virt-builder:///fedora-20 - """ - - self.source = source - self.protocol = protocol - self.hostname = hostname - self.port = port - self.username = username - self.password = password - self.path = path - self.params = params - if self.params is None: - self.params = {} - - @classmethod - def _get_source_impl(klass, source): - try: - p = re.compile("\W") - sourcemod = "".join(p.split(source)) - sourcename = "".join([i.capitalize() for i in p.split(source)]) - - mod = importlib.import_module( - "libvirt_sandbox.image.sources." + sourcemod) - classname = sourcename + "Source" - classimpl = getattr(mod, classname) - return classimpl() - except Exception as e: - print (e) - raise Exception("Invalid source: '%s'" % source) - - def get_source_impl(self): - if self.source == "": - raise Exception("Missing scheme in image URI") - - return self._get_source_impl(self.source) - - def __repr__(self): - if self.protocol is not None: - scheme = self.source + "+" + self.protocol - else: - scheme = self.source - if self.hostname: - if self.port: - netloc = "%s:%d" % (self.hostname, self.port) - else: - netloc = self.hostname - - if self.username: - if self.password: - auth = self.username + ":" + self.password - else: - auth = self.username - netloc = auth + "@" + netloc - else: - netloc = None - - query = "&".join([key + "=" + self.params[key] for key in self.params.keys()]) - ret = urllib.parse.urlunparse((scheme, netloc, self.path, None, query, None)) - return ret - - @classmethod - def from_uri(klass, uri): - o = urllib.parse.urlparse(uri) - - idx = o.scheme.find("+") - if idx == -1: - source = o.scheme - protocol = None - else: - source = o.scheme[0:idx] - protocol = o.scheme[idx + 1:] - - query = {} - if o.query is not None and o.query != "": - for param in o.query.split("&"): - (key, val) = param.split("=") - query[key] = val - return klass(source, protocol, - o.hostname, o.port, - o.username, o.password, - o.path, query) - - @classmethod - def get_all(klass, source, templatedir): - impl = klass._get_source_impl(source) - - return impl.list_templates(templatedir) diff --git a/m4/virt-win32.m4 b/m4/virt-win32.m4 index d088ce8..8b908e7 100644 --- a/m4/virt-win32.m4 +++ b/m4/virt-win32.m4 @@ -4,15 +4,11 @@ AC_DEFUN([LIBVIRT_SANDBOX_WIN32],[ dnl for now since I'm not supporting mingw at present. - RWMJ CYGWIN_EXTRA_LDFLAGS= CYGWIN_EXTRA_LIBADD= - CYGWIN_EXTRA_PYTHON_LIBADD= MINGW_EXTRA_LDFLAGS= case "$host" in *-*-cygwin*) CYGWIN_EXTRA_LDFLAGS="-no-undefined" CYGWIN_EXTRA_LIBADD="${INTLLIBS}" - if test "x$PYTHON_VERSION" != "x"; then - CYGWIN_EXTRA_PYTHON_LIBADD="-L/usr/lib/python${PYTHON_VERSION}/config -lpython${PYTHON_VERSION}" - fi ;; *-*-mingw*) MINGW_EXTRA_LDFLAGS="-no-undefined" @@ -20,6 +16,5 @@ AC_DEFUN([LIBVIRT_SANDBOX_WIN32],[ esac AC_SUBST([CYGWIN_EXTRA_LDFLAGS]) AC_SUBST([CYGWIN_EXTRA_LIBADD]) - AC_SUBST([CYGWIN_EXTRA_PYTHON_LIBADD]) AC_SUBST([MINGW_EXTRA_LDFLAGS]) ]) -- 2.14.3

On Wed, 2018-04-18 at 17:10 +0100, Daniel P. Berrangé wrote:
The virt-sandbox-image tool and its supporting code has been split into a separate libvirt-sandbox-image GIT repository. This allows its build system and distribution to work in the normal python way, and have a release lifecycle independent of the main libvirt-sandbox package.
https://libvirt.org/git/?p=libvirt-sandbox-image.git;a=summary
ACK -- Cedric
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com> --- autobuild.sh | 3 +- bin/virt-sandbox-image | 8 - configure.ac | 4 - libvirt-sandbox.spec.in | 9 - libvirt-sandbox/image/Makefile.am | 10 - libvirt-sandbox/image/__init__.py | 0 libvirt-sandbox/image/cli.py | 282 ----------- libvirt-sandbox/image/sources/Makefile.am | 10 - libvirt-sandbox/image/sources/__init__.py | 0 libvirt-sandbox/image/sources/base.py | 160 ------- libvirt-sandbox/image/sources/docker.py | 690 --------------------------- libvirt-sandbox/image/sources/virtbuilder.py | 109 ----- libvirt-sandbox/image/template.py | 133 ------ m4/virt-win32.m4 | 5 - 14 files changed, 1 insertion(+), 1422 deletions(-) delete mode 100755 bin/virt-sandbox-image delete mode 100644 libvirt-sandbox/image/Makefile.am delete mode 100644 libvirt-sandbox/image/__init__.py delete mode 100644 libvirt-sandbox/image/cli.py delete mode 100644 libvirt-sandbox/image/sources/Makefile.am delete mode 100644 libvirt-sandbox/image/sources/__init__.py delete mode 100644 libvirt-sandbox/image/sources/base.py delete mode 100755 libvirt-sandbox/image/sources/docker.py delete mode 100755 libvirt-sandbox/image/sources/virtbuilder.py delete mode 100644 libvirt-sandbox/image/template.py
diff --git a/autobuild.sh b/autobuild.sh index 9cd4ca2..c176fea 100755 --- a/autobuild.sh +++ b/autobuild.sh @@ -54,8 +54,7 @@ if [ -x /usr/bin/i686-pc-mingw32-gcc ]; then ../configure \ --build=$(uname -m)-pc-linux \ --host=i686-pc-mingw32 \ - --prefix="$AUTOBUILD_INSTALL_ROOT/i686-pc-mingw32/sys-root/mingw" \ - --without-python + --prefix="$AUTOBUILD_INSTALL_ROOT/i686-pc-mingw32/sys-root/mingw"
make make install diff --git a/bin/virt-sandbox-image b/bin/virt-sandbox-image deleted file mode 100755 index 61346ef..0000000 --- a/bin/virt-sandbox-image +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from libvirt_sandbox.image import cli -import sys - -if __name__ == '__main__': - sys.exit(cli.main()) diff --git a/configure.ac b/configure.ac index 28305d2..19d4523 100644 --- a/configure.ac +++ b/configure.ac @@ -130,13 +130,9 @@ 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([3]) - AC_OUTPUT(Makefile libvirt-sandbox/Makefile libvirt-sandbox/tests/Makefile - libvirt-sandbox/image/Makefile - libvirt-sandbox/image/sources/Makefile bin/Makefile examples/Makefile docs/Makefile diff --git a/libvirt-sandbox.spec.in b/libvirt-sandbox.spec.in index 125a361..0374d9e 100644 --- a/libvirt-sandbox.spec.in +++ b/libvirt-sandbox.spec.in @@ -36,14 +36,7 @@ BuildRequires: zlib-devel >= 1.2.0, zlib-static BuildRequires: libtirpc-devel BuildRequires: rpcgen %endif -Requires: python3-rpm -# For virsh lxc-enter-namespace command -Requires: libvirt-client >= %{libvirt_version} -Requires: systemd >= 198 -Requires: pygobject3-base -Requires: libselinux-python3 Requires: %{name}-libs = %{version}-%{release} -Requires: %{_bindir}/virt-builder
%package libs Group: Development/Libraries @@ -102,8 +95,6 @@ rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root,-) %{_bindir}/virt-sandbox -%{_bindir}/virt-sandbox-image -%{python3_sitelib}/libvirt_sandbox %{_mandir}/man1/virt-sandbox.1*
%files libs -f %{name}.lang diff --git a/libvirt-sandbox/image/Makefile.am b/libvirt-sandbox/image/Makefile.am deleted file mode 100644 index 60afbf3..0000000 --- a/libvirt-sandbox/image/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ - -SUBDIRS = sources - -pythonsandboxdir = $(pythondir)/libvirt_sandbox -pythonsandbox_DATA = __init__.py - -pythonimagedir = $(pythondir)/libvirt_sandbox/image -pythonimage_DATA = __init__.py cli.py template.py - -EXTRA_DIST = $(pythonimage_DATA) diff --git a/libvirt-sandbox/image/__init__.py b/libvirt-sandbox/image/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/libvirt-sandbox/image/cli.py b/libvirt-sandbox/image/cli.py deleted file mode 100644 index 605183c..0000000 --- a/libvirt-sandbox/image/cli.py +++ /dev/null @@ -1,282 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# Authors: Daniel P. Berrange <berrange@redhat.com> -# Eren Yagdiran <erenyagdiran@gmail.com> -# -# Copyright (C) 2013-2015 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 -# 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 re -import shutil -import sys -import subprocess -import random -import string - -from libvirt_sandbox.image import template - -if os.geteuid() == 0: - default_template_dir = "/var/lib/libvirt/templates" - default_image_dir = "/var/lib/libvirt/images" -else: - default_template_dir = os.environ['HOME'] + "/.local/share/libvirt/templates" - default_image_dir = os.environ['HOME'] + "/.local/share/libvirt/images" - -debug = False -verbose = False - -gettext.bindtextdomain("libvirt-sandbox", "/usr/share/locale") -gettext.textdomain("libvirt-sandbox") -try: - gettext.install("libvirt-sandbox", - localedir="/usr/share/locale", - 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_template_dir(args): - tmpl = template.Template.from_uri(args.template) - return "%s/%s" % (args.template_dir, tmpl.source) - -def purge(args): - tmpl = template.Template.from_uri(args.template) - source = tmpl.get_source_impl() - source.delete_template(template=tmpl, - templatedir=get_template_dir(args)) - -def prepare(args): - tmpl = template.Template.from_uri(args.template) - source = tmpl.get_source_impl() - source.create_template(template=tmpl, - templatedir=get_template_dir(args), - connect=args.connect) - -def random_domain_name(tmpl): - randomid = ''.join(random.choice(string.ascii_lowercase) for i in range(10)) - return re.sub('[^a-z0-9-]', '_', tmpl.path[1:], re.I) + ":" + randomid - -def run(args): - if args.connect is not None: - check_connect(args.connect) - - tmpl = template.Template.from_uri(args.template) - source = tmpl.get_source_impl() - template_dir = get_template_dir(args) - - # Create the template image if needed - if not source.has_template(tmpl, template_dir): - prepare(args) - - name = args.name - if name is None: - name = random_domain_name(tmpl) - - diskfile = source.get_disk(template=tmpl, - templatedir=template_dir, - imagedir=args.image_dir, - sandboxname=name) - - commandToRun = source.get_command(tmpl, template_dir, args.args) - if len(commandToRun) == 0: - commandToRun = ["/bin/sh"] - cmd = ['virt-sandbox', '--name', name] - if args.connect is not None: - cmd.append("-c") - cmd.append(args.connect) - params = ['-m','host-image:/=%s,format=qcow2' % diskfile] - - networkArgs = args.network - if networkArgs is not None: - params.append('-N') - params.append(networkArgs) - - allEnvs = source.get_env(tmpl, template_dir) - 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 - - cmd = cmd + params + ['--'] + commandToRun - subprocess.call(cmd) - os.unlink(diskfile) - source.post_run(tmpl, template_dir, name) - -def list_cached(args): - tmpls = [] - if args.source is not None: - tmpls.extend(template.Template.get_all(args.source, - "%s/%s" % (args.template_dir, args.source))) - else: - for source in ["docker", "virt-builder"]: - tmpls.extend(template.Template.get_all(source, - "%s/%s" % (args.template_dir, source))) - for tmpl in tmpls: - print (tmpl) - -def requires_template(parser): - parser.add_argument("template", - help=_("URI of the template")) - -def requires_name(parser): - parser.add_argument("-n","--name", - help=_("Name of the running sandbox")) - -def requires_debug(parser): - parser.add_argument("-d","--debug", - default=False, action="store_true", - help=_("Run in debug mode")) - -def check_connect(connectstr): - supportedDrivers = ['lxc:///','qemu:///session','qemu:///system'] - if not connectstr in supportedDrivers: - raise ValueError("URI '%s' is not supported by virt-sandbox-image" % connectstr) - return True - -def requires_connect(parser): - parser.add_argument("-c","--connect", - help=_("Connect string for libvirt")) - -def requires_template_dir(parser): - global default_template_dir - parser.add_argument("-t","--template-dir", - default=default_template_dir, - help=_("Template directory for saving templates")) - -def requires_image_dir(parser): - global default_image_dir - parser.add_argument("-I","--image-dir", - default=default_image_dir, - help=_("Image directory for saving images")) - -def gen_command_parser(subparser, name, helptext): - parser = subparser.add_parser( - name, help=helptext, - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" - -Example supported URI formats: - - docker:///ubuntu?tag=15.04 - docker://username:password@index.docker.io/private/image - docker://registry.access.redhat.com/rhel6 - virt-builder:///fedora-20 -""") - return parser - -def gen_purge_args(subparser): - parser = gen_command_parser(subparser, "purge", - _("Purge cached template")) - requires_template(parser) - requires_template_dir(parser) - parser.set_defaults(func=purge) - -def gen_prepare_args(subparser): - parser = gen_command_parser(subparser, "prepare", - _("Prepare local template")) - requires_template(parser) - requires_connect(parser) - requires_template_dir(parser) - parser.set_defaults(func=prepare) - -def gen_run_args(subparser): - parser = gen_command_parser(subparser, "run", - _("Run an instance of a template")) - requires_name(parser) - requires_template(parser) - requires_connect(parser) - requires_template_dir(parser) - requires_image_dir(parser) - parser.add_argument("args", - nargs=argparse.REMAINDER, - help=_("command arguments to run")) - parser.add_argument("-N","--network", - help=_("Network params for running template")) - parser.add_argument("-e","--env",action="append", - help=_("Environment params for running template")) - - parser.set_defaults(func=run) - -def gen_list_args(subparser): - parser = gen_command_parser(subparser, "list", - _("List locally cached images")) - requires_template_dir(parser) - - parser.add_argument("-s","--source", - help=_("Name of the template source")) - - parser.set_defaults(func=list_cached) - -def main(): - parser = argparse.ArgumentParser(description="Sandbox Container Image Tool") - - requires_debug(parser) - - subparser = parser.add_subparsers(help=_("commands")) - subparser.required = True - subparser.dest = "command" - gen_purge_args(subparser) - gen_prepare_args(subparser) - gen_run_args(subparser) - gen_list_args(subparser) - - args = parser.parse_args() - if args.debug: - args.func(args) - sys.exit(0) - else: - try: - args.func(args) - sys.exit(0) - except KeyboardInterrupt as e: - sys.exit(0) - except ValueError as e: - sys.stderr.write("%s: %s\n" % (sys.argv[0], e)) - sys.stderr.flush() - sys.exit(1) - except IOError as e: - sys.stderr.write("%s: %s\n" % (sys.argv[0], e.filename)) - sys.stderr.flush() - sys.exit(1) - except OSError as e: - sys.stderr.write("%s: %s\n" % (sys.argv[0], e)) - sys.stderr.flush() - sys.exit(1) - except Exception as e: - print (e) - sys.exit(1) diff --git a/libvirt-sandbox/image/sources/Makefile.am b/libvirt-sandbox/image/sources/Makefile.am deleted file mode 100644 index 817baa0..0000000 --- a/libvirt-sandbox/image/sources/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ - -pythonimagedir = $(pythondir)/libvirt_sandbox/image/sources -pythonimage_DATA = \ - __init__.py \ - base.py \ - docker.py \ - virtbuilder.py \ - $(NULL) - -EXTRA_DIST = $(pythonimage_DATA) diff --git a/libvirt-sandbox/image/sources/__init__.py b/libvirt-sandbox/image/sources/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/libvirt-sandbox/image/sources/base.py b/libvirt-sandbox/image/sources/base.py deleted file mode 100644 index 0fc9243..0000000 --- a/libvirt-sandbox/image/sources/base.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 Universitat Politècnica de Catalunya. -# Copyright (C) 2015 Red Hat, Inc. -# -# 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 -import subprocess - -class Source(): - '''The Source class defines the base interface for - all image provider source implementations. An image - provide source is able to download templates from - a repository, convert them to a host specific image - format and report commands used to invoke them.''' - - __metaclass__ = ABCMeta - def __init__(self): - pass - - @abstractmethod - def list_templates(self, templatedir): - """ - :param templatedir: local directory path in which to store the template - - Get a list of all templates that are locally cached - - :returns: a list of libvirt_sandbox.template.Template objects - """ - pass - - @abstractmethod - def has_template(self, template, templatedir): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to store the template - - Check if a template has already been created. - """ - pass - - @abstractmethod - def create_template(self, template, templatedir, - connect=None): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to store the template - :param connect: libvirt connection URI - - Create a set of local disk images populated with the content - of a template. The images creation process will be isolated - inside a sandbox using the requested libvirt connection URI. - """ - pass - - @abstractmethod - def delete_template(self, template, templatedir): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path from which to delete template - - Delete all local files associated with the template - """ - pass - - @abstractmethod - def get_command(self, template, templatedir, userargs): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which templates are stored - :param userargs: user specified arguments to run - - Get the command line to invoke in the container. If userargs - is specified, then this should override the default args in - the image""" - pass - - @abstractmethod - def get_disk(self, template, templatedir, imagedir, sandboxname): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to find template - :param imagedir: local directory in which to storage disk image - - Creates an instance private disk image, backed by the content - of a template. - """ - pass - - @abstractmethod - def get_env(self, template, templatedir): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to find template - - Get the dict of environment variables to set - """ - pass - - def post_run(self, template, templatedir, imagename): - """ - :param template: libvirt_sandbox.template.Template object - :param templatedir: local directory path in which to find template - :param imagename: name of the image that just stopped running - - Hook called after the image has been stopped. By default is doesn't - do anything, subclasses can override this to do some additional - cleanup. - """ - pass - - - # Utility functions to share between the sources. - - 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.check_call(cmd) - - def extract_tarball(self, diskfile, format, tarfile, connect): - cmd = ['virt-sandbox'] - if connect is not None: - cmd.append("-c") - cmd.append(connect) - cmd.append("-p") - compression = "" - if tarfile.endswith(".gz"): - compression = "z" - params = ['-m', - 'host-image:/mnt=%s,format=%s' % (diskfile, format), - '--', - '/bin/tar', - 'xf%s' % compression, - '%s' % tarfile, - '-C', - '/mnt'] - cmd = cmd + params - subprocess.check_call(cmd) diff --git a/libvirt-sandbox/image/sources/docker.py b/libvirt-sandbox/image/sources/docker.py deleted file mode 100755 index eaf41fc..0000000 --- a/libvirt-sandbox/image/sources/docker.py +++ /dev/null @@ -1,690 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (C) 2015 Universitat Politècnica de Catalunya. -# Copyright (C) 2015 Red Hat, Inc. -# -# 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 sys -import json -import traceback -import os -import subprocess -import shutil -import urllib.error -import urllib.parse -import urllib.request -import hashlib -from abc import ABCMeta, abstractmethod -import copy -from libvirt_sandbox.image.template import Template - -from . import base - -class DockerConfParser(): - - def __init__(self,jsonfile): - with open(jsonfile) as json_file: - self.json_data = json.load(json_file) - def getCommand(self): - return self.json_data['config']['Cmd'] - def getEntrypoint(self): - return self.json_data['config']['Entrypoint'] - def getEnvs(self): - lst = self.json_data['config']['Env'] - if lst is not None and isinstance(lst,list): - return lst - else: - return [] - -class DockerImage(): - - def __init__(self, repo, name, tag=None): - - self.repo = repo - self.name = name - self.tag = tag - - if self.tag is None: - self.tag = "latest" - - if self.repo is None: - self.repo = "library" - - def __repr__(self): - return "%s/%s,tag=%s" % (self.repo, self.name, self.tag) - - @classmethod - def from_template(cls, template): - bits = template.path[1:].split("/") - if len(bits) == 1: - return cls(repo=None, - name=bits[0], - tag=template.params.get("tag")) - elif len(bits) == 2: - return cls(repo=bits[0], - name=bits[1], - tag=template.params.get("tag")) - else: - raise Exception("Expected image name, or repo & image name for path, not '%s'", - template.path) - - -class DockerAuth(): - - __metaclass__ = ABCMeta - def __init__(self): - pass - - @abstractmethod - def prepare_req(self, req): - pass - - @abstractmethod - def process_res(self, res): - pass - - @abstractmethod - def process_err(self, err): - return False - - -class DockerAuthNop(DockerAuth): - - def prepare_req(self, req): - pass - - def process_res(self, res): - pass - - def process_err(self, err): - return False - - -class DockerAuthBasic(DockerAuth): - - def __init__(self, username, password): - self.username = username - self.password = password - self.token = None - - def prepare_req(self, req): - if self.username is not None: - auth = base64.encodestring( - '%s:%s' % (self.username, self.password)).replace('\n', '') - - req.add_header("Authorization", "Basic %s" % auth) - - req.add_header("X-Docker-Token", "true") - - def process_res(self, res): - self.token = res.info().get('X-Docker-Token') - - def process_err(self, err): - return False - - -class DockerAuthToken(DockerAuth): - - def __init__(self, token): - self.token = token - - def prepare_req(self, req): - req.add_header("Authorization", "Token %s" % self.token) - - def process_res(self, res): - pass - - def process_err(self, err): - return False - - -class DockerAuthBearer(DockerAuth): - - def __init__(self): - self.token = None - - def prepare_req(self, req): - if self.token is not None: - req.add_header("Authorization", "Bearer %s" % self.token) - - def process_res(self, res): - pass - - def process_err(self, err): - method = err.headers.get("WWW-Authenticate", None) - if method is None: - return False - - if not method.startswith("Bearer "): - return False - - challenge = method[7:] - - bits = challenge.split(",") - attrs = {} - for bit in bits: - subbit = bit.split("=") - attrs[subbit[0]] = subbit[1][1:-1] - - url = attrs["realm"] - del attrs["realm"] - if "error" in attrs: - del attrs["error"] - - params = "&".join([ - "%s=%s" % (attr, attrs[attr]) - for attr in attrs.keys() - ]) - if params != "": - url = url + "?" + params - - req = urllib.request.Request(url=url) - req.add_header("Accept", "application/json") - - res = urllib.request.urlopen(req) - data = json.loads(res.read()) - self.token = data["token"] - return True - - -class DockerRegistry(): - - def __init__(self, uri_base): - - self.uri_base = list(urllib.parse.urlparse(uri_base)) - self.auth_handler = DockerAuthNop() - - def set_auth_handler(self, auth_handler): - self.auth_handler = auth_handler - - def supports_v2(self): - try: - (data, res) = self.get_json("/v2/") - ver = res.info().get("Docker-Distribution-Api-Version") - except urllib.error.HTTPError as e: - ver = e.headers.get("Docker-Distribution-Api-Version", None) - - if ver is None: - return False - return ver.startswith("registry/2") - - def set_server(self, server): - self.uri_base[1] = server - - @classmethod - def from_template(cls, template): - protocol = template.protocol - hostname = template.hostname - port = template.port - - if protocol is None: - protocol = "https" - if hostname is None: - hostname = "index.docker.io" - - if port is None: - server = hostname - else: - server = "%s:%s" % (hostname, port) - - url = urllib.parse.urlunparse((protocol, server, "", None, None, None)) - - return cls(url) - - def get_url(self, path, headers=None): - url_bits = copy.copy(self.uri_base) - url_bits[2] = path - url = urllib.parse.urlunparse(url_bits) - debug("Fetching %s..." % url) - - req = urllib.request.Request(url=url) - - if headers is not None: - for h in headers.keys(): - req.add_header(h, headers[h]) - - self.auth_handler.prepare_req(req) - - try: - res = urllib.request.urlopen(req) - self.auth_handler.process_res(res) - return res - except urllib.error.HTTPError as e: - if e.code == 401: - retry = self.auth_handler.process_err(e) - if retry: - debug("Re-Fetching %s..." % url) - self.auth_handler.prepare_req(req) - res = urllib.request.urlopen(req) - self.auth_handler.process_res(res) - return res - else: - debug("Not re-fetching") - raise - else: - raise - - def save_data(self, path, dest, checksum=None): - try: - res = self.get_url(path) - - datalen = res.info().get("Content-Length") - if datalen is not None: - datalen = int(datalen) - - csum = None - if checksum is not None: - csum = hashlib.sha256() - - pattern = [".", "o", "O", "o"] - patternIndex = 0 - donelen = 0 - - with open(dest, "wb") 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 as e: - debug("FAIL %s\n" % str(e)) - raise - - def get_json(self, path): - try: - headers = {} - headers["Accept"] = "application/json" - res = self.get_url(path, headers) - data = json.loads(res.read()) - debug("OK\n") - return (data, res) - except Exception as e: - debug("FAIL %s\n" % str(e)) - raise - - -class DockerSource(base.Source): - - 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 _was_downloaded(self, image, templatedir): - try: - self._get_image_list(image, templatedir) - return True - except Exception: - return False - - def list_templates(self, templatedir): - indexes = [] - try: - imagedirs = os.listdir(templatedir) - except OSError: - return [] - - for imagetagid in imagedirs: - indexfile = templatedir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile,"r") as f: - index = json.load(f) - indexes.append(index) - - return [ - Template(source="docker", - protocol=None, - hostname=None, - port=None, - username=None, - password=None, - path="/%s/%s" % (index.get("repo", "library"), index["name"]), - params={ - "tag": index.get("tag", "latest"), - }) for index in indexes] - - def has_template(self, template, templatedir): - try: - image = DockerImage.from_template(template) - configfile, diskfile = self._get_template_data(image, templatedir) - return os.path.exists(diskfile) - except Exception: - return False - - def download_template(self, image, template, templatedir): - try: - createdFiles = [] - createdDirs = [] - - self._download_template_impl(image, template, templatedir, createdFiles, createdDirs) - except Exception as e: - for f in createdFiles: - try: - os.remove(f) - except: - pass - for d in createdDirs: - try: - shutil.rmtree(d) - except: - pass - raise - - def _download_template_impl(self, image, template, templatedir, createdFiles, createdDirs): - self._check_cert_validate() - - registry = DockerRegistry.from_template(template) - registry.set_auth_handler(DockerAuthBearer()) - if registry.supports_v2(): - self._download_template_impl_v2(registry, image, template, templatedir, createdFiles, createdDirs) - else: - self._download_template_impl_v1(registry, image, template, templatedir, createdFiles, createdDirs) - - def _download_template_impl_v1(self, registry, image, template, templatedir, createdFiles, createdDirs): - basicauth = DockerAuthBasic(template.username, template.password) - registry.set_auth_handler(basicauth) - try: - (data, res) = registry.get_json("/v1/repositories/%s/%s/images" % ( - image.repo, image.name, - )) - except urllib.error.HTTPError as e: - raise ValueError(["Image '%s' does not exist" % template]) - - registryendpoint = res.info().get('X-Docker-Endpoints') - - if basicauth.token is not None: - registry.set_auth_handler(DockerAuthToken(basicauth.token)) - else: - registry.set_auth_handler(DockerAuthNop()) - - (data, res) = registry.get_json("/v1/repositories/%s/%s/tags" %( - image.repo, image.name - )) - - if image.tag not in data: - raise ValueError(["Tag '%s' does not exist for image '%s'" % - (image.tag, template)]) - imagetagid = data[image.tag] - - (data, res) = registry.get_json("/v1/images/" + imagetagid + "/ancestry") - - if data[0] != imagetagid: - raise ValueError(["Expected first layer id '%s' to match image id '%s'", - data[0], imagetagid]) - - for layerid in data: - layerdir = templatedir + "/" + layerid - if not os.path.exists(layerdir): - os.makedirs(layerdir) - createdDirs.append(layerdir) - - jsonfile = layerdir + "/template.json" - datafile = layerdir + "/template.tar.gz" - - if not os.path.exists(jsonfile) or not os.path.exists(datafile): - res = registry.save_data("/v1/images/" + layerid + "/json", - jsonfile) - createdFiles.append(jsonfile) - - registry.save_data("/v1/images/" + layerid + "/layer", - datafile) - createdFiles.append(datafile) - - index = { - "repo": image.repo, - "name": image.name, - "tag": image.tag, - } - - indexfile = templatedir + "/" + imagetagid + "/index.json" - with open(indexfile, "w") as f: - f.write(json.dumps(index)) - - - def _download_template_impl_v2(self, registry, image, template, templatedir, createdFiles, createdDirs): - (manifest, res) = registry.get_json( "/v2/%s/%s/manifests/%s" % ( - image.repo, image.name, image.tag)) - - layerChecksums = [ - layer["blobSum"] for layer in manifest["fsLayers"] - ] - layers = [ - json.loads(entry["v1Compatibility"]) for entry in manifest["history"] - ] - - for i in range(len(layerChecksums)): - layerChecksum = layerChecksums[i] - config = layers[i] - - layerdir = templatedir + "/" + config["id"] - if not os.path.exists(layerdir): - os.makedirs(layerdir) - createdDirs.append(layerdir) - - jsonfile = layerdir + "/template.json" - datafile = layerdir + "/template.tar.gz" - - with open(jsonfile, "w") as fh: - fh.write(json.dumps(config)) - - registry.save_data("/v2/%s/%s/blobs/%s" % ( - image.repo, image.name, layerChecksum), - datafile, checksum=layerChecksum) - - - index = { - "repo": image.repo, - "name": image.name, - "tag": image.tag, - } - - indexfile = templatedir + "/" + layers[0]["id"] + "/index.json" - with open(indexfile, "w") as f: - f.write(json.dumps(index)) - - - def create_template(self, template, templatedir, connect=None): - image = DockerImage.from_template(template) - - if not self._was_downloaded(image, templatedir): - self.download_template(image, template, templatedir) - - imagelist = self._get_image_list(image, templatedir) - imagelist.reverse() - - parentImage = None - for imagetagid in imagelist: - templateImage = templatedir + "/" + imagetagid + "/template.qcow2" - 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.check_call(cmd) - - if parentImage is None: - self.format_disk(templateImage, "qcow2", connect) - - path = templatedir + "/" + imagetagid + "/template." - self.extract_tarball(path + "qcow2", - "qcow2", - path + "tar.gz", - connect) - parentImage = templateImage - - def _get_image_list(self, image, templatedir): - imageparent = {} - imagenames = {} - imagedirs = [] - try: - imagedirs = os.listdir(templatedir) - except OSError: - pass - for imagetagid in imagedirs: - indexfile = templatedir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile,"r") as f: - index = json.load(f) - thisimage = DockerImage(index.get("repo"), - index["name"], - index.get("tag")) - imagenames[str(thisimage)] = imagetagid - jsonfile = templatedir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - with open(jsonfile,"r") as f: - data = json.load(f) - parent = data.get("parent",None) - if parent: - imageparent[imagetagid] = parent - if str(image) not in imagenames: - raise ValueError(["Image %s does not exist locally" % image]) - imagetagid = imagenames[str(image)] - imagelist = [] - while imagetagid != None: - imagelist.append(imagetagid) - parent = imageparent.get(imagetagid,None) - imagetagid = parent - return imagelist - - def delete_template(self, template, templatedir): - image = DockerImage.from_template(template) - - imageusage = {} - imageparent = {} - imagenames = {} - imagedirs = [] - try: - imagedirs = os.listdir(templatedir) - except OSError: - pass - for imagetagid in imagedirs: - indexfile = templatedir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - with open(indexfile,"r") as f: - index = json.load(f) - thisimage = DockerImage(index.get("repo"), - index["name"], - index.get("tag")) - imagenames[str(thisimage)] = imagetagid - jsonfile = templatedir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - with open(jsonfile,"r") as f: - data = json.load(f) - - parent = data.get("parent",None) - if parent: - if parent not in imageusage: - imageusage[parent] = [] - imageusage[parent].append(imagetagid) - imageparent[imagetagid] = parent - - - if str(image) not in imagenames: - raise ValueError(["Image %s does not exist locally" % image]) - imagetagid = imagenames[str(image)] - while imagetagid != None: - debug("Remove %s\n" % imagetagid) - parent = imageparent.get(imagetagid,None) - - indexfile = templatedir + "/" + imagetagid + "/index.json" - if os.path.exists(indexfile): - os.remove(indexfile) - jsonfile = templatedir + "/" + imagetagid + "/template.json" - if os.path.exists(jsonfile): - os.remove(jsonfile) - datafile = templatedir + "/" + imagetagid + "/template.tar.gz" - if os.path.exists(datafile): - os.remove(datafile) - imagedir = templatedir + "/" + imagetagid - shutil.rmtree(imagedir) - - if parent: - if len(imageusage[parent]) != 1: - debug("Parent %s is shared\n" % parent) - parent = None - imagetagid = parent - - def _get_template_data(self, image, templatedir): - imageList = self._get_image_list(image, templatedir) - toplayer = imageList[0] - diskfile = templatedir + "/" + toplayer + "/template.qcow2" - configfile = templatedir + "/" + toplayer + "/template.json" - return configfile, diskfile - - def get_disk(self, template, templatedir, imagedir, sandboxname): - image = DockerImage.from_template(template) - configfile, diskfile = self._get_template_data(image, templatedir) - tempfile = imagedir + "/" + sandboxname.split('/')[-1] + ".qcow2" - if not os.path.exists(imagedir): - os.makedirs(imagedir) - cmd = ["qemu-img","create","-q","-f","qcow2"] - cmd.append("-o") - cmd.append("backing_fmt=qcow2,backing_file=%s" % diskfile) - cmd.append(tempfile) - subprocess.check_call(cmd) - return tempfile - - def get_command(self, template, templatedir, userargs): - image = DockerImage.from_template(template) - configfile, diskfile = self._get_template_data(image, templatedir) - configParser = DockerConfParser(configfile) - cmd = configParser.getCommand() - entrypoint = configParser.getEntrypoint() - if entrypoint is None: - entrypoint = [] - if cmd is None: - cmd = [] - if userargs is not None and len(userargs) > 0: - return entrypoint + userargs - else: - return entrypoint + cmd - - def get_env(self, template, templatedir): - image = DockerImage.from_template(template) - configfile, diskfile = self._get_template_data(image, templatedir) - configParser = DockerConfParser(configfile) - return configParser.getEnvs() - -def debug(msg): - sys.stderr.write(msg) diff --git a/libvirt-sandbox/image/sources/virtbuilder.py b/libvirt-sandbox/image/sources/virtbuilder.py deleted file mode 100755 index 1b32083..0000000 --- a/libvirt-sandbox/image/sources/virtbuilder.py +++ /dev/null @@ -1,109 +0,0 @@ -# -# Copyright (C) 2015 SUSE LLC -# -# 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: Cedric Bosdonnat <cbosdonnat@suse.com> -# - -import os -import os.path -import subprocess - -from . import base -from libvirt_sandbox.image.template import Template - - -class VirtBuilderSource(base.Source): - - def _get_template_name(self, template): - # We shouldn't have '/' in the names, but let's make sure - # nobody can try to alter the folders structure later. - return template.path[1:].replace('/', '_') - - def has_template(self, template, templatedir): - imagepath = "%s/%s.qcow2" % (templatedir, template.path) - return os.path.exists(imagepath) - - def create_template(self, template, templatedir, connect=None): - if not os.path.exists(templatedir): - os.makedirs(templatedir) - - # Get the image using virt-builder - templatename = self._get_template_name(template) - imagepath_original = "%s/%s-original.qcow2" % (templatedir, templatename) - imagepath = "%s/%s.qcow2" % (templatedir, templatename) - cmd = ["virt-builder", templatename, "--no-network", - "-o", imagepath_original, "--format", "qcow2"] - subprocess.check_call(cmd) - - try: - # We need to convert this image into a single partition one. - tarfile = "%s/%s.tar" % (templatedir, templatename) - cmd = ["virt-tar-out", "-a", imagepath_original, "/", tarfile] - subprocess.check_call(cmd) - - cmd = ["qemu-img", "create", "-q", "-f", "qcow2", imagepath, "10G"] - subprocess.check_call(cmd) - - self.format_disk(imagepath, "qcow2", connect) - self.extract_tarball(imagepath, "qcow2", tarfile, connect) - - finally: - os.unlink(imagepath_original) - os.unlink(tarfile) - - def list_templates(self, templatedir): - files = [] - try: - imagefiles = os.listdir(templatedir) - except OSError: - return [] - - for filename in imagefiles: - if not filename.endswith(".qcow2"): - continue - files.append(filename[0:-6]) - - return [ - Template(source="virt-builder", - protocol=None, - hostname=None, - port=None, - username=None, - password=None, - path="/%s" % filename, - params={}) for filename in files] - - def delete_template(self, template, templatedir): - os.unlink("%s/%s.qcow2" % (templatedir, self._get_template_name(template))) - - def get_command(self, template, templatedir, userargs): - return userargs - - def get_disk(self,template, templatedir, imagedir, sandboxname): - diskfile = "%s/%s.qcow2" % (templatedir, self._get_template_name(template)) - tempfile = imagedir + "/" + sandboxname + ".qcow2" - if not os.path.exists(imagedir): - os.makedirs(imagedir) - cmd = ["qemu-img", "create", "-q", - "-f", "qcow2", - "-o", "backing_fmt=qcow2,backing_file=%s" % diskfile, - tempfile] - subprocess.check_call(cmd) - return tempfile - - def get_env(self,template, templatedir): - return [] diff --git a/libvirt-sandbox/image/template.py b/libvirt-sandbox/image/template.py deleted file mode 100644 index ab2ea29..0000000 --- a/libvirt-sandbox/image/template.py +++ /dev/null @@ -1,133 +0,0 @@ -# -# -*- coding: utf-8 -*- -# Authors: Daniel P. Berrange <berrange@redhat.com> -# -# Copyright (C) 2015 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 urllib.parse -import importlib -import re - -class Template(object): - - def __init__(self, - source, protocol, - hostname, port, - username, password, - path, params): - """ - :param source: template source name - :param protocol: network transport protocol or None - :param hostname: registry hostname or None - :param port: registry port or None - :param username: username or None - :param password: password or None - :param path: template path identifier - :param params: template parameters - - docker:///ubuntu - - docker+https://index.docker.io/ubuntu?tag=latest - - virt-builder:///fedora-20 - """ - - self.source = source - self.protocol = protocol - self.hostname = hostname - self.port = port - self.username = username - self.password = password - self.path = path - self.params = params - if self.params is None: - self.params = {} - - @classmethod - def _get_source_impl(klass, source): - try: - p = re.compile("\W") - sourcemod = "".join(p.split(source)) - sourcename = "".join([i.capitalize() for i in p.split(source)]) - - mod = importlib.import_module( - "libvirt_sandbox.image.sources." + sourcemod) - classname = sourcename + "Source" - classimpl = getattr(mod, classname) - return classimpl() - except Exception as e: - print (e) - raise Exception("Invalid source: '%s'" % source) - - def get_source_impl(self): - if self.source == "": - raise Exception("Missing scheme in image URI") - - return self._get_source_impl(self.source) - - def __repr__(self): - if self.protocol is not None: - scheme = self.source + "+" + self.protocol - else: - scheme = self.source - if self.hostname: - if self.port: - netloc = "%s:%d" % (self.hostname, self.port) - else: - netloc = self.hostname - - if self.username: - if self.password: - auth = self.username + ":" + self.password - else: - auth = self.username - netloc = auth + "@" + netloc - else: - netloc = None - - query = "&".join([key + "=" + self.params[key] for key in self.params.keys()]) - ret = urllib.parse.urlunparse((scheme, netloc, self.path, None, query, None)) - return ret - - @classmethod - def from_uri(klass, uri): - o = urllib.parse.urlparse(uri) - - idx = o.scheme.find("+") - if idx == -1: - source = o.scheme - protocol = None - else: - source = o.scheme[0:idx] - protocol = o.scheme[idx + 1:] - - query = {} - if o.query is not None and o.query != "": - for param in o.query.split("&"): - (key, val) = param.split("=") - query[key] = val - return klass(source, protocol, - o.hostname, o.port, - o.username, o.password, - o.path, query) - - @classmethod - def get_all(klass, source, templatedir): - impl = klass._get_source_impl(source) - - return impl.list_templates(templatedir) diff --git a/m4/virt-win32.m4 b/m4/virt-win32.m4 index d088ce8..8b908e7 100644 --- a/m4/virt-win32.m4 +++ b/m4/virt-win32.m4 @@ -4,15 +4,11 @@ AC_DEFUN([LIBVIRT_SANDBOX_WIN32],[ dnl for now since I'm not supporting mingw at present. - RWMJ CYGWIN_EXTRA_LDFLAGS= CYGWIN_EXTRA_LIBADD= - CYGWIN_EXTRA_PYTHON_LIBADD= MINGW_EXTRA_LDFLAGS= case "$host" in *-*-cygwin*) CYGWIN_EXTRA_LDFLAGS="-no-undefined" CYGWIN_EXTRA_LIBADD="${INTLLIBS}" - if test "x$PYTHON_VERSION" != "x"; then - CYGWIN_EXTRA_PYTHON_LIBADD="-L/usr/lib/python${PYTHON_VERSION}/config -lpython${PYTHON_VERSION}" - fi ;; *-*-mingw*) MINGW_EXTRA_LDFLAGS="-no-undefined" @@ -20,6 +16,5 @@ AC_DEFUN([LIBVIRT_SANDBOX_WIN32],[ esac AC_SUBST([CYGWIN_EXTRA_LDFLAGS]) AC_SUBST([CYGWIN_EXTRA_LIBADD]) - AC_SUBST([CYGWIN_EXTRA_PYTHON_LIBADD]) AC_SUBST([MINGW_EXTRA_LDFLAGS]) ])
participants (2)
-
Cedric Bosdonnat
-
Daniel P. Berrangé