Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
vagrant_generator.py 11.79 KiB
"""Contains functions for generating a Vagrantfile from input definitions."""

import netaddr

from modules.file_manager import generate_file, open_yaml
from modules.controller import CONTROLLER_NAME

VAGRANT_MAPPING = open_yaml("conf/vagrant_mapping.yml")
VIRTUALBOX_MAPPING = open_yaml("conf/virtualbox_mapping.yml")
BOX_MAPPING = open_yaml("conf/box_mapping.yml")
BASE_PLAYBOOK = "base_provisioning/device_configuration.yml"
USER_PLAYBOOK = "provisioning/playbook.yml"


def _create_simple_attribute(key, value, attribute_type):
    """Create simple vagrant attributes like string, integer or boolean."""
    attribute = dict()
    attribute["type"] = attribute_type
    attribute["command"] = key
    attribute["value"] = value

    return attribute


def _create_complex_attribute(key, value):
    """Create complex vagrant attributes that are not string, int or bool."""
    separators = {VAGRANT_MAPPING["other"]["synced_folder"]: "",
                  VIRTUALBOX_MAPPING["other"]["vb_customize"]: ""}

    attribute = dict()
    attribute["type"] = "other"
    attribute["command"] = key
    attribute["separator"] = separators[key]
    attribute["value"] = value

    return attribute


def _create_special_commands(attribute, value):
    """Create special commands that cannot be mapped to a single command."""
    special_attributes = []
    if attribute == "base_box":
        if "image" in value:
            if value["image"] in BOX_MAPPING:
                value["image"] = BOX_MAPPING[value["image"]]
            box_name = dict()
            box_name["type"] = "string"
            box_name["command"] = "vm.box"
            box_name["value"] = value["image"]
            special_attributes.append(box_name)
        if "man_user" in value:
            user = dict()
            user["type"] = "string"
            user["command"] = "ssh.username"
            user["value"] = value["man_user"]
            special_attributes.append(user)

    return special_attributes


def _create_commands(device_attributes):
    """Create basic vagrant definition commands for a device."""
    commands = []
    vb_commands = []

    for attribute, value in device_attributes.items():
        if attribute in VAGRANT_MAPPING["string"]:
            vagrant_attribute = VAGRANT_MAPPING["string"][attribute]
            commands.append(_create_simple_attribute(vagrant_attribute, value,
                                                     "string"))
        elif attribute in VAGRANT_MAPPING["boolean"]:
            vagrant_attribute = VAGRANT_MAPPING["boolean"][attribute]
            commands.append(_create_simple_attribute(vagrant_attribute, value,
                                                     "boolean"))
        elif attribute in VAGRANT_MAPPING["integer"]:
            vagrant_attribute = VAGRANT_MAPPING["integer"][attribute]
            commands.append(_create_simple_attribute(vagrant_attribute, value,
                                                     "integer"))
        elif attribute in VAGRANT_MAPPING["other"]:
            vagrant_attribute = VAGRANT_MAPPING["other"][attribute]
            commands.append(_create_complex_attribute(vagrant_attribute,
                                                      value))
        elif attribute in VAGRANT_MAPPING["special"]:
            commands.extend(_create_special_commands(attribute, value))
        elif attribute in VIRTUALBOX_MAPPING["integer"]:
            vagrant_attribute = VIRTUALBOX_MAPPING["integer"][attribute]
            vb_commands.append(_create_simple_attribute(vagrant_attribute,
                                                        value, "integer"))
        elif attribute in VIRTUALBOX_MAPPING["other"]:
            vagrant_attribute = VIRTUALBOX_MAPPING["other"][attribute]
            vb_commands.append(_create_complex_attribute(vagrant_attribute,
                                                         value))

    if vb_commands:
        virtual_box_command = dict()
        virtual_box_command["type"] = "provider"
        virtual_box_command["name"] = "virtualbox"
        virtual_box_command["commands"] = vb_commands
        commands.append(virtual_box_command)

    return commands


def _create_ansible_commands(device_name, playbook_location, input_definitions, flags):
    """Create commands for running a playbook from the Vagrantfile."""
    commands = []
    windows_hosts = _find_windows_boxes(input_definitions);

    playbook = dict()
    playbook["type"] = "string"
    playbook["command"] = "playbook"
    playbook["value"] = playbook_location
    commands.append(playbook)

    limit = dict()
    limit["type"] = "string"
    limit["command"] = "limit"
    limit["value"] = device_name
    commands.append(limit)

    if "verbose_ansible" in flags and flags["verbose_ansible"]:
        verbosity = dict()
        verbosity["type"] = "string"
        verbosity["command"] = "verbose"
        verbosity["value"] = "vv"
        commands.append(verbosity)

    groups = dict()
    groups["type"] = "groups"
    groups["groups"] = dict()
    host_names = []
    for host in input_definitions["hosts"]:
        host_names.append(host["name"])
    groups["groups"]["hosts"] = host_names
    router_names = []
    for router in input_definitions["routers"]:
        router_names.append(router["name"])
    groups["groups"]["routers"] = router_names
    windows_hosts = _find_windows_boxes(input_definitions)
    groups["groups"]["windows"] = windows_hosts
    if device_name in windows_hosts:
        windows_vars = dict()
        windows_vars["ansible_connection"] = "\"winrm\""
        windows_vars["ansible_user"] = "\"windows\""
        windows_vars["ansible_password"] = "\"vagrant\""
        windows_vars["ansible_winrm_transport"] = "\"basic\""
        windows_vars["ansible_winrm_server_cert_validation"] = "\"ignore\""
        groups["groups"]["windows:vars"] = windows_vars
    if "groups" in input_definitions:
        for group in input_definitions["groups"]:
            groups["groups"][group["name"]] = group["nodes"]
    commands.append(groups)

    if "extra_vars" in flags and flags["extra_vars"]:
        if "ansible_local" in flags and flags["ansible_local"]:
            extra_vars_location = "/vagrant/user_files/extra_vars.yml"
        else:
            extra_vars_location = "user_files/extra_vars.yml"
        user_extra_vars = dict()
        user_extra_vars["type"] = "string"
        user_extra_vars["command"] = "raw_arguments"
        user_extra_vars["value"] = "--extra-vars=@" + extra_vars_location
        commands.append(user_extra_vars)

    extravars = dict()
    extravars["type"] = "dictionary"
    extravars["command"] = "extra_vars"
    extravars["dictionary"] = dict()
    if device_name not in windows_hosts:
        extravars["dictionary"]["ansible_python_interpreter"] = "\"python3\""
    commands.append(extravars)

    return commands


