Advertisement
Guest User

Untitled

a guest
Jan 18th, 2017
75
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 7.28 KB | None | 0 0
  1. # pylint: disable=E1101
  2. """
  3. Tracks and rotates the on call engineer at REDACTED.
  4. Uses Slack and Google's API to obtain access to the services.
  5. """
  6.  
  7. from os import mkdir, path, environ
  8. from os.path import join, dirname
  9. from datetime import datetime, timedelta
  10. import argparse
  11.  
  12. from httplib2 import Http
  13.  
  14. from apiclient import discovery
  15.  
  16. from oauth2client.file import Storage
  17. from oauth2client import client
  18. from oauth2client import tools
  19.  
  20. from dotenv import load_dotenv
  21. from slackclient import SlackClient
  22.  
  23. # try getting flags (if any, not sure what this does)
  24. FLAGS = None
  25.  
  26. # scopes and google stuff
  27. SCOPES = 'https://www.googleapis.com/auth/calendar https://www.googleapis.com/auth/spreadsheets'
  28. CLIENT_SECRET_FILE = 'client_secrets.json'
  29. APPLICATION_NAME = 'On Call Tracker'
  30. SHEET_ID = 'REDACTED'
  31. CALENDAR_ID = 'REDACTED'
  32.  
  33. # load variables from .env file
  34. DOTENV_PATH = join(dirname(__file__), '.env')
  35. load_dotenv(DOTENV_PATH)
  36.  
  37. # set slack authentication constants
  38. SLACK_TOKEN = environ.get("SLACK_TOKEN")
  39. SLACK_CLIENT = SlackClient(SLACK_TOKEN)
  40.  
  41. def get_credentials():
  42. """Gets valid user credentials from storage.
  43.  
  44. If nothing has been stored, or if the stored credentials are invalid,
  45. the OAuth2 flow is completed to obtain the new credentials.
  46.  
  47. Returns:
  48. Credentials, the obtained credential.
  49. """
  50. home_dir = path.expanduser('~')
  51. credential_dir = path.join(home_dir, '.credentials')
  52. if not path.exists(credential_dir):
  53. mkdir(credential_dir)
  54. credential_path = path.join(credential_dir,
  55. 'calendar-python-quickstart.json')
  56.  
  57. store = Storage(credential_path)
  58. credentials = store.get()
  59. if not credentials or credentials.invalid:
  60. flow = client.flow_from_clientsecrets(CLIENT_SECRET_FILE, SCOPES)
  61. flow.user_agent = APPLICATION_NAME
  62. if FLAGS:
  63. credentials = tools.run_flow(flow, store, FLAGS)
  64. print 'Storing credentials to ' + credential_path
  65. return credentials
  66.  
  67. def add_event(event_name, service, start_date):
  68. """Adds an event to the google calendar with the given name.
  69.  
  70. Args:
  71. event_name (str): the name of the calendar event
  72. service (obj): the google calendar api service
  73. """
  74. end_date = start_date + timedelta((2 - start_date.weekday()) % 7 + 1)
  75.  
  76. description = """If unreachable, please contact either:
  77.  
  78. foo bar: foo@bar.com (555) 555-5555
  79. bar foo: bar@foo.com (555) 555-5555"""
  80.  
  81. event = {
  82. 'summary': event_name + ' On Call',
  83. 'start': {
  84. 'date': datetime.strftime(start_date, "%Y-%m-%d")
  85. },
  86. 'end': {
  87. 'date': datetime.strftime(end_date, "%Y-%m-%d")
  88. },
  89. 'description': description
  90. }
  91.  
  92. event = service.events().insert(calendarId=CALENDAR_ID, body=event).execute()
  93.  
  94. def del_all_events(service):
  95. """Deletes all on-call events from the current time onwards.
  96.  
  97. Args:
  98. service (obj): the Google Calendar API service
  99. """
  100. cur_time = datetime.utcnow().isoformat("T") + "Z"
  101. events_list = service.events().list(calendarId=CALENDAR_ID,
  102. timeMin=cur_time, q='On Call').execute()['items']
  103. on_call_events = [event for event in events_list if event['creator']['email'] == 'hello@niceday.com']
  104.  
  105. for event in on_call_events:
  106. event_id = event['id']
  107. service.events().delete(calendarId=CALENDAR_ID, eventId=event_id).execute()
  108.  
  109. print "All events successfully deleted."
  110.  
  111. def rotate_names_in_sheet(value_range, service):
  112. """Rotates a given column of cells in the Google Spreadsheet using Google's API.
  113.  
  114. Args:
  115. value_range (obj): a ValueRange object from the Google Spreadsheet
  116. service (obj): the Google API service object
  117.  
  118. Returns:
  119. The list of names, after having been rotated.
  120. """
  121. values = value_range.get('values', [])
  122.  
  123. # rotate the list
  124. values = values[1:] + values[:1]
  125.  
  126. # create new ValueRange instance
  127. request_data = {
  128. "values": values
  129. }
  130.  
  131. service.spreadsheets().values().update(
  132. spreadsheetId=SHEET_ID, range='A2:C', valueInputOption='USER_ENTERED', body=request_data
  133. ).execute()
  134.  
  135. return [value[0] for value in values]
  136.  
  137. def send_message(channel_id, message_text, user):
  138. """Sends the given message to the given slack channel.
  139.  
  140. Args:
  141. channel_id (str): the ID of the channel to post a message in
  142. message_text (str): the message text to send_message
  143. user (bool): is the message sender a user or not
  144.  
  145. The user argument determines if the message will be sent as a bot or as the
  146. currently authorized user.
  147. """
  148. if not user:
  149. SLACK_CLIENT.api_call(
  150. "chat.postMessage",
  151. channel=channel_id,
  152. text=message_text,
  153. username='Not_A_Robot',
  154. as_user=user,
  155. icon_emoji=':robot_face:'
  156. )
  157. else:
  158. SLACK_CLIENT.api_call(
  159. "chat.postMessage",
  160. channel=channel_id,
  161. text=message_text,
  162. as_user=user
  163. )
  164.  
  165. def list_channels():
  166. """Lists all the private message channels of the authorized user"""
  167. channels_call = SLACK_CLIENT.api_call("groups.list")
  168. if channels_call['ok']:
  169. return channels_call['groups']
  170. return None
  171.  
  172. def ping_slack(on_call_name, chan_list):
  173. """Pings a Slack channel with to alert the channel with the new on call engineer.
  174.  
  175. Args:
  176. on_call_name (str): the name of the new on call engineer
  177. chan_list (str): the list of channels as a json object
  178. """
  179. names = []
  180. channel_ids = []
  181.  
  182. for chan in chan_list:
  183. names.append(chan['name'])
  184. channel_ids.append(chan['id'])
  185.  
  186. channels_dict = dict(zip(names, channel_ids))
  187. send_message(channels_dict['on_call_engineers'],
  188. on_call_name + " is on call for this week.", False)
  189.  
  190. def main():
  191. """Rotates names of on call engineers in a Google Spreadsheet and updates/notifies the team.
  192.  
  193. Uses Google's calendar and sheets API as well as Slack's API to alert a Slack channel.
  194. """
  195.  
  196. # google shizz
  197. creds = get_credentials()
  198.  
  199. http = creds.authorize(Http())
  200. calendar = discovery.build('calendar', 'v3', http=http)
  201. sheets = discovery.build('sheets', 'v4', http=http)
  202.  
  203. value_range = sheets.spreadsheets().values().get(
  204. spreadsheetId=SHEET_ID, range='A2:C'
  205. ).execute()
  206.  
  207. parser = argparse.ArgumentParser()
  208. group = parser.add_mutually_exclusive_group(required=True)
  209. group.add_argument('-u', '--update', help='rotate and update/notify', action='store_true')
  210. group.add_argument('-c', '--clear',
  211. help='clears all on-call events from future', action='store_true')
  212.  
  213. args = parser.parse_args()
  214.  
  215. if args.clear or args.update:
  216. del_all_events(calendar)
  217.  
  218. if args.update:
  219. list_of_names = rotate_names_in_sheet(value_range, sheets)
  220. print "List rotated, current new on call is: " + list_of_names[0]
  221. start_date = datetime.today()
  222. # display events for next cycle
  223. for name in list_of_names:
  224. add_event(name, calendar, start_date)
  225. start_date = start_date + timedelta((2 - start_date.weekday()) % 7 + 1)
  226.  
  227. # slack shizz
  228. channels = list_channels()
  229. ping_slack(list_of_names[0], channels)
  230.  
  231. if __name__ == "__main__":
  232. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement