Exemple #1
0
    def _validate_and_clean_url(self, param_name, param):
        """Validate and clean URL input."""
        if param.get('allow_list', False):
            value = param['value'].split(',')
        else:
            value = [param['value']]

        for url in value:
            # Django's validator requires a protocol but we accept just a
            # hostname or IP address.
            try:
                ip = ip_address(url)
            except ValueError:
                if '://' not in url:
                    url = 'http://%s' % url
            else:
                if ip.version == 4:
                    url = 'http://%s' % url
                else:
                    url = 'http://[%s]' % url
            # Allow all schemes supported by curl(used with the network
            # validation test) + icmp.
            try:
                validate_url(url,
                             schemes=[
                                 'icmp', 'file', 'ftp', 'ftps', 'gopher',
                                 'http', 'https', 'imap', 'imaps', 'ldap',
                                 'ldaps', 'pop3', 'pop3s', 'rtmp', 'rtsp',
                                 'scp', 'sftp', 'smb', 'smbs', 'smtp', 'smtps',
                                 'telnet', 'tftp'
                             ])
            except ValidationError:
                set_form_error(self, param_name, 'Invalid URL')
Exemple #2
0
 def _clean_disabled_boot_architectures(self, cleaned_data):
     disabled_boot_architectures = cleaned_data.get(
         "disabled_boot_architectures")
     if disabled_boot_architectures is None:
         return cleaned_data
     disabled_arches = []
     for disabled_arch in disabled_boot_architectures:
         disabled_arches += disabled_arch.split(" ")
     cleaned_arches = set()
     octet_to_boot_method = {
         boot_method.arch_octet: boot_method
         for _, boot_method in BootMethodRegistry if boot_method.arch_octet
     }
     for disabled_arch in disabled_arches:
         boot_method = BootMethodRegistry.get_item(
             disabled_arch,
             octet_to_boot_method.get(disabled_arch.replace("0x", "00:")),
         )
         if boot_method is None or (not boot_method.arch_octet
                                    and not boot_method.path_prefix_http):
             set_form_error(
                 self,
                 "disabled_boot_architectures",
                 f"Unknown boot architecture {disabled_arch}",
             )
         else:
             cleaned_arches.add(boot_method.name)
     cleaned_data["disabled_boot_architectures"] = list(cleaned_arches)
     return cleaned_data
Exemple #3
0
 def _validate_results(self, results={}):
     valid = True
     if isinstance(results, list):
         for result in results:
             if not isinstance(result, str):
                 set_form_error(
                     self, 'results',
                     'Each result in a result list must be a string.')
                 valid = False
     elif isinstance(results, dict):
         for result in results.values():
             if not isinstance(result, dict):
                 set_form_error(
                     self, 'results',
                     'Each result in a result dictionary must be a '
                     'dictionary.')
             elif 'title' not in result:
                 set_form_error(
                     self, 'results',
                     'title must be included in a result dictionary.')
                 valid = False
             else:
                 for key in ['title', 'description']:
                     if key in result and not isinstance(result[key], str):
                         set_form_error(
                             self, 'results', '%s must be a string.' % key)
                         valid = False
     else:
         set_form_error(
             self, 'results',
             'results must be a list of strings or a dictionary of '
             'dictionaries.')
         valid = False
     return valid
Exemple #4
0
    def _validate_and_clean_storage(self, param_name, param, result_params,
                                    ret):
        """Validate and clean storage input."""
        value = param['value']
        for i in value.split(','):
            if ':' in i:
                # Allow users to specify a disk using the model and serial.
                model, serial = i.split(':')
                try:
                    bd = self._node.physicalblockdevice_set.get(model=model,
                                                                serial=serial)
                except ObjectDoesNotExist:
                    pass
                else:
                    clean_param = copy.deepcopy(result_params)
                    clean_param[param_name] = copy.deepcopy(param)
                    clean_param[param_name][
                        'value'] = self._blockdevice_to_dict(bd)
                    ret.append(clean_param)
                    continue

            qs = self._node.physicalblockdevice_set.filter(
                Q(name=i) | Q(name=os.path.basename(i)) | Q(model=i)
                | Q(serial=i) | Q(tags__overlap=[i]))
            if len(qs) == 0:
                set_form_error(
                    self, param_name, "Unknown storage device for %s(%s)" %
                    (self._node.fqdn, self._node.system_id))
                continue
            for bd in qs:
                clean_param = copy.deepcopy(result_params)
                clean_param[param_name] = copy.deepcopy(param)
                clean_param[param_name]['value'] = self._blockdevice_to_dict(
                    bd)
                ret.append(clean_param)
