private async Task WriteResponse( CloudFormationResourceRequest <TProperties> rawRequest, CloudFormationResourceResponse <TAttributes> rawResponse ) { Exception exception = null; var backoff = TimeSpan.FromMilliseconds(100); // write response to pre-signed S3 URL for (var i = 0; i < MAX_SEND_ATTEMPTS; ++i) { try { if (rawRequest.ResponseURL == null) { throw new InvalidOperationException("ResponseURL is missing"); } var httpResponse = await HttpClient.SendAsync(new HttpRequestMessage { RequestUri = new Uri(rawRequest.ResponseURL), Method = HttpMethod.Put, Content = new ByteArrayContent(Encoding.UTF8.GetBytes(LambdaSerializer.Serialize(rawResponse))) }); if (httpResponse.StatusCode != HttpStatusCode.OK) { throw new LambdaCustomResourceException( "PUT operation to pre-signed S3 URL failed with status code: {0} [{1} {2}] = {3}", httpResponse.StatusCode, rawRequest.RequestType, rawRequest.ResourceType ?? "<MISSING>", await httpResponse.Content.ReadAsStringAsync() ); } return; } catch (InvalidOperationException e) { exception = e; break; } catch (Exception e) { exception = e; LogErrorAsWarning(e, "writing response to pre-signed S3 URL failed"); await Task.Delay(backoff); backoff = TimeSpan.FromSeconds(backoff.TotalSeconds * 2); } } if (exception == null) { exception = new ShouldNeverHappenException($"ALambdaCustomResourceFunction.WriteResponse failed w/o an explicit"); } // max attempts have been reached; fail permanently and record the failed request for playback LogError(exception); await RecordFailedMessageAsync(LambdaLogLevel.ERROR, FailedMessageOrigin.CloudFormation, LambdaSerializer.Serialize(rawRequest), exception); }
private async Task WriteResponse( CloudFormationResourceRequest <TProperties> rawRequest, CloudFormationResourceResponse <TAttributes> rawResponse ) { Exception exception = null; // write response to pre-signed S3 URL for (var i = 0; i < MAX_SEND_ATTEMPTS; ++i) { try { var httpResponse = await HttpClient.SendAsync(new HttpRequestMessage { RequestUri = new Uri(rawRequest.ResponseURL), Method = HttpMethod.Put, Content = new ByteArrayContent(Encoding.UTF8.GetBytes(SerializeJson(rawResponse))) }); if (httpResponse.StatusCode != HttpStatusCode.OK) { throw new LambdaCustomResourceException( "PUT operation to pre-signed S3 URL failed with status code: {0} [{1} {2}] = {3}", httpResponse.StatusCode, rawRequest.RequestType, rawRequest.ResourceType, await httpResponse.Content.ReadAsStringAsync() ); } return; } catch (Exception e) { exception = e; LogErrorAsWarning(e, "writing response to pre-signed S3 URL failed"); await Task.Delay(TimeSpan.FromMilliseconds(100)); } } // max attempts have been reached; fail permanently and record the failed request for playback LogError(exception); await RecordFailedMessageAsync(LambdaLogLevel.ERROR, FailedMessageOrigin.CloudFormation, SerializeJson(rawRequest), exception); }
//--- Methods --- public override async Task ProcessMessageAsync(CloudFormationResourceRequest <TRequestProperties> rawRequest, ILambdaContext context) { LogInfo(JsonConvert.SerializeObject(rawRequest, Formatting.Indented)); CloudFormationResourceResponse <TResponseProperties> rawResponse; LogInfo($"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation received"); try { var request = new Request <TRequestProperties> { RequestType = rawRequest.RequestType, ResourceType = rawRequest.ResourceType, LogicalResourceId = rawRequest.LogicalResourceId, PhysicalResourceId = rawRequest.PhysicalResourceId, ResourceProperties = rawRequest.ResourceProperties, OldResourceProperties = rawRequest.OldResourceProperties }; // handle slack request Response <TResponseProperties> response; switch (request.RequestType) { case RequestType.Create: response = await HandleCreateResourceAsync(request); break; case RequestType.Update: response = await HandleUpdateResourceAsync(request); break; case RequestType.Delete: response = await HandleDeleteResourceAsync(request); break; default: throw new ArgumentOutOfRangeException(nameof(request.RequestType), request.RequestType, "unexpected request value"); } rawResponse = new CloudFormationResourceResponse <TResponseProperties> { Status = CloudFormationResourceResponseStatus.SUCCESS, Reason = "", StackId = rawRequest.StackId, RequestId = rawRequest.RequestId, LogicalResourceId = rawRequest.LogicalResourceId, PhysicalResourceId = response.PhysicalResourceId ?? rawRequest.PhysicalResourceId, NoEcho = response.NoEcho, Data = response.Properties }; LogInfo($"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation was successful"); } catch (Exception e) { LogError(e, $"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation FAILED [{{0}}]", e.Message); rawResponse = new CloudFormationResourceResponse <TResponseProperties> { Status = CloudFormationResourceResponseStatus.FAILED, Reason = e.Message, StackId = rawRequest.StackId, RequestId = rawRequest.RequestId, LogicalResourceId = rawRequest.LogicalResourceId }; } // write response to pre-signed S3 URL try { var httpResponse = await HttpClient.SendAsync(new HttpRequestMessage { RequestUri = new Uri(rawRequest.ResponseURL), Method = HttpMethod.Put, Content = new ByteArrayContent(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rawResponse))) }); if (httpResponse.StatusCode != HttpStatusCode.OK) { throw new CustomResourceException( "PUT operation to pre-signed S3 URL failed with status code: {0} [{1} {2}] = {3}", httpResponse.StatusCode, rawRequest.RequestType, rawRequest.ResourceType, await httpResponse.Content.ReadAsStringAsync() ); } } catch (Exception e) { LogError(e, "writing response to pre-signed S3 URL failed"); // TODO (2018-06-14, bjorg): how should we handle this? we may want the handler to re-attempt the // resource creation since the request was SNS based throw; } }