Exemplo n.º 1
0
    def __init__(self, data={}, plan=None, name=None):
        """ Initialize and check the step data """
        super().__init__(name=name, parent=plan)
        # Initialize data
        self.plan = plan
        self.data = data
        self._status = None
        self._plugins = []

        # Create an empty step by default (can be updated from cli)
        if self.data is None:
            self.data = [{'name': tmt.utils.DEFAULT_NAME}]
        # Convert to list if only a single config provided
        elif isinstance(self.data, dict):
            # Give it a name unless defined
            if not self.data.get('name'):
                self.data['name'] = tmt.utils.DEFAULT_NAME
            self.data = [self.data]
        # Shout about invalid configuration
        elif not isinstance(self.data, list):
            raise GeneralError(f"Invalid '{self}' config in '{self.plan}'.")

        # Final sanity checks
        for data in self.data:
            # Set 'how' to the default if not specified
            if data.get('how') is None:
                data['how'] = self.how
            # Ensure that each config has a name
            if 'name' not in data and len(self.data) > 1:
                raise GeneralError(f"Missing '{self}' name in '{self.plan}'.")
Exemplo n.º 2
0
    def plugin_install(self, name):
        """ Install a vagrant plugin if it's not installed yet.
        """
        plugin = f'{self.executable}-{name}'
        command = ['plugin', 'install']
        try:
            # is it already present?
            run = f"{self.executable} {command[0]} list | grep '^{plugin} '"
            return self.run(f"bash -c \"{run}\"")
        except GeneralError:
            pass

        try:
            # try to install it
            return self.run_vagrant(command[0], command[1], plugin)
        except GeneralError as error:
            # Let's work-around the error handling limitation for now
            # by getting the output manually
            command = ' '.join([self.executable] + command + [plugin])

            out, err = self.run(f"bash -c \"{command}; :\"")

            if re.search(r"Conflicting dependency chains:", err) is None:
                raise error
            raise GeneralError(
                'Dependency conflict detected:\n'
                'Please install vagrant plugins from one source only (hint: `dnf remove rubygem-fog-core`).'
            )
Exemplo n.º 3
0
    def execute(self, *args, **kwargs):
        if not self.instance:
            raise GeneralError('Could not execute without a provisioned VM.')

        return self.run(['ssh'] + self.ssh_args + [self.ssh_user_host] +
                        [f'{self.shell_env} {self.join(args)}'],
                        shell=False)[0].rstrip()
Exemplo n.º 4
0
    def install(self, packages):
        """ Install specified package(s)
        """
        if type(packages) is list:
            packages = ' '.join(packages)

        ## TODO: remove this after run(shell=True) is in provision.prepare()
        try:
            self.plan.provision.prepare('shell', f"rpm -V {packages}")
        except GeneralError:
            self.plan.provision.prepare('shell', f"dnf install -y {packages}")
        return
        ## <

        failed = False
        logf = os.path.join(self.workdir, 'prepare.log')
        try:
            self.plan.provision.prepare(
                'shell',
                f"set -o pipefail; ( rpm -V {packages} || sudo dnf install -y {packages} ) 2>&1 | tee -a '{logf}'"
            )
        except GeneralError:
            failed = True

        self.plan.provision.sync_workdir_from_guest()

        output = open(logf).read()

        if failed:
            raise GeneralError(f'Install failed:\n{output}')

        self.debug(logf, output, 'yellow')
Exemplo n.º 5
0
    def execute(self, *args, **kwargs):
        if not self.container_name:
            raise GeneralError(
                'Could not execute without provisioned container')

        self.info('args', self.join(args), 'red')
        self.podman(f'exec {self.container_name} {self.join(args)}')
Exemplo n.º 6
0
    def get_compose_id(compose_id_url):
        response = requests.get(f'{compose_id_url}')

        if not response:
            raise GeneralError(f'Failed to find compose ID for '
                               f"'{name}' at '{compose_id_url}'")

        return response.text
Exemplo n.º 7
0
    def go(self):
        """ Execute actual provisioning """
        self.init()
        self.info(f'Provisioning {self.executable}, {self.vf_name}',
                  self.vf_read())
        out, err = self.run_vagrant('up')

        status = self.status()
        if status != 'running':
            raise GeneralError(
                f'Failed to provision (status: {status}), log:\n{out}\n{err}')
Exemplo n.º 8
0
    def execute(self, *args, **kwargs):
        """ Execute given commands in podman via shell """
        if not self.container_name:
            raise GeneralError(
                'Could not execute without provisioned container')

        # Note that we MUST run commands via bash, so variables
        # work as expected
        self.podman(['exec'] + self.podman_env +
                    [self.container_name, 'sh', '-c',
                     self.join(args)], **kwargs)