Exemple #5
0
 def clean(self):
     """Validate the lv_size."""
     cleaned_data = super().clean()
     lv_size = self.get_lv_size()
     if lv_size is not None:
         root_size = self.get_root_size()
         if root_size is None:
             root_size = (self.boot_disk.size - EFI_PARTITION_SIZE -
                          self.get_boot_size())
         if is_percentage(lv_size):
             lv_size = calculate_size_from_percentage(root_size, lv_size)
         if lv_size < MIN_ROOT_PARTITION_SIZE:
             set_form_error(
                 self,
                 "lv_size",
                 "Size is too small. Minimum size is %s." %
                 MIN_ROOT_PARTITION_SIZE,
             )
         if lv_size > root_size:
             set_form_error(
                 self,
                 "lv_size",
                 "Size is too large. Maximum size is %s." % root_size,
             )
         cleaned_data["lv_size"] = lv_size
     return cleaned_data
    def clean_arch(self):
        """Turn `arch` parameter into a list of architecture names.

        Even though `arch` is a single-value field, it turns into a list
        during cleaning.  The architecture parameter may be a wildcard.
        """
        # Import list_all_usable_architectures as part of its module, not
        # directly, so that patch_usable_architectures can easily patch it
        # for testing purposes.
        usable_architectures = maasserver_forms.list_all_usable_architectures()
        architecture_wildcards = get_architecture_wildcards(
            usable_architectures)
        value = self.cleaned_data[self.get_field_name("arch")]
        if value:
            if value in usable_architectures:
                # Full 'arch/subarch' specified directly.
                return [value]
            elif value in architecture_wildcards:
                # Try to expand 'arch' to all available 'arch/subarch'
                # matches.
                return architecture_wildcards[value]
            set_form_error(
                self,
                self.get_field_name("arch"),
                "Architecture not recognised.",
            )
        return None
Exemple #7
0
    def clean(self):
        cleaned_data = super().clean()
        power_type = self.cleaned_data.get("type")
        if not self.drivers:
            set_form_error(
                self,
                "type",
                "No rack controllers are connected, unable to validate.",
            )
        elif not self.is_new and self.instance.power_type != power_type:
            set_form_error(
                self,
                "type",
                "Cannot change the type of a pod. Delete and re-create the "
                "pod with a different type.",
            )

        should_generate_cert = (power_type == "lxd"
                                and not cleaned_data.get("certificate")
                                and not cleaned_data.get("key"))
        if should_generate_cert:
            pod_name = cleaned_data.get("name")
            cert = generate_certificate(get_maas_client_cn(pod_name))
            cleaned_data["certificate"] = cert.certificate_pem()
            cleaned_data["key"] = cert.private_key_pem()

        return cleaned_data
Exemple #8
0
 def clean(self):
     cleaned_data = super().clean()
     if "space" in self.data:
         set_form_error(
             self,
             "space",
             "Spaces may no longer be set on subnets. Set the space on the "
             "underlying VLAN.",
         )
     # The default value for 'allow_dns' is True.
     if "allow_dns" not in self.data:
         cleaned_data["allow_dns"] = True
     # The default value for 'allow_proxy' is True.
     if "allow_proxy" not in self.data:
         cleaned_data["allow_proxy"] = True
     # The default value for 'managed' is True.
     if "managed" not in self.data:
         cleaned_data["managed"] = True
     # The ArrayField form has a bug which leaves out the first entry.
     if "dns_servers" in self.data and self.data["dns_servers"] != "":
         cleaned_data["dns_servers"] = self.data.getlist("dns_servers")
     cleaned_data = self._clean_name(cleaned_data)
     cleaned_data = self._clean_dns_servers(cleaned_data)
     cleaned_data = self._clean_disabled_boot_architectures(cleaned_data)
     if self.instance.id is None:
         # We only allow the helpers when creating. When updating we require
         # the VLAN specifically. This is because we cannot make a correct
         # determination on what should be done in this case.
         cleaned_data = self._clean_vlan(cleaned_data)
     return cleaned_data
Exemple #9
0
 def clean_for_hardware(self):
     """Convert from JSON and validate for_hardware input."""
     if self.cleaned_data["for_hardware"] == "":
         return self.instance.for_hardware
     try:
         for_hardware = json.loads(self.cleaned_data["for_hardware"])
     except JSONDecodeError:
         for_hardware = self.cleaned_data["for_hardware"]
     if isinstance(for_hardware, str):
         for_hardware = for_hardware.split(",")
     if not isinstance(for_hardware, list):
         set_form_error(self, "for_hardware", "Must be a list or string")
         return
     regex = re.compile(
         r"^modalias:.+|pci:[\da-f]{4}:[\da-f]{4}|"
         r"usb:[\da-f]{4}:[\da-f]{4}|"
         r"system_vendor:.*|"
         r"system_product:.*|"
         r"system_version:.*|"
         r"mainboard_vendor:.*|"
         r"mainboard_product:.*$",
         re.I,
     )
     for hw_id in for_hardware:
         if regex.search(hw_id) is None:
             set_form_error(
                 self,
                 "for_hardware",
                 "Hardware identifier '%s' must be a modalias, PCI ID, "
                 "USB ID, system vendor, system product, system version, "
                 "mainboard vendor, or mainboard product." % hw_id,
             )
     return for_hardware
Exemple #10
0
 def _validate_and_clean_choices(self, param_name, param):
     # Support a Django choice list or a string list
     choices = [
         choice if isinstance(choice, str) else choice[0]
         for choice in param["choices"]
     ]
     if param["value"] not in choices:
         set_form_error(self, param_name,
                        f"Invalid choice \"{param['value']}\"")
Exemple #11
0
    def is_valid(self):
        valid = super().is_valid()

        if valid and self.instance.default and not self.edit_default:
            for field in self.Meta.fields:
                if field in ['tags', 'timeout']:
                    continue
                if field in self.data:
                    set_form_error(
                        self, field,
                        'Not allowed to change on default scripts.')
                    valid = False

        name = self.data.get('name')
        # none is used to tell the API to not run testing_scripts during
        # commissioning.
        if name is not None and name.lower() == 'none':
            set_form_error(self, 'name', '"none" is a reserved name.')
            valid = False

        # The name can't be a digit as MAAS allows scripts to be selected by
        # id.
        if name is not None and name.isdigit():
            set_form_error(self, 'name', 'Cannot be a number.')
            valid = False

        if name is not None and pipes.quote(name) != name:
            set_form_error(
                self, 'name',
                'Name contains disallowed characters, e.g. space or quotes.')
            valid = False

        # If comment and script exist __init__ combines both fields into a dict
        # to pass to VersionedTextFileField.
        if 'comment' in self.data:
            set_form_error(
                self, 'comment',
                '"comment" may only be used when specifying a "script" '
                'as well.')
            valid = False

        if 'script' in self.data:
            if not self._validate_results(self.instance.results):
                valid = False

        if 'parameters' in self.data:
            params_form = ParametersForm(data=self.data.get('parameters'))
            if not params_form.is_valid():
                valid = False

        if (not valid and self.instance.script_id is not None
                and self.initial.get('script') != self.instance.script_id
                and self.instance.script.id is not None):
            # If form validation failed cleanup any new VersionedTextFile
            # created by the VersionedTextFileField.
            self.instance.script.delete()
        return valid
Exemple #12
0
    def clean_packages(self):
        if self.cleaned_data["packages"] == "":
            return self.instance.packages
        else:
            packages = json.loads(self.cleaned_data["packages"])

            # Automatically convert into a list incase only one package is
            # needed.
            for key in ["apt", "snap", "url"]:
                if key in packages and not isinstance(packages[key], list):
                    packages[key] = [packages[key]]

            for key in ["apt", "url"]:
                if key in packages:
                    for package in packages[key]:
                        if not isinstance(package, str):
                            set_form_error(
                                self,
                                "packages",
                                "Each %s package must be a string." % key,
                            )
            if "snap" in packages:
                for package in packages["snap"]:
                    if isinstance(package, dict):
                        if "name" not in package or not isinstance(
                                package["name"], str):
                            set_form_error(
                                self,
                                "packages",
                                "Snap package name must be defined.",
                            )
                        if "channel" in package and package["channel"] not in [
                                "stable",
                                "edge",
                                "beta",
                                "candidate",
                        ]:
                            set_form_error(
                                self,
                                "packages",
                                "Snap channel must be stable, edge, beta, "
                                "or candidate.",
                            )
                        if "mode" in package and package["mode"] not in [
                                "classic",
                                "dev",
                                "jail",
                        ]:
                            set_form_error(
                                self,
                                "packages",
                                "Snap mode must be classic, dev, or jail.",
                            )
                    elif not isinstance(package, str):
                        set_form_error(self, "packages",
                                       "Snap package must be a string.")
            return packages
