Skip to content

Commit 5cc4340

Browse files
authored
Update CI for NetBox 4.1 (#1314)
* Fix formatting * Add 4.1 to tests * Adjust docker-compose * Adjust waiting-URL * Update dependencies * Add new inventories for 4.1 * Adjust python version * Remove 3.6 from CI * Adjust CI steps * Add changelog
1 parent 77cf784 commit 5cc4340

File tree

120 files changed

+22443
-627
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

120 files changed

+22443
-627
lines changed

.github/workflows/main.yml

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,17 @@ jobs:
6262
runs-on: ubuntu-latest
6363
needs: unit_testing
6464
env:
65-
python-version: "3.10"
65+
python-version: "3.12"
6666
strategy:
6767
fail-fast: false
6868
matrix:
69-
include:
70-
- VERSION: "v3.6"
71-
NETBOX_DOCKER_VERSION: 2.7.0
69+
include:
7270
- VERSION: "v3.7"
7371
NETBOX_DOCKER_VERSION: 2.7.0
7472
- VERSION: "v4.0"
7573
NETBOX_DOCKER_VERSION: 2.9.1
74+
- VERSION: "v4.1"
75+
NETBOX_DOCKER_VERSION: 3.0.1
7676

7777
steps:
7878

@@ -113,20 +113,27 @@ jobs:
113113
run: |
114114
docker container ls
115115
docker logs netbox-docker-netbox-1
116-
timeout 300 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:32768)" != "200" ]]; do echo "waiting for NetBox"; sleep 5; done' || false
117-
working-directory: /home/runner/.ansible/collections/ansible_collections/netbox/netbox
118-
#if: matrix.VERSION == 'v3.3'
116+
timeout 300 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' localhost:32768/login/)" != "200" ]]; do echo "waiting for NetBox"; sleep 5; done' || false
117+
working-directory: /home/runner/.ansible/collections/ansible_collections/netbox/netbox
119118

120119
- name: Pre-populate NetBox
121120
run: ./tests/integration/netbox-deploy.py
122121
working-directory: /home/runner/.ansible/collections/ansible_collections/netbox/netbox
123122

124-
- name: Run integration tests
125-
# Run regression and integration tests
126-
# Run the inventory test first, in case any of the other tests modify the data.
123+
- name: Run inventory tests
124+
continue-on-error: true
127125
run: |
128126
ansible-test integration -v --color --coverage --python ${{ env.python-version }} inventory-${{ matrix.VERSION }}
129-
ansible-test integration -v --color --coverage --python ${{ env.python-version }} regression-${{ matrix.VERSION }}
127+
working-directory: /home/runner/.ansible/collections/ansible_collections/netbox/netbox
128+
129+
- name: Run regression tests
130+
continue-on-error: true
131+
run: |
132+
ansible-test integration -v --color --coverage --python ${{ env.python-version }} regression-${{ matrix.VERSION }}
133+
working-directory: /home/runner/.ansible/collections/ansible_collections/netbox/netbox
134+
135+
- name: Run integration tests
136+
run: |
130137
ansible-test integration -v --color --coverage --python ${{ env.python-version }} ${{ matrix.VERSION }}
131138
ansible-test coverage report --all --omit "tests/*,hacking/*,docs/*" --show-missing
132139
working-directory: /home/runner/.ansible/collections/ansible_collections/netbox/netbox

changelogs/fragments/ci_netbox41.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
minor_changes:
2+
- Update CI for NetBox 4.1

plugins/modules/netbox_user.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,10 @@ def main():
170170
)
171171
)
172172

173-
required_if = [("state", "present", ["username"]), ("state", "absent", ["username"])]
173+
required_if = [
174+
("state", "present", ["username"]),
175+
("state", "absent", ["username"]),
176+
]
174177