def _find_netmask(network_name, networks):
    """Return the netmask of a network address from network name."""
    for network in networks:
        if network['name'] == network_name:
            address, netmask = network['cidr'].split('/')
            return netmask
    return None


def _add_networks_to_device(definition, mappings, input_definitions):
    """Add networks to the vagrant definition of one device."""
    for mapping in mappings:
        if mapping[definition["type"]] == definition["name"]:
            network = dict()
            network["type"] = "network"
            network["network_type"] = "private_network"
            network["name"] = mapping["network"]
            network["ip"] = mapping["ip"]
            netmask = netaddr.IPNetwork("0.0.0.0/" +
                                        _find_netmask(mapping["network"],
                                                      input_definitions["networks"])).netmask
            network["netmask"] = str(netmask)
            definition["commands"].append(network)


def _add_all_networks(vagrant_definitions, input_definitions, flags):
    """Add all networks to vagrant definitions."""
    for definition in vagrant_definitions:
        if definition["type"] == "host":
            _add_networks_to_device(definition,
                                    input_definitions["net_mappings"],
                                    input_definitions)
        elif definition["type"] == "router":
            _add_networks_to_device(definition,
                                    input_definitions["router_mappings"],
                                    input_definitions)


def _call_provisioner(device_name, input_definitions, flags):
    """Create entry to vagrant definitions for calling the provisioner."""
    provisioner_calls = []

    config_playbook = dict()
    config_playbook["type"] = "provision"
    if "ansible_local" in flags and flags["ansible_local"]:
        config_playbook["provisioner"] = "ansible_local"
    else:
        config_playbook["provisioner"] = "ansible"
    config_playbook["note"] = "basic configuration of devices and networks"
    config_playbook["commands"] = _create_ansible_commands(device_name,
                                                           BASE_PLAYBOOK,
                                                           input_definitions,
                                                           flags)

    provisioner_calls.append(config_playbook)

    user_playbook = dict()
    user_playbook["type"] = "provision"
    if "ansible_local" in flags and flags["ansible_local"]:
        user_playbook["provisioner"] = "ansible_local"
    else:
        user_playbook["provisioner"] = "ansible"
    user_playbook["note"] = "user configuration of devices"
    user_playbook["commands"] = _create_ansible_commands(device_name,
                                                         USER_PLAYBOOK,
                                                         input_definitions,
                                                         flags)

    provisioner_calls.append(user_playbook)

    return provisioner_calls


def _find_windows_boxes(definitions):
    """Find and return list of host names with windows boxes."""
    windows_hosts = []
    for host in definitions["hosts"]:
        if "windows" in host["base_box"]["image"].lower():
            windows_hosts.append(host["name"])
    return windows_hosts


def _build_vagrant_definitions(input_definitions, flags):
    """Create a definition structure for vagrant.

    This structure is more suitable for Vagrantfile generation than input
    definitions.
    """
    vagrant_definitions = []
    for router in input_definitions["routers"]:
        device = dict()
        device["type"] = "router"
        device["name"] = router["name"]
        device["commands"] = _create_commands(router)
        device["commands"].extend(_call_provisioner(device["name"], input_definitions, flags))
        vagrant_definitions.append(device)

    windows_hosts = _find_windows_boxes(input_definitions)
    windows_provisioning_commands = []
    for host in input_definitions["hosts"]:
        device = dict()
        device["type"] = "host"
        device["name"] = host["name"]
        device["commands"] = _create_commands(host)
        if host["name"] in windows_hosts:
            windows_provisioning_commands.extend(_call_provisioner(device["name"], input_definitions, flags))
        else:
            device["commands"].extend(_call_provisioner(device["name"], input_definitions, flags))
        if host["name"] == CONTROLLER_NAME:
            device["commands"].extend(windows_provisioning_commands)
        vagrant_definitions.append(device)

    _add_all_networks(vagrant_definitions, input_definitions, flags)

    return vagrant_definitions


def generate_vagrantfile(input_definitions, flags):
    """Generate the Vagrantfile.

    :param input_definitions: device definitions from the input file
    :param flags: command line flags
    """
    vagrant_definitions = _build_vagrant_definitions(input_definitions, flags)

    generate_file("vagrantfile", "Vagrantfile", defs=vagrant_definitions)