diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..5aea05a1
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,6 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+.idea
+*.retry
+*.iml
diff --git a/.idea/ansible-pfsense-core.iml b/.idea/ansible-pfsense-core.iml
new file mode 100644
index 00000000..d6ebd480
--- /dev/null
+++ b/.idea/ansible-pfsense-core.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..639900d1
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..a850e5f0
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 00000000..797acea5
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/module_utils/haproxy_frontend.py b/plugins/module_utils/haproxy_frontend.py
new file mode 100644
index 00000000..b8fd43b9
--- /dev/null
+++ b/plugins/module_utils/haproxy_frontend.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Frederic Bor
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+import re
+from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
+
+HAPROXY_FRONTEND_ARGUMENT_SPEC = dict(
+ state=dict(default='present', choices=['present', 'absent']),
+ mode=dict(default='active', choices=['active', 'disabled']),
+ name=dict(required=True, type='str'),
+ frontend_type=dict(default='http', choices=['http', 'ssl', 'tcp']),
+ httpclose=dict(default='http-keep-alive', choices=['http-keep-alive', 'http-tunnel', 'httpclose', 'http-server-close', 'forceclose']),
+ ssloffloadcert=dict(required=False, type='str'),
+ ssloffloadacl_an=dict(required=False, type='bool'),
+ ha_acls=dict(required=False, type='str'),
+ ha_certificates=dict(required=False, type='str'),
+ clientcert_ca=dict(required=False, type='str'),
+ clientcert_crl=dict(required=False, type='str'),
+ a_actionitems=dict(required=False, type='str'),
+ a_errorfiles=dict(required=False, type='str'),
+)
+
+
+class PFSenseHaproxyFrontendModule(PFSenseModuleBase):
+ """ module managing pfsense haproxy frontends """
+
+ @staticmethod
+ def get_argument_spec():
+ """ return argument spec """
+ return HAPROXY_FRONTEND_ARGUMENT_SPEC
+
+ ##############################
+ # init
+ #
+ def __init__(self, module, pfsense=None):
+ super(PFSenseHaproxyFrontendModule, self).__init__(module, pfsense)
+ self.name = "pfsense_haproxy_frontend"
+ self.obj = dict()
+
+ pkgs_elt = self.pfsense.get_element('installedpackages')
+ self.haproxy = pkgs_elt.find('haproxy') if pkgs_elt is not None else None
+ self.root_elt = self.haproxy.find('ha_pools') if self.haproxy is not None else None
+ if self.root_elt is None:
+ self.module.fail_json(msg='Unable to find frontends XML configuration entry. Are you sure haproxy is installed ?')
+
+ ##############################
+ # params processing
+ #
+ def _params_to_obj(self):
+ """ return a frontend dict from module params """
+ obj = dict()
+ obj['name'] = self.params['name']
+ if params['state'] == 'present':
+ obj['status'] = self.params['mode']
+
+ self._get_ansible_param(obj, 'frontend_type', fname='type', force=True)
+ self._get_ansible_param(obj, 'httpclose', force=True)
+ self._get_ansible_param(obj, 'ssloffloadcert', force=True)
+ self._get_ansible_param(obj, 'ha_acls', force=True)
+ self._get_ansible_param(obj, 'ha_certificates', force=True)
+ self._get_ansible_param(obj, 'clientcert_ca', force=True)
+ self._get_ansible_param(obj, 'clientcert_crl', force=True)
+ self._get_ansible_param_bool(obj, 'ssloffloadacl_an', force=True)
+ self._get_ansible_param(obj, 'a_actionitems', force=True)
+ self._get_ansible_param(obj, 'a_errorfiles', force=True)
+
+ return obj
+
+ def _validate_params(self):
+ """ do some extra checks on input parameters """
+ # check name
+ if re.search(r'[^a-zA-Z0-9\.\-_]', self.params['name']) is not None:
+ self.module.fail_json(msg="The field 'name' contains invalid characters.")
+
+ ##############################
+ # XML processing
+ #
+ def _create_target(self):
+ """ create the XML target_elt """
+ server_elt = self.pfsense.new_element('item')
+ self.obj['id'] = self._get_next_id()
+ return server_elt
+
+ def _find_target(self):
+ """ find the XML target_elt """
+ for item_elt in self.root_elt:
+ if item_elt.tag != 'item':
+ continue
+ name_elt = item_elt.find('name')
+ if name_elt is not None and name_elt.text == self.obj['name']:
+ return item_elt
+ return None
+
+ def _get_next_id(self):
+ """ get next free haproxy id """
+ max_id = 99
+ id_elts = self.haproxy.findall('.//id')
+ for id_elt in id_elts:
+ if id_elt.text is None:
+ continue
+ ha_id = int(id_elt.text)
+ if ha_id > max_id:
+ max_id = ha_id
+ return str(max_id + 1)
+
+ ##############################
+ # run
+ #
+ def _update(self):
+ """ make the target pfsense reload haproxy """
+ return self.pfsense.phpshell('''require_once("haproxy/haproxy.inc");
+$result = haproxy_check_and_run($savemsg, true); if ($result) unlink_if_exists($d_haproxyconfdirty_path);''')
+
+ ##############################
+ # Logging
+ #
+ def _log_fields(self, before=None):
+ """ generate pseudo-CLI command fields parameters to create an obj """
+ values = ''
+ if before is None:
+ values += self.format_cli_field(self.params, 'frontend_type')
+ values += self.format_cli_field(self.params, 'httpclose')
+ values += self.format_cli_field(self.params, 'ssloffloadcert')
+ values += self.format_cli_field(self.params, 'ssloffloadacl_an', fvalue=self.fvalue_bool)
+ values += self.format_cli_field(self.params, 'ha_acls')
+ values += self.format_cli_field(self.params, 'ha_certificates')
+ values += self.format_cli_field(self.params, 'clientcert_ca')
+ values += self.format_cli_field(self.params, 'clientcert_crl')
+ values += self.format_cli_field(self.params, 'a_actionitems')
+ values += self.format_cli_field(self.params, 'a_errorfiles')
+ else:
+ for param in ['type', 'ssloffloadacl_an']:
+ if param in before and before[param] == '':
+ before[param] = None
+ values += self.format_updated_cli_field(self.obj, before, 'frontend_type', add_comma=(values))
+ values += self.format_updated_cli_field(self.obj, before, 'httpclose', add_comma=(values))
+ values += self.format_updated_cli_field(self.obj, before, 'ssloffloadcert', add_comma=(values))
+ values += self.format_updated_cli_field(self.obj, before, 'ssloffloadacl_an', add_comma=(values), fvalue=self.fvalue_bool)
+ values += self.format_updated_cli_field(self.obj, before, 'ha_acls', add_comma=(values))
+ values += self.format_updated_cli_field(self.obj, before, 'ha_certificates', add_comma=(values))
+ values += self.format_updated_cli_field(self.obj, before, 'clientcert_ca', add_comma=(values))
+ values += self.format_updated_cli_field(self.obj, before, 'clientcert_crl', add_comma=(values))
+ values += self.format_updated_cli_field(self.obj, before, 'a_actionitems', add_comma=(values))
+ values += self.format_updated_cli_field(self.obj, before, 'a_errorfiles', add_comma=(values))
+ return values
+
+ def _get_obj_name(self):
+ """ return obj's name """
+ return "'{0}'".format(self.obj['name'])
diff --git a/plugins/modules/pfsense_haproxy_frontend.py b/plugins/modules/pfsense_haproxy_frontend.py
new file mode 100644
index 00000000..755cf5ac
--- /dev/null
+++ b/plugins/modules/pfsense_haproxy_frontend.py
@@ -0,0 +1,136 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Frederic Bor
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'}
+
+DOCUMENTATION = """
+---
+module: pfsense_haproxy_frontend
+version_added: 0.1.0
+author: Frederic Bor (@f-bor)
+short_description: Manage pfSense haproxy frontends
+description:
+ - Manage pfSense haproxy frontends
+notes:
+options:
+ name:
+ description: The frontend name.
+ required: true
+ type: str
+ balance:
+ description: The load balancing option.
+ required: false
+ type: str
+ choices: ['none', 'roundrobin', 'static-rr', 'leastconn', 'source', 'uri']
+ default: 'none'
+ balance_urilen:
+ description: Indicates that the algorithm should only consider that many characters at the beginning of the URI to compute the hash.
+ required: false
+ type: int
+ balance_uridepth:
+ description: Indicates the maximum directory depth to be used to compute the hash. One level is counted for each slash in the request.
+ required: false
+ type: int
+ balance_uriwhole:
+ description: Allow using whole URI including url parameters behind a question mark.
+ required: false
+ type: bool
+ connection_timeout:
+ description: The time (in milliseconds) we give up if the connection does not complete within (default 30000).
+ required: false
+ type: int
+ server_timeout:
+ description: The time (in milliseconds) we accept to wait for data from the server, or for the server to accept data (default 30000).
+ required: false
+ type: int
+ retries:
+ description: After a connection failure to a server, it is possible to retry, potentially on another server.
+ required: false
+ type: int
+ check_type:
+ description: Health check method.
+ type: str
+ choices: ['none', 'Basic', 'HTTP', 'Agent', 'LDAP', 'MySQL', 'PostgreSQL', 'Redis', 'SMTP', 'ESMTP', 'SSL']
+ default: 'none'
+ check_frequency:
+ description: The check interval (in milliseconds). For HTTP/HTTPS defaults to 1000 if left blank. For TCP no check will be performed if left empty.
+ required: false
+ type: int
+ log_checks:
+ description: When this option is enabled, any change of the health check status or to the server's health will be logged.
+ required: false
+ type: bool
+ httpcheck_method:
+ description: HTTP check method.
+ required: false
+ type: str
+ choices: ['OPTIONS', 'HEAD', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE']
+ monitor_uri:
+ description: Url used by http check requests.
+ required: false
+ type: str
+ monitor_httpversion:
+ description: Defaults to "HTTP/1.0" if left blank.
+ required: false
+ type: str
+ monitor_username:
+ description: Username used in checks (MySQL and PostgreSQL)
+ required: false
+ type: str
+ monitor_domain:
+ description: Domain used in checks (SMTP and ESMTP)
+ required: false
+ type: str
+ state:
+ description: State in which to leave the frontend
+ choices: [ "present", "absent" ]
+ default: present
+ type: str
+"""
+
+EXAMPLES = """
+- name: Add frontend
+ pfsense_haproxy_frontend:
+ name: exchange
+ balance: leastconn
+ httpcheck_method: HTTP
+ state: present
+
+- name: Remove frontend
+ pfsense_haproxy_frontend:
+ name: exchange
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: the set of commands that would be pushed to the remote device (if pfSense had a CLI)
+ returned: always
+ type: list
+ sample: ["create haproxy_frontend 'exchange', balance='leastconn', httpcheck_method='HTTP'", "delete haproxy_frontend 'exchange'"]
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.pfsensible.core.plugins.module_utils.haproxy_frontend import PFSenseHaproxyFrontendModule, HAPROXY_FRONTEND_ARGUMENT_SPEC
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=HAPROXY_FRONTEND_ARGUMENT_SPEC,
+ supports_check_mode=True)
+
+ pfmodule = PFSenseHaproxyFrontendModule(module)
+ pfmodule.run(module.params)
+ pfmodule.commit_changes()
+
+
+if __name__ == '__main__':
+ main()