Ejemplo n.º 1
0
    def cryptdisks_start_helper(self, emulated):
        """
        Test cryptdisks_start integration and emulation.

        This test requires the following line to be present in ``/etc/crypttab``::

         linux-utils /tmp/linux-utils.img /tmp/linux-utils.key discard,luks,noauto,readonly,tries=1
        """
        if not any(entry.target == TEST_TARGET_NAME and entry.source ==
                   TEST_IMAGE_FILE and entry.key_file == TEST_KEY_FILE
                   and 'luks' in entry.options for entry in parse_crypttab()):
            return self.skipTest(
                "/etc/crypttab isn't set up to test cryptdisks_start!")
        context = LocalContext()
        if emulated:
            # Disable the use of the `cryptdisks_start' program.
            context.find_program = MagicMock(return_value=[])
        # Generate the key file.
        with TemporaryKeyFile(filename=TEST_KEY_FILE):
            # Create the image file and the encrypted filesystem.
            create_image_file(filename=TEST_IMAGE_FILE,
                              size=coerce_size('10 MiB'))
            create_encrypted_filesystem(device_file=TEST_IMAGE_FILE,
                                        key_file=TEST_KEY_FILE)
            # Make sure the mapped device file doesn't exist yet.
            assert not os.path.exists(TEST_TARGET_DEVICE)
            # Unlock the encrypted filesystem using `cryptdisks_start'.
            if emulated:
                cryptdisks_start(context=context, target=TEST_TARGET_NAME)
            else:
                returncode, output = run_cli(cryptdisks_start_cli,
                                             TEST_TARGET_NAME)
                assert returncode == 0
            # Make sure the mapped device file has appeared.
            assert os.path.exists(TEST_TARGET_DEVICE)
            # Unlock the encrypted filesystem again (this should be a no-op).
            cryptdisks_start(context=context, target=TEST_TARGET_NAME)
            # Make sure the mapped device file still exists.
            assert os.path.exists(TEST_TARGET_DEVICE)
            # Lock the filesystem before we finish.
            if emulated:
                cryptdisks_stop(context=context, target=TEST_TARGET_NAME)
            else:
                returncode, output = run_cli(cryptdisks_stop_cli,
                                             TEST_TARGET_NAME)
                assert returncode == 0
            # Make sure the mapped device file has disappeared.
            assert not os.path.exists(TEST_TARGET_DEVICE)
            # Lock the filesystem again (this should be a no-op).
            cryptdisks_stop(context=context, target=TEST_TARGET_NAME)
            # Make sure the mapped device file is still gone.
            assert not os.path.exists(TEST_TARGET_DEVICE)
            # Test the error handling.
            for function in cryptdisks_start, cryptdisks_stop:
                self.assertRaises(
                    ValueError if emulated else ExternalCommandFailed,
                    function,
                    context=context,
                    target=TEST_UNKNOWN_TARGET,
                )
Ejemplo n.º 2
0
 def test_generate_key_file(self):
     """Test key file generation."""
     context = LocalContext(sudo=True)
     size = coerce_size('4 KiB')
     with TemporaryKeyFile(context=context,
                           filename=TEST_KEY_FILE,
                           size=size):
         # Make sure the key file was created with the correct size.
         assert os.path.getsize(TEST_KEY_FILE) == size
         # Ensure that the key file contains some randomness.
         cat = context.execute('cat', TEST_KEY_FILE, capture=True)
         random_bytes = set(cat.stdout)
         # Although I know that (by definition of randomness) the following
         # assertion can fail I nevertheless feel that it adds value :-p.
         assert len(random_bytes) > 10
