build v_1.0.1
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"""REST client for the Neutron API, replacing the `openstack` CLI subprocess
|
||||
wrapper (`openstack_client.py`, removed).
|
||||
|
||||
Mirrors `SprutClient`'s shape: a single `requests.Session`, token/base URL
|
||||
fixed at construction, `.get()`/`.post()` raising a clear error and exiting
|
||||
on a non-2xx response.
|
||||
|
||||
Every list function below does exactly one GET and returns the collection
|
||||
unwrapped -- no per-item `show` call. Unlike the `openstack` CLI's table
|
||||
output (which truncates to a handful of display columns unless `--long` is
|
||||
given), Neutron's list endpoints return the full attribute set for every
|
||||
item by default, so the per-item `show` calls the CLI-based version needed
|
||||
are redundant here.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class NeutronClient:
|
||||
def __init__(self, base_url: str, token: str, timeout: float = 30.0):
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.timeout = timeout
|
||||
self._session = requests.Session()
|
||||
self._session.headers.update({"X-Auth-Token": token})
|
||||
|
||||
def call(self, method: str, path: str, data: dict | None = None) -> Any:
|
||||
url = f"{self.base_url}{path}"
|
||||
try:
|
||||
response = self._session.request(
|
||||
method, url, json=data if data is not None else None, timeout=self.timeout
|
||||
)
|
||||
except requests.exceptions.RequestException as exc:
|
||||
print(f"Error: Neutron API {method} {url} failed: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not 200 <= response.status_code < 300:
|
||||
print(f"Error: Neutron API {method} {url} returned HTTP {response.status_code}", file=sys.stderr)
|
||||
print(f"Response: {response.text}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not response.text:
|
||||
return {}
|
||||
return response.json()
|
||||
|
||||
def get(self, path: str) -> Any:
|
||||
return self.call("GET", path)
|
||||
|
||||
def get_absolute(self, url: str) -> Any:
|
||||
"""Like get(), but for an already-absolute URL (pagination "next"
|
||||
links come back as full URLs, not paths relative to base_url)."""
|
||||
try:
|
||||
response = self._session.get(url, timeout=self.timeout)
|
||||
except requests.exceptions.RequestException as exc:
|
||||
print(f"Error: Neutron API GET {url} failed: {exc}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not 200 <= response.status_code < 300:
|
||||
print(f"Error: Neutron API GET {url} returned HTTP {response.status_code}", file=sys.stderr)
|
||||
print(f"Response: {response.text}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return response.json() if response.text else {}
|
||||
|
||||
def list_all(self, path: str, collection_key: str) -> list[dict]:
|
||||
"""GETs a collection and follows Neutron's pagination "next" links
|
||||
(`<collection_key>_links` with `rel: next`) until exhausted. Plain
|
||||
deployments without pagination enabled just return one page with no
|
||||
`_links` key, so this is a no-op there."""
|
||||
items: list[dict] = []
|
||||
body = self.get(path)
|
||||
while True:
|
||||
items.extend(body.get(collection_key, []))
|
||||
links = body.get(f"{collection_key}_links", [])
|
||||
next_url = next((link["href"] for link in links if link.get("rel") == "next"), None)
|
||||
if not next_url:
|
||||
return items
|
||||
body = self.get_absolute(next_url)
|
||||
|
||||
|
||||
def router_list(client: NeutronClient) -> list[dict]:
|
||||
return client.list_all("/v2.0/routers", "routers")
|
||||
|
||||
|
||||
def subnet_list(client: NeutronClient) -> list[dict]:
|
||||
return client.list_all("/v2.0/subnets", "subnets")
|
||||
|
||||
|
||||
def ike_policy_list(client: NeutronClient) -> list[dict]:
|
||||
return client.list_all("/v2.0/vpn/ikepolicies", "ikepolicies")
|
||||
|
||||
|
||||
def ipsec_policy_list(client: NeutronClient) -> list[dict]:
|
||||
return client.list_all("/v2.0/vpn/ipsecpolicies", "ipsecpolicies")
|
||||
|
||||
|
||||
def endpoint_group_list(client: NeutronClient) -> list[dict]:
|
||||
return client.list_all("/v2.0/vpn/endpoint-groups", "endpoint_groups")
|
||||
|
||||
|
||||
def vpn_service_list(client: NeutronClient) -> list[dict]:
|
||||
return client.list_all("/v2.0/vpn/vpnservices", "vpnservices")
|
||||
|
||||
|
||||
def ipsec_site_connection_list(client: NeutronClient) -> list[dict]:
|
||||
return client.list_all("/v2.0/vpn/ipsec-site-connections", "ipsec_site_connections")
|
||||
Reference in New Issue
Block a user