Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
forms.py 10.34 KiB
# -*- coding: utf-8 -*-
"""
app.network.forms
~~~~~~~~~~~~~~~~~

This module defines the network blueprint forms.

:copyright: (c) 2017 European Spallation Source ERIC
:license: BSD 2-Clause, see LICENSE for more details.

"""
from flask import current_app
from flask_login import current_user
from wtforms import (
    SelectField,
    StringField,
    TextAreaField,
    IntegerField,
    SelectMultipleField,
    BooleanField,
    validators,
)
from ..helpers import CSEntryForm
from ..validators import (
    Unique,
    UniqueAccrossModels,
    RegexpList,
    IPNetwork,
    HOST_NAME_RE,
    INTERFACE_NAME_RE,
    VLAN_NAME_RE,
    MAC_ADDRESS_RE,
    NoValidateSelectField,
)
from ..fields import YAMLField
from .. import utils, models


def starts_with_hostname(form, field):
    """Check that interface name starts with hostname"""
    try:
        # Create / Edit interface form
        host_id_field = form["host_id"]
    except KeyError:
        # Create host form
        hostname = form["name"].data
    else:
        host = models.Host.query.get(host_id_field.data)
        hostname = host.name
    if not field.data.startswith(hostname):
        raise validators.ValidationError(
            f'Interface name shall start with the hostname "{hostname}"'
        )


def ip_in_network(form, field):
    """Check that the IP is in the network"""
    network_id_field = form["network_id"]
    if not network_id_field.data:
        raise validators.ValidationError(
            "Can't validate the IP. No network was selected."
        )
    network = models.Network.query.get(network_id_field.data)
    utils.validate_ip(field.data, network)


class DomainForm(CSEntryForm):
    name = StringField(
        "Name",
        validators=[validators.InputRequired(), Unique(models.Domain, column="name")],
    )


class NetworkScopeForm(CSEntryForm):
    name = StringField(
        "Name",
        description="name must be 3-25 characters long and contain only letters, numbers and dash",
        validators=[
            validators.InputRequired(),
            validators.Regexp(VLAN_NAME_RE),
            Unique(models.NetworkScope, column="name"),
        ],
    )
    description = TextAreaField("Description")
    first_vlan = IntegerField("First vlan", validators=[validators.optional()])
    last_vlan = IntegerField("Last vlan", validators=[validators.optional()])
    supernet = StringField(
        "Supernet", validators=[validators.InputRequired(), IPNetwork()]
    )
    domain_id = SelectField("Default domain")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.domain_id.choices = utils.get_model_choices(models.Domain, attr="name")


class NetworkForm(CSEntryForm):
    scope_id = SelectField("Network Scope")
    vlan_name = StringField(
        "Vlan name",
        description="vlan name must be 3-25 characters long and contain only letters, numbers and dash",
        validators=[
            validators.InputRequired(),
            validators.Regexp(VLAN_NAME_RE),
            Unique(models.Network, column="vlan_name"),
        ],
    )
    vlan_id = NoValidateSelectField(
        "Vlan id", choices=[], coerce=utils.coerce_to_str_or_none
    )
    description = TextAreaField("Description")
    prefix = NoValidateSelectField("Prefix", choices=[])
    address = NoValidateSelectField("Address", choices=[])
    first_ip = NoValidateSelectField("First IP", choices=[])
    last_ip = NoValidateSelectField("Last IP", choices=[])
    gateway = NoValidateSelectField("Gateway IP", choices=[])
    domain_id = SelectField("Domain")
    admin_only = BooleanField("Admin only")
    sensitive = BooleanField("Sensitive")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.scope_id.choices = utils.get_model_choices(
            models.NetworkScope, attr="name"
        )
        self.domain_id.choices = utils.get_model_choices(models.Domain, attr="name")


class EditNetworkForm(CSEntryForm):
    vlan_name = StringField(
        "Vlan name",
        description="vlan name must be 3-25 characters long and contain only letters, numbers and dash",
        validators=[
            validators.InputRequired(),
            validators.Regexp(VLAN_NAME_RE),
            Unique(models.Network, column="vlan_name"),
        ],
    )
    vlan_id = IntegerField("Vlan id")
    description = TextAreaField("Description")
    address = StringField("Address")
    first_ip = StringField("First IP")
    last_ip = StringField("Last IP")
    gateway = StringField("Gateway IP")
    domain_id = SelectField("Domain")
    admin_only = BooleanField("Admin only")
    sensitive = BooleanField("Sensitive")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.domain_id.choices = utils.get_model_choices(models.Domain, attr="name")


