On Mon, 2015-09-21 at 22:11 +0200, Cedric Bosdonnat wrote: > On Mon, 2015-09-21 at 15:45 +0100, Daniel P. Berrange wrote: > > Currently the CLI syntax is somewhat docker specific requiring > > inclusion of --registry arg to identify the docker download > > server. Other app containers have a notion of download server, > > but don't separate it from the template name. > > > > This patch removes that docker-ism by changing to use a URI > > for identifying the template image. So instead of > > > > virt-sandbox-image download \ > > --source docker --registry index.docker.io > > --username dan --password 123456 ubuntu:15.04 > > > > You can use > > > > virt-sandbox-image download docker://dan:123456@xxxxxxxxxxxxxxx/ubuntu?tag=15.04 > > > > The only mandatory part is the source prefix and image name, so > > that can shorten to just > > > > virt-sandbox-image download docker:///ubuntu > > > > to pull down the latest ubuntu image, from the default registry > > using no authentication. > > --- > > > > Changed in v2: > > > > - Rebase against master, instead of (unpushed) docker volume patch > > > > libvirt-sandbox/image/cli.py | 71 +++++-------- > > libvirt-sandbox/image/sources/DockerSource.py | 142 ++++++++++++++------------ > > libvirt-sandbox/image/sources/Source.py | 29 +++--- > > libvirt-sandbox/image/template.py | 110 ++++++++++++++++++++ > > Missing change in libvirt-sandbox/image/Makefile.am to add template.py. > As is that file isn't installed. > > I'm also just realizing that we didn't add Eren't commit for the > virt-sandbox-image man page. Adding it later is fine, but we need to > keep that on our radar. > > > 4 files changed, 228 insertions(+), 124 deletions(-) > > create mode 100644 libvirt-sandbox/image/template.py > > > > diff --git a/libvirt-sandbox/image/cli.py b/libvirt-sandbox/image/cli.py > > index 1718cc5..4d02289 100755 > > --- a/libvirt-sandbox/image/cli.py > > +++ b/libvirt-sandbox/image/cli.py > > @@ -3,7 +3,7 @@ > > # Authors: Daniel P. Berrange <berrange@xxxxxxxxxx> > > # Eren Yagdiran <erenyagdiran@xxxxxxxxx> > > # > > -# Copyright (C) 2013 Red Hat, Inc. > > +# 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 > > @@ -34,6 +34,8 @@ 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" > > @@ -44,15 +46,6 @@ else: > > debug = False > > verbose = False > > > > -import importlib > > -def dynamic_source_loader(name): > > - name = name[0].upper() + name[1:] > > - modname = "libvirt_sandbox.image.sources." + name + "Source" > > - mod = importlib.import_module(modname) > > - classname = name + "Source" > > - classimpl = getattr(mod, classname) > > - return classimpl() > > - > > gettext.bindtextdomain("libvirt-sandbox", "/usr/share/locale") > > gettext.textdomain("libvirt-sandbox") > > try: > > @@ -73,11 +66,10 @@ def info(msg): > > > > def download(args): > > try: > > - dynamic_source_loader(args.source).download_template(templatename=args.template, > > - templatedir=args.template_dir, > > - registry=args.registry, > > - username=args.username, > > - password=args.password) > > + tmpl = template.Template.from_uri(args.template) > > + source = tmpl.get_source_impl() > > + source.download_template(template=tmpl, > > + templatedir=args.template_dir) > > except IOError,e: > > print "Source %s cannot be found in given path" %args.source This IOError exception message should be rephrased or handled with the general Exception. -- Cedric > > except Exception,e: > > @@ -85,17 +77,21 @@ def download(args): > > > > def delete(args): > > try: > > - dynamic_source_loader(args.source).delete_template(templatename=args.template, > > - templatedir=args.template_dir) > > + tmpl = template.Template.from_uri(args.template) > > + source = tmpl.get_source_impl() > > + source.delete_template(template=tmpl, > > + templatedir=args.template_dir) > > except Exception,e: > > print "Delete Error %s", str(e) > > > > def create(args): > > try: > > - dynamic_source_loader(args.source).create_template(templatename=args.template, > > - templatedir=args.template_dir, > > - connect=args.connect, > > - format=args.format) > > + tmpl = template.Template.from_uri(args.template) > > + source = tmpl.get_source_impl() > > + source.create_template(template=tmpl, > > + templatedir=args.template_dir, > > + connect=args.connect, > > + format=args.format) > > except Exception,e: > > print "Create Error %s" % str(e) > > > > @@ -103,19 +99,22 @@ def run(args): > > try: > > if args.connect is not None: > > check_connect(args.connect) > > - source = dynamic_source_loader(args.source) > > + > > + tmpl = template.Template.from_uri(args.template) > > + source = tmpl.get_source_impl() > > + > > name = args.name > > if name is None: > > randomid = ''.join(random.choice(string.lowercase) for i in range(10)) > > - name = args.template + ":" + randomid > > + name = tmpl.path[1:] + ":" + randomid > > > > - diskfile = source.get_disk(templatename=args.template, > > + diskfile = source.get_disk(template=tmpl, > > templatedir=args.template_dir, > > imagedir=args.image_dir, > > sandboxname=name) > > > > format = "qcow2" > > - commandToRun = source.get_command(args.template, args.template_dir, args.args) > > + commandToRun = source.get_command(tmpl, args.template_dir, args.args) > > if len(commandToRun) == 0: > > commandToRun = ["/bin/sh"] > > cmd = ['virt-sandbox', '--name', name] > > @@ -129,7 +128,7 @@ def run(args): > > params.append('-N') > > params.append(networkArgs) > > > > - allEnvs = source.get_env(args.template, args.template_dir) > > + allEnvs = source.get_env(tmpl, args.template_dir) > > envArgs = args.env > > if envArgs is not None: > > allEnvs = allEnvs + envArgs > > @@ -151,7 +150,7 @@ def run(args): > > > > def requires_template(parser): > > parser.add_argument("template", > > - help=_("name of the template")) > > + help=_("URI of the template")) > > Shouldn't we provide some examples here? As those URIs can't be invented > we need to give the user some chances to discover them without having to > read our code ;) > > > > > def requires_name(parser): > > parser.add_argument("-n","--name", > > @@ -163,23 +162,10 @@ def check_connect(connectstr): > > raise ValueError("URI '%s' is not supported by virt-sandbox-image" % connectstr) > > return True > > > > -def requires_source(parser): > > - parser.add_argument("-s","--source", > > - 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")) > > - 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 default_template_dir > > parser.add_argument("-t","--template-dir", > > @@ -196,8 +182,6 @@ def gen_download_args(subparser): > > parser = subparser.add_parser("download", > > help=_("Download template data")) > > requires_template(parser) > > - requires_source(parser) > > - requires_auth_conn(parser) > > requires_template_dir(parser) > > parser.set_defaults(func=download) > > > > @@ -205,7 +189,6 @@ def gen_delete_args(subparser): > > parser = subparser.add_parser("delete", > > help=_("Delete template data")) > > requires_template(parser) > > - requires_source(parser) > > requires_template_dir(parser) > > parser.set_defaults(func=delete) > > > > @@ -213,7 +196,6 @@ def gen_create_args(subparser): > > parser = subparser.add_parser("create", > > help=_("Create image from template data")) > > requires_template(parser) > > - requires_source(parser) > > requires_connect(parser) > > requires_template_dir(parser) > > parser.add_argument("-f","--format", > > @@ -226,7 +208,6 @@ def gen_run_args(subparser): > > help=_("Run an already built image")) > > requires_name(parser) > > requires_template(parser) > > - requires_source(parser) > > requires_connect(parser) > > requires_template_dir(parser) > > requires_image_dir(parser) > > diff --git a/libvirt-sandbox/image/sources/DockerSource.py b/libvirt-sandbox/image/sources/DockerSource.py > > index c374a0c..10f8537 100644 > > --- a/libvirt-sandbox/image/sources/DockerSource.py > > +++ b/libvirt-sandbox/image/sources/DockerSource.py > > @@ -2,6 +2,7 @@ > > # -*- 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 > > @@ -28,6 +29,8 @@ import traceback > > import os > > import subprocess > > import shutil > > +import urlparse > > + > > > > class DockerConfParser(): > > > > @@ -47,12 +50,6 @@ class DockerConfParser(): > > > > 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" > > @@ -62,43 +59,38 @@ class DockerSource(Source): > > 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, templatename, templatedir, > > - registry=None, username=None, password=None): > > - if registry is None: > > - registry = self.default_index_server > > - > > - if username is not None: > > - self.www_auth_username = username > > - self.www_auth_password = password > > - > > + def download_template(self, template, templatedir): > > self._check_cert_validate() > > - tag = "latest" > > - offset = templatename.find(':') > > - if offset != -1: > > - tag = templatename[offset + 1:] > > - templatename = templatename[0:offset] > > + > > try: > > - (data, res) = self._get_json(registry, "/v1/repositories/" + templatename + "/images", > > - {"X-Docker-Token": "true"}) > > + (data, res) = self._get_json(template, > > + None, > > + "/v1/repositories" + template.path + "/images", > > + {"X-Docker-Token": "true"}) > > except urllib2.HTTPError, e: > > - raise ValueError(["Image '%s' does not exist" % templatename]) > > + raise ValueError(["Image '%s' does not exist" % template]) > > > > registryendpoint = res.info().getheader('X-Docker-Endpoints') > > token = res.info().getheader('X-Docker-Token') > > checksums = {} > > for layer in data: > > pass > > - (data, res) = self._get_json(registryendpoint, "/v1/repositories/" + templatename + "/tags", > > - { "Authorization": "Token " + token }) > > + (data, res) = self._get_json(template, > > + registryendpoint, > > + "/v1/repositories" + template.path + "/tags", > > + { "Authorization": "Token " + token }) > > > > cookie = res.info().getheader('Set-Cookie') > > > > + tag = template.params.get("tag", "latest") > > if not tag in data: > > - raise ValueError(["Tag '%s' does not exist for image '%s'" % (tag, templatename)]) > > + raise ValueError(["Tag '%s' does not exist for image '%s'" % (tag, template)]) > > imagetagid = data[tag] > > > > - (data, res) = self._get_json(registryendpoint, "/v1/images/" + imagetagid + "/ancestry", > > - { "Authorization": "Token "+token }) > > + (data, res) = self._get_json(template, > > + registryendpoint, > > + "/v1/images/" + imagetagid + "/ancestry", > > + { "Authorization": "Token "+ token }) > > > > if data[0] != imagetagid: > > raise ValueError(["Expected first layer id '%s' to match image id '%s'", > > @@ -118,8 +110,11 @@ class DockerSource(Source): > > datafile = layerdir + "/template.tar.gz" > > > > if not os.path.exists(jsonfile) or not os.path.exists(datafile): > > - res = self._save_data(registryendpoint, "/v1/images/" + layerid + "/json", > > - { "Authorization": "Token " + token }, jsonfile) > > + res = self._save_data(template, > > + registryendpoint, > > + "/v1/images/" + layerid + "/json", > > + { "Authorization": "Token " + token }, > > + jsonfile) > > createdFiles.append(jsonfile) > > > > layersize = int(res.info().getheader("Content-Length")) > > @@ -128,12 +123,15 @@ class DockerSource(Source): > > if layerid in checksums: > > datacsum = checksums[layerid] > > > > - self._save_data(registryendpoint, "/v1/images/" + layerid + "/layer", > > - { "Authorization": "Token "+token }, datafile, datacsum, layersize) > > + self._save_data(template, > > + registryendpoint, > > + "/v1/images/" + layerid + "/layer", > > + { "Authorization": "Token "+token }, > > + datafile, datacsum, layersize) > > createdFiles.append(datafile) > > > > index = { > > - "name": templatename, > > + "name": template.path, > > } > > > > indexfile = templatedir + "/" + imagetagid + "/index.json" > > @@ -152,9 +150,11 @@ class DockerSource(Source): > > shutil.rmtree(d) > > except: > > pass > > - def _save_data(self,server, path, headers, dest, checksum=None, datalen=None): > > + > > + def _save_data(self, template, server, path, headers, > > + dest, checksum=None, datalen=None): > > try: > > - res = self._get_url(server, path, headers) > > + res = self._get_url(template, server, path, headers) > > > > csum = None > > if checksum is not None: > > @@ -193,8 +193,22 @@ class DockerSource(Source): > > debug("FAIL %s\n" % str(e)) > > raise > > > > - def _get_url(self,server, path, headers): > > - url = "https://" + server + path > > + def _get_url(self, template, server, path, headers): > > + if template.protocol is None: > > + protocol = "https" > > + else: > > + protocol = template.protocol > > + > > + if server is None: > > + if template.hostname is None: > > + server = "index.docker.io" > > + else: > > + if template.port is not None: > > + server = template.hostname + ":" + template.port > > + else: > > + server = template.hostname > > + > > + url = urlparse.urlunparse((protocol, server, path, None, None, None)) > > debug("Fetching %s..." % url) > > > > req = urllib2.Request(url=url) > > @@ -204,16 +218,18 @@ class DockerSource(Source): > > 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', '') > > + if template.username and template.password: > > + base64string = base64.encodestring( > > + '%s:%s' % (template.username, > > + template.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): > > + def _get_json(self, template, server, path, headers): > > try: > > - res = self._get_url(server, path, headers) > > + res = self._get_url(template, server, path, headers) > > data = json.loads(res.read()) > > debug("OK\n") > > return (data, res) > > @@ -221,11 +237,11 @@ class DockerSource(Source): > > debug("FAIL %s\n" % str(e)) > > raise > > > > - def create_template(self, templatename, templatedir, connect=None, format=None): > > + def create_template(self, template, templatedir, connect=None, format=None): > > if format is None: > > format = self.default_disk_format > > self._check_disk_format(format) > > - imagelist = self._get_image_list(templatename,templatedir) > > + imagelist = self._get_image_list(template, templatedir) > > imagelist.reverse() > > > > parentImage = None > > @@ -252,7 +268,7 @@ class DockerSource(Source): > > if not format in supportedFormats: > > raise ValueError(["Unsupported image format %s" % format]) > > > > - def _get_image_list(self,templatename,destdir): > > + def _get_image_list(self, template, destdir): > > imageparent = {} > > imagenames = {} > > imagedirs = os.listdir(destdir) > > @@ -265,13 +281,13 @@ class DockerSource(Source): > > 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) > > + data = json.load(f) > > + parent = data.get("parent",None) > > if parent: > > imageparent[imagetagid] = parent > > - if not templatename in imagenames: > > - raise ValueError(["Image %s does not exist locally" %templatename]) > > - imagetagid = imagenames[templatename] > > + if not template.path in imagenames: > > + raise ValueError(["Image %s does not exist locally" % template.path]) > > + imagetagid = imagenames[template.path] > > imagelist = [] > > while imagetagid != None: > > imagelist.append(imagetagid) > > @@ -310,7 +326,7 @@ class DockerSource(Source): > > cmd = cmd + params > > subprocess.call(cmd) > > > > - def delete_template(self, templatename, templatedir): > > + def delete_template(self, template, templatedir): > > imageusage = {} > > imageparent = {} > > imagenames = {} > > @@ -324,9 +340,9 @@ class DockerSource(Source): > > jsonfile = templatedir + "/" + imagetagid + "/template.json" > > if os.path.exists(jsonfile): > > with open(jsonfile,"r") as f: > > - template = json.load(f) > > + data = json.load(f) > > > > - parent = template.get("parent",None) > > + parent = data.get("parent",None) > > if parent: > > if parent not in imageusage: > > imageusage[parent] = [] > > @@ -334,10 +350,10 @@ class DockerSource(Source): > > imageparent[imagetagid] = parent > > > > > > - if not templatename in imagenames: > > - raise ValueError(["Image %s does not exist locally" %templatename]) > > + if not template.path in imagenames: > > + raise ValueError(["Image %s does not exist locally" % template.path]) > > > > - imagetagid = imagenames[templatename] > > + imagetagid = imagenames[template.path] > > while imagetagid != None: > > debug("Remove %s\n" % imagetagid) > > parent = imageparent.get(imagetagid,None) > > @@ -360,15 +376,15 @@ class DockerSource(Source): > > parent = None > > imagetagid = parent > > > > - def _get_template_data(self, templatename, templatedir): > > - imageList = self._get_image_list(templatename, templatedir) > > + def _get_template_data(self, template, templatedir): > > + imageList = self._get_image_list(template, templatedir) > > toplayer = imageList[0] > > diskfile = templatedir + "/" + toplayer + "/template.qcow2" > > configfile = templatedir + "/" + toplayer + "/template.json" > > return configfile, diskfile > > > > - def get_disk(self,templatename, templatedir, imagedir, sandboxname): > > - configfile, diskfile = self._get_template_data(templatename, templatedir) > > + def get_disk(self, template, templatedir, imagedir, sandboxname): > > + configfile, diskfile = self._get_template_data(template, templatedir) > > tempfile = imagedir + "/" + sandboxname + ".qcow2" > > if not os.path.exists(imagedir): > > os.makedirs(imagedir) > > @@ -379,8 +395,8 @@ class DockerSource(Source): > > subprocess.call(cmd) > > return tempfile > > > > - def get_command(self, templatename, templatedir, userargs): > > - configfile, diskfile = self._get_template_data(templatename, templatedir) > > + def get_command(self, template, templatedir, userargs): > > + configfile, diskfile = self._get_template_data(template, templatedir) > > configParser = DockerConfParser(configfile) > > cmd = configParser.getCommand() > > entrypoint = configParser.getEntrypoint() > > @@ -393,8 +409,8 @@ class DockerSource(Source): > > else: > > return entrypoint + cmd > > > > - def get_env(self, templatename, templatedir): > > - configfile, diskfile = self._get_template_data(templatename, templatedir) > > + def get_env(self, template, templatedir): > > + configfile, diskfile = self._get_template_data(template, templatedir) > > configParser = DockerConfParser(configfile) > > return configParser.getEnvs() > > > > diff --git a/libvirt-sandbox/image/sources/Source.py b/libvirt-sandbox/image/sources/Source.py > > index 8a21f90..597a7fb 100644 > > --- a/libvirt-sandbox/image/sources/Source.py > > +++ b/libvirt-sandbox/image/sources/Source.py > > @@ -2,6 +2,7 @@ > > # -*- 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 > > @@ -33,14 +34,10 @@ class Source(): > > pass > > > > @abstractmethod > > - def download_template(self, templatename, templatedir, > > - registry=None, username=None, password=None): > > + def download_template(self, template, templatedir): > > """ > > - :param templatename: name of the template image to download > > + :param template: libvirt_sandbox.template.Template object > > :param templatedir: local directory path in which to store the template > > - :param registry: optional hostname of image registry server > > - :param username: optional username to authenticate against registry server > > - :param password: optional password to authenticate against registry server > > > > Download a template from the registry, storing it in the local > > filesystem > > @@ -48,10 +45,10 @@ class Source(): > > pass > > > > @abstractmethod > > - def create_template(self, templatename, templatedir, > > + def create_template(self, template, templatedir, > > connect=None, format=None): > > """ > > - :param templatename: name of the template image to create > > + :param template: libvirt_sandbox.template.Template object > > :param templatedir: local directory path in which to store the template > > :param connect: libvirt connection URI > > :param format: disk image format > > @@ -63,9 +60,9 @@ class Source(): > > pass > > > > @abstractmethod > > - def delete_template(self, templatename, templatedir): > > + def delete_template(self, template, templatedir): > > """ > > - :param templatename: name of the template image to delete > > + :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 > > @@ -73,9 +70,9 @@ class Source(): > > pass > > > > @abstractmethod > > - def get_command(self, templatename, templatedir, userargs): > > + def get_command(self, template, templatedir, userargs): > > """ > > - :param templatename: name of the template image to query > > + :param template: libvirt_sandbox.template.Template object > > :param templatedir: local directory path in which templates are stored > > :param userargs: user specified arguments to run > > > > @@ -85,9 +82,9 @@ class Source(): > > pass > > > > @abstractmethod > > - def get_disk(self,templatename, templatedir, imagedir, sandboxname): > > + def get_disk(self, template, templatedir, imagedir, sandboxname): > > """ > > - :param templatename: name of the template image to download > > + :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 > > > > @@ -97,9 +94,9 @@ class Source(): > > pass > > > > @abstractmethod > > - def get_env(self,templatename, templatedir): > > + def get_env(self, template, templatedir): > > """ > > - :param templatename: name of the template image to download > > + :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 > > diff --git a/libvirt-sandbox/image/template.py b/libvirt-sandbox/image/template.py > > new file mode 100644 > > index 0000000..0ad767b > > --- /dev/null > > +++ b/libvirt-sandbox/image/template.py > > @@ -0,0 +1,110 @@ > > +# > > +# -*- coding: utf-8 -*- > > +# Authors: Daniel P. Berrange <berrange@xxxxxxxxxx> > > +# > > +# 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 urlparse > > +import importlib > > + > > +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 > > + """ > > + > > + 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 = {} > > + > > + def get_source_impl(self): > > + mod = importlib.import_module( > > + "libvirt_sandbox.image.sources." + > > + self.source.capitalize() + "Source") > > + classname = self.source.capitalize() + "Source" > > + classimpl = getattr(mod, classname) > > + return classimpl() > > + > > + 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 = 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()]) > > + return urlparse.urlunparse((scheme, netloc, self.path, None, query, None)) > > + > > + @classmethod > > + def from_uri(klass, uri): > > + o = urlparse.urlparse(uri) > > + > > + idx = o.scheme.find("+") > > + if idx == -1: > > + source = o.scheme > > + protocol = None > > + else: > > + source = o.scheme[0:idx] > > + protocol = o.schema[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) > > + > > git complains about the empty line here. > > ACK, with the help improvement + Makefile.am fix. > > -- > Cedric > > -- > libvir-list mailing list > libvir-list@xxxxxxxxxx > https://www.redhat.com/mailman/listinfo/libvir-list -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list