mirror of https://github.com/ansible/ansible.git
Merge remote-tracking branch 'upstream/devel' into ec2_util_boto3
commit
f95652e7db
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,341 @@
|
||||
#!/bin/env python
|
||||
|
||||
'''
|
||||
nsot
|
||||
====
|
||||
|
||||
Ansible Dynamic Inventory to pull hosts from NSoT, a flexible CMDB by Dropbox
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Define host groups in form of NSoT device attribute criteria
|
||||
|
||||
* All parameters defined by the spec as of 2015-09-05 are supported.
|
||||
|
||||
+ ``--list``: Returns JSON hash of host groups -> hosts and top-level
|
||||
``_meta`` -> ``hostvars`` which correspond to all device attributes.
|
||||
|
||||
Group vars can be specified in the YAML configuration, noted below.
|
||||
|
||||
+ ``--host <hostname>``: Returns JSON hash where every item is a device
|
||||
attribute.
|
||||
|
||||
* In addition to all attributes assigned to resource being returned, script
|
||||
will also append ``site_id`` and ``id`` as facts to utilize.
|
||||
|
||||
|
||||
Confguration
|
||||
------------
|
||||
|
||||
Since it'd be annoying and failure prone to guess where you're configuration
|
||||
file is, use ``NSOT_INVENTORY_CONFIG`` to specify the path to it.
|
||||
|
||||
This file should adhere to the YAML spec. All top-level variable must be
|
||||
desired Ansible group-name hashed with single 'query' item to define the NSoT
|
||||
attribute query.
|
||||
|
||||
Queries follow the normal NSoT query syntax, `shown here`_
|
||||
|
||||
.. _shown here: https://github.com/dropbox/pynsot#set-queries
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
routers:
|
||||
query: 'deviceType=ROUTER'
|
||||
vars:
|
||||
a: b
|
||||
c: d
|
||||
|
||||
juniper_fw:
|
||||
query: 'deviceType=FIREWALL manufacturer=JUNIPER'
|
||||
|
||||
not_f10:
|
||||
query: '-manufacturer=FORCE10'
|
||||
|
||||
The inventory will automatically use your ``.pynsotrc`` like normal pynsot from
|
||||
cli would, so make sure that's configured appropriately.
|
||||
|
||||
.. note::
|
||||
|
||||
Attributes I'm showing above are influenced from ones that the Trigger
|
||||
project likes. As is the spirit of NSoT, use whichever attributes work best
|
||||
for your workflow.
|
||||
|
||||
If config file is blank or absent, the following default groups will be
|
||||
created:
|
||||
|
||||
* ``routers``: deviceType=ROUTER
|
||||
* ``switches``: deviceType=SWITCH
|
||||
* ``firewalls``: deviceType=FIREWALL
|
||||
|
||||
These are likely not useful for everyone so please use the configuration. :)
|
||||
|
||||
.. note::
|
||||
|
||||
By default, resources will only be returned for what your default
|
||||
site is set for in your ``~/.pynsotrc``.
|
||||
|
||||
If you want to specify, add an extra key under the group for ``site: n``.
|
||||
|
||||
Output Examples
|
||||
---------------
|
||||
|
||||
Here are some examples shown from just calling the command directly::
|
||||
|
||||
$ NSOT_INVENTORY_CONFIG=$PWD/test.yaml ansible_nsot --list | jq '.'
|
||||
{
|
||||
"routers": {
|
||||
"hosts": [
|
||||
"test1.example.com"
|
||||
],
|
||||
"vars": {
|
||||
"cool_level": "very",
|
||||
"group": "routers"
|
||||
}
|
||||
},
|
||||
"firewalls": {
|
||||
"hosts": [
|
||||
"test2.example.com"
|
||||
],
|
||||
"vars": {
|
||||
"cool_level": "enough",
|
||||
"group": "firewalls"
|
||||
}
|
||||
},
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
"test2.example.com": {
|
||||
"make": "SRX",
|
||||
"site_id": 1,
|
||||
"id": 108
|
||||
},
|
||||
"test1.example.com": {
|
||||
"make": "MX80",
|
||||
"site_id": 1,
|
||||
"id": 107
|
||||
}
|
||||
}
|
||||
},
|
||||
"rtr_and_fw": {
|
||||
"hosts": [
|
||||
"test1.example.com",
|
||||
"test2.example.com"
|
||||
],
|
||||
"vars": {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$ NSOT_INVENTORY_CONFIG=$PWD/test.yaml ansible_nsot --host test1 | jq '.'
|
||||
{
|
||||
"make": "MX80",
|
||||
"site_id": 1,
|
||||
"id": 107
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import os
|
||||
import pkg_resources
|
||||
import argparse
|
||||
import json
|
||||
import yaml
|
||||
from textwrap import dedent
|
||||
from pynsot.client import get_api_client
|
||||
from pynsot.app import HttpServerError
|
||||
from click.exceptions import UsageError
|
||||
|
||||
|
||||
def warning(*objs):
|
||||
print("WARNING: ", *objs, file=sys.stderr)
|
||||
|
||||
|
||||
class NSoTInventory(object):
|
||||
'''NSoT Client object for gather inventory'''
|
||||
|
||||
def __init__(self):
|
||||
self.config = dict()
|
||||
config_env = os.environ.get('NSOT_INVENTORY_CONFIG')
|
||||
if config_env:
|
||||
try:
|
||||
config_file = os.path.abspath(config_env)
|
||||
except IOError: # If file non-existent, use default config
|
||||
self._config_default()
|
||||
except Exception as e:
|
||||
sys.exit('%s\n' % e)
|
||||
|
||||
with open(config_file) as f:
|
||||
try:
|
||||
self.config.update(yaml.safe_load(f))
|
||||
except TypeError: # If empty file, use default config
|
||||
warning('Empty config file')
|
||||
self._config_default()
|
||||
except Exception as e:
|
||||
sys.exit('%s\n' % e)
|
||||
else: # Use defaults if env var missing
|
||||
self._config_default()
|
||||
self.groups = self.config.keys()
|
||||
self.client = get_api_client()
|
||||
self._meta = {'hostvars': dict()}
|
||||
|
||||
def _config_default(self):
|
||||
default_yaml = '''
|
||||
---
|
||||
routers:
|
||||
query: deviceType=ROUTER
|
||||
switches:
|
||||
query: deviceType=SWITCH
|
||||
firewalls:
|
||||
query: deviceType=FIREWALL
|
||||
'''
|
||||
self.config = yaml.safe_load(dedent(default_yaml))
|
||||
|
||||
def do_list(self):
|
||||
'''Direct callback for when ``--list`` is provided
|
||||
|
||||
Relies on the configuration generated from init to run
|
||||
_inventory_group()
|
||||
'''
|
||||
inventory = dict()
|
||||
for group, contents in self.config.iteritems():
|
||||
group_response = self._inventory_group(group, contents)
|
||||
inventory.update(group_response)
|
||||
inventory.update({'_meta': self._meta})
|
||||
return json.dumps(inventory)
|
||||
|
||||
def do_host(self, host):
|
||||
return json.dumps(self._hostvars(host))
|
||||
|
||||
def _hostvars(self, host):
|
||||
'''Return dictionary of all device attributes
|
||||
|
||||
Depending on number of devices in NSoT, could be rather slow since this
|
||||
has to request every device resource to filter through
|
||||
'''
|
||||
device = [i for i in self.client.devices.get()['data']['devices']
|
||||
if host in i['hostname']][0]
|
||||
attributes = device['attributes']
|
||||
attributes.update({'site_id': device['site_id'], 'id': device['id']})
|
||||
return attributes
|
||||
|
||||
def _inventory_group(self, group, contents):
|
||||
'''Takes a group and returns inventory for it as dict
|
||||
|
||||
:param group: Group name
|
||||
:type group: str
|
||||
:param contents: The contents of the group's YAML config
|
||||
:type contents: dict
|
||||
|
||||
contents param should look like::
|
||||
|
||||
{
|
||||
'query': 'xx',
|
||||
'vars':
|
||||
'a': 'b'
|
||||
}
|
||||
|
||||
Will return something like::
|
||||
|
||||
{ group: {
|
||||
hosts: [],
|
||||
vars: {},
|
||||
}
|
||||
'''
|
||||
query = contents.get('query')
|
||||
hostvars = contents.get('vars', dict())
|
||||
site = contents.get('site', dict())
|
||||
obj = {group: dict()}
|
||||
obj[group]['hosts'] = []
|
||||
obj[group]['vars'] = hostvars
|
||||
try:
|
||||
assert isinstance(query, basestring)
|
||||
except:
|
||||
sys.exit('ERR: Group queries must be a single string\n'
|
||||
' Group: %s\n'
|
||||
' Query: %s\n' % (group, query)
|
||||
)
|
||||
try:
|
||||
if site:
|
||||
site = self.client.sites(site)
|
||||
devices = site.devices.query.get(query=query)
|
||||
else:
|
||||
devices = self.client.devices.query.get(query=query)
|
||||
except HttpServerError as e:
|
||||
if '500' in str(e.response):
|
||||
_site = 'Correct site id?'
|
||||
_attr = 'Queried attributes actually exist?'
|
||||
questions = _site + '\n' + _attr
|
||||
sys.exit('ERR: 500 from server.\n%s' % questions)
|
||||
else:
|
||||
raise
|
||||
except UsageError:
|
||||
sys.exit('ERR: Could not connect to server. Running?')
|
||||
|
||||
# Would do a list comprehension here, but would like to save code/time
|
||||
# and also acquire attributes in this step
|
||||
for host in devices['data']['devices']:
|
||||
# Iterate through each device that matches query, assign hostname
|
||||
# to the group's hosts array and then use this single iteration as
|
||||
# a chance to update self._meta which will be used in the final
|
||||
# return
|
||||
hostname = host['hostname']
|
||||
obj[group]['hosts'].append(hostname)
|
||||
attributes = host['attributes']
|
||||
attributes.update({'site_id': host['site_id'], 'id': host['id']})
|
||||
self._meta['hostvars'].update({hostname: attributes})
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def parse_args():
|
||||
desc = __doc__.splitlines()[4] # Just to avoid being redundant
|
||||
|
||||
# Establish parser with options and error out if no action provided
|
||||
parser = argparse.ArgumentParser(
|
||||
description=desc,
|
||||
conflict_handler='resolve',
|
||||
)
|
||||
|
||||
# Arguments
|
||||
#
|
||||
# Currently accepting (--list | -l) and (--host | -h)
|
||||
# These must not be allowed together
|
||||
parser.add_argument(
|
||||
'--list', '-l',
|
||||
help='Print JSON object containing hosts to STDOUT',
|
||||
action='store_true',
|
||||
dest='list_', # Avoiding syntax highlighting for list
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--host', '-h',
|
||||
help='Print JSON object containing hostvars for <host>',
|
||||
action='store',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.list_ and not args.host: # Require at least one option
|
||||
parser.exit(status=1, message='No action requested')
|
||||
|
||||
if args.list_ and args.host: # Do not allow multiple options
|
||||
parser.exit(status=1, message='Too many actions requested')
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
'''Set up argument handling and callback routing'''
|
||||
args = parse_args()
|
||||
client = NSoTInventory()
|
||||
|
||||
# Callback condition
|
||||
if args.list_:
|
||||
print(client.do_list())
|
||||
elif args.host:
|
||||
print(client.do_host(args.host))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -0,0 +1,22 @@
|
||||
---
|
||||
juniper_routers:
|
||||
query: 'deviceType=ROUTER manufacturer=JUNIPER'
|
||||
vars:
|
||||
group: juniper_routers
|
||||
netconf: true
|
||||
os: junos
|
||||
|
||||
cisco_asa:
|
||||
query: 'manufacturer=CISCO deviceType=FIREWALL'
|
||||
vars:
|
||||
group: cisco_asa
|
||||
routed_vpn: false
|
||||
stateful: true
|
||||
|
||||
old_cisco_asa:
|
||||
query: 'manufacturer=CISCO deviceType=FIREWALL -softwareVersion=8.3+'
|
||||
vars:
|
||||
old_nat: true
|
||||
|
||||
not_f10:
|
||||
query: '-manufacturer=FORCE10'
|
||||
@ -1,205 +0,0 @@
|
||||
{#
|
||||
basic/layout.html
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Master layout template for Sphinx themes.
|
||||
|
||||
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
#}
|
||||
{%- block doctype -%}
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
{%- endblock %}
|
||||
{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %}
|
||||
{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
|
||||
{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
|
||||
(sidebars != []) %}
|
||||
{%- set url_root = pathto('', 1) %}
|
||||
{# XXX necessary? #}
|
||||
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
|
||||
{%- if not embedded and docstitle %}
|
||||
{%- set titlesuffix = " — "|safe + docstitle|e %}
|
||||
{%- else %}
|
||||
{%- set titlesuffix = "" %}
|
||||
{%- endif %}
|
||||
|
||||
{%- macro relbar() %}
|
||||
<div class="related">
|
||||
<h3>{{ _('Navigation') }}</h3>
|
||||
<ul>
|
||||
{%- for rellink in rellinks %}
|
||||
<li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
|
||||
<a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
|
||||
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
|
||||
{%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
|
||||
{%- endfor %}
|
||||
{%- block rootrellink %}
|
||||
<li><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
|
||||
{%- endblock %}
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
|
||||
{%- endfor %}
|
||||
{%- block relbaritems %} {% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro sidebar() %}
|
||||
{%- if render_sidebar %}
|
||||
<div class="sphinxsidebar">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
{%- block sidebarlogo %}
|
||||
{%- if logo %}
|
||||
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
|
||||
</a></p>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- if sidebars != None %}
|
||||
{#- new style sidebar: explicitly include/exclude templates #}
|
||||
{%- for sidebartemplate in sidebars %}
|
||||
{%- include sidebartemplate %}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
{#- old style sidebars: using blocks -- should be deprecated #}
|
||||
{%- block sidebartoc %}
|
||||
{%- include "localtoc.html" %}
|
||||
{%- endblock %}
|
||||
{%- block sidebarrel %}
|
||||
{%- include "relations.html" %}
|
||||
{%- endblock %}
|
||||
{%- block sidebarsourcelink %}
|
||||
{%- include "sourcelink.html" %}
|
||||
{%- endblock %}
|
||||
{%- if customsidebar %}
|
||||
{%- include customsidebar %}
|
||||
{%- endif %}
|
||||
{%- block sidebarsearch %}
|
||||
{%- include "searchbox.html" %}
|
||||
{%- endblock %}
|
||||
{%- endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro script() %}
|
||||
<script type="text/javascript">
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: '{{ url_root }}',
|
||||
VERSION: '{{ release|e }}',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }}
|
||||
};
|
||||
</script>
|
||||
{%- for scriptfile in script_files %}
|
||||
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro css() %}
|
||||
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
|
||||
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
|
||||
{%- for cssfile in css_files %}
|
||||
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
|
||||
{{ metatags }}
|
||||
{%- block htmltitle %}
|
||||
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||
{%- endblock %}
|
||||
{{ css() }}
|
||||
{%- if not embedded %}
|
||||
{{ script() }}
|
||||
{%- if use_opensearch %}
|
||||
<link rel="search" type="application/opensearchdescription+xml"
|
||||
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
|
||||
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
|
||||
{%- endif %}
|
||||
{%- if favicon %}
|
||||
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- block linktags %}
|
||||
{%- if hasdoc('about') %}
|
||||
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('genindex') %}
|
||||
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('search') %}
|
||||
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('copyright') %}
|
||||
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
|
||||
{%- endif %}
|
||||
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
|
||||
{%- if parents %}
|
||||
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
|
||||
{%- endif %}
|
||||
{%- if prev %}
|
||||
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- block extrahead %} {% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{%- block header %}{% endblock %}
|
||||
|
||||
{%- block relbar1 %}{{ relbar() }}{% endblock %}
|
||||
|
||||
{%- block content %}
|
||||
{%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
|
||||
|
||||
<div class="document">
|
||||
{%- block document %}
|
||||
<div class="documentwrapper">
|
||||
{%- if render_sidebar %}
|
||||
<div class="bodywrapper">
|
||||
{%- endif %}
|
||||
<div class="body">
|
||||
{% block body %} {% endblock %}
|
||||
</div>
|
||||
{%- if render_sidebar %}
|
||||
</div>
|
||||
{%- endif %}
|
||||
</div>
|
||||
{%- endblock %}
|
||||
|
||||
{%- block sidebar2 %}{{ sidebar() }}{% endblock %}
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{%- endblock %}
|
||||
|
||||
{%- block relbar2 %}{{ relbar() }}{% endblock %}
|
||||
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
{%- if show_copyright %}
|
||||
{%- if hasdoc('copyright') %}
|
||||
{% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
|
||||
{%- else %}
|
||||
{% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- if last_updated %}
|
||||
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
|
||||
{%- endif %}
|
||||
{%- if show_sphinx %}
|
||||
{% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
|
||||
{%- endif %}
|
||||
</div>
|
||||
<p>asdf asdf asdf asdf 22</p>
|
||||
{%- endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
<!-- <form class="wy-form" action="{{ pathto('search') }}" method="get">
|
||||
<input type="text" name="q" placeholder="Search docs" />
|
||||
<input type="hidden" name="check_keywords" value="yes" />
|
||||
<input type="hidden" name="area" value="default" />
|
||||
</form> -->
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var cx = '006019874985968165468:eu5pbnxp4po';
|
||||
var gcse = document.createElement('script');
|
||||
gcse.type = 'text/javascript';
|
||||
gcse.async = true;
|
||||
gcse.src = (document.location.protocol == 'https:' ? 'https:' : 'http:') +
|
||||
'//www.google.com/cse/cse.js?cx=' + cx;
|
||||
var s = document.getElementsByTagName('script')[0];
|
||||
s.parentNode.insertBefore(gcse, s);
|
||||
})();
|
||||
</script>
|
||||
|
||||
<form id="search-form-id" action="">
|
||||
<input type="text" name="query" id="search-box-id" />
|
||||
<a class="search-reset-start" id="search-reset"><i class="fa fa-times"></i></a>
|
||||
<a class="search-reset-start" id="search-start"><i class="fa fa-search"></i></a>
|
||||
</form>
|
||||
|
||||
<script type="text/javascript" src="http://www.google.com/cse/brand?form=search-form-id&inputbox=search-box-id"></script>
|
||||
|
||||
<script>
|
||||
function executeQuery() {
|
||||
var input = document.getElementById('search-box-id');
|
||||
var element = google.search.cse.element.getElement('searchresults-only0');
|
||||
element.resultsUrl = '/htmlout/search.html'
|
||||
if (input.value == '') {
|
||||
element.clearAllResults();
|
||||
$('#page-content, .rst-footer-buttons, #search-start').show();
|
||||
$('#search-results, #search-reset').hide();
|
||||
} else {
|
||||
$('#page-content, .rst-footer-buttons, #search-start').hide();
|
||||
$('#search-results, #search-reset').show();
|
||||
element.execute(input.value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$('#search-reset').hide();
|
||||
|
||||
$('#search-box-id').css('background-position', '1em center');
|
||||
|
||||
$('#search-box-id').on('blur', function() {
|
||||
$('#search-box-id').css('background-position', '1em center');
|
||||
});
|
||||
|
||||
$('#search-start').click(function(e) { executeQuery(); });
|
||||
$('#search-reset').click(function(e) { $('#search-box-id').val(''); executeQuery(); });
|
||||
|
||||
$('#search-form-id').submit(function(e) {
|
||||
console.log('submitting!');
|
||||
executeQuery();
|
||||
e.preventDefault();
|
||||
});
|
||||
</script>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.8 KiB |
@ -0,0 +1,48 @@
|
||||
Releases
|
||||
========
|
||||
|
||||
.. contents:: Topics
|
||||
:local:
|
||||
|
||||
.. _schedule:
|
||||
|
||||
Release Schedule
|
||||
````````````````
|
||||
Ansible is on a 'flexible' 4 month release schedule, sometimes this can be extended if there is a major change that requires a longer cycle (i.e. 2.0 core rewrite).
|
||||
Currently modules get released at the same time as the main Ansible repo, even though they are separated into ansible-modules-core and ansible-modules-extras.
|
||||
|
||||
The major features and bugs fixed in a release should be reflected in the CHANGELOG.md, minor ones will be in the commit history (FIXME: add git exmaple to list).
|
||||
When a fix/feature gets added to the `devel` branch it will be part of the next release, some bugfixes can be backported to previous releases and might be part of a minor point release if it is deemed necessary.
|
||||
|
||||
Sometimes an RC can be extended by a few days if a bugfix makes a change that can have far reaching consequences, so users have enough time to find any new issues that may stem from this.
|
||||
|
||||
.. _methods:
|
||||
|
||||
Release methods
|
||||
````````````````
|
||||
|
||||
Ansible normally goes through a 'release candidate', issuing an RC1 for a release, if no major bugs are discovered in it after 5 business days we'll get a final release.
|
||||
Otherwise fixes will be applied and an RC2 will be provided for testing and if no bugs after 2 days, the final release will be made, iterating this last step and incrementing the candidate number as we find major bugs.
|
||||
|
||||
|
||||
.. _freezing:
|
||||
|
||||
Release feature freeze
|
||||
``````````````````````
|
||||
|
||||
During the release candidate process, the focus will be on bugfixes that affect the RC, new features will be delayed while we try to produce a final version. Some bugfixes that are minor or don't affect the RC will also be postponed until after the release is finalized.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`developing_api`
|
||||
Python API to Playbooks and Ad Hoc Task Execution
|
||||
:doc:`developing_modules`
|
||||
How to develop modules
|
||||
:doc:`developing_plugins`
|
||||
How to develop plugins
|
||||
`Ansible Tower <http://ansible.com/ansible-tower>`_
|
||||
REST API endpoint and GUI for Ansible, syncs with dynamic inventory
|
||||
`Development Mailing List <http://groups.google.com/group/ansible-devel>`_
|
||||
Mailing list for development topics
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
- hosts: localhost
|
||||
remote_user: root
|
||||
roles:
|
||||
- {{ role_name }}
|
||||
@ -0,0 +1,29 @@
|
||||
---
|
||||
language: python
|
||||
python: "2.7"
|
||||
|
||||
# Use the new container infrastructure
|
||||
sudo: false
|
||||
|
||||
# Install ansible
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- python-pip
|
||||
|
||||
install:
|
||||
# Install ansible
|
||||
- pip install ansible
|
||||
|
||||
# Check ansible version
|
||||
- ansible --version
|
||||
|
||||
# Create ansible.cfg with correct roles_path
|
||||
- printf '[defaults]\nroles_path=../' >ansible.cfg
|
||||
|
||||
script:
|
||||
# Basic role syntax check
|
||||
- ansible-playbook tests/test.yml -i tests/inventory --syntax-check
|
||||
|
||||
notifications:
|
||||
webhooks: https://galaxy.ansible.com/api/v1/notifications/
|
||||
@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# (C) 2015, Chris Houseknecht <chouse@ansible.com>
|
||||
#
|
||||
# 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
|
||||
|
||||
import getpass
|
||||
import json
|
||||
import urllib
|
||||
|
||||
from urllib2 import quote as urlquote, HTTPError
|
||||
from urlparse import urlparse
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.utils.color import stringc
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
except ImportError:
|
||||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
class GalaxyLogin(object):
|
||||
''' Class to handle authenticating user with Galaxy API prior to performing CUD operations '''
|
||||
|
||||
GITHUB_AUTH = 'https://api.github.com/authorizations'
|
||||
|
||||
def __init__(self, galaxy, github_token=None):
|
||||
self.galaxy = galaxy
|
||||
self.github_username = None
|
||||
self.github_password = None
|
||||
|
||||
if github_token == None:
|
||||
self.get_credentials()
|
||||
|
||||
def get_credentials(self):
|
||||
display.display(u'\n\n' + "We need your " + stringc("Github login",'bright cyan') +
|
||||
" to identify you.", screen_only=True)
|
||||
display.display("This information will " + stringc("not be sent to Galaxy",'bright cyan') +
|
||||
", only to " + stringc("api.github.com.","yellow"), screen_only=True)
|
||||
display.display("The password will not be displayed." + u'\n\n', screen_only=True)
|
||||
display.display("Use " + stringc("--github-token",'yellow') +
|
||||
" if you do not want to enter your password." + u'\n\n', screen_only=True)
|
||||
|
||||
try:
|
||||
self.github_username = raw_input("Github Username: ")
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
self.github_password = getpass.getpass("Password for %s: " % self.github_username)
|
||||
except:
|
||||
pass
|
||||
|
||||
if not self.github_username or not self.github_password:
|
||||
raise AnsibleError("Invalid Github credentials. Username and password are required.")
|
||||
|
||||
def remove_github_token(self):
|
||||
'''
|
||||
If for some reason an ansible-galaxy token was left from a prior login, remove it. We cannot
|
||||
retrieve the token after creation, so we are forced to create a new one.
|
||||
'''
|
||||
try:
|
||||
tokens = json.load(open_url(self.GITHUB_AUTH, url_username=self.github_username,
|
||||
url_password=self.github_password, force_basic_auth=True,))
|
||||
except HTTPError as e:
|
||||
res = json.load(e)
|
||||
raise AnsibleError(res['message'])
|
||||
|
||||
for token in tokens:
|
||||
if token['note'] == 'ansible-galaxy login':
|
||||
display.vvvvv('removing token: %s' % token['token_last_eight'])
|
||||
try:
|
||||
open_url('https://api.github.com/authorizations/%d' % token['id'], url_username=self.github_username,
|
||||
url_password=self.github_password, method='DELETE', force_basic_auth=True,)
|
||||
except HTTPError as e:
|
||||
res = json.load(e)
|
||||
raise AnsibleError(res['message'])
|
||||
|
||||
def create_github_token(self):
|
||||
'''
|
||||
Create a personal authorization token with a note of 'ansible-galaxy login'
|
||||
'''
|
||||
self.remove_github_token()
|
||||
args = json.dumps({"scopes":["public_repo"], "note":"ansible-galaxy login"})
|
||||
try:
|
||||
data = json.load(open_url(self.GITHUB_AUTH, url_username=self.github_username,
|
||||
url_password=self.github_password, force_basic_auth=True, data=args))
|
||||
except HTTPError as e:
|
||||
res = json.load(e)
|
||||
raise AnsibleError(res['message'])
|
||||
return data['token']
|
||||
@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# (C) 2015, Chris Houseknecht <chouse@ansible.com>
|
||||
#
|
||||
# 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
|
||||
|
||||
import os
|
||||
import yaml
|
||||
from stat import *
|
||||
|
||||
try:
|
||||
from __main__ import display
|
||||
except ImportError:
|
||||
from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
|
||||
class GalaxyToken(object):
|
||||
''' Class to storing and retrieving token in ~/.ansible_galaxy '''
|
||||
|
||||
def __init__(self):
|
||||
self.file = os.path.expanduser("~") + '/.ansible_galaxy'
|
||||
self.config = yaml.safe_load(self.__open_config_for_read())
|
||||
if not self.config:
|
||||
self.config = {}
|
||||
|
||||
def __open_config_for_read(self):
|
||||
if os.path.isfile(self.file):
|
||||
display.vvv('Opened %s' % self.file)
|
||||
return open(self.file, 'r')
|
||||
# config.yml not found, create and chomd u+rw
|
||||
f = open(self.file,'w')
|
||||
f.close()
|
||||
os.chmod(self.file,S_IRUSR|S_IWUSR) # owner has +rw
|
||||
display.vvv('Created %s' % self.file)
|
||||
return open(self.file, 'r')
|
||||
|
||||
def set(self, token):
|
||||
self.config['token'] = token
|
||||
self.save()
|
||||
|
||||
def get(self):
|
||||
return self.config.get('token', None)
|
||||
|
||||
def save(self):
|
||||
with open(self.file,'w') as f:
|
||||
yaml.safe_dump(self.config,f,default_flow_style=False)
|
||||
|
||||
@ -1,155 +0,0 @@
|
||||
#
|
||||
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
"""
|
||||
This module adds shared support for Arista EOS devices using eAPI over
|
||||
HTTP/S transport. It is built on module_utils/urls.py which is required
|
||||
for proper operation.
|
||||
|
||||
In order to use this module, include it as part of a custom
|
||||
module as shown below.
|
||||
|
||||
** Note: The order of the import statements does matter. **
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
from ansible.module_utils.urls import *
|
||||
from ansible.module_utils.eapi import *
|
||||
|
||||
The eapi module provides the following common argument spec:
|
||||
|
||||
* host (str) - [Required] The IPv4 address or FQDN of the network device
|
||||
|
||||
* port (str) - Overrides the default port to use for the HTTP/S
|
||||
connection. The default values are 80 for HTTP and
|
||||
443 for HTTPS
|
||||
|
||||
* url_username (str) - [Required] The username to use to authenticate
|
||||
the HTTP/S connection. Aliases: username
|
||||
|
||||
* url_password (str) - [Required] The password to use to authenticate
|
||||
the HTTP/S connection. Aliases: password
|
||||
|
||||
* use_ssl (bool) - Specifies whether or not to use an encrypted (HTTPS)
|
||||
connection or not. The default value is False.
|
||||
|
||||
* enable_mode (bool) - Specifies whether or not to enter `enable` mode
|
||||
prior to executing the command list. The default value is True
|
||||
|
||||
* enable_password (str) - The password for entering `enable` mode
|
||||
on the switch if configured.
|
||||
|
||||
In order to communicate with Arista EOS devices, the eAPI feature
|
||||
must be enabled and configured on the device.
|
||||
|
||||
"""
|
||||
def eapi_argument_spec(spec=None):
|
||||
"""Creates an argument spec for working with eAPI
|
||||
"""
|
||||
arg_spec = url_argument_spec()
|
||||
arg_spec.update(dict(
|
||||
host=dict(required=True),
|
||||
port=dict(),
|
||||
url_username=dict(required=True, aliases=['username']),
|
||||
url_password=dict(required=True, aliases=['password']),
|
||||
use_ssl=dict(default=True, type='bool'),
|
||||
enable_mode=dict(default=True, type='bool'),
|
||||
enable_password=dict()
|
||||
))
|
||||
if spec:
|
||||
arg_spec.update(spec)
|
||||
return arg_spec
|
||||
|
||||
def eapi_url(module):
|
||||
"""Construct a valid Arist eAPI URL
|
||||
"""
|
||||
if module.params['use_ssl']:
|
||||
proto = 'https'
|
||||
else:
|
||||
proto = 'http'
|
||||
host = module.params['host']
|
||||
url = '{}://{}'.format(proto, host)
|
||||
if module.params['port']:
|
||||
url = '{}:{}'.format(url, module.params['port'])
|
||||
return '{}/command-api'.format(url)
|
||||
|
||||
def to_list(arg):
|
||||
"""Convert the argument to a list object
|
||||
"""
|
||||
if isinstance(arg, (list, tuple)):
|
||||
return list(arg)
|
||||
elif arg is not None:
|
||||
return [arg]
|
||||
else:
|
||||
return []
|
||||
|
||||
def eapi_body(commands, encoding, reqid=None):
|
||||
"""Create a valid eAPI JSON-RPC request message
|
||||
"""
|
||||
params = dict(version=1, cmds=to_list(commands), format=encoding)
|
||||
return dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params)
|
||||
|
||||
def eapi_enable_mode(module):
|
||||
"""Build commands for entering `enable` mode on the switch
|
||||
"""
|
||||
if module.params['enable_mode']:
|
||||
passwd = module.params['enable_password']
|
||||
if passwd:
|
||||
return dict(cmd='enable', input=passwd)
|
||||
else:
|
||||
return 'enable'
|
||||
|
||||
def eapi_command(module, commands, encoding='json'):
|
||||
"""Send an ordered list of commands to the device over eAPI
|
||||
"""
|
||||
commands = to_list(commands)
|
||||
url = eapi_url(module)
|
||||
|
||||
enable = eapi_enable_mode(module)
|
||||
if enable:
|
||||
commands.insert(0, enable)
|
||||
|
||||
data = eapi_body(commands, encoding)
|
||||
data = module.jsonify(data)
|
||||
|
||||
headers = {'Content-Type': 'application/json-rpc'}
|
||||
|
||||
response, headers = fetch_url(module, url, data=data, headers=headers,
|
||||
method='POST')
|
||||
|
||||
if headers['status'] != 200:
|
||||
module.fail_json(**headers)
|
||||
|
||||
response = module.from_json(response.read())
|
||||
if 'error' in response:
|
||||
err = response['error']
|
||||
module.fail_json(msg='json-rpc error', **err)
|
||||
|
||||
if enable:
|
||||
response['result'].pop(0)
|
||||
|
||||
return response['result'], headers
|
||||
|
||||
def eapi_configure(module, commands):
|
||||
"""Send configuration commands to the device over eAPI
|
||||
"""
|
||||
commands.insert(0, 'configure')
|
||||
response, headers = eapi_command(module, commands)
|
||||
response.pop(0)
|
||||
return response, headers
|
||||
|
||||
|
||||
@ -0,0 +1,227 @@
|
||||
#
|
||||
# (c) 2015 Peter Sprygada, <psprygada@ansible.com>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I)
|
||||
|
||||
NET_COMMON_ARGS = dict(
|
||||
host=dict(required=True),
|
||||
port=dict(type='int'),
|
||||
username=dict(required=True),
|
||||
password=dict(no_log=True),
|
||||
authorize=dict(default=False, type='bool'),
|
||||
auth_pass=dict(no_log=True),
|
||||
transport=dict(choices=['cli', 'eapi']),
|
||||
use_ssl=dict(default=True, type='bool'),
|
||||
provider=dict()
|
||||
)
|
||||
|
||||
def to_list(val):
|
||||
if isinstance(val, (list, tuple)):
|
||||
return list(val)
|
||||
elif val is not None:
|
||||
return [val]
|
||||
else:
|
||||
return list()
|
||||
|
||||
class Eapi(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
# sets the module_utils/urls.py req parameters
|
||||
self.module.params['url_username'] = module.params['username']
|
||||
self.module.params['url_password'] = module.params['password']
|
||||
|
||||
self.url = None
|
||||
self.enable = None
|
||||
|
||||
def _get_body(self, commands, encoding, reqid=None):
|
||||
"""Create a valid eAPI JSON-RPC request message
|
||||
"""
|
||||
params = dict(version=1, cmds=commands, format=encoding)
|
||||
return dict(jsonrpc='2.0', id=reqid, method='runCmds', params=params)
|
||||
|
||||
def connect(self):
|
||||
host = self.module.params['host']
|
||||
port = self.module.params['port']
|
||||
|
||||
if self.module.params['use_ssl']:
|
||||
proto = 'https'
|
||||
if not port:
|
||||
port = 443
|
||||
else:
|
||||
proto = 'http'
|
||||
if not port:
|
||||
port = 80
|
||||
|
||||
self.url = '%s://%s:%s/command-api' % (proto, host, port)
|
||||
|
||||
def authorize(self):
|
||||
if self.module.params['auth_pass']:
|
||||
passwd = self.module.params['auth_pass']
|
||||
self.enable = dict(cmd='enable', input=passwd)
|
||||
else:
|
||||
self.enable = 'enable'
|
||||
|
||||
def send(self, commands, encoding='json'):
|
||||
"""Send commands to the device.
|
||||
"""
|
||||
clist = to_list(commands)
|
||||
|
||||
if self.enable is not None:
|
||||
clist.insert(0, self.enable)
|
||||
|
||||
data = self._get_body(clist, encoding)
|
||||
data = self.module.jsonify(data)
|
||||
|
||||
headers = {'Content-Type': 'application/json-rpc'}
|
||||
|
||||
response, headers = fetch_url(self.module, self.url, data=data,
|
||||
headers=headers, method='POST')
|
||||
|
||||
if headers['status'] != 200:
|
||||
self.module.fail_json(**headers)
|
||||
|
||||
response = self.module.from_json(response.read())
|
||||
if 'error' in response:
|
||||
err = response['error']
|
||||
self.module.fail_json(msg='json-rpc error', **err)
|
||||
|
||||
if self.enable:
|
||||
response['result'].pop(0)
|
||||
|
||||
return response['result']
|
||||
|
||||
class Cli(object):
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.shell = None
|
||||
|
||||
def connect(self, **kwargs):
|
||||
host = self.module.params['host']
|
||||
port = self.module.params['port'] or 22
|
||||
|
||||
username = self.module.params['username']
|
||||
password = self.module.params['password']
|
||||
|
||||
self.shell = Shell()
|
||||
self.shell.open(host, port=port, username=username, password=password)
|
||||
|
||||
def authorize(self):
|
||||
passwd = self.module.params['auth_pass']
|
||||
self.send(Command('enable', prompt=NET_PASSWD_RE, response=passwd))
|
||||
|
||||
def send(self, commands, encoding='text'):
|
||||
return self.shell.send(commands)
|
||||
|
||||
class NetworkModule(AnsibleModule):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NetworkModule, self).__init__(*args, **kwargs)
|
||||
self.connection = None
|
||||
self._config = None
|
||||
|
||||
@property
|
||||
def config(self):
|
||||
if not self._config:
|
||||
self._config = self.get_config()
|
||||
return self._config
|
||||
|
||||
def _load_params(self):
|
||||
params = super(NetworkModule, self)._load_params()
|
||||
provider = params.get('provider') or dict()
|
||||
for key, value in provider.items():
|
||||
if key in NET_COMMON_ARGS.keys():
|
||||
params[key] = value
|
||||
return params
|
||||
|
||||
def connect(self):
|
||||
if self.params['transport'] == 'eapi':
|
||||
self.connection = Eapi(self)
|
||||
else:
|
||||
self.connection = Cli(self)
|
||||
|
||||
try:
|
||||
self.connection.connect()
|
||||
self.execute('terminal length 0')
|
||||
|
||||
if self.params['authorize']:
|
||||
self.connection.authorize()
|
||||
|
||||
except Exception, exc:
|
||||
self.fail_json(msg=exc.message)
|
||||
|
||||
def configure(self, commands):
|
||||
commands = to_list(commands)
|
||||
commands.insert(0, 'configure terminal')
|
||||
responses = self.execute(commands)
|
||||
responses.pop(0)
|
||||
|
||||
return responses
|
||||
|
||||
def config_replace(self, commands):
|
||||
if self.params['transport'] == 'cli':
|
||||
self.fail_json(msg='config replace only supported over eapi')
|
||||
|
||||
cmd = 'configure replace terminal:'
|
||||
commands = '\n'.join(to_list(commands))
|
||||
command = dict(cmd=cmd, input=commands)
|
||||
self.execute(command)
|
||||
|
||||
def execute(self, commands, **kwargs):
|
||||
try:
|
||||
return self.connection.send(commands, **kwargs)
|
||||
except Exception, exc:
|
||||
self.fail_json(msg=exc.message, commands=commands)
|
||||
|
||||
def disconnect(self):
|
||||
self.connection.close()
|
||||
|
||||
def parse_config(self, cfg):
|
||||
return parse(cfg, indent=3)
|
||||
|
||||
def get_config(self):
|
||||
cmd = 'show running-config'
|
||||
if self.params.get('include_defaults'):
|
||||
cmd += ' all'
|
||||
if self.params['transport'] == 'cli':
|
||||
return self.execute(cmd)[0]
|
||||
else:
|
||||
resp = self.execute(cmd, encoding='text')
|
||||
return resp[0]
|
||||
|
||||
|
||||
def get_module(**kwargs):
|
||||
"""Return instance of NetworkModule
|
||||
"""
|
||||
argument_spec = NET_COMMON_ARGS.copy()
|
||||
if kwargs.get('argument_spec'):
|
||||
argument_spec.update(kwargs['argument_spec'])
|
||||
kwargs['argument_spec'] = argument_spec
|
||||
|
||||
module = NetworkModule(**kwargs)
|
||||
|
||||
# HAS_PARAMIKO is set by module_utils/shell.py
|
||||
if module.params['transport'] == 'cli' and not HAS_PARAMIKO:
|
||||
module.fail_json(msg='paramiko is required but does not appear to be installed')
|
||||
|
||||
module.connect()
|
||||
|
||||
return module
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue