Skip to content
60 changes: 41 additions & 19 deletions plugins/modules/firewall.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import copy
import ipaddress
import time
from typing import Any, List, Optional

import ansible_collections.linode.cloud.plugins.module_utils.doc_fragments.firewall as docs
Expand Down Expand Up @@ -219,14 +220,28 @@ def __init__(self) -> None:
super().__init__(module_arg_spec=self.module_arg_spec)

def _get_firewall_by_label(self, label: str) -> Optional[Firewall]:
try:
return self.client.networking.firewalls(Firewall.label == label)[0]
except IndexError:
return None
except Exception as exception:
return self.fail(
msg="failed to get firewall {0}: {1}".format(label, exception)
)
attempt = 0
retries = 5
retry_delay = 3

while attempt < retries:
try:
return self.client.networking.firewalls(
Firewall.label == label
)[0]
except IndexError:
attempt += 1
if attempt < retries:
time.sleep(retry_delay)
else:
return None
except Exception as exception:
return self.fail(
msg="failed to get firewall {0}: {1}".format(
label, exception
)
)
return None

def _create_firewall(self) -> Optional[Firewall]:
params = self.module.params
Expand Down Expand Up @@ -348,20 +363,21 @@ def _update_rules(self, remote_rules: dict, local_rules: dict) -> dict:
if self._state != "update":
return local_rules

# Add new local rules to remote rules if they don't exist
# Amend existing rules and append new local rules
for direction in ["inbound", "outbound"]:
rlr = {
remote_rule["label"]
for remote_rule in remote_rules.get(direction, {})
remote_labeled = {
r["label"] for r in remote_rules.get(direction, [])
}
for local_rule in local_rules.get(direction, {}):
if local_rule["label"] not in rlr:
remote_rules[direction].append(local_rule)

for direction in ["inbound", "outbound"]:
local_rules[direction] = self._amend_rules(
remote_rules[direction], local_rules[direction]
# Start with amended existing remote rules
amended = self._amend_rules(
remote_rules.get(direction, []),
local_rules.get(direction, []),
)
# Append any new local rules not present in remote
for local_rule in local_rules.get(direction, []):
if local_rule["label"] not in remote_labeled:
amended.append(local_rule)
local_rules[direction] = amended
return local_rules

def _change_rules(self) -> Optional[dict]:
Expand Down Expand Up @@ -438,6 +454,12 @@ def _handle_present(self) -> None:
self._firewall = self._get_firewall_by_label(label)

if self._firewall is None:
if self._state == "update":
self.fail(
msg="Firewall {0} does not exist and cannot be updated.".format(
label
)
)
self._firewall = self._create_firewall()
self.register_action("Created Firewall {0}".format(label))

Expand Down
92 changes: 78 additions & 14 deletions tests/integration/targets/firewall_update/tasks/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
- updated_firewall.firewall.rules.inbound | length == 1
- (updated_firewall.firewall.rules.inbound[0].addresses['ipv4'] is defined)
- (updated_firewall.firewall.rules.inbound[0].addresses['ipv6'] is not defined)

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -158,7 +158,7 @@
- updated_firewall.firewall.rules.inbound | length == 1
- (updated_firewall.firewall.rules.inbound[0].addresses['ipv4'] is not defined)
- (updated_firewall.firewall.rules.inbound[0].addresses['ipv6'] is defined)

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -238,7 +238,7 @@
- updated_firewall.firewall.rules.inbound | length == 1
- (updated_firewall.firewall.rules.inbound[0].addresses['ipv4'] is defined) and (updated_firewall.firewall.rules.inbound[0].addresses['ipv4'] == ['0.0.0.0/0'])
- (updated_firewall.firewall.rules.inbound[0].addresses['ipv6'] is defined) and (updated_firewall.firewall.rules.inbound[0].addresses['ipv6'] == ['ff00::/8'])

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -286,7 +286,7 @@
that:
- updated_firewall.changed
- updated_firewall.firewall.status == 'disabled'

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -326,7 +326,7 @@
that:
- updated_firewall.changed
- updated_firewall.firewall.status == 'enabled'

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -370,7 +370,7 @@
- updated_firewall.changed
- updated_firewall.firewall.rules.inbound_policy == 'ACCEPT'
- updated_firewall.firewall.rules.outbound_policy == 'ACCEPT'

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -447,7 +447,7 @@
- updated_firewall.firewall.rules.outbound_policy == 'DROP'
- updated_firewall.firewall.rules.outbound[0].addresses['ipv4'] == ['0.0.0.0/0']
- updated_firewall.firewall.rules.outbound[0].addresses['ipv6'] == ['ff00::/8']

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -507,7 +507,7 @@
- updated_firewall.firewall.rules.outbound[0].ports == '80,443'
- updated_firewall.firewall.rules.outbound[0].protocol == 'TCP'
- updated_firewall.firewall.rules.outbound[0].action == 'ACCEPT'

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -570,7 +570,7 @@
- updated_firewall.firewall.rules.outbound[0].protocol == 'TCP'
- updated_firewall.firewall.rules.outbound[0].action == 'ACCEPT'
- updated_firewall.firewall.rules.outbound_policy == 'DROP'

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -635,7 +635,7 @@
- updated_firewall.firewall.rules.outbound[0].action == 'ACCEPT'
- updated_firewall.firewall.rules.outbound[0].addresses['ipv4'] == ['0.0.0.0/0','8.8.8.8/32' ]
- updated_firewall.firewall.rules.outbound_policy == 'DROP'

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -700,7 +700,7 @@
- updated_firewall.firewall.rules.outbound[0].action == 'ACCEPT'
- updated_firewall.firewall.rules.outbound[0].addresses['ipv4'] == ['0.0.0.0/0','8.8.8.8/32' ]
- updated_firewall.firewall.rules.outbound_policy == 'DROP'

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -749,7 +749,7 @@
- updated_firewall.changed
- updated_firewall.firewall.rules.inbound[0].description == 'Amazing firewall rule.'
- updated_firewall.firewall.rules.outbound[0].description == 'Amazing firewall rule.'

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -798,7 +798,7 @@
assert:
that:
- firewall_info.devices|length == 0

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand Down Expand Up @@ -847,7 +847,7 @@
assert:
that:
- not updated_firewall.changed

- firewall_info.firewall.id == create.firewall.id
- firewall_info.devices|length == updated_firewall.devices|length
- firewall_info.firewall.status == updated_firewall.firewall.status
Expand All @@ -867,6 +867,70 @@
- (firewall_info.firewall.rules.outbound | length == 0 and updated_firewall.firewall.rules.outbound | length == 0) or ((firewall_info.firewall.rules.outbound[0].ports is not defined and updated_firewall.firewall.rules.outbound[0].ports is not defined) or (firewall_info.firewall.rules.outbound[0].ports == updated_firewall.firewall.rules.outbound[0].ports))
- (firewall_info.firewall.rules.outbound | length == 0 and updated_firewall.firewall.rules.outbound | length == 0) or ((firewall_info.firewall.rules.outbound[0].protocol is not defined and updated_firewall.firewall.rules.outbound[0].protocol is not defined) or (firewall_info.firewall.rules.outbound[0].protocol == updated_firewall.firewall.rules.outbound[0].protocol))
- (firewall_info.firewall.rules.outbound | length == 0 and updated_firewall.firewall.rules.outbound | length == 0) or ((firewall_info.firewall.rules.outbound[0].action is not defined and updated_firewall.firewall.rules.outbound[0].action is not defined) or (firewall_info.firewall.rules.outbound[0].action == updated_firewall.firewall.rules.outbound[0].action))

- name: Append a new inbound rule via update
linode.cloud.firewall:
state: update
api_version: v4beta
label: '{{ create.firewall.label }}'
rules:
inbound:
- label: new_rule
action: ACCEPT
protocol: TCP
addresses:
ipv4: ['2.2.2.2/32']
description: 'appended rule'
ports: '8080'
register: append_rule

- name: Assert new rule was appended
assert:
that:
- append_rule.changed
- append_rule.firewall.rules.inbound | length >= 2
- append_rule.firewall.rules.inbound | selectattr('label', 'equalto', 'new_rule') | list | length == 1

Comment thread
ezilber-akamai marked this conversation as resolved.
- name: Get firewall info after appending a new inbound rule
linode.cloud.firewall_info:
api_version: v4beta
label: '{{ create.firewall.label }}'
register: append_rule_info

- name: Assert appended rule exists in remote firewall state
assert:
that:
- append_rule_info.firewall.rules.inbound | length >= 2
- append_rule_info.firewall.rules.inbound | selectattr('label', 'equalto', 'new_rule') | list | length == 1

- name: Re-run append update to verify idempotency
linode.cloud.firewall:
state: update
api_version: v4beta
label: '{{ create.firewall.label }}'
rules:
inbound:
- label: new_rule
action: ACCEPT
protocol: TCP
addresses:
ipv4: ['2.2.2.2/32']
description: 'appended rule'
ports: '8080'
register: append_rule_again

- name: Get firewall info after idempotency check
linode.cloud.firewall_info:
api_version: v4beta
label: '{{ create.firewall.label }}'
register: append_rule_info_again

- name: Assert append update is idempotent
assert:
that:
- not append_rule_again.changed
- append_rule_info_again.firewall.rules.inbound | selectattr('label', 'equalto', 'new_rule') | list | length == 1

always:
- ignore_errors: yes
block:
Expand Down