mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00

Conflicts: * doc/topics/releases/neon.rst * requirements/tests.txt * salt/cli/caller.py * salt/daemons/test/__init__.py * salt/daemons/test/test_minion.py * salt/daemons/test/test_saltkeep.py * salt/modules/event.py * salt/modules/raet_publish.py * salt/transport/__init__.py * salt/utils/parsers.py * setup.py * tests/unit/modules/test_raet_publish.py
236 lines
8.3 KiB
Python
236 lines
8.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
'''
|
|
Use the :ref:`Salt Event System <events>` to fire events from the
|
|
master to the minion and vice-versa.
|
|
'''
|
|
|
|
# Import Python libs
|
|
from __future__ import absolute_import, print_function, unicode_literals
|
|
import collections
|
|
import logging
|
|
import os
|
|
import sys
|
|
import traceback
|
|
|
|
# Import salt libs
|
|
import salt.crypt
|
|
import salt.utils.event
|
|
import salt.utils.zeromq
|
|
import salt.payload
|
|
import salt.transport.client
|
|
from salt.ext import six
|
|
|
|
__proxyenabled__ = ['*']
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def _dict_subset(keys, master_dict):
|
|
'''
|
|
Return a dictionary of only the subset of keys/values specified in keys
|
|
'''
|
|
return dict([(k, v) for k, v in six.iteritems(master_dict) if k in keys])
|
|
|
|
|
|
def fire_master(data, tag, preload=None):
|
|
'''
|
|
Fire an event off up to the master server
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' event.fire_master '{"data":"my event data"}' 'tag'
|
|
'''
|
|
if (__opts__.get('local', None) or __opts__.get('file_client', None) == 'local') and not __opts__.get('use_master_when_local', False):
|
|
# We can't send an event if we're in masterless mode
|
|
log.warning('Local mode detected. Event with tag %s will NOT be sent.', tag)
|
|
return False
|
|
|
|
if preload or __opts__.get('__cli') == 'salt-call':
|
|
# If preload is specified, we must send a raw event (this is
|
|
# slower because it has to independently authenticate)
|
|
if 'master_uri' not in __opts__:
|
|
__opts__['master_uri'] = 'tcp://{ip}:{port}'.format(
|
|
ip=salt.utils.zeromq.ip_bracket(__opts__['interface']),
|
|
port=__opts__.get('ret_port', '4506') # TODO, no fallback
|
|
)
|
|
masters = list()
|
|
ret = True
|
|
if 'master_uri_list' in __opts__:
|
|
for master_uri in __opts__['master_uri_list']:
|
|
masters.append(master_uri)
|
|
else:
|
|
masters.append(__opts__['master_uri'])
|
|
auth = salt.crypt.SAuth(__opts__)
|
|
load = {'id': __opts__['id'],
|
|
'tag': tag,
|
|
'data': data,
|
|
'tok': auth.gen_token(b'salt'),
|
|
'cmd': '_minion_event'}
|
|
|
|
if isinstance(preload, dict):
|
|
load.update(preload)
|
|
|
|
for master in masters:
|
|
channel = salt.transport.client.ReqChannel.factory(__opts__, master_uri=master)
|
|
try:
|
|
channel.send(load)
|
|
# channel.send was successful.
|
|
# Ensure ret is True.
|
|
ret = True
|
|
except Exception:
|
|
ret = False
|
|
finally:
|
|
channel.close()
|
|
return ret
|
|
else:
|
|
# Usually, we can send the event via the minion, which is faster
|
|
# because it is already authenticated
|
|
try:
|
|
return salt.utils.event.MinionEvent(__opts__, listen=False).fire_event(
|
|
{'data': data, 'tag': tag, 'events': None, 'pretag': None}, 'fire_master')
|
|
except Exception:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
log.debug(lines)
|
|
return False
|
|
|
|
|
|
def fire(data, tag):
|
|
'''
|
|
Fire an event on the local minion event bus. Data must be formed as a dict.
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt '*' event.fire '{"data":"my event data"}' 'tag'
|
|
'''
|
|
try:
|
|
event = salt.utils.event.get_event('minion', # was __opts__['id']
|
|
sock_dir=__opts__['sock_dir'],
|
|
transport=__opts__['transport'],
|
|
opts=__opts__,
|
|
listen=False)
|
|
|
|
return event.fire_event(data, tag)
|
|
except Exception:
|
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
|
|
log.debug(lines)
|
|
return False
|
|
|
|
|
|
def send(tag,
|
|
data=None,
|
|
preload=None,
|
|
with_env=False,
|
|
with_grains=False,
|
|
with_pillar=False,
|
|
with_env_opts=False,
|
|
**kwargs):
|
|
'''
|
|
Send an event to the Salt Master
|
|
|
|
.. versionadded:: 2014.7.0
|
|
|
|
:param tag: A tag to give the event.
|
|
Use slashes to create a namespace for related events. E.g.,
|
|
``myco/build/buildserver1/start``, ``myco/build/buildserver1/success``,
|
|
``myco/build/buildserver1/failure``.
|
|
|
|
:param data: A dictionary of data to send in the event.
|
|
This is free-form. Send any data points that are needed for whoever is
|
|
consuming the event. Arguments on the CLI are interpreted as YAML so
|
|
complex data structures are possible.
|
|
|
|
:param with_env: Include environment variables from the current shell
|
|
environment in the event data as ``environ``.. This is a short-hand for
|
|
working with systems that seed the environment with relevant data such
|
|
as Jenkins.
|
|
:type with_env: Specify ``True`` to include all environment variables, or
|
|
specify a list of strings of variable names to include.
|
|
|
|
:param with_grains: Include grains from the current minion in the event
|
|
data as ``grains``.
|
|
:type with_grains: Specify ``True`` to include all grains, or specify a
|
|
list of strings of grain names to include.
|
|
|
|
:param with_pillar: Include Pillar values from the current minion in the
|
|
event data as ``pillar``. Remember Pillar data is often sensitive data
|
|
so be careful. This is useful for passing ephemeral Pillar values
|
|
through an event. Such as passing the ``pillar={}`` kwarg in
|
|
:py:func:`state.sls <salt.modules.state.sls>` from the Master, through
|
|
an event on the Minion, then back to the Master.
|
|
:type with_pillar: Specify ``True`` to include all Pillar values, or
|
|
specify a list of strings of Pillar keys to include. It is a
|
|
best-practice to only specify a relevant subset of Pillar data.
|
|
|
|
:param with_env_opts: Include ``saltenv`` and ``pillarenv`` set on minion
|
|
at the moment when event is send into event data.
|
|
:type with_env_opts: Specify ``True`` to include ``saltenv`` and
|
|
``pillarenv`` values or ``False`` to omit them.
|
|
|
|
:param kwargs: Any additional keyword arguments passed to this function
|
|
will be interpreted as key-value pairs and included in the event data.
|
|
This provides a convenient alternative to YAML for simple values.
|
|
|
|
CLI Example:
|
|
|
|
.. code-block:: bash
|
|
|
|
salt-call event.send myco/mytag foo=Foo bar=Bar
|
|
salt-call event.send 'myco/mytag' '{foo: Foo, bar: Bar}'
|
|
|
|
A convenient way to allow Jenkins to execute ``salt-call`` is via sudo. The
|
|
following rule in sudoers will allow the ``jenkins`` user to run only the
|
|
following command.
|
|
|
|
``/etc/sudoers`` (allow preserving the environment):
|
|
|
|
.. code-block:: text
|
|
|
|
jenkins ALL=(ALL) NOPASSWD:SETENV: /usr/bin/salt-call event.send*
|
|
|
|
Call Jenkins via sudo (preserve the environment):
|
|
|
|
.. code-block:: bash
|
|
|
|
sudo -E salt-call event.send myco/jenkins/build/success with_env=[BUILD_ID, BUILD_URL, GIT_BRANCH, GIT_COMMIT]
|
|
|
|
'''
|
|
data_dict = {}
|
|
|
|
if with_env:
|
|
if isinstance(with_env, list):
|
|
data_dict['environ'] = _dict_subset(with_env, dict(os.environ))
|
|
else:
|
|
data_dict['environ'] = dict(os.environ)
|
|
|
|
if with_grains:
|
|
if isinstance(with_grains, list):
|
|
data_dict['grains'] = _dict_subset(with_grains, __grains__)
|
|
else:
|
|
data_dict['grains'] = __grains__
|
|
|
|
if with_pillar:
|
|
if isinstance(with_pillar, list):
|
|
data_dict['pillar'] = _dict_subset(with_pillar, __pillar__)
|
|
else:
|
|
data_dict['pillar'] = __pillar__
|
|
|
|
if with_env_opts:
|
|
data_dict['saltenv'] = __opts__.get('saltenv', 'base')
|
|
data_dict['pillarenv'] = __opts__.get('pillarenv')
|
|
|
|
if kwargs:
|
|
data_dict.update(kwargs)
|
|
|
|
# Allow values in the ``data`` arg to override any of the above values.
|
|
if isinstance(data, collections.Mapping):
|
|
data_dict.update(data)
|
|
|
|
if __opts__.get('local') or __opts__.get('file_client') == 'local' or __opts__.get('master_type') == 'disable':
|
|
return fire(data_dict, tag)
|
|
else:
|
|
return fire_master(data_dict, tag, preload=preload)
|