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

Major refactor of PFSenseModuleBase supporting generate_module pfsensible module generator #112

Open
wants to merge 62 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
c6933dd
[module_base] Refactor
opoplawski Jan 11, 2024
5cf4c74
[pfsense_cert] Use expanded PFSenseModuleBase
opoplawski Jan 11, 2024
4a67cda
[pfsense_ca] Use expanded PFSenseModuleBase
opoplawski Jan 11, 2024
d7b929c
[module_base] Allow setting root_is_exclusive; self.obj default to {}…
opoplawski Jan 11, 2024
fe33ad8
[pfsense_alias] Use expanded PFSenseModuleBase
opoplawski Jan 11, 2024
dbc449d
[module_base] Add key, root_is_exclusive defaults to True; root_is_ex…
opoplawski Jan 11, 2024
d7ca82f
[pfsense_interface_group] Use expanded PFSenseModuleBase
opoplawski Jan 11, 2024
f9185d9
[pfsense_default_gateway] Use PFSenseModuleBase root
opoplawski Jan 12, 2024
bb235e1
[pfsense_gateway] Use expanded PFSenseModuleBase
opoplawski Jan 12, 2024
22e40a8
[pfsense_interface_group] Drop _get_obj_name()
opoplawski Jan 12, 2024
af6a85b
[module_base] set name; allow update_php code
opoplawski Jan 12, 2024
2397e58
[pfsense_alias] auto set name; drop _find_target, _update
opoplawski Jan 12, 2024
e9370d4
[module_base] Implement basic _params_to_obj()
opoplawski Jan 12, 2024
fd58189
[checks] Add name to check_name output for clarity with aggregate
opoplawski Jan 12, 2024
2d1dd71
[pfsense_alias] Refactor _params_to_obj() and add url
opoplawski Jan 12, 2024
db0f352
[module_base] Basic _log_fields() implementation
opoplawski Jan 13, 2024
254360b
[pfsense_alias] Use PFSenseModuleBase _get_obj_name and _log_fields
opoplawski Jan 13, 2024
ad5ecac
[generate_module] Initial checkin
opoplawski Jan 14, 2024
998a629
[generate_module] Add author handling
opoplawski Jan 14, 2024
8ed3cb6
[generate_module] Better name generation; Do not overwrite module fil…
opoplawski Jan 14, 2024
b2c7775
[module_base] Add have_refid, create_default
opoplawski Jan 14, 2024
2d1ee75
[pfsense_authserver_ldap] Use have_refid, create_default more PFSense…
opoplawski Jan 14, 2024
1beca47
[pfsense_module] Add _REQUIRED_IF
opoplawski Jan 14, 2024
5c52c2d
Add *.swp to .gitignore
opoplawski Jan 15, 2024
723bf26
[generate_module] Refactor
opoplawski Jan 15, 2024
8c5953b
[pfsense_authserver_ldap] Update copyright
opoplawski Jan 15, 2024
031b2f7
[generate_module] Add --type-suffix option
opoplawski Jan 15, 2024
8a667b2
[generate_module] Mark items required if they have "element-required"…
opoplawski Jan 15, 2024
f91f037
[generate_module] Scan php code
opoplawski Jan 15, 2024
814955a
[module_base] Store refid in obj; Use diff["after"] for logging compa…
opoplawski Jan 15, 2024
cd2b183
[pfsense_ca] Use PFSenseModuleBase _create_target, logging; Drop pfSe…
opoplawski Jan 15, 2024
5f1be15
[module_base] Initial support for bool_values
opoplawski Jan 15, 2024
665648d
[pfsense_ca] Define CA_BOOL_VALUES
opoplawski Jan 15, 2024
8f20ba2
[misc/pfsense_module.py.j2] fix - handle module_type better
opoplawski Jan 15, 2024
8ad7928
[pfsense_module.py.j2] Generate compact ARGUMENT_SPEC as well; fix fo…
opoplawski Jan 15, 2024
633ef2e
[generate_module] Handle web pages with different input methods that …
opoplawski Jan 15, 2024
295ae99
[module_base] Use root_elt.findall() instead of get_elements; Reset s…
opoplawski Jan 16, 2024
1df96cd
[pfsense_user] Use updated PFSenseModuleBase
opoplawski Jan 16, 2024
9069191
[generate_module] Handle textarea; type_param -> args.type_param
opoplawski Jan 16, 2024
42692a6
[module_base] Initialize instance vars; Add arg_route for parsing and…
opoplawski Jan 17, 2024
edb2e85
[module_base] Add map_param; Use ValueError to handle errors in valid…
opoplawski Jan 17, 2024
13b0e3f
[pfsense_user] Use PFSenseModuleBase parsing and validation
opoplawski Jan 17, 2024
71e9a5f
[generate_module] Add _CREATE_DEFAULT, _MAP_PARAM, _ARG_ROUTE, drop _…
opoplawski Jan 17, 2024
4378b7a
[generate_module] First stab at pkg_edit paths
opoplawski Jan 17, 2024
40c8606
[generate_module] Start on package handling
opoplawski Jan 17, 2024
61503bd
[generate_module] Enhancements
opoplawski Jan 19, 2024
59fab4b
[pfsense_shellcmd] Add module
opoplawski Jan 18, 2024
ec59b80
[generate_module] Enforce "." at end of descriptions
opoplawski Jan 20, 2024
396c0c6
[module_base] Add default arg_route for interface; Process all parame…
opoplawski Jan 21, 2024
a12ed88
[generate_module] Support for standard interface parser; stub in
opoplawski Jan 20, 2024
c9c2ee3
[pfsense_alias] Use CREATE_DEFAULT for detail/descr
opoplawski Jan 21, 2024
6e73cd7
[pfsense_nat_outbound] Use mutually_exclusive; Use arg_route; Add som…
opoplawski Jan 21, 2024
c6cb4f1
[pfsense_ipsec] Start using expanded PFSenseModuleBase; Start droppin…
opoplawski Jan 21, 2024
95cd167
[module_base] Bugfixes and enhancements
opoplawski Jan 21, 2024
efdb97a
[pfsense_nat_outbound] Refactor
opoplawski Jan 21, 2024
91032dc
[args_route] Add NOTE, p2o_strip
opoplawski Jan 21, 2024
011c8d5
[module_config_base] Add initial base module for configuration modules
opoplawski Jan 23, 2024
20c43a8
[pfsense_setup] Use PFSenseConfigBaseModule; Drop pre 2.5.0 support
opoplawski Jan 23, 2024
5734adb
[generate_module] First stabe at config module
opoplawski Jan 23, 2024
85b1249
[tests/plays] README
opoplawski Jan 31, 2024
266ab05
[module_base] style
opoplawski Feb 8, 2024
c6a6a78
[pfsense] Allow turning off debug in phpshell()
opoplawski Feb 19, 2024
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,6 @@ venv.bak/

