code: def search(self, **kwargs):
          
          # Because keyword_search takes dicts, and we have objects with complex
          # properties, we convert them to a list of dicts.
          # First, store the things we're going to convert.
          prop_keys = ('service', 'interface', 'module_name', 'module_args')
          search = []
          # Can't find a neat way to do this as a comprehension, so it's back
          # to loops.
          for obj in self.data:
              row = {'entry_obj': obj}
              for key in prop_keys:
                  row[key] = getattr(obj, key)
              # For hysterical reasons, module_args can contain None if not
              # defined, but that's non-iterable: replace with ''
              if row['module_args'] is None:
                  row['module_args'] = ''
              # Convert control_flags down to key/value dictionary
              flags = {}
              for cf in obj.control_flags:
                  flags[cf.flag] = cf.value
              row['control_flags'] = flags
              search.append(row)
          # Now get the result, and just return the entry objects
          found = []
          for r in keyword_search(search, **kwargs):
              found.append(r['entry_obj'])
          return found
docs: Search the pam.d configuration file by keyword.  This is provided by
          the :func:`insights.parsers.keyword_search` function - see its
          documentation for more information.
  
          Searching on the list of PAM configuration entries is exactly like
          they were dictionaries instead of objects with properties.  In
          addition, the 'control_flags' property becomes a dictionary of
          keywords and values, so that 'control_flags__contains' allows
          searching for a particular control flag.
  
          Returns:
              (list): A list of PamConfEntry objects that match the given
              search criteria.