mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 01:30:20 +00:00
Restore the previous slack engine and deprecate it, rename replace the slack engine to slack_bolt until deprecation
This commit is contained in:
parent
21463a2f46
commit
4212c320e6
24 changed files with 1765 additions and 568 deletions
1
changelog/63095.added
Normal file
1
changelog/63095.added
Normal file
|
@ -0,0 +1 @@
|
|||
Restore the previous slack engine and deprecate it, rename replace the slack engine to slack_bolt until deprecation
|
1
changelog/63095.deprecated
Normal file
1
changelog/63095.deprecated
Normal file
|
@ -0,0 +1 @@
|
|||
Deprecating the Salt Slack engine in favor of the Salt Slack Bolt Engine.
|
|
@ -23,6 +23,7 @@ engine modules
|
|||
redis_sentinel
|
||||
script
|
||||
slack
|
||||
slack_bolt_engine
|
||||
sqs_events
|
||||
stalekey
|
||||
test
|
||||
|
|
5
doc/ref/engines/all/salt.engines.slack_bolt_engine.rst
Normal file
5
doc/ref/engines/all/salt.engines.slack_bolt_engine.rst
Normal file
|
@ -0,0 +1,5 @@
|
|||
salt.engines.slack_bolt_engine
|
||||
==============================
|
||||
|
||||
.. automodule:: salt.engines.slack_bolt_engine
|
||||
:members:
|
|
@ -14,3 +14,4 @@ mercurial
|
|||
hglib
|
||||
redis-py-cluster
|
||||
python-consul
|
||||
slack_bolt
|
||||
|
|
|
@ -798,6 +798,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
snowballstemmer==2.1.0
|
||||
|
|
|
@ -797,6 +797,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -856,6 +856,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==3.0.4
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -820,6 +820,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
snowballstemmer==2.1.0
|
||||
|
|
|
@ -822,6 +822,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -879,6 +879,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==3.0.4
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -846,6 +846,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
snowballstemmer==2.1.0
|
||||
|
|
|
@ -848,6 +848,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -900,6 +900,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==3.0.4
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -838,6 +838,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
snowballstemmer==2.1.0
|
||||
|
|
|
@ -839,6 +839,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -890,6 +890,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==3.0.4
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -349,6 +349,8 @@ botocore==1.21.27
|
|||
# boto3
|
||||
# moto
|
||||
# s3transfer
|
||||
cached-property==1.5.2
|
||||
# via pygit2
|
||||
cachetools==4.2.2
|
||||
# via
|
||||
# google-auth
|
||||
|
@ -839,6 +841,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
snowballstemmer==2.1.0
|
||||
|
|
|
@ -347,6 +347,8 @@ botocore==1.21.27
|
|||
# boto3
|
||||
# moto
|
||||
# s3transfer
|
||||
cached-property==1.5.2
|
||||
# via pygit2
|
||||
cachetools==4.2.2
|
||||
# via
|
||||
# google-auth
|
||||
|
@ -840,6 +842,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==4.0.0
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -895,6 +895,10 @@ six==1.16.0
|
|||
# vcert
|
||||
# virtualenv
|
||||
# websocket-client
|
||||
slack-bolt==1.15.5
|
||||
# via -r requirements/static/ci/linux.in
|
||||
slack-sdk==3.19.5
|
||||
# via slack-bolt
|
||||
smmap==3.0.4
|
||||
# via gitdb
|
||||
sqlparse==0.4.2
|
||||
|
|
|
@ -3,47 +3,21 @@ An engine that reads messages from Slack and can act on them
|
|||
|
||||
.. versionadded:: 2016.3.0
|
||||
|
||||
:depends: `slack_bolt <https://pypi.org/project/slack_bolt/>`_ Python module
|
||||
:depends: `slackclient <https://pypi.org/project/slackclient/>`_ Python module
|
||||
|
||||
.. important::
|
||||
This engine requires a Slack app and a Slack Bot user. To create a
|
||||
bot user, first go to the **Custom Integrations** page in your
|
||||
Slack Workspace. Copy and paste the following URL, and log in with
|
||||
account credentials with administrative privileges:
|
||||
This engine requires a bot user. To create a bot user, first go to the
|
||||
**Custom Integrations** page in your Slack Workspace. Copy and paste the
|
||||
following URL, and replace ``myworkspace`` with the proper value for your
|
||||
workspace:
|
||||
|
||||
``https://api.slack.com/apps/new``
|
||||
``https://myworkspace.slack.com/apps/manage/custom-integrations``
|
||||
|
||||
Next, click on the ``From scratch`` option from the ``Create an app`` popup.
|
||||
Give your new app a unique name, eg. ``SaltSlackEngine``, select the workspace
|
||||
where your app will be running, and click ``Create App``.
|
||||
|
||||
Next, click on ``Socket Mode`` and then click on the toggle button for
|
||||
``Enable Socket Mode``. In the dialog give your Socket Mode Token a unique
|
||||
name and then copy and save the app level token. This will be used
|
||||
as the ``app_token`` parameter in the Slack engine configuration.
|
||||
|
||||
Next, click on ``Event Subscriptions`` and ensure that ``Enable Events`` is in
|
||||
the on position. Then add the following bot events, ``message.channel``
|
||||
and ``message.im`` to the ``Subcribe to bot events`` list.
|
||||
|
||||
Next, click on ``OAuth & Permissions`` and then under ``Bot Token Scope``, click
|
||||
on ``Add an OAuth Scope``. Ensure the following scopes are included:
|
||||
|
||||
- ``channels:history``
|
||||
- ``channels:read``
|
||||
- ``chat:write``
|
||||
- ``commands``
|
||||
- ``files:read``
|
||||
- ``files:write``
|
||||
- ``im:history``
|
||||
- ``mpim:history``
|
||||
- ``usergroups:read``
|
||||
- ``users:read``
|
||||
|
||||
Once all the scopes have been added, click the ``Install to Workspace`` button
|
||||
under ``OAuth Tokens for Your Workspace``, then click ``Allow``. Copy and save
|
||||
the ``Bot User OAuth Token``, this will be used as the ``bot_token`` parameter
|
||||
in the Slack engine configuration.
|
||||
Next, click on the ``Bots`` integration and request installation. Once
|
||||
approved by an admin, you will be able to proceed with adding the bot user.
|
||||
Once the bot user has been added, you can configure it by adding an avatar,
|
||||
setting the display name, etc. You will also at this time have access to
|
||||
your API token, which will be needed to configure this engine.
|
||||
|
||||
Finally, add this bot user to a channel by switching to the channel and
|
||||
using ``/invite @mybotuser``. Keep in mind that this engine will process
|
||||
|
@ -100,9 +74,6 @@ Configuration Examples
|
|||
.. versionchanged:: 2017.7.0
|
||||
Access control group support added
|
||||
|
||||
.. versionchanged:: 3006.0
|
||||
Updated to use slack_bolt Python library.
|
||||
|
||||
This example uses a single group called ``default``. In addition, other groups
|
||||
are being loaded from pillar data. The group names do not have any
|
||||
significance, it is the users and commands defined within them that are used to
|
||||
|
@ -112,8 +83,7 @@ determine whether the Slack user has permission to run the desired command.
|
|||
|
||||
engines:
|
||||
- slack:
|
||||
app_token: "xapp-x-xxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
bot_token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
control: True
|
||||
fire_all: False
|
||||
groups_pillar_name: 'slack_engine:groups_pillar'
|
||||
|
@ -151,8 +121,7 @@ must be quoted, or else PyYAML will fail to load the configuration.
|
|||
engines:
|
||||
- slack:
|
||||
groups_pillar: slack_engine_pillar
|
||||
app_token: "xapp-x-xxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
bot_token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
control: True
|
||||
fire_all: True
|
||||
tag: salt/engines/slack
|
||||
|
@ -177,7 +146,6 @@ must be quoted, or else PyYAML will fail to load the configuration.
|
|||
"""
|
||||
|
||||
import ast
|
||||
import collections
|
||||
import datetime
|
||||
import itertools
|
||||
import logging
|
||||
|
@ -198,12 +166,11 @@ import salt.utils.slack
|
|||
import salt.utils.yaml
|
||||
|
||||
try:
|
||||
import slack_bolt
|
||||
import slack_bolt.adapter.socket_mode
|
||||
import slackclient
|
||||
|
||||
HAS_SLACKBOLT = True
|
||||
HAS_SLACKCLIENT = True
|
||||
except ImportError:
|
||||
HAS_SLACKBOLT = False
|
||||
HAS_SLACKCLIENT = False
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -211,38 +178,17 @@ __virtualname__ = "slack"
|
|||
|
||||
|
||||
def __virtual__():
|
||||
if not HAS_SLACKBOLT:
|
||||
return (False, "The 'slack_bolt' Python module could not be loaded")
|
||||
if not HAS_SLACKCLIENT:
|
||||
return (False, "The 'slackclient' Python module could not be loaded")
|
||||
return __virtualname__
|
||||
|
||||
|
||||
class SlackClient:
|
||||
def __init__(self, app_token, bot_token, trigger_string):
|
||||
def __init__(self, token):
|
||||
self.master_minion = salt.minion.MasterMinion(__opts__)
|
||||
|
||||
self.app = slack_bolt.App(token=bot_token)
|
||||
self.handler = slack_bolt.adapter.socket_mode.SocketModeHandler(
|
||||
self.app, app_token
|
||||
)
|
||||
self.handler.connect()
|
||||
|
||||
self.app_token = app_token
|
||||
self.bot_token = bot_token
|
||||
|
||||
self.msg_queue = collections.deque()
|
||||
|
||||
trigger_pattern = "(^{}.*)".format(trigger_string)
|
||||
|
||||
# Register message_trigger when we see messages that start
|
||||
# with the trigger string
|
||||
self.app.message(re.compile(trigger_pattern))(self.message_trigger)
|
||||
|
||||
def _run_until(self):
|
||||
return True
|
||||
|
||||
def message_trigger(self, message):
|
||||
# Add the received message to the queue
|
||||
self.msg_queue.append(message)
|
||||
self.sc = slackclient.SlackClient(token)
|
||||
self.slack_connect = self.sc.rtm_connect()
|
||||
|
||||
def get_slack_users(self, token):
|
||||
"""
|
||||
|
@ -597,12 +543,13 @@ class SlackClient:
|
|||
return data
|
||||
|
||||
for sleeps in (5, 10, 30, 60):
|
||||
if self.handler:
|
||||
if self.slack_connect:
|
||||
break
|
||||
else:
|
||||
# see https://api.slack.com/docs/rate-limits
|
||||
log.warning(
|
||||
"Slack connection is invalid, sleeping %s",
|
||||
"Slack connection is invalid. Server: %s, sleeping %s",
|
||||
self.sc.server,
|
||||
sleeps,
|
||||
)
|
||||
time.sleep(
|
||||
|
@ -611,51 +558,51 @@ class SlackClient:
|
|||
else:
|
||||
raise UserWarning(
|
||||
"Connection to slack is still invalid, giving up: {}".format(
|
||||
self.handler
|
||||
self.slack_connect
|
||||
)
|
||||
) # Boom!
|
||||
while self._run_until():
|
||||
while self.msg_queue:
|
||||
msg = self.msg_queue.popleft()
|
||||
while True:
|
||||
msg = self.sc.rtm_read()
|
||||
for m_data in msg:
|
||||
try:
|
||||
msg_text = self.message_text(msg)
|
||||
msg_text = self.message_text(m_data)
|
||||
except (ValueError, TypeError) as msg_err:
|
||||
log.debug(
|
||||
"Got an error from trying to get the message text %s", msg_err
|
||||
)
|
||||
yield {"message_data": msg} # Not a message type from the API?
|
||||
yield {"message_data": m_data} # Not a message type from the API?
|
||||
continue
|
||||
|
||||
# Find the channel object from the channel name
|
||||
channel = msg["channel"]
|
||||
data = just_data(msg)
|
||||
channel = self.sc.server.channels.find(m_data["channel"])
|
||||
data = just_data(m_data)
|
||||
if msg_text.startswith(trigger_string):
|
||||
loaded_groups = self.get_config_groups(groups, groups_pillar_name)
|
||||
if not data.get("user_name"):
|
||||
log.error(
|
||||
"The user %s can not be looked up via slack. What has"
|
||||
" happened here?",
|
||||
msg.get("user"),
|
||||
m_data.get("user"),
|
||||
)
|
||||
channel.send_message(
|
||||
"The user {} can not be looked up via slack. Not"
|
||||
" running {}".format(data["user_id"], msg_text)
|
||||
)
|
||||
yield {"message_data": msg}
|
||||
yield {"message_data": m_data}
|
||||
continue
|
||||
(allowed, target, cmdline) = self.control_message_target(
|
||||
data["user_name"], msg_text, loaded_groups, trigger_string
|
||||
)
|
||||
log.debug("Got target: %s, cmdline: %s", target, cmdline)
|
||||
if allowed:
|
||||
ret = {
|
||||
"message_data": msg,
|
||||
"channel": msg["channel"],
|
||||
yield {
|
||||
"message_data": m_data,
|
||||
"channel": m_data["channel"],
|
||||
"user": data["user_id"],
|
||||
"user_name": data["user_name"],
|
||||
"cmdline": cmdline,
|
||||
"target": target,
|
||||
}
|
||||
yield ret
|
||||
continue
|
||||
else:
|
||||
channel.send_message(
|
||||
|
@ -823,48 +770,45 @@ class SlackClient:
|
|||
|
||||
outstanding = {} # set of job_id that we need to check for
|
||||
|
||||
while self._run_until():
|
||||
while True:
|
||||
log.trace("Sleeping for interval of %s", interval)
|
||||
time.sleep(interval)
|
||||
# Drain the slack messages, up to 10 messages at a clip
|
||||
count = 0
|
||||
for msg in message_generator:
|
||||
if msg:
|
||||
# The message_generator yields dicts. Leave this loop
|
||||
# on a dict that looks like {'done': True} or when we've done it
|
||||
# 10 times without taking a break.
|
||||
log.trace("Got a message from the generator: %s", msg.keys())
|
||||
if count > 10:
|
||||
log.warning(
|
||||
"Breaking in getting messages because count is exceeded"
|
||||
)
|
||||
break
|
||||
if not msg:
|
||||
count += 1
|
||||
log.warning("Skipping an empty message.")
|
||||
continue # This one is a dud, get the next message
|
||||
if msg.get("done"):
|
||||
log.trace("msg is done")
|
||||
break
|
||||
if fire_all:
|
||||
log.debug("Firing message to the bus with tag: %s", tag)
|
||||
log.debug("%s %s", tag, msg)
|
||||
self.fire(
|
||||
"{}/{}".format(tag, msg["message_data"].get("type")), msg
|
||||
)
|
||||
if control and (len(msg) > 1) and msg.get("cmdline"):
|
||||
jid = self.run_command_async(msg)
|
||||
log.debug("Submitted a job and got jid: %s", jid)
|
||||
outstanding[
|
||||
jid
|
||||
] = msg # record so we can return messages to the caller
|
||||
text_msg = "@{}'s job is submitted as salt jid {}".format(
|
||||
# The message_generator yields dicts. Leave this loop
|
||||
# on a dict that looks like {'done': True} or when we've done it
|
||||
# 10 times without taking a break.
|
||||
log.trace("Got a message from the generator: %s", msg.keys())
|
||||
if count > 10:
|
||||
log.warning(
|
||||
"Breaking in getting messages because count is exceeded"
|
||||
)
|
||||
break
|
||||
if not msg:
|
||||
count += 1
|
||||
log.warning("Skipping an empty message.")
|
||||
continue # This one is a dud, get the next message
|
||||
if msg.get("done"):
|
||||
log.trace("msg is done")
|
||||
break
|
||||
if fire_all:
|
||||
log.debug("Firing message to the bus with tag: %s", tag)
|
||||
log.debug("%s %s", tag, msg)
|
||||
self.fire("{}/{}".format(tag, msg["message_data"].get("type")), msg)
|
||||
if control and (len(msg) > 1) and msg.get("cmdline"):
|
||||
channel = self.sc.server.channels.find(msg["channel"])
|
||||
jid = self.run_command_async(msg)
|
||||
log.debug("Submitted a job and got jid: %s", jid)
|
||||
outstanding[
|
||||
jid
|
||||
] = msg # record so we can return messages to the caller
|
||||
channel.send_message(
|
||||
"@{}'s job is submitted as salt jid {}".format(
|
||||
msg["user_name"], jid
|
||||
)
|
||||
self.app.client.chat_postMessage(
|
||||
channel=msg["channel"], text=text_msg
|
||||
)
|
||||
count += 1
|
||||
)
|
||||
count += 1
|
||||
start_time = time.time()
|
||||
job_status = self.get_jobs_from_runner(
|
||||
outstanding.keys()
|
||||
|
@ -881,7 +825,7 @@ class SlackClient:
|
|||
log.debug("ret to send back is %s", result)
|
||||
# formatting function?
|
||||
this_job = outstanding[jid]
|
||||
channel = this_job["channel"]
|
||||
channel = self.sc.server.channels.find(this_job["channel"])
|
||||
return_text = self.format_return_text(result, function)
|
||||
return_prefix = (
|
||||
"@{}'s job `{}` (id: {}) (target: {}) returned".format(
|
||||
|
@ -891,19 +835,19 @@ class SlackClient:
|
|||
this_job["target"],
|
||||
)
|
||||
)
|
||||
self.app.client.chat_postMessage(
|
||||
channel=channel, text=return_prefix
|
||||
)
|
||||
channel.send_message(return_prefix)
|
||||
ts = time.time()
|
||||
st = datetime.datetime.fromtimestamp(ts).strftime("%Y%m%d%H%M%S%f")
|
||||
filename = "salt-results-{}.yaml".format(st)
|
||||
resp = self.app.client.files_upload(
|
||||
channels=channel,
|
||||
r = self.sc.api_call(
|
||||
"files.upload",
|
||||
channels=channel.id,
|
||||
filename=filename,
|
||||
content=return_text,
|
||||
)
|
||||
# Handle unicode return
|
||||
log.debug("Got back %s via the slack client", resp)
|
||||
log.debug("Got back %s via the slack client", r)
|
||||
resp = salt.utils.yaml.safe_load(salt.utils.json.dumps(r))
|
||||
if "ok" in resp and resp["ok"] is False:
|
||||
this_job["channel"].send_message(
|
||||
"Error: {}".format(resp["error"])
|
||||
|
@ -971,8 +915,7 @@ class SlackClient:
|
|||
|
||||
|
||||
def start(
|
||||
app_token,
|
||||
bot_token,
|
||||
token,
|
||||
control=False,
|
||||
trigger="!",
|
||||
groups=None,
|
||||
|
@ -984,17 +927,23 @@ def start(
|
|||
Listen to slack events and forward them to salt, new version
|
||||
"""
|
||||
|
||||
if (not bot_token) or (not bot_token.startswith("xoxb")):
|
||||
salt.utils.versions.warn_until(
|
||||
"Argon",
|
||||
"This 'slack' engine will be deprecated and "
|
||||
"will be replace by the slack_bolt engine. This new "
|
||||
"engine will use the new Bolt library from Slack and requires "
|
||||
"a Slack app and a Slack bot account.",
|
||||
)
|
||||
|
||||
if (not token) or (not token.startswith("xoxb")):
|
||||
time.sleep(2) # don't respawn too quickly
|
||||
log.error("Slack bot token not found, bailing...")
|
||||
raise UserWarning("Slack Engine bot token not configured")
|
||||
|
||||
try:
|
||||
client = SlackClient(
|
||||
app_token=app_token, bot_token=bot_token, trigger_string=trigger
|
||||
)
|
||||
client = SlackClient(token=token)
|
||||
message_generator = client.generate_triggered_messages(
|
||||
bot_token, trigger, groups, groups_pillar_name
|
||||
token, trigger, groups, groups_pillar_name
|
||||
)
|
||||
client.run_commands_from_slack_async(message_generator, fire_all, tag, control)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
|
|
1077
salt/engines/slack_bolt_engine.py
Normal file
1077
salt/engines/slack_bolt_engine.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -4,110 +4,30 @@ unit tests for the slack engine
|
|||
import pytest
|
||||
|
||||
import salt.config
|
||||
import salt.engines.slack as slack_engine
|
||||
from tests.support.mock import MagicMock, call, patch
|
||||
import salt.engines.slack as slack
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(
|
||||
slack_engine.HAS_SLACKBOLT is False, reason="The slack_bolt is not installed"
|
||||
slack.HAS_SLACKCLIENT is False, reason="The SlackClient is not installed"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class MockRunnerClient:
|
||||
"""
|
||||
Mock RunnerClient class
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def asynchronous(self, *args, **kwargs):
|
||||
"""
|
||||
Mock asynchronous method
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
class MockLocalClient:
|
||||
"""
|
||||
Mock RunnerClient class
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def cmd_async(self, *args, **kwargs):
|
||||
"""
|
||||
Mock cmd_async method
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
class MockSlackBoltSocketMode:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def connect(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
class MockSlackBoltApp:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
self.client = MockSlackBoltAppClient()
|
||||
self.logger = None
|
||||
self.proxy = None
|
||||
|
||||
def message(self, *args, **kwargs):
|
||||
return MagicMock(return_value=True)
|
||||
|
||||
|
||||
class MockSlackBoltAppClient:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def chat_postMessage(self, *args, **kwargs):
|
||||
return MagicMock(return_value=True)
|
||||
|
||||
def files_upload(self, *args, **kwargs):
|
||||
return MagicMock(return_value=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def configure_loader_modules():
|
||||
return {slack_engine: {}}
|
||||
return {slack: {}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def slack_client(minion_opts):
|
||||
def slack_client():
|
||||
mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
|
||||
app_token = "xapp-x-xxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
bot_token = "xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
trigger = "!"
|
||||
token = "xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
with patch.dict(slack_engine.__opts__, minion_opts):
|
||||
with patch(
|
||||
"slack_bolt.App", MagicMock(autospec=True, return_value=MockSlackBoltApp())
|
||||
):
|
||||
with patch(
|
||||
"slack_bolt.adapter.socket_mode.SocketModeHandler",
|
||||
MagicMock(autospec=True, return_value=MockSlackBoltSocketMode()),
|
||||
):
|
||||
slack_client = slack_engine.SlackClient(app_token, bot_token, trigger)
|
||||
yield slack_client
|
||||
with patch.dict(slack.__opts__, mock_opts):
|
||||
with patch("slackclient.SlackClient.rtm_connect", MagicMock(return_value=True)):
|
||||
slack_client = slack.SlackClient(token)
|
||||
yield slack_client
|
||||
|
||||
|
||||
def test_control_message_target(slack_client):
|
||||
|
@ -173,341 +93,3 @@ def test_control_message_target(slack_client):
|
|||
)
|
||||
|
||||
assert target_commandline == _expected
|
||||
|
||||
|
||||
def test_run_commands_from_slack_async(slack_client):
|
||||
"""
|
||||
Test slack engine: test_run_commands_from_slack_async
|
||||
"""
|
||||
|
||||
mock_job_status = {
|
||||
"20221027001127600438": {
|
||||
"data": {"minion": {"return": True, "retcode": 0, "success": True}},
|
||||
"function": "test.ping",
|
||||
}
|
||||
}
|
||||
|
||||
message_generator = [
|
||||
{
|
||||
"message_data": {
|
||||
"client_msg_id": "c1d0c13d-5e78-431e-9921-4786a7d27543",
|
||||
"type": "message",
|
||||
"text": '!test.ping target="minion"',
|
||||
"user": "U02QY11UJ",
|
||||
"ts": "1666829486.542159",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "2vdy",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": '!test.ping target="minion"',
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"team": "T02QY11UG",
|
||||
"channel": "C02QY11UQ",
|
||||
"event_ts": "1666829486.542159",
|
||||
"channel_type": "channel",
|
||||
},
|
||||
"channel": "C02QY11UQ",
|
||||
"user": "U02QY11UJ",
|
||||
"user_name": "garethgreenaway",
|
||||
"cmdline": ["test.ping"],
|
||||
"target": {"target": "minion", "tgt_type": "glob"},
|
||||
}
|
||||
]
|
||||
|
||||
mock_files_upload_resp = {
|
||||
"ok": True,
|
||||
"file": {
|
||||
"id": "F047YTDGJF9",
|
||||
"created": 1666883749,
|
||||
"timestamp": 1666883749,
|
||||
"name": "salt-results-20221027081549173603.yaml",
|
||||
"title": "salt-results-20221027081549173603",
|
||||
"mimetype": "text/plain",
|
||||
"filetype": "yaml",
|
||||
"pretty_type": "YAML",
|
||||
"user": "U0485K894PN",
|
||||
"user_team": "T02QY11UG",
|
||||
"editable": True,
|
||||
"size": 18,
|
||||
"mode": "snippet",
|
||||
"is_external": False,
|
||||
"external_type": "",
|
||||
"is_public": True,
|
||||
"public_url_shared": False,
|
||||
"display_as_bot": False,
|
||||
"username": "",
|
||||
"url_private": "",
|
||||
"url_private_download": "",
|
||||
"permalink": "",
|
||||
"permalink_public": "",
|
||||
"edit_link": "",
|
||||
"preview": "minion:\n True",
|
||||
"preview_highlight": "",
|
||||
"lines": 2,
|
||||
"lines_more": 0,
|
||||
"preview_is_truncated": False,
|
||||
"comments_count": 0,
|
||||
"is_starred": False,
|
||||
"shares": {
|
||||
"public": {
|
||||
"C02QY11UQ": [
|
||||
{
|
||||
"reply_users": [],
|
||||
"reply_users_count": 0,
|
||||
"reply_count": 0,
|
||||
"ts": "1666883749.485979",
|
||||
"channel_name": "general",
|
||||
"team_id": "T02QY11UG",
|
||||
"share_user_id": "U0485K894PN",
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"channels": ["C02QY11UQ"],
|
||||
"groups": [],
|
||||
"ims": [],
|
||||
"has_rich_preview": False,
|
||||
"file_access": "visible",
|
||||
},
|
||||
}
|
||||
|
||||
patch_app_client_files_upload = patch.object(
|
||||
MockSlackBoltAppClient,
|
||||
"files_upload",
|
||||
MagicMock(autospec=True, return_value=mock_files_upload_resp),
|
||||
)
|
||||
patch_app_client_chat_postMessage = patch.object(
|
||||
MockSlackBoltAppClient,
|
||||
"chat_postMessage",
|
||||
MagicMock(autospec=True, return_value=True),
|
||||
)
|
||||
patch_slack_client_run_until = patch.object(
|
||||
slack_client, "_run_until", MagicMock(autospec=True, side_effect=[True, False])
|
||||
)
|
||||
patch_slack_client_run_command_async = patch.object(
|
||||
slack_client,
|
||||
"run_command_async",
|
||||
MagicMock(autospec=True, return_value="20221027001127600438"),
|
||||
)
|
||||
patch_slack_client_get_jobs_from_runner = patch.object(
|
||||
slack_client,
|
||||
"get_jobs_from_runner",
|
||||
MagicMock(autospec=True, return_value=mock_job_status),
|
||||
)
|
||||
|
||||
upload_calls = call(
|
||||
channels="C02QY11UQ",
|
||||
content="minion:\n True",
|
||||
filename="salt-results-20221027090136014442.yaml",
|
||||
)
|
||||
|
||||
chat_postMessage_calls = [
|
||||
call(
|
||||
channel="C02QY11UQ",
|
||||
text="@garethgreenaway's job is submitted as salt jid 20221027001127600438",
|
||||
),
|
||||
call(
|
||||
channel="C02QY11UQ",
|
||||
text="@garethgreenaway's job `['test.ping']` (id: 20221027001127600438) (target: {'target': 'minion', 'tgt_type': 'glob'}) returned",
|
||||
),
|
||||
]
|
||||
|
||||
#
|
||||
# test with control as True and fire_all as False
|
||||
#
|
||||
with patch_slack_client_run_until, patch_slack_client_run_command_async, patch_slack_client_get_jobs_from_runner, patch_app_client_files_upload as app_client_files_upload, patch_app_client_chat_postMessage as app_client_chat_postMessage:
|
||||
slack_client.run_commands_from_slack_async(
|
||||
message_generator=message_generator,
|
||||
fire_all=False,
|
||||
tag="salt/engines/slack",
|
||||
control=True,
|
||||
)
|
||||
app_client_files_upload.asser_has_calls(upload_calls)
|
||||
app_client_chat_postMessage.asser_has_calls(chat_postMessage_calls)
|
||||
|
||||
#
|
||||
# test with control and fire_all as True
|
||||
#
|
||||
patch_slack_client_run_until = patch.object(
|
||||
slack_client, "_run_until", MagicMock(autospec=True, side_effect=[True, False])
|
||||
)
|
||||
|
||||
mock_event_send = MagicMock(return_value=True)
|
||||
patch_event_send = patch.dict(
|
||||
slack_engine.__salt__, {"event.send": mock_event_send}
|
||||
)
|
||||
|
||||
event_send_calls = [
|
||||
call(
|
||||
"salt/engines/slack/message",
|
||||
{
|
||||
"message_data": {
|
||||
"client_msg_id": "c1d0c13d-5e78-431e-9921-4786a7d27543",
|
||||
"type": "message",
|
||||
"text": '!test.ping target="minion"',
|
||||
"user": "U02QY11UJ",
|
||||
"ts": "1666829486.542159",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "2vdy",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": '!test.ping target="minion"',
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"team": "T02QY11UG",
|
||||
"channel": "C02QY11UQ",
|
||||
"event_ts": "1666829486.542159",
|
||||
"channel_type": "channel",
|
||||
},
|
||||
"channel": "C02QY11UQ",
|
||||
"user": "U02QY11UJ",
|
||||
"user_name": "garethgreenaway",
|
||||
"cmdline": ["test.ping"],
|
||||
"target": {"target": "minion", "tgt_type": "glob"},
|
||||
},
|
||||
)
|
||||
]
|
||||
with patch_slack_client_run_until, patch_slack_client_run_command_async, patch_slack_client_get_jobs_from_runner, patch_event_send, patch_app_client_files_upload as app_client_files_upload, patch_app_client_chat_postMessage as app_client_chat_postMessage:
|
||||
slack_client.run_commands_from_slack_async(
|
||||
message_generator=message_generator,
|
||||
fire_all=True,
|
||||
tag="salt/engines/slack",
|
||||
control=True,
|
||||
)
|
||||
app_client_files_upload.asser_has_calls(upload_calls)
|
||||
app_client_chat_postMessage.asser_has_calls(chat_postMessage_calls)
|
||||
mock_event_send.asser_has_calls(event_send_calls)
|
||||
|
||||
|
||||
def test_run_command_async(slack_client):
|
||||
"""
|
||||
Test slack engine: test_run_command_async
|
||||
"""
|
||||
|
||||
msg = {
|
||||
"message_data": {
|
||||
"client_msg_id": "6c71d7f9-a44d-402f-8f9f-d1bb5b650853",
|
||||
"type": "message",
|
||||
"text": '!test.ping target="minion"',
|
||||
"user": "U02QY11UJ",
|
||||
"ts": "1667427929.764169",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "AjL",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{"type": "text", "text": '!test.ping target="minion"'}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"team": "T02QY11UG",
|
||||
"channel": "C02QY11UQ",
|
||||
"event_ts": "1667427929.764169",
|
||||
"channel_type": "channel",
|
||||
},
|
||||
"channel": "C02QY11UQ",
|
||||
"user": "U02QY11UJ",
|
||||
"user_name": "garethgreenaway",
|
||||
"cmdline": ["test.ping"],
|
||||
"target": {"target": "minion", "tgt_type": "glob"},
|
||||
}
|
||||
|
||||
local_client_mock = MagicMock(autospec=True, return_value=MockLocalClient())
|
||||
patch_local_client = patch("salt.client.LocalClient", local_client_mock)
|
||||
|
||||
local_client_cmd_async_mock = MagicMock(
|
||||
autospec=True, return_value={"jid": "20221027001127600438"}
|
||||
)
|
||||
patch_local_client_cmd_async = patch.object(
|
||||
MockLocalClient, "cmd_async", local_client_cmd_async_mock
|
||||
)
|
||||
|
||||
expected_calls = [call("minion", "test.ping", arg=[], kwarg={}, tgt_type="glob")]
|
||||
with patch_local_client, patch_local_client_cmd_async as local_client_cmd_async:
|
||||
ret = slack_client.run_command_async(msg)
|
||||
local_client_cmd_async.assert_has_calls(expected_calls)
|
||||
|
||||
msg = {
|
||||
"message_data": {
|
||||
"client_msg_id": "35f4783f-8913-4687-8f04-21182bcacd5a",
|
||||
"type": "message",
|
||||
"text": "!test.arg arg1 arg2 arg3 key1=value1 key2=value2",
|
||||
"user": "U02QY11UJ",
|
||||
"ts": "1667429460.576889",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "EAzTy",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "!test.arg arg1 arg2 arg3 key1=value1 key2=value2",
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"team": "T02QY11UG",
|
||||
"channel": "C02QY11UQ",
|
||||
"event_ts": "1667429460.576889",
|
||||
"channel_type": "channel",
|
||||
},
|
||||
"channel": "C02QY11UQ",
|
||||
"user": "U02QY11UJ",
|
||||
"user_name": "garethgreenaway",
|
||||
"cmdline": ["test.arg", "arg1", "arg2", "arg3", "key1=value1", "key2=value2"],
|
||||
"target": {"target": "*", "tgt_type": "glob"},
|
||||
}
|
||||
|
||||
runner_client_mock = MagicMock(autospec=True, return_value=MockRunnerClient())
|
||||
patch_runner_client = patch("salt.runner.RunnerClient", runner_client_mock)
|
||||
|
||||
runner_client_asynchronous_mock = MagicMock(
|
||||
autospec=True, return_value={"jid": "20221027001127600438"}
|
||||
)
|
||||
patch_runner_client_asynchronous = patch.object(
|
||||
MockRunnerClient, "asynchronous", runner_client_asynchronous_mock
|
||||
)
|
||||
|
||||
expected_calls = [
|
||||
call(
|
||||
"test.arg",
|
||||
{
|
||||
"arg": ["arg1", "arg2", "arg3"],
|
||||
"kwarg": {"key1": "value1", "key2": "value2"},
|
||||
},
|
||||
)
|
||||
]
|
||||
with patch_runner_client, patch_runner_client_asynchronous as runner_client_asynchronous:
|
||||
ret = slack_client.run_command_async(msg)
|
||||
runner_client_asynchronous.assert_has_calls(expected_calls)
|
||||
|
|
516
tests/pytests/unit/engines/test_slack_bolt_engine.py
Normal file
516
tests/pytests/unit/engines/test_slack_bolt_engine.py
Normal file
|
@ -0,0 +1,516 @@
|
|||
"""
|
||||
unit tests for the slack engine
|
||||
"""
|
||||
import pytest
|
||||
|
||||
import salt.config
|
||||
import salt.engines.slack_bolt_engine as slack_bolt_engine
|
||||
from tests.support.mock import MagicMock, call, patch
|
||||
|
||||
pytestmark = [
|
||||
pytest.mark.skipif(
|
||||
slack_bolt_engine.HAS_SLACKBOLT is False,
|
||||
reason="The slack_bolt is not installed",
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class MockRunnerClient:
|
||||
"""
|
||||
Mock RunnerClient class
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def asynchronous(self, *args, **kwargs):
|
||||
"""
|
||||
Mock asynchronous method
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
class MockLocalClient:
|
||||
"""
|
||||
Mock RunnerClient class
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __enter__(self, *args, **kwargs):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def cmd_async(self, *args, **kwargs):
|
||||
"""
|
||||
Mock cmd_async method
|
||||
"""
|
||||
return True
|
||||
|
||||
|
||||
class MockSlackBoltSocketMode:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def connect(self, *args, **kwargs):
|
||||
return True
|
||||
|
||||
|
||||
class MockSlackBoltApp:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
self.client = MockSlackBoltAppClient()
|
||||
self.logger = None
|
||||
self.proxy = None
|
||||
|
||||
def message(self, *args, **kwargs):
|
||||
return MagicMock(return_value=True)
|
||||
|
||||
|
||||
class MockSlackBoltAppClient:
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def chat_postMessage(self, *args, **kwargs):
|
||||
return MagicMock(return_value=True)
|
||||
|
||||
def files_upload(self, *args, **kwargs):
|
||||
return MagicMock(return_value=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def configure_loader_modules():
|
||||
return {slack_bolt_engine: {}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def slack_client(minion_opts):
|
||||
mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
|
||||
app_token = "xapp-x-xxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
bot_token = "xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
trigger = "!"
|
||||
|
||||
with patch.dict(slack_bolt_engine.__opts__, minion_opts):
|
||||
with patch(
|
||||
"slack_bolt.App", MagicMock(autospec=True, return_value=MockSlackBoltApp())
|
||||
):
|
||||
with patch(
|
||||
"slack_bolt.adapter.socket_mode.SocketModeHandler",
|
||||
MagicMock(autospec=True, return_value=MockSlackBoltSocketMode()),
|
||||
):
|
||||
slack_client = slack_bolt_engine.SlackClient(
|
||||
app_token, bot_token, trigger
|
||||
)
|
||||
yield slack_client
|
||||
|
||||
|
||||
def test_control_message_target(slack_client):
|
||||
"""
|
||||
Test slack engine: control_message_target
|
||||
"""
|
||||
trigger_string = "!"
|
||||
|
||||
loaded_groups = {
|
||||
"default": {
|
||||
"targets": {},
|
||||
"commands": {"cmd.run", "test.ping"},
|
||||
"default_target": {"tgt_type": "glob", "target": "*"},
|
||||
"users": {"gareth"},
|
||||
"aliases": {
|
||||
"whoami": {"cmd": "cmd.run whoami"},
|
||||
"list_pillar": {"cmd": "pillar.items"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
slack_user_name = "gareth"
|
||||
|
||||
# Check for correct cmdline
|
||||
_expected = (True, {"tgt_type": "glob", "target": "*"}, ["cmd.run", "whoami"])
|
||||
text = "!cmd.run whoami"
|
||||
target_commandline = slack_client.control_message_target(
|
||||
slack_user_name, text, loaded_groups, trigger_string
|
||||
)
|
||||
|
||||
assert target_commandline == _expected
|
||||
|
||||
# Check aliases result in correct cmdline
|
||||
text = "!whoami"
|
||||
target_commandline = slack_client.control_message_target(
|
||||
slack_user_name, text, loaded_groups, trigger_string
|
||||
)
|
||||
|
||||
assert target_commandline == _expected
|
||||
|
||||
# Check pillar is overridden
|
||||
_expected = (
|
||||
True,
|
||||
{"tgt_type": "glob", "target": "*"},
|
||||
["pillar.items", 'pillar={"hello": "world"}'],
|
||||
)
|
||||
text = r"""!list_pillar pillar='{"hello": "world"}'"""
|
||||
target_commandline = slack_client.control_message_target(
|
||||
slack_user_name, text, loaded_groups, trigger_string
|
||||
)
|
||||
|
||||
assert target_commandline == _expected
|
||||
|
||||
# Check target is overridden
|
||||
_expected = (
|
||||
True,
|
||||
{"tgt_type": "glob", "target": "localhost"},
|
||||
["cmd.run", "whoami"],
|
||||
)
|
||||
text = "!cmd.run whoami target='localhost'"
|
||||
target_commandline = slack_client.control_message_target(
|
||||
slack_user_name, text, loaded_groups, trigger_string
|
||||
)
|
||||
|
||||
assert target_commandline == _expected
|
||||
|
||||
|
||||
def test_run_commands_from_slack_async(slack_client):
|
||||
"""
|
||||
Test slack engine: test_run_commands_from_slack_async
|
||||
"""
|
||||
|
||||
mock_job_status = {
|
||||
"20221027001127600438": {
|
||||
"data": {"minion": {"return": True, "retcode": 0, "success": True}},
|
||||
"function": "test.ping",
|
||||
}
|
||||
}
|
||||
|
||||
message_generator = [
|
||||
{
|
||||
"message_data": {
|
||||
"client_msg_id": "c1d0c13d-5e78-431e-9921-4786a7d27543",
|
||||
"type": "message",
|
||||
"text": '!test.ping target="minion"',
|
||||
"user": "U02QY11UJ",
|
||||
"ts": "1666829486.542159",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "2vdy",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": '!test.ping target="minion"',
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"team": "T02QY11UG",
|
||||
"channel": "C02QY11UQ",
|
||||
"event_ts": "1666829486.542159",
|
||||
"channel_type": "channel",
|
||||
},
|
||||
"channel": "C02QY11UQ",
|
||||
"user": "U02QY11UJ",
|
||||
"user_name": "garethgreenaway",
|
||||
"cmdline": ["test.ping"],
|
||||
"target": {"target": "minion", "tgt_type": "glob"},
|
||||
}
|
||||
]
|
||||
|
||||
mock_files_upload_resp = {
|
||||
"ok": True,
|
||||
"file": {
|
||||
"id": "F047YTDGJF9",
|
||||
"created": 1666883749,
|
||||
"timestamp": 1666883749,
|
||||
"name": "salt-results-20221027081549173603.yaml",
|
||||
"title": "salt-results-20221027081549173603",
|
||||
"mimetype": "text/plain",
|
||||
"filetype": "yaml",
|
||||
"pretty_type": "YAML",
|
||||
"user": "U0485K894PN",
|
||||
"user_team": "T02QY11UG",
|
||||
"editable": True,
|
||||
"size": 18,
|
||||
"mode": "snippet",
|
||||
"is_external": False,
|
||||
"external_type": "",
|
||||
"is_public": True,
|
||||
"public_url_shared": False,
|
||||
"display_as_bot": False,
|
||||
"username": "",
|
||||
"url_private": "",
|
||||
"url_private_download": "",
|
||||
"permalink": "",
|
||||
"permalink_public": "",
|
||||
"edit_link": "",
|
||||
"preview": "minion:\n True",
|
||||
"preview_highlight": "",
|
||||
"lines": 2,
|
||||
"lines_more": 0,
|
||||
"preview_is_truncated": False,
|
||||
"comments_count": 0,
|
||||
"is_starred": False,
|
||||
"shares": {
|
||||
"public": {
|
||||
"C02QY11UQ": [
|
||||
{
|
||||
"reply_users": [],
|
||||
"reply_users_count": 0,
|
||||
"reply_count": 0,
|
||||
"ts": "1666883749.485979",
|
||||
"channel_name": "general",
|
||||
"team_id": "T02QY11UG",
|
||||
"share_user_id": "U0485K894PN",
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"channels": ["C02QY11UQ"],
|
||||
"groups": [],
|
||||
"ims": [],
|
||||
"has_rich_preview": False,
|
||||
"file_access": "visible",
|
||||
},
|
||||
}
|
||||
|
||||
patch_app_client_files_upload = patch.object(
|
||||
MockSlackBoltAppClient,
|
||||
"files_upload",
|
||||
MagicMock(autospec=True, return_value=mock_files_upload_resp),
|
||||
)
|
||||
patch_app_client_chat_postMessage = patch.object(
|
||||
MockSlackBoltAppClient,
|
||||
"chat_postMessage",
|
||||
MagicMock(autospec=True, return_value=True),
|
||||
)
|
||||
patch_slack_client_run_until = patch.object(
|
||||
slack_client, "_run_until", MagicMock(autospec=True, side_effect=[True, False])
|
||||
)
|
||||
patch_slack_client_run_command_async = patch.object(
|
||||
slack_client,
|
||||
"run_command_async",
|
||||
MagicMock(autospec=True, return_value="20221027001127600438"),
|
||||
)
|
||||
patch_slack_client_get_jobs_from_runner = patch.object(
|
||||
slack_client,
|
||||
"get_jobs_from_runner",
|
||||
MagicMock(autospec=True, return_value=mock_job_status),
|
||||
)
|
||||
|
||||
upload_calls = call(
|
||||
channels="C02QY11UQ",
|
||||
content="minion:\n True",
|
||||
filename="salt-results-20221027090136014442.yaml",
|
||||
)
|
||||
|
||||
chat_postMessage_calls = [
|
||||
call(
|
||||
channel="C02QY11UQ",
|
||||
text="@garethgreenaway's job is submitted as salt jid 20221027001127600438",
|
||||
),
|
||||
call(
|
||||
channel="C02QY11UQ",
|
||||
text="@garethgreenaway's job `['test.ping']` (id: 20221027001127600438) (target: {'target': 'minion', 'tgt_type': 'glob'}) returned",
|
||||
),
|
||||
]
|
||||
|
||||
#
|
||||
# test with control as True and fire_all as False
|
||||
#
|
||||
with patch_slack_client_run_until, patch_slack_client_run_command_async, patch_slack_client_get_jobs_from_runner, patch_app_client_files_upload as app_client_files_upload, patch_app_client_chat_postMessage as app_client_chat_postMessage:
|
||||
slack_client.run_commands_from_slack_async(
|
||||
message_generator=message_generator,
|
||||
fire_all=False,
|
||||
tag="salt/engines/slack",
|
||||
control=True,
|
||||
)
|
||||
app_client_files_upload.asser_has_calls(upload_calls)
|
||||
app_client_chat_postMessage.asser_has_calls(chat_postMessage_calls)
|
||||
|
||||
#
|
||||
# test with control and fire_all as True
|
||||
#
|
||||
patch_slack_client_run_until = patch.object(
|
||||
slack_client, "_run_until", MagicMock(autospec=True, side_effect=[True, False])
|
||||
)
|
||||
|
||||
mock_event_send = MagicMock(return_value=True)
|
||||
patch_event_send = patch.dict(
|
||||
slack_bolt_engine.__salt__, {"event.send": mock_event_send}
|
||||
)
|
||||
|
||||
event_send_calls = [
|
||||
call(
|
||||
"salt/engines/slack/message",
|
||||
{
|
||||
"message_data": {
|
||||
"client_msg_id": "c1d0c13d-5e78-431e-9921-4786a7d27543",
|
||||
"type": "message",
|
||||
"text": '!test.ping target="minion"',
|
||||
"user": "U02QY11UJ",
|
||||
"ts": "1666829486.542159",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "2vdy",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": '!test.ping target="minion"',
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"team": "T02QY11UG",
|
||||
"channel": "C02QY11UQ",
|
||||
"event_ts": "1666829486.542159",
|
||||
"channel_type": "channel",
|
||||
},
|
||||
"channel": "C02QY11UQ",
|
||||
"user": "U02QY11UJ",
|
||||
"user_name": "garethgreenaway",
|
||||
"cmdline": ["test.ping"],
|
||||
"target": {"target": "minion", "tgt_type": "glob"},
|
||||
},
|
||||
)
|
||||
]
|
||||
with patch_slack_client_run_until, patch_slack_client_run_command_async, patch_slack_client_get_jobs_from_runner, patch_event_send, patch_app_client_files_upload as app_client_files_upload, patch_app_client_chat_postMessage as app_client_chat_postMessage:
|
||||
slack_client.run_commands_from_slack_async(
|
||||
message_generator=message_generator,
|
||||
fire_all=True,
|
||||
tag="salt/engines/slack",
|
||||
control=True,
|
||||
)
|
||||
app_client_files_upload.asser_has_calls(upload_calls)
|
||||
app_client_chat_postMessage.asser_has_calls(chat_postMessage_calls)
|
||||
mock_event_send.asser_has_calls(event_send_calls)
|
||||
|
||||
|
||||
def test_run_command_async(slack_client):
|
||||
"""
|
||||
Test slack engine: test_run_command_async
|
||||
"""
|
||||
|
||||
msg = {
|
||||
"message_data": {
|
||||
"client_msg_id": "6c71d7f9-a44d-402f-8f9f-d1bb5b650853",
|
||||
"type": "message",
|
||||
"text": '!test.ping target="minion"',
|
||||
"user": "U02QY11UJ",
|
||||
"ts": "1667427929.764169",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "AjL",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{"type": "text", "text": '!test.ping target="minion"'}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"team": "T02QY11UG",
|
||||
"channel": "C02QY11UQ",
|
||||
"event_ts": "1667427929.764169",
|
||||
"channel_type": "channel",
|
||||
},
|
||||
"channel": "C02QY11UQ",
|
||||
"user": "U02QY11UJ",
|
||||
"user_name": "garethgreenaway",
|
||||
"cmdline": ["test.ping"],
|
||||
"target": {"target": "minion", "tgt_type": "glob"},
|
||||
}
|
||||
|
||||
local_client_mock = MagicMock(autospec=True, return_value=MockLocalClient())
|
||||
patch_local_client = patch("salt.client.LocalClient", local_client_mock)
|
||||
|
||||
local_client_cmd_async_mock = MagicMock(
|
||||
autospec=True, return_value={"jid": "20221027001127600438"}
|
||||
)
|
||||
patch_local_client_cmd_async = patch.object(
|
||||
MockLocalClient, "cmd_async", local_client_cmd_async_mock
|
||||
)
|
||||
|
||||
expected_calls = [call("minion", "test.ping", arg=[], kwarg={}, tgt_type="glob")]
|
||||
with patch_local_client, patch_local_client_cmd_async as local_client_cmd_async:
|
||||
ret = slack_client.run_command_async(msg)
|
||||
local_client_cmd_async.assert_has_calls(expected_calls)
|
||||
|
||||
msg = {
|
||||
"message_data": {
|
||||
"client_msg_id": "35f4783f-8913-4687-8f04-21182bcacd5a",
|
||||
"type": "message",
|
||||
"text": "!test.arg arg1 arg2 arg3 key1=value1 key2=value2",
|
||||
"user": "U02QY11UJ",
|
||||
"ts": "1667429460.576889",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"block_id": "EAzTy",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "!test.arg arg1 arg2 arg3 key1=value1 key2=value2",
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
],
|
||||
"team": "T02QY11UG",
|
||||
"channel": "C02QY11UQ",
|
||||
"event_ts": "1667429460.576889",
|
||||
"channel_type": "channel",
|
||||
},
|
||||
"channel": "C02QY11UQ",
|
||||
"user": "U02QY11UJ",
|
||||
"user_name": "garethgreenaway",
|
||||
"cmdline": ["test.arg", "arg1", "arg2", "arg3", "key1=value1", "key2=value2"],
|
||||
"target": {"target": "*", "tgt_type": "glob"},
|
||||
}
|
||||
|
||||
runner_client_mock = MagicMock(autospec=True, return_value=MockRunnerClient())
|
||||
patch_runner_client = patch("salt.runner.RunnerClient", runner_client_mock)
|
||||
|
||||
runner_client_asynchronous_mock = MagicMock(
|
||||
autospec=True, return_value={"jid": "20221027001127600438"}
|
||||
)
|
||||
patch_runner_client_asynchronous = patch.object(
|
||||
MockRunnerClient, "asynchronous", runner_client_asynchronous_mock
|
||||
)
|
||||
|
||||
expected_calls = [
|
||||
call(
|
||||
"test.arg",
|
||||
{
|
||||
"arg": ["arg1", "arg2", "arg3"],
|
||||
"kwarg": {"key1": "value1", "key2": "value2"},
|
||||
},
|
||||
)
|
||||
]
|
||||
with patch_runner_client, patch_runner_client_asynchronous as runner_client_asynchronous:
|
||||
ret = slack_client.run_command_async(msg)
|
||||
runner_client_asynchronous.assert_has_calls(expected_calls)
|
Loading…
Add table
Reference in a new issue