Exemplo n.º 9
0
    def __init__(self, step, data):
        """ Store plugin name, data and parent step """

        # Ensure that plugin data contains name
        if 'name' not in data:
            raise GeneralError("Missing 'name' in plugin data.")

        # Store name, data and parent step
        super().__init__(parent=step, name=data['name'])
        self.data = data
        self.step = step
Exemplo n.º 10
0
    def __init__(self, data=None, plan=None, name=None):
        """ Initialize and check the step data """
        super().__init__(name=name, parent=plan)
        # Initialize data
        self.plan = plan
        self.data = data or {}
        self._status = None
        self._plugins = []

        # Create an empty step by default (can be updated from cli)
        if self.data is None:
            self.data = [{'name': tmt.utils.DEFAULT_NAME}]
        # Convert to list if only a single config provided
        elif isinstance(self.data, dict):
            # Give it a name unless defined
            if not self.data.get('name'):
                self.data['name'] = tmt.utils.DEFAULT_NAME
            self.data = [self.data]
        # Shout about invalid configuration
        elif not isinstance(self.data, list):
            raise GeneralError(f"Invalid '{self}' config in '{self.plan}'.")

        # Add default unique names even to multiple configs so that the users
        # don't need to specify it if they don't care about the name
        for i, data in enumerate(self.data):
            if 'name' not in data:
                data['name'] = f'{tmt.utils.DEFAULT_NAME}-{i}'

        # Final sanity checks
        for data in self.data:
            # Set 'how' to the default if not specified
            if data.get('how') is None:
                data['how'] = self.how
            # Ensure that each config has a name
            if 'name' not in data and len(self.data) > 1:
                raise GeneralError(f"Missing 'name' in the {self} step config "
                                   f"of the '{self.plan}' plan.")
Exemplo n.º 11
0
    def __init__(self, step, data):
        """ Store plugin name, data and parent step """

        # Ensure that plugin data contains name
        if 'name' not in data:
            raise GeneralError("Missing 'name' in plugin data.")

        # Store name, data and parent step
        super().__init__(parent=step, name=data['name'])
        self.data = data
        self.step = step

        # Initialize plugin order
        try:
            self.order = int(self.data['order'])
        except (ValueError, KeyError):
            self.order = tmt.utils.DEFAULT_PLUGIN_ORDER
Exemplo n.º 12
0
    def status(self, status=None):
        """
        Get and set current step status

        The meaning of the status is as follows:
        todo ... config, data and command line processed (we know what to do)
        done ... the final result of the step stored to workdir (we are done)
        """
        # Update status
        if status is not None:
            # Check for valid values
            if status not in ['todo', 'done']:
                raise GeneralError(f"Invalid status '{status}'.")
            # Show status only if changed
            elif self._status != status:
                self._status = status
                self.debug('status', status, color='yellow', level=2)
        # Return status
        return self._status
Exemplo n.º 13
0
def guess_image_url(name):
    """ Guess image url for given name """
    def get_compose_id(compose_id_url):
        response = requests.get(f'{compose_id_url}')

        if not response:
            raise GeneralError(f'Failed to find compose ID for '
                               f"'{name}' at '{compose_id_url}'")

        return response.text

    # map fedora, rawhide or fedora-rawhide to latest rawhide image
    if re.match(r'^(fedora|fedora-rawhide|rawhide)$', name, re.IGNORECASE):
        compose_id = get_compose_id(RAWHIDE_ID)
        compose_name = compose_id.replace('Fedora-Rawhide',
                                          'Fedora-Cloud-Base-Rawhide')
        return f'{RAWHIDE_IMAGE_URL}/{compose_name}.x86_64.qcow2'

    raise GeneralError("Could not map '{name}' to compose")
Exemplo n.º 14
0
    def prepare(self, how, what):
        """ add single 'preparator' and run it """

        name = 'prepare'
        cmd = 'provision'

        self.vf_backup("Prepare")

        # decide what to do
        if how == 'ansible':
            name = how

            # Prepare verbose level based on the --debug option count
            verbose = self.opt('debug') * 'v' if self.opt('debug') else 'false'
            self.add_config_block(cmd, name, f'become = true',
                                  self.kve('become_user', self.data['user']),
                                  self.kve('playbook', what),
                                  self.kve('verbose', verbose))
            # I'm not sure whether this is needed:
            # run: 'never'

        else:
            if self.is_uri(what):
                method = 'path'
            else:
                method = 'inline'

            self.add_config('vm', cmd, quote(name), self.kv('type', how),
                            self.kv('privileged', 'true'),
                            self.kv('run', 'never'), self.kv(method, what))

        try:
            self.validate()
        except GeneralError as error:
            self.vf_restore()
            raise GeneralError(
                f'Invalid input for vagrant prepare ({how}):\n{what}')

        return self.run_vagrant(cmd, f'--{cmd}-with', name)
