#!/usr/bin/python
# Copyright (c) 2015 Monty Taylor
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import contextlib
import copy
import errno
import json
import logging
import os
import re
import subprocess
import sys
import time

from glean import systemlock
from glean import utils
from glean._vendor import distro

try:
    import configparser
except ImportError:
    import ConfigParser as configparser

try:
    from StringIO import StringIO
except ImportError:
    from io import StringIO

log = logging.getLogger("glean")


# Type value for permanent mac addrs as defined by the linux kernel.
PERMANENT_ADDR_TYPE = '0'

# Global flag for selinux restore.
SELINUX_RESTORECON = '/usr/sbin/restorecon'
HAVE_SELINUX = os.path.exists(SELINUX_RESTORECON)


# Wrap open calls in this to make sure that any created or modified
# files retain their selinux context.
@contextlib.contextmanager
def safe_open(*args, **kwargs):
    f = open(*args, **kwargs)
    yield f
    f.close()
    path = os.path.abspath(f.name)
    if HAVE_SELINUX:
        logging.debug("Restoring selinux context for %s" % path)
        subprocess.call([SELINUX_RESTORECON, path])


def _exists_rh_interface(name, distro):
    file_to_check = _network_files(distro)['ifcfg'] + '-{name}'.format(
        name=name
        )
    return os.path.exists(file_to_check)


def _is_suse(distro):
    # 'distro could be any of suse, opensuse,
    # opensuse-leap, opensuse-tumbleweed
    return 'suse' in distro


def _network_files(distro):
    network_files = {}
    if _is_suse(distro):
        network_files = {
            "ifcfg": "/etc/sysconfig/network/ifcfg",
            "route": "/etc/sysconfig/network/ifroute",
        }
    else:
        network_files = {
            "ifcfg": "/etc/sysconfig/network-scripts/ifcfg",
            "route": "/etc/sysconfig/network-scripts/route",
        }

    return network_files


def _network_config(args):
    distro = args.distro
    network_config = {}
    if _is_suse(distro):
        header = "\n".join(["# Automatically generated, do not edit",
                            "BOOTPROTO={bootproto}",
                            "LLADDR={hwaddr}"])
        footer = "STARTMODE=auto" + "\n"

        network_config = {
            "static": "\n".join([header,
                                 "IPADDR={ip_address}",
                                 "NETMASK={netmask}",
                                 footer])
        }
    else:
        header = "\n".join(["# Automatically generated, do not edit",
                            "DEVICE={name}",
                            "BOOTPROTO={bootproto}",
                            "HWADDR={hwaddr}"])
        footer = "\n".join(["ONBOOT=yes",
                            "NM_CONTROLLED=%s" %
                            ("yes" if args.use_nm else "no"),
                            "TYPE=Ethernet"]) + "\n"

        network_config = {
            # RedHat does not use TYPE=Ethernet in the static configurations
            "static": "\n".join([header,
                                 "IPADDR={ip_address}",
                                 "NETMASK={netmask}",
                                 footer.replace("TYPE=Ethernet\n", "")])
        }

    # RedHat does not use TYPE=Ethernet in the dhcp configurations
    network_config["dhcp"] = "\n".join([header, footer])
    network_config["none"] = "\n".join([header, footer])

    return network_config


def _set_rh_bonding(name, interface, distro, results):
    if not any(bond in ['bond_slaves', 'bond_master'] for bond in interface):
        return results

    # Careful, we are operating on the live 'results' variable
    # so we need to always append our data
    if _is_suse(distro):
        # SUSE configures the slave interfaces on the master ifcfg file.
        # The master interface contains a 'bond_slaves' key containing a list
        # of the slave interfaces
        if 'bond_slaves' in interface:
            results += "BONDING_MASTER=yes\n"
            slave_cnt = 0
            for slave in interface['bond_slaves']:
                results += "BONDING_SLAVE_{id}={name}\n".format(
                    id=slave_cnt, name=slave)
                slave_cnt += 1
        else:
            # Slave interfaces do not know they are part of a bonded
            # interface. All we need to do is to set the STARTMODE
            # to hotplug
            results = results.replace("=auto", "=hotplug")

    else:
        # RedHat does not add any specific configuration to the master
        # interface. All configuration is done in the slave ifcfg files.
        if 'bond_slaves' in interface:
            results += "BONDING_MASTER=yes\n"
            results += "BONDING_OPTS=\"downdelay=0 miimon={bond_miimon} mode={bond_mode} updelay=0\"\n".format(
                bond_miimon=interface['bond_miimon'], bond_mode=interface['bond_mode'])
            results = results.replace("=Ethernet", "=Bond")
            return results

        results += "SLAVE=yes\n"
        results += "MASTER={0}\n".format(interface['bond_master'])

    return results


def _set_rh_vlan(name, interface, distro):
    results = ""

    if 'vlan_id' not in interface:
        return results

    if _is_suse(distro):
        results += "VLAN_ID={vlan_id}\n".format(vlan_id=interface['vlan_id'])
        results += "ETHERDEVICE={etherdevice}\n".format(
            etherdevice=name.split('.')[0])
    else:
        results += "VLAN=yes\n"

    return results


def _write_rh_interface(name, interface, args):
    distro = args.distro
    files_to_write = dict()
    results = _network_config(args)["static"].format(
        bootproto="static",
        name=name,
        hwaddr=interface['mac_address'],
        ip_address=interface['ip_address'],
        netmask=interface['netmask'],
    )
    results += _set_rh_vlan(name, interface, distro)
    # set_rh_bonding takes results as argument so we need to assign
    # the return value, not append it
    results = _set_rh_bonding(name, interface, distro, results)
    routes = []
    for route in interface['routes']:
        if route['network'] == '0.0.0.0' and route['netmask'] == '0.0.0.0':
            if not _is_suse(distro):
                results += "DEFROUTE=yes\n"
                results += "GATEWAY={gw}\n".format(gw=route['gateway'])
            else:
                # Special notation for default route on SUSE/wicked
                routes.append(dict(
                    net='default', mask='', gw=route['gateway']))
        else:
            routes.append(dict(
                net=route['network'], mask=route['netmask'],
                gw=route['gateway']))

    if routes:
        route_content = ""
        for x in range(0, len(routes)):
            if not _is_suse(distro):
                route_content += "ADDRESS{x}={net}\n".format(x=x, **routes[x])
                route_content += "NETMASK{x}={mask}\n".format(x=x, **routes[x])
                route_content += "GATEWAY{x}={gw}\n".format(x=x, **routes[x])
            else:
                # Avoid the extra trailing whitespace for the default route
                # because mask is empty in that case.
                route_content += "{net} {gw} {mask}\n".format(
                    **routes[x]).replace(' \n', '\n')
        files_to_write[_network_files(distro)["route"] + '-{name}'
                       .format(name=name)] = route_content
    files_to_write[_network_files(distro)["ifcfg"] + '-{name}'.format(
        name=name)] = results

    return files_to_write


