diff --git a/event.py b/event.py index 9a7da33..5ea6527 100644 --- a/event.py +++ b/event.py @@ -16,6 +16,9 @@ class Event( object ): return "content/event/" + os.path.basename( self.path ) def open( self ): + if os.path.exists( self.path ): + self.read() + return False for path in glob.glob( self.glob ): if os.path.exists( path ): self.path = path @@ -24,21 +27,84 @@ class Event( object ): return True def write( self ): - outfile = open( self.path, 'w', encoding = "utf-8" ) - outfile.write( ( '''\ + outdata = ( '''\ --- %(yaml)s --- %(md)s ''' % { 'yaml': yaml.dump( self.fields, default_flow_style = False ), 'md': self.doc, - } ) ) + } ) + outfile = open( self.path, 'w', encoding = "utf-8" ) + outfile.write( outdata ) + return outdata def read( self ): indata = open( self.path, 'r', encoding = "utf-8" ).read() - try: - ( pre, yamldata, docdata ) = re.split( "^---$", indata, flags = re.MULTILINE ) - except ValueError: - return - self.yaml = yaml.load( yamldata ) + ( pre, yamldata, docdata ) = re.split( "^---$", indata, flags = re.MULTILINE ) + self.fields = yaml.load( yamldata ) self.doc = docdata.strip() + + def set_title( self, title ): + self.fields[ '$title@' ] = title + + def set_slug( self, slug ): + self.fields[ '$slug' ] = slug + + def set_published( self, published ): + if not self.fields.get( '$date' ): + self.fields[ '$date' ] = {} + if not self.fields[ '$date' ].get( 'published' ): + self.fields[ '$date' ][ 'published' ] = published + return published + + def set_started( self, started ): + if not self.fields.get( '$date' ): + self.fields[ '$date' ] = {} + self.fields[ '$date' ][ 'started' ] = started + return started + + def set_expected( self, expected ): + if not self.fields.get( '$date' ): + self.fields[ '$date' ] = {} + self.fields[ '$date' ][ 'expected' ] = expected + return expected + + def set_finished( self, finished ): + if not self.fields.get( '$date' ): + self.fields[ '$date' ] = {} + self.fields[ '$date' ][ 'finished' ] = finished + return finished + + def set_resolved( self, resolved ): + if not self.fields.get( '$date' ): + self.fields[ '$date' ] = {} + self.fields[ '$date' ][ 'resolved' ] = resolved + return resolved + + def add_timeline( self, time, line ): + if not self.fields.get( 'timeline' ): + self.fields[ 'timeline' ] = [] + self.fields[ 'timeline' ].append( { 'time': time, 'line': line } ) + + def _toggle( self, fieldname ): + if self.fields.get( fieldname ): + del self.fields[ fieldname ] + else: + self.fields[ fieldname ] = True + return " ".join( [ x for x in [ 'incident', 'degraded', 'maintenance', 'notice', 'ok' ] if self.fields.get( x ) ] ) + + def toggle_incident( self ): + return self._toggle( 'incident' ) + + def toggle_degraded( self ): + return self._toggle( 'degraded' ) + + def toggle_maintenance( self ): + return self._toggle( 'maintenance' ) + + def toggle_notice( self ): + return self._toggle( 'notice' ) + + def toggle_ok( self ): + return self._toggle( 'ok' ) diff --git a/fih.py b/fih.py index d0b370d..761286e 100644 --- a/fih.py +++ b/fih.py @@ -53,34 +53,34 @@ def channel_name_to_raw_incident_number( channel_name ): return channel_name[ len( app.config[ "MATTERMOST_INCIDENT_CHANNEL_PREFIX" ] ): ] -def mattermost_incident_command( command, args, channel_id, raw_incident_number ): - app.mm.posts.create_post( options = { 'channel_id': channel_id, - 'message': "### Executing `" + command + "`...", - } ) +def mattermost_incident_command( command, args, channel_id, raw_incident_number, user_name ): + channel = app.mm.channels.get_channel( channel_id ) + slug = slugify.slugify( channel[ 'purpose' ] ) + ev = event.Event( path = app.config[ "STATUS_EVENT_SCHEME" ] % { "incident_number": raw_incident_number, + "slug": slug, + }, + glob = app.config[ "STATUS_EVENT_GLOB" ] % { "incident_number": raw_incident_number, + }, + ) if command == "PUBLISH": + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "### `PUBLISH` requested by @%s!" % user_name, + } ) + + # update local repository cmdline = app.config[ "MERCURIAL_BIN" ] + " update" message = exec_to_message( cmdline ) app.mm.posts.create_post( options = { 'channel_id': channel_id, 'message': message, } ) - channel = app.mm.channels.get_channel( channel_id ) - slug = slugify.slugify( channel[ 'purpose' ] ) - ev = event.Event( path = app.config[ "STATUS_EVENT_SCHEME" ] % { "incident_number": raw_incident_number, - "slug": slug, - }, - glob = app.config[ "STATUS_EVENT_GLOB" ] % { "incident_number": raw_incident_number, - }, - ) + # create/update event content file new = ev.open() ev.doc = channel[ 'header' ] - ev.fields[ '$title@' ] = channel[ 'purpose' ] - ev.fields[ '$slug' ] = raw_incident_number - if not ev.fields.get( '$date' ): - ev.fields[ '$date' ] = {} - if not ev.fields[ '$date' ].get( 'published' ): - ev.fields[ '$date' ][ 'published' ] = datetime.datetime.utcnow().strftime( "%Y-%m-%d %H:%M" ) + ev.set_title( channel[ 'purpose' ] ) + ev.set_slug( raw_incident_number ) + ev.set_published( datetime.datetime.utcnow().strftime( "%Y-%m-%d %H:%M" ) ) written = ev.write() app.mm.posts.create_post( options = { 'channel_id': channel_id, @@ -101,7 +101,15 @@ def mattermost_incident_command( command, args, channel_id, raw_incident_number app.mm.posts.create_post( options = { 'channel_id': channel_id, 'message': message, } ) + else: + cmdline = app.config[ "MERCURIAL_BIN" ] + " commit -m 'update event for incident " + raw_incident_number + " via FIH' " + ev.path_short() + message = exec_to_message( cmdline ) + if message: + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': message, + } ) + # pull, merge, commit for pull_from in app.config[ "MERCURIAL_PUSH_TO" ]: cmdline = app.config[ "MERCURIAL_BIN" ] + " pull " + pull_from message = exec_to_message( cmdline ) @@ -123,7 +131,16 @@ def mattermost_incident_command( command, args, channel_id, raw_incident_number app.mm.posts.create_post( options = { 'channel_id': channel_id, 'message': message, } ) - + + # push updated repository + for pull_from in app.config[ "MERCURIAL_PUSH_TO" ]: + cmdline = app.config[ "MERCURIAL_BIN" ] + " push " + pull_from + message = exec_to_message( cmdline ) + if message: + app.mm.posts.create_post( options = { 'channel_id': 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 ) @@ -132,34 +149,98 @@ def mattermost_incident_command( command, args, channel_id, raw_incident_number 'message': message, } ) + app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], + 'message': "### `PUBLISH` completed!", + } ) + elif command == "START": - pass + new = ev.open() + started = ev.set_started( args or datetime.datetime.utcnow().strftime( "%Y-%m-%d %H:%M" ) ) + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Event start date set by %s to: `%s`" % ( user_name, started ), + } ) elif command == "ETR": - pass + new = ev.open() + expected = ev.set_expected( args or datetime.datetime.utcnow().strftime( "%Y-%m-%d %H:%M" ) ) + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Event estimated time of resolution set by %s to: `%s`" % ( user_name, expected ), + } ) + elif command == "FINISHED": + new = ev.open() + finished = ev.set_finished( args or datetime.datetime.utcnow().strftime( "%Y-%m-%d %H:%M" ) ) + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Event finished time set by %s to: `%s`" % ( user_name, finished ), + } ) elif command == "RESOLVED": - pass - elif command == "CLOSED": - pass + new = ev.open() + resolved = ev.set_resolved( args or datetime.datetime.utcnow().strftime( "%Y-%m-%d %H:%M" ) ) + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Event resolved time set by %s to: `%s`" % ( user_name, resolved ), + } ) elif command == "TIMELINE": - pass + if not args: + return "You need to specify some text to add to the timeline..." + + new = ev.open() + ev.add_timeline( time = datetime.datetime.utcnow().strftime( "%Y-%m-%d %H:%M" ), line = args ) + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Timeline entry added by @%s:\n\n> %s" % ( user_name, args ), + } ) + elif command == "INCIDENT": - pass + new = ev.open() + flags = ev.toggle_incident() + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Flags set by @%s to: `%s`" % ( user_name, flags ), + } ) elif command == "DEGRADED": - pass + new = ev.open() + flags = ev.toggle_degraded() + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Flags set by @%s to: `%s`" % ( user_name, flags ), + } ) elif command == "MAINTENANCE": - pass + new = ev.open() + flags = ev.toggle_maintenance() + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Flags set by @%s to: `%s`" % ( user_name, flags ), + } ) elif command == "NOTICE": - pass + new = ev.open() + flags = ev.toggle_notice() + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Flags set by @%s to: `%s`" % ( user_name, flags ), + } ) elif command == "OK": - pass + new = ev.open() + flags = ev.toggle_ok() + ev.write() + + app.mm.posts.create_post( options = { 'channel_id': channel_id, + 'message': "Flags set by @%s to: `%s`" % ( user_name, flags ), + } ) else: return "WTF command?" - app.mm.posts.create_post( options = { 'channel_id': request.form[ 'channel_id' ], - 'message': "### Finished `" + command + "`!", - } ) - - return "OK" + return "" def mattermost_incident_start( description, team_id, user_name, user_id, channel_id ): raw_incident_number = incident.generate_raw_incident_number() @@ -199,10 +280,10 @@ The contents of this channel are strictly confidential. ## Status Updates * add an update: **/incident TIMELINE** _text_ -* set the incident start: **/incident START** _%(now)s_ (UTC) -* set the incident ETR: **/incident ETR** _%(now)s_ (UTC) -* set the incident resolved time: **/incident RESOLVED** _%(now)s_ (UTC) -* set the incident finished time: **/incident CLOSED** _%(now)s_ (UTC) +* set the event start: **/incident START** _%(now)s_ (UTC) +* set the event ETR: **/incident ETR** _%(now)s_ (UTC) +* set the event finished time: **/incident FINISHED** _%(now)s_ (UTC) +* set the event closure time: **/incident RESOLVED** _%(now)s_ (UTC) Again, don't forget to **/incident PUBLISH** once making changes. @@ -248,6 +329,7 @@ def mattermost_incident(): return mattermost_incident_command( command = text[ 0 ].upper(), args = " ".join( text[ 1: ] ), channel_id = request.form[ 'channel_id' ], + user_name = request.form[ 'user_name' ], raw_incident_number = raw_incident_number, ) else: