Merge "Update SFDC handler."
diff --git a/sensu/files/handlers/sfdc.json b/sensu/files/handlers/sfdc.json
index 87d2019..e683bdb 100644
--- a/sensu/files/handlers/sfdc.json
+++ b/sensu/files/handlers/sfdc.json
@@ -13,6 +13,7 @@
 }
 {%- endif %}
 {%- if handler_setting == "config" %}
+{%- set token_cache_file = handler.get('token_cache_file', False) %}
 {
   "sfdc": {
     "sfdc_client_id": "{{ handler.sfdc_client_id }}",
@@ -22,6 +23,9 @@
     "sfdc_auth_url": "{{ handler.sfdc_auth_url }}",
     "environment": "{{ handler.environment }}",
     "sfdc_organization_id": "{{ handler.sfdc_organization_id }}"
+{%- if token_cache_file %}
+    ,"token_cache_file": "{{ token_cache_file }}"
+{%- endif %}
   }
 }
-{%- endif %}
\ No newline at end of file
+{%- endif %}
diff --git a/sensu/files/plugins/handlers/notification/sfdc.py b/sensu/files/plugins/handlers/notification/sfdc.py
old mode 100644
new mode 100755
index c813c1f..e02f76b
--- a/sensu/files/plugins/handlers/notification/sfdc.py
+++ b/sensu/files/plugins/handlers/notification/sfdc.py
@@ -17,6 +17,7 @@
 from argparse import ArgumentParser
 from salesforce import OAuth2, Client
 
+
 try:
     from sensu import Handler
 except ImportError:
@@ -30,6 +31,19 @@
 
 class SfdcHandler(Handler):
 
+    def _format_body(self, data):
+        s = ""
+        keys = list(data.keys())
+        keys.sort()
+        for k in keys:
+            if k == 'Description':
+                continue
+            else:
+                s += "{}: {}\n".format(k, data[k])
+        if 'Description' in keys:
+            s += "\nDescription:\n" + self._format_body(data['Description'])
+        return s
+
     def handle(self):
         client_id = self.settings.get('sfdc', {}).get('sfdc_client_id')
         client_secret = self.settings.get('sfdc', {}).get('sfdc_client_secret')
@@ -38,14 +52,16 @@
         auth_url = self.settings.get('sfdc', {}).get('sfdc_auth_url')
         organization_id = self.settings.get('sfdc', {}).get('sfdc_organization_id')
         environment = self.settings.get('sfdc', {}).get('environment')
+        token_cache_file = self.settings.get('sfdc', {}).get('token_cache_file', None)
+
         print self.event
         print "client_id: ", client_id
         print "client_secrete: ", client_secret
         print "auth_url: ", auth_url
         print "organization: ", organization_id
         print "username: ", username
-#        print "password", password
-        sfdc_oauth2 = OAuth2(client_id, client_secret, username, password, auth_url, organization_id)
+        sfdc_oauth2 = OAuth2(client_id, client_secret, username, password,
+                             auth_url, organization_id)
 
         data = self.event
         client_host = data.get('client', {}).get('name')
@@ -53,8 +69,9 @@
         check_action = data.get('action')
         timestamp = data.get('check', {}).get('issued')
         check_date = datetime.fromtimestamp(int(timestamp)).strftime('%Y-%m-%d %H:%M:%S')
-        description = data.get('check', {}).get('output')
+        check_output = data.get('check', {}).get('output')
         status = data.get('check', {}).get('status')
+
         severity_map = {
             0: '060 Informational',
             1: '080 Warning',
@@ -65,14 +82,15 @@
             'create': 'PROBLEM',
             'resolve': 'RECOVERY'
         }
-        if isinstance(status, int):
-            severity = severity_map[status]
-        else:
-            severity = "none"
 
-        if isinstance(check_action, str):
+        try:
+            severity = severity_map[int(status)]
+        except (KeyError, ValueError):
+            severity = "none"
+            
+        try:
             notification = notification_map[check_action]