# ansible-galaxy package
*.tar.gz

# vi
*.swp
2 changes: 2 additions & 0 deletions changelogs/fragments/pfsense_alias-add-url.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- pfsense_alias - Add `url` parameter and deprecate using `address` for `urltable` and `urltable_ports` types.
461 changes: 461 additions & 0 deletions misc/generate_module

Large diffs are not rendered by default.

233 changes: 233 additions & 0 deletions misc/pfsense_module.py.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) {{ year }}, {{ author_name }} <{{ author_email }}>
# 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

DOCUMENTATION = r'''
---
module: pfsense_{{ module_name }}

short_description: Manage pfSense {{ module_name }}s

version_added: "0.7.0"

description:
- Manage pfSense {{ module_name }}s.{{ ' This requires the pfSense ' ~ package ~ ' package to be installed.' if is_package else '' }}

options:
{% if not is_config %}
{{ name_param }}:
description: The {{ name_param }} of the {{ module_name }}.
required: true
type: str
state:
description: State in which to leave the {{ module_name }}.
default: present
choices: ['present', 'absent']
type: str
{% endif %}
{% for name, param in params.items() %}
{{ name }}:
description: {{ "'" if ':' in param['description'] else '' }}{{ param['description'] | default('') }}{{ "'" if ':' in param['description'] else '' }}
{% if 'choices' in param %}
choices: {{ param['choices'] }}
{% endif %}
type: {{ param['type'] | default('') }}
{% if param['type'] == 'list' %}
elements: 'str'
{% endif %}
{% endfor %}

author: {{ author_name }} (@{{ author_handle }})
'''

