Skip to content

Commit 333b6d5

Browse files
feat(nautobot-target-proxy): Mechanical move of nautobot-target-proxy in to understack repo
1 parent 4318eef commit 333b6d5

8 files changed

Lines changed: 2000 additions & 0 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM python:3.12.13-alpine AS builder
2+
RUN pip install --upgrade poetry poetry-plugin-export
3+
COPY pyproject.toml poetry.lock /
4+
RUN poetry export --only main -o /tmp/requirements.txt --without-hashes
5+
6+
# App
7+
FROM python:3.12.13-alpine AS app
8+
9+
RUN pip3 install -U pip setuptools wheel
10+
11+
# Copy Python dependencies from the builder stage
12+
COPY --from=builder /tmp/requirements.txt /tmp/requirements.txt
13+
14+
# Install Poetry dependencies
15+
RUN pip3 install -r /tmp/requirements.txt
16+
17+
COPY src /src
18+
WORKDIR /src
19+
EXPOSE 8000
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A FastAPI application to query Nautobots GraphQL API to scrape targets for use with Prometheus `http_sd_config`.

python/nautobot_target_proxy/poetry.lock

Lines changed: 1797 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[tool.poetry]
2+
name = "Nautobot API Scraper"
3+
description = "Simple API to scrape targets from Nautobot for use with other systems"
4+
version = "0.0.1"
5+
authors = ["Project Undercloud Developers"]
6+
license = "MIT"
7+
readme = "README.md"
8+
9+
[tool.poetry.dependencies]
10+
python = "^3.12"
11+
12+
# App requirements
13+
requests = "2.32.5"
14+
fastapi = {version = "0.123.10", extras = ["standard"]}
15+
16+
# test depends
17+
coverage = { version = "*", optional = true }
18+
19+
[tool.poetry.extras]
20+
test = ["coverage"]
21+
22+
[tool.ruff]
23+
line-length = 80
24+
target-version = "py312"
25+
fix = true
26+
27+
[tool.ruff.lint]
28+
select = [
29+
"E", # pycodestyle (error)
30+
"F", # pyflakes
31+
"B", # flake8-bugbear
32+
"I", # isort
33+
"UP", # pyupgrade
34+
"ASYNC", # flake8-async
35+
"ISC", # flake8-implicit-str-concat
36+
"PTH", # flake8-use-pathlib
37+
"RUF100", # Unused noqa comments
38+
"PGH004" # blanket noqa comments
39+
]
40+
41+
ignore = [
42+
"ISC001", # avoid formatter issues
43+
]
44+
45+
[tool.ruff.lint.isort]
46+
force-single-line = true
47+
48+
[build-system]
49+
requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
50+
build-backend = "poetry_dynamic_versioning.backend"
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
An API to obtain data from Nautobot, and format it for use in various external
3+
systems
4+
"""
5+
6+
# Standard Library
7+
8+
# Third Party
9+
from fastapi import FastAPI
10+
from fastapi import Request
11+
from fastapi.responses import JSONResponse
12+
13+
# First Party
14+
from helpers.graphql import query_nautobot_graphql
15+
from helpers.queries import OOB_TARGET_QUERY
16+
from helpers.schemas import TargetResponse
17+
18+
app = FastAPI()
19+
20+
21+
@app.exception_handler(Exception)
22+
async def validation_exception_handler(request: Request, exc: Exception):
23+
"""
24+
Generic catchall for exception handling
25+
"""
26+
return JSONResponse(
27+
status_code=500,
28+
content={
29+
"error": True,
30+
"path": str(request.url),
31+
"detail": f"Exception message is {exc!r}.",
32+
},
33+
)
34+
35+
36+
@app.get("/targets/oob")
37+
def get_oob_targets() -> list[TargetResponse]:
38+
"""
39+
Obtains a list of targets to monitor from Nautobot, and returns them in a
40+
format that can be read by the Prometheus `http_sd_config` method.
41+
"""
42+
response = query_nautobot_graphql(OOB_TARGET_QUERY).json()
43+
res = []
44+
for interface in response["data"]["interfaces"]:
45+
for device in interface["ip_addresses"]:
46+
urn = interface["device"].get("cpf_urn")
47+
# Only return devices which contain a urn.
48+
if not urn:
49+
continue
50+
device_name = interface["device"]["name"]
51+
device_uuid = interface["device"]["id"]
52+
location = interface["device"]["location"]["name"]
53+
rack = interface["device"]["rack"]["name"]
54+
res.append(
55+
{
56+
"targets": [device["host"]],
57+
"labels": {
58+
"device_name": device_name,
59+
"uuid": device_uuid,
60+
"location": location,
61+
"rack": rack,
62+
"urn": urn,
63+
},
64+
}
65+
)
66+
return res
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Standard Library
2+
import os
3+
4+
# Third Party
5+
import requests
6+
7+
8+
def query_nautobot_graphql(query):
9+
nautobot_token = os.environ.get("NAUTOBOT_TOKEN")
10+
if not nautobot_token:
11+
raise RuntimeError("A Nautobot Token is required")
12+
nautobot_url = os.environ.get("NAUTOBOT_URL")
13+
if not nautobot_url:
14+
raise RuntimeError("A Nautobot URL is required")
15+
headers = {
16+
"Authorization": f"Token {nautobot_token}",
17+
"Content-Type": "application/json",
18+
"Accept": "application/json",
19+
}
20+
res = requests.post(
21+
f"{nautobot_url}/api/graphql/", headers=headers, json={"query": query}
22+
)
23+
res.raise_for_status()
24+
return res
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
OOB_TARGET_QUERY = """
2+
{
3+
interfaces(mgmt_only: true, name: ["iDRAC", "iLO"]) {
4+
device {
5+
name
6+
rack {
7+
name
8+
}
9+
id
10+
cpf_urn
11+
location {
12+
name
13+
}
14+
}
15+
ip_addresses {
16+
host
17+
}
18+
}
19+
}
20+
"""
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Third Party
2+
from pydantic import BaseModel
3+
4+
5+
class TargetResponse(BaseModel):
6+
labels: dict
7+
targets: list
8+
9+
model_config = {
10+
"json_schema_extra": {
11+
"examples": [
12+
[
13+
{
14+
"labels": {
15+
"location": "<datacenter>",
16+
"name": "<dns_name>",
17+
},
18+
"targets": ["<host>"],
19+
}
20+
]
21+
]
22+
}
23+
}

0 commit comments

Comments
 (0)