Basic Slack use with Python

At my last job many fellow enigneers would made the claim that “I like the idea, and I like the info you can provide, but I am not going to build an entire application, with the proper code and prerequistes, for this use case.” I ended up building a webpage, but with all the security concerns, and limited resources it wasn’t a good fit for production use. However, it was adopted in an unofficial manner and that in turn expanded who could use my programs significantly. Not just other network engineers, but it also made life easier for the system admins. For example: when a system admin wanted to know what VLANs were configured to which VM servers they could just go to a webpage, view the simplified layout, and it would provide them the information in a format they were comfortable with. External users didn’t have to put in tickets and wait a week, network engineering didn’t have to stop what we were doing and look at those tickets. So, I love the idea, but my implementation was not ready for production with a proper support model. I have seen several other developers doing something along these lines, but they often seem to hit the same issues I did. I had see a few of the bigger brain developers I know build some Slack integrations, and I thought “Hey that’s cool, but I bet it’s REALLY complex”…. I was wrong: it isn’t hard at all.

Prerequisites

Let’s take a look at what we do to build a Slack integrated program. After you get your Slack account and channels setup you need to get your token which should look something like this “xoxp-XXXXXXXX-XXXXXXXX-XXXXX”

Figuring out where the channel’s ID is can be a bit difficult. All you need to do though is right click the channel you want to interact with, and select copy link paste that into your text editor, and pull out the ID.

copy slack
link

To get your token just go to https://api.slack.com/custom-integrations/legacy-tokens Scroll down to the “Legacy information” section and click Issue token/Re-issue token

get_slack_token

Post a message:

This is all the Python code needed to build a function send a message in Slack.

def post_to_slack(message, channel, token):
	slack_token = token
	client = slack.WebClient(token=slack_token)
	client.chat_postMessage(
	channel = channel,
	text=message)

Pull the last message:

Similar amount of code for building a function to pull a message in Slack.

def get_last_message(token, channel):
	url = 'https://slack.com/api/conversations.history?token={}&channel={}&limit=1&pretty=1'.format(token, channel)
	r = requests.get(url)
	all_response = r.json()
	last_message = all_response['messages'][0]['text']
	return last_message

You can test this mini Slack application without worry about Python here: This particular API tests obtaining Slack conversations. Please note when it asks for a channel it is looking for a unique identifier like CP02W0ABQ, not #general. https://api.slack.com/methods/conversations.history/test

So we post in some text, and that causes our program to kick off it’s work.

slack img

Solution

Lets take a quick look at the final code used.

from pprint import pprint
import requests
import os
import slack
import json
import time
import netmiko

token = "xoxp-fake-token"      
#  The account that will be used to SSH into the devices
username = 'username'
password = 'password'

#Fake channel
channel ="BAR2FUFM"

#SSH or Telnet to the Cisco device
#If it fails to SSH and Telnet add an entry to Issues.csv
def make_connection(ip, username, password):
	try:
		net_connect = netmiko.ConnectHandler(device_type = 'cisco_ios', ip = ip, username = username, password = password)
		return net_connect
	except:
		try:
			return netmiko.ConnectHandler(device_type = 'cisco_ios_telnet', ip = ip, username = username, password = password)
		except:
			issue = ip + ", can't be ssh/telneted to"
			to_doc_a("Issues.csv", issue)
			to_doc_a("Issues.csv", '\n')
			return None

#Send a command to the device that has been SSHed to
def send_command(net_connect,command):
	return net_connect.send_command_expect(command)


#In this case you pass in the output from show interfaces but split into a list based on line return
#This splits the interfaces up into their own list
#So all[0]  will give you a list of lines of the first interface
#all[0][0] will give you the first line of the first interface
def find_child_text (file, text):
	all = []
	parse = CiscoConfParse(file)
	for obj in parse.find_objects(text):
		each_obj = []
		each_obj.append(obj.text)
		for each in obj.all_children:
			each_obj.append(each.text)
		all.append(each_obj)
	return all


#Post new message to slack channel in question
def post_to_slack(message, channel, token):
	slack_token = token
	client = slack.WebClient(token=slack_token)
	client.chat_postMessage(
	channel=channel,
	text=message)

#Pull last 1 message from slack channel in question
def get_last_message(token, channel):
	url = 'https://slack.com/api/conversations.history?token={}&channel={}&limit=1&pretty=1'.format(token, channel)
	r = requests.get(url)
	all_response = r.json()
	last_message = all_response['messages'][0]['text']
	return last_message

#All the devices in the lab, and they should all be eigrp neighbors		  
def check_lab():
	lab_devices=[
	'192.168.0.12',
	'192.168.0.13',
	'192.168.0.14',
	'192.168.0.104',
	#An IP I know won't be there so there are issues
	'1.1.1.1'
	]
	
	#Hit each device in the lab
	for ip in lab_devices:
		info_output = "*checking {}*".format(ip)
		#Post updates to slack so they know what's happening
		post_to_slack(info_output, channel, token)
		#SSH to device
		net_connect = make_connection(ip, username, password)
		#If it fails to connect post the error to slack and move to the next one
		if net_connect == None:
			issue = "      can't connect to {}".format(ip)
			post_to_slack(issue, channel, token)
			continue
		command = 'show ip eigrp nei'
		#Get the output from the command sent
		output = send_command(net_connect,command)
		#Chek and make sure each device is listed as a neighbor, or is the one we SSHed to
		for neighbor in lab_devices:
			if ip == neighbor:
				continue
			if neighbor not in output:
				issue = '      {} is not found in the active neighbors'.format(neighbor)
				#If it's not there post the issue to slack
				post_to_slack(issue, channel, token)
		#Pull the interface data, and look for collisions		
		command = 'show interfaces'
		output = send_command(net_connect,command)
		output = output.split("\n")
		interfaces = find_child_text (output, 'GigabitEthernet')
		for interface in interfaces:
			for line in interface:
				if "collisions" in line:
					if '0 collisions' not in line:
						interface_name = interface[0].split(' ')[0]
						issue = '      {} has collisions on {}'.format (interface_name, ip)
						post_to_slack(issue, channel, token)
	#Let the user know it's done
	post_to_slack("done", channel, token)
		
		


#Name of things people will type in to kick off an action
functions=['say hi', 'check lab']

def say_hi():
	post_to_slack("hi", channel, token)
	post_to_slack("done", channel, token)

#Run forever, check back every second to see if it wants you to do anything.	
while 1 == 1:
	last_message = get_last_message(token, channel)
	if last_message in  functions:
		#Start it off by posting something.  That way if something bad happens you don't have
		#An infinate loop of it trying to do the same thing because the last message posted was
		#A kick off prompt
		post_to_slack("processing", channel, token)
	if last_message == 'say hi':
		say_hi()
	if last_message == 'check lab':
		check_lab()
	time.sleep(1)

Conclusion

Using Slack’s native integrations, and a Python library, there wasn’t actually that much code or logic required. You can see everything bundled up to a single file with around 100 lines of code. It’s also important to realize the user experience for people who are not developers or engineers, and an integration such as this can provide a reasonable middle ground.



ntc img
ntc img

Contact Us to Learn More

Share details about yourself & someone from our team will reach out to you ASAP!

Author