EXAMPLES = r'''
- name: {{ 'Configure' if is_config else 'Add myitem' }} {{ module_name }}
pfsensible.{{ package }}.pfsense_{{ module_name }}:
{% if not is_config %}
{{ name_param }}: myitem
{% endif %}
{% for name, param in params.items() %}
{% if param['example'] is defined %}
{% if param['type'] == 'list' %}
{{ name }}:
- {{ param['example'] }}
- {{ param['example2'] | default('another item') }}
{% else %}
{{ name }}: {{ param['example'] }}
{% endif %}
{% else %}
{{ name }}: {% if param['type'] == 'bool' %}false{% elif param['type'] == 'list' %}{% if 'choices' in param %}['{{ param['choices'][0:1] | join("', '") }}']{% else %}['item']{% endif %}{% elif param['type'] == 'str' %}{{ param['choices'][0] if 'choices' in param else '' }}{% endif %}

{% endif %}
{% endfor %}
{% if not is_config %}
state: present

- name: Remove myitem {{ module_name }}
pfsensible.{{ package }}.pfsense_{{ module_name }}:
{{ name_param }}: myitem
state: absent
{% endif %}
'''
RETURN = r'''
commands:
description: the set of commands that would be pushed to the remote device (if pfSense had a CLI).
returned: always
type: list
{% if is_config %}
sample: ["update {{ module_name }} set ..."]
{% else %}
sample: ["create {{ module_name }} 'myitem'", "update {{ module_name }} 'myitem' set ...", "delete {{ module_name }} 'myitem'"]
{% endif %}
'''

from ansible.module_utils.basic import AnsibleModule
{% if is_config %}
from ansible_collections.pfsensible.core.plugins.module_utils.module_config_base import PFSenseModuleConfigBase
{% else %}
from ansible_collections.pfsensible.core.plugins.module_utils.module_base import PFSenseModuleBase
{% endif %}
{% if args_imports %}
from ansible_collections.pfsensible.core.plugins.module_utils.arg_route import {{ args_imports | sort | join(', ') }}
{% endif %}

# TODO -Change to name of module, extend for needed parameters
# TODO -Keep either this or the next compact version of {{ module_name | upper() }}_ARGUMENT_SPEC
{{ module_name | upper() }}_ARGUMENT_SPEC = {
{% if not is_config %}
# Only {{ name_param }} should be required here - othewise you cannot remove an item with just '{{ name_param }}'
# Required arguments for creation should be noted in {{ module_name | upper() }}_REQUIRED_IF = ['state', 'present', ...] below
'{{ name_param }}': {'required': True, 'type': 'str'},
'state': {
'type': 'str',
'default': 'present',
'choices': ['present', 'absent']
},
{% endif %}
{% for param in params %}
'{{ param }}': {
{% if 'choices' in params[param] %}
'choices': {{ params[param]['choices'] }},
{% endif %}
'type': '{{ params[param]['type'] | default('') }}',
},
{% endfor %}
}

# Compact style
{{ module_name | upper() }}_ARGUMENT_SPEC = dict(
{% if not is_config %}
# Only {{ name_param }} should be required here - othewise you cannot remove an item with just '{{ name_param }}'
# Required arguments for creation should be noted in {{ module_name | upper() }}_REQUIRED_IF = ['state', 'present', ...] below
{{ name_param }}=dict(required=True, type='str'),
state=dict(type='str', default='present', choices=['present', 'absent']),
{% endif %}
{% for param in params %}
{{ param }}=dict(type='{{ params[param]['type'] | default('') }}'{% if 'choices' in params[param] %}, choices={{ params[param]['choices'] }},{% endif %}),
{% endfor %}
)

# TODO - check for validity - what parameters are actually required when creating a new {{ module_name }}?
{{ module_name | upper() }}_REQUIRED_IF = [
{% if not is_config %}
{% if module_type %}
['state', 'present', ['type']],
['type', '{{ params['type']['example'] }}', ['{{ params | dict2items | rejectattr('key', 'equalto', 'type') | selectattr('value.required', 'defined') | rejectattr('value.default', 'defined') | map(attribute='key') | join("', '") }}']],
{% else %}
['state', 'present', ['{{ params | dict2items | selectattr('value.required', 'defined') | rejectattr('value.default', 'defined') | map(attribute='key') | join("', '") }}']],
{% endif %}
{% endif %}
]