def _write_rh_dhcp(name, interface, args):
    distro = args.distro
    filename = _network_files(distro)["ifcfg"] + '-{name}'.format(name=name)
    results = _network_config(args)["dhcp"].format(
        bootproto="dhcp", name=name, hwaddr=interface['mac_address'])
    results += _set_rh_vlan(name, interface, distro)
    # set_rh_bonding takes results as argument so we need to assign
    # the return value, not append it
    results = _set_rh_bonding(name, interface, distro, results)

    return {filename: results}


def _write_rh_manual(name, interface, args):
    distro = args.distro
    filename = _network_files(distro)["ifcfg"] + '-{name}'.format(name=name)
    results = _network_config(args)["none"].format(
        bootproto="none", name=name, hwaddr=interface['mac_address'])
    results += _set_rh_vlan(name, interface, distro)
    # set_rh_bonding takes results as argument so we need to assign
    # the return value, not append it
    results = _set_rh_bonding(name, interface, distro, results)

    return {filename: results}


def write_redhat_interfaces(interfaces, sys_interfaces, args):
    files_to_write = dict()
    # Sort the interfaces by id so that we'll have consistent output order
    for iname, interface in sorted(
            interfaces.items(), key=lambda x: x[1]['id']):
        if interface['type'] == 'ipv6':
            continue
        # sys_interfaces is pruned by --interface; if one of the
        # raw_macs (or, *the* MAC for single interfaces) does not
        # match as one of the interfaces we want configured, skip
        raw_macs = interface.get('raw_macs', [interface['mac_address']])
        if not set(sys_interfaces).intersection(set(raw_macs)):
            continue

        if 'vlan_id' in interface:
            # raw_macs will have a single entry if the vlan device is a
            # phsical device and >1 when it is a bond device.
            if len(raw_macs) == 1:
                vlan_raw_device = sys_interfaces.get(raw_macs[0])
            else:
                vlan_raw_device = interface['vlan_link']
            interface_name = "{0}.{1}".format(
                vlan_raw_device, interface['vlan_id'])
        elif 'bond_mode' in interface:
            # It is possible our interface does not have a link, so fall back
            # to iname which is the link id.
            interface_name = interface.get('link', iname)
        else:
            interface_name = sys_interfaces[interface['mac_address']]

        if 'bond_links' in interface and _is_suse(args.distro):
            # We need to keep track of the slave interfaces because
            # SUSE configures the slaves on the master ifcfg file
            bond_slaves = []
            for phy in interface['raw_macs']:
                bond_slaves.append(sys_interfaces[phy])
            interface['bond_slaves'] = bond_slaves
            # Remove the 'bond_links' key
            interface.pop('bond_links')

        if interface['type'] == 'ipv4':
            files_to_write.update(
                _write_rh_interface(interface_name, interface, args))
        if interface['type'] == 'ipv4_dhcp':
            files_to_write.update(
                _write_rh_dhcp(interface_name, interface, args))
        if interface['type'] == 'manual':
            files_to_write.update(
                _write_rh_manual(interface_name, interface, args))
    for mac, iname in sorted(
            sys_interfaces.items(), key=lambda x: x[1]):
        if _exists_rh_interface(iname, args.distro):
            # This interface already has a config file, move on
            log.debug("%s already has config file, skipping" % iname)
            continue
        inter_macs = [intf['mac_address'] for intf in interfaces.values()]
        link_macs = [intf.get('link_mac') for intf in interfaces.values()
                     if 'vlan_id' in interface]
        if mac in inter_macs or mac in link_macs:
            # We have a config drive config, move on
            log.debug("%s configured via config-drive" % mac)
            continue
        files_to_write.update(_write_rh_dhcp(iname, {'mac_address': mac},
                                             args))
    return files_to_write


def _write_networkd_interface(name, interfaces, args, files_struct=dict()):
    vlans = []
    for interface in interfaces:
        iname = name
        # if vlan set interface name to vlan format
        if 'vlan_id' in interface:
            iname = name + '-vlan' + str(interface['vlan_id'])
            vlans.append(iname)
        network_file = '/etc/systemd/network/{name}.network'.format(name=iname)
        if network_file not in files_struct:
            files_struct[network_file] = dict()
        if '[Match]' not in files_struct[network_file]:
            files_struct[network_file]['[Match]'] = list()
        files_struct[network_file]['[Match]'].append(
            'MACAddress={mac_address}'.format(
                mac_address=interface['mac_address']
            )
        )
        files_struct[network_file]['[Match]'].append(
            'Name={name}'.format(name=iname)
        )
        # define network if needed (basically always)
        if ((interface['type'] in ['ipv4_dhcp', 'ipv6_slaac',
            'ipv6_dhcpv6_stateful', 'manual', 'ipv4', 'ipv6']) or
                ('vlan_id' in interface) or
                ('bond_mode' in interface)):
            if '[Network]' not in files_struct[network_file]:
                files_struct[network_file]['[Network]'] = list()
            if 'services' in interface:
                for service in interface['services']:
                    if service['type'] == 'dns':
                        if not args.skip_dns:
                            files_struct[network_file]['[Network]'].append(
                                'DNS={address}'.format(
                                    address=service['address']
                                )
                            )
        # dhcp network, set to yes if both dhcp6 and dhcp4 are set
        if interface['type'] == 'ipv4_dhcp':
            if 'DHCP=ipv6' in files_struct[network_file]['[Network]']:
                files_struct[network_file]['[Network]'].append('DHCP=yes')
            else:
                files_struct[network_file]['[Network]'].append('DHCP=ipv4')
        if interface['type'] == 'ipv6_dhcpv6_stateful':
            if 'DHCP=ipv4' in files_struct[network_file]['[Network]']:
                files_struct[network_file]['[Network]'].append('DHCP=yes')
            else:
                files_struct[network_file]['[Network]'].append('DHCP=ipv6')
        # slaac can start dhcp6 if the associated RA option is sent to server
        if interface['type'] == 'ipv6_slaac':
            # we are accepting slaac now, remove the disabling of slaac
            if 'IPv6AcceptRA=no' in files_struct[network_file]['[Network]']:
                files_struct[network_file]['[Network]'].remove(
                    'IPv6AcceptRA=no'
                )
            files_struct[network_file]['[Network]'].append('IPv6AcceptRA=yes')
        else:
            # only disbale slaac if slac is not already enabled
            if 'IPv6AcceptRA=yes' not in \
                    files_struct[network_file]['[Network]']:
                files_struct[network_file]['[Network]'].append(
                    'IPv6AcceptRA=no'
                )
        # vlan network

        # static network
        if interface['type'] in ['ipv4', 'ipv6']:
            if 'addresses' not in files_struct[network_file]:
                files_struct[network_file]['addresses'] = list()
        if interface['type'] == 'ipv4':
            files_struct[network_file]['addresses'].append(
                'Address={address}/{cidr}'.format(
                    address=interface['ip_address'],
                    cidr=utils.ipv4_netmask_length(interface['netmask'])
                )
            )
        if interface['type'] == 'ipv6':
            files_struct[network_file]['addresses'].append(
                'Address={address}/{cidr}'.format(
                    address=interface['ip_address'],
                    cidr=utils.ipv6_netmask_length(interface['netmask'])
                )
            )
        # routes
        if 'routes' in interface:
            if 'routes' not in files_struct[network_file]:
                files_struct[network_file]['routes'] = list()
            for route in interface['routes']:
                route_destination = None
                route_gateway = None
                if 'network' in route:
                    if 'v6' in interface['type']:
                        cidr = utils.ipv6_netmask_length(route['netmask'])
                    else:
                        cidr = utils.ipv4_netmask_length(route['netmask'])
                    route_destination = 'Destination={network}/{cidr}'.format(
                        network=route['network'], cidr=cidr
                    )
                if 'gateway' in route:
                    route_gateway = 'Gateway={gateway}'.format(
                        gateway=route['gateway']
                    )
                # add route as a dictionary to the routes list
                files_struct[network_file]['routes'].append({
                    'route': route_destination,
                    'gw': route_gateway
                })

        # create netdev files
        if 'bond_mode' or 'vlan_id' in interface:
            netdev_file = \
                '/etc/systemd/network/{name}.netdev'.format(name=iname)
            if netdev_file not in files_struct:
                files_struct[netdev_file] = dict()
            if '[NetDev]' not in files_struct[netdev_file]:
                files_struct[netdev_file]['[NetDev]'] = list()
            files_struct[netdev_file]['[NetDev]'].append(
                'Name={name}'.format(name=iname)
            )
            if 'mac_address' in interface:
                files_struct[netdev_file]['[NetDev]'].append(
                    'MACAddress={mac_address}'.format(
                        mac_address=interface['mac_address']
                    )
                )
            if 'vlan_id' in interface:
                files_struct[netdev_file]['[NetDev]'].append('Kind=vlan')
                files_struct[netdev_file]['[VLAN]'] = list()
                files_struct[netdev_file]['[VLAN]'].append(
                    'Id={id}'.format(id=interface['vlan_id'])
                )
            if 'bond_mode' in interface:
                files_struct[netdev_file]['[NetDev]'].append('Kind=bond')
                files_struct[netdev_file]['[Bond]'] = list()
                files_struct[netdev_file]['[Bond]'].append(
                    'Mode={bond_mode}'.format(bond_mode=interface['bond_mode'])
                )
                files_struct[netdev_file]['[Bond]'].append(
                    'LACPTransmitRate=fast'
                )
                if 'slaves' in interface:
                    for slave in interface['slaves']:
                        slave_net_file = \
                            '/etc/systemd/network/{name}.network'.format(
                                name=slave
                            )
                        if slave_net_file not in files_struct:
                            files_struct[slave_net_file] = dict()
                        if '[Network]' not in files_struct[slave_net_file]:
                            files_struct[slave_net_file]['[Network]'] = list()
                        files_struct[slave_net_file]['[Network]'].append(
                            'Bond={name}'.format(name=iname)
                        )
                if 'bond_xmit_hash_policy' in interface:
                    files_struct[netdev_file]['[Bond]'].append(
                        'TransmitHashPolicy={bond_xmit_hash_policy}'.format(
                            bond_xmit_hash_policy=interface[
                                'bond_xmit_hash_policy'
                            ]
                        )
                    )
                if 'bond_miimon' in interface:
                    files_struct[netdev_file]['[Bond]'].append(
                        'MIIMonitorSec={milliseconds}'.format(
                            milliseconds=interface['bond_miimon']
                        )
                    )

    # vlan mapping sucks (forward and reverse)
    if vlans:
        netdev = vlans[0].split('-')[0]
        vlan_master_file = \
            '/etc/systemd/network/{name}.network'.format(name=netdev)
        if vlan_master_file not in files_struct:
            files_struct[vlan_master_file] = dict()
        if '[Network]' not in files_struct[vlan_master_file]:
            files_struct[vlan_master_file]['[Network]'] = list()
        for vlan in vlans:
            files_struct[vlan_master_file]['[Network]'].append('VLAN=' + vlan)
            vlan_file = '/etc/systemd/network/{name}.network'.format(name=vlan)
            if vlan_file not in files_struct:
                files_struct[vlan_file] = dict()
            if '[Network]' not in files_struct[vlan_file]:
                files_struct[vlan_file]['[Network]'] = list()
            files_struct[vlan_file]['[Network]'].append('VLAN=' + vlan)

    return files_struct


