Exemple #1
0
def run_playbook(playbook_location, verbose, vault_pass=None):
    """
    Runs a playbook in a change root.

    :param playbook_location: The location of the playbook file.
    :param verbose: True, if a more verbose output should be used.
    :param vault_pass: If the playbook references an encrypted Ansible vault, this is the password to decrypt it.
    """
    # Some distributions (e.g. Arch) have fewer bin paths in the PATH variable.
    # This causes plain ubuntu/debian-chroots to fail. Since this is what
    # Ansible does, so we extend the path accordingly.
    env = os.environ.copy()
    env["PATH"] += ':/usr/sbin:/sbin:/bin'

    inventory_file_path = os.path.join(playbook_location, 'hosts')
    playbook_file_path = os.path.join(playbook_location, 'main.yml')
    vars_reference = '@' + os.path.join(playbook_location, 'slingring_vars.yml')
    user_vars_reference = '@' + os.path.join(playbook_location, 'slingring_user_vars.yml')
    secret_user_vars_reference = '@' + os.path.join(playbook_location, 'slingring_user_secrets.yml')
    cmd = ['sudo', 'ansible-playbook', '--inventory', inventory_file_path,
           playbook_file_path, '--extra-vars', vars_reference, '--extra-vars', user_vars_reference]
    with tempfile.TemporaryDirectory() as tempdir:
        if vault_pass:
            cmd.append('--extra-vars')
            cmd.append(secret_user_vars_reference)
            cmd.append('--vault-password-file')
            random_string = key.create_random_password(8)
            pipe_path = os.path.join(tempdir, random_string)
            cmd.append(pipe_path)
            PipeThread(pipe_path, vault_pass).start()
        run_command(cmd, 'ansible_phase', verbose, env)
Exemple #2
0
def execute_in_schroot(name, command, sudo, phase, verbose, env=None):
    """
    Executes the given command string in the given schroot.
    :param name: The schroot name.
    :param command: The command as a single string.
    :param sudo: True, if the command shall be executed as root.
    :param phase: a message key which describes the current phase. This is used if something fails.
    :param verbose: True, if a more verbose output is desired.
    :param env: A dictionary containing environment variables which should be present within the schroot
                for the time of execution (e.g. { 'VARIABLE': 'content' })
    """
    effective_command = []
    if sudo:
        effective_command.append('sudo')
    effective_command.append('schroot')
    effective_command.append('-c')
    effective_command.append(name)
    effective_command.append('--directory')
    effective_command.append('/')
    effective_command.append('--')
    effective_command.append('/bin/bash')
    effective_command.append('-c')
    environment_exports = str()
    if env:
        for variable_name, variable_value in env.items():
            environment_exports += "export {}={};".format(
                variable_name, variable_value)
    effective_command.append(environment_exports + command)
    run_command(effective_command, phase, verbose)
Exemple #3
0
def _write_config_file_as_root(config_parser, path, phase, verbose):
    with tempfile.TemporaryDirectory() as tempdir:
        file_name = os.path.join(tempdir, 'temp.conf')
        with open(file_name, 'w') as temp_file:
            config_parser.write(temp_file, space_around_delimiters=False)
        command = ['sudo', 'cp', file_name, path]
        run_command(command, phase, verbose)
Exemple #4
0
def _copy_initializers_to_chroot(source_directory, target_directory, verbose):
    copied_initializer_scripts = []
    for file in os.listdir(source_directory):
        file_path = os.path.join(source_directory, file)
        run_command(['sudo', 'cp', file_path, target_directory],
                    'copy-initializers-phase', verbose)
        copied_initializer_scripts.append(file)
    return sorted(copied_initializer_scripts)
Exemple #5
0
def umount(mount_point, phase_key, verbose):
    """
    Unmounts a mount point.
    :param mount_point: The mount point (e.g. /mnt/disk1)
    :param phase_key: a message key which describes the current phase. This is used if something fails.
    :param verbose: True, if a more verbose output is desired.
    """
    umount_cmd = ['sudo', 'umount', mount_point]
    run_command(umount_cmd, phase_key, verbose)
Exemple #6
0
def contains_active_mount_point(mount_point):
    """
    Checks if the given mount point contains an active mount. E.g. if /mnt/ is given and
    something is mounted in /mnt/foo/bar, this will return True
    :param mount_point: The directory to check.
    :return: True, if something is mounted beneath the given mount_point.
    """
    output = run_command('mount', 'mount-check', False).stdout.decode('utf-8')
    return mount_point in output