{% if params_xml_only %}
# TODO - check this for validity and matching module argument
{{ module_name | upper() }}_MAP_PARAM = [
{% for param in params_xml_only %}
('ARG', '{{ param }}'),
{% endfor %}
]

{% endif %}
# TODO - review this for clues for input validation. Search for functions in the below require_once files in /etc and /usr/local/pfSense/include
PHP_VALIDATION = r'''
{{ php_requires }}

{{ php_save }}
'''

# TODO - add validation and parsing methods for parameters that require it
{{ module_name | upper() }}_ARG_ROUTE = dict(
{% set param_items = ((params | dict2items | selectattr('value.parse', 'defined') | list) + (params | dict2items | selectattr('value.validate', 'defined')) | list) | unique %}
{% if param_items %}
{% for param_item in param_items %}
{{ param_item.key }}=dict({% if param_item.value.parse is defined %}parse={{ param_item.value.parse }},{% endif %}{% if param_item.value.validate is defined %}validate={{ param_item.value.validate }},{% endif %}),
{% endfor %}
{% else %}
# TODO - these are just examples
authorizedkeys=dict(parse=p2o_ssh_pub_key),
password=dict(validate=validate_password),
{% endif %}
)

{% if not is_config %}
# TODO - check for validity - what are default values when creating a new {{ module_name }}
{{ module_name | upper() }}_CREATE_DEFAULT = dict(
{% for item in params | dict2items | selectattr('value.default', 'defined') %}
{{ item.key }}='{{ item.value.default | default('VALUE') }}',
{% endfor %}
{% for param in params_xml_only %}
{{ param }}='{{ params[param]['example'] | default('VALUE') }}',
{% endfor %}
)

{% endif %}
{% if is_package %}
{{ module_name | upper() }}_PHP_COMMAND_SET = r'''
require_once("{{ package }}.inc");
{{ package }}_sync_package();
'''

{% elif 'filter.inc' in php_requires %}
{{ module_name | upper() }}_PHP_COMMAND_SET = r'''
require_once("filter.inc");
if (filter_configure() == 0) { clear_subsystem_dirty('{{ php_subsystem }}'); }
'''

{% endif %}

class PFSense{{ module_name | capitalize() }}Module({{ module_base }}):
""" module managing pfsense {{ module_name }}s """

##############################
# unit tests
#
# Must be class method for unit test usage
@staticmethod
def get_argument_spec():
""" return argument spec """
return {{ module_name | upper() }}_ARGUMENT_SPEC

def __init__(self, module, pfsense=None):
super(PFSense{{ module_name | capitalize() }}Module, self).__init__(module, pfsense, {{ 'package=\'' ~ package ~ '\', ' if is_package else ''}}root='{{ module_root }}', node='{{ module_node }}', key='{{ module_key }}'{{ ', update_php=' ~ module_name | upper() ~ '_PHP_COMMAND_SET' if 'filter.inc' in php_requires else '' }},
arg_route={{ module_name | upper() }}_ARG_ROUTE{% if params_xml_only %}, map_param={{ module_name | upper() }}_MAP_PARAM{% endif %}{% if not is_config %}, create_default={{ module_name | upper() }}_CREATE_DEFAULT{% endif %})


def main():
module = AnsibleModule(
argument_spec={{ module_name | upper() }}_ARGUMENT_SPEC,
required_if={{ module_name | upper() }}_REQUIRED_IF,
supports_check_mode=True)

pfmodule = PFSense{{ module_name | capitalize() }}Module(module)
# Pass params for testing framework
pfmodule.run(module.params)
pfmodule.commit_changes()


if __name__ == '__main__':
main()
3 changes: 2 additions & 1 deletion plugins/module_utils/__impl/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def check_name(self, name, objtype):

msg = None
if len(name) >= 32 or len(re.findall(r'(^_*$|^\d*$|[^a-zA-Z0-9_])', name)) > 0:
msg = "The {0} name must be less than 32 characters long, may not consist of only numbers, may not consist of only underscores, ".format(objtype)
msg = "The {0} name '{1}' must be less than 32 characters long, may not consist of only numbers, may not consist of only underscores, ".format(
objtype, name)
msg += "and may only contain the following characters: a-z, A-Z, 0-9, _"
elif name in ["port", "pass"]:
msg = "The {0} name must not be either of the reserved words 'port' or 'pass'".format(objtype)
Expand Down
Loading