Exemple #13
0
 def clean(self):
     gateway_ip = self.cleaned_data.get('gateway_ip')
     source = self.cleaned_data.get('source')
     if gateway_ip:
         # This will not raise an AddrFormatErorr because it is validated at
         # the field first and if that fails the gateway_ip will be blank.
         if IPAddress(gateway_ip) not in source.get_ipnetwork():
             set_form_error(
                 self, 'gateway_ip',
                 'Enter an IP address in %s.' % source.cidr)
Exemple #14
0
 def clean_zone(self):
     value = self.cleaned_data[self.get_field_name('zone')]
     if value:
         nonexistent_names = detect_nonexistent_zone_names([value])
         if len(nonexistent_names) > 0:
             error_msg = "No such zone: '%s'." % value
             set_form_error(self, self.get_field_name('zone'), error_msg)
             return None
         return value
     return None
 def clean_not_in_pool(self):
     value = self.cleaned_data[self.get_field_name("not_in_pool")]
     if not value:
         return None
     nonexistent_names = detect_nonexistent_names(ResourcePool, value)
     if nonexistent_names:
         error_msg = "No such pool(s): %s." % ", ".join(nonexistent_names)
         set_form_error(self, self.get_field_name("not_in_pool"), error_msg)
         return None
     return value
 def clean_zone(self):
     value = self.cleaned_data[self.get_field_name("zone")]
     if value:
         nonexistent_names = detect_nonexistent_names(Zone, [value])
         if nonexistent_names:
             error_msg = "No such zone: '%s'." % value
             set_form_error(self, self.get_field_name("zone"), error_msg)
             return None
         return value
     return None
 def clean_pool(self):
     value = self.cleaned_data[self.get_field_name("pool")]
     if value:
         nonexistent_names = detect_nonexistent_names(ResourcePool, [value])
         if nonexistent_names:
             error_msg = "No such pool: '%s'." % value
             set_form_error(self, self.get_field_name("pool"), error_msg)
             return None
         return value
     return None
 def clean_not_in_zone(self):
     value = self.cleaned_data[self.get_field_name('not_in_zone')]
     if not value:
         return None
     nonexistent_names = detect_nonexistent_names(Zone, value)
     if nonexistent_names:
         error_msg = "No such zone(s): %s." % ', '.join(nonexistent_names)
         set_form_error(self, self.get_field_name('not_in_zone'), error_msg)
         return None
     return value
Exemple #19
0
 def clean_not_in_zone(self):
     value = self.cleaned_data[self.get_field_name('not_in_zone')]
     if value is None or len(value) == 0:
         return None
     nonexistent_names = detect_nonexistent_zone_names(value)
     if len(nonexistent_names) > 0:
         error_msg = "No such zone(s): %s." % ', '.join(nonexistent_names)
         set_form_error(self, self.get_field_name('not_in_zone'), error_msg)
         return None
     return value
Exemple #20
0
 def clean(self):
     """Validate that the form is valid and that the destinations can accept
     the storage and/or interfaces configuration from the source."""
     cleaned_data = super().clean()
     source = self.cleaned_data.get("source")
     if not source:
         # Django should be placing this automatically, but it does not
         # occur. So we force the setting of this error here.
         set_form_error(self, "source", "This field is required.")
     destinations = self.cleaned_data.get("destinations")
     destination_field = self.fields["destinations"]
     item_invalid = destination_field.error_messages["item_invalid"]
     storage = self.cleaned_data.get("storage", False)
     interfaces = self.cleaned_data.get("interfaces", False)
     if source and destinations:
         for index, dest in enumerate(destinations):
             if source == dest:
                 error = prefix_validation_error(
                     ValidationError(
                         "Source machine cannot be a destination machine."
                     ),
                     prefix=item_invalid,
                     code="item_invalid",
                     params={"nth": index},
                 )
                 set_form_error(self, "destinations", error)
             else:
                 if storage:
                     try:
                         dest._get_storage_mapping_between_nodes(source)
                     except ValidationError as exc:
                         error = prefix_validation_error(
                             exc,
                             prefix=item_invalid,
                             code="item_invalid",
                             params={"nth": index},
                         )
                         set_form_error(self, "destinations", error)
                 if interfaces:
                     try:
                         dest._get_interface_mapping_between_nodes(source)
                     except ValidationError as exc:
                         error = prefix_validation_error(
                             exc,
                             prefix=item_invalid,
                             code="item_invalid",
                             params={"nth": index},
                         )
                         set_form_error(self, "destinations", error)
     if not storage and not interfaces:
         set_form_error(
             self, "__all__", "Either storage or interfaces must be true."
         )
     return cleaned_data
