├── media ├── testfile ├── WX20200505-214220.png ├── WX20200505-214553.png ├── WX20200505-220505.png ├── WX20200508-221500.png ├── WX20200505-213724@2x.png ├── Screen Shot 2020-02-23 at 9.02.34 pm.png ├── Screen Shot 2020-02-23 at 9.30.48 pm.png ├── Screen Shot 2020-02-23 at 9.32.55 pm.png ├── Screen Shot 2020-02-23 at 9.34.54 pm.png ├── Screen Shot 2020-02-27 at 10.04.33 pm.png ├── Screen Shot 2020-02-27 at 10.04.52 pm.png ├── Screen Shot 2020-02-27 at 8.24.34 pm.png ├── Screen Shot 2020-02-27 at 8.35.19 pm.png ├── Screen Shot 2020-02-27 at 9.46.46 pm.png ├── Screen Shot 2020-02-27 at 9.52.27 pm.png ├── Screen Shot 2020-02-27 at 9.58.07 pm.png ├── Screen Shot 2020-03-06 at 4.02.42 pm.png ├── Screen Shot 2020-03-08 at 4.48.39 pm.png ├── Screen Shot 2020-03-08 at 4.56.48 pm.png ├── Screen Shot 2020-03-08 at 9.02.24 pm.png ├── Screen Shot 2020-03-08 at 9.13.36 pm.png ├── Screen Shot 2020-03-08 at 9.15.56 pm.png ├── Screen Shot 2020-03-08 at 9.16.11 pm.png ├── Screen Shot 2020-03-08 at 9.16.36 pm.png ├── Screen Shot 2020-03-08 at 9.49.24 pm.png ├── Screen Shot 2020-03-09 at 9.20.09 pm.png ├── Screen Shot 2020-03-09 at 9.22.56 pm.png ├── Screen Shot 2020-03-14 at 4.29.45 pm.png ├── Screen Shot 2020-03-14 at 4.37.33 pm.png ├── Screen Shot 2020-05-05 at 9.31.00 pm.png ├── Screen Shot 2020-05-05 at 9.33.32 pm.png ├── Screen Shot 2020-05-08 at 10.00.57 pm.png ├── Screen Shot 2020-05-08 at 10.01.05 pm.png ├── Screen Shot 2020-05-08 at 10.07.45 pm.png ├── Screen Shot 2020-05-08 at 10.08.25 pm.png └── Screen Shot 2020-05-08 at 10.18.05 pm.png ├── manifest.json ├── aws-health-cfn-stack.yaml ├── health_org_demo.py ├── README.md └── lambda_function.py /media/testfile: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /media/WX20200505-214220.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/WX20200505-214220.png -------------------------------------------------------------------------------- /media/WX20200505-214553.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/WX20200505-214553.png -------------------------------------------------------------------------------- /media/WX20200505-220505.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/WX20200505-220505.png -------------------------------------------------------------------------------- /media/WX20200508-221500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/WX20200508-221500.png -------------------------------------------------------------------------------- /media/WX20200505-213724@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/WX20200505-213724@2x.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-23 at 9.02.34 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-23 at 9.02.34 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-23 at 9.30.48 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-23 at 9.30.48 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-23 at 9.32.55 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-23 at 9.32.55 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-23 at 9.34.54 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-23 at 9.34.54 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-27 at 10.04.33 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-27 at 10.04.33 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-27 at 10.04.52 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-27 at 10.04.52 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-27 at 8.24.34 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-27 at 8.24.34 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-27 at 8.35.19 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-27 at 8.35.19 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-27 at 9.46.46 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-27 at 9.46.46 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-27 at 9.52.27 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-27 at 9.52.27 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-02-27 at 9.58.07 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-02-27 at 9.58.07 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-06 at 4.02.42 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-06 at 4.02.42 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-08 at 4.48.39 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-08 at 4.48.39 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-08 at 4.56.48 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-08 at 4.56.48 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-08 at 9.02.24 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-08 at 9.02.24 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-08 at 9.13.36 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-08 at 9.13.36 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-08 at 9.15.56 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-08 at 9.15.56 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-08 at 9.16.11 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-08 at 9.16.11 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-08 at 9.16.36 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-08 at 9.16.36 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-08 at 9.49.24 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-08 at 9.49.24 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-09 at 9.20.09 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-09 at 9.20.09 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-09 at 9.22.56 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-09 at 9.22.56 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-14 at 4.29.45 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-14 at 4.29.45 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-03-14 at 4.37.33 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-03-14 at 4.37.33 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-05-05 at 9.31.00 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-05-05 at 9.31.00 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-05-05 at 9.33.32 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-05-05 at 9.33.32 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-05-08 at 10.00.57 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-05-08 at 10.00.57 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-05-08 at 10.01.05 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-05-08 at 10.01.05 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-05-08 at 10.07.45 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-05-08 at 10.07.45 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-05-08 at 10.08.25 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-05-08 at 10.08.25 pm.png -------------------------------------------------------------------------------- /media/Screen Shot 2020-05-08 at 10.18.05 pm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/HEAD/media/Screen Shot 2020-05-08 at 10.18.05 pm.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileLocations": [ 3 | { 4 | "URIs": [ 5 | 6 | "https://bucket-name.s3.amazonaws.com/event_data_file.csv" 7 | 8 | ] 9 | }, 10 | ], 11 | "globalUploadSettings": { 12 | "format": "CSV", 13 | "delimiter": ",", 14 | "containsHeader": "true" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /aws-health-cfn-stack.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Transform: AWS::Serverless-2016-10-31 4 | Description: AWS Cloudformation Template to launch S3 bucket, IAM Lambda Role and 5 | Health API polling Lambda. 6 | Parameters: 7 | S3BucketNameforHealthData: 8 | Description: Name of the new S3 Bucket to host AWS Health API data 9 | AllowedPattern: ^[0-9a-zA-Z]+([0-9a-zA-Z-]*[0-9a-zA-Z])*$ 10 | Type: String 11 | Resources: 12 | AWSHealthApiDataHostingBucket: 13 | Type: AWS::S3::Bucket 14 | Properties: 15 | BucketName: 16 | Ref: S3BucketNameforHealthData 17 | CopyZips: 18 | Type: Custom::CopyZips 19 | Properties: 20 | ServiceToken: !GetAtt 'CopyZipsFunction.Arn' 21 | DestBucket: !Ref 'AWSHealthApiDataHostingBucket' 22 | SourceBucket: 'my-public-bucket-2021' 23 | Prefix: '' 24 | Objects: 25 | - aws-health-api-polling-ae1b9c80-aae2-44f2-9a3c-9c282db54ee3.zip 26 | DependsOn: 27 | - LambdaExeRole 28 | - AWSHealthApiDataHostingBucket 29 | LambdaExeRole: 30 | Type: AWS::IAM::Role 31 | Properties: 32 | RoleName: HealthPollingLambdaExecutionRole 33 | AssumeRolePolicyDocument: 34 | Version: '2012-10-17' 35 | Statement: 36 | - Effect: Allow 37 | Principal: 38 | Service: lambda.amazonaws.com 39 | Action: 40 | - sts:AssumeRole 41 | ManagedPolicyArns: 42 | - arn:aws:iam::aws:policy/AWSHealthFullAccess 43 | - arn:aws:iam::aws:policy/AmazonS3FullAccess 44 | - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole 45 | awshealthapipolling: 46 | Type: AWS::Serverless::Function 47 | Properties: 48 | FunctionName: HealthAPIPollingLambdaFunction 49 | Description: This Lambda function is the main function to poll health data at organizations level 50 | Environment: 51 | Variables: 52 | s3_bucket_name: 53 | Ref: S3BucketNameforHealthData 54 | Handler: lambda_function.lambda_handler 55 | Runtime: python3.8 56 | CodeUri: 57 | Bucket: 58 | Ref: S3BucketNameforHealthData 59 | Key: aws-health-api-polling-ae1b9c80-aae2-44f2-9a3c-9c282db54ee3.zip 60 | Description: lambda function 61 | MemorySize: 256 62 | Timeout: 900 63 | Role: !GetAtt 'LambdaExeRole.Arn' 64 | Events: 65 | Schedule1: 66 | Type: Schedule 67 | Properties: 68 | Schedule: rate(10 minutes) 69 | DependsOn: 70 | - LambdaExeRole 71 | - AWSHealthApiDataHostingBucket 72 | - CopyZips 73 | CopyZipsFunction: 74 | Type: AWS::Lambda::Function 75 | Properties: 76 | Description: Copies objects from a source S3 bucket to a destination 77 | Handler: index.handler 78 | Runtime: python2.7 79 | Role: !GetAtt 'LambdaExeRole.Arn' 80 | Timeout: 240 81 | Code: 82 | ZipFile: | 83 | import json 84 | import logging 85 | import threading 86 | import boto3 87 | import cfnresponse 88 | def copy_objects(source_bucket, dest_bucket, prefix, objects): 89 | s3 = boto3.client('s3') 90 | for o in objects: 91 | key = prefix + o 92 | copy_source = { 93 | 'Bucket': source_bucket, 94 | 'Key': key 95 | } 96 | print('copy_source: %s' % copy_source) 97 | print('dest_bucket = %s'%dest_bucket) 98 | print('key = %s' %key) 99 | s3.copy_object(CopySource=copy_source, Bucket=dest_bucket, 100 | Key=key) 101 | def delete_objects(bucket, prefix, objects): 102 | s3 = boto3.client('s3') 103 | objects = {'Objects': [{'Key': prefix + o} for o in objects]} 104 | s3.delete_objects(Bucket=bucket, Delete=objects) 105 | def timeout(event, context): 106 | logging.error('Execution is about to time out, sending failure response to CloudFormation') 107 | cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) 108 | def handler(event, context): 109 | # make sure we send a failure to CloudFormation if the function 110 | # is going to timeout 111 | timer = threading.Timer((context.get_remaining_time_in_millis() 112 | / 1000.00) - 0.5, timeout, args=[event, context]) 113 | timer.start() 114 | print('Received event: %s' % json.dumps(event)) 115 | status = cfnresponse.SUCCESS 116 | try: 117 | source_bucket = event['ResourceProperties']['SourceBucket'] 118 | dest_bucket = event['ResourceProperties']['DestBucket'] 119 | prefix = event['ResourceProperties']['Prefix'] 120 | objects = event['ResourceProperties']['Objects'] 121 | if event['RequestType'] == 'Delete': 122 | delete_objects(dest_bucket, prefix, objects) 123 | else: 124 | copy_objects(source_bucket, dest_bucket, prefix, objects) 125 | except Exception as e: 126 | logging.error('Exception: %s' % e, exc_info=True) 127 | status = cfnresponse.FAILED 128 | finally: 129 | timer.cancel() 130 | cfnresponse.send(event, context, status, {}, None) -------------------------------------------------------------------------------- /health_org_demo.py: -------------------------------------------------------------------------------- 1 | #################################################################################################################################################################################### 2 | # Script Function: Demonstrate AWS Health API for Organization View 3 | # Author: JC 4 | # Time: 2020.11.03 5 | # Version: 1.2 6 | # Execution requirements: 7 | # Update the "bucketName" value following the instruction from Step 10 in https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/README.md#setup 8 | #################################################################################################################################################################################### 9 | 10 | import logging 11 | import datetime 12 | import boto3 13 | import json 14 | from botocore.exceptions import ClientError 15 | import os 16 | import pandas as pd 17 | 18 | #################################################################################################################################################################################### 19 | bucketName = 'YOUR-S3-BUCKET-NAME-HERE' 20 | #################################################################################################################################################################################### 21 | 22 | arn_list = [] 23 | service_list = [] 24 | eventTypeCode_list = [] 25 | eventTypeCategory_list = [] 26 | region_list = [] 27 | startTime_list = [] 28 | endTime_list = [] 29 | lastUpdatedTime_list = [] 30 | statusCode_list = [] 31 | impactedAccount_List = [] 32 | eventDescription_List = [] 33 | impactedEntity_List = [] 34 | 35 | # time encoder class 36 | class DatetimeEncoder(json.JSONEncoder): 37 | def default(self, obj): 38 | try: 39 | return super(DatetimeEncoder, obj).default(obj) 40 | except TypeError: 41 | return str(obj) 42 | 43 | ## Upload the organization events json message to S3 file 44 | def upload_to_s3(file_name, bucket, key): 45 | """Upload a file to an S3 bucket 46 | :param file_name: File to upload 47 | :param bucket: Bucket to upload to 48 | :param object_name: S3 object name. If not specified then file_name is used 49 | :return: True if file was uploaded, else False 50 | """ 51 | s3 = boto3.resource('s3') 52 | try: 53 | s3.meta.client.upload_file(file_name, bucket, key) 54 | print("s3 upload success -- uploaded " + file_name + " to the bucket: " + bucket) 55 | except ClientError as e: 56 | logging.error(e) 57 | return False 58 | print("s3 upload error occurs", e) 59 | return True 60 | 61 | csvFileName = 'event_data_file.csv' 62 | 63 | # Store event data info into csv file 64 | def write_to_csv(): 65 | whole_table = pd.DataFrame( 66 | { 67 | 'arn': arn_list, 68 | 'service': service_list, 69 | 'eventTypeCode': eventTypeCode_list, 70 | 'eventTypeCategory': eventTypeCategory_list, 71 | 'region': region_list, 72 | 'startTime': startTime_list, 73 | 'endTime': endTime_list, 74 | 'lastUpdatedTime': lastUpdatedTime_list, 75 | 'statusCode': statusCode_list, 76 | 'impactedAccount': impactedAccount_List, 77 | 'impactedEntity': impactedEntity_List, 78 | 'eventDescription': eventDescription_List 79 | } 80 | ) 81 | event_data_file = open(csvFileName, "w") 82 | event_data_file.write(whole_table.to_csv(index=False)) 83 | event_data_file.close() 84 | print("\n#########################################################################\n") 85 | print("Event data saved to CSV file.") 86 | print("\n#########################################################################\n") 87 | 88 | ## Create the Manifest file, and save it to the user specified S3 bucket 89 | def create_manifest(): 90 | """Create a Manifest file to an user specified S3 bucket 91 | This is required when user need to visualise the event data hosted in S3 bucket via QuickSight 92 | """ 93 | dirpath = os.getcwd() 94 | file_path_ori = dirpath + "/manifest.json" 95 | file_path_new = dirpath + "/manifests3.json" 96 | 97 | with open(file_path_ori, "rt") as fin: 98 | with open(file_path_new, "wt") as fout: 99 | for line in fin: 100 | fout.write(line.replace('bucket-name', bucketName)) 101 | 102 | ## Enable the health service in organization level 103 | def enable_health_org(): 104 | client = boto3.client('health', region_name='us-east-1') 105 | try: 106 | response = client.enable_health_service_access_for_organization() 107 | print("enable health api at organization level success") 108 | except ClientError as e: 109 | logging.error(e) 110 | print("enable health api at organization level error occurs:", e) 111 | 112 | print("\n#########################################################################\n") 113 | print(response) 114 | print("\n#########################################################################\n") 115 | print("Health Service has been enabled at AWS Organization Level -- Done!") 116 | print("\n#########################################################################\n") 117 | 118 | ## describe_health_service_status_for_organization 119 | def describe_health_service_status_for_org(): 120 | client = boto3.client('health', region_name='us-east-1') 121 | response = client.describe_health_service_status_for_organization() 122 | print(response) 123 | print("\n#########################################################################\n") 124 | 125 | ## describe_events_for_organization(**kwargs) 126 | def describe_events_for_org(): 127 | client = boto3.client('health', region_name='us-east-1') 128 | event_paginator = client.get_paginator('describe_events_for_organization') 129 | event_page_iterator = event_paginator.paginate() 130 | 131 | for event_page in event_page_iterator: 132 | json_event = json.dumps(event_page, cls=DatetimeEncoder) 133 | parsed_event = json.loads(json_event) 134 | 135 | events = parsed_event.get("events") 136 | 137 | for event in events: 138 | arn_list.append(event.get("arn")) 139 | service_list.append(event.get("service")) 140 | eventTypeCode_list.append(event.get("eventTypeCode")) 141 | eventTypeCategory_list.append(event.get("eventTypeCategory")) 142 | region_list.append(event.get("region")) 143 | startTime_list.append(event.get("startTime")) 144 | lastUpdatedTime_list.append(event.get("lastUpdatedTime")) 145 | statusCode_list.append(event.get("statusCode")) 146 | if (event.get("statusCode") == "open"): 147 | endTime_list.append("NULL") 148 | elif (event.get("statusCode") == "closed"): 149 | endTime_list.append(event.get("endTime")) 150 | 151 | response = client.describe_event_details(eventArns = [event.get("arn")], locale = "en") 152 | json_response = json.dumps(response, cls=DatetimeEncoder) 153 | parsed_response = json.loads (json_response) 154 | eventDescription_List.append(parsed_response["successfulSet"][0]["eventDescription"]["latestDescription"]) 155 | 156 | print("\n#########################################################################\n") 157 | print("describe_events_for_organization response - Total events:", len(arn_list)) 158 | print("\n") 159 | print("These events are related to the following services:\n", service_list) 160 | print("\n#########################################################################\n") 161 | return(True) 162 | 163 | ## describe_affected_accounts 164 | def describe_affected_accounts(event_arn): 165 | affectedAccounts = [] 166 | client = boto3.client('health', region_name='us-east-1') 167 | event_accounts_paginator = client.get_paginator('describe_affected_accounts_for_organization') 168 | 169 | event_accounts_page_iterator = event_accounts_paginator.paginate(eventArn=event_arn) 170 | 171 | for event_accounts_page in event_accounts_page_iterator: 172 | json_event_accounts = json.dumps(event_accounts_page, cls=DatetimeEncoder) 173 | parsed_event_accounts = json.loads (json_event_accounts) 174 | 175 | if((parsed_event_accounts['affectedAccounts']) == "[]"): 176 | affectedAccounts = affectedAccounts +"[]" 177 | else: 178 | affectedAccounts = affectedAccounts + (parsed_event_accounts['affectedAccounts']) 179 | print("For service event arn: {}".format(arn)) 180 | print("Affected accounts are: {}".format(affectedAccounts)) 181 | 182 | return(affectedAccounts) 183 | 184 | ## Retrieve account id automatically 185 | def get_account_id(): 186 | return(boto3.client('sts').get_caller_identity().get('Account')) 187 | 188 | ## describe_affected_entities_for_organization(**kwargs) 189 | def describe_affected_entities(event_arn): 190 | affectedEntities = [] 191 | affectedEntities_sub_list = [] 192 | client = boto3.client('health', region_name='us-east-1') 193 | affected_accounts = describe_affected_accounts(event_arn) 194 | 195 | for affected_account in affected_accounts: 196 | affected_account = ''.join(str(e) for e in affected_account) 197 | 198 | #If there's no affected account for this event, the 'affected account' will be filled by '[]' 199 | if(not affected_account): 200 | affected_account = '[]' 201 | 202 | print("affected_account: ", affected_account) 203 | print("affected account type:", type(affected_account)) 204 | 205 | event_entities_paginator = client.get_paginator('describe_affected_entities_for_organization') 206 | event_entities_page_iterator = event_entities_paginator.paginate( 207 | organizationEntityFilters=[ 208 | { 209 | 'awsAccountId': affected_account, 210 | 'eventArn': event_arn 211 | } 212 | ] 213 | ) 214 | 215 | affectedEntities_sub_list = [] 216 | 217 | for event_entities_page in event_entities_page_iterator: 218 | json_event_entities = json.dumps(event_entities_page, cls=DatetimeEncoder) 219 | parsed_event_entities = json.loads (json_event_entities) 220 | print("for event {} and affected account {}: ".format(event_arn, affected_account)) 221 | print("event entities list are: {} \n".format(parsed_event_entities)) 222 | 223 | for entity in parsed_event_entities['entities']: 224 | 225 | if((entity['entityValue']) != "[]"): 226 | affectedEntities_sub_list.append(entity['entityValue']) 227 | 228 | print("\n#########################################################################\n") 229 | print("affected entities list are:", affectedEntities_sub_list) 230 | print("\n#########################################################################\n") 231 | 232 | if(len(affectedEntities_sub_list) == 0): 233 | affectedEntities = affectedEntities + "[]" 234 | else: 235 | affectedEntities = affectedEntities + affectedEntities_sub_list 236 | return(affectedEntities) 237 | 238 | 239 | # Main part of Script 240 | # ------------------- 241 | if __name__ == "__main__": 242 | 243 | ## Enable the health service in organization level 244 | enable_health_org() 245 | 246 | ## describe_health_service_status_for_organization 247 | describe_health_service_status_for_org() 248 | 249 | ## describe_events_for_organization(**kwargs) 250 | describe_events_for_org() 251 | 252 | ## describe_affected_accounts & describe_affected_entities 253 | for arn in arn_list: 254 | eventAffectedAccounts = describe_affected_accounts(arn) 255 | print ("eventAffectedAccounts:",eventAffectedAccounts) 256 | impactedAccount_List.append(eventAffectedAccounts) 257 | 258 | eventAffectedEntities = describe_affected_entities(arn) 259 | print ("eventAffectedEntities:",eventAffectedEntities) 260 | impactedEntity_List.append(eventAffectedEntities) 261 | 262 | print("complete impacted account list:", impactedAccount_List) 263 | print("complete impacted entity list:", impactedEntity_List) 264 | 265 | ## save event data to csv file 266 | write_to_csv() 267 | 268 | ## upload the csv file to S3 bucket 269 | upload_to_s3(file_name=csvFileName, bucket=bucketName, key=csvFileName) 270 | 271 | ## create manifest file, and save it to S3 bucket 272 | create_manifest() 273 | upload_to_s3(file_name="manifests3.json", bucket=bucketName, key="manifests3.json") 274 | 275 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building Organization Service Health Check Solution with Cloud9 and QuickSight 2 | 3 | This lab is provided as part of [AWS Summit Online](https://aws.amazon.com/events/summits/online/), click [here](https://github.com/phonghuule/awssummmitonline) to explore the full list of hands-on labs.

4 | ℹ️ You will run this lab in your own AWS account. Please follow directions at the end of the lab to remove resources to avoid future costs.
5 | ℹ️ Make sure your AWS account has "Business" or "Enterprise" support plan, so as to be able to consume Health API at Organization level.

6 | 7 | 8 | 9 | This lab is intended to showcase the Health API Organization View feature. Organization View is intended to aggregate Personal Health Dashboard/Health events at the PAYER account level within an organization. Thus it requires an Organization hierarchy, including linked accounts. Furthermore, for the data required to be aggregated at the PAYER account level, it needs to be enabled prior to the Personal Health Dashboard notification being created. 10 | 11 | At minimum, user can use their own AWS account with Business or Enterprise support and with single account under the AWS organization to run this lab. The lab content and process can be best referenced for user account with multiple linked accounts under the AWS organization. 12 | 13 |
14 | 15 | 16 | # Introduction 17 | This lab aims to show users how easy it is to call AWS health API at organization level through Cloud9. The python code initially runs locally in the Cloud9 environment, where we will upload the health status data to S3 bucket, and then visualise the data using QuickSight. Optionally, user can consider to integrate email SNS to get notification upon the conditions setup by the operation team. 18 | 19 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-23%20at%209.02.34%20pm.png) 20 |

21 | 22 | # How much will this lab cost? 23 | Base costs will be ( $USD in us-east-1): 24 | - t2.micro Cloud9 instances $0.0116 per Hour 25 | - S3 used by Cloud9 python script to store event data/manifest files with size less than 5KB 26 | - For QuickSight associated cost please refer to [Setting up QuickSight](#setting-up-quicksight) in appendix section 27 | 28 | 29 | # Setup 30 | 1. Goto to AWS Console, select Cloud9 service (In N.Virginia Region -- "us-east-1"). Or simply click the following link: 31 | https://console.aws.amazon.com/cloud9/home?region=us-east-1 32 | 33 | 2. Create a new Cloud9 environment by Clicking the "Create Environment" button 34 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-23%20at%209.30.48%20pm.png) 35 | 36 | 3. Give a name to the new environment. In this demo, we can name it as "demo-env". Then click "Next step". 37 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-23%20at%209.32.55%20pm.png) 38 | 39 | 4. Accept other default settings, click "Next step" 40 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-23%20at%209.34.54%20pm.png) 41 | 42 | 5. In the "Review" page, review the environment configurations, and click "Create environment" to kickoff the creation process. It would take around 1 to 2 mins for the Cloud9 environment to be provisioned. 43 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-03-06%20at%204.02.42%20pm.png) 44 | 45 | 6. Once the Cloud9 environment has been provisioned, use the following command to update the boto3 library -- as the current default library version (1.10.41) doesn't support the latest AWS health API for Organization. 46 | 47 | ec2-user:~/environment $ `python -m pip install boto3 -t ./` 48 | 49 | 7. As we need to use python pandas module to translate the json data into csv file, please use the following command to install pandas module 50 | 51 | ec2-user:~/environment $ `python -m pip install pandas --user` 52 | 53 | 8. Now the environment has almost ready. So please use the following command to download the project to Cloud9, and copy the "health_org_demo.py" and "manifest.json" files to the environment folder. 54 | 55 | ec2-user:~/environment $ `git clone https://github.com/JerryChenZeyun/aws-health-api-organization-view.git` 56 | 57 | ec2-user:~/environment $ `cp /home/ec2-user/environment/aws-health-api-organization-view/health_org_demo.py /home/ec2-user/environment/health_org_demo.py` 58 | 59 | ec2-user:~/environment $ `cp /home/ec2-user/environment/aws-health-api-organization-view/manifest.json /home/ec2-user/environment/manifest.json` 60 | 61 | 9. Create the S3 bucket to host the event data fed back from the organization health api call. Lab user can go to S3 console (`https://s3.console.aws.amazon.com/s3/home?region=us-east-1`) to create a new bucket. Simply give the new bucket a name, then accept all the default setting and keep on clicking "Next", and finally click "Create Bucket" to finalise S3 bucket creation. You may jot down the name which is needed in next step. 62 | 63 | For those user need more guidance on creating the new bucket, you may refer to the [Create new S3 bucket](#create-new-s3-bucket) in appendix section. 64 | 65 | 10. Use the Cloud9 editor environment to change the S3 bucket name in the "health_org_demo.py" file: 66 | 67 | "bucketName" - change it to your bucket name, which is used to host the data fetched by the health api. You can find your buckets via this link: 68 | https://s3.console.aws.amazon.com/s3/home 69 | 70 | Once the "bucketName" has been updated, you can simply save the python file. 71 | ("Command" + "S" for MAC, or "Control" + "S" for Windows) 72 | 73 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-03-14%20at%204.37.33%20pm.png) 74 | 75 | 76 | For those who interested in digging out the api call functions, you may take a quick look at the python code. 77 | 78 | 79 | 80 | 11. Execute the python script -- Use the following command in Cloud9 to proceed: 81 | 82 | ec2-user:~/environment $ `python health_org_demo.py` 83 | 84 | 85 | Once the code has been executed successfully, You can see the output from Cloud9, showing the health status info summary, and detail health info in json format. 86 | 87 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-27%20at%208.35.19%20pm.png) 88 | 89 | 12. Analyze the health event data printed at the Cloud9 terminal. In the Cloud9 terminal, we will find the json format data retrieved from health api call at organization level. 90 | 91 | Caveat: If the response for the script execution shows that there's no affected accounts/event details/entities, it's because the Lab account doesn't have any resource impacted by those service events. While Lab users should still be able to get the general service events data, including event arn, impacting regions, etc. 92 |
93 | 94 | # Visualize the health event csv data through QuickSight 95 | 96 | After the python script has been executed, the health status data would be stored in S3 bucket as csv file. We can utilise various tooling to visualise the dataset. In this Lab, we are going to use QuickSight. 97 | 98 | 1. Go to the following QuickSight link to generate the view of the dataset 99 | https://us-east-1.quicksight.aws.amazon.com/sn/start 100 | 101 | As a first time QuickSight user, you might need to sign up for QuickSight service. Please refer to [Setting up QuickSight](#setting-up-quicksight) in appendix section for the sign up process. 102 | 103 | 2. Enable QuickSight to be able to access the data file stored in S3 bucket you created 104 | 105 | a) Click the user icon on the top right corner of QuickSight page, then select "Manage QuickSight". 106 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-05-05%20at%209.33.32%20pm.png) 107 | 108 | b) Select "Security & permissions" on the left, then click the "Add or remove" button under "QuickSight access to AWS services" 109 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/WX20200505-213724%402x.png) 110 | 111 | c) Click the "Details" link under "Amazon S3", then click the "Select S3 buckets" button 112 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/WX20200505-214220.png) 113 | 114 | d) Check the box left to the bucket that you created for this lab in previous step, then click "Finish". 115 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/WX20200505-214553.png) 116 | 117 | e) Click the "Update" button. Once this done, QuickSight will have access right to this bucket to visualise the data. 118 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/WX20200505-220505.png) 119 | 120 | 121 | 3. Go back to the QuickSight front page (https://us-east-1.quicksight.aws.amazon.com/sn/start) and choose "New analysis" at the top left corner of the QuickSight page 122 | 123 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-27%20at%209.46.46%20pm.png) 124 | 125 | 4. Choose "New data set" at the top left corner of the page 126 | 127 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-27%20at%209.52.27%20pm.png) 128 | 129 | 5. Up till now, there's a "manifests3.json" file that has been stored in the S3 bucket you created for this Lab. Please jot down the URL for this json file. You can use this link (https://s3.console.aws.amazon.com/s3/object/) to go to your S3 and click on your bucket, and then click on "manifests3.json" file to copy its URL. The screenshot is for reference. 130 | 131 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-03-09%20at%209.20.09%20pm.png) 132 | 133 | 6. Select S3, then fill in "Data source name" (e.g. event_data_file). Then choose the "URL" radio button. Then paste the URL you copied from last step in the URL blank. 134 | 135 | Then hit "Connect", and then click "Visualize" to proceed. 136 | 137 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-03-09%20at%209.22.56%20pm.png) 138 | 139 | 7. Up till this step, you should be able to upload the file to QuickSight. If you meet with SPICE related error message, please take the following actions:
140 | 141 | a. Go to this link to increase the SPICE capacity in US east 1 region:https://us-east-1.quicksight.aws.amazon.com/sn/admin?#capacity. 142 | 143 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/WX20200508-221500.png) 144 | 145 | b. Fill in "1" GB in the black, the click "Purchase SPICE capacity", then you can retry from step 3 to upload the data file from S3 to QuickSight. 146 | 147 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-05-08%20at%2010.18.05%20pm.png) 148 | 149 | 150 | 8. Experience the dataset visualization -- at this step, user can simply select or drag/drop via QuickSight GUI to visualize the dataset based on specific need. the screenshots show visualize the whole dataset, and only the "region" data for the health events within the organization. 151 | 152 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-27%20at%2010.04.33%20pm.png) 153 | 154 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-02-27%20at%2010.04.52%20pm.png) 155 |
156 | 157 | Congratulations! Till this step, you've accomplished this Lab. 158 | Please proceed to next step to clean up the resource. 159 |
160 |
161 |
162 | 163 | # Clean Up Step 164 | 165 | 1. Go to Cloud9 dashboard in AWS Console via the following link. Select your IDE environment and select "Delete". 166 | 'https://console.aws.amazon.com/cloud9/home?region=us-east-1' 167 | 168 | 2. Go to the follow link to delete the S3 bucket that you created for this lab use: 169 | 'https://s3.console.aws.amazon.com/s3/home?region=us-east-1' 170 | 171 | 3. Canceling Your Amazon QuickSight Subscription and Closing the Account -- Please refer to [Canceling Your Amazon QuickSight Subscription](#Canceling-Your-Amazon-QuickSight-Subscription) in appendix section. 172 | 173 | 174 |
175 |
176 |
177 | 178 | 179 | 180 | # Appendix 181 |
182 | 183 | # Create new S3 bucket 184 | 185 | This section give you more detailed guidance on how to create the S3 bucket to host data fed back from Organization Health API Call. Please follow these steps to complete the bucket creation: 186 | 187 | 1. Go to the following link to access AWS S3 service, then click "Create bucket". 188 | `https://s3.console.aws.amazon.com/s3/home?region=us-east-1` 189 | 190 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-05-08%20at%2010.07.45%20pm.png) 191 | 192 | 2. Give the new bucket a name, which cannot duplicate with other S3 bucket that have been existed. In this example, the new bucket is named as "my-health-api-lab-202003082100", and accept other default settings, Then click "Create bucket" to finish the creation process. 193 | 194 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-05-08%20at%2010.08.25%20pm.png) 195 | 196 |
197 | 198 | # Setting up QuickSight 199 | 200 | For those AWS accounts that use QuickSight for the first time, users need to explicitely sign up for QuickSight. Please follow the following steps: 201 | 202 | 1. Go to follow link to access AWS QuickSight service, then click "Sign up for QuickSight". 203 | 204 | `https://us-east-1.quicksight.aws.amazon.com/sn/start` 205 | 206 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-03-08%20at%204.48.39%20pm.png) 207 | 208 | 2. In the QuickSight account selection page, please select "Standard" edition for this lab as shown below. Then click "Continue" at the bottom right corner. 209 | 210 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-03-08%20at%204.56.48%20pm.png) 211 | 212 | 3. Give an Account Name to QuickSight service. 213 |

