Package: catops
CatOpsHighly trained cats for managing servers.What is CatOpsCatOps is a very simple NoOps framework for deploying your own ChatOps bot.Commands you wish to add to your CatOps implementation are added to apluginsfolder in the same directory, and will then be automatically imported and callable
using the function name.Why CatOpsNoOps.Deploy, rewrite, and redeploy FaaS easily with no worrying about setting up and managing servers.Only charged when CatOps is called.Codify common maintenance procedures.Perform high level actions without intimate low level knowledge.Prevent errors doing complicated but routine tasks.Unify documentation.CatOps can act as a unified go-to location for help, merging/pooling all documentation into one place.Transparency.Team members can see all actions taken by others in solving a problem. Organic learning.No 'go-to' person for certain maintenance tasks.Everyone aware of server changes. No-one surprised that the server is down if they see/meow restart serverin the chat.Spread knowledge; everyone becomes equally capable of solving problems.Out of date help messages or documentation is more obvious to everyone.Context-aware suggestions, suggest actions and display help depending on context.Docs/procedures/etc are useful, but can be too much to read through, hard to find, not up to date.Reduce clutter when trying to figure out next actions.Reduce context switching.No need for bash, Linux, ssh or VPN to fix most server issues.No checking server logs.Easily accesible and readble output.Control access.Only gives necessary access, no unnecessary ssh-ing into production!FeaturesCompletely NoOps.Easily extensible.Pay per invocation.Provider agnostic.Quick StartInstall catopspip install catopsRunmeow install [--template] [--target-dir]Adjust the template according to your needs e.g. add Slack OAuth tokens, adjust service names etc.Install serverless dependenciesnpm installin the template directory.Runserverless deployConfigure your Slack app (i.e. set Slash command/Bot endpoint URLs to appropriate URLs)ExampleHandlerEvery Lambda function needs a handler, which takes arguments(event, context). In this case, it is necessary to respond instantly to the request with a200so the handler below calls the actual functionality asynchronously and then returns a200response.Example Handlerimportjsonfromsix.moves.urllib.parseimportparse_qsimportrequestsimportboto3fromcatopsimportconvert_dispatchdefrespond(event,context):"""Call handler.main asynchronously and then return instant response."""lambda_client=boto3.client('lambda')response={'statusCode':'200'}# Call actual function asynchronouslylambda_client.invoke(FunctionName='CatOps-dev-dispatcher',InvocationType='Event',Payload=json.dumps(event))returnresponsedefmain(event,context):"""Main lamda function logic, to be called asynchronously."""params=parse_qs(event.get('body'))payload=convert_dispatch(params)username=params.get('user_name',['catops'])[0]# Post to Slack channelresponse_url=params.get('response_url')iftype(response_url)islist:response_url=response_url[0]r=requests.post(response_url,data=json.dumps(payload))ifnotr.ok:print(r)print(r.reason)print(r.text)returnPluginsCatOps works around plugins.Plugins are python files stored in the 'plugins/' directory.CatOps scans this directory for valid functions to import.All files and/or functions starting with_are ignored. (_means they are private and will not be added to the CatOps dispatcher)Other functions are added to the CatOps dispatcher and can be called via/catops <functionname> [argv]All functions need to have the arguments(argv, params)argv will contain the arguments passed to the function e.g. for/catops role --user t.user, argv will contain `['role', '--user', 't.user']params will contain the parse Lambda event body, which contains all the information from Slack e.g.{"text": ... , "username": ..., "response_url": ...}.Example plugin"""example.py - example plugin for ChatOps."""fromcatopsimportcreate_slack_payloaddefping(argv,params):"""Check is working."""text='@{}Meow!'.format(params.get('user_name',['CatOps'])[0]),returncreate_slack_payload(text=text)Serverless configurationservice:CatOpspackage:include:-handler.py-plugins/**custom:pythonRequirements:slim:trueprovider:name:awsstage:${opt:stage, 'dev'}runtime:python3.6profile:serverless# Permissions for the lambda function# If using boto3, ensure correct permissions# have been granted to the lambda functioniamRoleStatements:-Effect:AllowAction:-lambda:InvokeFunction-lambda:InvokeAsyncResource:"*"functions:dispatcher:handler:handler.mainrespond:handler:handler.respondevents:-http:path:pingmethod:postplugins:-serverless-python-requirementsDeploy and Testserverlessdeploy
serverlessinvoke--functiondispatcher--path/path/to/json/data--logSeeexamplesandtemplatesfor more.Useful functions# auth.pydefverify_request(event,slack_secret,timeout=True):"""Verify that Lambda event came from Slack"""defget_user_slack(event,oauth,team_id=None,channel_id=None):"""Check whether user exists and is in specified team and channel.Arguments:event       - AWS Lambda eventoauth       - Slack OAUTH tokenteam_id     - Slack team_id (workspace, i.e. BBOXX)channel_id  - Channel user must be inReturns:False, err_msgTrue, user_dict with id, name, team_id, channel_id, email"""# dispatcher.pydefdispatch(command,params=None,plugin_dir='plugins/'):"""Create Dispatcher object and run parse_command on (command, params)"""# helpers.pydefget_slack_colour(level):"""Return Slack colour value based on log level."""defget_text(params):"""Return event_text from parse_qs event.get('body')."""defcreate_slack_attachment(fallback=None,color=None,pretext=None,author_name=None,author_link=None,author_icon=None,title=None,title_link=None,text=None,fields=None,image_url=None,thumb_url=None,footer=None,footer_icon=None,ts=None):"""Create slack attachment payloadSee https://api.slack.com/docs/message-attachments for more info.Arguments:fallback - Required plain-text summary of the attachment[color] - Colour of the attachment[pretext] - Optional text that appears above the attachment block[author_name][author_link][author_icon] - URL to author icon[title] - Title of the attachment[title_link][text] - Optional text that appears inside the attachment[fields] - Array of dicts containing more values[image_url] - URL to image attached[thumb_url] - URL to image thumbnail[footer] - Footer message[footer_icon] - URL to footer icon[ts] - timestamp"""defcreate_slack_payload(response_type='ephemeral',text="",attachments=None):"""Create a Slack payload formatted correctly."""defconvert_dispatch(params,convert_function=None,plugin_dir='plugins/'):"""Call dispatch and convert the output accordingly into a payload."""# slack_handler.pyclassSlackHandler(logging.Handler):"""Logger slack_handler which posts json log body to lambda_url."""# install.pydefinstall(argv=None):"""Install catops serverless Slack template."""# parser.pyclassArgumentParserError(Exception):"""Error raised by ArgumentParser"""passclassCatParser(argparse.ArgumentParser):"""Overrides error method to throw an error instead of exiting"""deferror(self,message):raiseArgumentParserError(message)Installationsudoapt-getinstallnpm
sudonpminstall-gserverless
npminstallserverless-python-requirements
pipinstallcatopsInstallserverless-python-requirementsin the same dir asserverless.yml.LimitationsPassive rather than active; needs to be triggered (e.g. by Slack slash commands (could run it every command))Limitations of FaaSMax size (256MB for AWS Lambda)Execution time limit (5 minute for AWS Lambda)No state (recommend using a cloud-based database for state e.g. DynamoDB for AWS)No autocomplete inside of Slack.