Exemple #7
0
def run_ansible(universe_name, chroot_path, user_vars, user_secrets,
                slingring_vars, verbose):
    """
    Runs the universe playbook on the given chroot.
    :param universe_name: The universe name.
    :param chroot_path: The path to the chroot.
    :param user_vars: A dictionary containing user vars (key: name, value: value)
    :param user_secrets: A dictionary containing user secrets (key: name, value: value)
    :param slingring_vars: A dictionary containing slingring vars (key: name, value: value)
    :param verbose: True, if a more verbose output is desired.
    """
    print(_('config-files'))
    with open(playbook_hosts_file_path(universe_name), 'w') as hosts_file:
        hosts_file.write('"{}" {}'.format(chroot_path,
                                          " ansible_connection=chroot"))

    # create ansible variable files
    playbook_directory = playbook_directory_path(universe_name)
    run_command(['mkdir', '-p', playbook_directory], 'create-cache-dir-phase',
                verbose)
    ansible.write_vars_file(playbook_user_vars_path(universe_name), user_vars,
                            'user_vars')
    ansible.write_vars_file(playbook_slingring_vars_path(universe_name),
                            slingring_vars, 'slingring')

    if user_secrets:
        password = key.create_random_password(20)
        ansible.write_vars_vault_file(
            playbook_user_secrets_path(universe_name), user_secrets, password,
            'user_secrets', 'write-user-secret-phase', verbose)
    else:
        password = None

    # mount chroot
    print(_('mount-chroot'))
    try:
        mount(chroot_path, verbose)
        # run ansible
        print(_('ansible'))
        ansible.run_playbook(playbook_directory, verbose, password)
        print(_('umount-chroot'))
    finally:
        umount(chroot_path, verbose)
Exemple #8
0
def change_schroot_name(current_name, new_name, current_path, new_path, phase,
                        verbose):
    """
    Changes the name of the schroot config to the given value. Also renames the config file.
    :param current_name: The current schroot name.
    :param new_name: The new schroot name.
    :param current_path: The current path to the schroot configuration.
    :param new_path: The new path to the schroot configuration.
    :param phase: a message key which describes the current phase. This is used if something fails.
    :param verbose: True, if a more verbose output is desired.
    """
    config = configparser.ConfigParser()
    config.read(current_path)
    config.add_section(new_name)
    config[new_name] = config[current_name]
    config[new_name]['description'] = new_name + ' Slingring Universe'
    config.remove_section(current_name)
    run_command(['sudo', 'rm', current_path], phase, verbose)
    _write_config_file_as_root(config, new_path, phase, verbose)
Exemple #9
0
def mount(source, mount_point, phase_key, verbose, bind=False, fstype=None):
    """
    Mounts a given source onto an mount point.
    :param source: The source (e.g. /dev/sda1)
    :param mount_point: The mount point (e.g. /mnt/disk1)
    :param phase_key: a message key which describes the current phase. This is used if something fails.
    :param verbose: True, if a more verbose output is desired.
    :param bind: If bind option shall be used.
    :param fstype: The filesystem type.
    """
    mount_cmd = ['sudo', 'mount']
    if bind:
        mount_cmd.append('-o')
        mount_cmd.append('bind')
    if fstype:
        mount_cmd.append('-t')
        mount_cmd.append(fstype)
    mount_cmd.append(source)
    mount_cmd.append(mount_point)

    run_command(mount_cmd, phase_key, verbose)
Exemple #10
0
def bootstrap(chroot_path, seed_dictionary, verbose, temp_dir=None):
    """
    Bootstraps a chroot into the given directory. If the parent base directory
    does not exist, it will be created.
    :param chroot_path: The path of the created chroot.
    :param seed_dictionary: The universe description as a dictionary.
    :param verbose: True, if a more verbose output is desired.
    :param temp_dir:
    """
    run_command(['sudo', 'mkdir', '-p', chroot_path],
                'create-base-directory-phase', verbose)
    image_variant = seed_dictionary[
        'variant'] if 'variant' in seed_dictionary else None
    debootstrap_dir = TemporaryDirectory(dir=temp_dir)
    with debootstrap_dir as debootstrap_dir_path:
        base_image_path = os.path.join(debootstrap_dir_path, 'base_image')
        mnt.mount('none',
                  debootstrap_dir_path,
                  'tmpfs-mount-phase',
                  verbose,
                  fstype='tmpfs')

        # create a dedicated directory inside the tmpfs, so we can mv it later
        run_command(['sudo', 'mkdir', base_image_path],
                    'create-base-temp-directory-phase', verbose)
        debootstrap.debootstrap(base_image_path, seed_dictionary['arch'],
                                image_variant, seed_dictionary['suite'],
                                seed_dictionary['mirror'], 'debootstrap-phase',
                                verbose)

        run_command(['sudo', 'mv', base_image_path, '-T', chroot_path],
                    'mv-debootstrap-dir-phase', verbose)
        mnt.umount(debootstrap_dir_path, 'tmpfs-umount-phase', verbose)