def write_networkd_interfaces(interfaces, sys_interfaces, args):
    files_to_write = dict()
    gen_intfs = {}
    files_struct = dict()
    # Sort the interfaces by id so that we'll have consistent output order
    for iname, interface in sorted(
            interfaces.items(), key=lambda x: x[1]['id']):
        # sys_interfaces is pruned by --interface; if one of the
        # raw_macs (or, *the* MAC for single interfaces) does not
        # match as one of the interfaces we want configured, skip
        raw_macs = interface.get('raw_macs', [interface['mac_address']])
        if not set(sys_interfaces).intersection(set(raw_macs)):
            continue

        if 'bond_mode' in interface:
            interface['slaves'] = [
                sys_interfaces[mac] for mac in interface['raw_macs']]

        if 'raw_macs' in interface:
            key = tuple(interface['raw_macs'])
            if key not in gen_intfs:
                gen_intfs[key] = []
            gen_intfs[key].append(interface)
        else:
            key = (interface['mac_address'],)
            if key not in gen_intfs:
                gen_intfs[key] = []
            gen_intfs[key].append(interface)

    for raw_macs, interfs in gen_intfs.items():
        if len(raw_macs) == 1:
            interface_name = sys_interfaces[raw_macs[0]]
        else:
            # It is possible our interface does not have a link, so
            # fall back to interface id.
            interface_name = next(
                intf.get('link', intf['id']) for intf in interfs
                if 'bond_mode' in intf)
        files_struct = _write_networkd_interface(
            interface_name, interfs, args, files_struct)

    for mac, iname in sorted(
            sys_interfaces.items(), key=lambda x: x[1]):
        if _exists_networkd_interface(iname):
            # This interface already has a config file, move on
            log.debug("%s already has config file, skipping" % iname)
            continue
        if (mac,) in gen_intfs:
            # We have a config drive config, move on
            log.debug("%s configured via config-drive" % mac)
            continue
        interface = {'type': 'ipv4_dhcp', 'mac_address': mac}
        files_struct = _write_networkd_interface(
            iname, [interface], args, files_struct)

    for networkd_file in files_struct:
        file_contents = '# Automatically generated, do not edit\n'
        if '[Match]' in files_struct[networkd_file]:
            file_contents += '[Match]\n'
            for line in sorted(set(files_struct[networkd_file]['[Match]'])):
                file_contents += line
                file_contents += '\n'
            file_contents += '\n'
        if '[Network]' in files_struct[networkd_file]:
            file_contents += '[Network]\n'
            for line in sorted(set(files_struct[networkd_file]['[Network]'])):
                file_contents += line
                file_contents += '\n'
            file_contents += '\n'
        if 'addresses' in files_struct[networkd_file]:
            for address in files_struct[networkd_file]['addresses']:
                file_contents += '[Address]\n'
                file_contents += address + '\n\n'
        if 'routes' in files_struct[networkd_file]:
            for route in files_struct[networkd_file]['routes']:
                file_contents += '[Route]\n'
                if route['route'] is not None:
                    file_contents += route['route'] + '\n'
                if route['gw'] is not None:
                    file_contents += route['gw'] + '\n'
                file_contents += '\n'
        if '[NetDev]' in files_struct[networkd_file]:
            file_contents += '[NetDev]\n'
            for line in sorted(set(files_struct[networkd_file]['[NetDev]'])):
                file_contents += line
                file_contents += '\n'
            file_contents += '\n'
        if '[VLAN]' in files_struct[networkd_file]:
            file_contents += '[VLAN]\n'
            for line in sorted(set(files_struct[networkd_file]['[VLAN]'])):
                file_contents += line
                file_contents += '\n'
            file_contents += '\n'
        if '[Bond]' in files_struct[networkd_file]:
            file_contents += '[Bond]\n'
            for line in sorted(set(files_struct[networkd_file]['[Bond]'])):
                file_contents += line
                file_contents += '\n'
            file_contents += '\n'
        files_to_write['{path}'.format(path=networkd_file)] = file_contents
    return files_to_write


def _exists_networkd_interface(name):
    network_file = '/etc/systemd/network/{name}.network'.format(name=name)
    netdev_file = '/etc/systemd/network/{name}.netdev'.format(name=name)
    return (os.path.exists(network_file) or os.path.exists(netdev_file))


def _exists_gentoo_interface(name):
    file_to_check = '/etc/conf.d/net.{name}'.format(name=name)
    return os.path.exists(file_to_check)


def _enable_gentoo_interface(name):
    log.debug('rc-update add {name} default'.format(name=name))
    subprocess.call(['rc-update', 'add',
                     'net.{name}'.format(name=name), 'default'])


def _write_gentoo_interface(name, interfaces):
    files_to_write = dict()
    results = ""
    vlans = []
    for interface in interfaces:
        iname = name
        if 'vlan_id' in interface:
            vlans.append(interface['vlan_id'])
            iname = "%s_%s" % (iname, interface['vlan_id'])
        if interface['type'] == 'ipv4':
            results += """config_{name}="{ip_address} netmask {netmask}"
mac_{name}="{hwaddr}\"\n""".format(
                name=iname,
                ip_address=interface['ip_address'],
                netmask=interface['netmask'],
                hwaddr=interface['mac_address']
            )
            routes = list()
            for route in interface['routes']:
                if (route['network'] == '0.0.0.0' and
                        route['netmask'] == '0.0.0.0'):
                    # add default route if it exists
                    routes.append('default via {gw}'.format(
                        name=name,
                        gw=route['gateway']
                    ))
                else:
                    # add remaining static routes
                    routes.append('{net} netmask {mask} via {gw}'.format(
                        net=route['network'],
                        mask=route['netmask'],
                        gw=route['gateway']
                    ))
            if routes:
                routes_string = '\n'.join(route for route in routes)
                results += 'routes_{name}="{routes}"'.format(
                    name=name,
                    routes=routes_string
                    # routes='\n'.join(str(route) for route in routes)
                )
                results += '\n'
        elif interface['type'] == 'manual':
            results += """config_{name}="null"
mac_{name}="{hwaddr}"
""".format(name=iname, hwaddr=interface['mac_address'])
            _enable_gentoo_interface(iname)
        else:
            results += """config_{name}="dhcp"
mac_{name}="{hwaddr}"
""".format(name=iname, hwaddr=interface['mac_address'])
            _enable_gentoo_interface(iname)
        if 'bond_mode' in interface:
            slaves = ' '.join(interface['slaves'])
            results += """slaves_{name}="{slaves}"
mode_{name}="{mode}"
""".format(name=iname, slaves=slaves, mode=interface['bond_mode'])

    full_results = "# Automatically generated, do not edit\n"
    if vlans:
        full_results += 'vlans_{name}="{vlans}"\n'.format(
            name=name,
            vlans=' '.join(str(vlan) for vlan in vlans))
    full_results += results

    files_to_write['/etc/conf.d/net.{name}'.format(name=name)] = full_results
    return files_to_write


def _setup_gentoo_network_init(sys_interface, interfaces):
    for interface in interfaces:
        interface_name = '{name}'.format(name=sys_interface)
        if 'vlan_id' in interface:
            interface_name += ".{vlan}".format(
                vlan=interface['vlan_id'])
            log.debug('vlan {vlan} found, interface named {name}'.
                      format(vlan=interface['vlan_id'], name=interface_name))
        if 'bond_master' in interface:
            continue
        _create_gentoo_net_symlink_and_enable(interface_name)
    if not interfaces:
        _create_gentoo_net_symlink_and_enable(sys_interface)


def _create_gentoo_net_symlink_and_enable(interface_name):
    file_path = '/etc/init.d/net.{name}'.format(name=interface_name)
    if not os.path.islink(file_path):
        log.debug('ln -s /etc/init.d/net.lo {file_path}'.
                  format(file_path=file_path))
        os.symlink('/etc/init.d/net.lo',
                   '{file_path}'.format(file_path=file_path))
        _enable_gentoo_interface(interface_name)


