diff --git a/ansible_collections/f5networks/f5_modules/CHANGELOG.rst b/ansible_collections/f5networks/f5_modules/CHANGELOG.rst index 33b11f0..bf7ae64 100644 --- a/ansible_collections/f5networks/f5_modules/CHANGELOG.rst +++ b/ansible_collections/f5networks/f5_modules/CHANGELOG.rst @@ -4,6 +4,16 @@ F5Networks F5\_Modules Collection Release Notes .. contents:: Topics +v1.31.0 +======= + +Minor Changes +------------- + +- bigip_asm_dos_application - add support for creating dos profile. +- bigip_device_info - virtual-servers - return per_flow_request_access_policy if defined. +- bigip_virtual_server - set per_flow_request_access_policy and stay idempotent. + v1.30.1 ======= diff --git a/ansible_collections/f5networks/f5_modules/changelogs/changelog.yaml b/ansible_collections/f5networks/f5_modules/changelogs/changelog.yaml index efe79e7..ac6f4bc 100644 --- a/ansible_collections/f5networks/f5_modules/changelogs/changelog.yaml +++ b/ansible_collections/f5networks/f5_modules/changelogs/changelog.yaml @@ -1141,6 +1141,16 @@ releases: release_date: '2024-08-01' 1.30.1: release_date: '2024-08-02' + 1.31.0: + changes: + minor_changes: + - bigip_asm_dos_application - add support for creating dos profile. + - bigip_device_info - virtual-servers - return per_flow_request_access_policy + if defined. + - bigip_virtual_server - set per_flow_request_access_policy and stay idempotent. + fragments: + - issue_2419_2421.yaml + release_date: '2024-09-11' 1.4.0: changes: bugfixes: diff --git a/ansible_collections/f5networks/f5_modules/galaxy.yml b/ansible_collections/f5networks/f5_modules/galaxy.yml index d0557d5..798a6d7 100644 --- a/ansible_collections/f5networks/f5_modules/galaxy.yml +++ b/ansible_collections/f5networks/f5_modules/galaxy.yml @@ -30,4 +30,4 @@ tags: - networking - bigip - bigiq -version: 1.30.1 +version: 1.31.0 diff --git a/ansible_collections/f5networks/f5_modules/plugins/module_utils/icontrol.py b/ansible_collections/f5networks/f5_modules/plugins/module_utils/icontrol.py index 723c536..ad21e56 100644 --- a/ansible_collections/f5networks/f5_modules/plugins/module_utils/icontrol.py +++ b/ansible_collections/f5networks/f5_modules/plugins/module_utils/icontrol.py @@ -182,7 +182,7 @@ def send(self, method, url, **kwargs): if not data and json is not None: self.request.headers.update(BASE_HEADERS) - body = _json.dumps(json) + body = _json.dumps(json, ensure_ascii=False) if not isinstance(body, bytes): body = body.encode('utf-8') if data: diff --git a/ansible_collections/f5networks/f5_modules/plugins/module_utils/version.py b/ansible_collections/f5networks/f5_modules/plugins/module_utils/version.py index 61b1f6f..1e01ac3 100644 --- a/ansible_collections/f5networks/f5_modules/plugins/module_utils/version.py +++ b/ansible_collections/f5networks/f5_modules/plugins/module_utils/version.py @@ -4,4 +4,4 @@ # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # This collection version needs to be updated at each release -CURRENT_COLL_VERSION = "1.30.1" +CURRENT_COLL_VERSION = "1.31.0" diff --git a/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_asm_dos_application.py b/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_asm_dos_application.py index 5ee49e6..4519104 100644 --- a/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_asm_dos_application.py +++ b/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_asm_dos_application.py @@ -1136,7 +1136,21 @@ def profile_exists(self): raise F5ModuleError(str(ex)) if resp.status == 404 or 'code' in response and response['code'] == 404: - return False + uri = "https://{0}:{1}/mgmt/tm/security/dos/profile".format( + self.client.provider['server'], + self.client.provider['server_port'], + ) + payload = {'name': self.want.profile, 'partition': self.want.partition} + resp = self.client.api.post(uri, json=payload) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + # response = resp.json() + if resp.status == 404 or 'code' in response and response['code'] == 404: + return False + if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: + return True if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: return True @@ -1148,16 +1162,11 @@ def profile_exists(self): def exists(self): errors = [401, 403, 409, 500, 501, 502, 503, 504] - if not self.profile_exists(): - raise F5ModuleError( - 'Specified DOS profile: {0} on partition: {1} does not exist.'.format( - self.want.profile, self.want.partition) - ) - uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/application/{3}".format( + + uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}".format( self.client.provider['server'], self.client.provider['server_port'], transform_name(self.want.partition, self.want.profile), - self.want.profile ) resp = self.client.api.get(uri) try: @@ -1169,7 +1178,6 @@ def exists(self): return False if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: return True - if resp.status in errors or 'code' in response and response['code'] in errors: if 'message' in response: raise F5ModuleError(response['message']) @@ -1179,6 +1187,19 @@ def exists(self): def create_on_device(self): params = self.changes.api_params() params['name'] = self.want.profile + uri = "https://{0}:{1}/mgmt/tm/security/dos/profile".format( + self.client.provider['server'], + self.client.provider['server_port'], + ) + payload = {'name': self.want.profile, 'partition': self.want.partition} + + resp = self.client.api.post(uri, json=payload) + try: + response = resp.json() + except ValueError as ex: + raise F5ModuleError(str(ex)) + if resp.status == 404 or 'code' in response and response['code'] == 404: + raise F5ModuleError(resp.content) uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/application/".format( self.client.provider['server'], self.client.provider['server_port'], @@ -1213,11 +1234,10 @@ def update_on_device(self): raise F5ModuleError(resp.content) def remove_from_device(self): - uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/application/{3}".format( + uri = "https://{0}:{1}/mgmt/tm/security/dos/profile/{2}/".format( self.client.provider['server'], self.client.provider['server_port'], transform_name(self.want.partition, self.want.profile), - self.want.profile ) response = self.client.api.delete(uri) if response.status == 200: diff --git a/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_device_info.py b/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_device_info.py index 6753634..6b07d48 100644 --- a/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_device_info.py +++ b/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_device_info.py @@ -7454,6 +7454,12 @@ returned: queried type: str sample: tcp + per_flow_request_access_policy: + description: + - per request policy. + returned: queried + type: str + sample: /Common/my-custom-per-request-policy total_requests: description: - Total requests. @@ -17114,6 +17120,7 @@ class VirtualServersParameters(BaseParameters): 'securityLogProfiles': 'security_log_profiles', 'profilesReference': 'profiles', 'policiesReference': 'policies', + 'perFlowRequestAccessPolicy': 'per_flow_request_access_policy', } returnables = [ @@ -17153,6 +17160,7 @@ class VirtualServersParameters(BaseParameters): 'type', 'policies', 'profiles', + 'per_flow_request_access_policy', 'destination_address', 'destination_port', 'availability_status', diff --git a/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_virtual_server.py b/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_virtual_server.py index 7994c15..82e0290 100644 --- a/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_virtual_server.py +++ b/ansible_collections/f5networks/f5_modules/plugins/modules/bigip_virtual_server.py @@ -227,6 +227,10 @@ elements: str aliases: - all_policies + per_flow_request_access_policy: + description: + - Specifies the Per-Request access policy for the virtual server. + type: str snat: description: - Source network address policy. @@ -811,6 +815,11 @@ returned: changed type: list sample: ['/Common/policy1', '/Common/policy2'] +per_flow_request_access_policy: + description: Per-request policy attached to the virtual. + returned: changed + type: str + sample: '/Common/sample_per-request_policy' port: description: Port the virtual server is configured to listen on. returned: changed @@ -981,7 +990,8 @@ class Parameters(AnsibleF5Parameters): 'rateLimitSrcMask': 'rate_limit_src_mask', 'clonePools': 'clone_pools', 'autoLasthop': 'auto_last_hop', - 'serviceDownImmediateAction': 'service_down_immediate_action' + 'serviceDownImmediateAction': 'service_down_immediate_action', + 'perFlowRequestAccessPolicy': 'per_flow_request_access_policy', } api_attributes = [ @@ -1025,6 +1035,7 @@ class Parameters(AnsibleF5Parameters): 'rateLimitSrcMask', 'clonePools', 'autoLasthop', + 'perFlowRequestAccessPolicy', ] updatables = [ @@ -1062,6 +1073,7 @@ class Parameters(AnsibleF5Parameters): 'rate_limit_dst_mask', 'clone_pools', 'auto_last_hop', + 'per_flow_request_access_policy', ] returnables = [ @@ -1103,6 +1115,7 @@ class Parameters(AnsibleF5Parameters): 'rate_limit_dst_mask', 'clone_pools', 'auto_last_hop', + 'per_flow_request_access_policy', ] profiles_mutex = [ @@ -1627,6 +1640,7 @@ def profiles(self): return None result = [] prof_path = 'https://localhost/mgmt/tm/ltm/profile/' + accprof_path = 'https://localhost/mgmt/tm/apm/profile/access' for item in self._values['profiles']['items']: context = item['context'] name = item['name'] @@ -1634,6 +1648,8 @@ def profiles(self): if context in ['all', 'serverside', 'clientside']: if path.startswith(prof_path): result.append(dict(name=name, context=context, fullPath=item['fullPath'])) + if path.startswith(accprof_path): + result.append(dict(name=name, context=context, fullPath=item['fullPath'])) else: raise F5ModuleError( "Unknown profile context found: '{0}'".format(context) @@ -2631,6 +2647,7 @@ def check_update(self): def check_create(self): # Regular checks + self._verify_virtual_has_required_parameters() self._set_default_ip_protocol() self._set_default_profiles() self._override_port_by_type() @@ -2639,7 +2656,6 @@ def check_create(self): self._verify_default_persistence_profile_for_type() self._verify_fallback_persistence_profile_for_type() self._update_persistence_profile() - self._verify_virtual_has_required_parameters() self._ensure_server_type_supports_vlans() self._override_vlans_if_all_specified() self._check_source_and_destination_match() @@ -3629,6 +3645,9 @@ def create_on_device(self): params = self.changes.api_params() params['name'] = self.want.name params['partition'] = self.want.partition + if 'destination' not in params: + params['destination'] = f"/Common/0.0.0.0:{self.want.port}" + params['mask'] = "255.255.255.255" if self.want.insert_metadata: # Mark the resource as managed by Ansible, this is default behavior params = mark_managed_by(self.module.ansible_version, params) @@ -3743,6 +3762,7 @@ def __init__(self): service_down_immediate_action=dict( choices=['drop', 'none', 'reset'] ), + per_flow_request_access_policy=dict(), security_log_profiles=dict( type='list', elements='str', diff --git a/ansible_collections/f5networks/f5_modules/tests/sanity/ignore-2.17.txt b/ansible_collections/f5networks/f5_modules/tests/sanity/ignore-2.17.txt new file mode 100644 index 0000000..ed7db93 --- /dev/null +++ b/ansible_collections/f5networks/f5_modules/tests/sanity/ignore-2.17.txt @@ -0,0 +1,2 @@ +plugins/action/bigip.py action-plugin-docs # undocumented action plugin to fix, existed before sanity test was added +plugins/action/bigiq.py action-plugin-docs # undocumented action plugin to fix, existed before sanity test was added \ No newline at end of file diff --git a/ansible_collections/f5networks/f5_modules/tests/unit/compat/mock.py b/ansible_collections/f5networks/f5_modules/tests/unit/compat/mock.py index 0972cd2..c7d0183 100644 --- a/ansible_collections/f5networks/f5_modules/tests/unit/compat/mock.py +++ b/ansible_collections/f5networks/f5_modules/tests/unit/compat/mock.py @@ -64,8 +64,7 @@ def _iterate_read_data(read_data): # newline that our naive format() added data_as_list[-1] = data_as_list[-1][:-1] - for line in data_as_list: - yield line + yield from data_as_list def mock_open(mock=None, read_data=''): """ @@ -93,8 +92,7 @@ def _readline_side_effect(): if handle.readline.return_value is not None: while True: yield handle.readline.return_value - for line in _data: - yield line + yield from _data global file_spec if file_spec is None: diff --git a/ansible_collections/f5networks/f5_modules/tests/unit/mock/loader.py b/ansible_collections/f5networks/f5_modules/tests/unit/mock/loader.py index c41a6ea..edeac45 100644 --- a/ansible_collections/f5networks/f5_modules/tests/unit/mock/loader.py +++ b/ansible_collections/f5networks/f5_modules/tests/unit/mock/loader.py @@ -30,7 +30,7 @@ class DictDataLoader(DataLoader): def __init__(self, file_mapping=None): file_mapping = {} if file_mapping is None else file_mapping - assert type(file_mapping) is dict + assert isinstance(file_mapping, dict) super(DictDataLoader, self).__init__()