Exemple #11
0
def debootstrap(path, architecture, variant, suite, mirror, phase, verbose):
    """
    Creates a Debian-based chroot in the given location.

    Find more details about the possible parameters in man debootstrap(8)

    :param path: The target path (e.g. /seu/chrootname). Will be created if it does not exist.
    :param architecture: The architecture (e.g. amd64, i386)
    :param variant: the bootstrap variant (e.g. minbase)
    :param suite: the suite name (e.g. xenial for ubuntu 16.04 lts)
    :param mirror: the image mirror (e.g. http://de.archive.ubuntu.com/ubuntu)
    :param phase: a message key which describes the current phase. This is used if something fails.
    :param verbose: True, if a more verbose output is desired.
    """
    if variant is not None:
        variant_cmd = '--variant=' + variant
    else:
        variant_cmd = ''

    arch_cmd = '--arch=' + architecture

    cmd = ['sudo', 'debootstrap', variant_cmd, arch_cmd, suite, path, mirror]

    run_command(cmd, phase, verbose)
Exemple #12
0
def remove_universe(universe_name, verbose):
    """
    Removes a universe from the local machine.
    :param universe_name: The name of the universe which should be removed.
    :param verbose: True, for more verbose output.
    """
    print(_('remove-intro').format(universe_name))

    installation_configuration_path = installation_file_path(universe_name)

    if not os.path.exists(installation_configuration_path):
        print(_('universe-does-not-exist'))
        exit(1)

    installation_path = configuration.read_configuration(
        installation_configuration_path)['location']
    schroot_path = schroot_config_file_path(universe_name)

    if mount.contains_active_mount_point(session_mount_point(universe_name)) \
            or mount.contains_active_mount_point(installation_path):
        print(_('still-mounted-error'))
        exit(1)

    if os.path.exists(installation_path):
        run_command(['sudo', 'rm', '-rf', installation_path], 'deletion-phase',
                    verbose)
        print(_('installation-path-removed'))
    else:
        print(_('installation-path-does-not-exist'))

    if os.path.exists(schroot_path):
        run_command(['sudo', 'rm', schroot_path], 'deletion-phase', verbose)
        print(_('schroot-config-removed'))
    else:
        print(_('schroot-config-does-not-exist'))

    local_universe_path = local_universe_dir(universe_name)
    if os.path.exists(local_universe_path):
        run_command(['rm', '-rf', local_universe_path], 'deletion-phase',
                    verbose)
        print(_('local-cache-removed'))
    else:
        print(_('local-cache-does-not-exist'))
Exemple #13
0
def upgrade_universe(seed_path, universe_name, verbose):
    """
    Replaces the local seed of an existing universe with a newer
    version and runs the Ansible playbook on the given universe's chroot.
    :param seed_path: The path to the universe seed directory as string.
    :param universe_name: The name of the universe
    :param verbose: True, for more verbose output.
    """
    old_universe_name = universe_name

    local_installation_path = local_universe_dir(old_universe_name)

    if not os.path.exists(local_installation_path):
        print(_('universe-does-not-exist'))
        exit(1)

    # read current information
    old_installation_configuration_path = installation_file_path(
        old_universe_name)
    universe_path = configuration.read_configuration(
        old_installation_configuration_path)['location']

    seed_universe_file_path = universe_file_path(old_universe_name)
    old_seed_dictionary = configuration.read_seed_file(seed_universe_file_path)

    # read new information
    source_seed_directory = get_seed_directory_from_argument(seed_path)
    source_seed_universe_path = source_universe_file_path(
        source_seed_directory)
    new_seed_dictionary = configuration.read_seed_file(
        source_seed_universe_path)
    new_universe_name = new_seed_dictionary['name']

    if not yes_no_prompt(
            _('upgrade-warning').format(new_seed_dictionary['version'],
                                        old_seed_dictionary['version'])):
        exit(1)
    else:
        print()

    # validate
    _validate_seed_dictionaries(old_seed_dictionary, new_seed_dictionary)
    if old_universe_name != new_universe_name:
        _validate_paths_for_collision(new_universe_name)

    # replace old seed with new seed
    run_command(['rm', '-rf', local_installation_path],
                'remove-local-seed-phase', verbose)
    copy_seed_to_local_home(source_seed_directory, new_universe_name)
    new_installation_configuration_path = installation_file_path(
        new_universe_name)
    configuration.write_installation_configuration(
        new_installation_configuration_path, universe_path)

    # take care of the schroot config in case of a universe rename
    if old_universe_name != new_universe_name:
        change_schroot_name(old_universe_name, new_universe_name,
                            schroot_config_file_path(old_universe_name),
                            schroot_config_file_path(new_universe_name),
                            'rename-schroot-phase', verbose)

    # run the new Ansible playbook on the universe as if we just updated
    update_universe(new_universe_name, verbose)