mirror of
https://github.com/valitydev/salt.git
synced 2024-11-08 01:18:58 +00:00
Merge branch 'develop' into develop
This commit is contained in:
commit
70eae35cc5
@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import
|
||||
import sys
|
||||
import salt.client.ssh
|
||||
import salt.utils.parsers
|
||||
from salt.utils.verify import verify_log
|
||||
@ -13,6 +14,9 @@ class SaltSSH(salt.utils.parsers.SaltSSHOptionParser):
|
||||
'''
|
||||
|
||||
def run(self):
|
||||
if '-H' in sys.argv or '--hosts' in sys.argv:
|
||||
sys.argv += ['x', 'x'] # Hack: pass a mandatory two options
|
||||
# that won't be used anyways with -H or --hosts
|
||||
self.parse_args()
|
||||
self.setup_logfile_logger()
|
||||
verify_log(self.config)
|
||||
|
@ -17,12 +17,11 @@ import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import yaml
|
||||
import uuid
|
||||
import tempfile
|
||||
import binascii
|
||||
import sys
|
||||
import ntpath
|
||||
import datetime
|
||||
|
||||
# Import salt libs
|
||||
import salt.output
|
||||
@ -51,6 +50,8 @@ import salt.utils.verify
|
||||
from salt.utils.locales import sdecode
|
||||
from salt.utils.platform import is_windows
|
||||
from salt.utils.process import MultiprocessingProcess
|
||||
import salt.roster
|
||||
from salt.template import compile_template
|
||||
|
||||
# Import 3rd-party libs
|
||||
from salt.ext import six
|
||||
@ -211,8 +212,11 @@ class SSH(object):
|
||||
'''
|
||||
Create an SSH execution system
|
||||
'''
|
||||
ROSTER_UPDATE_FLAG = '#__needs_update'
|
||||
|
||||
def __init__(self, opts):
|
||||
pull_sock = os.path.join(opts[u'sock_dir'], u'master_event_pull.ipc')
|
||||
self.__parsed_rosters = {SSH.ROSTER_UPDATE_FLAG: True}
|
||||
pull_sock = os.path.join(opts['sock_dir'], 'master_event_pull.ipc')
|
||||
if os.path.isfile(pull_sock) and HAS_ZMQ:
|
||||
self.event = salt.utils.event.get_event(
|
||||
u'master',
|
||||
@ -223,18 +227,21 @@ class SSH(object):
|
||||
else:
|
||||
self.event = None
|
||||
self.opts = opts
|
||||
self.opts['__salt_ssh'] = True
|
||||
if self.opts[u'regen_thin']:
|
||||
self.opts[u'ssh_wipe'] = True
|
||||
if not salt.utils.path.which(u'ssh'):
|
||||
raise salt.exceptions.SaltSystemExit(u'No ssh binary found in path -- ssh must be installed for salt-ssh to run. Exiting.')
|
||||
self.opts[u'_ssh_version'] = ssh_version()
|
||||
self.tgt_type = self.opts[u'selected_target_option'] \
|
||||
if self.opts[u'selected_target_option'] else u'glob'
|
||||
self.roster = salt.roster.Roster(opts, opts.get(u'roster', u'flat'))
|
||||
if self.opts['regen_thin']:
|
||||
self.opts['ssh_wipe'] = True
|
||||
if not salt.utils.path.which('ssh'):
|
||||
raise salt.exceptions.SaltSystemExit('No ssh binary found in path -- ssh must be '
|
||||
'installed for salt-ssh to run. Exiting.')
|
||||
self.opts['_ssh_version'] = ssh_version()
|
||||
self.tgt_type = self.opts['selected_target_option'] \
|
||||
if self.opts['selected_target_option'] else 'glob'
|
||||
self._expand_target()
|
||||
self.roster = salt.roster.Roster(self.opts, self.opts.get('roster', 'flat'))
|
||||
self.targets = self.roster.targets(
|
||||
self.opts[u'tgt'],
|
||||
self.tgt_type)
|
||||
if not self.targets:
|
||||
self._update_targets()
|
||||
# If we're in a wfunc, we need to get the ssh key location from the
|
||||
# top level opts, stored in __master_opts__
|
||||
if u'__master_opts__' in self.opts:
|
||||
@ -326,6 +333,90 @@ class SSH(object):
|
||||
python3_bin=self.opts[u'python3_bin'])
|
||||
self.mods = mod_data(self.fsclient)
|
||||
|
||||
def _get_roster(self):
|
||||
'''
|
||||
Read roster filename as a key to the data.
|
||||
:return:
|
||||
'''
|
||||
roster_file = salt.roster.get_roster_file(self.opts)
|
||||
if roster_file not in self.__parsed_rosters:
|
||||
roster_data = compile_template(roster_file, salt.loader.render(self.opts, {}),
|
||||
self.opts['renderer'], self.opts['renderer_blacklist'],
|
||||
self.opts['renderer_whitelist'])
|
||||
self.__parsed_rosters[roster_file] = roster_data
|
||||
return roster_file
|
||||
|
||||
def _expand_target(self):
|
||||
'''
|
||||
Figures out if the target is a reachable host without wildcards, expands if any.
|
||||
:return:
|
||||
'''
|
||||
# TODO: Support -L
|
||||
target = self.opts['tgt']
|
||||
if isinstance(target, list):
|
||||
return
|
||||
|
||||
hostname = self.opts['tgt'].split('@')[-1]
|
||||
needs_expansion = '*' not in hostname and salt.utils.network.is_reachable_host(hostname)
|
||||
if needs_expansion:
|
||||
hostname = salt.utils.network.ip_to_host(hostname)
|
||||
self._get_roster()
|
||||
for roster_filename in self.__parsed_rosters:
|
||||
roster_data = self.__parsed_rosters[roster_filename]
|
||||
if not isinstance(roster_data, bool):
|
||||
for host_id in roster_data:
|
||||
if hostname in [host_id, roster_data.get('host')]:
|
||||
if hostname != self.opts['tgt']:
|
||||
self.opts['tgt'] = hostname
|
||||
self.__parsed_rosters[self.ROSTER_UPDATE_FLAG] = False
|
||||
return
|
||||
|
||||
def _update_roster(self):
|
||||
'''
|
||||
Update default flat roster with the passed in information.
|
||||
:return:
|
||||
'''
|
||||
roster_file = self._get_roster()
|
||||
if os.access(roster_file, os.W_OK):
|
||||
if self.__parsed_rosters[self.ROSTER_UPDATE_FLAG]:
|
||||
with salt.utils.files.fopen(roster_file, 'a') as roster_fp:
|
||||
roster_fp.write('# Automatically added by "{s_user}" at {s_time}\n{hostname}:\n host: '
|
||||
'{hostname}\n user: {user}'
|
||||
'\n passwd: {passwd}\n'.format(s_user=getpass.getuser(),
|
||||
s_time=datetime.datetime.utcnow().isoformat(),
|
||||
hostname=self.opts.get('tgt', ''),
|
||||
user=self.opts.get('ssh_user', ''),
|
||||
passwd=self.opts.get('ssh_passwd', '')))
|
||||
log.info('The host {0} has been added to the roster {1}'.format(self.opts.get('tgt', ''),
|
||||
roster_file))
|
||||
else:
|
||||
log.error('Unable to update roster {0}: access denied'.format(roster_file))
|
||||
|
||||
def _update_targets(self):
|
||||
'''
|
||||
Uptade targets in case hostname was directly passed without the roster.
|
||||
:return:
|
||||
'''
|
||||
|
||||
hostname = self.opts.get('tgt', '')
|
||||
if '@' in hostname:
|
||||
user, hostname = hostname.split('@', 1)
|
||||
else:
|
||||
user = self.opts.get('ssh_user')
|
||||
if hostname == '*':
|
||||
hostname = ''
|
||||
|
||||
if salt.utils.network.is_reachable_host(hostname):
|
||||
hostname = salt.utils.network.ip_to_host(hostname)
|
||||
self.opts['tgt'] = hostname
|
||||
self.targets[hostname] = {
|
||||
'passwd': self.opts.get('ssh_passwd', ''),
|
||||
'host': hostname,
|
||||
'user': user,
|
||||
}
|
||||
if not self.opts.get('ssh_skip_roster'):
|
||||
self._update_roster()
|
||||
|
||||
def get_pubkey(self):
|
||||
'''
|
||||
Return the key string for the SSH public key
|
||||
@ -594,8 +685,21 @@ class SSH(object):
|
||||
'''
|
||||
Execute the overall routine, print results via outputters
|
||||
'''
|
||||
fstr = u'{0}.prep_jid'.format(self.opts[u'master_job_cache'])
|
||||
jid = self.returners[fstr](passed_jid=jid or self.opts.get(u'jid', None))
|
||||
if self.opts['list_hosts']:
|
||||
self._get_roster()
|
||||
ret = {}
|
||||
for roster_file in self.__parsed_rosters:
|
||||
if roster_file.startswith('#'):
|
||||
continue
|
||||
ret[roster_file] = {}
|
||||
for host_id in self.__parsed_rosters[roster_file]:
|
||||
hostname = self.__parsed_rosters[roster_file][host_id]['host']
|
||||
ret[roster_file][host_id] = hostname
|
||||
salt.output.display_output(ret, 'nested', self.opts)
|
||||
sys.exit()
|
||||
|
||||
fstr = '{0}.prep_jid'.format(self.opts['master_job_cache'])
|
||||
jid = self.returners[fstr](passed_jid=jid or self.opts.get('jid', None))
|
||||
|
||||
# Save the invocation information
|
||||
argv = self.opts[u'argv']
|
||||
|
38
salt/grains/panos.py
Normal file
38
salt/grains/panos.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Generate baseline proxy minion grains for panos hosts.
|
||||
|
||||
'''
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.utils.platform
|
||||
import salt.proxy.panos
|
||||
|
||||
__proxyenabled__ = ['panos']
|
||||
__virtualname__ = 'panos'
|
||||
|
||||
log = logging.getLogger(__file__)
|
||||
|
||||
GRAINS_CACHE = {'os_family': 'panos'}
|
||||
|
||||
|
||||
def __virtual__():
|
||||
try:
|
||||
if salt.utils.platform.is_proxy() and __opts__['proxy']['proxytype'] == 'panos':
|
||||
return __virtualname__
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def panos(proxy=None):
|
||||
if not proxy:
|
||||
return {}
|
||||
if proxy['panos.initialized']() is False:
|
||||
return {}
|
||||
return {'panos': proxy['panos.grains']()}
|
1857
salt/modules/panos.py
Normal file
1857
salt/modules/panos.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -76,6 +76,11 @@ def __virtual__():
|
||||
cmd, output_loglevel='quiet', ignore_retcode=True
|
||||
) == 0:
|
||||
return 'zfs'
|
||||
|
||||
_zfs_fuse = lambda f: __salt__['service.' + f]('zfs-fuse')
|
||||
if _zfs_fuse('available') and (_zfs_fuse('status') or _zfs_fuse('start')):
|
||||
return 'zfs'
|
||||
|
||||
return (False, "The zfs module cannot be loaded: zfs not found")
|
||||
|
||||
|
||||
|
418
salt/proxy/panos.py
Normal file
418
salt/proxy/panos.py
Normal file
@ -0,0 +1,418 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
|
||||
Proxy Minion interface module for managing Palo Alto firewall devices.
|
||||
|
||||
:codeauthor: :email:`Spencer Ervin <spencer_ervin@hotmail.com>`
|
||||
:maturity: new
|
||||
:depends: none
|
||||
:platform: unix
|
||||
|
||||
This proxy minion enables Palo Alto firewalls (hereafter referred to
|
||||
as simply 'panos') to be treated individually like a Salt Minion.
|
||||
|
||||
The panos proxy leverages the XML API functionality on the Palo Alto
|
||||
firewall. The Salt proxy must have access to the Palo Alto firewall on
|
||||
HTTPS (tcp/443).
|
||||
|
||||
More in-depth conceptual reading on Proxy Minions can be found in the
|
||||
:ref:`Proxy Minion <proxy-minion>` section of Salt's
|
||||
documentation.
|
||||
|
||||
|
||||
Configuration
|
||||
=============
|
||||
To use this integration proxy module, please configure the following:
|
||||
|
||||
Pillar
|
||||
------
|
||||
|
||||
Proxy minions get their configuration from Salt's Pillar. Every proxy must
|
||||
have a stanza in Pillar and a reference in the Pillar top-file that matches
|
||||
the ID. There are four connection options available for the panos proxy module.
|
||||
|
||||
- Direct Device (Password)
|
||||
- Direct Device (API Key)
|
||||
- Panorama Pass-Through (Password)
|
||||
- Panorama Pass-Through (API Key)
|
||||
|
||||
|
||||
Direct Device (Password)
|
||||
------------------------
|
||||
|
||||
The direct device configuration configures the proxy to connect directly to
|
||||
the device with username and password.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy:
|
||||
proxytype: panos
|
||||
host: <ip or dns name of panos host>
|
||||
username: <panos username>
|
||||
password: <panos password>
|
||||
|
||||
proxytype
|
||||
^^^^^^^^^
|
||||
The ``proxytype`` key and value pair is critical, as it tells Salt which
|
||||
interface to load from the ``proxy`` directory in Salt's install hierarchy,
|
||||
or from ``/srv/salt/_proxy`` on the Salt Master (if you have created your
|
||||
own proxy module, for example). To use this panos Proxy Module, set this to
|
||||
``panos``.
|
||||
|
||||
host
|
||||
^^^^
|
||||
The location, or ip/dns, of the panos host. Required.
|
||||
|
||||
username
|
||||
^^^^^^^^
|
||||
The username used to login to the panos host. Required.
|
||||
|
||||
password
|
||||
^^^^^^^^
|
||||
The password used to login to the panos host. Required.
|
||||
|
||||
Direct Device (API Key)
|
||||
------------------------
|
||||
|
||||
Palo Alto devices allow for access to the XML API with a generated 'API key'_
|
||||
instead of username and password.
|
||||
|
||||
.. _API key: https://www.paloaltonetworks.com/documentation/71/pan-os/xml-api/get-started-with-the-pan-os-xml-api/get-your-api-key
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy:
|
||||
proxytype: panos
|
||||
host: <ip or dns name of panos host>
|
||||
apikey: <panos generated api key>
|
||||
|
||||
proxytype
|
||||
^^^^^^^^^
|
||||
The ``proxytype`` key and value pair is critical, as it tells Salt which
|
||||
interface to load from the ``proxy`` directory in Salt's install hierarchy,
|
||||
or from ``/srv/salt/_proxy`` on the Salt Master (if you have created your
|
||||
own proxy module, for example). To use this panos Proxy Module, set this to
|
||||
``panos``.
|
||||
|
||||
host
|
||||
^^^^
|
||||
The location, or ip/dns, of the panos host. Required.
|
||||
|
||||
apikey
|
||||
^^^^^^^^
|
||||
The generated XML API key for the panos host. Required.
|
||||
|
||||
Panorama Pass-Through (Password)
|
||||
------------------------
|
||||
|
||||
The Panorama pass-through method sends all connections through the Panorama
|
||||
management system. It passes the connections to the appropriate device using
|
||||
the serial number of the Palo Alto firewall.
|
||||
|
||||
This option will reduce the number of connections that must be present for the
|
||||
proxy server. It will only require a connection to the Panorama server.
|
||||
|
||||
The username and password will be for authentication to the Panorama server,
|
||||
not the panos device.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy:
|
||||
proxytype: panos
|
||||
serial: <serial number of panos host>
|
||||
host: <ip or dns name of the panorama server>
|
||||
username: <panorama server username>
|
||||
password: <panorama server password>
|
||||
|
||||
proxytype
|
||||
^^^^^^^^^
|
||||
The ``proxytype`` key and value pair is critical, as it tells Salt which
|
||||
interface to load from the ``proxy`` directory in Salt's install hierarchy,
|
||||
or from ``/srv/salt/_proxy`` on the Salt Master (if you have created your
|
||||
own proxy module, for example). To use this panos Proxy Module, set this to
|
||||
``panos``.
|
||||
|
||||
serial
|
||||
^^^^^^
|
||||
The serial number of the panos host. Required.
|
||||
|
||||
host
|
||||
^^^^
|
||||
The location, or ip/dns, of the Panorama server. Required.
|
||||
|
||||
username
|
||||
^^^^^^^^
|
||||
The username used to login to the Panorama server. Required.
|
||||
|
||||
password
|
||||
^^^^^^^^
|
||||
The password used to login to the Panorama server. Required.
|
||||
|
||||
Panorama Pass-Through (API Key)
|
||||
------------------------
|
||||
|
||||
The Panorama server can also utilize a generated 'API key'_ for authentication.
|
||||
|
||||
.. _API key: https://www.paloaltonetworks.com/documentation/71/pan-os/xml-api/get-started-with-the-pan-os-xml-api/get-your-api-key
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
proxy:
|
||||
proxytype: panos
|
||||
serial: <serial number of panos host>
|
||||
host: <ip or dns name of the panorama server>
|
||||
apikey: <panos generated api key>
|
||||
|
||||
proxytype
|
||||
^^^^^^^^^
|
||||
The ``proxytype`` key and value pair is critical, as it tells Salt which
|
||||
interface to load from the ``proxy`` directory in Salt's install hierarchy,
|
||||
or from ``/srv/salt/_proxy`` on the Salt Master (if you have created your
|
||||
own proxy module, for example). To use this panos Proxy Module, set this to
|
||||
``panos``.
|
||||
|
||||
serial
|
||||
^^^^^^
|
||||
The serial number of the panos host. Required.
|
||||
|
||||
host
|
||||
^^^^
|
||||
The location, or ip/dns, of the Panorama server. Required.
|
||||
|
||||
apikey
|
||||
^^^^^^^^
|
||||
The generated XML API key for the Panorama server. Required.
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import Python Libs
|
||||
import logging
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.exceptions
|
||||
|
||||
# This must be present or the Salt loader won't load this module.
|
||||
__proxyenabled__ = ['panos']
|
||||
|
||||
# Variables are scoped to this module so we can have persistent data.
|
||||
GRAINS_CACHE = {'vendor': 'Palo Alto'}
|
||||
DETAILS = {}
|
||||
|
||||
# Set up logging
|
||||
log = logging.getLogger(__file__)
|
||||
|
||||
# Define the module's virtual name
|
||||
__virtualname__ = 'panos'
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only return if all the modules are available.
|
||||
'''
|
||||
return __virtualname__
|
||||
|
||||
|
||||
def init(opts):
|
||||
'''
|
||||
This function gets called when the proxy starts up. For
|
||||
panos devices, a determination is made on the connection type
|
||||
and the appropriate connection details that must be cached.
|
||||
'''
|
||||
if 'host' not in opts['proxy']:
|
||||
log.critical('No \'host\' key found in pillar for this proxy.')
|
||||
return False
|
||||
if 'apikey' not in opts['proxy']:
|
||||
# If we do not have an apikey, we must have both a username and password
|
||||
if 'username' not in opts['proxy']:
|
||||
log.critical('No \'username\' key found in pillar for this proxy.')
|
||||
return False
|
||||
if 'password' not in opts['proxy']:
|
||||
log.critical('No \'passwords\' key found in pillar for this proxy.')
|
||||
return False
|
||||
|
||||
DETAILS['url'] = 'https://{0}/api/'.format(opts['proxy']['host'])
|
||||
|
||||
# Set configuration details
|
||||
DETAILS['host'] = opts['proxy']['host']
|
||||
if 'serial' in opts['proxy']:
|
||||
DETAILS['serial'] = opts['proxy'].get('serial')
|
||||
if 'apikey' in opts['proxy']:
|
||||
log.debug("Selected pan_key method for panos proxy module.")
|
||||
DETAILS['method'] = 'pan_key'
|
||||
DETAILS['apikey'] = opts['proxy'].get('apikey')
|
||||
else:
|
||||
log.debug("Selected pan_pass method for panos proxy module.")
|
||||
DETAILS['method'] = 'pan_pass'
|
||||
DETAILS['username'] = opts['proxy'].get('username')
|
||||
DETAILS['password'] = opts['proxy'].get('password')
|
||||
else:
|
||||
if 'apikey' in opts['proxy']:
|
||||
log.debug("Selected dev_key method for panos proxy module.")
|
||||
DETAILS['method'] = 'dev_key'
|
||||
DETAILS['apikey'] = opts['proxy'].get('apikey')
|
||||
else:
|
||||
log.debug("Selected dev_pass method for panos proxy module.")
|
||||
DETAILS['method'] = 'dev_pass'
|
||||
DETAILS['username'] = opts['proxy'].get('username')
|
||||
DETAILS['password'] = opts['proxy'].get('password')
|
||||
|
||||
# Ensure connectivity to the device
|
||||
log.debug("Attempting to connect to panos proxy host.")
|
||||
query = {'type': 'op', 'cmd': '<show><system><info></info></system></show>'}
|
||||
call(query)
|
||||
log.debug("Successfully connected to panos proxy host.")
|
||||
|
||||
DETAILS['initialized'] = True
|
||||
|
||||
|
||||
def call(payload=None):
|
||||
'''
|
||||
This function captures the query string and sends it to the Palo Alto device.
|
||||
'''
|
||||
ret = {}
|
||||
try:
|
||||
if DETAILS['method'] == 'dev_key':
|
||||
# Pass the api key without the target declaration
|
||||
conditional_payload = {'key': DETAILS['apikey']}
|
||||
payload.update(conditional_payload)
|
||||
r = __utils__['http.query'](DETAILS['url'],
|
||||
data=payload,
|
||||
method='POST',
|
||||
decode_type='xml',
|
||||
decode=True,
|
||||
verify_ssl=False,
|
||||
raise_error=True)
|
||||
ret = r['dict'][0]
|
||||
elif DETAILS['method'] == 'dev_pass':
|
||||
# Pass credentials without the target declaration
|
||||
r = __utils__['http.query'](DETAILS['url'],
|
||||
username=DETAILS['username'],
|
||||
password=DETAILS['password'],
|
||||
data=payload,
|
||||
method='POST',
|
||||
decode_type='xml',
|
||||
decode=True,
|
||||
verify_ssl=False,
|
||||
raise_error=True)
|
||||
ret = r['dict'][0]
|
||||
elif DETAILS['method'] == 'pan_key':
|
||||
# Pass the api key with the target declaration
|
||||
conditional_payload = {'key': DETAILS['apikey'],
|
||||
'target': DETAILS['serial']}
|
||||
payload.update(conditional_payload)
|
||||
r = __utils__['http.query'](DETAILS['url'],
|
||||
data=payload,
|
||||
method='POST',
|
||||
decode_type='xml',
|
||||
decode=True,
|
||||
verify_ssl=False,
|
||||
raise_error=True)
|
||||
ret = r['dict'][0]
|
||||
elif DETAILS['method'] == 'pan_pass':
|
||||
# Pass credentials with the target declaration
|
||||
conditional_payload = {'target': DETAILS['serial']}
|
||||
payload.update(conditional_payload)
|
||||
r = __utils__['http.query'](DETAILS['url'],
|
||||
username=DETAILS['username'],
|
||||
password=DETAILS['password'],
|
||||
data=payload,
|
||||
method='POST',
|
||||
decode_type='xml',
|
||||
decode=True,
|
||||
verify_ssl=False,
|
||||
raise_error=True)
|
||||
ret = r['dict'][0]
|
||||
except KeyError as err:
|
||||
raise salt.exceptions.CommandExecutionError("Did not receive a valid response from host.")
|
||||
return ret
|
||||
|
||||
|
||||
def is_required_version(required_version='0.0.0'):
|
||||
'''
|
||||
Because different versions of Palo Alto support different command sets, this function
|
||||
will return true if the current version of Palo Alto supports the required command.
|
||||
'''
|
||||
if 'sw-version' in DETAILS['grains_cache']:
|
||||
current_version = DETAILS['grains_cache']['sw-version']
|
||||
else:
|
||||
# If we do not have the current sw-version cached, we cannot check version requirements.
|
||||
return False
|
||||
|
||||
required_version_split = required_version.split(".")
|
||||
current_version_split = current_version.split(".")
|
||||
|
||||
try:
|
||||
if int(current_version_split[0]) > int(required_version_split[0]):
|
||||
return True
|
||||
elif int(current_version_split[0]) < int(required_version_split[0]):
|
||||
return False
|
||||
|
||||
if int(current_version_split[1]) > int(required_version_split[1]):
|
||||
return True
|
||||
elif int(current_version_split[1]) < int(required_version_split[1]):
|
||||
return False
|
||||
|
||||
if int(current_version_split[2]) > int(required_version_split[2]):
|
||||
return True
|
||||
elif int(current_version_split[2]) < int(required_version_split[2]):
|
||||
return False
|
||||
|
||||
# We have an exact match
|
||||
return True
|
||||
except Exception as err:
|
||||
return False
|
||||
|
||||
|
||||
def initialized():
|
||||
'''
|
||||
Since grains are loaded in many different places and some of those
|
||||
places occur before the proxy can be initialized, return whether
|
||||
our init() function has been called
|
||||
'''
|
||||
return DETAILS.get('initialized', False)
|
||||
|
||||
|
||||
def grains():
|
||||
'''
|
||||
Get the grains from the proxied device
|
||||
'''
|
||||
if not DETAILS.get('grains_cache', {}):
|
||||
DETAILS['grains_cache'] = GRAINS_CACHE
|
||||
try:
|
||||
query = {'type': 'op', 'cmd': '<show><system><info></info></system></show>'}
|
||||
DETAILS['grains_cache'] = call(query)['system']
|
||||
except Exception as err:
|
||||
pass
|
||||
return DETAILS['grains_cache']
|
||||
|
||||
|
||||
def grains_refresh():
|
||||
'''
|
||||
Refresh the grains from the proxied device
|
||||
'''
|
||||
DETAILS['grains_cache'] = None
|
||||
return grains()
|
||||
|
||||
|
||||
def ping():
|
||||
'''
|
||||
Returns true if the device is reachable, else false.
|
||||
'''
|
||||
try:
|
||||
query = {'type': 'op', 'cmd': '<show><system><info></info></system></show>'}
|
||||
if 'result' in call(query)['system']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
except Exception as err:
|
||||
return False
|
||||
|
||||
|
||||
def shutdown():
|
||||
'''
|
||||
Shutdown the connection to the proxy device. For this proxy,
|
||||
shutdown is a no-op.
|
||||
'''
|
||||
log.debug('Panos proxy shutdown() called.')
|
579
salt/states/panos.py
Normal file
579
salt/states/panos.py
Normal file
@ -0,0 +1,579 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
A state module to manage Palo Alto network devices.
|
||||
|
||||
:codeauthor: :email:`Spencer Ervin <spencer_ervin@hotmail.com>`
|
||||
:maturity: new
|
||||
:depends: none
|
||||
:platform: unix
|
||||
|
||||
|
||||
About
|
||||
=====
|
||||
This state module was designed to handle connections to a Palo Alto based
|
||||
firewall. This module relies on the Palo Alto proxy module to interface with the devices.
|
||||
|
||||
This state module is designed to give extreme flexibility in the control over XPATH values on the PANOS device. It
|
||||
exposes the core XML API commands and allows state modules to chain complex XPATH commands.
|
||||
|
||||
Below is an example of how to construct a security rule and move to the top of the policy. This will take a config
|
||||
lock to prevent execution during the operation, then remove the lock. After the XPATH has been deployed, it will
|
||||
commit to the device.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/takelock:
|
||||
panos.add_config_lock
|
||||
panos/service_tcp_22:
|
||||
panos.set_config:
|
||||
- xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/service
|
||||
- value: <entry name='tcp-22'><protocol><tcp><port>22</port></tcp></protocol></entry>
|
||||
- commit: False
|
||||
panos/create_rule1:
|
||||
panos.set_config:
|
||||
- xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/rulebase/security/rules
|
||||
- value: '
|
||||
<entry name="rule1">
|
||||
<from><member>trust</member></from>
|
||||
<to><member>untrust</member></to>
|
||||
<source><member>10.0.0.1</member></source>
|
||||
<destination><member>10.0.1.1</member></destination>
|
||||
<service><member>tcp-22</member></service>
|
||||
<application><member>any</member></application>
|
||||
<action>allow</action>
|
||||
<disabled>no</disabled>
|
||||
</entry>'
|
||||
- commit: False
|
||||
panos/moveruletop:
|
||||
panos.move_config:
|
||||
- xpath: /config/devices/entry[@name='localhost.localdomain']/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='rule1']
|
||||
- where: top
|
||||
- commit: False
|
||||
panos/removelock:
|
||||
panos.remove_config_lock
|
||||
panos/commit:
|
||||
panos.commit
|
||||
|
||||
.. seealso::
|
||||
:prox:`Palo Alto Proxy Module <salt.proxy.panos>`
|
||||
|
||||
'''
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def __virtual__():
|
||||
return 'panos.commit' in __salt__
|
||||
|
||||
|
||||
def _default_ret(name):
|
||||
'''
|
||||
Set the default response values.
|
||||
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'changes': {},
|
||||
'commit': None,
|
||||
'result': False,
|
||||
'comment': ''
|
||||
}
|
||||
return ret
|
||||
|
||||
|
||||
def add_config_lock(name):
|
||||
'''
|
||||
Prevent other users from changing configuration until the lock is released.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/takelock:
|
||||
panos.add_config_lock
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
ret.update({
|
||||
'changes': __salt__['panos.add_config_lock'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def clone_config(name, xpath=None, newname=None, commit=False):
|
||||
'''
|
||||
Clone a specific XPATH and set it to a new name.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
xpath(str): The XPATH of the configuration API tree to clone.
|
||||
|
||||
newname(str): The new name of the XPATH clone.
|
||||
|
||||
commit(bool): If true the firewall will commit the changes, if false do not commit changes.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/clonerule:
|
||||
panos.clone_config:
|
||||
- xpath: /config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules&from=/config/devices/
|
||||
entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='rule1']
|
||||
- value: rule2
|
||||
- commit: True
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
if not xpath:
|
||||
return ret
|
||||
|
||||
if not newname:
|
||||
return ret
|
||||
|
||||
query = {'type': 'config',
|
||||
'action': 'clone',
|
||||
'xpath': xpath,
|
||||
'newname': newname}
|
||||
|
||||
response = __proxy__['panos.call'](query)
|
||||
|
||||
ret.update({
|
||||
'changes': response,
|
||||
'result': True
|
||||
})
|
||||
|
||||
if commit is True:
|
||||
ret.update({
|
||||
'commit': __salt__['panos.commit'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def commit(name):
|
||||
'''
|
||||
Commits the candidate configuration to the running configuration.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/commit:
|
||||
panos.commit
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
ret.update({
|
||||
'commit': __salt__['panos.commit'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def delete_config(name, xpath=None, commit=False):
|
||||
'''
|
||||
Deletes a Palo Alto XPATH to a specific value.
|
||||
|
||||
Use the xpath parameter to specify the location of the object to be deleted.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
xpath(str): The XPATH of the configuration API tree to control.
|
||||
|
||||
commit(bool): If true the firewall will commit the changes, if false do not commit changes.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/deletegroup:
|
||||
panos.delete_config:
|
||||
- xpath: /config/devices/entry/vsys/entry[@name='vsys1']/address-group/entry[@name='test']
|
||||
- commit: True
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
if not xpath:
|
||||
return ret
|
||||
|
||||
query = {'type': 'config',
|
||||
'action': 'delete',
|
||||
'xpath': xpath}
|
||||
|
||||
response = __proxy__['panos.call'](query)
|
||||
|
||||
ret.update({
|
||||
'changes': response,
|
||||
'result': True
|
||||
})
|
||||
|
||||
if commit is True:
|
||||
ret.update({
|
||||
'commit': __salt__['panos.commit'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def download_software(name, version=None, synch=False, check=False):
|
||||
'''
|
||||
Ensures that a software version is downloaded.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
version(str): The software version to check. If this version is not already downloaded, it will attempt to download
|
||||
the file from Palo Alto.
|
||||
|
||||
synch(bool): If true, after downloading the file it will be synched to its peer.
|
||||
|
||||
check(bool): If true, the PANOS device will first attempt to pull the most recent software inventory list from Palo
|
||||
Alto.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/version8.0.0:
|
||||
panos.download_software:
|
||||
- version: 8.0.0
|
||||
- synch: False
|
||||
- check: True
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
if check is True:
|
||||
__salt__['panos.check_software']()
|
||||
|
||||
versions = __salt__['panos.get_software_info']()
|
||||
|
||||
if 'sw-updates' not in versions \
|
||||
or 'versions' not in versions['sw-updates'] \
|
||||
or 'entry' not in versions['sw-updates']['versions']:
|
||||
ret.update({
|
||||
'comment': 'Software version is not found in the local software list.',
|
||||
'result': False
|
||||
})
|
||||
return ret
|
||||
|
||||
for entry in versions['sw-updates']['versions']['entry']:
|
||||
if entry['version'] == version and entry['downloaded'] == "yes":
|
||||
ret.update({
|
||||
'comment': 'Software version is already downloaded.',
|
||||
'result': True
|
||||
})
|
||||
return ret
|
||||
|
||||
ret.update({
|
||||
'changes': __salt__['panos.download_software_version'](version=version, synch=synch)
|
||||
})
|
||||
|
||||
versions = __salt__['panos.get_software_info']()
|
||||
|
||||
if 'sw-updates' not in versions \
|
||||
or 'versions' not in versions['sw-updates'] \
|
||||
or 'entry' not in versions['sw-updates']['versions']:
|
||||
ret.update({
|
||||
'result': False
|
||||
})
|
||||
return ret
|
||||
|
||||
for entry in versions['sw-updates']['versions']['entry']:
|
||||
if entry['version'] == version and entry['downloaded'] == "yes":
|
||||
ret.update({
|
||||
'result': True
|
||||
})
|
||||
return ret
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def edit_config(name, xpath=None, value=None, commit=False):
|
||||
'''
|
||||
Edits a Palo Alto XPATH to a specific value. This will always overwrite the existing value, even if it is not
|
||||
changed.
|
||||
|
||||
You can replace an existing object hierarchy at a specified location in the configuration with a new value. Use
|
||||
the xpath parameter to specify the location of the object, including the node to be replaced.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
xpath(str): The XPATH of the configuration API tree to control.
|
||||
|
||||
value(str): The XML value to edit. This must be a child to the XPATH.
|
||||
|
||||
commit(bool): If true the firewall will commit the changes, if false do not commit changes.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/addressgroup:
|
||||
panos.edit_config:
|
||||
- xpath: /config/devices/entry/vsys/entry[@name='vsys1']/address-group/entry[@name='test']
|
||||
- value: <static><entry name='test'><member>abc</member><member>xyz</member></entry></static>
|
||||
- commit: True
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
if not xpath:
|
||||
return ret
|
||||
|
||||
if not value:
|
||||
return ret
|
||||
|
||||
query = {'type': 'config',
|
||||
'action': 'edit',
|
||||
'xpath': xpath,
|
||||
'element': value}
|
||||
|
||||
response = __proxy__['panos.call'](query)
|
||||
|
||||
ret.update({
|
||||
'changes': response,
|
||||
'result': True
|
||||
})
|
||||
|
||||
if commit is True:
|
||||
ret.update({
|
||||
'commit': __salt__['panos.commit'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def move_config(name, xpath=None, where=None, dst=None, commit=False):
|
||||
'''
|
||||
Moves a XPATH value to a new location.
|
||||
|
||||
Use the xpath parameter to specify the location of the object to be moved, the where parameter to
|
||||
specify type of move, and dst parameter to specify the destination path.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
xpath(str): The XPATH of the configuration API tree to move.
|
||||
|
||||
where(str): The type of move to execute. Valid options are after, before, top, bottom. The after and before
|
||||
options will require the dst option to specify the destination of the action. The top action will move the
|
||||
XPATH to the top of its structure. The botoom action will move the XPATH to the bottom of its structure.
|
||||
|
||||
dst(str): Optional. Specifies the destination to utilize for a move action. This is ignored for the top
|
||||
or bottom action.
|
||||
|
||||
commit(bool): If true the firewall will commit the changes, if false do not commit changes.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/moveruletop:
|
||||
panos.move_config:
|
||||
- xpath: /config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='rule1']
|
||||
- where: top
|
||||
- commit: True
|
||||
|
||||
panos/moveruleafter:
|
||||
panos.move_config:
|
||||
- xpath: /config/devices/entry/vsys/entry[@name='vsys1']/rulebase/security/rules/entry[@name='rule1']
|
||||
- where: after
|
||||
- dst: rule2
|
||||
- commit: True
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
if not xpath:
|
||||
return ret
|
||||
|
||||
if not where:
|
||||
return ret
|
||||
|
||||
if where == 'after':
|
||||
query = {'type': 'config',
|
||||
'action': 'move',
|
||||
'xpath': xpath,
|
||||
'where': 'after',
|
||||
'dst': dst}
|
||||
elif where == 'before':
|
||||
query = {'type': 'config',
|
||||
'action': 'move',
|
||||
'xpath': xpath,
|
||||
'where': 'before',
|
||||
'dst': dst}
|
||||
elif where == 'top':
|
||||
query = {'type': 'config',
|
||||
'action': 'move',
|
||||
'xpath': xpath,
|
||||
'where': 'top'}
|
||||
elif where == 'bottom':
|
||||
query = {'type': 'config',
|
||||
'action': 'move',
|
||||
'xpath': xpath,
|
||||
'where': 'bottom'}
|
||||
|
||||
response = __proxy__['panos.call'](query)
|
||||
|
||||
ret.update({
|
||||
'changes': response,
|
||||
'result': True
|
||||
})
|
||||
|
||||
if commit is True:
|
||||
ret.update({
|
||||
'commit': __salt__['panos.commit'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def remove_config_lock(name):
|
||||
'''
|
||||
Release config lock previously held.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/takelock:
|
||||
panos.remove_config_lock
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
ret.update({
|
||||
'changes': __salt__['panos.remove_config_lock'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def rename_config(name, xpath=None, newname=None, commit=False):
|
||||
'''
|
||||
Rename a Palo Alto XPATH to a specific value. This will always rename the value even if a change is not needed.
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
xpath(str): The XPATH of the configuration API tree to control.
|
||||
|
||||
newname(str): The new name of the XPATH value.
|
||||
|
||||
commit(bool): If true the firewall will commit the changes, if false do not commit changes.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/renamegroup:
|
||||
panos.rename_config:
|
||||
- xpath: /config/devices/entry/vsys/entry[@name='vsys1']/address/entry[@name='old_address']
|
||||
- value: new_address
|
||||
- commit: True
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
if not xpath:
|
||||
return ret
|
||||
|
||||
if not newname:
|
||||
return ret
|
||||
|
||||
query = {'type': 'config',
|
||||
'action': 'rename',
|
||||
'xpath': xpath,
|
||||
'newname': newname}
|
||||
|
||||
response = __proxy__['panos.call'](query)
|
||||
|
||||
ret.update({
|
||||
'changes': response,
|
||||
'result': True
|
||||
})
|
||||
|
||||
if commit is True:
|
||||
ret.update({
|
||||
'commit': __salt__['panos.commit'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def set_config(name, xpath=None, value=None, commit=False):
|
||||
'''
|
||||
Sets a Palo Alto XPATH to a specific value. This will always overwrite the existing value, even if it is not
|
||||
changed.
|
||||
|
||||
You can add or create a new object at a specified location in the configuration hierarchy. Use the xpath parameter
|
||||
to specify the location of the object in the configuration
|
||||
|
||||
name: The name of the module function to execute.
|
||||
|
||||
xpath(str): The XPATH of the configuration API tree to control.
|
||||
|
||||
value(str): The XML value to set. This must be a child to the XPATH.
|
||||
|
||||
commit(bool): If true the firewall will commit the changes, if false do not commit changes.
|
||||
|
||||
SLS Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
panos/hostname:
|
||||
panos.set_config:
|
||||
- xpath: /config/devices/entry[@name='localhost.localdomain']/deviceconfig/system
|
||||
- value: <hostname>foobar</hostname>
|
||||
- commit: True
|
||||
|
||||
'''
|
||||
ret = _default_ret(name)
|
||||
|
||||
if not xpath:
|
||||
return ret
|
||||
|
||||
if not value:
|
||||
return ret
|
||||
|
||||
query = {'type': 'config',
|
||||
'action': 'set',
|
||||
'xpath': xpath,
|
||||
'element': value}
|
||||
|
||||
response = __proxy__['panos.call'](query)
|
||||
|
||||
ret.update({
|
||||
'changes': response,
|
||||
'result': True
|
||||
})
|
||||
|
||||
if commit is True:
|
||||
ret.update({
|
||||
'commit': __salt__['panos.commit'](),
|
||||
'result': True
|
||||
})
|
||||
|
||||
return ret
|
@ -77,7 +77,7 @@ def _ruby_installed(ret, ruby, user=None):
|
||||
for version in __salt__['rbenv.versions'](user):
|
||||
if version == ruby:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Requested ruby exists.'
|
||||
ret['comment'] = 'Requested ruby exists'
|
||||
ret['default'] = default == ruby
|
||||
break
|
||||
|
||||
@ -97,7 +97,7 @@ def _check_and_install_ruby(ret, ruby, default=False, user=None):
|
||||
ret['default'] = default
|
||||
else:
|
||||
ret['result'] = False
|
||||
ret['comment'] = 'Could not install ruby.'
|
||||
ret['comment'] = 'Failed to install ruby'
|
||||
return ret
|
||||
|
||||
if default:
|
||||
@ -131,7 +131,11 @@ def installed(name, default=False, user=None):
|
||||
name = re.sub(r'^ruby-', '', name)
|
||||
|
||||
if __opts__['test']:
|
||||
ret = _ruby_installed(ret, name, user=user)
|
||||
if not ret['result']:
|
||||
ret['comment'] = 'Ruby {0} is set to be installed'.format(name)
|
||||
else:
|
||||
ret['comment'] = 'Ruby {0} is already installed'.format(name)
|
||||
return ret
|
||||
|
||||
rbenv_installed_ret = _check_and_install_rbenv(rbenv_installed_ret, user)
|
||||
@ -188,16 +192,22 @@ def absent(name, user=None):
|
||||
if name.startswith('ruby-'):
|
||||
name = re.sub(r'^ruby-', '', name)
|
||||
|
||||
if __opts__['test']:
|
||||
ret['comment'] = 'Ruby {0} is set to be uninstalled'.format(name)
|
||||
return ret
|
||||
|
||||
ret = _check_rbenv(ret, user)
|
||||
if ret['result'] is False:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Rbenv not installed, {0} not either'.format(name)
|
||||
return ret
|
||||
else:
|
||||
if __opts__['test']:
|
||||
ret = _ruby_installed(ret, name, user=user)
|
||||
if ret['result']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Ruby {0} is set to be uninstalled'.format(name)
|
||||
else:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Ruby {0} is already uninstalled'.format(name)
|
||||
return ret
|
||||
|
||||
return _check_and_uninstall_ruby(ret, name, user=user)
|
||||
|
||||
|
||||
@ -205,7 +215,6 @@ def _check_and_install_rbenv(ret, user=None):
|
||||
'''
|
||||
Verify that rbenv is installed, install if unavailable
|
||||
'''
|
||||
|
||||
ret = _check_rbenv(ret, user)
|
||||
if ret['result'] is False:
|
||||
if __salt__['rbenv.install'](user):
|
||||
@ -216,7 +225,7 @@ def _check_and_install_rbenv(ret, user=None):
|
||||
ret['comment'] = 'Rbenv failed to install'
|
||||
else:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Rbenv already installed'
|
||||
ret['comment'] = 'Rbenv is already installed'
|
||||
|
||||
return ret
|
||||
|
||||
@ -237,7 +246,13 @@ def install_rbenv(name, user=None):
|
||||
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
|
||||
|
||||
if __opts__['test']:
|
||||
ret = _check_rbenv(ret, user=user)
|
||||
if ret['result'] is False:
|
||||
ret['result'] = None
|
||||
ret['comment'] = 'Rbenv is set to be installed'
|
||||
else:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Rbenv is already installed'
|
||||
return ret
|
||||
|
||||
return _check_and_install_rbenv(ret, user)
|
||||
|
@ -210,6 +210,21 @@ def ip_to_host(ip):
|
||||
# pylint: enable=C0103
|
||||
|
||||
|
||||
def is_reachable_host(entity_name):
|
||||
'''
|
||||
Returns a bool telling if the entity name is a reachable host (IPv4/IPv6/FQDN/etc).
|
||||
:param hostname:
|
||||
:return:
|
||||
'''
|
||||
try:
|
||||
assert type(socket.getaddrinfo(entity_name, 0, 0, 0, 0)) == list
|
||||
ret = True
|
||||
except socket.gaierror:
|
||||
ret = False
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def is_ip(ip):
|
||||
'''
|
||||
Returns a bool telling if the passed IP is a valid IPv4 or IPv6 address.
|
||||
|
@ -1058,6 +1058,13 @@ class TargetOptionsMixIn(six.with_metaclass(MixInMeta, object)):
|
||||
self, 'Target Options', 'Target selection options.'
|
||||
)
|
||||
self.add_option_group(group)
|
||||
group.add_option(
|
||||
'-H', '--hosts',
|
||||
default=False,
|
||||
action='store_true',
|
||||
dest='list_hosts',
|
||||
help='List all known hosts to currently visible or other specified rosters'
|
||||
)
|
||||
group.add_option(
|
||||
'-E', '--pcre',
|
||||
default=False,
|
||||
@ -3038,6 +3045,14 @@ class SaltSSHOptionParser(six.with_metaclass(OptionParserMeta,
|
||||
action='store_true',
|
||||
help='Run command via sudo.'
|
||||
)
|
||||
auth_group.add_option(
|
||||
'--skip-roster',
|
||||
dest='ssh_skip_roster',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='If hostname is not found in the roster, do not store the information'
|
||||
'into the default roster file (flat).'
|
||||
)
|
||||
self.add_option_group(auth_group)
|
||||
|
||||
scan_group = optparse.OptionGroup(
|
||||
|
@ -33,36 +33,71 @@ class RbenvTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
Test to verify that the specified ruby is installed with rbenv.
|
||||
'''
|
||||
name = 'rbenv-deps'
|
||||
# rbenv.is_installed is used wherever test is False.
|
||||
mock_is = MagicMock(side_effect=[False, True, True, True, True])
|
||||
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': ''}
|
||||
# rbenv.install is only called when an action is attempted
|
||||
# (ie. Successfully... or Failed...)
|
||||
mock_i = MagicMock(side_effect=[False, False, False])
|
||||
|
||||
mock_t = MagicMock(side_effect=[False, True, True])
|
||||
mock_f = MagicMock(return_value=False)
|
||||
mock_def = MagicMock(return_value='2.7')
|
||||
mock_ver = MagicMock(return_value=['2.7'])
|
||||
# rbenv.install_ruby is only called when rbenv is successfully
|
||||
# installed and an attempt to install a version of Ruby is
|
||||
# made.
|
||||
mock_ir = MagicMock(side_effect=[True, False])
|
||||
mock_def = MagicMock(return_value='2.3.4')
|
||||
mock_ver = MagicMock(return_value=['2.3.4', '2.4.1'])
|
||||
with patch.dict(rbenv.__salt__,
|
||||
{'rbenv.is_installed': mock_f,
|
||||
'rbenv.install': mock_t,
|
||||
{'rbenv.is_installed': mock_is,
|
||||
'rbenv.install': mock_i,
|
||||
'rbenv.default': mock_def,
|
||||
'rbenv.versions': mock_ver,
|
||||
'rbenv.install_ruby': mock_t}):
|
||||
'rbenv.install_ruby': mock_ir}):
|
||||
with patch.dict(rbenv.__opts__, {'test': True}):
|
||||
comt = ('Ruby rbenv-deps is set to be installed')
|
||||
ret.update({'comment': comt, 'result': None})
|
||||
name = '1.9.3-p551'
|
||||
comt = 'Ruby {0} is set to be installed'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'result': None}
|
||||
self.assertDictEqual(rbenv.installed(name), ret)
|
||||
|
||||
name = '2.4.1'
|
||||
comt = 'Ruby {0} is already installed'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'default': False, 'result': True}
|
||||
self.assertDictEqual(rbenv.installed(name), ret)
|
||||
|
||||
name = '2.3.4'
|
||||
comt = 'Ruby {0} is already installed'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'default': True, 'result': True}
|
||||
self.assertDictEqual(rbenv.installed(name), ret)
|
||||
|
||||
with patch.dict(rbenv.__opts__, {'test': False}):
|
||||
comt = ('Rbenv failed to install')
|
||||
ret.update({'comment': comt, 'result': False})
|
||||
name = '2.4.1'
|
||||
comt = 'Rbenv failed to install'
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'result': False}
|
||||
self.assertDictEqual(rbenv.installed(name), ret)
|
||||
|
||||
comt = ('Successfully installed ruby')
|
||||
ret.update({'comment': comt, 'result': True, 'default': False,
|
||||
'changes': {name: 'Installed'}})
|
||||
comt = 'Requested ruby exists'
|
||||
ret = {'name': name, 'comment': comt, 'default': False,
|
||||
'changes': {}, 'result': True}
|
||||
self.assertDictEqual(rbenv.installed(name), ret)
|
||||
|
||||
name = '2.3.4'
|
||||
comt = 'Requested ruby exists'
|
||||
ret = {'name': name, 'comment': comt, 'default': True,
|
||||
'changes': {}, 'result': True}
|
||||
self.assertDictEqual(rbenv.installed(name), ret)
|
||||
|
||||
name = '1.9.3-p551'
|
||||
comt = 'Successfully installed ruby'
|
||||
ret = {'name': name, 'comment': comt, 'default': False,
|
||||
'changes': {name: 'Installed'}, 'result': True}
|
||||
self.assertDictEqual(rbenv.installed(name), ret)
|
||||
|
||||
comt = 'Failed to install ruby'
|
||||
ret = {'name': name, 'comment': comt,
|
||||
'changes': {}, 'result': False}
|
||||
self.assertDictEqual(rbenv.installed(name), ret)
|
||||
|
||||
# 'absent' function tests: 1
|
||||
@ -71,32 +106,76 @@ class RbenvTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
Test to verify that the specified ruby is not installed with rbenv.
|
||||
'''
|
||||
name = 'myqueue'
|
||||
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': ''}
|
||||
|
||||
mock = MagicMock(side_effect=[False, True])
|
||||
mock_def = MagicMock(return_value='2.7')
|
||||
mock_ver = MagicMock(return_value=['2.7'])
|
||||
# rbenv.is_installed is used for all tests here.
|
||||
mock_is = MagicMock(side_effect=[False, True, True, True, False,
|
||||
True, True, True, True, True])
|
||||
# rbenv.uninstall_ruby is only called when an action is
|
||||
# attempted (ie. Successfully... or Failed...)
|
||||
mock_uninstalled = MagicMock(side_effect=[True, False, False, True])
|
||||
mock_def = MagicMock(return_value='2.3.4')
|
||||
mock_ver = MagicMock(return_value=['2.3.4', '2.4.1'])
|
||||
with patch.dict(rbenv.__salt__,
|
||||
{'rbenv.is_installed': mock,
|
||||
{'rbenv.is_installed': mock_is,
|
||||
'rbenv.default': mock_def,
|
||||
'rbenv.versions': mock_ver}):
|
||||
'rbenv.versions': mock_ver,
|
||||
'rbenv.uninstall_ruby': mock_uninstalled}):
|
||||
|
||||
with patch.dict(rbenv.__opts__, {'test': True}):
|
||||
comt = ('Ruby myqueue is set to be uninstalled')
|
||||
ret.update({'comment': comt, 'result': None})
|
||||
name = '1.9.3-p551'
|
||||
comt = 'Rbenv not installed, {0} not either'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'result': True}
|
||||
self.assertDictEqual(rbenv.absent(name), ret)
|
||||
|
||||
comt = 'Ruby {0} is already uninstalled'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'result': True}
|
||||
self.assertDictEqual(rbenv.absent(name), ret)
|
||||
|
||||
name = '2.3.4'
|
||||
comt = 'Ruby {0} is set to be uninstalled'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'default': True, 'result': None}
|
||||
self.assertDictEqual(rbenv.absent('2.3.4'), ret)
|
||||
|
||||
name = '2.4.1'
|
||||
comt = 'Ruby {0} is set to be uninstalled'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'default': False, 'result': None}
|
||||
self.assertDictEqual(rbenv.absent('2.4.1'), ret)
|
||||
|
||||
with patch.dict(rbenv.__opts__, {'test': False}):
|
||||
comt = ('Rbenv not installed, myqueue not either')
|
||||
ret.update({'comment': comt, 'result': True})
|
||||
name = '1.9.3-p551'
|
||||
comt = 'Rbenv not installed, {0} not either'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'result': True}
|
||||
self.assertDictEqual(rbenv.absent(name), ret)
|
||||
|
||||
comt = ('Ruby myqueue is already absent')
|
||||
ret.update({'comment': comt, 'result': True})
|
||||
comt = 'Ruby {0} is already absent'.format(name)
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'result': True}
|
||||
self.assertDictEqual(rbenv.absent(name), ret)
|
||||
|
||||
name = '2.3.4'
|
||||
comt = 'Successfully removed ruby'
|
||||
ret = {'name': name, 'changes': {name: 'Uninstalled'},
|
||||
'comment': comt, 'default': True, 'result': True}
|
||||
self.assertDictEqual(rbenv.absent(name), ret)
|
||||
|
||||
comt = 'Failed to uninstall ruby'
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'default': True, 'result': False}
|
||||
self.assertDictEqual(rbenv.absent(name), ret)
|
||||
|
||||
name = '2.4.1'
|
||||
comt = 'Failed to uninstall ruby'
|
||||
ret = {'name': name, 'changes': {}, 'comment': comt,
|
||||
'default': False, 'result': False}
|
||||
self.assertDictEqual(rbenv.absent(name), ret)
|
||||
|
||||
comt = 'Successfully removed ruby'
|
||||
ret = {'name': name, 'changes': {name: 'Uninstalled'},
|
||||
'comment': comt, 'default': False, 'result': True}
|
||||
self.assertDictEqual(rbenv.absent(name), ret)
|
||||
|
||||
# 'install_rbenv' function tests: 1
|
||||
@ -112,16 +191,30 @@ class RbenvTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'result': True,
|
||||
'comment': ''}
|
||||
|
||||
mock_is = MagicMock(side_effect=[False, True, True, False, False])
|
||||
mock_i = MagicMock(side_effect=[False, True])
|
||||
with patch.dict(rbenv.__salt__,
|
||||
{'rbenv.is_installed': mock_is,
|
||||
'rbenv.install': mock_i}):
|
||||
|
||||
with patch.dict(rbenv.__opts__, {'test': True}):
|
||||
comt = ('Rbenv is set to be installed')
|
||||
comt = 'Rbenv is set to be installed'
|
||||
ret.update({'comment': comt, 'result': None})
|
||||
self.assertDictEqual(rbenv.install_rbenv(name), ret)
|
||||
|
||||
with patch.dict(rbenv.__opts__, {'test': False}):
|
||||
mock = MagicMock(side_effect=[False, True])
|
||||
with patch.dict(rbenv.__salt__,
|
||||
{'rbenv.is_installed': mock,
|
||||
'rbenv.install': mock}):
|
||||
comt = ('Rbenv installed')
|
||||
comt = 'Rbenv is already installed'
|
||||
ret.update({'comment': comt, 'result': True})
|
||||
self.assertDictEqual(rbenv.install_rbenv(name), ret)
|
||||
|
||||
with patch.dict(rbenv.__opts__, {'test': False}):
|
||||
comt = 'Rbenv is already installed'
|
||||
ret.update({'comment': comt, 'result': True})
|
||||
self.assertDictEqual(rbenv.install_rbenv(name), ret)
|
||||
|
||||
comt = 'Rbenv failed to install'
|
||||
ret.update({'comment': comt, 'result': False})
|
||||
self.assertDictEqual(rbenv.install_rbenv(name), ret)
|
||||
|
||||
comt = 'Rbenv installed'
|
||||
ret.update({'comment': comt, 'result': True})
|
||||
self.assertDictEqual(rbenv.install_rbenv(name), ret)
|
||||
|
Loading…
Reference in New Issue
Block a user