docs: def _present(name,
              block_icmp=None,
              prune_block_icmp=False,
              default=None,
              masquerade=False,
              ports=None,
              prune_ports=False,
              port_fwd=None,
              prune_port_fwd=False,
              services=None,
              # TODO: prune_services=False in future release
              # prune_services=False,
              prune_services=None,
              interfaces=None,
              prune_interfaces=False,
              sources=None,
              prune_sources=False,
              rich_rules=None,
              prune_rich_rules=False):
      '''
      Ensure a zone has specific attributes.
      '''
      ret = {'name': name,
             'result': False,
             'changes': {},
             'comment': ''}
  
      try:
          zones = __salt__['firewalld.get_zones'](permanent=True)
      except CommandExecutionError as err:
          ret['comment'] = 'Error: {0}'.format(err)
          return ret
  
      if name not in zones:
          if not __opts__['test']:
              try:
                  __salt__['firewalld.new_zone'](name)
              except CommandExecutionError as err:
                  ret['comment'] = 'Error: {0}'.format(err)
                  return ret
  
          ret['changes'].update({name:
                                  {'old': zones,
                                  'new': name}})
  
      if block_icmp or prune_block_icmp:
          block_icmp = block_icmp or []
          new_icmp_types = []
          old_icmp_types = []
  
          try:
              _current_icmp_blocks = __salt__['firewalld.list_icmp_block'](name,
                  permanent=True)
          except CommandExecutionError as err:
              ret['comment'] = 'Error: {0}'.format(err)
              return ret
  
          if block_icmp:
              try:
                  _valid_icmp_types = __salt__['firewalld.get_icmp_types'](
                      permanent=True)
              except CommandExecutionError as err:
                  ret['comment'] = 'Error: {0}'.format(err)
                  return ret
  
              # log errors for invalid ICMP types in block_icmp input
              for icmp_type in set(block_icmp) - set(_valid_icmp_types):
                  log.error('%s is an invalid ICMP type', icmp_type)
                  block_icmp.remove(icmp_type)
  
              new_icmp_types = set(block_icmp) - set(_current_icmp_blocks)
              for icmp_type in new_icmp_types:
                  if not __opts__['test']:
                      try:
                          __salt__['firewalld.block_icmp'](name, icmp_type,
                                                           permanent=True)
                      except CommandExecutionError as err:
                          ret['comment'] = 'Error: {0}'.format(err)
                          return ret
  
          if prune_block_icmp:
              old_icmp_types = set(_current_icmp_blocks) - set(block_icmp)
              for icmp_type in old_icmp_types:
                  # no need to check against _valid_icmp_types here, because all
                  # elements in old_icmp_types are guaranteed to be in
                  # _current_icmp_blocks, whose elements are inherently valid
                  if not __opts__['test']:
                      try:
                          __salt__['firewalld.allow_icmp'](name, icmp_type,
                                                           permanent=True)
                      except CommandExecutionError as err:
                          ret['comment'] = 'Error: {0}'.format(err)
                          return ret
  
          if new_icmp_types or old_icmp_types:
              # If we're not pruning, include current items in new output so it's clear
              # that they're still present
              if not prune_block_icmp:
                  block_icmp = list(new_icmp_types | set(_current_icmp_blocks))
              ret['changes'].update({'icmp_types':
                                      {'old': _current_icmp_blocks,
                                      'new': block_icmp}})
  
      # that's the only parameter that can't be permanent or runtime, it's
      # directly both
      if default:
          try:
              default_zone = __salt__['firewalld.default_zone']()
          except CommandExecutionError as err:
              ret['comment'] = 'Error: {0}'.format(err)
              return ret
          if name != default_zone:
              if not __opts__['test']:
                  try:
                      __salt__['firewalld.set_default_zone'](name)
                  except CommandExecutionError as err:
                      ret['comment'] = 'Error: {0}'.format(err)
                      return ret
              ret['changes'].update({'default':
                                    {'old': default_zone,
                                     'new': name}})
  
      try:
          masquerade_ret = __salt__['firewalld.get_masquerade'](name,
              permanent=True)
      except CommandExecutionError as err:
          ret['comment'] = 'Error: {0}'.format(err)
          return ret
  
      if masquerade and not masquerade_ret:
          if not __opts__['test']:
              try:
                  __salt__['firewalld.add_masquerade'](name, permanent=True)
              except CommandExecutionError as err:
                  ret['comment'] = 'Error: {0}'.format(err)
                  return ret
          ret['changes'].update({'masquerade':
                                {'old': '',
                                 'new': 'Masquerading successfully set.'}})
      elif not masquerade and masquerade_ret:
          if not __opts__['test']:
              try:
                  __salt__['firewalld.remove_masquerade'](name,
                                                          permanent=True)
              except CommandExecutionError as err:
                  ret['comment'] = 'Error: {0}'.format(err)
                  return ret
          ret['changes'].update({'masquerade':
                                {'old': '',
                                 'new': 'Masquerading successfully '
                                 'disabled.'}})
  
      if ports or prune_ports:
          ports = ports or []
          try:
              _current_ports = __salt__['firewalld.list_ports'](name, permanent=True)
          except CommandExecutionError as err:
              ret['comment'] = 'Error: {0}'.format(err)
              return ret
  
          new_ports = set(ports) - set(_current_ports)
          old_ports = []
  
          for port in new_ports:
              if not __opts__['test']:
                  try:
                      # TODO: force_masquerade to be removed in future release
                      __salt__['firewalld.add_port'](name, port, permanent=True, force_masquerade=False)
                  except CommandExecutionError as err:
                      ret['comment'] = 'Error: {0}'.format(err)
                      return ret
  
          if prune_ports:
              old_ports = set(_current_ports) - set(ports)
              for port in old_ports:
                  if not __opts__['test']:
                      try:
                          __salt__['firewalld.remove_port'](name, port, permanent=True)
                      except CommandExecutionError as err:
                          ret['comment'] = 'Error: {0}'.format(err)
                          return ret
  
          if new_ports or old_ports:
              # If we're not pruning, include current items in new output so it's clear
              # that they're still present
              if not prune_ports:
                  ports = list(new_ports | set(_current_ports))
              ret['changes'].update({'ports':
                                      {'old': _current_ports,
                                      'new': ports}})
  
      if port_fwd or prune_port_fwd:
          port_fwd = port_fwd or []
          try:
              _current_port_fwd = __salt__['firewalld.list_port_fwd'](name,
                                                                      permanent=True)
          except CommandExecutionError as err:
              ret['comment'] = 'Error: {0}'.format(err)
              return ret
  
          port_fwd = [_parse_forward(fwd) for fwd in port_fwd]
          _current_port_fwd = [
              ForwardingMapping(
                  srcport=fwd['Source port'],
                  destport=fwd['Destination port'],
                  protocol=fwd['Protocol'],
                  destaddr=fwd['Destination address']
              ) for fwd in _current_port_fwd]
  
          new_port_fwd = set(port_fwd) - set(_current_port_fwd)
          old_port_fwd = []
  
          for fwd in new_port_fwd:
              if not __opts__['test']:
                  try:
                      # TODO: force_masquerade to be removed in future release
                      __salt__['firewalld.add_port_fwd'](name, fwd.srcport,
                          fwd.destport, fwd.protocol, fwd.destaddr, permanent=True,
                          force_masquerade=False)
                  except CommandExecutionError as err:
                      ret['comment'] = 'Error: {0}'.format(err)
                      return ret
  
          if prune_port_fwd:
              old_port_fwd = set(_current_port_fwd) - set(port_fwd)
              for fwd in old_port_fwd:
                  if not __opts__['test']:
                      try:
                          __salt__['firewalld.remove_port_fwd'](name, fwd.srcport,
                              fwd.destport, fwd.protocol, fwd.destaddr, permanent=True)
                      except CommandExecutionError as err:
                          ret['comment'] = 'Error: {0}'.format(err)
                          return ret
  
          if new_port_fwd or old_port_fwd:
              # If we're not pruning, include current items in new output so it's clear
              # that they're still present
              if not prune_port_fwd:
                  port_fwd = list(new_port_fwd | set(_current_port_fwd))
              ret['changes'].update({'port_fwd':
                                      {'old': [fwd.todict() for fwd in
                                               _current_port_fwd],
                                      'new': [fwd.todict() for fwd in port_fwd]}})
  
      if services or prune_services:
          services = services or []
          try:
              _current_services = __salt__['firewalld.list_services'](name,
                  permanent=True)
          except CommandExecutionError as err:
              ret['comment'] = 'Error: {0}'.format(err)
              return ret
  
          new_services = set(services) - set(_current_services)
          old_services = []
  
          for new_service in new_services:
              if not __opts__['test']:
                  try:
                      __salt__['firewalld.add_service'](new_service, name,
                                                        permanent=True)
                  except CommandExecutionError as err:
                      ret['comment'] = 'Error: {0}'.format(err)
                      return ret
  
          if prune_services:
              old_services = set(_current_services) - set(services)
              for old_service in old_services:
                  if not __opts__['test']:
                      try:
                          __salt__['firewalld.remove_service'](old_service, name,
                                                               permanent=True)
                      except CommandExecutionError as err:
                          ret['comment'] = 'Error: {0}'.format(err)
                          return ret
  
          if new_services or old_services:
              # If we're not pruning, include current items in new output so it's clear
              # that they're still present
              if not prune_services:
                  services = list(new_services | set(_current_services))
              ret['changes'].update({'services':
                                      {'old': _current_services,
                                      'new': services}})
  
      if interfaces or prune_interfaces:
          interfaces = interfaces or []
          try:
              _current_interfaces = __salt__['firewalld.get_interfaces'](name,
                  permanent=True)
          except CommandExecutionError as err:
              ret['comment'] = 'Error: {0}'.format(err)
              return ret
  
          new_interfaces = set(interfaces) - set(_current_interfaces)
          old_interfaces = []
  
          for interface in new_interfaces:
              if not __opts__['test']:
                  try:
                      __salt__['firewalld.add_interface'](name, interface,
                                                          permanent=True)
                  except CommandExecutionError as err:
                      ret['comment'] = 'Error: {0}'.format(err)
                      return ret
  
          if prune_interfaces:
              old_interfaces = set(_current_interfaces) - set(interfaces)
              for interface in old_interfaces:
                  if not __opts__['test']:
                      try:
                          __salt__['firewalld.remove_interface'](name, interface,
                                                                 permanent=True)
                      except CommandExecutionError as err:
                          ret['comment'] = 'Error: {0}'.format(err)
                          return ret
  
          if new_interfaces or old_interfaces:
              # If we're not pruning, include current items in new output so it's clear
              # that they're still present
              if not prune_interfaces:
                  interfaces = list(new_interfaces | set(_current_interfaces))
              ret['changes'].update({'interfaces':
                                      {'old': _current_interfaces,
                                      'new': interfaces}})
  
      if sources or prune_sources:
          sources = sources or []
          try:
              _current_sources = __salt__['firewalld.get_sources'](name,
                                                                   permanent=True)
          except CommandExecutionError as err:
              ret['comment'] = 'Error: {0}'.format(err)
              return ret
  
          new_sources = set(sources) - set(_current_sources)
          old_sources = []
  
          for source in new_sources:
              if not __opts__['test']:
                  try:
                      __salt__['firewalld.add_source'](name, source, permanent=True)
                  except CommandExecutionError as err:
                      ret['comment'] = 'Error: {0}'.format(err)
                      return ret
  
          if prune_sources:
              old_sources = set(_current_sources) - set(sources)
              for source in old_sources:
                  if not __opts__['test']:
                      try:
                          __salt__['firewalld.remove_source'](name, source,
                                                              permanent=True)
                      except CommandExecutionError as err:
                          ret['comment'] = 'Error: {0}'.format(err)
                          return ret
  
          if new_sources or old_sources:
              # If we're not pruning, include current items in new output so it's clear
              # that they're still present
              if not prune_sources:
                  sources = list(new_sources | set(_current_sources))
              ret['changes'].update({'sources':
                                      {'old': _current_sources,
                                      'new': sources}})
  
      if rich_rules or prune_rich_rules:
          rich_rules = rich_rules or []
          try:
              _current_rich_rules = __salt__['firewalld.get_rich_rules'](name,
                  permanent=True)
          except CommandExecutionError as err:
              ret['comment'] = 'Error: {0}'.format(err)
              return ret
  
          new_rich_rules = set(rich_rules) - set(_current_rich_rules)
          old_rich_rules = []
  
          for rich_rule in new_rich_rules:
              if not __opts__['test']:
                  try:
                      __salt__['firewalld.add_rich_rule'](name, rich_rule,
                                                          permanent=True)
                  except CommandExecutionError as err:
                      ret['comment'] = 'Error: {0}'.format(err)
                      return ret
  
          if prune_rich_rules:
              old_rich_rules = set(_current_rich_rules) - set(rich_rules)
              for rich_rule in old_rich_rules:
                  if not __opts__['test']:
                      try:
                          __salt__['firewalld.remove_rich_rule'](name, rich_rule,
                                                                 permanent=True)
                      except CommandExecutionError as err:
                          ret['comment'] = 'Error: {0}'.format(err)
                          return ret
  
          if new_rich_rules or old_rich_rules:
              # If we're not pruning, include current items in new output so it's clear
              # that they're still present
              if not prune_rich_rules:
                  rich_rules = list(new_rich_rules | set(_current_rich_rules))
              ret['changes'].update({'rich_rules':
                                    {'old': _current_rich_rules,
                                     'new': rich_rules}})
  
      # No changes
      if ret['changes'] == {}:
          ret['result'] = True
          ret['comment'] = '\'{0}\' is already in the desired state.'.format(name)
          return ret
  
      # test=True and changes predicted
      if __opts__['test']:
          ret['result'] = None
          # build comment string
          nested.__opts__ = __opts__
          comment = []
          comment.append('Configuration for \'{0}\' will change:'.format(name))
          comment.append(nested.output(ret['changes']).rstrip())
          ret['comment'] = '\n'.join(comment)
          ret['changes'] = {}
          return ret
  
      # Changes were made successfully
      ret['result'] = True
      ret['comment'] = '\'{0}\' was configured.'.format(name)
      return ret