def write_gentoo_interfaces(interfaces, sys_interfaces):
    files_to_write = dict()
    gen_intfs = {}
    # Sort the interfaces by id so that we'll have consistent output order
    for iname, interface in sorted(
            interfaces.items(), key=lambda x: x[1]['id']):
        if interface['type'] == 'ipv6':
            continue
        # sys_interfaces is pruned by --interface; if one of the
        # raw_macs (or, *the* MAC for single interfaces) does not
        # match as one of the interfaces we want configured, skip
        raw_macs = interface.get('raw_macs', [interface['mac_address']])
        if not set(sys_interfaces).intersection(set(raw_macs)):
            continue

        if 'bond_mode' in interface:
            interface['slaves'] = [
                sys_interfaces[mac] for mac in interface['raw_macs']]

        if 'raw_macs' in interface:
            key = tuple(interface['raw_macs'])
            if key not in gen_intfs:
                gen_intfs[key] = []
            gen_intfs[key].append(interface)
        else:
            key = (interface['mac_address'],)
            if key not in gen_intfs:
                gen_intfs[key] = []
            gen_intfs[key].append(interface)

    for raw_macs, interfs in gen_intfs.items():
        if len(raw_macs) == 1:
            interface_name = sys_interfaces[raw_macs[0]]
        else:
            # It is possible our interface does not have a link, so
            # fall back to interface id.
            interface_name = next(
                intf.get('link', intf['id']) for intf in interfs
                if 'bond_mode' in intf)
        files_to_write.update(
            _write_gentoo_interface(interface_name, interfs))
        _setup_gentoo_network_init(interface_name, interfs)

    for mac, iname in sorted(
            sys_interfaces.items(), key=lambda x: x[1]):
        if _exists_gentoo_interface(iname):
            # This interface already has a config file, move on
            log.debug("%s already has config file, skipping" % iname)
            continue
        if (mac,) in gen_intfs:
            # We have a config drive config, move on
            log.debug("%s configured via config-drive" % mac)
            continue
        interface = {'type': 'ipv4_dhcp', 'mac_address': mac}
        files_to_write.update(_write_gentoo_interface(iname, [interface]))
        _setup_gentoo_network_init(iname, [])
    return files_to_write


def _write_debian_bond_conf(interface_name, interface, sys_interfaces):
    result = ""
    if interface['mac_address']:
        result += "    hwaddress {0}\n".format(
            interface['mac_address'])
    result += "    bond-mode {0}\n".format(interface['bond_mode'])
    result += "    bond-miimon {0}\n".format(
        interface.get('bond_miimon', 0))
    result += "    bond-lacp-rate {0}\n".format(
        interface.get('bond_lacp_rate', 'slow'))
    result += "    bond-xmit_hash_policy {0}\n".format(
        interface.get('bond_xmit_hash_policy', 'layer2'))
    slave_devices = [sys_interfaces[mac]
                     for mac in interface['raw_macs']]
    slaves = ' '.join(slave_devices)
    result += "    bond-slaves none\n"
    result += "    post-up ifenslave {0} {1}\n".format(interface_name, slaves)
    result += "    pre-down ifenslave -d {0} {1}\n".format(
        interface_name, slaves)
    return result


def write_debian_interfaces(interfaces, sys_interfaces):
    eni_path = '/etc/network/interfaces'
    eni_d_path = eni_path + '.d'
    files_to_write = dict()
    files_to_write[eni_path] = "auto lo\niface lo inet loopback\n"
    files_to_write[eni_path] += "source /etc/network/interfaces.d/*.cfg\n"
    # Sort the interfaces by id so that we'll have consistent output order
    for iname, interface in sorted(
            interfaces.items(), key=lambda x: x[1]['id']):
        # sys_interfaces is pruned by --interface; if one of the
        # raw_macs (or, *the* MAC for single interfaces) does not
        # match as one of the interfaces we want configured, skip
        raw_macs = interface.get('raw_macs', [interface['mac_address']])
        if not set(sys_interfaces).intersection(set(raw_macs)):
            continue

        # Determine the debian interface name and skip configuration for
        # this interface if config already exists for it.
        vlan_raw_device = None
        if 'vlan_id' in interface:
            # raw_macs will have a single entry if the vlan device is a
            # phsical device and >1 when it is a bond device.
            if len(raw_macs) == 1:
                vlan_raw_device = sys_interfaces.get(raw_macs[0])
            else:
                vlan_raw_device = interface['vlan_link']
            interface_name = "{0}.{1}".format(vlan_raw_device,
                                              interface['vlan_id'])
        elif 'bond_mode' in interface:
            # It is possible our interface does not have a link, so fall back
            # to iname which is the link id.
            interface_name = interface.get('link', iname)
        else:
            interface_name = sys_interfaces[interface['mac_address']]

        iface_path = os.path.join(eni_d_path, '%s-%s.cfg' % (interface['priority'], interface_name))
        if os.path.exists(iface_path):
            continue

        if iface_path not in files_to_write:
            # Write the header if this is the first time editing
            # this interface.
            result = "auto {0}\n".format(interface_name)
        else:
            # Append to existing config
            result = files_to_write[iface_path]

        if interface['type'] == 'ipv4_dhcp':
            result += "iface {0} inet dhcp\n".format(interface_name)
            result += "    mtu {0}\n".format(interface['mtu'])
            if vlan_raw_device is not None:
                result += "    vlan-raw-device {0}\n".format(vlan_raw_device)
                result += "    hw-mac-address {0}\n".format(
                    interface['mac_address'])
        elif interface['type'] == 'manual':
            result += "iface {0} inet manual\n".format(interface_name)
            result += "    mtu {0}\n".format(interface['mtu'])
        else:
            # Static ipv4 and ipv6
            if interface['type'] == 'ipv6':
                link_type = "inet6"
            elif interface['type'] == 'ipv4':
                link_type = "inet"
            else:
                # We do not know this type of entry
                continue

            result += "iface {name} {link_type} static\n".format(
                name=interface_name, link_type=link_type)
            if vlan_raw_device:
                result += "    vlan-raw-device {0}\n".format(vlan_raw_device)
            result += "    address {0}\n".format(interface['ip_address'])

            if interface['type'] == 'ipv4':
                result += "    netmask {0}\n".format(interface['netmask'])
            else:
                result += "    netmask {0}\n".format(
                    utils.ipv6_netmask_length(interface['netmask']))

            for route in interface['routes']:
                if ((route['network'] == '0.0.0.0' and
                        route['netmask'] == '0.0.0.0') or
                    (route['network'] == '::' and
                        route['netmask'] == '::')):
                    result += "    gateway {0}\n".format(route['gateway'])
                else:
                    if interface['type'] == 'ipv4':
                        route_add = ("    up route add -net {net} netmask "
                                     "{mask} gw {gw} || true\n")
                        route_del = ("    down route del -net {net} netmask "
                                     "{mask} gw {gw} || true\n")
                        _netmask = route['netmask']
                    else:
                        route_add = ("    up ip -6 route add {net}/{mask} "
                                     "via {gw} dev {interface} || true\n")
                        route_del = ("    down ip -6 route del {net}/{mask} "
                                     "via {gw} dev {interface} || true\n")
                        _netmask = utils.ipv6_netmask_length(route['netmask'])

                    result += route_add.format(
                        net=route['network'], mask=_netmask,
                        gw=route['gateway'], interface=interface_name)
                    result += route_del.format(
                        net=route['network'], mask=_netmask,
                        gw=route['gateway'], interface=interface_name)
        if 'bond_master' in interface:
            result += "    bond-master {0}\n".format(
                interface['bond_master'])
        if 'bond_mode' in interface:
            result += _write_debian_bond_conf(interface_name,
                                              interface,
                                              sys_interfaces)
        files_to_write[iface_path] = result

    # Configure any interfaces not mentioned in the config drive data for DHCP.
    for mac, iname in sorted(
            sys_interfaces.items(), key=lambda x: x[1]):
        iface_path = os.path.join(eni_d_path, '%s.cfg' % iname)
        if os.path.exists(iface_path):
            # This interface already has a config file, move on
            continue
        inter_macs = [intf['mac_address'] for intf in interfaces.values()]
        link_macs = [intf.get('link_mac') for intf in interfaces.values()
                     if 'vlan_id' in interface]
        if mac in inter_macs or mac in link_macs:
            # We have a config drive config, move on
            continue
        result = "auto {0}\n".format(iname)
        result += "iface {0} inet dhcp\n".format(iname)
        files_to_write[iface_path] = result
    return files_to_write