Ejemplo n.º 3
0
def coerce_context(value):
    """
    Coerce the given value to an execution context.

    :param value: The value to coerce (an execution context created
                  by :mod:`executor.contexts` or :data:`None`).
    :returns: An execution context created by :mod:`executor.contexts`.
    :raises: :exc:`~exceptions.ValueError` when `value` isn't :data:`None`
             but also isn't a valid execution context.

    This function is used throughout the modules in the :mod:`linux_utils`
    package to abstract away the details of external command execution using
    `dependency injection`_:

    - If no execution context is provided (`value` is :data:`None`)
      :class:`executor.contexts.LocalContext` is used by default.

    - Callers can provide :class:`executor.contexts.RemoteContext` in
      which case the :mod:`linux_utils` package seamlessly adapts to
      command execution on a remote system over :man:`ssh`.

    .. _dependency injection: https://en.wikipedia.org/wiki/Dependency_injection
    """
    if value is None:
        value = LocalContext()
    if not isinstance(value, AbstractContext):
        msg = "Expected execution context or None, got %r instead!"
        raise ValueError(msg % type(value))
    return value
Ejemplo n.º 4
0
def find_graphical_context():
    """
    Create a command execution context for the current graphical session.

    :returns: A :class:`~executor.contexts.LocalContext` object.

    This function scans the process tree for processes that are running in a
    graphical session and collects information about graphical sessions from
    each of these processes. The collected information is then ranked by
    "popularity" (number of occurrences) and the most popular information is
    used to create a command execution context that targets the graphical
    session.
    """
    options = {}
    # Collect information about graphical sessions from running processes.
    matches = collections.defaultdict(int)
    for process in find_processes():
        environment = dict((k, v) for k, v in process.environ.items()
                           if k in REQUIRED_VARIABLES and v)
        if environment:
            hashable_environment = tuple(sorted(environment.items()))
            matches[(process.user_ids.real, hashable_environment)] += 1
    ordered = sorted((counter, key) for key, counter in matches.items())
    if ordered:
        # Pick the most popular graphical session.
        counter, key = ordered[-1]
        uid, environment = key
        # Apply the user ID to the context?
        if os.getuid() != uid:
            options['uid'] = uid
        # Apply the environment to the context.
        options['environment'] = dict(environment)
    return LocalContext(**options)
Ejemplo n.º 5
0
    def context(self):
        """
        An execution context created using :mod:`executor.contexts`.

        The value of :attr:`context` defaults to a
        :class:`~executor.contexts.LocalContext` object with the following
        characteristics:

        - The working directory of the execution context is set to the
          value of :attr:`directory`.

        - The environment variable given by :data:`DIRECTORY_VARIABLE` is set
          to the value of :attr:`directory`.

        :raises: :exc:`.MissingPasswordStoreError` when :attr:`directory`
                 doesn't exist.
        """
        # Make sure the directory exists.
        self.ensure_directory_exists()
        # Prepare the environment variables.
        environment = {DIRECTORY_VARIABLE: self.directory}
        try:
            # Try to enable the GPG agent in headless sessions.
            environment.update(get_gpg_variables())
        except Exception:
            # If we failed then let's at least make sure that the
            # $GPG_TTY environment variable is set correctly.
            environment.update(GPG_TTY=execute("tty", capture=True, check=False, tty=True, silent=True))
        return LocalContext(directory=self.directory, environment=environment)
Ejemplo n.º 6
0
 def source_context(self):
     """
     The execution context of the system that is being backed up (the source).
     This is expected to be an execution context created by
     :mod:`executor.contexts`. It defaults to
     :class:`executor.contexts.LocalContext`.
     """
     return LocalContext()
Ejemplo n.º 7
0
    def context(self):
        """
        An execution context created using :mod:`executor.contexts`.

        The value of this property defaults to a
        :class:`~executor.contexts.LocalContext` object.
        """
        return LocalContext()
Ejemplo n.º 8
0
 def check_mirror_url(self, url):
     """Check whether the given URL looks like a mirror URL for the system running the test suite."""
     if not hasattr(self, 'context'):
         self.context = LocalContext()
     if self.context.distributor_id == 'debian':
         self.check_debian_mirror(url)
     elif self.context.distributor_id == 'ubuntu':
         self.check_ubuntu_mirror(url)
     else:
         raise Exception("Unsupported platform!")
