mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-25 11:51:26 -07:00
cisco firepower : Make API endpoints configurable via hostvars (#44952)
* httpapi host vars * Make configurable end-points for firepower * pep8 fix
This commit is contained in:
parent
276ad32a45
commit
50c7702e46
3 changed files with 56 additions and 13 deletions
|
@ -104,7 +104,7 @@ DEFAULT_REMOTE_PASS = None
|
||||||
DEFAULT_SUBSET = None
|
DEFAULT_SUBSET = None
|
||||||
DEFAULT_SU_PASS = None
|
DEFAULT_SU_PASS = None
|
||||||
# FIXME: expand to other plugins, but never doc fragments
|
# FIXME: expand to other plugins, but never doc fragments
|
||||||
CONFIGURABLE_PLUGINS = ('cache', 'callback', 'connection', 'inventory', 'lookup', 'shell', 'cliconf')
|
CONFIGURABLE_PLUGINS = ('cache', 'callback', 'connection', 'inventory', 'lookup', 'shell', 'cliconf', 'httpapi')
|
||||||
# NOTE: always update the docs/docsite/Makefile to match
|
# NOTE: always update the docs/docsite/Makefile to match
|
||||||
DOCUMENTABLE_PLUGINS = CONFIGURABLE_PLUGINS + ('module', 'strategy', 'vars')
|
DOCUMENTABLE_PLUGINS = CONFIGURABLE_PLUGINS + ('module', 'strategy', 'vars')
|
||||||
IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES") # ignore during module search
|
IGNORE_FILES = ("COPYING", "CONTRIBUTING", "LICENSE", "README", "VERSION", "GUIDELINES") # ignore during module search
|
||||||
|
|
|
@ -20,6 +20,33 @@ from __future__ import (absolute_import, division, print_function)
|
||||||
|
|
||||||
__metaclass__ = type
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = """
|
||||||
|
---
|
||||||
|
author: Ansible Networking Team
|
||||||
|
httpapi : ftd
|
||||||
|
short_description: HttpApi Plugin for Cisco ASA Firepower device
|
||||||
|
description:
|
||||||
|
- This HttpApi plugin provides methods to connect to Cisco ASA firepower
|
||||||
|
devices over a HTTP(S)-based api.
|
||||||
|
version_added: "2.7"
|
||||||
|
options:
|
||||||
|
token_path:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- Specifies the api token path of the FTD device
|
||||||
|
default: '/api/fdm/v2/fdm/token'
|
||||||
|
vars:
|
||||||
|
- name: ansible_httpapi_ftd_token_path
|
||||||
|
|
||||||
|
spec_path:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- Specifies the api spec path of the FTD device
|
||||||
|
default: '/apispec/ngfw.json'
|
||||||
|
vars:
|
||||||
|
- name: ansible_httpapi_ftd_spec_path
|
||||||
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
@ -39,9 +66,6 @@ BASE_HEADERS = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json'
|
'Accept': 'application/json'
|
||||||
}
|
}
|
||||||
API_TOKEN_PATH_ENV_VAR = 'FTD_API_TOKEN_PATH'
|
|
||||||
DEFAULT_API_TOKEN_PATH = '/api/fdm/v2/fdm/token'
|
|
||||||
API_SPEC_PATH = '/apispec/ngfw.json'
|
|
||||||
|
|
||||||
TOKEN_EXPIRATION_STATUS_CODE = 408
|
TOKEN_EXPIRATION_STATUS_CODE = 408
|
||||||
UNAUTHORIZED_STATUS_CODE = 401
|
UNAUTHORIZED_STATUS_CODE = 401
|
||||||
|
@ -49,6 +73,7 @@ UNAUTHORIZED_STATUS_CODE = 401
|
||||||
|
|
||||||
class HttpApi(HttpApiBase):
|
class HttpApi(HttpApiBase):
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
|
super(HttpApi, self).__init__(connection)
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
self.access_token = None
|
self.access_token = None
|
||||||
self.refresh_token = None
|
self.refresh_token = None
|
||||||
|
@ -168,9 +193,11 @@ class HttpApi(HttpApiBase):
|
||||||
headers['Authorization'] = 'Bearer %s' % self.access_token
|
headers['Authorization'] = 'Bearer %s' % self.access_token
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
@staticmethod
|
def _get_api_spec_path(self):
|
||||||
def _get_api_token_path():
|
return self.get_option('spec_path')
|
||||||
return os.environ.get(API_TOKEN_PATH_ENV_VAR, DEFAULT_API_TOKEN_PATH)
|
|
||||||
|
def _get_api_token_path(self):
|
||||||
|
return self.get_option('token_path')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _response_to_json(response_data):
|
def _response_to_json(response_data):
|
||||||
|
@ -199,7 +226,8 @@ class HttpApi(HttpApiBase):
|
||||||
@property
|
@property
|
||||||
def api_spec(self):
|
def api_spec(self):
|
||||||
if self._api_spec is None:
|
if self._api_spec is None:
|
||||||
response = self.send_request(url_path=API_SPEC_PATH, http_method=HTTPMethod.GET)
|
spec_path_url = self._get_api_spec_path()
|
||||||
|
response = self.send_request(url_path=spec_path_url, http_method=HTTPMethod.GET)
|
||||||
if response[ResponseParams.SUCCESS]:
|
if response[ResponseParams.SUCCESS]:
|
||||||
self._api_spec = FdmSwaggerParser().parse_spec(response[ResponseParams.RESPONSE])
|
self._api_spec = FdmSwaggerParser().parse_spec(response[ResponseParams.RESPONSE])
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -29,7 +29,7 @@ from ansible.module_utils.connection import ConnectionError
|
||||||
from ansible.module_utils.network.ftd.common import HTTPMethod, ResponseParams
|
from ansible.module_utils.network.ftd.common import HTTPMethod, ResponseParams
|
||||||
from ansible.module_utils.network.ftd.fdm_swagger_client import SpecProp, FdmSwaggerParser
|
from ansible.module_utils.network.ftd.fdm_swagger_client import SpecProp, FdmSwaggerParser
|
||||||
from ansible.module_utils.six import BytesIO, PY3, StringIO
|
from ansible.module_utils.six import BytesIO, PY3, StringIO
|
||||||
from ansible.plugins.httpapi.ftd import HttpApi, API_TOKEN_PATH_ENV_VAR
|
from ansible.plugins.httpapi.ftd import HttpApi
|
||||||
|
|
||||||
if PY3:
|
if PY3:
|
||||||
BUILTINS_NAME = 'builtins'
|
BUILTINS_NAME = 'builtins'
|
||||||
|
@ -37,12 +37,25 @@ else:
|
||||||
BUILTINS_NAME = '__builtin__'
|
BUILTINS_NAME = '__builtin__'
|
||||||
|
|
||||||
|
|
||||||
|
class FakeFtdHttpApiPlugin(HttpApi):
|
||||||
|
def __init__(self, conn):
|
||||||
|
super(FakeFtdHttpApiPlugin, self).__init__(conn)
|
||||||
|
self.hostvars = {
|
||||||
|
'token_path': '/testLoginUrl',
|
||||||
|
'spec_path': '/testSpecUrl'
|
||||||
|
}
|
||||||
|
|
||||||
|
def get_option(self, var):
|
||||||
|
return self.hostvars[var]
|
||||||
|
|
||||||
|
|
||||||
class TestFtdHttpApi(unittest.TestCase):
|
class TestFtdHttpApi(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.connection_mock = mock.Mock()
|
self.connection_mock = mock.Mock()
|
||||||
self.ftd_plugin = HttpApi(self.connection_mock)
|
self.ftd_plugin = FakeFtdHttpApiPlugin(self.connection_mock)
|
||||||
self.ftd_plugin.access_token = 'ACCESS_TOKEN'
|
self.ftd_plugin.access_token = 'ACCESS_TOKEN'
|
||||||
|
self.ftd_plugin._load_name = 'httpapi'
|
||||||
|
|
||||||
def test_login_should_request_tokens_when_no_refresh_token(self):
|
def test_login_should_request_tokens_when_no_refresh_token(self):
|
||||||
self.connection_mock.send.return_value = self._connection_response(
|
self.connection_mock.send.return_value = self._connection_response(
|
||||||
|
@ -69,15 +82,17 @@ class TestFtdHttpApi(unittest.TestCase):
|
||||||
expected_body = json.dumps({'grant_type': 'refresh_token', 'refresh_token': 'REFRESH_TOKEN'})
|
expected_body = json.dumps({'grant_type': 'refresh_token', 'refresh_token': 'REFRESH_TOKEN'})
|
||||||
self.connection_mock.send.assert_called_once_with(mock.ANY, expected_body, headers=mock.ANY, method=mock.ANY)
|
self.connection_mock.send.assert_called_once_with(mock.ANY, expected_body, headers=mock.ANY, method=mock.ANY)
|
||||||
|
|
||||||
@patch.dict(os.environ, {API_TOKEN_PATH_ENV_VAR: '/testLoginUrl'})
|
def test_login_should_use_host_variable_when_set(self):
|
||||||
def test_login_should_use_env_variable_when_set(self):
|
temp_token_path = self.ftd_plugin.hostvars['token_path']
|
||||||
|
self.ftd_plugin.hostvars['token_path'] = '/testFakeLoginUrl'
|
||||||
self.connection_mock.send.return_value = self._connection_response(
|
self.connection_mock.send.return_value = self._connection_response(
|
||||||
{'access_token': 'ACCESS_TOKEN', 'refresh_token': 'REFRESH_TOKEN'}
|
{'access_token': 'ACCESS_TOKEN', 'refresh_token': 'REFRESH_TOKEN'}
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ftd_plugin.login('foo', 'bar')
|
self.ftd_plugin.login('foo', 'bar')
|
||||||
|
|
||||||
self.connection_mock.send.assert_called_once_with('/testLoginUrl', mock.ANY, headers=mock.ANY, method=mock.ANY)
|
self.connection_mock.send.assert_called_once_with('/testFakeLoginUrl', mock.ANY, headers=mock.ANY, method=mock.ANY)
|
||||||
|
self.ftd_plugin.hostvars['token_path'] = temp_token_path
|
||||||
|
|
||||||
def test_login_raises_exception_when_no_refresh_token_and_no_credentials(self):
|
def test_login_raises_exception_when_no_refresh_token_and_no_credentials(self):
|
||||||
with self.assertRaises(AnsibleConnectionFailure) as res:
|
with self.assertRaises(AnsibleConnectionFailure) as res:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue