diff --git a/modules/ansible_generator.py b/modules/ansible_generator.py index 74fd818b6b48ee206fa3e6eea2af59b749ed8c8f..928a943aa9d493b47a1c72d471c60365804a2460 100644 --- a/modules/ansible_generator.py +++ b/modules/ansible_generator.py @@ -4,6 +4,15 @@ from modules.file_manager import generate_file, copy_template_file,\ copy_user_provisioning_dir from modules.ansible_vars_generator import generate_ansible_vars from conf.border_router import BORDER_ROUTER_NAME +from modules.controller import CONTROLLER_NAME + +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 _create_config_playbooks(input_definitions, flags): @@ -18,14 +27,23 @@ def _create_config_playbooks(input_definitions, flags): copy_template_file("routers", "base_provisioning/roles/routers/tasks/main.yml") + windows_hosts = _find_windows_boxes(input_definitions) + for device in input_definitions["hosts"] + input_definitions["routers"]: if "border_router" in flags and flags["border_router"] and\ device["name"] == BORDER_ROUTER_NAME: copy_template_file("br", "base_provisioning/roles/" + device["name"] + "/tasks/main.yml") else: - copy_template_file("separate_devices", "base_provisioning/roles/" + - device["name"] + "/tasks/main.yml") + if device["name"] == CONTROLLER_NAME: + copy_template_file("controller", "base_provisioning/roles/" + + device["name"] + "/tasks/main.yml") + elif device["name"] in windows_hosts: + copy_template_file("windows_devices", "base_provisioning/roles/" + + device["name"] + "/tasks/main.yml") + else: + copy_template_file("separate_devices", "base_provisioning/roles/" + + device["name"] + "/tasks/main.yml") def _generate_user_playbooks(input_definitions): diff --git a/modules/controller.py b/modules/controller.py new file mode 100644 index 0000000000000000000000000000000000000000..2876bea160d3e936598e3ed524bf5a96367860ea --- /dev/null +++ b/modules/controller.py @@ -0,0 +1,66 @@ +"""Contains functions for controller creation.""" + + +from netaddr import * +from itertools import chain + +CONTROLLER_NAME = "controller" +CONTROLLER_BOX_NAME = "debian/contrib-stretch64" +CONTROLLER_MEMORY = 512 + + +def _are_controller_parameters_free(definitions): + """Check if controller parameters are not already taken.""" + for host in definitions["hosts"]: + if host["name"] == CONTROLLER_NAME: + return False + # TODO add other parameters + + return True + + +def _is_ip_available(ip, definitions): + """Check if the given ip is used somewhere""" + for mapping in chain(definitions["router_mappings"], + definitions["net_mappings"]): + if mapping["ip"] == ip: + return False + return True + + +def _find_available_ip(definitions): + """Return an available ip for the controller""" + network = IPNetwork(definitions["networks"][0]["cidr"]) + for ip in network: + if _is_ip_available(ip, definitions): + return str(ip) + + +def _add_controller(definitions): + """Add controller to definitions""" + controller_ip = _find_available_ip(definitions) + controller = dict() + controller["name"] = CONTROLLER_NAME + controller["base_box"] = dict() + controller["base_box"]["image"] = CONTROLLER_BOX_NAME + controller["memory"] = CONTROLLER_MEMORY + definitions["hosts"].append(controller) + mapping = dict() + mapping["host"] = CONTROLLER_NAME + mapping["network"] = definitions["networks"][0]["name"] + mapping["ip"] = controller_ip + definitions["net_mappings"].append(mapping) + + +def create_controller(definitions): + """Add the definition of controller to definitions. + + :param definitions: device definition structure + """ + + # TODO this should be later moved to input check + if not _are_controller_parameters_free(definitions): + raise ValueError("A device with the same name as border router " + "already exists.") + + _add_controller(definitions) diff --git a/modules/preprocessing.py b/modules/preprocessing.py index 06deecd2389573bedd8905e587191255dff68ad5..b331775d219b38ee60437f919d808a6734aa6b3f 100644 --- a/modules/preprocessing.py +++ b/modules/preprocessing.py @@ -7,6 +7,7 @@ creation. import itertools from modules.border_router import create_border_router +from modules.controller import create_controller from modules.file_manager import open_yaml, cleanup_and_exit FLAVORS = open_yaml("conf/flavors.yml") @@ -44,7 +45,7 @@ def _add_extra_arguments(definitions, flags): if "border_router" in flags and flags["ansible_local"]: for device in itertools.chain(definitions["hosts"], definitions["routers"]): - if "synced_folder" not in device: + if "synced_folder" not in device and "communicator" not in device: device["synced_folder"] = "\".\", \"/vagrant\", type: \"rsync"\ "\", rsync__exclude: \".git/\"" @@ -71,6 +72,22 @@ def _delete_dummy_router(definitions): return +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 _add_windows_attributes(host): + """Add vagrant attributes to windows hosts""" + host["communicator"] = "winrm" + host["winrm_username"] = "windows" + host["winrm_password"] = "vagrant" + + def preprocess(definitions, flags): """Run preprocessing. @@ -97,6 +114,22 @@ def preprocess(definitions, flags): cleanup_and_exit("Preprocessing not successful: " "Could not create border router (" + error + ")") + windows_hosts = _find_windows_boxes(definitions) + try: + if windows_hosts: + create_controller(definitions) + except (ValueError, IndexError) as error: + cleanup_and_exit("Preprocessing not successful: " + "Could not create controller (" + error + ")") + + try: + for host in definitions["hosts"]: + if host["name"] in windows_hosts: + _add_windows_attributes(host) + except (ValueError, IndexError) as error: + cleanup_and_exit("Preprocessing not successful: " + "Could not create controller (" + error + ")") + try: _configure_routers(definitions) except Exception: diff --git a/modules/vagrant_generator.py b/modules/vagrant_generator.py index f6211bbfd49b13306c9c2be60bfa4af6d0952f62..83f57574611a1393d4097538dd9f6c14564c4a41 100644 --- a/modules/vagrant_generator.py +++ b/modules/vagrant_generator.py @@ -1,6 +1,9 @@ """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") @@ -93,9 +96,10 @@ def _create_commands(device_attributes): return commands -def _create_ansible_commands(playbook_location, input_definitions, flags): +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" @@ -103,6 +107,12 @@ def _create_ansible_commands(playbook_location, input_definitions, flags): 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" @@ -121,6 +131,16 @@ def _create_ansible_commands(playbook_location, input_definitions, flags): 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"] @@ -148,8 +168,8 @@ def _create_ansible_commands(playbook_location, input_definitions, flags): extravars["type"] = "dictionary" extravars["command"] = "extra_vars" extravars["dictionary"] = dict() - extravars["dictionary"]["ansible_python_interpreter"] = \ - "\"/usr/bin/python3\"" + if device_name not in windows_hosts: + extravars["dictionary"]["ansible_python_interpreter"] = "\"/usr/bin/python3\"" commands.append(extravars) return commands @@ -173,8 +193,10 @@ def _add_networks_to_device(definition, mappings, input_definitions): network["network_type"] = "private_network" network["name"] = mapping["network"] network["ip"] = mapping["ip"] - network["netmask"] = _find_netmask(mapping["network"], - input_definitions["networks"]) + netmask = netaddr.IPNetwork("0.0.0.0/" + + _find_netmask(mapping["network"], + input_definitions["networks"])).netmask + network["netmask"] = str(netmask) definition["commands"].append(network) @@ -191,10 +213,23 @@ def _add_all_networks(vagrant_definitions, input_definitions, flags): input_definitions) -def _call_provisioner(input_definitions, flags): +def _call_provisioner(device_name, input_definitions, flags): """Create entry to vagrant definitions for calling the provisioner.""" provisioner_calls = [] + if device_name == CONTROLLER_NAME: + shell_provisioning = dict() + shell_provisioning["type"] = "provision" + shell_provisioning["provisioner"] = "shell" + shell_provisioning["note"] = "install pip3 and ansible" + + install_ansible = dict() + install_ansible["type"] = "string" + install_ansible["command"] = "inline" + install_ansible["value"] = "sudo apt-get install -y python3-pip && pip3 install --upgrade ansible" + shell_provisioning["commands"] = [install_ansible] + provisioner_calls.append(shell_provisioning) + config_playbook = dict() config_playbook["type"] = "provision" if "ansible_local" in flags and flags["ansible_local"]: @@ -202,7 +237,8 @@ def _call_provisioner(input_definitions, flags): else: config_playbook["provisioner"] = "ansible" config_playbook["note"] = "basic configuration of devices and networks" - config_playbook["commands"] = _create_ansible_commands(BASE_PLAYBOOK, + config_playbook["commands"] = _create_ansible_commands(device_name, + BASE_PLAYBOOK, input_definitions, flags) @@ -215,7 +251,8 @@ def _call_provisioner(input_definitions, flags): else: user_playbook["provisioner"] = "ansible" user_playbook["note"] = "user configuration of devices" - user_playbook["commands"] = _create_ansible_commands(USER_PLAYBOOK, + user_playbook["commands"] = _create_ansible_commands(device_name, + USER_PLAYBOOK, input_definitions, flags) @@ -224,6 +261,15 @@ def _call_provisioner(input_definitions, flags): 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. @@ -236,19 +282,26 @@ def _build_vagrant_definitions(input_definitions, flags): 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) - vagrant_definitions.extend(_call_provisioner(input_definitions, flags)) - return vagrant_definitions diff --git a/requirements.txt b/requirements.txt index 72a3c107abb60bc793986db432e8f7c783d82869..fdada436324c91dded5553993f73e454b218319b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ Jinja2>=2.10.1 PyYAML>=5.3.1 +netaddr>=0.7 \ No newline at end of file diff --git a/templates/controller b/templates/controller new file mode 100644 index 0000000000000000000000000000000000000000..8351a03292d43cd27bb35abdb98960876166ce61 --- /dev/null +++ b/templates/controller @@ -0,0 +1,27 @@ +--- +- name: Adding aliases + loop: "{{ aliases | dict2items }}" + lineinfile: + path: /etc/hosts + line: "{{ item.value }} {{ item.key }}" + +- name: Configuring routes + include_role: + name: interface + vars: + interface_configuration_type: static + interface_identification: "{{ interface.interface_ip }}" + interface_static_ip: "{{ interface.interface_ip }}" + interface_static_netmask: "{{ interface.interface_netmask }}" + interface_default_gateway: "{{ interface.interface_default_gateway | default('') }}" + interface_routes: "{{ interface.interface_routes | default([]) }}" + loop: "{{ routes }}" + loop_control: + loop_var: interface + + +- name: Install pywinrm + pip: + name: pywinrm + executable: "/usr/bin/pip3" +... diff --git a/templates/device_configuration b/templates/device_configuration index 056e19806fae62a00131a4ae13a3c87ec4a7cae8..39582f43fcbd55f9b3bb3ea88fda7bebcbdd2612 100644 --- a/templates/device_configuration +++ b/templates/device_configuration @@ -10,12 +10,6 @@ file: config.yml name: config -- name: Configuring hosts - hosts: hosts - become: yes - roles: - - hosts - - name: Configuring routers hosts: routers become: yes @@ -25,9 +19,21 @@ name: routers when: config.routers -- name: Configuring devices separately - hosts: all +- name: Configuring Linux devices separately + hosts: all:!windows + become: yes + tasks: + - name: include role + include_role: + name: "{{ inventory_hostname }}" + +- name: Configuring Windows devices separately + hosts: windows become: yes + become_method: runas + become_user: windows + vars: + ansible_become_pass: vagrant tasks: - name: include role include_role: diff --git a/templates/separate_devices b/templates/separate_devices index 0c8df57912921e8d52cb7064b4265e2233550ac4..88b2ca7c7f2fdc18e2a14ab4bc11414bc9c54df5 100644 --- a/templates/separate_devices +++ b/templates/separate_devices @@ -1,4 +1,8 @@ --- +- name: Install net-tools + apt: + name: "net-tools" + - name: Adding aliases loop: "{{ aliases | dict2items }}" lineinfile: @@ -18,4 +22,4 @@ loop: "{{ routes }}" loop_control: loop_var: interface -... \ No newline at end of file +... diff --git a/templates/vagrantfile b/templates/vagrantfile index c0f4cc928a998ef0f594fbe66049d8d34ed16d6a..b5eff4a5e6f8302d7d955bbe862f7f1a6a449ba8 100644 --- a/templates/vagrantfile +++ b/templates/vagrantfile @@ -27,8 +27,8 @@ {# Macro for provision items #} {% macro provision(item, namespace) %} # {{ item.note }} - {{ namespace }}.vm.provision :{{ item.provisioner }} do |provisioner| -{{ layer2(item.commands, "provisioner") }} + {{ namespace }}.vm.provision :{{ item.provisioner }} do |provisioner| +{{ layer3(item.commands, "provisioner") }} {% endmacro -%} {# Macro for string items #} @@ -64,25 +64,40 @@ {{ namespace }}.{{ item.command }} = { {% for key, value in item.dictionary.items() %} {% if loop.last %} - {{ key }}: {{ value }} + {{ key }}: {{ value }} {% else %} - {{ key }}: {{ value }}, + {{ key }}: {{ value }}, {% endif %} {% endfor %} - } + } {% endmacro -%} +{# Macro for group dictionaries #} +{% macro group_dict(item, namespace) %}{ + {% for key, value in item.items() %} + {% if loop.last %} + "{{ key }}" => {{ value }} + {% else %} + "{{ key }}" => {{ value }}, + {% endif %} + {% endfor %} + }{% endmacro -%} + {# Macro for dictionaries #} {% macro groups(item, namespace) %} {{ namespace }}.groups = { {% for key, value in item.groups.items() %} - {% if loop.last %} - "{{ key }}" => {{ value }} + {% if value is mapping %} + "{{ key }}" => {{ group_dict(value, namespace) }} {% else %} - "{{ key }}" => {{ value }}, + {% if loop.last %} + "{{ key }}" => {{ value }} + {% else %} + "{{ key }}" => {{ value }}, + {% endif %} {% endif %} {% endfor %} - } + } {% endmacro -%} {# Macro for network items #} @@ -103,8 +118,6 @@ {{ router(item, namespace) }} {% elif item.type == "host" %} {{ host(item, namespace) }} -{% elif item.type == "provision" %} -{{ provision(item, namespace) }} {% endif %} {% endfor %} end @@ -129,6 +142,8 @@ end {{ dictionary(item, namespace) -}} {% elif item.type == "groups" %} {{ groups(item, namespace) -}} +{% elif item.type == "provision" %} + {{ provision(item, namespace) }} {% endif %} {% endfor %} end @@ -143,6 +158,10 @@ end {{ boolean(item, namespace) -}} {% elif item.type == "integer" %} {{ integer(item, namespace) -}} +{% elif item.type == "dictionary" %} + {{ dictionary(item, namespace) -}} +{% elif item.type == "groups" %} + {{ groups(item, namespace) -}} {% endif %} {% endfor %} end diff --git a/templates/windows_devices b/templates/windows_devices new file mode 100644 index 0000000000000000000000000000000000000000..bf9673bbaad95198a2275c17c42258ef7dd50980 --- /dev/null +++ b/templates/windows_devices @@ -0,0 +1,6 @@ +--- + +- name: print hello world + debug: + msg: "Hello World" +...