a. You might use "dev-lab" as shown in screenshot.

214 |

b. Fill in an email address for the registration. For simplicity, you might use a fake email address "abc@gmail.com" to proceed.

215 |

c. leave all other options as default settings, and click "Finish" to finalise the QuickSight sign up process.

216 | 217 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-03-08%20at%209.49.24%20pm.png) 218 | 219 | # Canceling Your Amazon QuickSight Subscription 220 | 221 | Here's the public guidance link: https://docs.aws.amazon.com/quicksight/latest/user/closing-account.html 222 | 223 | 1. Goes to this URL https://us-east-1.quicksight.aws.amazon.com/sn/admin?#permissions 224 | 2. Choose Unsubscribe. 225 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-05-08%20at%2010.00.57%20pm.png) 226 | ![Image of Yaktocat](https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/media/Screen%20Shot%202020-05-08%20at%2010.01.05%20pm.png) 227 | -------------------------------------------------------------------------------- /lambda_function.py: -------------------------------------------------------------------------------- 1 | #################################################################################################################################################################################### 2 | # Script Function: Demonstrate AWS Health API for Organization View 3 | # Author: JC 4 | # Time: 2020.12.24 5 | # Version: 1.4 6 | # Execution requirements: 7 | # Update the "bucketName" value following the instruction from Step 10 in https://github.com/JerryChenZeyun/aws-health-api-organization-view/blob/master/README.md#setup 8 | #################################################################################################################################################################################### 9 | import json 10 | import logging 11 | from datetime import datetime 12 | from dateutil import parser 13 | import boto3 14 | import json 15 | import csv 16 | from itertools import zip_longest 17 | from botocore.exceptions import ClientError 18 | import os 19 | import urllib.request 20 | import fileinput 21 | from time import sleep 22 | 23 | #################################################################################################################################################################################### 24 | bucketName = os.environ.get('s3_bucket_name') 25 | #################################################################################################################################################################################### 26 | 27 | csvFileName = 'event_data_file.csv' 28 | manifest_url = 'https://raw.githubusercontent.com/JerryChenZeyun/aws-health-api-organization-view/master/manifest.json' 29 | 30 | arn_list = [] 31 | service_list = [] 32 | eventTypeCode_list = [] 33 | eventTypeCategory_list = [] 34 | region_list = [] 35 | startTime_list = [] 36 | endTime_list = [] 37 | lastUpdatedTime_list = [] 38 | statusCode_list = [] 39 | impactedAccount_List = [] 40 | eventDescription_List = [] 41 | impactedEntity_List = [] 42 | 43 | def enable_health_org(): 44 | client = boto3.client('health', 'us-east-1') 45 | try: 46 | response = client.enable_health_service_access_for_organization() 47 | print("enable health api at organization level success") 48 | except ClientError as e: 49 | logging.error(e) 50 | print("enable health api at organization level error occurs:", e) 51 | print(response) 52 | print("\n#########################################################################\n") 53 | print("Health Service has been enabled at AWS Organization Level -- Done!") 54 | print("\n#########################################################################\n") 55 | 56 | # time encoder class 57 | class DatetimeEncoder(json.JSONEncoder): 58 | def default(self, obj): 59 | try: 60 | return super(DatetimeEncoder, obj).default(obj) 61 | except TypeError: 62 | return str(obj) 63 | 64 | ## Check if the health data has been stored in the S3 bucket. So lambda will know if it needs to pull the whole set of health data 65 | def get_s3_file_status(): 66 | s3 = boto3.resource('s3') 67 | try: 68 | s3.Object(bucketName, csvFileName).load() 69 | except ClientError as e: 70 | if e.response['Error']['Code'] == "404": 71 | # The object does not exist. 72 | print ("data file doesn't exist.") 73 | return (False) 74 | print ("data file does exist.") 75 | return (True) 76 | 77 | # Combine the latest health data with historical data 78 | def data_merge(file1, file2, combined_file): 79 | lambda_file1 = '/tmp/' + file1 80 | lambda_file2 = '/tmp/' + file2 81 | s3 = boto3.client("s3") 82 | s3.download_file(bucketName, file1, lambda_file1) 83 | s3.download_file(bucketName, file2, lambda_file2) 84 | reader1 = csv.reader(open(lambda_file1)) 85 | reader2 = csv.reader(open(lambda_file2)) 86 | file_name = "/tmp/" + combined_file 87 | f = open(file_name, "w") 88 | writer = csv.writer(f) 89 | 90 | for row in reader1: 91 | writer.writerow(row) 92 | for row in reader2: 93 | writer.writerow(row) 94 | f.close() 95 | 96 | tempfile = "/tmp/tmp_" + combined_file 97 | inFile = open(file_name,'r') 98 | outFile = open(tempfile,'w') 99 | listLines = [] 100 | 101 | for line in inFile: 102 | if line in listLines: 103 | continue 104 | else: 105 | outFile.write(line) 106 | listLines.append(line) 107 | outFile.close() 108 | inFile.close() 109 | 110 | ## Upload the organization events json message to S3 file 111 | def upload_to_s3(file_name, bucket, key): 112 | """Upload a file to an S3 bucket 113 | :param file_name: File to upload 114 | :param bucket: Bucket to upload to 115 | :param object_name: S3 object name. If not specified then file_name is used 116 | :return: True if file was uploaded, else False 117 | """ 118 | lambdaFileName = '/tmp/' + file_name 119 | s3 = boto3.resource('s3') 120 | try: 121 | s3.meta.client.upload_file(lambdaFileName, bucket, key) 122 | print("s3 upload success -- uploaded " + key + " to the bucket: " + bucket) 123 | except ClientError as e: 124 | logging.error(e) 125 | return False 126 | print("s3 upload error occurs", e) 127 | return True 128 | 129 | # Store event data info into csv file 130 | def write_to_csv(file_name): 131 | whole_table = [arn_list, service_list, eventTypeCode_list, eventTypeCategory_list, region_list, startTime_list, 132 | endTime_list, lastUpdatedTime_list, statusCode_list, impactedAccount_List, impactedEntity_List, eventDescription_List] 133 | export_data = zip_longest(*whole_table, fillvalue = '') 134 | 135 | lambdaFileName = '/tmp/' + file_name 136 | with open(lambdaFileName, 'w', encoding="utf-8", newline='') as myfile: 137 | wr = csv.writer(myfile) 138 | wr.writerow(("arn", "service", "eventTypeCode", "eventTypeCategory", "region", "startTime", "endTime", "lastUpdatedTime", 139 | "statusCode", "impactedAccount", "impactedEntity", "eventDescription")) 140 | wr.writerows(export_data) 141 | myfile.close() 142 | 143 | print("\n#########################################################################\n") 144 | print("Event data saved to CSV file.") 145 | print("\n#########################################################################\n") 146 | 147 | ## Create the Manifest file, and save it to the user specified S3 bucket 148 | def create_manifest(): 149 | """Create a Manifest file to an user specified S3 bucket 150 | This is required when user need to visualise the event data hosted in S3 bucket via QuickSight 151 | """ 152 | # download the manifest file template 153 | urllib.request.urlretrieve(manifest_url, '/tmp/manifest.json') 154 | 155 | file_path_ori = "/tmp" + "/manifest.json" 156 | file_path_new = "/tmp" + "/manifests3.json" 157 | 158 | with open(file_path_ori, "rt") as fin: 159 | with open(file_path_new, "wt") as fout: 160 | for line in fin: 161 | fout.write(line.replace('bucket-name', bucketName)) 162 | 163 | ## describe_health_service_status_for_organization 164 | def describe_health_service_status_for_org(): 165 | client = boto3.client('health', 'us-east-1') 166 | response = client.describe_health_service_status_for_organization() 167 | print(response) 168 | print("\n#########################################################################\n") 169 | 170 | ## describe_events_for_organization(**kwargs) 171 | def describe_events_for_org(start_time): 172 | client = boto3.client('health', 'us-east-1') 173 | event_paginator = client.get_paginator('describe_events_for_organization') 174 | event_page_iterator = event_paginator.paginate( 175 | filter={ 176 | 'lastUpdatedTime':{ 177 | 'from': start_time 178 | } 179 | } 180 | ) 181 | for event_page in event_page_iterator: 182 | json_event = json.dumps(event_page, cls=DatetimeEncoder) 183 | parsed_event = json.loads(json_event) 184 | events = parsed_event.get("events") 185 | 186 | for event in events: 187 | arn_list.append(event.get("arn")) 188 | service_list.append(event.get("service")) 189 | eventTypeCode_list.append(event.get("eventTypeCode")) 190 | eventTypeCategory_list.append(event.get("eventTypeCategory")) 191 | region_list.append(event.get("region")) 192 | startTime_list.append(event.get("startTime")) 193 | lastUpdatedTime_list.append(event.get("lastUpdatedTime")) 194 | statusCode_list.append(event.get("statusCode")) 195 | if (event.get("statusCode") == "open"): 196 | endTime_list.append("NULL") 197 | elif (event.get("statusCode") == "closed"): 198 | endTime_list.append(event.get("endTime")) 199 | else: 200 | endTime_list.append("NULL") 201 | 202 | response = client.describe_event_details(eventArns = [event.get("arn")], locale = "en") 203 | json_response = json.dumps(response, cls=DatetimeEncoder) 204 | parsed_response = json.loads (json_response) 205 | eventDescription_List.append(parsed_response["successfulSet"][0]["eventDescription"]["latestDescription"]) 206 | 207 | print("\n#########################################################################\n") 208 | print("describe_events_for_organization response - Total events:{}".format(len(arn_list))) 209 | print("These events are related to the following services:\n", service_list) 210 | print("\n#########################################################################\n") 211 | return(True) 212 | 213 | ## read the latest event time 214 | def check_latest_event(): 215 | last_update_time = [] 216 | lambdaFileName = '/tmp/' + csvFileName 217 | s3 = boto3.resource('s3') 218 | obj = s3.Object(bucketName, csvFileName) 219 | s3.Bucket(bucketName).download_file(csvFileName, lambdaFileName) 220 | 221 | with open(lambdaFileName, 'r') as file: 222 | reader = csv.reader(file) 223 | row1 = next(reader) 224 | column = 0 225 | 226 | for item in row1: 227 | if (item == 'lastUpdatedTime'): 228 | break 229 | else: 230 | column += 1 231 | 232 | for row in reader: 233 | if (row[column] != "lastUpdatedTime"): 234 | #date_time_obj = datetime.datetime.strptime(row[column], '%y-%m-%d %H:%M:%S.%f%z') 235 | strUpdate = parser.parse(row[column]) 236 | last_update_time.append(strUpdate) 237 | 238 | return(max(last_update_time)) 239 | 240 | ## describe_affected_accounts 241 | def describe_affected_accounts(event_arn): 242 | affectedAccounts = [] 243 | client = boto3.client('health', 'us-east-1') 244 | event_accounts_paginator = client.get_paginator('describe_affected_accounts_for_organization') 245 | 246 | event_accounts_page_iterator = event_accounts_paginator.paginate(eventArn=event_arn) 247 | 248 | for event_accounts_page in event_accounts_page_iterator: 249 | json_event_accounts = json.dumps(event_accounts_page, cls=DatetimeEncoder) 250 | parsed_event_accounts = json.loads (json_event_accounts) 251 | 252 | if((parsed_event_accounts['affectedAccounts']) == "[]"): 253 | affectedAccounts = affectedAccounts +"[]" 254 | else: 255 | affectedAccounts = affectedAccounts + (parsed_event_accounts['affectedAccounts']) 256 | print("For service event arn: {}".format(event_arn)) 257 | print("Affected accounts are: {}".format(affectedAccounts)) 258 | 259 | return(affectedAccounts) 260 | 261 | ## Retrieve account id automatically 262 | def get_account_id(): 263 | return(boto3.client('sts').get_caller_identity().get('Account')) 264 | 265 | ## describe_affected_entities_for_organization(**kwargs) 266 | def describe_affected_entities(event_arn): 267 | affectedEntities = [] 268 | affectedEntities_sub_list = [] 269 | client = boto3.client('health', 'us-east-1') 270 | affected_accounts = describe_affected_accounts(event_arn) 271 | 272 | for affected_account in affected_accounts: 273 | affected_account = ''.join(str(e) for e in affected_account) 274 | 275 | #If there's no affected account for this event, the 'affected account' will be filled by '[]' 276 | if(not affected_account): 277 | affected_account = '[]' 278 | 279 | print("affected_account: ", affected_account) 280 | print("affected account type:", type(affected_account)) 281 | 282 | event_entities_paginator = client.get_paginator('describe_affected_entities_for_organization') 283 | event_entities_page_iterator = event_entities_paginator.paginate( 284 | organizationEntityFilters=[ 285 | { 286 | 'awsAccountId': affected_account, 287 | 'eventArn': event_arn 288 | } 289 | ] 290 | ) 291 | 292 | affectedEntities_sub_list = [] 293 | 294 | for event_entities_page in event_entities_page_iterator: 295 | json_event_entities = json.dumps(event_entities_page, cls=DatetimeEncoder) 296 | parsed_event_entities = json.loads (json_event_entities) 297 | print("for event {} and affected account {}: ".format(event_arn, affected_account)) 298 | print("event entities list are: {} \n".format(parsed_event_entities)) 299 | 300 | for entity in parsed_event_entities['entities']: 301 | 302 | if((entity['entityValue']) != "[]"): 303 | affectedEntities_sub_list.append(entity['entityValue']) 304 | 305 | print("\n#########################################################################\n") 306 | print("affected entities list are:", affectedEntities_sub_list) 307 | print("\n#########################################################################\n") 308 | 309 | if(len(affectedEntities_sub_list) == 0): 310 | affectedEntities = affectedEntities + "[]" 311 | else: 312 | affectedEntities = affectedEntities + affectedEntities_sub_list 313 | return(affectedEntities) 314 | 315 | def lambda_handler(event, context): 316 | # TODO implement 317 | arn_list.clear() 318 | service_list.clear() 319 | eventTypeCode_list.clear() 320 | eventTypeCategory_list.clear() 321 | region_list.clear() 322 | startTime_list.clear() 323 | endTime_list.clear() 324 | lastUpdatedTime_list.clear() 325 | statusCode_list.clear() 326 | impactedAccount_List.clear() 327 | eventDescription_List.clear() 328 | impactedEntity_List.clear() 329 | 330 | print("boto3 version is ---", boto3.__version__) 331 | 332 | # If the data file not exist, then lambda pull out the whole health dataset. Otherwise, Lambda just pull the delta dataset. 333 | if (not (get_s3_file_status())): 334 | # Enable the health service in organization level 335 | enable_health_org() 336 | ## describe_health_service_status_for_organization 337 | describe_health_service_status_for_org() 338 | ## describe_events_for_organization(**kwargs) 339 | describe_events_for_org(datetime(2015,1,1)) 340 | ## describe_affected_accounts & describe_affected_entities 341 | for arn in arn_list: 342 | eventAffectedAccounts = describe_affected_accounts(arn) 343 | sleep(1) 344 | print ("eventAffectedAccounts:",eventAffectedAccounts) 345 | impactedAccount_List.append(eventAffectedAccounts) 346 | sleep(1) 347 | eventAffectedEntities = describe_affected_entities(arn) 348 | print ("eventAffectedEntities:",eventAffectedEntities) 349 | impactedEntity_List.append(eventAffectedEntities) 350 | 351 | print("complete impacted account list:", impactedAccount_List) 352 | print("complete impacted entity list:", impactedEntity_List) 353 | 354 | ## create manifest file, and save it to S3 bucket 355 | create_manifest() 356 | upload_to_s3(file_name="manifests3.json", bucket=bucketName, key="manifests3.json") 357 | 358 | ## save event data to csv file 359 | write_to_csv(csvFileName) 360 | 361 | ## upload the csv file to S3 bucket 362 | upload_to_s3(file_name=csvFileName, bucket=bucketName, key=csvFileName) 363 | 364 | else: 365 | latest_time = check_latest_event() 366 | print("latest_time is: ", latest_time ) 367 | ## describe_events_for_organization(**kwargs) 368 | describe_events_for_org(latest_time) 369 | 370 | ## Check if there's updated event data 371 | if (len(arn_list) >= 1): 372 | ## describe_affected_accounts & describe_affected_entities 373 | for arn in arn_list: 374 | eventAffectedAccounts = describe_affected_accounts(arn) 375 | print ("eventAffectedAccounts:",eventAffectedAccounts) 376 | impactedAccount_List.append(eventAffectedAccounts) 377 | sleep(1) 378 | 379 | eventAffectedEntities = describe_affected_entities(arn) 380 | print ("eventAffectedEntities:",eventAffectedEntities) 381 | impactedEntity_List.append(eventAffectedEntities) 382 | sleep(1) 383 | 384 | print("complete impacted account list:", impactedAccount_List) 385 | print("complete impacted entity list:", impactedEntity_List) 386 | 387 | ## save event data to csv file 388 | new_file = 'event_data_file_recent.csv' 389 | write_to_csv(new_file) 390 | 391 | ## upload the csv file to S3 bucket 392 | upload_to_s3(file_name=new_file, bucket=bucketName, key=new_file) 393 | 394 | ## merge the latest data with historical data into csvFileName 395 | data_merge(new_file, csvFileName, 'combined_new_file.csv') 396 | 397 | ## upload the csv file to S3 bucket 398 | upload_to_s3(file_name='tmp_combined_new_file.csv', bucket=bucketName, key=csvFileName) 399 | 400 | return { 401 | 'statusCode': 200, 402 | 'body': json.dumps('AWS Service Health Data has been polled and uploaded to the specified S3 bucket!') 403 | } 404 | --------------------------------------------------------------------------------