Source code for COT.inject_config

#!/usr/bin/env python
#
# inject_config.py - Implements "cot inject-config" command
#
# February 2014, Glenn F. Matthews
# Copyright (c) 2014-2016 the COT project developers.
# See the COPYRIGHT.txt file at the top-level directory of this distribution
# and at https://github.com/glennmatthews/cot/blob/master/COPYRIGHT.txt.
#
# This file is part of the Common OVF Tool (COT) project.
# It is subject to the license terms in the LICENSE.txt file found in the
# top-level directory of this distribution and at
# https://github.com/glennmatthews/cot/blob/master/LICENSE.txt. No part
# of COT, including this file, may be copied, modified, propagated, or
# distributed except according to the terms contained in the LICENSE.txt file.

"""Implements "inject-config" command."""

import logging
import os.path
import shutil

from COT.add_disk import add_disk_worker
from COT.data_validation import ValueUnsupportedError, InvalidInputError
from COT.disks import create_disk
from COT.submodule import COTSubmodule

logger = logging.getLogger(__name__)


[docs]class COTInjectConfig(COTSubmodule): """Wrap configuration file(s) into a disk image embedded into the VM. Inherited attributes: :attr:`~COTGenericSubmodule.ui`, :attr:`~COTSubmodule.package`, :attr:`~COTSubmodule.output` Attributes: :attr:`config_file`, :attr:`secondary_config_file`, :attr:`extra_files` """
[docs] def __init__(self, ui): """Instantiate this submodule with the given UI. Args: ui (UI): User interface instance. """ super(COTInjectConfig, self).__init__(ui) self._config_file = None self._secondary_config_file = None self._extra_files = []
@property def config_file(self): """Primary configuration file. Raises: InvalidInputError: if the file does not exist InvalidInputError: if the platform described by :attr:`package` doesn't support configuration files. """ return self._config_file @config_file.setter def config_file(self, value): if value is not None: value = str(value) if not os.path.exists(value): raise InvalidInputError("Primary config file {0} not found!" .format(value)) if not self.vm.platform.CONFIG_TEXT_FILE: raise InvalidInputError( "Configuration file not supported for platform {0}" .format(self.vm.platform.__name__)) self._config_file = value @property def secondary_config_file(self): """Secondary configuration file. Raises: InvalidInputError: if the file does not exist InvalidInputError: if the platform described by :attr:`package` doesn't support secondary configuration files. """ return self._secondary_config_file @secondary_config_file.setter def secondary_config_file(self, value): if value is not None: value = str(value) if not os.path.exists(value): raise InvalidInputError("Secondary config file {0} not found!" .format(value)) if not self.vm.platform.SECONDARY_CONFIG_TEXT_FILE: raise InvalidInputError( "Secondary configuration file not supported " "for platform {0}".format(self.vm.platform.__name__)) self._secondary_config_file = value @property def extra_files(self): """Additional files to be embedded as-is. Raises: InvalidInputError: if any file in the list does not exist """ return self._extra_files @extra_files.setter def extra_files(self, values): for path in values: if not os.path.exists(path): raise InvalidInputError("File {0} not found!".format(path)) self._extra_files = values
[docs] def ready_to_run(self): """Check whether the module is ready to :meth:`run`. Returns: tuple: ``(True, ready_message)`` or ``(False, reason_why_not)`` """ if not (self.config_file or self.secondary_config_file or self.extra_files): return False, "No files specified - nothing to do!" return super(COTInjectConfig, self).ready_to_run()
[docs] def run(self): """Do the actual work of this submodule. Raises: InvalidInputError: if :func:`ready_to_run` reports ``False`` ValueUnsupportedError: if the :const:`~COT.platforms.GenericPlatform.BOOTSTRAP_DISK_TYPE` of the associated VM's :attr:`~COT.vm_description.VMDescription.platform` is not 'cdrom' or 'harddisk' LookupError: if unable to find a disk drive device to inject the configuration into. """ super(COTInjectConfig, self).run() vm = self.vm platform = vm.platform # Find the disk drive where the config should be injected # First, look for any previously-injected config disk to overwrite: if platform.BOOTSTRAP_DISK_TYPE == 'cdrom': (f, _, _, drive_device) = vm.search_from_filename('config.iso') elif platform.BOOTSTRAP_DISK_TYPE == 'harddisk': (f, _, _, drive_device) = vm.search_from_filename('config.vmdk') else: raise ValueUnsupportedError("bootstrap disk drive type", platform.BOOTSTRAP_DISK_TYPE, "'cdrom' or 'harddisk'") if f is not None: file_id = vm.get_id_from_file(f) self.ui.confirm_or_die( "Existing configuration disk '{0}' found.\n" "Continue and overwrite it?".format(file_id)) logger.warning("Overwriting existing config disk '%s'", file_id) else: file_id = None # Find the empty slot where we should inject the config drive_device = vm.find_empty_drive(platform.BOOTSTRAP_DISK_TYPE) if drive_device is None: raise LookupError("Could not find an empty {0} drive to " "inject the config into" .format(platform.BOOTSTRAP_DISK_TYPE)) (cont_type, drive_address) = vm.find_device_location(drive_device) # Copy config file(s) to per-platform name in working directory config_files = [] if self.config_file: dest = os.path.join(vm.working_dir, platform.CONFIG_TEXT_FILE) shutil.copy(self.config_file, dest) config_files.append(dest) if self.secondary_config_file: dest = os.path.join(vm.working_dir, platform.SECONDARY_CONFIG_TEXT_FILE) shutil.copy(self.secondary_config_file, dest) config_files.append(dest) # Extra files are packaged as-is config_files += self.extra_files # Package the config files into a disk image # pylint:disable=redefined-variable-type if platform.BOOTSTRAP_DISK_TYPE == 'cdrom': bootstrap_file = os.path.join(vm.working_dir, 'config.iso') disk_image = create_disk(disk_format='iso', path=bootstrap_file, files=config_files) elif platform.BOOTSTRAP_DISK_TYPE == 'harddisk': bootstrap_file = os.path.join(vm.working_dir, 'config.img') disk_image = create_disk(disk_format='raw', path=bootstrap_file, files=config_files) else: raise ValueUnsupportedError("bootstrap disk drive type", platform.BOOTSTRAP_DISK_TYPE, "'cdrom' or 'harddisk'") # Inject the disk image into the OVA, using "add-disk" functionality add_disk_worker( ui=self.ui, vm=vm, disk_image=disk_image, drive_type=platform.BOOTSTRAP_DISK_TYPE, file_id=file_id, controller=cont_type, address=drive_address, subtype=None, description='Configuration disk', diskname=None, )
[docs] def create_subparser(self): """Create 'inject-config' CLI subparser.""" p = self.ui.add_subparser( 'inject-config', aliases=['add-bootstrap'], help="Inject a configuration file into an OVF package", usage=self.ui.fill_usage("inject-config", [ "PACKAGE [-o OUTPUT] [-c CONFIG_FILE] " "[-s SECONDARY_CONFIG_FILE] [-e EXTRA_FILE [EXTRA_FILE2 ...]]", ]), description=""" Add one or more "bootstrap" configuration file(s) to the given OVF or OVA. These files will be packaged into a virtual hard disk, or virtual CD-ROM, as appropriate to the target platform. Any specified primary and secondary config files will be renamed if necessary to meet expectations of the target platform, while any files provided with the --extra-files option will be included as-is and will not be renamed.""") p.add_argument('-o', '--output', help="Name/path of new VM package to create " "instead of updating the existing package") p.add_argument('-c', '--config-file', help="Text file to embed as primary configuration") p.add_argument('-s', '--secondary-config-file', help="Text file to embed as secondary configuration" " (currently only used for IOS XR admin config)") p.add_argument('-e', '--extra-files', action='append', nargs='+', metavar=('EXTRA_FILE', 'EXTRA_FILE2'), help="Additional file(s) to include as-is") p.add_argument('PACKAGE', help="Package, OVF descriptor or OVA file to edit") p.set_defaults(instance=self)