diff options
| author | omnom62 <omnom62@outlook.com> | 2026-05-06 08:08:27 +1000 |
|---|---|---|
| committer | omnom62 <omnom62@outlook.com> | 2026-05-06 08:08:27 +1000 |
| commit | 9a3fa5777e7203d46faf1185cfb56c4fc121d885 (patch) | |
| tree | 80988400ba3582cfc97ff6445fb8f97c724d3fc6 /plugins/httpapi | |
| parent | c2493986714604aa992bea28628b74587b3b51bd (diff) | |
| download | rest.vyos-9a3fa5777e7203d46faf1185cfb56c4fc121d885.tar.gz rest.vyos-9a3fa5777e7203d46faf1185cfb56c4fc121d885.zip | |
More modules
Diffstat (limited to 'plugins/httpapi')
| -rw-r--r-- | plugins/httpapi/vyos.py | 216 |
1 files changed, 154 insertions, 62 deletions
diff --git a/plugins/httpapi/vyos.py b/plugins/httpapi/vyos.py index 9340261..1719254 100644 --- a/plugins/httpapi/vyos.py +++ b/plugins/httpapi/vyos.py @@ -1,81 +1,173 @@ -import json +#!/usr/bin/python +# -*- coding: utf-8 -*- +# GNU General Public License v3.0+ -from urllib.parse import urlencode +from __future__ import absolute_import, division, print_function -from ansible.plugins.httpapi import HttpApiBase +__metaclass__ = type DOCUMENTATION = r""" --- -httpapi: vyos -short_description: VyOS REST API +name: vyos +short_description: HttpApi plugin for VyOS REST API description: - - HTTPAPI plugin for interacting with VyOS REST API. -author: Evgeny Molotkov (@eomnom62) + - This HttpApi plugin provides methods to connect to VyOS devices via their + HTTPS REST API. + - Use with C(ansible_connection=ansible.netcommon.httpapi) and + C(ansible_network_os=vyos.rest.vyos). + - The VyOS REST API must be enabled with + C(set service https api keys id ansible key YOUR_KEY), + C(set service https api rest), then C(commit && save). +version_added: "1.0.0" +author: + - VyOS Community (@vyos) options: api_key: - description: VyOS API key type: str + description: + - The API key configured on the VyOS device. + - Set C(ansible_httpapi_api_key) in inventory or the C(VYOS_API_KEY) + environment variable. + env: + - name: VYOS_API_KEY vars: - name: ansible_httpapi_api_key - env: - - name: ANSIBLE_HTTPAPI_API_KEY - ini: - - section: httpapi - key: api_key + - name: ansible_vyos_api_key """ +import json +import traceback -class HttpApi(HttpApiBase): - - def set_options(self, task_keys=None, var_options=None, direct=None): - super().set_options( - task_keys=task_keys, - var_options=var_options, - direct=direct, - ) - - def send_request(self, path, data=None, method="POST", headers=None): - - api_key = self.get_option("api_key") - - if not api_key: - # fallback to inventory vars - api_key = self._play_context.vars.get("ansible_httpapi_api_key") - - if not api_key: - raise ValueError("api_key is required") - - form_data = {} - - if data is not None: - if not isinstance(data, str): - data = json.dumps(data) - form_data["data"] = data - - form_data["key"] = api_key - - body = urlencode(form_data).encode("utf-8") - - if headers is None: - headers = {} - - headers["Content-Type"] = "application/x-www-form-urlencoded" - - status, response = self.connection.send( - path, - body, - method=method, - headers=headers, - ) - if hasattr(response, "read"): - response = response.read() +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode - if isinstance(response, bytes): - response = response.decode("utf-8") +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import ConnectionError +from ansible.plugins.httpapi import HttpApiBase - if isinstance(response, str): - response = json.loads(response) - return response +class HttpApi(HttpApiBase): + """HttpApi plugin for the VyOS HTTPS REST API.""" + + def login(self, username, password): + """VyOS uses a static API key — no login endpoint needed.""" + pass + + def logout(self): + pass + + def update_auth(self, response, response_text): + return None + + def handle_httperror(self, exc): + if exc.code == 401: + raise AnsibleConnectionFailure( + "VyOS API returned HTTP 401 Unauthorized. " + "Check ansible_httpapi_api_key is correct and that " + "'set service https api rest' is configured on the device.", + ) + return exc + + def _get_api_key(self): + """Read the API key — option, env var, or fail clearly.""" + try: + key = self.get_option("api_key") + except Exception: + key = None + if not key: + import os + + key = os.environ.get("VYOS_API_KEY", "") + if not key: + raise ConnectionError( + "No VyOS API key found. Set ansible_httpapi_api_key in " + "inventory or export VYOS_API_KEY=<key>.", + ) + return key + + def send_request(self, endpoint, **payload): + """POST to a VyOS REST endpoint. + + Args: + endpoint (str): API path, e.g. '/configure' or '/retrieve'. + Named 'endpoint' not 'path' to avoid collision + with the VyOS payload field also called 'path'. + **payload: VyOS API fields: op, path, value, url, file, etc. + + Returns: + dict: Parsed JSON response from VyOS. + + Raises: + ConnectionError: on HTTP error or VyOS success=false response. + """ + try: + api_key = self._get_api_key() + body = json.dumps(payload) + form_data = urlencode({"data": body, "key": api_key}) + + response, response_data = self.connection.send( + endpoint, + data=form_data, + method="POST", + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + + raw = to_text(response_data.getvalue()) + + try: + result = json.loads(raw) + except ValueError: + raise ConnectionError( + "VyOS API at {ep} returned non-JSON ({code}): {raw}".format( + ep=endpoint, + code=getattr(response, "status", "?"), + raw=raw[:300], + ), + ) + + if not result.get("success"): + raise ConnectionError( + "VyOS API error [{ep}]: {err}".format( + ep=endpoint, + err=result.get("error") or "success=false", + ), + ) + + return result + + except (ConnectionError, AnsibleConnectionFailure): + raise + except Exception as exc: + raise ConnectionError( + "{exc_type} in send_request({ep}): {exc}\n{tb}".format( + exc_type=type(exc).__name__, + ep=endpoint, + exc=to_text(exc), + tb=traceback.format_exc(), + ), + ) + + def get_info(self): + """GET /info — the one unauthenticated endpoint.""" + try: + response, response_data = self.connection.send( + "/info", + data=None, + method="GET", + ) + return json.loads(to_text(response_data.getvalue())) + except (ConnectionError, AnsibleConnectionFailure): + raise + except Exception as exc: + raise ConnectionError( + "{exc_type} in get_info(): {exc}\n{tb}".format( + exc_type=type(exc).__name__, + exc=to_text(exc), + tb=traceback.format_exc(), + ), + ) |
