Skip to content

Commit 911b69c

Browse files
version 1.3.0 (#3)
* feat: handling secrets without the need for `no_log` * feat: POST method module * fix: dependencies, duplicate import * feat: docs updates, new examples * feat: boilerplate
1 parent 8b24b17 commit 911b69c

File tree

23 files changed

+863
-351
lines changed

23 files changed

+863
-351
lines changed

.github/dependabot.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "pip"
4+
directory: "/"
5+
schedule:
6+
interval: "weekly"
7+
ignore:
8+
# ignore external dependency
9+
- dependency-name: "ansible-core"

Makefile

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,9 @@ clean-test: ## remove test and coverage artifacts
2828
find . -name '.mypy_cache' -exec rm -fr {} +
2929
rm -rf ansible_collections/f5_ps_ansible/f5os/tests/output
3030

31-
lint: ruff pylint ## check style with ruff and pylint
32-
33-
pylint:
34-
pylint ansible_collections/f5_ps_ansible/f5os/plugins/modules/*.py
35-
pylint ansible_collections/f5_ps_ansible/f5os/plugins/module_utils/*.py
36-
37-
black:
38-
black ansible_collections/f5_ps_ansible/f5os/plugins/modules/*.py
39-
black ansible_collections/f5_ps_ansible/f5os/plugins/module_utils/*.py
40-
41-
ruff:
42-
ruff format ansible_collections/f5_ps_ansible/f5os/plugins/modules/*.py
43-
ruff format ansible_collections/f5_ps_ansible/f5os/plugins/module_utils/*.py
44-
45-
isort:
46-
isort ansible_collections/f5_ps_ansible/f5os/plugins/modules/*.py
47-
isort ansible_collections/f5_ps_ansible/f5os/plugins/module_utils/*.py
48-
49-
code-format: isort black ruff # more is MORE!
31+
code-format:
32+
ruff check --select I --fix --exclude .venv
33+
ruff format --exclude .venv
5034

5135
doc: docs ## alias for docs
5236

ansible_collections/f5_ps_ansible/f5os/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ Using `requirements.yml`:
1717
type: git
1818
```
1919
20-
`f5_ps_ansible.f5os` is currently **NOT** available on Ansible Galaxy!
20+
> [!NOTE]
21+
> `f5_ps_ansible.f5os` is currently **NOT** available on Ansible Galaxy!
2122

2223

2324
## Dependencies

ansible_collections/f5_ps_ansible/f5os/galaxy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
namespace: f5_ps_ansible
33
name: f5os
4-
version: 1.2.0
4+
version: 1.3.0
55
readme: README.md
66
authors:
77
- Simon Kowallik <[email protected]>

ansible_collections/f5_ps_ansible/f5os/plugins/module_utils/utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ def put(self, *args, **kwargs):
4545
"""update a resource."""
4646
return self.call("PUT", *args, **kwargs)
4747

48+
def post(self, *args, **kwargs):
49+
"""update a resource."""
50+
return self.call("POST", *args, **kwargs)
51+
4852
def patch(self, *args, **kwargs):
4953
"""update a resource."""
5054
return self.call("PATCH", *args, **kwargs)

ansible_collections/f5_ps_ansible/f5os/plugins/modules/f5os_restconf_config.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@
5151
description: A JMESPath query to filter the current configuration before it is compared to the desired configuration.
5252
required: False
5353
type: str
54+
secrets:
55+
description: A list of secrets to redact from the output. Any value in this list will be redacted with 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'.
56+
required: False
57+
type: list
58+
elements: str
5459
attributes:
5560
check_mode:
5661
description: The module supports check mode and will report what changes would have been made.
@@ -67,16 +72,20 @@
6772

6873
EXAMPLES = r"""
6974
- name: 'Set the login banner'
75+
vars:
76+
f5os_api_prefix: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}"
7077
f5_ps_ansible.f5os.f5os_restconf_config:
71-
uri: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}/data/openconfig-system:system/config/login-banner"
78+
uri: "{{ f5os_api_prefix }}/data/openconfig-system:system/config/login-banner"
7279
config:
7380
openconfig-system:login-banner: |-
7481
With great power comes great responsibility!
7582
-- Spider-man's grandpa
7683
7784
- name: 'Configure trunked VLANs'
85+
vars:
86+
f5os_api_prefix: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}"
7887
f5_ps_ansible.f5os.f5os_restconf_config:
79-
uri: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}/data/openconfig-interfaces:interfaces/interface={{ item.interface }}/openconfig-if-aggregate:aggregation/openconfig-vlan:switched-vlan/config/trunk-vlans={{ item.id }}"
88+
uri: "{{ f5os_api_prefix }}/data/openconfig-interfaces:interfaces/interface={{ item.interface }}/openconfig-if-aggregate:aggregation/openconfig-vlan:switched-vlan/config/trunk-vlans={{ item.id }}"
8089
state: "{{ item.state | default('present') }}"
8190
config:
8291
openconfig-vlan:trunk-vlans: ['{{ item.id }}']
@@ -90,8 +99,10 @@
9099
state: absent # Remove VLAN 30 from interface 7.0
91100
92101
- name: 'Partially configure LLDP'
102+
vars:
103+
f5os_api_prefix: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}"
93104
f5_ps_ansible.f5os.f5os_restconf_config:
94-
uri: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}/data/openconfig-lldp:lldp/config"
105+
uri: "{{ f5os_api_prefix }}/data/openconfig-lldp:lldp/config"
95106
method: PATCH # Use PATCH to partially update the configuration
96107
config:
97108
openconfig-lldp:config:
@@ -111,6 +122,7 @@
111122
112123
- name: 'Using PATCH on a group of resources (list) with additional config endpoints'
113124
vars:
125+
f5os_api_prefix: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}"
114126
server:
115127
address: 9.9.9.11
116128
port: 53
@@ -136,6 +148,26 @@
136148
config:
137149
address: "{{ server.address }}"
138150
port: "{{ server.port }}" # no need to change to int, the type is ignored by the ansible module
151+
152+
- name: 'Set system proxy for automatic licensing and qkview uploads'
153+
# https://clouddocs.f5.com/training/community/rseries-training/html/rseries_security.html#proxy-server-via-api-for-licensing-and-qkview-uploads-to-ihealth
154+
vars:
155+
f5os_api_prefix: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}"
156+
proxy_username: f5os-proxy-user
157+
proxy_password: "{{ lookup('env', 'PROXY_PASSWORD') }}"
158+
proxy_server: https://proxyserver.internal.example.net
159+
f5_ps_ansible.f5os.f5os_restconf_config:
160+
uri: '{{ f5os_api_prefix }}/data/openconfig-system:system/f5-system-diagnostics-qkview:diagnostics/f5-system-diagnostics-proxy:proxy'
161+
config:
162+
f5-system-diagnostics-proxy:proxy:
163+
config:
164+
proxy-username: "{{ proxy_username }}"
165+
proxy-password: "{{ proxy_password }}"
166+
proxy-server: "{{ proxy_server }}"
167+
keys_ignore:
168+
- proxy-password
169+
secrets:
170+
- "{{ proxy_password }}" # redact the proxy password in logs and debug messages (no_log)
139171
"""
140172

141173
RETURN = r"""
@@ -198,6 +230,7 @@ def main():
198230
),
199231
keys_ignore=dict(required=False, type="list", default=[]),
200232
config_query=dict(required=False, type="str", default=""),
233+
secrets=dict(required=False, type="list", default=[], no_log=True),
201234
)
202235
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
203236

ansible_collections/f5_ps_ansible/f5os/plugins/modules/f5os_restconf_get.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
type: str
2424
attributes:
2525
check_mode:
26-
description: The module supports check mode and will report what changes would have been made.
26+
description: The module supports check mode.
2727
support: full
2828
diff_mode:
29-
description: The module supports diff mode and will report the differences between the desired and actual state.
29+
description: The module does not supports diff mode.
3030
support: none
3131
notes:
3232
- This module requires the f5networks.f5os collection to be installed on the ansible controller.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright: Simon Kowallik for the F5 DevCentral Community
4+
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
6+
from __future__ import absolute_import, division, print_function
7+
8+
__metaclass__ = type
9+
10+
DOCUMENTATION = r"""
11+
---
12+
module: f5os_restconf_post
13+
short_description: POST to resources of the F5OS RESTCONF API.
14+
description:
15+
- POST/Write to resources of the F5OS RESTCONF API.
16+
author:
17+
- Simon Kowallik (@simonkowallik)
18+
version_added: "1.3.0"
19+
options:
20+
uri:
21+
description: The URI of the resource to write to.
22+
required: True
23+
type: str
24+
config:
25+
description: The desired configuration to apply to the resource (PATCH) or to replace the resource with (PUT).
26+
required: False
27+
type: dict
28+
secrets:
29+
description: A list of secrets to redact from the output. Any value in this list will be redacted with 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'.
30+
required: False
31+
type: list
32+
elements: str
33+
attributes:
34+
check_mode:
35+
description: The module does not support check mode.
36+
support: none
37+
diff_mode:
38+
description: The module does not support diff mode.
39+
support: none
40+
notes:
41+
- This module requires the f5networks.f5os collection to be installed on the ansible controller.
42+
- This module uses the httpapi of the f5networks.f5os collection.
43+
"""
44+
45+
EXAMPLES = r"""
46+
- name: 'Update user password'
47+
vars:
48+
f5os_api_prefix: "{{ '/restconf' if ansible_httpapi_port == '8888' else '/api' }}"
49+
f5os_username: admin
50+
f5os_password: "{{ lookup('env', 'F5OS_PASSWORD') }}"
51+
block:
52+
- name: 'Set user password'
53+
f5_ps_ansible.f5os.f5os_restconf_post:
54+
uri: '{{ f5os_api_prefix }}/data/openconfig-system:system/aaa/authentication/f5-system-aaa:users/f5-system-aaa:user={{ f5os_username }}/config/f5-system-aaa:set-password'
55+
config:
56+
f5-system-aaa:password: "{{ f5os_password }}"
57+
secrets:
58+
- "{{ f5os_password }}"
59+
- name: 'Get ansible_date_time variable'
60+
ansible.builtin.setup:
61+
gather_subset: [min]
62+
- name: 'Set last changed - prevents prompting to change password on next login'
63+
f5_ps_ansible.f5os.f5os_restconf_config:
64+
uri: '{{ f5os_api_prefix }}/data/openconfig-system:system/aaa/authentication/f5-system-aaa:users/f5-system-aaa:user={{ f5os_username }}/config'
65+
method: PATCH
66+
config:
67+
f5-system-aaa:config:
68+
last-change: "{{ ansible_date_time.date }}"
69+
config_query: |-
70+
"f5-system-aaa:config"."last-change" | { "f5-system-aaa:config": { "last-change": @ } }
71+
"""
72+
73+
RETURN = r"""
74+
api_response:
75+
description: The API response received from the F5OS RESTCONF API.
76+
returned: always
77+
type: dict
78+
"""
79+
80+
from ansible.module_utils._text import to_text
81+
from ansible.module_utils.basic import AnsibleModule
82+
from ansible.module_utils.connection import ConnectionError
83+
84+
from ansible_collections.f5_ps_ansible.f5os.plugins.module_utils.utils import (
85+
APIClient,
86+
format_bool_values,
87+
number_values_to_string,
88+
)
89+
90+
91+
def main():
92+
"""entry point for module execution"""
93+
argument_spec = dict(
94+
uri=dict(required=True, type="str"),
95+
config=dict(required=False, type="dict", default=None),
96+
secrets=dict(required=False, type="list", default=[], no_log=True),
97+
)
98+
module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
99+
100+
result = {"changed": True, "failed": True}
101+
102+
desired_config = number_values_to_string(module.params["config"])
103+
desired_config = format_bool_values(desired_config)
104+
105+
api_client = APIClient(module)
106+
try:
107+
api_response = api_client.post(uri=module.params["uri"], config=desired_config)
108+
result.update({"api_response": api_response or {}})
109+
if api_response.get("code", 0) >= 200 and api_response.get("code", 0) < 300:
110+
result["failed"] = False
111+
except ConnectionError as exc:
112+
module.fail_json(msg=to_text(exc))
113+
114+
module.exit_json(**result)
115+
116+
117+
if __name__ == "__main__":
118+
main()

0 commit comments

Comments
 (0)