mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Restconf HTTPAPI plugin and modules (#49476)
* Initial code for restconf support * Add restconf httpapi plugin * Add restonf_get module * Fix some ConnectionError usage
This commit is contained in:
parent
57349c0611
commit
d14f16e31b
6 changed files with 248 additions and 0 deletions
0
lib/ansible/module_utils/network/restconf/__init__.py
Normal file
0
lib/ansible/module_utils/network/restconf/__init__.py
Normal file
57
lib/ansible/module_utils/network/restconf/restconf.py
Normal file
57
lib/ansible/module_utils/network/restconf/restconf.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
# This code is part of Ansible, but is an independent component.
|
||||
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||
# still belong to the author of the module, and may assign their own license
|
||||
# to the complete work.
|
||||
#
|
||||
# (c) 2018 Red Hat Inc.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
|
||||
from ansible.module_utils.connection import Connection
|
||||
|
||||
|
||||
def get(module, path=None, content=None, fields=None, output='json'):
|
||||
if path is None:
|
||||
raise ValueError('path value must be provided')
|
||||
if content:
|
||||
path += '?' + 'content=%s' % content
|
||||
if fields:
|
||||
path += '?' + 'field=%s' % fields
|
||||
|
||||
accept = None
|
||||
if output == 'xml':
|
||||
accept = 'application/yang.data+xml'
|
||||
|
||||
connection = Connection(module._socket_path)
|
||||
return connection.send_request(None, path=path, method='GET', accept=accept)
|
||||
|
||||
|
||||
def edit_config(module, path=None, content=None, method='GET', format='json'):
|
||||
if path is None:
|
||||
raise ValueError('path value must be provided')
|
||||
|
||||
content_type = None
|
||||
if format == 'xml':
|
||||
content_type = 'application/yang.data+xml'
|
||||
|
||||
connection = Connection(module._socket_path)
|
||||
return connection.send_request(content, path=path, method=method, content_type=content_type)
|
0
lib/ansible/modules/network/restconf/__init__.py
Normal file
0
lib/ansible/modules/network/restconf/__init__.py
Normal file
110
lib/ansible/modules/network/restconf/restconf_get.py
Normal file
110
lib/ansible/modules/network/restconf/restconf_get.py
Normal file
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/python
|
||||
# Copyright: Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'network'}
|
||||
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: restconf_get
|
||||
version_added: "2.8"
|
||||
author: "Ganesh Nalawade (@ganeshrn)"
|
||||
short_description: Fetch configuration/state data from RESTCONF enabled devices.
|
||||
description:
|
||||
- RESTCONF is a standard mechanisms to allow web applications to access the
|
||||
configuration data and state data developed and standardized by
|
||||
the IETF. It is documented in RFC 8040.
|
||||
- This module allows the user to fetch configuration and state data from RESTCONF
|
||||
enabled devices.
|
||||
options:
|
||||
path:
|
||||
description:
|
||||
- URI being used to execute API calls.
|
||||
required: true
|
||||
content:
|
||||
description:
|
||||
- The C(content) is a query parameter that controls how descendant nodes of the
|
||||
requested data nodes in C(path) will be processed in the reply. If value is
|
||||
I(config) return only configuration descendant data nodes of value in C(path).
|
||||
If value is I(nonconfig) return only non-configuration descendant data nodes
|
||||
of value in C(path). If value is I(all) return all descendant data nodes of
|
||||
value in C(path)
|
||||
required: false
|
||||
choices: ['config', 'nonconfig', 'all']
|
||||
output:
|
||||
description:
|
||||
- The output of response received.
|
||||
required: false
|
||||
default: json
|
||||
choices: ['json', 'xml']
|
||||
"""
|
||||
|
||||
EXAMPLES = """
|
||||
- name: get l3vpn services
|
||||
restconf_get:
|
||||
path: /config/ietf-l3vpn-svc:l3vpn-svc/vpn-services
|
||||
"""
|
||||
|
||||
RETURN = """
|
||||
response:
|
||||
description: A dictionary representing a JSON-formatted response
|
||||
returned: when the device response is valid JSON
|
||||
type: dict
|
||||
sample: |
|
||||
{
|
||||
"vpn-services": {
|
||||
"vpn-service": [
|
||||
{
|
||||
"customer-name": "red",
|
||||
"vpn-id": "blue_vpn1",
|
||||
"vpn-service-topology": "ietf-l3vpn-svc:any-to-any"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
from ansible.module_utils._text import to_text
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.connection import ConnectionError
|
||||
from ansible.module_utils.network.restconf import restconf
|
||||
|
||||
|
||||
def main():
|
||||
"""entry point for module execution
|
||||
"""
|
||||
argument_spec = dict(
|
||||
path=dict(required=True),
|
||||
content=dict(choices=['config', 'nonconfig', 'all']),
|
||||
output=dict(choices=['json', 'xml'], default='json'),
|
||||
)
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=argument_spec,
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
result = {'changed': False}
|
||||
|
||||
try:
|
||||
response = restconf.get(module, **module.params)
|
||||
except ConnectionError as exc:
|
||||
module.fail_json(msg=to_text(exc), code=exc.code)
|
||||
|
||||
result.update({
|
||||
'response': response,
|
||||
})
|
||||
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -288,4 +288,6 @@ class Connection(NetworkConnectionBase):
|
|||
# Try to assign a new auth token if one is given
|
||||
self._auth = self.update_auth(response, response_buffer) or self._auth
|
||||
|
||||
response_buffer.seek(0)
|
||||
|
||||
return response, response_buffer
|
||||
|
|
79
lib/ansible/plugins/httpapi/restconf.py
Normal file
79
lib/ansible/plugins/httpapi/restconf.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
# Copyright (c) 2018 Cisco and/or its affiliates.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
author: Ansible Networking Team
|
||||
httpapi: restconf
|
||||
short_description: HttpApi Plugin for devices supporting Restconf API
|
||||
description:
|
||||
- This HttpApi plugin provides methods to connect to Restconf API
|
||||
endpoints.
|
||||
version_added: "2.8"
|
||||
options:
|
||||
root_path:
|
||||
type: str
|
||||
description:
|
||||
- Specifies the location of the Restconf root.
|
||||
default: '/restconf'
|
||||
vars:
|
||||
- name: ansible_httpapi_restconf_root
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from ansible.module_utils.network.common.utils import to_list
|
||||
from ansible.module_utils.connection import ConnectionError
|
||||
from ansible.plugins.httpapi import HttpApiBase
|
||||
|
||||
|
||||
CONTENT_TYPE = 'application/yang.data+json'
|
||||
|
||||
|
||||
class HttpApi(HttpApiBase):
|
||||
def send_request(self, data, **message_kwargs):
|
||||
if data:
|
||||
data = json.dumps(data)
|
||||
|
||||
path = self.get_option('root_path') + message_kwargs.get('path', '')
|
||||
|
||||
headers = {
|
||||
'Content-Type': message_kwargs.get('content_type') or CONTENT_TYPE,
|
||||
'Accept': message_kwargs.get('accept') or CONTENT_TYPE,
|
||||
}
|
||||
response, response_data = self.connection.send(path, data, headers=headers, method=message_kwargs.get('method'))
|
||||
|
||||
return handle_response(response_data.read())
|
||||
|
||||
|
||||
def handle_response(response):
|
||||
if 'error' in response and 'jsonrpc' not in response:
|
||||
error = response['error']
|
||||
|
||||
error_text = []
|
||||
for data in error['data']:
|
||||
error_text.extend(data.get('errors', []))
|
||||
error_text = '\n'.join(error_text) or error['message']
|
||||
|
||||
raise ConnectionError(error_text, code=error['code'])
|
||||
|
||||
return response
|
Loading…
Reference in a new issue