-        else:
+        except KeyError:
             notification = "CUSTOM"
 
         Alert_ID = '{}--{}--{}'.format(environment,client_host, check_name)
@@ -81,34 +99,55 @@
         LOG.debug('Alert_Id: {} '.format(Alert_ID))
 
         sfdc_client = Client(sfdc_oauth2)
+        # read cached token if it exists
+        if token_cache_file:
+            try:
+                with open(token_cache_file, 'r') as fp:
+                    cached_tokens = yaml.load(fp)
+            except IOError as e:
+                cached_tokens = None
+                LOG.debug('Error reading token_cache_file')
+                LOG.debug(e)
+            if isinstance(cached_tokens, dict) and 'access' in cached_tokens \
+                    and 'instance_url' in cached_tokens:
+
+                sfdc_client.access_token = cached_tokens['access']
+                sfdc_client.instance_url = cached_tokens['instance_url']
+
+                #TODO: There's probably better way to handle this test.
+                test_response = sfdc_client.get_case('case_id_that_doesnt_exist')
+                if test_response.status_code == 401:
+                    # If auth fails, reset tokens to None to force re-auth.
+                    sfdc_client.access_token = None
+                    sfdc_client.instance_url = None
+                    LOG.debug('Cached access token expired.  Going to re-auth.')
+                else:
+                    LOG.debug('Using cached access token.')
 
         print "severity", severity
-        print "check_action", check_action #resolve, create
-        print "description", description
+        print "check_action", check_action
+        print "check_output", check_output
         print "long_date_time", check_date
         print "environment", environment
         print "NOTIFICATION", notification
-#        severity = "CRITICAL"
-#        notification = "PROBLEM"
-#        check_date = "Wed Sep 7 14:09:58 CEST 2016"
         payload = {
             'notification_type': notification,
-            'description':       description,
+            'check_output':      check_output,
             'long_date_time':    check_date,
-             }
+        }
 
+        subject = "{}/{}".format(client_host, check_name)
         data = {
             'IsMosAlert__c':     'true',
-            'Description':       json.dumps(payload, sort_keys=True, indent=4),
+            #'Description':       json.dumps(payload, sort_keys=True, indent=4),
+            'Description':       self._format_body(payload),
             'Alert_ID__c':       Alert_ID,
-            'Subject':           Alert_ID,
+            'Subject':           subject,
             'Environment2__c':   environment,
             'Alert_Priority__c': severity,
             'Alert_Host__c':     client_host,
             'Alert_Service__c':  check_name,
-
-        #        'sort_marker__c': sort_marker,
-            }
+        }
 
         feed_data_body = {
             'Description':    payload,
@@ -116,7 +155,7 @@
             'Cloud_ID':       environment,
             'Alert_Priority': severity,
             'Status':         "New",
-            }
+        }
 
         try:
             new_case = sfdc_client.create_case(data)
@@ -125,84 +164,147 @@
             sys.exit(1)
 
 
-
         #  If Case exist
-        if (new_case.status_code  == 400) and (new_case.json()[0]['errorCode'] == 'DUPLICATE_VALUE'):
-            LOG.debug('Code: {}, Error message: {} '.format(new_case.status_code, new_case.text))
+        if (new_case.status_code == 400) and \
+                (new_case.json()[0]['errorCode'] == 'DUPLICATE_VALUE'):
+
+            LOG.debug('Code: {}, Error message: {} '.format(new_case.status_code,
+                                                            new_case.text))
             # Find Case ID
             ExistingCaseId = new_case.json()[0]['message'].split(" ")[-1]
             LOG.debug('ExistingCaseId: {} '.format(ExistingCaseId))
-            # Get Case 
+            # Get Case
             current_case = sfdc_client.get_case(ExistingCaseId).json()
-            LOG.debug("Existing Case: \n {}".format(json.dumps(current_case,sort_keys=True, indent=4)))
+            LOG.debug("Existing Case: \n {}".format(json.dumps(current_case,
+                                                    sort_keys=True, indent=4)))
 
-            LastModifiedDate=current_case['LastModifiedDate']
-            Now=datetime.now().replace(tzinfo=None)
+            LastModifiedDate = current_case['LastModifiedDate']
+            ExistingCaseStatus = current_case['Status']
+            feed_data_body['Status'] = ExistingCaseStatus
+
+            Now = datetime.now().replace(tzinfo=None)
             delta = Now - dateutil.parser.parse(LastModifiedDate).replace(tzinfo=None)
 
-            LOG.debug("Check if Case should be marked as OUTDATED. Case modification date is: {} , Now: {} , Delta(sec): {}, OutdateDelta(sec): {}".format(LastModifiedDate, Now, delta.seconds, DELTA_SECONDS))
+            LOG.debug("Check if Case should be marked as OUTDATED. Case "
+                      "modification date is: {} , Now: {} , Delta(sec): {}, "
+                      "OutdateDelta(sec): {}".format(LastModifiedDate, Now,
+                                                     delta.seconds, DELTA_SECONDS))
+
             if (delta.seconds > DELTA_SECONDS):
                 # Old Case is outdated
+                tmp_date = datetime.strftime(datetime.now(), "%Y.%m.%d-%H:%M:%S")
                 new_data = {
-                   'Alert_Id__c': '{}_closed_at_{}'.format(current_case['Alert_ID__c'],datetime.strftime(datetime.now(), "%Y.%m.%d-%H:%M:%S")),
+                   'Alert_Id__c': '{}_closed_at_{}'.format(current_case['Alert_ID__c'],
+                                                           tmp_date),
                    'Alert_Priority__c': '000 OUTDATED',
                 }
                 u = sfdc_client.update_case(id=ExistingCaseId, data=new_data)
-                LOG.debug('Upate status code: {} \n\nUpate content: {}\n\n Upate headers: {}\n\n'.format(u.status_code,u.content, u.headers))
+                LOG.debug('Update status code: {} \n\nUpdate content: {}'
+                          '\n\n Update headers: {}\n\n'.format(u.status_code,
+                                                               u.content,
+                                                               u.headers))
 
-                # Try to create new caset again 
+                # Try to create new case again
                 try:
                     new_case = sfdc_client.create_case(data)
                 except Exception as E:
                     LOG.debug(E)
                     sys.exit(1)
                 else:
-                   # Case was outdated an new was created
+                    # Case was outdated an new was created
                     CaseId = new_case.json()['id']
-                    LOG.debug("Case was just created, old one was marked as Outdated")
-                    # Add commnet, because Case head should conains  LAST data  overriden on any update
+                    LOG.debug("Case was just created, old one marked as Outdated")
+                    # Add comment, because Case head should contains LAST data
+                    # overwritten on any update
                     CaseId = new_case.json()['id']
 
                     feeditem_data = {
                       'ParentId':   CaseId,
                       'Visibility': 'AllUsers',
-                      'Body': json.dumps(feed_data_body, sort_keys=True, indent=4),
+                      #'Body': json.dumps(feed_data_body, sort_keys=True, indent=4),
+                      'Body': self._format_body(feed_data_body),
                     }
-                    LOG.debug("FeedItem Data: {}".format(json.dumps(feeditem_data, sort_keys=True, indent=4)))
+                    LOG.debug("FeedItem Data: {}".format(json.dumps(feeditem_data,
+                                                                    sort_keys=True,
+                                                                    indent=4)))
                     add_feed_item = sfdc_client.create_feeditem(feeditem_data)
-                    LOG.debug('Add FeedItem status code: {} \n Add FeedItem reply: {} '.format(add_feed_item.status_code, add_feed_item.text))
+                    LOG.debug('Add FeedItem status code: {}\nAdd FeedItem '
+                              'reply: {}'.format(add_feed_item.status_code,
+                                                 add_feed_item.text))
 
             else:
                 # Update Case
+                # If ok, mark case as solved.
+                if notification == "RECOVERY":
+                    data['Status'] = 'Solved'
+                    feed_data_body['Status'] = 'Solved'
+
                 u = sfdc_client.update_case(id=ExistingCaseId, data=data)
                 LOG.debug('Upate status code: {} '.format(u.status_code))
 
                 feeditem_data = {
                     'ParentId':   ExistingCaseId,
                     'Visibility': 'AllUsers',
-                    'Body': json.dumps(feed_data_body, sort_keys=True, indent=4),
+                    #'Body': json.dumps(feed_data_body, sort_keys=True, indent=4),
+                    'Body': self._format_body(feed_data_body),
                 }
 
-                LOG.debug("FeedItem Data: {}".format(json.dumps(feeditem_data, sort_keys=True, indent=4)))
+                LOG.debug("FeedItem Data: {}".format(json.dumps(feeditem_data,
+                                                                sort_keys=True,
+                                                                indent=4)))
                 add_feed_item = sfdc_client.create_feeditem(feeditem_data)
-                LOG.debug('Add FeedItem status code: {} \n Add FeedItem reply: {} '.format(add_feed_item.status_code, add_feed_item.text))
+                LOG.debug('Add FeedItem status code: {}\nAdd FeedItem '
+                          'reply: {} '.format(add_feed_item.status_code,
+                                              add_feed_item.text))
 
-        # Else If Case did not exist before and was just  created
-        elif  (new_case.status_code  == 201):
+        # Else If Case did not exist before and was just created
+        elif (new_case.status_code == 201):
             LOG.debug("Case was just created")
-            # Add commnet, because Case head should conains  LAST data  overriden on any update
+
+            # Add comment, because Case head should contain LAST data
+            # overwritten on any update
             CaseId = new_case.json()['id']
+
+            # If OK, ensure "Solved" is in the first feed.
+            if notification == "RECOVERY":
+                feed_data_body['Status'] = 'Solved'
             feeditem_data = {
               'ParentId':   CaseId,
               'Visibility': 'AllUsers',
-              'Body': json.dumps(feed_data_body, sort_keys=True, indent=4),
+              #'Body': json.dumps(feed_data_body, sort_keys=True, indent=4),
+              'Body': self._format_body(feed_data_body),
+     
             }
-            LOG.debug("FeedItem Data: {}".format(json.dumps(feeditem_data, sort_keys=True, indent=4)))
+            LOG.debug("FeedItem Data: {}".format(json.dumps(feeditem_data,
+                                                            sort_keys=True,
+                                                            indent=4)))
             add_feed_item = sfdc_client.create_feeditem(feeditem_data)
-            LOG.debug('Add FeedItem status code: {} \n Add FeedItem reply: {} '.format(add_feed_item.status_code, add_feed_item.text))
+            LOG.debug('Add FeedItem status code: {}\nAdd FeedItem '
+                      'reply: {} '.format(add_feed_item.status_code,
+                                          add_feed_item.text))
+
+            # If OK, mark case as solved.
+            if notification == "RECOVERY":
+                data['Status'] = 'Solved'
+
+            u = sfdc_client.update_case(id=CaseId, data=data)
+            LOG.debug('Update status code: {} '.format(u.status_code))
 
         else:
-            LOG.debug("Unexpected error: Case was not created (code !=201) and Case does not exist (code != 400)")
+            LOG.debug("Unexpected error: Case was not created (code !=201) "
+                      "and Case does not exist (code != 400)")
+
+
+        # Write out token/instance_url
+        if token_cache_file:
+            try:
+                with open(token_cache_file, 'w') as fp:
+                    fp.write("access: {}\n".format(sfdc_client.access_token))
+                    fp.write("instance_url: {}\n".format(sfdc_client.instance_url))
+            except IOError as e:
+                LOG.debug('Error writing out token cache.')
+                LOG.debug(e)
+
         sys.exit(1)
 
     def check_kedb(self):