├── scrapli-and-genie-demo ├── requirements.txt ├── config.yml ├── README.md └── swportutil.py ├── README.md ├── postman-python ├── README.md ├── simpleGET.py └── getOScount.py ├── simple-discord-chatbot ├── go.mod ├── main.go ├── README.md ├── go.sum └── bot │ ├── bot.go │ └── command-weather.go ├── cli-vs-netconf ├── README.md ├── change-snmp-cli.py └── change-snmp-netconf.py ├── simple-webex-chatbot ├── bot.py ├── README.md └── weather.py ├── simple-webex-chatbot-with-cards ├── bot.py ├── README.md ├── input-card.json └── weather.py └── LICENSE /scrapli-and-genie-demo/requirements.txt: -------------------------------------------------------------------------------- 1 | scrapli 2 | scrapli[genie] -------------------------------------------------------------------------------- /scrapli-and-genie-demo/config.yml: -------------------------------------------------------------------------------- 1 | Devices: 2 | hostname: 3 | type: ios-xe 4 | address: 192.168.1.1 5 | username: net_api 6 | password: cisco1234 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Example Scripts 2 | 3 | This repo will be used to keep track of example scripts I write. Many of these will be storing code from blogs or YouTube videos that I post. 4 | 5 | Please see the individual directories for additional details on each script. 6 | -------------------------------------------------------------------------------- /postman-python/README.md: -------------------------------------------------------------------------------- 1 | # Postman to Python Example 2 | 3 | Please see the following blog post [here](https://0x2142.com/from-postman-to-python-your-first-get-request/) or [YouTube Video](https://www.youtube.com/watch?v=nOdIyrA2l5A) 4 | 5 | The files in this repo are the full examples from the content referenced above. 6 | -------------------------------------------------------------------------------- /simple-discord-chatbot/go.mod: -------------------------------------------------------------------------------- 1 | module discord-weather-bot 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/bwmarrin/discordgo v0.26.0 // indirect 7 | github.com/gorilla/websocket v1.4.2 // indirect 8 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect 9 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /cli-vs-netconf/README.md: -------------------------------------------------------------------------------- 1 | ## CLI vs NETCONF 2 | 3 | This folder contains example scripts to show a quick demonstration of using CLI screen scaping vs NETCONF. 4 | 5 | In this example, both scripts will retrieve & print the current SNMP communities from an IOS-XE device. Then each script will prompt the user to enter a new SNMP community string, which is applied to the device. 6 | 7 | While there are many ways to accomplish these config changes, these two scripts demonstrate two possible methods. 8 | -------------------------------------------------------------------------------- /postman-python/simpleGET.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | URL = "https://jsonplaceholder.typicode.com/users" 5 | 6 | print("Search by Username:") 7 | user = input("> ") 8 | queryURL = URL + f"?username={user}" 9 | response = requests.get(queryURL) 10 | 11 | userdata = json.loads(response.text)[0] 12 | 13 | name = userdata["name"] 14 | email = userdata["email"] 15 | phone = userdata["phone"] 16 | 17 | print(f"{name} can be reached via the following methods:") 18 | print(f"Email: {email}") 19 | print(f"Phone: {phone}") 20 | -------------------------------------------------------------------------------- /simple-discord-chatbot/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "discord-weather-bot/bot" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | // Load environment variables 11 | botToken, ok := os.LookupEnv("BOT_TOKEN") 12 | if !ok { 13 | log.Fatal("Must set Discord token as env variable: BOT_TOKEN") 14 | } 15 | openWeatherToken, ok := os.LookupEnv("OPENWEATHER_TOKEN") 16 | if !ok { 17 | log.Fatal("Must set Open Weather token as env variable: OPENWEATHER_TOKEN") 18 | } 19 | 20 | // Save API keys & start bot 21 | bot.BotToken = botToken 22 | bot.OpenWeatherToken = openWeatherToken 23 | bot.Run() 24 | } 25 | -------------------------------------------------------------------------------- /simple-webex-chatbot/bot.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from webex_bot.webex_bot import WebexBot 4 | 5 | from weather import WeatherByZIP 6 | 7 | # Set Webex API key as the WEBEX_TOKEN environment variable & 8 | # we'll retrieve that here: 9 | webex_token = os.environ["WEBEX_TOKEN"] 10 | 11 | # Bot only needs the Webex token, but optionally we can also 12 | # restrict who the bot will respond to by user or domain. 13 | # For example: 14 | # Restrict by user: WebexBot(webex_token, approved_users=['user@example.local']) 15 | # Restrict by domain: WebexBot(webex_token, approved_domains=['example.local']) 16 | bot = WebexBot(webex_token) 17 | 18 | # Registed custom command with the bot: 19 | bot.add_command(WeatherByZIP()) 20 | 21 | # Connect to Webex & start bot listener: 22 | bot.run() 23 | -------------------------------------------------------------------------------- /simple-webex-chatbot-with-cards/bot.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from webex_bot.webex_bot import WebexBot 4 | 5 | from weather import WeatherByZIP 6 | 7 | # Set Webex API key as the WEBEX_TOKEN environment variable & 8 | # we'll retrieve that here: 9 | webex_token = os.environ["WEBEX_TOKEN"] 10 | 11 | # Bot only needs the Webex token, but optionally we can also 12 | # restrict who the bot will respond to by user or domain. 13 | # For example: 14 | # Restrict by user: WebexBot(webex_token, approved_users=['user@example.local']) 15 | # Restrict by domain: WebexBot(webex_token, approved_domains=['example.local']) 16 | bot = WebexBot(webex_token) 17 | 18 | # Registed custom command with the bot: 19 | bot.add_command(WeatherByZIP()) 20 | 21 | # Connect to Webex & start bot listener: 22 | bot.run() 23 | -------------------------------------------------------------------------------- /simple-webex-chatbot-with-cards/README.md: -------------------------------------------------------------------------------- 1 | # Webex Chatbot with Adaptive Cards 2 | 3 | This repo builds upon the previous example in [this repo](https://github.com/0x2142/example-scripts/tree/master/simple-webex-chatbot). The bot has the ability to query OpenWeatherMap.org APIs and return the current weather conditions. In this iteration of the bot, we've added support for Adaptive Cards - to provide an easier way to input & display data. 4 | 5 | 6 | To use this code: 7 | - Install webex_bot: `pip install webex_bot` 8 | - Open `weather.py` and input your OpenWeather API key 9 | - Set your Webex bot API key as an environment variable: `export WEBEX_TOKEN=` 10 | - Run `python bot.py` 11 | - Open Webex & chat with your bot! 12 | 13 | A Webex API key can be created by signing up for a free developer account at developer.webex.com 14 | 15 | ## Additional Details 16 | 17 | - [Blog Post](https://0x2142.com/webex-chatbot-with-adaptivecards/) 18 | - [YouTube Video](https://youtu.be/q4LaBvMePTw) 19 | -------------------------------------------------------------------------------- /simple-discord-chatbot/README.md: -------------------------------------------------------------------------------- 1 | # Simple Discord Chatbot 2 | 3 | This repo contains example code for creating a Discord chatbot. The bot has the ability to query OpenWeatherMap.org APIs and return the current weather conditions. 4 | 5 | This example leverages the [DiscordGo](https://github.com/bwmarrin/discordgo) module to quickly & easily build a Discord bot. 6 | 7 | To use this code: 8 | - Set your Discord bot API key as an environment variable: `export BOT_TOKEN=` 9 | - Set your OpenWeather API key: `export OPENWEATHER_TOKEN=` 10 | - Run `go run main.go` 11 | - Add bot to a discord server & chat with your bot! 12 | 13 | Where to get API keys: 14 | - [OpenWeather](https://openweathermap.org/api) 15 | - [Discord](https://discord.com/developers/applications) 16 | 17 | 18 | ## Additional Details 19 | 20 | Need help? Check out the following resources created along with this example code: 21 | 22 | - [Blog Post](https://0x2142.com/how-to-discordgo-bot/) 23 | - [YouTube Video](https://youtu.be/G7A3nnMvfCk) 24 | -------------------------------------------------------------------------------- /simple-webex-chatbot/README.md: -------------------------------------------------------------------------------- 1 | # Simple Webex Chatbot 2 | 3 | This repo contains example code for creating a Webex chatbot. The bot has the ability to query OpenWeatherMap.org APIs and return the current weather conditions. 4 | 5 | This example leverages the [webex_bot](https://github.com/fbradyirl/webex_bot) module to quickly & easily build a Webex bot. webex_bot uses websockets to create a direct connection to the Webex Cloud, therefore avoiding some of the complexities required with using webhooks. 6 | 7 | To use this code: 8 | - Install webex_bot: `pip install webex_bot` 9 | - Open `weather.py` and input your OpenWeather API key 10 | - Set your Webex bot API key as an environment variable: `export WEBEX_TOKEN=` 11 | - Run `python bot.py` 12 | - Open Webex & chat with your bot! 13 | 14 | A Webex API key can be created by signing up for a free developer account at developer.webex.com 15 | 16 | ## Additional Details 17 | 18 | - [Blog Post](https://0x2142.com/how-to-building-a-basic-webex-chatbot/) 19 | - [YouTube Video](https://www.youtube.com/watch?v=yZQjoe5XUYE) 20 | -------------------------------------------------------------------------------- /scrapli-and-genie-demo/README.md: -------------------------------------------------------------------------------- 1 | # Scrapli & Cisco Genie Demo 2 | 3 | This is an example script that makes use of Scrapli & Cisco genie to parse CLI output from an IOS-XE device. 4 | 5 | The purpose of this script is to: 6 | 1. Connect to one or more devices (Specified in config.yml) 7 | 2. Pull a 'show version' & 'show interfaces' using Scrapli 8 | 3. Use Genie to parse the output 9 | 4. Collect stats on total ports, used ports, port speed / media types, etc 10 | 5. Write out this data to a spreadsheet 11 | 12 | ## Usage 13 | 14 | `config.yml` - This file contains a list of devices. Specify the hostname, username, and password for each device. Note: device type is not currently used 15 | 16 | `swportutil.py` - After filling in the config file, run this script to collect data from each device specified in config.yml 17 | 18 | Only two dependencies are required, as specified in requirements.txt: 19 | 1. scrapli 20 | 2. scrapli[genie] 21 | 22 | ## Additional Details 23 | 24 | - [Blog Post](https://0x2142.com/automating-the-cli-using-scrapli/) 25 | - [Youtube Video](https://www.youtube.com/watch?v=79WitHmGW9I) -------------------------------------------------------------------------------- /simple-webex-chatbot-with-cards/input-card.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "AdaptiveCard", 3 | "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", 4 | "version": "1.2", 5 | "body": [ 6 | { 7 | "type": "TextBlock", 8 | "text": "Get Current Weather", 9 | "wrap": true, 10 | "size": "Medium", 11 | "weight": "Bolder" 12 | }, 13 | { 14 | "type": "TextBlock", 15 | "text": "Enter a Zip code:", 16 | "wrap": true 17 | }, 18 | { 19 | "type": "Input.Number", 20 | "placeholder": "00000", 21 | "id": "zip_code", 22 | "min": 1, 23 | "max": 99950 24 | }, 25 | { 26 | "type": "ActionSet", 27 | "actions": [ 28 | { 29 | "type": "Action.Submit", 30 | "title": "Get Weather", 31 | "data": { 32 | "callback_keyword": "weather" 33 | } 34 | } 35 | ] 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /simple-discord-chatbot/go.sum: -------------------------------------------------------------------------------- 1 | github.com/bwmarrin/discordgo v0.26.0 h1:/AdFmxHXSHInYAZ7K0O3VEIXlVjGpztk/nuCr9o+JCs= 2 | github.com/bwmarrin/discordgo v0.26.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= 3 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 4 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 5 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= 6 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 7 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 8 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= 9 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 10 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 11 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 12 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /simple-discord-chatbot/bot/bot.go: -------------------------------------------------------------------------------- 1 | package bot 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/signal" 8 | "strings" 9 | 10 | "github.com/bwmarrin/discordgo" 11 | ) 12 | 13 | // Store Bot API Tokens: 14 | var ( 15 | OpenWeatherToken string 16 | BotToken string 17 | ) 18 | 19 | func Run() { 20 | // Create new Discord Session 21 | discord, err := discordgo.New("Bot " + BotToken) 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | 26 | // Add event handler for general messages 27 | discord.AddHandler(newMessage) 28 | 29 | // Open session 30 | discord.Open() 31 | defer discord.Close() 32 | 33 | // Run until code is terminated 34 | fmt.Println("Bot running...") 35 | c := make(chan os.Signal, 1) 36 | signal.Notify(c, os.Interrupt) 37 | <-c 38 | 39 | } 40 | 41 | func newMessage(discord *discordgo.Session, message *discordgo.MessageCreate) { 42 | // Ignore bot message 43 | if message.Author.ID == discord.State.User.ID { 44 | return 45 | } 46 | 47 | // Respond to messages 48 | switch { 49 | case strings.Contains(message.Content, "weather"): 50 | discord.ChannelMessageSend(message.ChannelID, "I can help with that! Use '!zip '") 51 | case strings.Contains(message.Content, "bot"): 52 | discord.ChannelMessageSend(message.ChannelID, "Hi there!") 53 | case strings.Contains(message.Content, "!zip"): 54 | currentWeather := getCurrentWeather(message.Content) 55 | discord.ChannelMessageSendComplex(message.ChannelID, currentWeather) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /simple-webex-chatbot/weather.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | 4 | import requests 5 | from webex_bot.models.command import Command 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | # Get a free account at openweathermap.org & 10 | # insert API key here: 11 | OPENWEATHER_KEY = "" 12 | 13 | 14 | class WeatherByZIP(Command): 15 | def __init__(self): 16 | # Define custom command info here 17 | # command_keyword = what chat keyword will trigger this command to execute 18 | # help_message = what message is returned when user sends 'help' message 19 | # card = optionally send an AdaptiveCard response 20 | super().__init__( 21 | command_keyword="weather", 22 | help_message="Get current weather conditions by ZIP code.", 23 | card=None, 24 | ) 25 | 26 | def execute(self, message, attachment_actions, activity): 27 | # By default, command keyword will be stripped out before being passed to execute function 28 | # For example, If user sends "weather 12345", then message variable will be " 12345" 29 | # Need to strip the additional whitespace around the input: 30 | zip_code = message.strip() 31 | 32 | # Define our URL, with desired parameters: ZIP code, units, and API Key 33 | url = "https://api.openweathermap.org/data/2.5/weather?" 34 | url += f"zip={zip_code}&units=imperial&appid={OPENWEATHER_KEY}" 35 | 36 | # Query weather 37 | response = requests.get(url) 38 | weather = response.json() 39 | 40 | # Pull out desired info 41 | city = weather["name"] 42 | conditions = weather["weather"][0]["description"] 43 | temperature = weather["main"]["temp"] 44 | humidity = weather["main"]["humidity"] 45 | wind = weather["wind"]["speed"] 46 | 47 | # Format message that will be sent back to the user 48 | response_message = ( 49 | f"In {city}, it's currently {temperature}F with {conditions}. " 50 | ) 51 | response_message += f"Wind speed is {wind}mph. Humidity is {humidity}%" 52 | 53 | # Message returned will be sent back to the user by bot 54 | return response_message 55 | -------------------------------------------------------------------------------- /postman-python/getOScount.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | URL = "https://api.meraki.com/api/v1" 5 | 6 | APIKEY = {"X-Cisco-Meraki-API-Key": ""} 7 | 8 | 9 | def getOrgID(): 10 | """ 11 | Query Meraki API for which Organizations we have access to & return Org ID 12 | """ 13 | queryURL = URL + "/organizations" 14 | response = requests.get(queryURL, headers=APIKEY) 15 | orgID = json.loads(response.text)[0]["id"] 16 | return orgID 17 | 18 | def getNetworks(orgID): 19 | """ 20 | Use Organization ID to pull list of networks we have access to 21 | """ 22 | queryURL = URL + f"/organizations/{orgID}/networks" 23 | response = requests.get(queryURL, headers=APIKEY) 24 | data = json.loads(response.text) 25 | networkList = [] 26 | for network in data: 27 | networkList.append(network["id"]) 28 | return networkList 29 | 30 | def getClients(orgID, networkList): 31 | """ 32 | Query clients for each network, return client list 33 | """ 34 | clientCount = {} 35 | total = 0 36 | # Query Parameters: Return up to 100 devices seen in the past 43,200 seconds (30 days) 37 | q = {"perPage": "100", 38 | "timespan": "43200"} 39 | for network in networkList: 40 | # Query clients for each network 41 | queryURL = URL + f"/networks/{network}/clients" 42 | response = requests.get(queryURL, params=q, headers=APIKEY) 43 | data = json.loads(response.text) 44 | # Grab client OS from each device & append to clientCount dictionary 45 | for client in data: 46 | try: 47 | clientCount[client["os"]] += 1 48 | except KeyError: 49 | clientCount[client["os"]] = 1 50 | except TypeError: 51 | continue 52 | total += 1 53 | # Append final count of all devices & return dict 54 | clientCount["Total Devices"] = total 55 | return clientCount 56 | 57 | def printReport(clientOS): 58 | """ 59 | Print final output to terminal 60 | """ 61 | print("Count of clients by operating system:") 62 | for OS in clientOS: 63 | print(f"{OS}: {clientOS[OS]}") 64 | 65 | if __name__ == '__main__': 66 | orgID = getOrgID() 67 | networkList = getNetworks(orgID) 68 | clientOS = getClients(orgID, networkList) 69 | printReport(clientOS) 70 | -------------------------------------------------------------------------------- /cli-vs-netconf/change-snmp-cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | Requires scrapli module: 3 | pip install scrapli 4 | """ 5 | 6 | from scrapli.driver.core import IOSXEDriver 7 | 8 | ### Step 1: Open device file, load devices 9 | 10 | # Open device list 11 | # CSV Format: device_IP, username, password 12 | with open('./devices.csv', 'r') as file: 13 | # Read each line in the csv 14 | for line in file: 15 | # Split the line into pieces, separated by comma 16 | device_info = line.split(',') 17 | 18 | # Build dictionary with device IP / authentication details 19 | device = {} 20 | # Device IP should be first value in the CSV row 21 | device['host'] = device_info[0] 22 | # Username is second value in the CSV row 23 | device['auth_username'] = device_info[1] 24 | # Password is 3rd value 25 | device['auth_password'] = device_info[2] 26 | # Don't validate host SSH key 27 | device['auth_strict_key'] = False 28 | 29 | ### Step 2: Connect to device 30 | 31 | # Open new connection using IOSXEDriver & values from device dictionary 32 | print(f"Connecting to {device['host']}") 33 | cli = IOSXEDriver(**device) 34 | cli.open() 35 | 36 | ### Step 3: Check current SNMP Configuration 37 | 38 | # Request current SNMP config 39 | get_snmp = cli.send_command("show run | inc snmp") 40 | 41 | # Get the CLI output result, then split it into individual lines 42 | config_list = get_snmp.result.splitlines() 43 | 44 | print("\nDevice has the following SNMP Communities configured:") 45 | # Check each line returned from the CLI 46 | for config in config_list: 47 | # Only look for config lines that contain "community" 48 | if "community" in config: 49 | # Split the line of config into a list, separated by spaces 50 | snmp = config.split(" ") 51 | # Print each SNMP community 52 | print(f"Community: {snmp[2]}, Mode: {snmp[3]}") 53 | 54 | 55 | ### Step 4: Add new SNMP Community 56 | 57 | # Prompt to enter new comunity string 58 | print("\nPlease provide a new SNMP community:") 59 | new_snmp = input("> ") 60 | 61 | # Provide read-only or read-write 62 | print("\nPlease provide a mode (rw / ro):") 63 | new_mode = input("> ") 64 | 65 | # Send config change 66 | cli.send_config(f"snmp-server community {new_snmp} {new_mode} ") 67 | print("Added new SNMP community") 68 | -------------------------------------------------------------------------------- /simple-discord-chatbot/bot/command-weather.go: -------------------------------------------------------------------------------- 1 | package bot 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "regexp" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/bwmarrin/discordgo" 13 | ) 14 | 15 | const URL string = "https://api.openweathermap.org/data/2.5/weather?" 16 | 17 | 18 | type WeatherData struct { 19 | Weather []struct { 20 | Description string `json:"description"` 21 | } `json:"weather"` 22 | Main struct { 23 | Temp float64 `json:"temp"` 24 | Humidity int `json:"humidity"` 25 | } `json:"main"` 26 | Wind struct { 27 | Speed float64 `json:"speed"` 28 | } `json:"wind"` 29 | Name string `json:"name"` 30 | } 31 | 32 | func getCurrentWeather(message string) *discordgo.MessageSend { 33 | // Match 5-digit US ZIP code 34 | r, _ := regexp.Compile(`\d{5}`) 35 | zip := r.FindString(message) 36 | 37 | // If ZIP not found, return an error 38 | if zip == "" { 39 | return &discordgo.MessageSend{ 40 | Content: "Sorry that ZIP code doesn't look right", 41 | } 42 | } 43 | 44 | // Build full URL to query OpenWeather 45 | weatherURL := fmt.Sprintf("%szip=%s&units=imperial&appid=%s", URL, zip, OpenWeatherToken) 46 | 47 | // Create new HTTP client & set timeout 48 | client := http.Client{Timeout: 5 * time.Second} 49 | 50 | // Query OpenWeather API 51 | response, err := client.Get(weatherURL) 52 | if err != nil { 53 | return &discordgo.MessageSend{ 54 | Content: "Sorry, there was an error trying to get the weather", 55 | } 56 | } 57 | 58 | // Open HTTP response body 59 | body, _ := ioutil.ReadAll(response.Body) 60 | defer response.Body.Close() 61 | 62 | // Convert JSON 63 | var data WeatherData 64 | json.Unmarshal([]byte(body), &data) 65 | 66 | // Pull out desired weather info & Convert to string if necessary 67 | city := data.Name 68 | conditions := data.Weather[0].Description 69 | temperature := strconv.FormatFloat(data.Main.Temp, 'f', 2, 64) 70 | humidity := strconv.Itoa(data.Main.Humidity) 71 | wind := strconv.FormatFloat(data.Wind.Speed, 'f', 2, 64) 72 | 73 | // Build Discord embed response 74 | embed := &discordgo.MessageSend{ 75 | Embeds: []*discordgo.MessageEmbed{{ 76 | Type: discordgo.EmbedTypeRich, 77 | Title: "Current Weather", 78 | Description: "Weather for " + city, 79 | Fields: []*discordgo.MessageEmbedField{ 80 | { 81 | Name: "Conditions", 82 | Value: conditions, 83 | Inline: true, 84 | }, 85 | { 86 | Name: "Temperature", 87 | Value: temperature + "°F", 88 | Inline: true, 89 | }, 90 | { 91 | Name: "Humidity", 92 | Value: humidity + "%", 93 | Inline: true, 94 | }, 95 | { 96 | Name: "Wind", 97 | Value: wind + " mph", 98 | Inline: true, 99 | }, 100 | }, 101 | }, 102 | }, 103 | } 104 | 105 | return embed 106 | } 107 | -------------------------------------------------------------------------------- /cli-vs-netconf/change-snmp-netconf.py: -------------------------------------------------------------------------------- 1 | from scrapli_netconf.driver import NetconfDriver 2 | import xmltodict 3 | 4 | 5 | NETCONF_GET_SNMP_SERVER = """ 6 | 7 | 8 | 9 | """ 10 | 11 | NETCONF_CONFIG_SNMP_SERVER = """ 12 | 13 | 14 | 15 | {community} 16 | 17 | 18 | 19 | 20 | """ 21 | 22 | ### Step 1: Open device file, load devices 23 | 24 | # Open device list 25 | # CSV Format: device_IP, username, password 26 | with open('./devices.csv', 'r') as file: 27 | # Read each line in the csv 28 | for line in file: 29 | # Split the line into pieces, separated by comma 30 | device_info = line.split(',') 31 | 32 | # Build dictionary with device IP / authentication details 33 | device = {} 34 | # Device IP should be first value in the CSV row 35 | device['host'] = device_info[0] 36 | # Username is second value in the CSV row 37 | device['auth_username'] = device_info[1] 38 | # Password is 3rd value 39 | device['auth_password'] = device_info[2] 40 | # Don't validate host SSH key 41 | device['auth_strict_key'] = False 42 | 43 | ### Step 2: Connect to device 44 | 45 | # Open new connection using NetconfDriver & values from device dictionary 46 | print(f"Connecting to {device['host']}") 47 | cli = NetconfDriver(**device) 48 | cli.open() 49 | 50 | ### Step 3: Check current SNMP Configuration 51 | config = cli.get_config(filter_=NETCONF_GET_SNMP_SERVER) 52 | # Covert XML into python dictionary for easy parsing 53 | config_dict = xmltodict.parse(config.result) 54 | 55 | print("\nDevice has the following SNMP Communities configured:") 56 | 57 | # Search in each SNMP Config item for community string & read/write mode 58 | for item in config_dict['rpc-reply']['data']['native']['snmp-server']['community']: 59 | # RO/RW is stored as a dictionary key, with value None. Check to see which key exists 60 | if 'RO' in item: mode = "RO" 61 | if 'RW' in item: mode = "RW" 62 | # Print SNMP config item: 63 | print(f"Community: {item['name']}, Mode: {mode}") 64 | 65 | ### Step 4: Add new SNMP Community 66 | 67 | # Prompt to enter new comunity string 68 | print("\nPlease provide a new SNMP community:") 69 | new_snmp = input("> ") 70 | 71 | # Provide read-only or read-write 72 | print("\nPlease provide a mode (rw / ro):") 73 | new_mode = input("> ") 74 | 75 | # Insert values into XML template 76 | new_config = NETCONF_CONFIG_SNMP_SERVER.format(community=new_snmp, mode=new_mode) 77 | 78 | # Send config change 79 | response = cli.edit_config(config=new_config, target="running") 80 | print("Added new SNMP community") 81 | -------------------------------------------------------------------------------- /simple-webex-chatbot-with-cards/weather.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | 4 | import requests 5 | from webex_bot.models.command import Command 6 | from webex_bot.models.response import Response 7 | from adaptivecardbuilder import * 8 | from datetime import datetime 9 | 10 | 11 | log = logging.getLogger(__name__) 12 | 13 | # Get a free account at openweathermap.org & 14 | # insert API key here: 15 | OPENWEATHER_KEY = "" 16 | 17 | with open("./input-card.json", "r") as card: 18 | INPUT_CARD = json.load(card) 19 | 20 | class WeatherByZIP(Command): 21 | def __init__(self): 22 | # Define custom command info here 23 | # command_keyword = what chat keyword will trigger this command to execute 24 | # help_message = what message is returned when user sends 'help' message 25 | # card = optionally send an AdaptiveCard response 26 | super().__init__( 27 | command_keyword="weather", 28 | help_message="Get current weather conditions by ZIP code.", 29 | card=INPUT_CARD, 30 | ) 31 | 32 | def execute(self, message, attachment_actions, activity): 33 | # By default, all incoming input will come from adaptive card submission 34 | # Will pull 'zip_code' from incoming attachment_actions dictionary 35 | zip_code = attachment_actions.inputs['zip_code'] 36 | 37 | # Define our URL, with desired parameters: ZIP code, units, and API Key 38 | url = "https://api.openweathermap.org/data/2.5/weather?" 39 | url += f"zip={zip_code}&units=imperial&appid={OPENWEATHER_KEY}" 40 | 41 | # Query weather 42 | response = requests.get(url) 43 | weather = response.json() 44 | 45 | # Pull out desired info 46 | city = weather["name"] 47 | conditions = weather["weather"][0]["description"] 48 | temperature = weather["main"]["temp"] 49 | low_temp = weather["main"]["temp_min"] 50 | high_temp = weather["main"]["temp_max"] 51 | humidity = weather["main"]["humidity"] 52 | pressure = weather["main"]["pressure"] 53 | wind = weather["wind"]["speed"] 54 | sunrise = datetime.fromtimestamp(weather["sys"]["sunrise"]).strftime(f"%I:%M%p") 55 | sunset = datetime.fromtimestamp(weather["sys"]["sunset"]).strftime(f"%I:%M%p") 56 | 57 | # Build adaptive card 58 | # For more info around structure, check adaptivecardbuilder project readme 59 | card = AdaptiveCard() 60 | card.add( 61 | [ 62 | TextBlock(text=f"Weather for {city}", size="Medium", weight="Bolder"), 63 | ColumnSet(), 64 | Column(width="stretch"), 65 | FactSet(), 66 | Fact(title="Temp", value=f"{temperature}F"), 67 | Fact(title="Conditions", value=f"{conditions}"), 68 | Fact(title="Wind", value=f"{wind} mph"), 69 | "<", 70 | "<", 71 | Column(width="stretch"), 72 | FactSet(), 73 | Fact(title="High", value=f"{high_temp}F"), 74 | Fact(title="Low", value=f"{low_temp}F"), 75 | "^", 76 | ActionSet(), 77 | ActionShowCard(title="More Details"), 78 | ColumnSet(), 79 | Column(width="stretch"), 80 | FactSet(), 81 | Fact(title="Humidity", value=f"{humidity}%"), 82 | Fact(title="Pressure", value=f"{pressure} hPa"), 83 | "<", 84 | "<", 85 | Column(width="stretch"), 86 | FactSet(), 87 | Fact(title="Sunrise", value=f"{sunrise}"), 88 | Fact(title="Sunset", value=f"{sunset}"), 89 | ] 90 | ) 91 | # Convert card data to JSON 92 | card_data = json.loads(asyncio.run(card.to_json())) 93 | 94 | # Add necessary headers to attach card for response 95 | card_payload = { 96 | "contentType": "application/vnd.microsoft.card.adaptive", 97 | "content": card_data, 98 | } 99 | 100 | # Build card response 101 | response = Response() 102 | # Fallback text 103 | response.text = "Test Card" 104 | # Attachments being sent to user 105 | response.attachments = card_payload 106 | 107 | return response 108 | -------------------------------------------------------------------------------- /scrapli-and-genie-demo/swportutil.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | import yaml 4 | from scrapli.driver.core import IOSXEDriver 5 | 6 | 7 | def loadDevices(): 8 | """ Load device inventory from config.yml """ 9 | with open("config.yml", 'r') as config: 10 | devicelist = yaml.full_load(config) 11 | return devicelist 12 | 13 | 14 | def connect_to_device(deviceconfig): 15 | """ Parse device config data & open SSH connection """ 16 | device = {} 17 | device['host'] = deviceconfig['address'] 18 | device['auth_username'] = deviceconfig['username'] 19 | device['auth_password'] = deviceconfig['password'] 20 | device['auth_strict_key'] = False 21 | conn = IOSXEDriver(**device) 22 | conn.open() 23 | return conn 24 | 25 | 26 | def getInterfaceInfo(device): 27 | """ Issue 'Show Interfaces' command to device """ 28 | resp = device.send_command("show interfaces") 29 | intdata = resp.genie_parse_output() 30 | interfaceStats = { 31 | 'total': 0, 32 | 'intup': 0, 33 | 'intdown': 0, 34 | 'intdisabled': 0, 35 | 'intop10m': 0, 36 | 'intop100m': 0, 37 | 'intop1g': 0, 38 | 'intop10g': 0, 39 | 'intmedcop': 0, 40 | 'intmedsfp': 0, 41 | } 42 | for iface in intdata: 43 | # Skip VLAN / PortChannel Interfaces 44 | if 'Ethernet' not in iface: 45 | print(f'found non-ethernet interface: {iface}') 46 | continue 47 | # Skip Management interface 48 | if 'GigabitEthernet0/0' in iface: 49 | print(f'found management interface: {iface}') 50 | continue 51 | print(f"Working on interface {iface}") 52 | # Count all Ethernet interfaces 53 | interfaceStats['total'] += 1 54 | # Count admin-down interfaces 55 | if not intdata[iface]['enabled']: 56 | interfaceStats['intdisabled'] += 1 57 | # Count not connected interfaces 58 | elif intdata[iface]['enabled'] and intdata[iface]['oper_status'] == 'down': 59 | interfaceStats['intdown'] += 1 60 | # Count up / connected interfaces - Then collect current speeds 61 | elif intdata[iface]['enabled'] and intdata[iface]['connected']: 62 | interfaceStats['intup'] += 1 63 | speed = intdata[iface]['bandwidth'] 64 | if speed == 10_000: 65 | interfaceStats['intop10m'] += 1 66 | if speed == 100_000: 67 | interfaceStats['intop100m'] += 1 68 | if speed == 1_000_000: 69 | interfaceStats['intop1g'] += 1 70 | if speed == 10_000_000: 71 | interfaceStats['intop10g'] += 1 72 | # Count number of interfaces by media type 73 | try: 74 | media = intdata[iface]['media_type'] 75 | if '1000BaseTX' in media: 76 | interfaceStats['intmedcop'] += 1 77 | else: 78 | interfaceStats['intmedsfp'] += 1 79 | except KeyError: 80 | interfaceStats['intmedsfp'] += 1 81 | # When complete - return int stats list 82 | return interfaceStats 83 | 84 | 85 | def getSystemInfo(device): 86 | """ Issue 'Show Version' command to device 87 | Return serial number, model, current software version 88 | """ 89 | resp = device.send_command("show version") 90 | parsed = resp.genie_parse_output() 91 | sysinfo = [] 92 | sysinfo.append(parsed['version']['hostname']) 93 | sysinfo.append(parsed['version']['chassis']) 94 | sysinfo.append(parsed['version']['chassis_sn']) 95 | sysinfo.append(parsed['version']['version']) 96 | return sysinfo 97 | 98 | 99 | def writeCSV(deviceData): 100 | """ Write device info to CSV file """ 101 | with open('deviceinventory.csv', 'a') as file: 102 | writer = csv.writer(file) 103 | writer.writerow(deviceData) 104 | 105 | 106 | def collectData(deviceConnection): 107 | """ Runs batch of CLI scraping / parsing """ 108 | deviceInfo = [] 109 | # Get device info 110 | sysInfo = getSystemInfo(deviceConnection) 111 | intInfo = getInterfaceInfo(deviceConnection) 112 | # Get values from returned dictonary 113 | intList = [x for x in intInfo.values()] 114 | deviceInfo = sysInfo + intList 115 | # Write to CSV 116 | writeCSV(deviceInfo) 117 | 118 | 119 | def initCSV(): 120 | """ Pre-fills header row of CSV file """ 121 | header = [ 122 | 'Hostname', 123 | 'Model', 124 | 'Serial Number', 125 | 'Software Version', 126 | 'Total Interfaces', 127 | 'Interfaces UP (Total)', 128 | 'Interfaces DOWN (Total)', 129 | 'Interfaces Disabled', 130 | 'Interface Operational Speed (10M)', 131 | 'Interface Operational Speed (100M)', 132 | 'Interface Operational Speed (1G)', 133 | 'Interface Operational Speed (10G)', 134 | 'Interface Media (Copper)', 135 | 'Interface Media (SFP)', 136 | ] 137 | writeCSV(header) 138 | 139 | 140 | def run(): 141 | devicelist = loadDevices() 142 | initCSV() 143 | for node in devicelist['Devices']: 144 | device = connect_to_device(devicelist['Devices'][node]) 145 | collectData(device) 146 | 147 | 148 | run() 149 | --------------------------------------------------------------------------------