Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GraqhQL Inventory: Add ability for users to specify non-foreign key relationships with Null/None #123

Merged
merged 8 commits into from
Mar 30, 2022
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Dockerfile
docker-compose.yml
*.md
.env
.vscode/
.github/
2 changes: 1 addition & 1 deletion ansible.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
force_valid_group_names = always

[inventory]
enable_plugins = networktocode.nautobot.inventory, auto, host_list, yaml, ini, toml, script
enable_plugins = networktocode.nautobot.inventory, networktocode.nautobot.gql_inventory, yaml, ini
72 changes: 72 additions & 0 deletions plugins/filter/graphql.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
"""GraphQL related filter plugins."""


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
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.
FragmentedPacket marked this conversation as resolved.
Show resolved Hide resolved

Returns:
str: GraphQL query string
"""
graphql_string = f""""""
for k, v in query.items():
loop_string = "{}".format(" " * (start * 2))
loop_filter = None
if not v:
loop_string += f"{k}\n"
elif isinstance(v, dict):
if v.get("filters"):
loop_filter = build_graphql_filter_string(v.pop("filters"))
# Increment start for recursion
start += 1
loop_string += "{0} {1}\n".format(k, loop_filter + " {" if loop_filter else " {")
loop_string += convert_to_graphql_string(v, start)
# Decrement start to continue at the same level we were at prior to recursion
start -= 1
loop_string += "{0}{1}\n".format(" " * (start * 2), "}")
else:
loop_string += '{0} {1}\n'.format(k, "{")
# We want to keep the doubling spaces, but add 2 more
loop_string += "{0}".format(" " * (start * 2 + 2))
loop_string += f"{v}\n"
loop_string += "{0}{1}\n".format(" " * (start * 2), "}")
# 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,
}
25 changes: 19 additions & 6 deletions plugins/inventory/gql_inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@
from ansible.module_utils.urls import open_url

from ansible.module_utils.six.moves.urllib import error as urllib_error
from jinja2 import Environment, FileSystemLoader
# from jinja2 import Environment, FileSystemLoader
FragmentedPacket marked this conversation as resolved.
Show resolved Hide resolved
from ansible_collections.networktocode.nautobot.plugins.filter.graphql import convert_to_graphql_string

try:
from netutils.lib_mapper import ANSIBLE_LIB_MAPPER_REVERSE, NAPALM_LIB_MAPPER
Expand Down Expand Up @@ -242,10 +243,20 @@ def main(self):
if not HAS_NETUTILS:
raise AnsibleError("networktocode.nautobot.gql_inventory requires netutils. Please pip install netutils.")

file_loader = FileSystemLoader(f"{PATH}/../templates")
env = Environment(loader=file_loader, autoescape=True)
template = env.get_template("graphql_default_query.j2")
query = template.render(query=self.gql_query, filters=self.filters)
base_query = {
"devices": {
"name": None,
"platform": "napalm_driver",
"status": "name",
"primary_ip4": "address",
FragmentedPacket marked this conversation as resolved.
Show resolved Hide resolved
"device_role": "name",
"site": "name",

}
}
base_query["devices"].update(self.gql_query)
base_query["devices"]["filters"] = self.filters
query = convert_to_graphql_string(base_query)
data = {"query": "query {%s}" % query}

try:
Expand All @@ -272,6 +283,7 @@ def main(self):
# Need to return mock response data that is empty to prevent any failures downstream
return {"results": [], "next": None}
else:
self.display.display(f"{e.code}", color="red")
self.display.display(
"Something went wrong while executing the query.\nReason: {reason}".format(
reason=json.loads(e.fp.read().decode())["errors"][0]["message"],
Expand All @@ -295,7 +307,8 @@ def main(self):
self.inventory.add_host(device["name"])
self.add_ipv4_address(device)
self.add_ansible_platform(device)
self.populate_variables(device)
if self.variables:
self.populate_variables(device)
self.create_groups(device)

def parse(self, inventory, loader, path, cache=True):
Expand Down
5 changes: 0 additions & 5 deletions plugins/templates/graphql_additional_query.j2

This file was deleted.

19 changes: 0 additions & 19 deletions plugins/templates/graphql_default_query.j2

This file was deleted.

1 change: 0 additions & 1 deletion plugins/templates/graphql_filters.j2

This file was deleted.