Ejemplo n.º 9
0
    def context(self):
        """
        The command execution context from which the remote system is being controlled.

        The computed value of this property is a command execution context
        created by :mod:`executor.contexts`. When :attr:`ssh_proxy` is set this
        will be a :class:`~executor.contexts.RemoteContext` object, otherwise
        it will be a :class:`~executor.contexts.LocalContext` object.
        """
        return RemoteContext(
            ssh_alias=self.ssh_proxy) if self.ssh_proxy else LocalContext()
Ejemplo n.º 10
0
def coerce_context(value):
    """
    Coerce a value to an execution context.

    :param value: The value to coerce (an execution context created
                  by :mod:`executor.contexts` or :data:`None`).
    :returns: An execution context created by :mod:`executor.contexts`.
    :raises: :exc:`~exceptions.ValueError` when `value` isn't :data:`None`
             but also isn't a valid execution context.
    """
    if value is None:
        value = LocalContext()
    if not isinstance(value, AbstractContext):
        msg = "Expected execution context or None, got %r instead!"
        raise ValueError(msg % type(value))
    return value
Ejemplo n.º 11
0
def main():
    """Command line interface for the ``apt-smart`` program."""
    # Initialize logging to the terminal and system log.
    coloredlogs.install(syslog=True)
    # Command line option defaults.
    context = LocalContext()
    updater = AptMirrorUpdater(context=context)
    limit = MAX_MIRRORS
    url_char_len = URL_CHAR_LEN
    actions = []
    # Parse the command line arguments.
    try:
        options, arguments = getopt.getopt(sys.argv[1:],
                                           'r:fF:blL:c:aux:m:vVR:qh', [
                                               'remote-host=',
                                               'find-current-mirror',
                                               'find-best-mirror',
                                               'file-to-read=',
                                               'list-mirrors',
                                               'url-char-len=',
                                               'change-mirror',
                                               'auto-change-mirror',
                                               'update',
                                               'update-package-lists',
                                               'exclude=',
                                               'max=',
                                               'verbose',
                                               'version',
                                               'create-chroot=',
                                               'quiet',
                                               'help',
                                           ])
        for option, value in options:
            if option in ('-r', '--remote-host'):
                if actions:
                    msg = "The %s option should be the first option given on the command line!"
                    raise Exception(msg % option)
                context = RemoteContext(value)
                updater = AptMirrorUpdater(context=context)
            elif option in ('-f', '--find-current-mirror'):
                actions.append(
                    functools.partial(report_current_mirror, updater))
            elif option in ('-F', '--file-to-read'):
                updater.custom_mirror_file_path = value
            elif option in ('-b', '--find-best-mirror'):
                actions.append(functools.partial(report_best_mirror, updater))
            elif option in ('-l', '--list-mirrors'):
                actions.append(
                    functools.partial(report_available_mirrors, updater))
            elif option in ('-L', '--url-char-len'):
                url_char_len = int(value)
            elif option in ('-c', '--change-mirror'):
                if value.strip().startswith(('http://', 'https://', 'ftp://')):
                    actions.append(
                        functools.partial(updater.change_mirror, value))
                else:
                    raise Exception("\'%s\' is not a valid mirror URL" % value)
            elif option in ('-a', '--auto-change-mirror'):
                actions.append(updater.change_mirror)
            elif option in ('-u', '--update', '--update-package-lists'):
                actions.append(updater.smart_update)
            elif option in ('-x', '--exclude'):
                actions.insert(0,
                               functools.partial(updater.ignore_mirror, value))
            elif option in ('-m', '--max'):
                limit = int(value)
            elif option in ('-v', '--verbose'):
                coloredlogs.increase_verbosity()
            elif option in ('-V', '--version'):
                output("Version: %s on Python %i.%i", updater_version,
                       sys.version_info[0], sys.version_info[1])
                return
            elif option in ('-R', '--create-chroot'):
                actions.append(functools.partial(updater.create_chroot, value))
            elif option in ('-q', '--quiet'):
                coloredlogs.decrease_verbosity()
            elif option in ('-h', '--help'):
                usage(__doc__)
                return
            else:
                assert False, "Unhandled option!"
        if not actions:
            usage(__doc__)
            return
        # Propagate options to the Python API.
        updater.max_mirrors = limit
        updater.url_char_len = url_char_len
    except Exception as e:
        warning("Error: Failed to parse command line arguments! (%s)" % e)
        sys.exit(1)
    # Perform the requested action(s).
    try:
        for callback in actions:
            callback()
    except Exception:
        logger.exception("Encountered unexpected exception! Aborting ..")
        sys.exit(1)