Exemple #21
0
    def _clean_boot_disk(self):
        """https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.esxi.install.doc/GUID-DEB8086A-306B-4239-BF76-E354679202FC.html

        ESXi 7.0 requires a boot disk of at least 32 GB of persistent
        storage such as HDD, SSD, or NVMe. Use USB, SD and non-USB
        flash media devices only for ESXi boot bank partitions. A boot
        device must not be shared between ESXi hosts.
        """
        if self.boot_disk.size < (32 * 1024**3):
            set_form_error(self, "boot_size",
                           "Boot disk must be at least 32Gb.")
Exemple #22
0
    def _validate_and_clean_url(self, param_name, param):
        """Validate and clean URL input."""
        if param.get("allow_list", False):
            value = param["value"].split(",")
        else:
            value = [param["value"]]

        for url in value:
            # Django's validator requires a protocol but we accept just a
            # hostname or IP address.
            try:
                ip = ip_address(url)
            except ValueError:
                if "://" not in url:
                    url = "http://%s" % url
            else:
                if ip.version == 4:
                    url = "http://%s" % url
                else:
                    url = "http://[%s]" % url
            # Allow all schemes supported by curl(used with the network
            # validation test) + icmp.
            try:
                validate_url(
                    url,
                    schemes=[
                        "icmp",
                        "file",
                        "ftp",
                        "ftps",
                        "gopher",
                        "http",
                        "https",
                        "imap",
                        "imaps",
                        "ldap",
                        "ldaps",
                        "pop3",
                        "pop3s",
                        "rtmp",
                        "rtsp",
                        "scp",
                        "sftp",
                        "smb",
                        "smbs",
                        "smtp",
                        "smtps",
                        "telnet",
                        "tftp",
                    ],
                )
            except ValidationError:
                set_form_error(self, param_name, "Invalid URL")
 def _validate_and_clean_interface_id(self, param_name, param,
                                      result_params, ret):
     """Validate and clean interface input when id."""
     interfaces = self._get_interfaces(id=int(param["value"]))
     if len(interfaces) != 1:
         set_form_error(self, param_name, "Interface id does not exist")
     else:
         clean_param = copy.deepcopy(result_params)
         clean_param[param_name] = copy.deepcopy(param)
         clean_param[param_name]["value"] = self._interface_to_dict(
             interfaces[0])
         ret.append(clean_param)
Exemple #24
0
 def clean(self):
     cleaned_data = super(PodForm, self).clean()
     if len(self.drivers) == 0:
         set_form_error(
             self, 'type',
             "No rack controllers are connected, unable to validate.")
     elif (not self.is_new
           and self.instance.power_type != self.cleaned_data.get('type')):
         set_form_error(
             self, 'type',
             "Cannot change the type of a pod. Delete and re-create the "
             "pod with a different type.")
     return cleaned_data
Exemple #25
0
 def _clean_mode_dhcp(self):
     # Can only have one DHCP link on an interface.
     dhcp_address = get_one(
         self.instance.ip_addresses.filter(alloc_type=IPADDRESS_TYPE.DHCP))
     if dhcp_address is not None:
         if dhcp_address.subnet is not None:
             set_form_error(
                 self, "mode",
                 "Interface is already set to DHCP from '%s'." %
                 (dhcp_address.subnet))
         else:
             set_form_error(self, "mode",
                            "Interface is already set to DHCP.")