Exemplo n.º 15
0
    def __init__(self, data, step):
        super(ProvisionTestcloud, self).__init__(data, step)

        self._prepare_map = {
            'ansible': self._prepare_ansible,
            'shell': self._prepare_shell,
        }

        # Get image from provision options
        if not self.option('image'):
            raise GeneralError('No image specified')

        # Initialize testcloud image
        self.image = None

        # Testcloud instance and ip
        self.instance = None
        self.ip = None

        # Default user
        self.user = self.option('user') or DEFAULT_USER

        # Create ssh key
        self.ssh_key = os.path.join(self.provision_dir, 'id_rsa')
        self.ssh_pubkey = os.path.join(self.provision_dir, 'id_rsa.pub')

        # Common ssh args
        self.ssh_args = (f'-i {self.ssh_key} -o StrictHostKeyChecking=no '
                         f'-o UserKnownHostsFile=/dev/null')

        # Make sure required directories exist
        os.makedirs(TESTCLOUD_DATA, exist_ok=True)
        os.makedirs(TESTCLOUD_IMAGES, exist_ok=True)

        # Make sure libvirt domain template exists
        ProvisionTestcloud._create_template()
Exemplo n.º 16
0
    def execute(self, *args, **kwargs):
        if not self.instance:
            raise GeneralError('Could not execute without provisioned VM')

        return self._ssh_run(f'{self.join(args)}')
Exemplo n.º 17
0
    def go(self):
        super(ProvisionTestcloud, self).go()

        # If image does not start with http/https/file, consider it a mapping
        # value and try to guess the URL
        image_url = self.option('image') or DEFAULT_IMAGE
        if not re.match(r'^(?:https?|file)://.*', image_url):
            image_url = guess_image_url(image_url)

        # Import testcloud module only when needed (until we have a
        # separate package for each plugin)
        try:
            import testcloud.image
            import testcloud.instance
        except ImportError:
            raise GeneralError(
                "Install 'testcloud' to provision using this method.")

        # Get configuration
        config = testcloud.config.get_config()

        # Make sure download progress is disabled, so it
        # does not spoil our logging
        config.DOWNLOAD_PROGRESS = False

        # Configure to tmt's storage directories
        config.DATA_DIR = TESTCLOUD_DATA
        config.STORE_DIR = TESTCLOUD_IMAGES

        # Initialize testcloud image
        self.image = testcloud.image.Image(image_url)

        # Show which image we are using
        self.info('image', f'{self.image.name}', 'green')

        status = f'{self.image.name}'
        if not os.path.exists(self.image.local_path):
            self.info('status', 'downloading', 'green')

        # prepare testcloud image
        try:
            self.image.prepare()
        except FileNotFoundError:
            raise GeneralError(
                f"Could not find image '{self.image.local_path}'")

        self.instance = testcloud.instance.Instance(self.instance_name,
                                                    image=self.image)

        # generate ssh key
        self.run(f'ssh-keygen -f {self.ssh_key} -N ""')

        with open(self.ssh_pubkey, 'r') as pubkey:
            config.USER_DATA = USER_DATA.format(user_name=self.user,
                                                public_key=pubkey.read())

        self.info('status', 'booting', 'green')
        self.instance.ram = self.option('memory') or DEFAULT_MEMORY
        self.instance.disk_size = DEFAULT_DISK_SIZE
        self.instance.prepare()
        self.instance.spawn_vm()

        try:
            self.instance.start(DEFAULT_BOOT_TIMEOUT)
        except testcloud.exceptions.TestcloudInstanceError:
            # TODO: find out how to get detailed information about boot problem
            raise GeneralError('Failed to boot instance')

        self.ip = self.instance.get_ip()
        self.instance.create_ip_file(self.ip)
        self.ssh_user_host = f'{self.user}@{self.instance.get_ip()}'

        for i in range(1, DEFAULT_SSH_CONNECT_TIMEOUT):
            try:
                self.execute('whoami')
                break
            except GeneralError:
                self.debug('failed to connect to machine, retrying')
            time.sleep(1)

        if i == DEFAULT_BOOT_TIMEOUT:
            raise GeneralError(
                'Failed to login to the machine in {DEFAULT_BOOT_TIMEOUT}s')

        self.info('instance', self.ssh_user_host, 'green')