-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GraqhQL Inventory: Add ability for users to specify non-foreign key r…
…elationships with Null/None (#123)
- Loading branch information
1 parent
40d8f40
commit b5d98c7
Showing
10 changed files
with
222 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Dockerfile | ||
docker-compose.yml | ||
*.md | ||
.env | ||
.vscode/ | ||
.github/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
"""GraphQL related filter plugins.""" | ||
# Copyright (c) 2022, Network to Code (@networktocode) <info@networktocode.com> | ||
# 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 | ||
|
||
|
||
def build_graphql_filter_string(filter: dict) -> str: | ||
"""Takes a dictionary and builds a graphql filter | ||
Args: | ||
filter (dict): Key/Value pairs to build filter from | ||
Returns: | ||
str: Proper graphQL filter | ||
""" | ||
base_filter = "({0})" | ||
loop_filters = [] | ||
for key, value in filter.items(): | ||
temp_string = f"{key}: " | ||
value_string = f"{value}" | ||
|
||
# GraphQL variables do not need quotes (This isn't completely support with inventory yet, but code added here) | ||
if isinstance(value, str) and not key.startswith("$"): | ||
value_string = "'" + value_string + "'" | ||
|
||
loop_filters.append(temp_string + value_string) | ||
|
||
return base_filter.format(", ".join(loop_filters)) | ||
|
||
|
||
def convert_to_graphql_string(query: dict, start=0) -> str: | ||
"""Provide a dictionary to convert to a graphQL string. | ||
Args: | ||
query (dict): A dictionary mapping to the graphQL call to be made. | ||
start (int): The starting indentation when compiling string. | ||
Returns: | ||
str: GraphQL query string | ||
""" | ||
graphql_string = """""" | ||
loop_index = 0 | ||
query_len = len(query) | ||
for k, v in query.items(): | ||
loop_index += 1 | ||
loop_string = "".rjust(start * 2, " ") | ||
loop_filter = None | ||
|
||
# Determine what to do | ||
if not v: | ||
loop_string += f"{k}" | ||
elif isinstance(v, dict): | ||
if v.get("filters"): | ||
loop_filter = build_graphql_filter_string(v.pop("filters")) | ||
loop_string += "{0} {1}\n".format(k, loop_filter + " {" if loop_filter else "{") | ||
loop_string += convert_to_graphql_string(v, start + 1) | ||
loop_string += "{0}{1}".format(" " * (start * 2), "}") | ||
else: | ||
loop_string += "{0} {1}\n".format(k, "{") | ||
# We want to keep the double spaces, but add 2 more | ||
loop_string += "{0}".format(" " * (start * 2 + 2)) | ||
loop_string += f"{v}\n" | ||
loop_string += "{0}{1}".format(" " * (start * 2), "}") | ||
|
||
if loop_index != query_len or start > 0: | ||
loop_string += "\n" | ||
|
||
graphql_string += loop_string | ||
|
||
return graphql_string.replace("'", '"') | ||
|
||
|
||
class FilterModule: | ||
"""Return graphQL filters.""" | ||
|
||
def filters(self): | ||
"""Map filter functions to filter names.""" | ||
return { | ||
"graphql_string": convert_to_graphql_string, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
[ | ||
{ | ||
"query": { | ||
"serial": null, | ||
"asset_tag": null | ||
}, | ||
"expected": "serial\nasset_tag" | ||
}, | ||
{ | ||
"query": { | ||
"query": { | ||
"devices": { | ||
"serial": null, | ||
"asset_tag": null | ||
} | ||
} | ||
}, | ||
"expected": "query {\n devices {\n serial\n asset_tag\n }\n}" | ||
}, | ||
{ | ||
"query": { | ||
"serial": null, | ||
"asset_tag": null, | ||
"site": { | ||
"name": null, | ||
"contact_name": null, | ||
"description": null, | ||
"region": { | ||
"name": null, | ||
"parent": { | ||
"name": null | ||
} | ||
} | ||
} | ||
}, | ||
"expected": "serial\nasset_tag\nsite {\n name\n contact_name\n description\n region {\n name\n parent {\n name\n }\n }\n}" | ||
}, | ||
{ | ||
"query": { | ||
"serial": null, | ||
"asset_tag": null, | ||
"site": { | ||
"filters": { | ||
"role": "core", | ||
"tenant": "den" | ||
}, | ||
"name": null, | ||
"contact_name": null, | ||
"description": null, | ||
"region": { | ||
"filters": { | ||
"$tenant": "den" | ||
}, | ||
"name": null, | ||
"parent": { | ||
"name": null | ||
} | ||
} | ||
} | ||
}, | ||
"expected": "serial\nasset_tag\nsite (role: \"core\", tenant: \"den\") {\n name\n contact_name\n description\n region ($tenant: den) {\n name\n parent {\n name\n }\n }\n}" | ||
}, | ||
{ | ||
"query": { | ||
"device": { | ||
"name": null, | ||
"platform": "napalm_driver", | ||
"status": "name", | ||
"primary_ip4": "address", | ||
"device_role": "name", | ||
"site": "name" | ||
} | ||
}, | ||
"expected": "device {\n name\n platform {\n napalm_driver\n }\n status {\n name\n }\n primary_ip4 {\n address\n }\n device_role {\n name\n }\n site {\n name\n }\n}" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
"""Tests for Nautobot GraphQL filter plugins.""" | ||
|
||
import json | ||
import pytest | ||
from ansible_collections.networktocode.nautobot.plugins.filter.graphql import ( | ||
convert_to_graphql_string, | ||
build_graphql_filter_string, | ||
) | ||
|
||
|
||
def load_test_data(test): | ||
with open(f"tests/unit/filter/test_data/{test}.json", "r") as f: | ||
data = json.loads(f.read()) | ||
return data | ||
|
||
|
||
@pytest.mark.parametrize(("test_data"), load_test_data("graphql_string")) | ||
def test_convert_to_graphql_string(test_data): | ||
result = convert_to_graphql_string(test_data["query"]) | ||
assert result == test_data["expected"] | ||
|
||
|
||
def test_build_graphql_filter_string(): | ||
gql_filters = {"role": "core", "$tenant": "den", "device_type": "c9300"} | ||
assert build_graphql_filter_string(gql_filters) == "(role: 'core', $tenant: den, device_type: 'c9300')" |