Backup AWS Support case history to your Wiki
Why do you need a backup?
AWS Support case histories are retained for up to 12 months.
Q: How long is case history retained?
Case history information is available for 12 months after creation.
Therefore, if you want to store the answers as knowledge in the long term, you need a backup.
This post summarises how to convert support case answers into markdown format and post them to your Wiki via the API.
Architecture
- Detecting support case closures with EventBridge event rules
- Use AWS Support API in AWS Lambda to get case details
- Formatted into Markdown format and posted to your Wiki
Note: Using AWS Support API requires a subscription to one of the following support plans: Business, Enterprise On-Ramp, or Enterprise.
This post is about GROWI, the OSS wiki, but it can be applied to any software or service that can post via API.
https://growi.org/en/
Points for Implementation
EventBridge event rule
Set to detect events where event-name is ResolveCase
.
{
"source": ["aws.support"],
"detail-type": ["Support Case Update"],
"detail": {
"event-name": ["ResolveCase"]
}
}
See documentation for examples of events.
{
"version": "0",
"id": "1aa4458d-556f-732e-ddc1-4a5b2fbd14a5",
"detail-type": "Support Case Update",
"source": "aws.support",
"account": "111122223333",
"time": "2022-02-21T15:51:31Z",
"region": "us-east-1",
"resources": [],
"detail": {
"case-id": "case-111122223333-muen-2022-7118885805350839",
"display-id": "1234563851",
"communication-id": "",
"event-name": "ResolveCase",
"origin": ""
}
}
Getting support case information
The event passed from EventBrige contains a Case ID, so use DescribeCases API to get the details. Communications is obtained separately, so includeCommunications
should be False.
def describe_case(case_id):
response = support.describe_cases(
caseIdList=[
case_id
],
includeResolvedCases=True,
includeCommunications=False
)
return response['cases'][0]
def lambda_handler(event, context):
case_info = describe_case(event['detail']['case-id'])
Cases excluded from backup
Increased service quotas and Enterprise support activation would not require backup.
def lambda_handler(event, context):
case_info = describe_case(event['detail']['case-id'])
if case_info['serviceCode'] == "service-limit-increase" or \
case_info['subject'] == "Enterprise Activation Request for Linked account.":
return {
'Result': 'Service limit increase or Enterprise support activation will not be posted.'
}
Get communication history with support
Use DescribeCommunications API. The data is retrieved from the most recent communication, so the order is reordered and concatenated.
Certain symbols of three or more consecutive characters are displayed as headers or horizontal lines in Markdown notation. They are replaced to avoid involuntary conversions.
def describe_communications(case_id):
body = ""
paginator = support.get_paginator('describe_communications')
page_iterator = paginator.paginate(caseId=case_id)
for page in page_iterator:
for communication in page['communications']:
body = re.sub(
r'-{3,}|={3,}|\*{3,}|_{3,}', "...", communication['body']
) + '\n\n---\n\n' + body
communications = '## Communications\n' + body
return communications
Post to GROWI
The API reference is below.
New pages are created using createPage
(POST /pages
).
{
"body": "string",
"path": "/",
"grant": 1,
"grantUserGroupId": "5ae5fccfc5577b0004dbd8ab",
"pageTags": [
{
"_id": "5e2d6aede35da4004ef7e0b7",
"name": "daily",
"count": 3
}
],
"createFromPageTree": true
}
- Only
body
(the body of the article) andpath
(the path to the post) are required. - The
grant
specifies the extent to which the page is public (1: public, 2: only people who know the link, etc.). - In practice, you should also include the
access_token
(api_key).
def create_payload(account_id, case_info):
token = os.environ['API_KEY']
title = '# ' + case_info['subject'] + '\n'
information = '## Case Information\n' + \
'* Account ID: ' + account_id + '\n' + \
'* Case ID: ' + case_info['displayId'] + '\n' +\
'* Create Date ' + case_info['timeCreated'] + '\n' + \
'* Severity: ' + case_info['severityCode'] + '\n' + \
'* Service: ' + case_info['serviceCode'] + '\n' + \
'* Category: ' + case_info['categoryCode'] + '\n'
communications = describe_communications(case_info['caseId'])
return {
'access_token': token,
'path': '/PathYouWantToPost/' + case_info['subject'],
'body': title + information + communications,
'grant': 1,
}
def lambda_handler(event, context):
case_info = describe_case(event['detail']['case-id'])
payload = create_payload(event['account'], case_info)
url = os.environ['API_URL']
headers = {
'Content-Type': 'application/json',
}
req = Request(url, json.dumps(payload).encode('utf-8'), headers)
Lambda function example
The function execution role must be granted AWS Support referencing rights.
from logging import getLogger, INFO
import json
import os
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
import re
from botocore.exceptions import ClientError
import boto3
logger = getLogger()
logger.setLevel(INFO)
support = boto3.client('support')
def describe_communications(case_id):
body = ''
try:
paginator = support.get_paginator('describe_communications')
page_iterator = paginator.paginate(caseId=case_id)
except ClientError as err:
logger.error(err.response['Error']['Message'])
raise
for page in page_iterator:
for communication in page['communications']:
body = re.sub(
r'-{3,}|={3,}|\*{3,}|_{3,}', "...", communication['body']
) + '\n\n---\n\n' + body
communications = '## Communications\n' + body
return communications
def create_payload(account_id, case_info):
token = os.environ['API_KEY']
title = '# ' + case_info['subject'] + '\n'
information = '## Case Information\n' + \
'* Account ID: ' + account_id + '\n' + \
'* Case ID: ' + case_info['displayId'] + '\n' +\
'* Create Date ' + case_info['timeCreated'] + '\n' + \
'* Severity: ' + case_info['severityCode'] + '\n' + \
'* Service: ' + case_info['serviceCode'] + '\n' + \
'* Category: ' + case_info['categoryCode'] + '\n'
communications = describe_communications(case_info['caseId'])
return {
'access_token': token,
'path': '/PathYouWantToPost/' + case_info['subject'],
'body': title + information + communications,
'grant': 1,
}
def describe_case(case_id):
try:
response = support.describe_cases(
caseIdList=[
case_id
],
includeResolvedCases=True,
includeCommunications=False
)
except ClientError as err:
logger.error(err.response['Error']['Message'])
raise
else:
return response['cases'][0]
def lambda_handler(event, context):
case_info = describe_case(event['detail']['case-id'])
if case_info['serviceCode'] == "service-limit-increase" or \
case_info['subject'] == "Enterprise Activation Request for Linked account.":
return {
'Result': 'Service limit increase or Enterprise support activation will not be posted.'
}
payload = create_payload(event['account'], case_info)
url = os.environ['API_URL']
headers = {
'Content-Type': 'application/json',
}
req = Request(url, json.dumps(payload).encode('utf-8'), headers)
try:
response = urlopen(req)
response.read()
except HTTPError as e:
return {
'Result': f'''Request failed: {e.code}, {e.reason})'''
}
except URLError as e:
return {
'Result': f'''Request failed: {e.reason})'''
}
else:
return {
'Result' : 'Knowledge posted.'
}
The results of the POST are as follows. Sorry, I can't share the case's contents, so it's mostly blacked out.
I hope this will be of help to someone else.
Subscribe to my newsletter
Read articles from hayao_k directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by