summaryrefslogtreecommitdiffstats
path: root/src/virtBootstrap/sources.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/virtBootstrap/sources.py')
-rw-r--r--src/virtBootstrap/sources.py330
1 files changed, 0 insertions, 330 deletions
diff --git a/src/virtBootstrap/sources.py b/src/virtBootstrap/sources.py
deleted file mode 100644
index 40b66f9..0000000
--- a/src/virtBootstrap/sources.py
+++ /dev/null
@@ -1,330 +0,0 @@
-# Authors: Cedric Bosdonnat <cbosdonnat@suse.com>
-#
-# Copyright (C) 2017 SUSE, 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 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/>.
-
-"""
-Class definitions which process container image or
-archive from source and unpack them in destination directory.
-"""
-
-import select
-import shutil
-import getpass
-import os
-import logging
-import subprocess
-
-from virtBootstrap import utils
-
-
-# pylint: disable=invalid-name
-# Create logger
-logger = logging.getLogger(__name__)
-
-
-class FileSource(object):
- """
- Extract root filesystem from file.
- """
- def __init__(self, **kwargs):
- """
- Bootstrap root filesystem from tarball
-
- @param uri: Path to tar archive file.
- @param fmt: Format used to store image [dir, qcow2]
- @param progress: Instance of the progress module
- """
- self.path = kwargs['uri'].path
- self.output_format = kwargs.get('fmt', utils.DEFAULT_OUTPUT_FORMAT)
- self.progress = kwargs['progress'].update_progress
-
- def unpack(self, dest):
- """
- Safely extract root filesystem from tarball
-
- @param dest: Directory path where the files to be extraced
- """
-
- if not os.path.isfile(self.path):
- raise Exception('Invalid file source "%s"' % self.path)
-
- if self.output_format == 'dir':
- self.progress("Extracting files into destination directory",
- value=0, logger=logger)
- utils.safe_untar(self.path, dest)
-
- elif self.output_format == 'qcow2':
- # Remove the old path
- file_name = os.path.basename(self.path)
- qcow2_file = os.path.realpath('{}/{}.qcow2'.format(dest,
- file_name))
-
- self.progress("Extracting files into qcow2 image", value=0,
- logger=logger)
- utils.create_qcow2(self.path, qcow2_file)
- else:
- raise Exception("Unknown format:" + self.output_format)
-
- self.progress("Extraction completed successfully!", value=100,
- logger=logger)
- logger.info("Files are stored in: " + dest)
-
-
-class DockerSource(object):
- """
- Extract files from Docker image
- """
-
- # pylint: disable=too-many-instance-attributes
- def __init__(self, **kwargs):
- """
- Bootstrap root filesystem from Docker registry
-
- @param uri: Address of source registry
- @param username: Username to access source registry
- @param password: Password to access source registry
- @param fmt: Format used to store image [dir, qcow2]
- @param not_secure: Do not require HTTPS and certificate verification
- @param no_cache: Whether to store downloaded images or not
- @param progress: Instance of the progress module
- """
-
- self.url = self.gen_valid_uri(kwargs['uri'])
- self.username = kwargs.get('username', None)
- self.password = kwargs.get('password', None)
- self.output_format = kwargs.get('fmt', utils.DEFAULT_OUTPUT_FORMAT)
- self.insecure = kwargs.get('not_secure', False)
- self.no_cache = kwargs.get('no_cache', False)
- self.progress = kwargs['progress'].update_progress
- self.images_dir = utils.get_image_dir(self.no_cache)
- self.manifest = None
- self.layers = []
-
- if self.username and not self.password:
- self.password = getpass.getpass()
-
- self.retrieve_layers_info()
-
- def retrieve_layers_info(self):
- """
- Retrive manifest from registry and get layers' digest,
- sum_type, size and file_path in a list.
- """
- self.manifest = utils.get_image_details(self.url, raw=True,
- insecure=self.insecure,
- username=self.username,
- password=self.password)
-
- if self.manifest['schemaVersion'] == 1:
- layers_list = self.manifest['fsLayers'][::-1]
- digest_field = 'blobSum'
- elif self.manifest['schemaVersion'] == 2:
- layers_list = self.manifest['layers']
- digest_field = 'digest'
- else:
- raise ValueError('Unsupported manifest schema.')
-
- for layer in layers_list:
- layer_digest = layer[digest_field]
- layer_size = layer['size'] if 'size' in layer else None
-
- sum_type, layer_sum = layer_digest.split(':')
- file_path = os.path.join(self.images_dir, layer_sum + '.tar')
- self.layers.append([sum_type, layer_sum, file_path, layer_size])
-
- def gen_valid_uri(self, uri):
- """
- Generate Docker URI in format accepted by skopeo.
- """
- registry = uri.netloc
- image = uri.path
-
- # Convert "docker:///<image>" to "docker://<image>"
- if not registry and image.startswith('/'):
- image = image[1:]
-
- # Convert "docker://<image>/" to "docker://<image>"
- if image.endswith('/'):
- image = image[:-1]
-
- return "docker://" + registry + image
-
- def download_image(self):
- """
- Download image layers using "skopeo copy".
- """
- # Note: we don't want to expose --src-cert-dir to users as
- # they should place the certificates in the system
- # folders for broader enablement
- skopeo_copy = ["skopeo", "copy", self.url, "dir:" + self.images_dir]
-
- if self.insecure:
- skopeo_copy.append('--src-tls-verify=false')
- if self.username:
- skopeo_copy.append('--src-creds={}:{}'.format(self.username,
- self.password))
- self.progress("Downloading container image", value=0, logger=logger)
- # Run "skopeo copy" command
- self.read_skopeo_progress(skopeo_copy)
- # Remove the manifest file as it is not needed
- os.remove(os.path.join(self.images_dir, "manifest.json"))
-
- def parse_output(self, proc):
- """
- Read stdout from skopeo's process asynchconosly.
- """
- current_layer, total_layers_num = 0, len(self.layers)
-
- # Process the output until the process terminates
- while proc.poll() is None:
- # Wait for data to become available
- stdout = select.select([proc.stdout], [], [])[0]
- # Split output into line
- output = utils.read_async(stdout[0]).strip().split('\n')
- for line in output:
- line_split = line.split()
- if len(line_split) > 2: # Avoid short lines
- if utils.is_new_layer_message(line):
- current_layer += 1
- self.progress("Downloading layer (%s/%s)"
- % (current_layer, total_layers_num))
- # Use the single slash between layer's "downloaded" and
- # "total size" in the output to recognise progress message
- elif line_split[2] == '/':
- self.update_progress_from_output(line_split,
- current_layer,
- total_layers_num)
-
- # Stop parsing when manifest is copied.
- elif utils.is_layer_config_message(line):
- break
- else:
- continue # continue if the inner loop didn't break
- break
-
- if proc.poll() is None:
- proc.wait() # Wait until the process is finished
- return proc.returncode == 0
-
- def update_progress_from_output(self, line_split, current_l, total_l):
- """
- Parse a line from skopeo's output to extract the downloaded and
- total size of image layer.
-
- Calculate percentage and update the progress of virt-bootstrap.
-
- @param current_l: Number of currently downloaded layer
- @param total_l: Total number of layers
- @param line_split: A list with format:
- [<d_size>, <d_format>, '/', <t_size>, <t_format>, <progress>]
- Example:
- ['5.92', 'MB', '/', '44.96', 'MB', '[===>-----------------]']
- """
-
- if not (len(line_split) > 4 and isinstance(line_split, list)):
- return
-
- d_size, d_format = utils.str2float(line_split[0]), line_split[1]
- t_size, t_format = utils.str2float(line_split[3]), line_split[4]
-
- if d_size and t_size:
- downloaded_size = utils.size_to_bytes(d_size, d_format)
- total_size = utils.size_to_bytes(t_size, t_format)
- if downloaded_size and total_size:
- try:
- frac = float(1) / total_l
- downloaded = float(downloaded_size) / total_size
- layer_frac = float(max(0, current_l - 1)) / total_l
-
- progress = 50 * (layer_frac + (frac * downloaded))
-
- self.progress(value=progress)
- except Exception:
- pass # Ignore failures
-
- def read_skopeo_progress(self, cmd):
- """
- Parse the output from skopeo copy to track download progress.
- """
- proc = subprocess.Popen(
- cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=True
- )
-
- # Without `make_async`, `fd.read` in `read_async` blocks.
- utils.make_async(proc.stdout)
- if not self.parse_output(proc):
- raise subprocess.CalledProcessError(proc.returncode, ' '.join(cmd))
-
- def validate_image_layers(self):
- """
- Check if layers of container image exist in image_dir
- and have valid hash sum.
- """
- self.progress("Checking cached layers", value=0, logger=logger)
- for sum_type, sum_expected, path, _ignore in self.layers:
- logger.debug("Checking layer: %s", path)
- if not (os.path.exists(path)
- and utils.checksum(path, sum_type, sum_expected)):
- return False
- return True
-
- def fetch_layers(self):
- """
- Retrieve layers of container image.
- """
- # Check if layers have been downloaded
- if not self.validate_image_layers():
- self.download_image()
-
- def unpack(self, dest):
- """
- Extract image files from Docker image
-
- @param dest: Directory path where the files to be extraced
- """
- try:
- # Layers are in order - root layer first
- # Reference:
- # https://github.com/containers/image/blob/master/image/oci.go#L100
- self.fetch_layers()
-
- # Unpack to destination directory
- if self.output_format == 'dir':
- self.progress("Extracting container layers", value=50,
- logger=logger)
- utils.untar_layers(self.layers, dest, self.progress)
- elif self.output_format == 'qcow2':
- self.progress("Extracting container layers into qcow2 images",
- value=50, logger=logger)
- utils.extract_layers_in_qcow2(self.layers, dest, self.progress)
- else:
- raise Exception("Unknown format:" + self.output_format)
-
- except Exception:
- raise
-
- else:
- self.progress("Download and extract completed!", value=100,
- logger=logger)
- logger.info("Files are stored in: " + dest)
-
- finally:
- # Clean up
- if self.no_cache and self.images_dir != utils.DEFAULT_IMG_DIR:
- shutil.rmtree(self.images_dir)