1
+ from collections import defaultdict
2
+ import boto3
3
+ import time
4
+ from crhelper import CfnResource
5
+ from sumoappclient .sumoclient .outputhandlers import HTTPHandler
6
+ from sumoappclient .common .utils import read_yaml_file
7
+ from abc import ABC , abstractmethod
8
+
9
+ helper = CfnResource (json_logging = False , log_level = 'INFO' , sleep_on_delete = 30 )
10
+
11
+ @helper .create
12
+ def create (event , context ):
13
+ try :
14
+ T = telemetryFactory (event , context )
15
+ T .fetch_and_send_telemetry ()
16
+ except Exception as e :
17
+ print (e )
18
+ return "Telemetry failed to sent for Create Stack"
19
+ helper .Status = "SUCCESS"
20
+ return "Telemetry sent for Create Stack"
21
+
22
+
23
+ @helper .update
24
+ def update (event , context ):
25
+ try :
26
+ T = telemetryFactory (event , context )
27
+ T .fetch_and_send_telemetry ()
28
+ except Exception as e :
29
+ print (e )
30
+ return "Telemetry failed to sent for Update Stack"
31
+ helper .Status = "SUCCESS"
32
+ return "Telemetry sent for Update Stack"
33
+
34
+
35
+ @helper .delete
36
+ def delete (event , context ):
37
+ lambda_client = boto3 .client ('lambda' )
38
+ try :
39
+ T = telemetryFactory (event , context )
40
+ T .fetch_and_send_telemetry ()
41
+ # Self Delete the Telemetry Lambda function
42
+ if event ['RequestType' ]== 'Delete' :
43
+ response = lambda_client .delete_function (FunctionName = context .invoked_function_arn )
44
+ except Exception as e :
45
+ print (e )
46
+ helper .Status = "SUCCESS"
47
+
48
+
49
+ def lambda_handler (event , context ):
50
+ helper (event , context )
51
+
52
+ def telemetryFactory (event , context ):
53
+ # create an obj of default class and return in case of none
54
+ if event ['ResourceProperties' ]['solutionName' ] == 'AWSO' :
55
+ return awsoTelemetry (event , context )
56
+ else :
57
+ return parentStackTelemetry (event , context )
58
+ # elif event['ResourceProperties']['solutionName'] == 'CIS':
59
+ # return cisTelemetry(event, context)
60
+
61
+ # Interface
62
+ class baseTelemetry (ABC ):
63
+
64
+ def __init__ (self , event ,context ,* args , ** kwargs ):
65
+ self .event = event
66
+ self .context = context
67
+ self .config = read_yaml_file ("./metadata.yaml" )
68
+ self .config ['SumoLogic' ]['SUMO_ENDPOINT' ] = self .event ['ResourceProperties' ]['TelemetryEndpoint' ]
69
+ self .sumoHttpHandler = HTTPHandler (self .config )
70
+ self .log = self .sumoHttpHandler .log
71
+ self .log .debug ("Telemetry enabled" )
72
+
73
+ @abstractmethod
74
+ def fetch_and_send_telemetry (self ):
75
+ raise NotImplementedError
76
+
77
+ def send_telemetry (self , data ):
78
+ r = self .sumoHttpHandler .send (data )
79
+
80
+ # class cisTelemetry(baseTelemetry): # parentStackSetTelemetry
81
+ # def create_telemetry_data(self):
82
+ # pass
83
+
84
+ class parentStackTelemetry (baseTelemetry ):
85
+ def __init__ (self , event ,context ,* args , ** kwargs ):
86
+ super ().__init__ (event ,context )
87
+ self .stackID = event ['ResourceProperties' ]['stackID' ]
88
+ self .cfclient = boto3 .client ('cloudformation' )
89
+ self .all_resource_statuses = defaultdict (list )
90
+
91
+ def enrich_telemetry_data (self , log_data_list ):
92
+ return log_data_list
93
+
94
+ # This function will return True if any of the child resources are *IN_PROGRESS state.
95
+ def _has_any_child_resources_in_progress_state (self ):
96
+ all_stacks = self .cfclient .describe_stack_resources (StackName = self .stackID )
97
+ # PrimeInvoke - only responsible for triggering lambda
98
+ # Removing 'Primerinvoke' status from all_stacks status so that it is not considered during status checking else it'll result in endless loop becoz if PriveInvoke is not completed overall stack can't be completed.
99
+ for stack_resource in filter (lambda x : x ["LogicalResourceId" ] != self .event ['LogicalResourceId' ] ,all_stacks ["StackResources" ]):
100
+ stackStatus = stack_resource ["ResourceStatus" ]
101
+ if stackStatus .endswith ('_IN_PROGRESS' ):
102
+ return True
103
+ return False # None of the child resources are in IN_PROGRESS state
104
+
105
+ def _create_telemetry_data (self ):
106
+ log_data_list = []
107
+ all_stacks_events = self .cfclient .describe_stack_events (StackName = self .stackID )
108
+ for stack_resource in all_stacks_events ["StackEvents" ]:
109
+ resourceID = stack_resource ["PhysicalResourceId" ]
110
+ status = stack_resource ["ResourceStatus" ]
111
+ resource_status_reason = stack_resource .get ('ResourceStatusReason' , '' )
112
+ if status not in self .all_resource_statuses .get (resourceID , []):
113
+ self .all_resource_statuses [resourceID ].append (status )
114
+ log_data = {
115
+ 'requestid' : self .context .aws_request_id ,
116
+ 'timestamp' : stack_resource ['Timestamp' ].isoformat (timespec = 'milliseconds' ),
117
+ 'data' : {
118
+ 'stackId' : self .event ['StackId' ],
119
+ 'resourceType' : stack_resource ["ResourceType" ],
120
+ 'resourceName' : stack_resource ["LogicalResourceId" ],
121
+ 'resourceID' : stack_resource ["PhysicalResourceId" ],
122
+ 'status' : stack_resource ["ResourceStatus" ],
123
+ 'details' : resource_status_reason
124
+ }
125
+ }
126
+ log_data_list .append (log_data )
127
+ return log_data_list
128
+
129
+ def fetch_and_send_telemetry (self ):
130
+ resources_in_progress = True
131
+ while (resources_in_progress ):
132
+ resources_in_progress = self ._has_any_child_resources_in_progress_state ()
133
+ log_data_list = self ._create_telemetry_data ()
134
+ log_data_list = self .enrich_telemetry_data (log_data_list )
135
+ self .send_telemetry (log_data_list )
136
+ # If all child resources are completed except PrimeInvoker, marking PrimeInvoker as completed
137
+ if not resources_in_progress :
138
+ helper ._cfn_response (self .event )
139
+ time .sleep (int (self .event ['ResourceProperties' ]['scanInterval' ]))
140
+ # If all resources are completed, make final call to know Parent stack status
141
+ if not resources_in_progress :
142
+ log_data_list = self ._create_telemetry_data ()
143
+ log_data_list = self .enrich_telemetry_data (log_data_list )
144
+ self .send_telemetry (log_data_list )
145
+
146
+
147
+ class awsoTelemetry (parentStackTelemetry ):
148
+ def __init__ (self ,event ,context ):
149
+ super ().__init__ (event ,context )
150
+
151
+ def enrich_telemetry_data (self , log_data_list ):
152
+ static_data = {
153
+ 'profile' : {
154
+ 'sumo' : {
155
+ 'deployment' : self .event ['ResourceProperties' ]['sumoDeployment' ],
156
+ 'orgid' : self .event ['ResourceProperties' ]['sumoOrgId' ],
157
+ },
158
+ 'solution' : {
159
+ 'name' : self .event ['ResourceProperties' ]['solutionName' ],
160
+ 'version' : self .event ['ResourceProperties' ]['solutionVersion' ],
161
+ 'deploymentSource' : self .event ['ResourceProperties' ]['deploymentSource' ]
162
+ },
163
+ }
164
+ }
165
+ for log_data in log_data_list :
166
+ log_data .update (static_data )
167
+ return log_data_list
168
+
169
+ if __name__ == "__main__" :
170
+ event = {}
171
+ context = {"aws_request_id" :"5678-sxcvbnm-fghjk-123456789" }
172
+ create (event ,context )
0 commit comments