def write_dns_info(dns_servers):
    resolve_confs = {}
    resolv_nameservers = ""
    for server in dns_servers:
        resolv_nameservers += "nameserver {0}\n".format(server)
    resolve_confs['/etc/resolv.conf'] = resolv_nameservers
    # set up resolved if available
    if os.path.isfile('/etc/systemd/resolved.conf'):
        # read the existing config  so we only overwrite what's needed
        resolved_conf = configparser.ConfigParser()
        resolved_conf.read('/etc/systemd/resolved.conf')
        # create config section if not created
        if not resolved_conf.has_section('Resolve'):
            resolved_conf.add_section('Resolve')
        # write space separated dns servers
        resolved_conf.set('Resolve', 'DNS', " ".join(dns_servers))
        # use stringio to output the resulting config to string
        # configparser only outputs to file descriptors
        resolved_conf_fd = StringIO("")
        resolved_conf.write(resolved_conf_fd)
        resolved_conf_output = resolved_conf_fd.getvalue()
        resolved_conf_fd.close()
        # add the config to files to be written
        resolve_confs['/etc/systemd/resolved.conf'] = resolved_conf_output
    return resolve_confs


def get_config_drive_interfaces(net):
    interfaces = {}

    if 'networks' not in net or 'links' not in net:
        log.debug("No config-drive interfaces defined")
        return interfaces

    networks = {}
    for network in net['networks']:
        networks[network['link']] = network

    vlans = {}
    phys = {}
    bonds = {}
    for link in net['links']:
        if link['type'] == 'vlan':
            vlans[link['id']] = link
        elif link['type'] == 'bond':
            bonds[link['id']] = link
        else:
            phys[link['id']] = link

    for link in vlans.values():
        if link['vlan_link'] in phys:
            vlan_link = phys[link['vlan_link']]
            link['raw_macs'] = [vlan_link['ethernet_mac_address'].lower()]
        elif link['vlan_link'] in bonds:
            vlan_link = bonds[link['vlan_link']]
            link['raw_macs'] = []
            for phy in vlan_link['bond_links']:
                link['raw_macs'].append(
                    phys[phy]['ethernet_mac_address'].lower())
        link['mac_address'] = link.pop(
            'vlan_mac_address', vlan_link['ethernet_mac_address']).lower()
        link['priority'] = 30

    for link in bonds.values():
        phy_macs = []
        for phy in link['bond_links']:
            phy_link = phys[phy]
            phy_link['bond_master'] = link['id']
            if phy in phys:
                phy_macs.append(phy_link['ethernet_mac_address'].lower())
        link['raw_macs'] = phy_macs
        link['mac_address'] = link.pop('ethernet_mac_address').lower()
        if link['id'] not in networks:
            link['type'] = 'manual'
            interfaces[link['id']] = link
        link['priority'] = 30

    for link in phys.values():
        link['mac_address'] = link.pop('ethernet_mac_address').lower()
        link['mtu'] = link.pop('mtu')
        if link['id'] not in networks:
            link['type'] = 'manual'
            interfaces[link['id']] = link

    for network in net['networks']:
        link = vlans.get(
            network['link'],
            phys.get(network['link'], bonds.get(network['link'])))
        if not link:
            continue

        link.update(network)
        # NOTE(pabelanger): Make sure we index by the existing network id,
        # rather then creating out own.
        interfaces[network['id']] = copy.deepcopy(link)

    return interfaces


def get_dns_from_config_drive(net):
    if 'services' not in net:
        log.debug("No DNS info available from config-drive")
        return []
    return [
        f['address'] for f in net['services'] if f['type'] == 'dns'
    ]


def write_static_network_info(
        interfaces, sys_interfaces, files_to_write, args):

    if args.distro in ('debian', 'ubuntu'):
        files_to_write.update(
            write_debian_interfaces(interfaces, sys_interfaces))
    elif args.distro in ('redhat', 'centos', 'fedora') or \
            _is_suse(args.distro):
        files_to_write.update(
            write_redhat_interfaces(interfaces, sys_interfaces, args))
    elif args.distro in 'gentoo':
        files_to_write.update(
            write_gentoo_interfaces(interfaces, sys_interfaces)
        )
    elif args.distro in 'networkd':
        files_to_write.update(
            write_networkd_interfaces(interfaces, sys_interfaces, args)
        )
    else:
        return False

    finish_files(files_to_write, args)


def finish_files(files_to_write, args):
    files = sorted(files_to_write.keys())
    log.debug("Writing output files")
    for k in files:
        if not files_to_write[k]:
            # Don't write empty files
            log.debug("%s is blank, skipped" % k)
            continue

        if args.noop:
            sys.stdout.write("### Write {0}\n{1}".format(k, files_to_write[k]))
            continue

        retries = 0
        while True:
            try:
                log.debug("Writing output file : %s" % k)
                with safe_open(k, 'w') as outfile:
                    outfile.write(files_to_write[k])
                log.debug(" ... done")
                break
            except IOError as e:
                # if we got ELOOP the file was a dangling or bad
                # symlink.  We're taking ownership of this, so
                # overwrite it.
                if e.errno == errno.ELOOP and retries < 1:
                    log.debug("Dangling symlink <%s>; "
                              "unlinking and trying again" % k)
                    os.unlink(k)
                    retries = 1
                    continue
                elif e.errno == errno.EACCESS:
                    log.debug(" ... is read only, skipped")
                    break
                else:
                    raise