class HostForm(CSEntryForm):
    name = StringField(
        "Hostname",
        description="hostname must be 2-24 characters long and contain only letters, numbers and dash",
        validators=[
            validators.InputRequired(),
            validators.Regexp(HOST_NAME_RE),
            Unique(models.Host),
            UniqueAccrossModels([models.Cname]),
        ],
        filters=[utils.lowercase_field],
    )
    description = TextAreaField("Description")
    device_type_id = SelectField("Device Type")
    is_ioc = BooleanField(
        "IOC",
        default=False,
        description="This host will be used to run IOCs",
    )
    ansible_vars = YAMLField(
        "Ansible vars",
        description="Enter variables in YAML format. See https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html",
    )
    ansible_groups = SelectMultipleField(
        "Ansible groups", coerce=utils.coerce_to_str_or_none
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.device_type_id.choices = utils.get_model_choices(models.DeviceType)
        ansible_group_query = models.AnsibleGroup.query.filter(
            models.AnsibleGroup.type == models.AnsibleGroupType.STATIC
        )
        self.ansible_groups.choices = utils.get_model_choices(
            models.AnsibleGroup, attr="name", query=ansible_group_query
        )


class InterfaceForm(CSEntryForm):
    host_id = SelectField("Host")
    network_id = SelectField("Network")
    ip = StringField(
        "IP address",
        validators=[
            validators.InputRequired(),
            validators.IPAddress(),
            ip_in_network,
            Unique(models.Interface, column="ip"),
        ],
    )
    interface_name = StringField(
        "Interface name",
        description="name must be 2-29 characters long and contain only letters, numbers and dash",
        validators=[
            validators.InputRequired(),
            validators.Regexp(INTERFACE_NAME_RE),
            Unique(models.Interface),
            starts_with_hostname,
            UniqueAccrossModels([models.Cname]),
        ],
        filters=[utils.lowercase_field],
    )
    interface_description = TextAreaField("Description")
    random_mac = BooleanField("Random MAC", default=False)
    mac = StringField(
        "MAC",
        validators=[
            validators.Optional(),
            validators.Regexp(MAC_ADDRESS_RE, message="Invalid MAC address"),
            Unique(models.Interface, column="mac"),
        ],
    )
    cnames_string = StringField(
        "Cnames",
        description="space separated list of cnames (must be 2-24 characters long and contain only letters, numbers and dash)",
        validators=[
            validators.Optional(),
            RegexpList(HOST_NAME_RE),
            UniqueAccrossModels([models.Host, models.Interface]),
        ],
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.host_id.choices = utils.get_model_choices(models.Host)
        # Only return the networks the user has access to
        self.network_id.choices = [
            (str(network.id), network.vlan_name)
            for network in models.Network.query.order_by(models.Network.vlan_name).all()
            if current_user.has_access_to_network(network)
        ]


class HostInterfaceForm(HostForm, InterfaceForm):
    pass


class CreateVMForm(CSEntryForm):
    cores = SelectField("Cores", default=2, coerce=int)
    memory = SelectField("Memory (GB)", default=2, coerce=int)
    disk = SelectField("Disk (GB)", default=15, coerce=int)
    osversion = SelectField("OS Version")
    skip_post_install_job = BooleanField("Skip post install job", default=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.cores.choices = utils.get_choices(current_app.config["VM_CORES_CHOICES"])
        self.memory.choices = utils.get_choices(current_app.config["VM_MEMORY_CHOICES"])
        self.disk.choices = utils.get_choices(current_app.config["VM_DISK_CHOICES"])
        self.osversion.choices = utils.get_choices(
            current_app.config["VM_OSVERSION_CHOICES"]
        )


class BootProfileForm(CSEntryForm):
    boot_profile = SelectField("Boot profile")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.boot_profile.choices = utils.get_choices(
            current_app.config["AUTOINSTALL_BOOT_PROFILES"]
        )


class AnsibleGroupForm(CSEntryForm):
    name = StringField(
        "name",
        validators=[validators.InputRequired(), Unique(models.AnsibleGroup)],
        filters=[utils.lowercase_field],
    )
    vars = YAMLField(
        "Ansible vars",
        description="Enter variables in YAML format. See https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html",
    )
    type = SelectField(
        "Type",
        choices=models.AnsibleGroupType.choices(),
        default=models.AnsibleGroupType.STATIC,
        coerce=models.AnsibleGroupType.coerce,
    )
    children = SelectMultipleField("Children", coerce=utils.coerce_to_str_or_none)
    hosts = SelectMultipleField("Hosts", coerce=utils.coerce_to_str_or_none)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.children.choices = [
            (str(group.id), group.name)
            for group in models.AnsibleGroup.query.order_by(
                models.AnsibleGroup.name
            ).all()
            if group.name != "all"
        ]
        self.hosts.choices = utils.get_model_choices(
            models.Host, attr="fqdn", order_by="name"
        )