from flask import Flask, request import os import incident import datetime import subprocess import re def strip_ansi( text, escape = re.compile( r'\x1B\[[0-?]*[ -/]*[@-~]' ) ): return escape.sub( '', text ) def strip_progress( text, escape = re.compile( r'\r[^\n]*\n' ) ): return escape.sub( '', text ) app = Flask( __name__ ) app.config.from_envvar( 'FIH_SETTINGS' ) app.mm = incident.Mattermost( url = app.config[ 'MATTERMOST_URL' ], port = app.config[ 'MATTERMOST_PORT' ], login_id = app.config[ 'MATTERMOST_USER_EMAIL' ], password = app.config[ 'MATTERMOST_USER_PASSWORD' ], ).mm @app.route( '/mattermost/incident', methods = [ 'POST' ] ) def mattermost_incident(): # channel_name: testing # command: /incident # channel_id: o3ob1utim3nummoswjyt4174mo # user_name: marek # test_domain: faelix # text: test # team_id: 3woo8mbjrbb1in53xwdzi4ynqh # user_id: ko3uxmend7ne8ger4uw9mxkt1o # token: XXXXXXXXXXXXXXXXXXXXXXXXXX if request.form[ 'token' ] in app.config[ 'MATTERMOST_COMMAND_TOKENS' ]: incident_number = incident.generate_incident_number() fi_number = "FI#" + incident_number channel_name = "incident" + incident_number purpose = request.form[ 'text' ] channel = app.mm.channels.create_channel( options = { "team_id": request.form[ 'team_id' ], "name": channel_name, "display_name": fi_number, "purpose": purpose, "header": "", "type": "O", } ) # add user to the channel we just created app.mm.client.make_request( 'post', '/channels/' + channel[ 'id' ] + '/members', options = { 'user_id': request.form[ 'user_id' ], } ) app.mm.posts.create_post( options = { 'channel_id': channel[ 'id' ], 'message': "Incident discussion channel for [%s](https://status.faelix.net/incident/%s) created by @%s." % ( fi_number, incident_number, request.form[ 'user_name' ] ), } ) now = datetime.datetime.utcnow().strftime( "%Y-%m-%dT%H:%M:%S" ) app.mm.posts.create_post( options = { 'channel_id': channel[ 'id' ], 'message': """\ # Incident Response Communication Procedure The contents of this channel are strictly confidential. ## Public Announcement 1. adjust the name of the incident with **/purpose** 2. adjust the description of the incident with **/header** 3. choose a front page status (see below) 4. publish https://status.faelix.net/incident/%(incident_number)s with **@FIH PUBLISH** ## Status Updates * add an update: **@FIH TIMELINE** _text_ * set the incident start: **@FIH START** _%(now)s_ (UTC) * set the incident ETR: **@FIH ETR** _%(now)s_ (UTC) * set the incident resolved time: **@FIH RESOLVED** _%(now)s_ (UTC) * set the incident finished time: **@FIH CLOSED** _%(now)s_ (UTC) Again, don't forget to **@FIH PUBLISH** once making changes. ## Front Page Status * **@FIH INCIDENT** * **@FIH DEGRADED** * **@FIH MAINTENANCE** * **@FIH NOTICE** * **@FIH OK** Again, after setting the headline status you must: **@FIH PUBLISH** ## Good luck, Incident Commander @%(commander_name)s!""" % { "incident_number": incident_number, "commander_name": request.form[ 'user_name' ], "now": now, }, } ) app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], 'message': 'On behalf of @%s I have started incident ~%s.' % ( request.form[ 'user_name' ], channel_name, ), } ) return "" else: return "Invalid command token." def exec_to_message( cmdline ): results = "`" + cmdline + "`:\n\n" process = subprocess.Popen( cmdline, shell = True, cwd = app.config[ "STATUS_ROOT" ], stdout = subprocess.PIPE, stderr = subprocess.PIPE ) stdout = strip_progress( strip_ansi( process.stdout.read().decode( 'ascii', 'ignore' ) ) ) if stdout: results += "\n\n```\n" + stdout.replace( "```", "` ` `" ) + "```\n" stderr = strip_progress( strip_ansi( process.stderr.read().decode( 'ascii', 'ignore' ) ) ) if stderr: results += "\n\n```\n" + stderr.replace( "```", "` ` `" ) + "```\n" if not stdout and not stderr: results += '_no output_\n' return results @app.route( '/mattermost/fih', methods = [ 'POST' ] ) def mattermost_fih(): # text: @FIH PUBLISH # file_ids: # user_name: marek # trigger_word: @FIH # channel_name: incident201806158693 # timestamp: 1529100914 # channel_id: zq61mk8ig7rmibtbyqf4babhfr # team_id: 3woo8mbjrbb1in53xwdzi4ynqh # token: nn54y4ozbtnzb8pri3c4gdhbce # user_id: ko3uxmend7ne8ger4uw9mxkt1o # team_domain: faelix # post_id: 8cxn8yeyepyczcfbazdsd1bonw if request.form[ 'token' ] in app.config[ 'MATTERMOST_WEBHOOK_TOKENS' ]: cmd = request.form[ 'text' ].strip().split()[ 1 ].upper() app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], 'message': "### Executing `" + cmd + "`...", } ) if cmd == "PUBLISH": for pull_from in app.config[ "MERCURIAL_PUSH_TO" ]: cmdline = app.config[ "MERCURIAL_BIN" ] + " pull " + pull_from message = exec_to_message( cmdline ) app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], 'message': message, } ) cmdline = app.config[ "MERCURIAL_BIN" ] + " merge" message = exec_to_message( cmdline ) app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], 'message': message, } ) cmdline = app.config[ "MERCURIAL_BIN" ] + " commit -m 'merge from " + pull_from + "'" message = exec_to_message( cmdline ) app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], 'message': message, } ) for deploy_to in app.config[ "GROW_DEPLOY_TO" ]: cmdline = app.config[ "GROW_BIN" ] + " deploy -f " + deploy_to message = exec_to_message( cmdline ) app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], 'message': message, } ) elif cmd == "START": pass elif cmd == "ETR": pass elif cmd == "RESOLVED": pass elif cmd == "CLOSED": pass elif cmd == "TIMELINE": pass elif cmd == "INCIDENT": pass elif cmd == "DEGRADED": pass elif cmd == "MAINTENANCE": pass elif cmd == "NOTICE": pass elif cmd == "OK": pass else: return "WTF command?" app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], 'message': "### Finished `" + cmd + "`!", } ) return "OK" else: return "Invalid command token." @app.route( '/' ) def hello_world(): return 'Hello, World!'