def is_interface_live(interface, sys_root):
    try:
        if open('{root}/{iface}/carrier'.format(
                root=sys_root, iface=interface)).read().strip() == '1':
            return True
    except IOError as e:
        # We get this error if the link is not up
        if e.errno != 22:
            raise
    return False


def interface_live(iface, sys_root, args):
    log.debug("Checking if interface %s has an active link carrier." % iface)
    if is_interface_live(iface, sys_root):
        return True

    if args.noop:
        return False

    subprocess.check_call(['ip', 'link', 'set', 'dev', iface, 'up'])

    return True


def is_interface_vlan(iface, distro):
    if distro in ('debian', 'ubuntu'):
        file_name = '/etc/network/interfaces.d/%s.cfg' % iface
        if os.path.exists(file_name):
            return 'vlan-raw-device' in open(file_name).read()
    elif distro in ('redhat', 'centos', 'fedora'):
        file_name = '/etc/sysconfig/network-scripts/ifcfg-%s' % iface
        if os.path.exists(file_name):
            return 'VLAN=YES' in open(file_name).read()
    elif _is_suse(distro):
        file_name = '/etc/sysconfig/network/ifcfg-%s' % iface
        if os.path.exists(file_name):
            return 'ETHERDEVICE' in open(file_name).read()
    elif distro in ('gentoo'):
        file_name = '/etc/conf.d/net.%s' % iface
        if os.path.exists(file_name):
            return 'vlan_id' in open(file_name).read()

    return False


def is_interface_bridge(iface, distro):
    if distro in ('debian', 'ubuntu'):
        file_name = '/etc/network/interfaces.d/%s.cfg' % iface
        if os.path.exists(file_name):
            return 'bridge_ports' in open(file_name).read().lower()
    elif distro in ('redhat', 'centos', 'fedora'):
        file_name = '/etc/sysconfig/network-scripts/ifcfg-%s' % iface
        if os.path.exists(file_name):
            return 'type=bridge' in open(file_name).read().lower()
    elif _is_suse(distro):
        file_name = '/etc/sysconfig/network/ifcfg-%s' % iface
        if os.path.exists(file_name):
            return 'bridge=yes' in open(file_name).read().lower()
    elif distro in ('gentoo'):
        file_name = '/etc/conf.d/net.%s' % iface
        if os.path.exists(file_name):
            return 'bridge' in open(file_name).read().lower()

    return False


def get_sys_interfaces(interface, args):
    log.debug("Probing system interfaces")
    sys_root = os.path.join(args.root, 'sys/class/net')

    ignored_interfaces = ('sit', 'tunl', 'bonding_master', 'teql', 'wg',
                          'ip6gre', 'ip6_vti', 'ip6tnl', 'bond', 'lo')
    sys_interfaces = {}
    if interface is not None:
        log.debug("Only considering interface %s from arguments" % interface)
        interfaces = [interface]
    else:
        interfaces = [f for f in os.listdir(sys_root)
                      if not f.startswith(ignored_interfaces)]
    # build interface dict. so we can enumerate through later
    if_dict = {}
    for iface in interfaces:
        # if interface is for an already configured vlan, skip it
        if is_interface_vlan(iface, args.distro):
            log.debug("Skipping vlan %s" % iface)
            continue

        # if interface is for an already configured bridge, skip it
        if is_interface_bridge(iface, args.distro):
            log.debug("Skipping bridge %s" % iface)
            continue

        mac_addr_type = open(
            '%s/%s/addr_assign_type' % (sys_root, iface), 'r').read().strip()
        # Interfaces without a permanent address are likely created by some
        # other system on the host like a running neutron agent. In these cases
        # that system should be responsible for configuring the interface not
        # glean.
        if mac_addr_type != PERMANENT_ADDR_TYPE:
            continue
        # check if interface is up if not try and bring it up
        if interface_live(iface, sys_root, args):
            mac = open('%s/%s/address' % (sys_root, iface), 'r').read().strip()
            if_dict[iface] = mac

    # wait up to 9 seconds all interfaces to reach up
    log.debug("Waiting for interfaces to become active.")
    if_up_list = []
    for x in range(0, 90):
        for iface in if_dict:
            mac = if_dict[iface]
            if iface in if_up_list:
                continue
            if is_interface_live(iface, sys_root):
                # Add system interface
                sys_interfaces[mac] = iface
                log.debug("Added system interface %s (%s)" % (iface, mac))
                if_up_list.append(iface)

        if sorted(if_up_list) == sorted(if_dict.keys()):
            # all interfaces are up no need to continue looping
            break
        time.sleep(.1)

    if sorted(if_up_list) != sorted(if_dict.keys()):
        # not all interfaces became active with in the time limit
        for iface in if_dict:
            if iface in if_up_list:
                continue
            msg = "Skipping system interface %s (%s)" % (iface,
                                                         if_dict[iface])
            log.warn(msg)

    return sys_interfaces


def get_network_info(args):
    """Retrieves network info from config-drive.

    If there is no meta_data.json in config-drive, it means that there
    is no config drive mounted- which means we know nothing.
    """
    config_drive = os.path.join(args.root, 'mnt/config')
    network_info_file = '%s/openstack/latest/network_info.json' % config_drive
    network_data_file = '%s/openstack/latest/network_data.json' % config_drive
    vendor_data_file = '%s/openstack/latest/vendor_data.json' % config_drive

    network_info = {}
    if os.path.exists(network_info_file):
        log.debug("Found network_info file %s" % network_info_file)
        network_info = json.load(open(network_info_file))
    # network_data.json is the file written by nova that should be there.
    # Other cloud deployments may use the above network_info.json or
    # vendor_data.json but the canonical location is this one.
    if os.path.exists(network_data_file):
        log.debug("Found network_info file %s" % network_data_file)
        network_info = json.load(open(network_data_file))
    elif os.path.exists(vendor_data_file):
        log.debug("Found vendor_data_file file %s" % vendor_data_file)
        vendor_data = json.load(open(vendor_data_file))
        if 'network_info' in vendor_data:
            log.debug("Found network_info in vendor_data_file")
            network_info = vendor_data['network_info']
    else:
        log.debug("Did not find vendor_data or network_info in config-drive")

    if not network_info:
        log.debug("Found no network_info in config-drive!  "
                  "Asusming DHCP interfaces")

    return network_info