175178
module = NetboxAnsibleModule(
176179
argument_spec=argument_spec, supports_check_mode=True, required_if=required_if

poetry.lock

Lines changed: 677 additions & 611 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ license = "GPLv3"
77

88
[tool.poetry.dependencies]
99
python = "^3.10"
10-
ansible-core = "2.15.9"
10+
ansible-core = "^2.16"
1111
black = "*"
1212
codecov = "*"
13-
coverage = "6.5.*"
13+
coverage = "7.3.2"
1414
deepdiff = "*"
1515
cryptography = "*"
1616
jinja2 = "*"
1717
jmespath = "*"
18-
pynetbox = "^7.3"
18+
pynetbox = "*"
1919
pytest = "*"
2020
pytest-mock = "*"
2121
pytest-xdist = "*"

tests/integration/inventory

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[testgroup]
2-
testhost ansible_connection="local" ansible_pipelining="yes" ansible_python_interpreter="/Users/rodvand/collections/ansible_collections/netbox/netbox/venv/bin/python3.9"
2+
testhost ansible_connection="local" ansible_pipelining="yes" ansible_python_interpreter="/Users/rodvand/ansible_collections/netbox/netbox/venv/bin/python"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
runme_config
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# https://docs.ansible.com/ansible/devel/dev_guide/testing/sanity/integration-aliases.html
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env python
2+
3+
# Inspired by community.aws collection script_inventory_ec2 test
4+
# https://github.com/ansible-collections/community.aws/blob/master/tests/integration/targets/script_inventory_ec2/inventory_diff.py
5+
6+
from __future__ import absolute_import, division, print_function
7+
8+
__metaclass__ = type
9+
10+
import argparse
11+
import json
12+
import sys
13+
from operator import itemgetter
14+
15+
from deepdiff import DeepDiff
16+
17+
# NetBox includes "created" and "last_updated" times on objects. These end up in the interfaces objects that are included verbatim from the NetBox API.
18+
# "url" may be different if local tests use a different host/port
19+
# Remove these from files saved in git as test data
20+
KEYS_REMOVE = frozenset(["created", "last_updated", "url"])
21+
22+
# Ignore these when performing diffs as they will be different for each test run
23+
# (Was previously keys specific to NetBox 2.6)
24+
KEYS_IGNORE = frozenset()
25+
26+
# Rack Groups became hierarchical in NetBox 2.8. Don't bother comparing against test data in NetBox 2.7
27+
KEYS_IGNORE_27 = frozenset(
28+
[
29+
"rack_groups", # host var
30+
"rack_group_parent_rack_group", # group, group_names_raw = False
31+
"parent_rack_group", # group, group_names_raw = True
32+
]
33+
)
34+
35+
36+
# Assume the object will not be recursive, as it originally came from JSON
37+
def remove_keys(obj, keys):
38+
if isinstance(obj, dict):
39+
keys_to_remove = keys.intersection(obj.keys())
40+
for key in keys_to_remove:
41+
del obj[key]
42+
43+
for key, value in obj.items():
44+
remove_keys(value, keys)
45+
46+
elif isinstance(obj, list):
47+
# Iterate over temporary copy, as we may remove items
48+
for item in obj[:]:
49+
if isinstance(item, str) and item in keys:
50+
# List contains a string that we want to remove
51+
# eg. a group name in list of groups
52+
obj.remove(item)
53+
remove_keys(item, keys)
54+
55+
56+
def sort_hostvar_arrays(obj):
57+
meta = obj.get("_meta")
58+
if not meta:
59+
return
60+
61+
hostvars = meta.get("hostvars")
62+
if not hostvars:
63+
return
64+
65+
for _, host in hostvars.items(): # pylint: disable=disallowed-name
66+
if interfaces := host.get("interfaces"):
67+
host["interfaces"] = sorted(interfaces, key=itemgetter("id"))
68+
69+
if services := host.get("services"):
70+
host["services"] = sorted(services, key=itemgetter("id"))
71+
72+
73+
def read_json(filename):
74+
with open(filename, "r", encoding="utf-8") as file:
75+
return json.loads(file.read())
76+
77+
78+
def write_json(filename, data):
79+
with open(filename, "w", encoding="utf-8") as file:
80+
json.dump(data, file, indent=4)
81+
82+
83+
def main():
84+
parser = argparse.ArgumentParser(description="Diff Ansible inventory JSON output")
85+
parser.add_argument(
86+
"filename_a",
87+
metavar="ORIGINAL.json",
88+
type=str,
89+
help="Original json to test against",
90+
)
91+
parser.add_argument(
92+
"filename_b",
93+
metavar="NEW.json",
94+
type=str,
95+
help="Newly generated json to compare against original",
96+
)
97+
parser.add_argument(
98+
"--write",
99+
action="store_true",
100+
help=(
101+
"When comparing files, various keys are removed. "
102+
"This option will not compare the files, and instead writes ORIGINAL.json to NEW.json after removing these keys. "
103+
"This is used to clean the test json files before saving to the git repo. "
104+
"For example, this removes dates. "
105+
),
106+
)
107+
parser.add_argument(
108+
"--netbox-version",
109+
metavar="VERSION",
110+
type=str,
111+
help=(
112+
"Apply comparison specific to NetBox version. "
113+
"For example, rack_groups arrays will only contain a single item in v2.7, so are ignored in the comparison."
114+
),
115+
)
116+
117+
args = parser.parse_args()
118+
119+
data_a = read_json(args.filename_a)
120+
121+
if args.write:
122+
# When writing test data, only remove "remove_keys" that will change on every git commit.
123+
# This makes diffs more easily readable to ensure changes to test data look correct.
124+
remove_keys(data_a, KEYS_REMOVE)
125+
sort_hostvar_arrays(data_a)
126+
write_json(args.filename_b, data_a)
127+
128+
else:
129+
data_b = read_json(args.filename_b)
130+
131+
# Ignore keys that we don't want to diff, in addition to the ones removed that change on every commit
132+
keys = KEYS_REMOVE.union(KEYS_IGNORE)
133+
remove_keys(data_a, keys)
134+
remove_keys(data_b, keys)
135+
136+
sort_hostvar_arrays(data_a)
137+
sort_hostvar_arrays(data_b)
138+
139+
# Perform the diff
140+
result = DeepDiff(data_a, data_b, ignore_order=True)
141+
142+
if result:
143+
# Dictionary is not empty - print differences
144+
print(json.dumps(result, sort_keys=True, indent=4))
145+
sys.exit(1)
146+
else:
147+
# Success, no differences
148+
sys.exit(0)
149+
150+
151+
if __name__ == "__main__":
152+
main()

0 commit comments

Comments
 (0)