mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 09:40: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
|
redis_sentinel
|
||||||
script
|
script
|
||||||
slack
|
slack
|
||||||
|
slack_bolt_engine
|
||||||
sqs_events
|
sqs_events
|
||||||
stalekey
|
stalekey
|
||||||
test
|
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
|
hglib
|
||||||
redis-py-cluster
|
redis-py-cluster
|
||||||
python-consul
|
python-consul
|
||||||
|
slack_bolt
|
||||||
|
|
|
@ -798,6 +798,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
|
|
|
@ -797,6 +797,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -856,6 +856,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==3.0.4
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -820,6 +820,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
|
|
|
@ -822,6 +822,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -879,6 +879,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==3.0.4
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -846,6 +846,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
|
|
|
@ -848,6 +848,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -900,6 +900,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==3.0.4
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -838,6 +838,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
|
|
|
@ -839,6 +839,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -890,6 +890,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==3.0.4
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -349,6 +349,8 @@ botocore==1.21.27
|
||||||
# boto3
|
# boto3
|
||||||
# moto
|
# moto
|
||||||
# s3transfer
|
# s3transfer
|
||||||
|
cached-property==1.5.2
|
||||||
|
# via pygit2
|
||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
# via
|
# via
|
||||||
# google-auth
|
# google-auth
|
||||||
|
@ -839,6 +841,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
snowballstemmer==2.1.0
|
snowballstemmer==2.1.0
|
||||||
|
|
|
@ -347,6 +347,8 @@ botocore==1.21.27
|
||||||
# boto3
|
# boto3
|
||||||
# moto
|
# moto
|
||||||
# s3transfer
|
# s3transfer
|
||||||
|
cached-property==1.5.2
|
||||||
|
# via pygit2
|
||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
# via
|
# via
|
||||||
# google-auth
|
# google-auth
|
||||||
|
@ -840,6 +842,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==4.0.0
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -895,6 +895,10 @@ six==1.16.0
|
||||||
# vcert
|
# vcert
|
||||||
# virtualenv
|
# virtualenv
|
||||||
# websocket-client
|
# 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
|
smmap==3.0.4
|
||||||
# via gitdb
|
# via gitdb
|
||||||
sqlparse==0.4.2
|
sqlparse==0.4.2
|
||||||
|
|
|
@ -3,47 +3,21 @@ An engine that reads messages from Slack and can act on them
|
||||||
|
|
||||||
.. versionadded:: 2016.3.0
|
.. 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::
|
.. important::
|
||||||
This engine requires a Slack app and a Slack Bot user. To create a
|
This engine requires a bot user. To create a bot user, first go to the
|
||||||
bot user, first go to the **Custom Integrations** page in your
|
**Custom Integrations** page in your Slack Workspace. Copy and paste the
|
||||||
Slack Workspace. Copy and paste the following URL, and log in with
|
following URL, and replace ``myworkspace`` with the proper value for your
|
||||||
account credentials with administrative privileges:
|
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.
|
Next, click on the ``Bots`` integration and request installation. Once
|
||||||
Give your new app a unique name, eg. ``SaltSlackEngine``, select the workspace
|
approved by an admin, you will be able to proceed with adding the bot user.
|
||||||
where your app will be running, and click ``Create App``.
|
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
|
||||||
Next, click on ``Socket Mode`` and then click on the toggle button for
|
your API token, which will be needed to configure this engine.
|
||||||
``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.
|
|
||||||
|
|
||||||
Finally, add this bot user to a channel by switching to the channel and
|
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
|
using ``/invite @mybotuser``. Keep in mind that this engine will process
|
||||||
|
@ -100,9 +74,6 @@ Configuration Examples
|
||||||
.. versionchanged:: 2017.7.0
|
.. versionchanged:: 2017.7.0
|
||||||
Access control group support added
|
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
|
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
|
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
|
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:
|
engines:
|
||||||
- slack:
|
- slack:
|
||||||
app_token: "xapp-x-xxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
bot_token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
|
|
||||||
control: True
|
control: True
|
||||||
fire_all: False
|
fire_all: False
|
||||||
groups_pillar_name: 'slack_engine:groups_pillar'
|
groups_pillar_name: 'slack_engine:groups_pillar'
|
||||||
|
@ -151,8 +121,7 @@ must be quoted, or else PyYAML will fail to load the configuration.
|
||||||
engines:
|
engines:
|
||||||
- slack:
|
- slack:
|
||||||
groups_pillar: slack_engine_pillar
|
groups_pillar: slack_engine_pillar
|
||||||
app_token: "xapp-x-xxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
bot_token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
|
|
||||||
control: True
|
control: True
|
||||||
fire_all: True
|
fire_all: True
|
||||||
tag: salt/engines/slack
|
tag: salt/engines/slack
|
||||||
|
@ -177,7 +146,6 @@ must be quoted, or else PyYAML will fail to load the configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import collections
|
|
||||||
import datetime
|
import datetime
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
@ -198,12 +166,11 @@ import salt.utils.slack
|
||||||
import salt.utils.yaml
|
import salt.utils.yaml
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import slack_bolt
|
import slackclient
|
||||||
import slack_bolt.adapter.socket_mode
|
|
||||||
|
|
||||||
HAS_SLACKBOLT = True
|
HAS_SLACKCLIENT = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_SLACKBOLT = False
|
HAS_SLACKCLIENT = False
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -211,38 +178,17 @@ __virtualname__ = "slack"
|
||||||
|
|
||||||
|
|
||||||
def __virtual__():
|
def __virtual__():
|
||||||
if not HAS_SLACKBOLT:
|
if not HAS_SLACKCLIENT:
|
||||||
return (False, "The 'slack_bolt' Python module could not be loaded")
|
return (False, "The 'slackclient' Python module could not be loaded")
|
||||||
return __virtualname__
|
return __virtualname__
|
||||||
|
|
||||||
|
|
||||||
class SlackClient:
|
class SlackClient:
|
||||||
def __init__(self, app_token, bot_token, trigger_string):
|
def __init__(self, token):
|
||||||
self.master_minion = salt.minion.MasterMinion(__opts__)
|
self.master_minion = salt.minion.MasterMinion(__opts__)
|
||||||
|
|
||||||
self.app = slack_bolt.App(token=bot_token)
|
self.sc = slackclient.SlackClient(token)
|
||||||
self.handler = slack_bolt.adapter.socket_mode.SocketModeHandler(
|
self.slack_connect = self.sc.rtm_connect()
|
||||||
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)
|
|
||||||
|
|
||||||
def get_slack_users(self, token):
|
def get_slack_users(self, token):
|
||||||
"""
|
"""
|
||||||
|
@ -597,12 +543,13 @@ class SlackClient:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
for sleeps in (5, 10, 30, 60):
|
for sleeps in (5, 10, 30, 60):
|
||||||
if self.handler:
|
if self.slack_connect:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# see https://api.slack.com/docs/rate-limits
|
# see https://api.slack.com/docs/rate-limits
|
||||||
log.warning(
|
log.warning(
|
||||||
"Slack connection is invalid, sleeping %s",
|
"Slack connection is invalid. Server: %s, sleeping %s",
|
||||||
|
self.sc.server,
|
||||||
sleeps,
|
sleeps,
|
||||||
)
|
)
|
||||||
time.sleep(
|
time.sleep(
|
||||||
|
@ -611,51 +558,51 @@ class SlackClient:
|
||||||
else:
|
else:
|
||||||
raise UserWarning(
|
raise UserWarning(
|
||||||
"Connection to slack is still invalid, giving up: {}".format(
|
"Connection to slack is still invalid, giving up: {}".format(
|
||||||
self.handler
|
self.slack_connect
|
||||||
)
|
)
|
||||||
) # Boom!
|
) # Boom!
|
||||||
while self._run_until():
|
while True:
|
||||||
while self.msg_queue:
|
msg = self.sc.rtm_read()
|
||||||
msg = self.msg_queue.popleft()
|
for m_data in msg:
|
||||||
try:
|
try:
|
||||||
msg_text = self.message_text(msg)
|
msg_text = self.message_text(m_data)
|
||||||
except (ValueError, TypeError) as msg_err:
|
except (ValueError, TypeError) as msg_err:
|
||||||
log.debug(
|
log.debug(
|
||||||
"Got an error from trying to get the message text %s", msg_err
|
"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
|
continue
|
||||||
|
|
||||||
# Find the channel object from the channel name
|
# Find the channel object from the channel name
|
||||||
channel = msg["channel"]
|
channel = self.sc.server.channels.find(m_data["channel"])
|
||||||
data = just_data(msg)
|
data = just_data(m_data)
|
||||||
if msg_text.startswith(trigger_string):
|
if msg_text.startswith(trigger_string):
|
||||||
loaded_groups = self.get_config_groups(groups, groups_pillar_name)
|
loaded_groups = self.get_config_groups(groups, groups_pillar_name)
|
||||||
if not data.get("user_name"):
|
if not data.get("user_name"):
|
||||||
log.error(
|
log.error(
|
||||||
"The user %s can not be looked up via slack. What has"
|
"The user %s can not be looked up via slack. What has"
|
||||||
" happened here?",
|
" happened here?",
|
||||||
msg.get("user"),
|
m_data.get("user"),
|
||||||
)
|
)
|
||||||
channel.send_message(
|
channel.send_message(
|
||||||
"The user {} can not be looked up via slack. Not"
|
"The user {} can not be looked up via slack. Not"
|
||||||
" running {}".format(data["user_id"], msg_text)
|
" running {}".format(data["user_id"], msg_text)
|
||||||
)
|
)
|
||||||
yield {"message_data": msg}
|
yield {"message_data": m_data}
|
||||||
continue
|
continue
|
||||||
(allowed, target, cmdline) = self.control_message_target(
|
(allowed, target, cmdline) = self.control_message_target(
|
||||||
data["user_name"], msg_text, loaded_groups, trigger_string
|
data["user_name"], msg_text, loaded_groups, trigger_string
|
||||||
)
|
)
|
||||||
|
log.debug("Got target: %s, cmdline: %s", target, cmdline)
|
||||||
if allowed:
|
if allowed:
|
||||||
ret = {
|
yield {
|
||||||
"message_data": msg,
|
"message_data": m_data,
|
||||||
"channel": msg["channel"],
|
"channel": m_data["channel"],
|
||||||
"user": data["user_id"],
|
"user": data["user_id"],
|
||||||
"user_name": data["user_name"],
|
"user_name": data["user_name"],
|
||||||
"cmdline": cmdline,
|
"cmdline": cmdline,
|
||||||
"target": target,
|
"target": target,
|
||||||
}
|
}
|
||||||
yield ret
|
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
channel.send_message(
|
channel.send_message(
|
||||||
|
@ -823,48 +770,45 @@ class SlackClient:
|
||||||
|
|
||||||
outstanding = {} # set of job_id that we need to check for
|
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)
|
log.trace("Sleeping for interval of %s", interval)
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
# Drain the slack messages, up to 10 messages at a clip
|
# Drain the slack messages, up to 10 messages at a clip
|
||||||
count = 0
|
count = 0
|
||||||
for msg in message_generator:
|
for msg in message_generator:
|
||||||
if msg:
|
# The message_generator yields dicts. Leave this loop
|
||||||
# The message_generator yields dicts. Leave this loop
|
# on a dict that looks like {'done': True} or when we've done it
|
||||||
# on a dict that looks like {'done': True} or when we've done it
|
# 10 times without taking a break.
|
||||||
# 10 times without taking a break.
|
log.trace("Got a message from the generator: %s", msg.keys())
|
||||||
log.trace("Got a message from the generator: %s", msg.keys())
|
if count > 10:
|
||||||
if count > 10:
|
log.warning(
|
||||||
log.warning(
|
"Breaking in getting messages because count is exceeded"
|
||||||
"Breaking in getting messages because count is exceeded"
|
)
|
||||||
)
|
break
|
||||||
break
|
if not msg:
|
||||||
if not msg:
|
count += 1
|
||||||
count += 1
|
log.warning("Skipping an empty message.")
|
||||||
log.warning("Skipping an empty message.")
|
continue # This one is a dud, get the next message
|
||||||
continue # This one is a dud, get the next message
|
if msg.get("done"):
|
||||||
if msg.get("done"):
|
log.trace("msg is done")
|
||||||
log.trace("msg is done")
|
break
|
||||||
break
|
if fire_all:
|
||||||
if fire_all:
|
log.debug("Firing message to the bus with tag: %s", tag)
|
||||||
log.debug("Firing message to the bus with tag: %s", tag)
|
log.debug("%s %s", tag, msg)
|
||||||
log.debug("%s %s", tag, msg)
|
self.fire("{}/{}".format(tag, msg["message_data"].get("type")), msg)
|
||||||
self.fire(
|
if control and (len(msg) > 1) and msg.get("cmdline"):
|
||||||
"{}/{}".format(tag, msg["message_data"].get("type")), msg
|
channel = self.sc.server.channels.find(msg["channel"])
|
||||||
)
|
jid = self.run_command_async(msg)
|
||||||
if control and (len(msg) > 1) and msg.get("cmdline"):
|
log.debug("Submitted a job and got jid: %s", jid)
|
||||||
jid = self.run_command_async(msg)
|
outstanding[
|
||||||
log.debug("Submitted a job and got jid: %s", jid)
|
jid
|
||||||
outstanding[
|
] = msg # record so we can return messages to the caller
|
||||||
jid
|
channel.send_message(
|
||||||
] = msg # record so we can return messages to the caller
|
"@{}'s job is submitted as salt jid {}".format(
|
||||||
text_msg = "@{}'s job is submitted as salt jid {}".format(
|
|
||||||
msg["user_name"], jid
|
msg["user_name"], jid
|
||||||
)
|
)
|
||||||
self.app.client.chat_postMessage(
|
)
|
||||||
channel=msg["channel"], text=text_msg
|
count += 1
|
||||||
)
|
|
||||||
count += 1
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
job_status = self.get_jobs_from_runner(
|
job_status = self.get_jobs_from_runner(
|
||||||
outstanding.keys()
|
outstanding.keys()
|
||||||
|
@ -881,7 +825,7 @@ class SlackClient:
|
||||||
log.debug("ret to send back is %s", result)
|
log.debug("ret to send back is %s", result)
|
||||||
# formatting function?
|
# formatting function?
|
||||||
this_job = outstanding[jid]
|
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_text = self.format_return_text(result, function)
|
||||||
return_prefix = (
|
return_prefix = (
|
||||||
"@{}'s job `{}` (id: {}) (target: {}) returned".format(
|
"@{}'s job `{}` (id: {}) (target: {}) returned".format(
|
||||||
|
@ -891,19 +835,19 @@ class SlackClient:
|
||||||
this_job["target"],
|
this_job["target"],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.app.client.chat_postMessage(
|
channel.send_message(return_prefix)
|
||||||
channel=channel, text=return_prefix
|
|
||||||
)
|
|
||||||
ts = time.time()
|
ts = time.time()
|
||||||
st = datetime.datetime.fromtimestamp(ts).strftime("%Y%m%d%H%M%S%f")
|
st = datetime.datetime.fromtimestamp(ts).strftime("%Y%m%d%H%M%S%f")
|
||||||
filename = "salt-results-{}.yaml".format(st)
|
filename = "salt-results-{}.yaml".format(st)
|
||||||
resp = self.app.client.files_upload(
|
r = self.sc.api_call(
|
||||||
channels=channel,
|
"files.upload",
|
||||||
|
channels=channel.id,
|
||||||
filename=filename,
|
filename=filename,
|
||||||
content=return_text,
|
content=return_text,
|
||||||
)
|
)
|
||||||
# Handle unicode return
|
# 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:
|
if "ok" in resp and resp["ok"] is False:
|
||||||
this_job["channel"].send_message(
|
this_job["channel"].send_message(
|
||||||
"Error: {}".format(resp["error"])
|
"Error: {}".format(resp["error"])
|
||||||
|
@ -971,8 +915,7 @@ class SlackClient:
|
||||||
|
|
||||||
|
|
||||||
def start(
|
def start(
|
||||||
app_token,
|
token,
|
||||||
bot_token,
|
|
||||||
control=False,
|
control=False,
|
||||||
trigger="!",
|
trigger="!",
|
||||||
groups=None,
|
groups=None,
|
||||||
|
@ -984,17 +927,23 @@ def start(
|
||||||
Listen to slack events and forward them to salt, new version
|
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
|
time.sleep(2) # don't respawn too quickly
|
||||||
log.error("Slack bot token not found, bailing...")
|
log.error("Slack bot token not found, bailing...")
|
||||||
raise UserWarning("Slack Engine bot token not configured")
|
raise UserWarning("Slack Engine bot token not configured")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = SlackClient(
|
client = SlackClient(token=token)
|
||||||
app_token=app_token, bot_token=bot_token, trigger_string=trigger
|
|
||||||
)
|
|
||||||
message_generator = client.generate_triggered_messages(
|
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)
|
client.run_commands_from_slack_async(message_generator, fire_all, tag, control)
|
||||||
except Exception: # pylint: disable=broad-except
|
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 pytest
|
||||||
|
|
||||||
import salt.config
|
import salt.config
|
||||||
import salt.engines.slack as slack_engine
|
import salt.engines.slack as slack
|
||||||
from tests.support.mock import MagicMock, call, patch
|
from tests.support.mock import MagicMock, patch
|
||||||
|
|
||||||
pytestmark = [
|
pytestmark = [
|
||||||
pytest.mark.skipif(
|
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
|
@pytest.fixture
|
||||||
def configure_loader_modules():
|
def configure_loader_modules():
|
||||||
return {slack_engine: {}}
|
return {slack: {}}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def slack_client(minion_opts):
|
def slack_client():
|
||||||
mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
|
mock_opts = salt.config.DEFAULT_MINION_OPTS.copy()
|
||||||
app_token = "xapp-x-xxxxxxxxxxx-xxxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
|
token = "xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
|
||||||
bot_token = "xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx"
|
|
||||||
trigger = "!"
|
|
||||||
|
|
||||||
with patch.dict(slack_engine.__opts__, minion_opts):
|
with patch.dict(slack.__opts__, mock_opts):
|
||||||
with patch(
|
with patch("slackclient.SlackClient.rtm_connect", MagicMock(return_value=True)):
|
||||||
"slack_bolt.App", MagicMock(autospec=True, return_value=MockSlackBoltApp())
|
slack_client = slack.SlackClient(token)
|
||||||
):
|
yield slack_client
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def test_control_message_target(slack_client):
|
def test_control_message_target(slack_client):
|
||||||
|
@ -173,341 +93,3 @@ def test_control_message_target(slack_client):
|
||||||
)
|
)
|
||||||
|
|
||||||
assert target_commandline == _expected
|
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