def write_network_info_from_config_drive(args):
    """Write network info from config-drive.

    If there is no meta_data.json in config-drive, it means that there
    is no config drive mounted- which means we know nothing.

    Can set 'glean_ignore_interfaces' in nova metadata to ignore the
    interface configuration specified by the config drive. This will
    cause it to fallback to using dhcp configuration.

    Returns False on any issue, which will cause the writing of
    DHCP network files.
    """

    config_drive = os.path.join(args.root, 'mnt/config')
    meta_data_path = '%s/openstack/latest/meta_data.json' % config_drive
    meta_data = {}
    if os.path.exists(meta_data_path):
        meta_data = json.load(open(meta_data_path))

    network_info = get_network_info(args)

    dns = {}
    if not args.skip_dns:
        dns = write_dns_info(get_dns_from_config_drive(network_info))
    interfaces = get_config_drive_interfaces(network_info)
    if 'meta' in meta_data and 'glean_ignore_interfaces' in meta_data['meta']:
        # Force DHCP to be used ignoring the interface information.
        # Some clouds have neutron configured in such a way that we get
        # interface config drive data that is at odds with the networking
        # in the cloud. Note we set interfaces to {} so that fallback dhcp
        # configuration can happen in write_static_network_info().
        interfaces = {}
    sys_interfaces = get_sys_interfaces(args.interface, args)

    write_static_network_info(interfaces, sys_interfaces, dns, args)


def write_ssh_keys(args):
    """Write ssh-keys from config-drive.

    If there is no meta_data.json in config-drive, it means that there
    is no config drive mounted- which means we do nothing.
    """

    config_drive = os.path.join(args.root, 'mnt/config')
    ssh_path = os.path.join(args.root, 'root/.ssh')
    meta_data_path = '%s/openstack/latest/meta_data.json' % config_drive
    if not os.path.exists(meta_data_path):
        return 0

    meta_data = json.load(open(meta_data_path))
    if 'public_keys' not in meta_data:
        return 0

    keys_to_write = []

    # if we have keys already there, we want to preserve them
    if os.path.exists('/root/.ssh/authorized_keys'):
        with open('/root/.ssh/authorized_keys', 'r') as fk:
            for line in fk:
                keys_to_write.append(line.strip())
    for (name, key) in meta_data['public_keys'].items():
        key_title = "# Injected key {name} by keypair extension".format(
            name=name)
        if key_title not in keys_to_write:
            keys_to_write.append(key_title)

        if key not in keys_to_write:
            keys_to_write.append(key)

    files_to_write = {
        '/root/.ssh/authorized_keys': '\n'.join(keys_to_write) + '\n',
    }
    try:
        os.mkdir(ssh_path, 0o700)
    except OSError as e:
        if e.errno != 17:  # not File Exists
            raise
    finish_files(files_to_write, args)


def set_hostname_from_config_drive(args):
    if args.noop:
        return

    config_drive = os.path.join(args.root, 'mnt/config')
    meta_data_path = '%s/openstack/latest/meta_data.json' % config_drive
    if not os.path.exists(meta_data_path):
        return

    meta_data = json.load(open(meta_data_path))
    if 'name' not in meta_data:
        return

    hostname = meta_data['name']
    log.debug("Got hostname from meta_data.json : %s" % hostname)
    # underscore is not a valid hostname, but it's easy to name your
    # host with that on the command-line.  be helpful...
    if '_' in hostname:
        hostname = hostname.replace('_', '-')
        log.debug("Fixed up hostname to %s" % hostname)

    ret = subprocess.call(['hostname', hostname])
    if ret != 0:
        raise RuntimeError('Error setting hostname')
    else:
        # gentoo's hostname file is in a different location
        if args.distro is 'gentoo':
            with open('/etc/conf.d/hostname', 'w') as fh:
                fh.write("hostname=\"{host}\"\n".format(host=hostname))
        else:
            with safe_open('/etc/hostname', 'w') as fh:
                fh.write(hostname)
                fh.write('\n')

        # generate the lists of hosts and ips
        hosts_to_add = {'localhost': '127.0.0.1'}

        # get information on the network
        hostname_ip = '127.0.1.1'
        network_info = get_network_info(args)
        if network_info:
            interfaces = get_config_drive_interfaces(network_info)
            keys = sorted(interfaces.keys())

            for key in keys:
                interface = interfaces[key]
                if interface and 'ip_address' in interface:
                    hostname_ip = interface['ip_address']
                    break

        # check short hostname and generate list for hosts
        hosts_to_add[hostname] = hostname_ip
        short_hostname = hostname.split('.')[0]
        if short_hostname != hostname:
            hosts_to_add[short_hostname] = hostname_ip

        for host in hosts_to_add:
            host_value = hosts_to_add[host]
            # See if we already have a hosts entry for hostname
            prog = re.compile('^%s .*%s\n' % (host_value, host))
            match = None
            if os.path.isfile('/etc/hosts'):
                with open('/etc/hosts') as fh:
                    match = prog.match(fh.read())

            # Write out a hosts entry for hostname
            if match is None:
                with safe_open('/etc/hosts', 'a+') as fh:
                    fh.write(u'%s %s\n' % (host_value, host))


def main(argv=None):

    if argv is None:
        args = sys.argv[1:]

    parser = argparse.ArgumentParser(description="Static network config")
    parser.add_argument(
        '-n', '--noop', action='store_true', help='Do not write files')
    _distro = distro.linux_distribution(
        full_distribution_name=False)[0].lower()
    parser.add_argument(
        '--distro', dest='distro', default=_distro,
        help='Override distro (detected "%s")' % _distro)
    parser.add_argument(
        '--root', dest='root', default='/',
        help='Mounted root for config drive info, defaults to /')
    parser.add_argument(
        '-i', '--interface', dest='interface',
        default=None, help="Interface to process")
    parser.add_argument(
        '--ssh', dest='ssh', action='store_true', help="Write ssh key")
    parser.add_argument(
        '--hostname', dest='hostname', action='store_true',
        help="Set the hostname if name is available in config drive.")
    parser.add_argument(
        '--skip-network', dest='skip', action='store_true',
        help="Do not write network info")
    parser.add_argument(
        '--use-nm', dest='use_nm', action='store_true',
        help=('Use NetworkManager instead of legacy'
              'configuration scripts to manage interfaces'))
    parser.add_argument(
        '--skip-dns', dest='skip_dns', action='store_true',
        help='Do not write dns info')
    parser.add_argument(
        '--debug', dest='debug', action='store_true',
        help="Enable debugging output")
    args = parser.parse_args(argv)

    if args.debug:
        logging.basicConfig(level=logging.DEBUG)
    else:
        logging.basicConfig(level=logging.INFO)

    log.debug("Starting glean")
    log.debug("Detected distro : %s" % args.distro)
    log.debug("Configuring %s NetworkManager" %
              "with" if args.use_nm else "without")

    with systemlock.Lock('/tmp/glean.lock'):
        if args.ssh:
            write_ssh_keys(args)
        if args.hostname:
            set_hostname_from_config_drive(args)
        if args.interface != 'lo' and not args.skip:
            write_network_info_from_config_drive(args)
    log.debug("Done!")
    return 0


if __name__ == '__main__':
    sys.exit(main())