Ejemplo n.º 12
0
 def test_coerce_context(self):
     """Test coercion of execution contexts."""
     assert isinstance(coerce_context(None), LocalContext)
     instance = LocalContext()
     assert coerce_context(instance) is instance
     self.assertRaises(ValueError, coerce_context, 1)
Ejemplo n.º 13
0
def main():
    """Command line interface for the ``apt-mirror-updater`` program."""
    # Initialize logging to the terminal and system log.
    coloredlogs.install(syslog=True)
    # Command line option defaults.
    context = LocalContext()
    updater = AptMirrorUpdater(context=context)
    limit = MAX_MIRRORS
    actions = []
    # Parse the command line arguments.
    try:
        options, arguments = getopt.getopt(sys.argv[1:], 'r:fblc:aux:m:vqh', [
            'remote-host=',
            'find-current-mirror',
            'find-best-mirror',
            'list-mirrors',
            'change-mirror',
            'auto-change-mirror',
            'update',
            'update-package-lists',
            'exclude=',
            'max=',
            'verbose',
            'quiet',
            'help',
        ])
        for option, value in options:
            if option in ('-r', '--remote-host'):
                if actions:
                    msg = "The %s option should be the first option given on the command line!"
                    raise Exception(msg % option)
                context = RemoteContext(value)
                updater = AptMirrorUpdater(context=context)
            elif option in ('-f', '--find-current-mirror'):
                actions.append(
                    functools.partial(report_current_mirror, updater))
            elif option in ('-b', '--find-best-mirror'):
                actions.append(functools.partial(report_best_mirror, updater))
            elif option in ('-l', '--list-mirrors'):
                actions.append(
                    functools.partial(report_available_mirrors, updater))
            elif option in ('-c', '--change-mirror'):
                actions.append(functools.partial(updater.change_mirror, value))
            elif option in ('-a', '--auto-change-mirror'):
                actions.append(updater.change_mirror)
            elif option in ('-u', '--update', '--update-package-lists'):
                actions.append(updater.smart_update)
            elif option in ('-x', '--exclude'):
                actions.insert(0,
                               functools.partial(updater.ignore_mirror, value))
            elif option in ('-m', '--max'):
                limit = int(value)
            elif option in ('-v', '--verbose'):
                coloredlogs.increase_verbosity()
            elif option in ('-q', '--quiet'):
                coloredlogs.decrease_verbosity()
            elif option in ('-h', '--help'):
                usage(__doc__)
                return
            else:
                assert False, "Unhandled option!"
        if not actions:
            usage(__doc__)
            return
        # Propagate options to the Python API.
        updater.max_mirrors = limit
    except Exception as e:
        warning("Error: Failed to parse command line arguments! (%s)" % e)
        sys.exit(1)
    # Perform the requested action(s).
    try:
        for callback in actions:
            callback()
    except Exception:
        logger.exception("Encountered unexpected exception! Aborting ..")
        sys.exit(1)
Ejemplo n.º 14
0
 def context(self):
     """An execution context created by :mod:`executor.contexts`."""
     return LocalContext()