[libvirt] [PATCH v2 sandbox] virt-sandbox-image: switch to use URI to identify templates

Cedric Bosdonnat cbosdonnat at suse.com
Mon Sep 21 20:11:48 UTC 2015


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@index.docker.io/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 at redhat.com>
>  #          Eren Yagdiran <erenyagdiran at gmail.com>
>  #
> -# 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
>      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 at 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 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




More information about the libvir-list mailing list