salt.engines.slack module

An engine that reads messages from Slack and can act on them.

It has two major uses.

  1. When the control parameter is set to True and a message is prefaced with the trigger (which defaults to !) then the engine will validate that the user has permission, and if so will run the command
  2. In addition, when the parameter fire_all is set (defaults to False), all other messages (the messages that aren’t control messages) will be fired off to the salt event bus with the tag prefixed by the string provided by the tag config option (defaults to salt/engines/slack).

This allows for configuration to be gotten from either the engine config, or from the saltmaster’s minion pillar.

configuration:Example configuration using only a ‘default’ group. The default group is not special.

In addition, other groups are being loaded from pillars.

engines:
  - slack:
      token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
      control: True
      fire_all: False
      groups_pillar_name: 'slack_engine:groups_pillar'
      groups:
        default:
          users:
            - *
        commands:
          - test.ping
          - cmd.run
          - list_jobs
          - list_commands
        aliases:
          list_jobs:
            cmd: jobs.list_jobs
          list_commands:
            cmd: pillar.get salt:engines:slack:valid_commands target=saltmaster tgt_type=list
        default_target:
          target: saltmaster
          tgt_type: glob
        targets:
          test.ping:
            target: '*'
            tgt_type: glob
          cmd.run:
            target: saltmaster
            tgt_type: list
configuration:Example configuration using the ‘default’ group and a non-default group and a pillar that will be merged in If the user is ‘*’ (without the quotes) then the group’s users or commands will match all users as appropriate
engines:
  - slack:
      groups_pillar: slack_engine_pillar
      token: 'xoxb-xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx'
      control: True
      fire_all: True
      tag: salt/engines/slack
      groups_pillar_name: 'slack_engine:groups_pillar'
      groups:
        default:
          valid_users:
            - *
          valid_commands:
            - test.ping
          aliases:
            list_jobs:
              cmd: jobs.list_jobs
            list_commands:
              cmd: pillar.get salt:engines:slack:valid_commands target=saltmaster tgt_type=list
        gods:
          users:
            - garethgreenaway
          commands:
            - *
depends:slackclient
class salt.engines.slack.SlackClient(token)
can_user_run(user, command, groups)

Break out the permissions into the folowing:

Check whether a user is in any group, including whether a group has the ‘*’ membership

Parameters:
  • user (str) – The username being checked against
  • command (str) – The command that is being invoked (e.g. test.ping)
  • groups (dict) – the dictionary with groups permissions structure.
Return type:

tuple

Returns:

On a successful permitting match, returns 2-element tuple that contains the name of the group that successfuly matched, and a dictionary containing the configuration of the group so it can be referenced.

On failure it returns an empty tuple

commandline_to_list(cmdline_str, trigger_string)

cmdline_str is the string of the command line trigger_string is the trigger string, to be removed

control_message_target(slack_user_name, text, loaded_groups, trigger_string)

Returns a tuple of (target, cmdline,) for the response

Raises IndexError if a user can’t be looked up from all_slack_users

Returns (False, False) if the user doesn’t have permission

These are returned together because the commandline and the targeting interact with the group config (specifically aliases and targeting configuration) so taking care of them together works out.

The cmdline that is returned is the actual list that should be processed by salt, and not the alias.

fire(tag, msg)

This replaces a function in main called ‘fire’

It fires an event into the salt bus.

format_return_text(data, **kwargs)

Print out YAML using the block mode

generate_triggered_messages(token, trigger_string, groups, groups_pillar_name)

slack_token = string trigger_string = string input_valid_users = set input_valid_commands = set

When the trigger_string prefixes the message text, yields a dictionary of {
‘message_data’: m_data, ‘cmdline’: cmdline_list, # this is a list ‘channel’: channel, ‘user’: m_data[‘user’], ‘slack_client’: sc

}

else yields {‘message_data’: m_data} and the caller can handle that

When encountering an error (e.g. invalid message), yields {}, the caller can proceed to the next message

When the websocket being read from has given up all its messages, yields {‘done’: True} to indicate that the caller has read all of the relevent data for now, and should continue its own processing and check back for more data later.

This relies on the caller sleeping between checks, otherwise this could flood

get_config_groups(groups_conf, groups_pillar_name)

get info from groups in config, and from the named pillar

todo: add specification for the minion to use to recover pillar

get_jobs_from_runner(outstanding_jids)

Given a list of job_ids, return a dictionary of those job_ids that have completed and their results.

Query the salt event bus via the jobs runner. jobs.list_job will show a job in progress, jobs.lookup_jid will return a job that has completed.

returns a dictionary of job id: result

get_slack_channels(token)

Get all channel names from Slack

get_slack_users(token)

Get all users from Slack

get_target(permitted_group, cmdline, alias_cmdline)

When we are permitted to run a command on a target, look to see what the default targeting is for that group, and for that specific command (if provided).

It’s possible for None or False to be the result of either, which means that it’s expected that the caller provide a specific target.

If no configured target is provided, the command line will be parsed for target=foo and tgt_type=bar

Test for this: h = {‘aliases’: {}, ‘commands’: {‘cmd.run’, ‘pillar.get’},

‘default_target’: {‘target’: ‘*’, ‘tgt_type’: ‘glob’}, ‘targets’: {‘pillar.get’: {‘target’: ‘you_momma’, ‘tgt_type’: ‘list’}}, ‘users’: {‘dmangot’, ‘jmickle’, ‘pcn’}}
f = {‘aliases’: {}, ‘commands’: {‘cmd.run’, ‘pillar.get’},
‘default_target’: {}, ‘targets’: {},’users’: {‘dmangot’, ‘jmickle’, ‘pcn’}}
g = {‘aliases’: {}, ‘commands’: {‘cmd.run’, ‘pillar.get’},
‘default_target’: {‘target’: ‘*’, ‘tgt_type’: ‘glob’}, ‘targets’: {}, ‘users’: {‘dmangot’, ‘jmickle’, ‘pcn’}}

Run each of them through get_configured_target(('foo', f), 'pillar.get') and confirm a valid target

message_text(m_data)

Raises ValueError if a value doesn’t work out, and TypeError if this isn’t a message type

parse_args_and_kwargs(cmdline)

cmdline: list

returns tuple of: args (list), kwargs (dict)

run_command_async(msg)
Parameters:
  • message_generator (generator of dict) – Generates messages from slack that should be run
  • fire_all (bool) – Whether to also fire messages to the event bus
  • tag (str) – The tag to send to use to send to the event bus
  • interval (int) – time to wait between ending a loop and beginning the next
run_commands_from_slack_async(message_generator, fire_all, tag, control, interval=1)

Pull any pending messages from the message_generator, sending each one to either the event bus, the command_async or both, depending on the values of fire_all and command

salt.engines.slack.start(token, control=False, trigger=u'!', groups=None, groups_pillar_name=None, fire_all=False, tag=u'salt/engines/slack')

Listen to slack events and forward them to salt, new version