Exemple #26
0
 def _validate_and_clean_storage_id(self, param_name, param, result_params,
                                    ret):
     """Validate and clean storage input when id."""
     try:
         bd = self._node.physicalblockdevice_set.get(id=int(param['value']))
     except ObjectDoesNotExist:
         set_form_error(self, param_name,
                        'Physical block id does not exist')
     else:
         clean_param = copy.deepcopy(result_params)
         clean_param[param_name] = copy.deepcopy(param)
         clean_param[param_name]['value'] = self._blockdevice_to_dict(bd)
         ret.append(clean_param)
Exemple #27
0
    def clean(self):
        # Circular imports.
        from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE

        cleaned_data = super(BcacheStorageLayoutBase, self).clean()
        cache_device = self.get_cache_device()
        cache_size = self.get_cache_size()
        cache_no_part = self.get_cache_no_part()
        if cache_size is not None and cache_no_part:
            error_msg = (
                "Cannot use cache_size and cache_no_part at the same time."
            )
            set_form_error(self, "cache_size", error_msg)
            set_form_error(self, "cache_no_part", error_msg)
        elif cache_device is not None and cache_size is not None:
            if is_percentage(cache_size):
                cache_size = calculate_size_from_percentage(
                    cache_device.size, cache_size
                )
            if cache_size < MIN_BLOCK_DEVICE_SIZE:
                set_form_error(
                    self,
                    "cache_size",
                    "Size is too small. Minimum size is %s."
                    % MIN_BLOCK_DEVICE_SIZE,
                )
            if cache_size > cache_device.size:
                set_form_error(
                    self,
                    "cache_size",
                    "Size is too large. Maximum size is %s."
                    % (cache_device.size),
                )
            cleaned_data["cache_size"] = cache_size
        return cleaned_data
Exemple #28
0
 def _validate_and_clean_interface_id(
         self, param_name, param, result_params, ret):
     """Validate and clean interface input when id."""
     try:
         interface = self._get_interfaces().get(id=int(param['value']))
     except ObjectDoesNotExist:
         set_form_error(
             self, param_name, 'Interface id does not exist')
     else:
         clean_param = copy.deepcopy(result_params)
         clean_param[param_name] = copy.deepcopy(param)
         clean_param[param_name]['value'] = self._interface_to_dict(
             interface)
         ret.append(clean_param)
Exemple #29
0
 def _clean_mode_link_up(self):
     # Cannot set LINK_UP unless no other IP address are attached to
     # this interface. But exclude STICKY addresses where the IP address is
     # null, because the user could be trying to change the subnet for a
     # LINK_UP address. And exclude DISCOVERED because what MAAS discovered
     # doesn't matter with regard to the user's intention.
     exclude_types = (IPADDRESS_TYPE.STICKY, IPADDRESS_TYPE.DISCOVERED)
     has_active_links = self.instance.ip_addresses.exclude(
         alloc_type__in=exclude_types, ip__isnull=True).count() > 0
     if has_active_links and self.force is not True:
         set_form_error(
             self, "mode",
             "Cannot configure interface to link up (with no IP address) "
             "while other links are already configured. Specify force=True "
             "to override this behavior and delete all links.")
Exemple #30
0
    def _validate_and_clean_interface(
        self, param_name, param, result_params, ret
    ):
        """Validate and clean interface input."""
        value = param["value"]
        for i in value.split(","):
            if ":" in i:
                # Allow users to specify an Interface using the vendor and
                # product.
                vendor, product = i.split(":")
                interfaces = self._get_interfaces(
                    vendor=vendor, product=product
                )
                if len(interfaces) != 0:
                    for interface in interfaces:
                        clean_param = copy.deepcopy(result_params)
                        clean_param[param_name] = copy.deepcopy(param)
                        clean_param[param_name][
                            "value"
                        ] = self._interface_to_dict(interface)
                        ret.append(clean_param)
                    continue

            if is_mac(i):
                interfaces = self._get_interfaces(mac_address=i)
            else:
                interfaces = self._get_interfaces(
                    Q(name=i)
                    | Q(vendor=i)
                    | Q(product=i)
                    | Q(tags__overlap=[i])
                )
            if len(interfaces) == 0:
                set_form_error(
                    self,
                    param_name,
                    "Unknown interface for %s(%s)"
                    % (self._node.fqdn, self._node.system_id),
                )
                continue
            for interface in interfaces:
                clean_param = copy.deepcopy(result_params)
                clean_param[param_name] = copy.deepcopy(param)
                clean_param[param_name]["value"] = self._interface_to_dict(
                    interface
                )
                ret.append(clean_param)