diff options
author | Radostin Stoyanov <rstoyanov1@gmail.com> | 2017-08-26 21:42:06 +0100 |
---|---|---|
committer | Radostin Stoyanov <rstoyanov1@gmail.com> | 2017-08-28 17:00:19 +0100 |
commit | e98df68ded6515c3f6d8f8854697c15407aff000 (patch) | |
tree | c6f0f087b89b6f0966025e7441fb0f01765dc25c /src | |
parent | 2bb76d45f14ae1bce4240bd3c1eeabcb44f663ac (diff) | |
download | virt-bootstrap.git-e98df68ded6515c3f6d8f8854697c15407aff000.tar.gz virt-bootstrap.git-e98df68ded6515c3f6d8f8854697c15407aff000.tar.xz virt-bootstrap.git-e98df68ded6515c3f6d8f8854697c15407aff000.zip |
Add virt-builder source
Add implementation for virt-builder source which aims to create
container root file system from VM image build with virt-builder.
Usage examples:
$ virt-bootstrap virt-builder://fedora-25 /tmp/foo
$ virt-bootstrap virt-builder://ubuntu-16.04 /tmp/bar --root-password secret
$ virt-bootstrap virt-builder://fedora-25 /tmp/foo -f qcow2 --idmap 0:1000:10
$ sudo virt-bootstrap virt-builder://fedora-25 /tmp/foo --idmap 0:1000:10
Tests are also introduced along with the implementation. They cover
creation of root file system and UID/GID mapping for 'dir' and 'qcow2'
output format by mocking the build_image() method to avoid the time
consuming call to virt-builder which might also require network
connection with function which creates dummy disk image.
Setting root password is handled by virt-builder and hence the
introduced test only ensures that the password string is passed
correctly.
Diffstat (limited to 'src')
-rw-r--r-- | src/virtBootstrap/sources/__init__.py | 1 | ||||
-rw-r--r-- | src/virtBootstrap/sources/virt_builder_source.py | 154 | ||||
-rwxr-xr-x | src/virtBootstrap/virt_bootstrap.py | 2 |
3 files changed, 156 insertions, 1 deletions
diff --git a/src/virtBootstrap/sources/__init__.py b/src/virtBootstrap/sources/__init__.py index e891e9b..be6b25c 100644 --- a/src/virtBootstrap/sources/__init__.py +++ b/src/virtBootstrap/sources/__init__.py @@ -24,3 +24,4 @@ sources - Class definitions which process container image or from virtBootstrap.sources.file_source import FileSource from virtBootstrap.sources.docker_source import DockerSource +from virtBootstrap.sources.virt_builder_source import VirtBuilderSource diff --git a/src/virtBootstrap/sources/virt_builder_source.py b/src/virtBootstrap/sources/virt_builder_source.py new file mode 100644 index 0000000..628544f --- /dev/null +++ b/src/virtBootstrap/sources/virt_builder_source.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +# Authors: Radostin Stoyanov <rstoyanov1@gmail.com> +# +# Copyright (C) 2017 Radostin Stoyanov +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + +""" +VirtBuilderSource aim is to extract the root file system from VM image +build with virt-builder from template. +""" + +import os +import logging +import subprocess +import tempfile + +import guestfs +from virtBootstrap import utils + + +# pylint: disable=invalid-name +# Create logger +logger = logging.getLogger(__name__) + + +class VirtBuilderSource(object): + """ + Extract root file system from image build with virt-builder. + """ + def __init__(self, **kwargs): + """ + Create container rootfs by building VM from virt-builder template + and extract the rootfs. + + @param uri: Template name + @param fmt: Format used to store the output [dir, qcow2] + @param uid_map: Mappings for UID of files in rootfs + @param gid_map: Mappings for GID of files in rootfs + @param root_password: Root password to set in rootfs + @param progress: Instance of the progress module + """ + # Parsed URIs: + # - "virt-builder:///<template>" + # - "virt-builder://<template>" + # - "virt-builder:/<template>" + self.template = kwargs['uri'].netloc or kwargs['uri'].path[1:] + self.output_format = kwargs.get('fmt', utils.DEFAULT_OUTPUT_FORMAT) + self.uid_map = kwargs.get('uid_map', []) + self.gid_map = kwargs.get('gid_map', []) + self.root_password = kwargs.get('root_password', None) + self.progress = kwargs['progress'].update_progress + + def build_image(self, output_file): + """ + Build VM from virt-builder template + """ + cmd = ['virt-builder', self.template, + '-o', output_file, + '--no-network', + '--delete', '/dev/*', + '--delete', '/boot/*', + # Comment out all lines in fstab + '--edit', '/etc/fstab:s/^/#/'] + if self.root_password is not None: + cmd += ['--root-password', "password:%s" % self.root_password] + self.run_builder(cmd) + + def run_builder(self, cmd): + """ + Execute virt-builder command + """ + subprocess.check_call(cmd) + + def unpack(self, dest): + """ + Build image and extract root file system + + @param dest: Directory path where output files will be stored. + """ + + with tempfile.NamedTemporaryFile(prefix='bootstrap_') as tmp_file: + if self.output_format == 'dir': + self.progress("Building image", value=0, logger=logger) + self.build_image(tmp_file.name) + self.progress("Extracting rootfs", value=50, logger=logger) + g = guestfs.GuestFS(python_return_dict=True) + g.add_drive_opts(tmp_file.name, readonly=False, format='raw') + g.launch() + + # Get the device with file system + root_dev = g.inspect_os() + if not root_dev: + raise Exception("No file system was found") + g.mount(root_dev[0], '/') + + # Extract file system to destination directory + g.copy_out('/', dest) + + g.umount('/') + g.shutdown() + + self.progress("Extraction completed successfully!", + value=100, logger=logger) + logger.info("Files are stored in: %s", dest) + + elif self.output_format == 'qcow2': + output_file = os.path.join(dest, 'layer-0.qcow2') + + self.progress("Building image", value=0, logger=logger) + self.build_image(tmp_file.name) + + self.progress("Extracting rootfs", value=50, logger=logger) + g = guestfs.GuestFS(python_return_dict=True) + g.add_drive_opts(tmp_file.name, readonly=True, format='raw') + # Create qcow2 disk image + g.disk_create( + filename=output_file, + format='qcow2', + size=os.path.getsize(tmp_file.name) + ) + g.add_drive_opts(output_file, readonly=False, format='qcow2') + g.launch() + # Get the device with file system + root_dev = g.inspect_os() + if not root_dev: + raise Exception("No file system was found") + output_dev = g.list_devices()[1] + # Copy the file system to the new qcow2 disk + g.copy_device_to_device(root_dev[0], output_dev, sparse=True) + g.shutdown() + + # UID/GID mapping + if self.uid_map or self.gid_map: + logger.info("Mapping UID/GID") + utils.map_id_in_image(1, dest, self.uid_map, self.gid_map) + + self.progress("Extraction completed successfully!", value=100, + logger=logger) + logger.info("Image is stored in: %s", output_file) + + else: + raise Exception("Unknown format:" + self.output_format) diff --git a/src/virtBootstrap/virt_bootstrap.py b/src/virtBootstrap/virt_bootstrap.py index e387842..fe95f5e 100755 --- a/src/virtBootstrap/virt_bootstrap.py +++ b/src/virtBootstrap/virt_bootstrap.py @@ -62,7 +62,7 @@ def get_source(source_type): Get object which match the source type """ try: - class_name = "%sSource" % source_type.capitalize() + class_name = "%sSource" % source_type.title().replace('-', '') clazz = getattr(sources, class_name) return clazz except Exception: |