Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/haproxy frontend #2

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions .idea/ansible-pfsense-core.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/runConfigurations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

153 changes: 153 additions & 0 deletions plugins/module_utils/haproxy_frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, Frederic Bor <[email protected]>
# 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'])
136 changes: 136 additions & 0 deletions plugins/modules/pfsense_haproxy_frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2019, Frederic Bor <[email protected]>
# 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()