public static async Task <(bool Success, Stack Stack)> GetStackAsync(this IAmazonCloudFormation cfnClient, string stackName, LogErrorDelegate logError) { Stack stack = null; try { var describe = await cfnClient.DescribeStacksAsync(new DescribeStacksRequest { StackName = stackName }); // make sure the stack is in a stable state (not updating and not failed) stack = describe.Stacks.FirstOrDefault(); switch (stack?.StackStatus) { case null: case "CREATE_COMPLETE": case "ROLLBACK_COMPLETE": case "UPDATE_COMPLETE": case "UPDATE_ROLLBACK_COMPLETE": // we're good to go break; default: logError?.Invoke($"{stackName} is not in a valid state; module deployment must be complete and successful (status: {stack?.StackStatus})", null); return(false, null); } } catch (AmazonCloudFormationException) { // stack not found; nothing to do } return(true, stack); }
public static async Task <(bool Success, Stack Stack)> GetStackAsync(this IAmazonCloudFormation cfnClient, string stackName, LogErrorDelegate logError) { Stack stack = null; var attempts = 0; try { var describe = await cfnClient.DescribeStacksAsync(new DescribeStacksRequest { StackName = stackName }); // make sure the stack is in a stable state (not updating and not failed) stack = describe.Stacks.FirstOrDefault(); switch (stack?.StackStatus) { case null: case "CREATE_COMPLETE": case "ROLLBACK_COMPLETE": case "UPDATE_COMPLETE": case "UPDATE_ROLLBACK_COMPLETE": // we're good to go break; default: logError?.Invoke($"{stackName} is not in a valid state; CloudFormation stack must be complete and successful (status: {stack?.StackStatus})", null); return(false, null); } } catch (AmazonCloudFormationException) { // stack not found; nothing to do } catch (HttpRequestException e) when(e.Message == "The requested name is valid, but no data of the requested type was found") { // NOTE (2020-03-31, bjorg): avoid sporadic DNS issues by waiting an trying again if (++attempts < 3) { await Task.Delay(TimeSpan.FromSeconds(attempts)); } } return(true, stack); }
public static async Task <(Stack Stack, bool Success)> TrackStackUpdateAsync( this IAmazonCloudFormation cfnClient, string stackName, string stackId, string mostRecentStackEventId, ModuleNameMappings nameMappings = null, LogErrorDelegate logError = null ) { var seenEventIds = new HashSet <string>(); var foundMostRecentStackEvent = (mostRecentStackEventId == null); var request = new DescribeStackEventsRequest { StackName = stackId ?? stackName }; var eventList = new List <StackEvent>(); var ansiLinesPrinted = 0; // iterate as long as the stack is being created/updated var active = true; var success = false; while (active) { await Task.Delay(TimeSpan.FromSeconds(3)); // fetch as many events as possible for the current stack var events = new List <StackEvent>(); try { var response = await cfnClient.DescribeStackEventsAsync(request); events.AddRange(response.StackEvents); } catch (System.Net.Http.HttpRequestException e) when((e.InnerException is System.Net.Sockets.SocketException) && (e.InnerException.Message == "No such host is known")) { // ignore network issues and just try again continue; } events.Reverse(); // skip any events that preceded the most recent event before the stack update operation while (!foundMostRecentStackEvent && events.Any()) { var evt = events.First(); if (evt.EventId == mostRecentStackEventId) { foundMostRecentStackEvent = true; } seenEventIds.Add(evt.EventId); events.RemoveAt(0); } if (!foundMostRecentStackEvent) { throw new ApplicationException($"unable to find starting event for stack: {stackName}"); } // report only on new events foreach (var evt in events.Where(evt => !seenEventIds.Contains(evt.EventId))) { UpdateEvent(evt); if (!seenEventIds.Add(evt.EventId)) { // we found an event we already saw in the past, no point in looking at more events break; } if (IsFinalStackEvent(evt) && (evt.LogicalResourceId == stackName)) { // event signals stack creation/update completion; time to stop active = false; success = IsSuccessfulFinalStackEvent(evt); break; } } RenderEvents(); } if (!success) { return(Stack : null, Success : false); } // describe stack and report any output values var description = await cfnClient.DescribeStacksAsync(new DescribeStacksRequest { StackName = stackName }); return(Stack : description.Stacks.FirstOrDefault(), Success : success); // local function string TranslateLogicalIdToFullName(string logicalId) { var fullName = logicalId; nameMappings?.ResourceNameMappings?.TryGetValue(logicalId, out fullName); return(fullName ?? logicalId); } string TranslateResourceTypeToFullName(string awsType) { var fullName = awsType; nameMappings?.TypeNameMappings?.TryGetValue(awsType, out fullName); return(fullName ?? awsType); } void RenderEvents() { if (Settings.UseAnsiConsole) { if (ansiLinesPrinted > 0) { Console.Write(AnsiTerminal.MoveLineUp(ansiLinesPrinted)); } var maxResourceStatusLength = eventList.Any() ? eventList.Max(evt => evt.ResourceStatus.ToString().Length) : 0; var maxResourceTypeNameLength = eventList.Any() ? eventList.Max(evt => TranslateResourceTypeToFullName(evt.ResourceType).Length) : 0; foreach (var evt in eventList) { var resourceStatus = evt.ResourceStatus.ToString(); var resourceType = TranslateResourceTypeToFullName(evt.ResourceType); if (_ansiStatusColorCodes.TryGetValue(evt.ResourceStatus, out var ansiColor)) { // print resource status Console.Write(ansiColor); Console.Write(resourceStatus); Console.Write(AnsiTerminal.Reset); Console.Write("".PadRight(maxResourceStatusLength - resourceStatus.Length + 4)); // print resource type Console.Write(resourceType); Console.Write("".PadRight(maxResourceTypeNameLength - resourceType.Length + 4)); // print resource name Console.Write(TranslateLogicalIdToFullName(evt.LogicalResourceId)); // print status reason if ((logError == null) && (evt.ResourceStatusReason != null)) { Console.Write($" ({evt.ResourceStatusReason})"); } } else { Console.Write($"{resourceStatus} {resourceType} {TranslateLogicalIdToFullName(evt.LogicalResourceId)}{(evt.ResourceStatusReason != null ? $" ({evt.ResourceStatusReason})" : "")}"); } Console.Write(AnsiTerminal.ClearEndOfLine); Console.WriteLine(); } ansiLinesPrinted = eventList.Count; } } void UpdateEvent(StackEvent evt) { if (Settings.UseAnsiConsole) { var index = eventList.FindIndex(e => e.LogicalResourceId == evt.LogicalResourceId); if (index < 0) { eventList.Add(evt); } else { eventList[index] = evt; } } else { Console.WriteLine($"{evt.ResourceStatus,-35} {TranslateResourceTypeToFullName(evt.ResourceType),-55} {TranslateLogicalIdToFullName(evt.LogicalResourceId)}{(evt.ResourceStatusReason != null ? $" ({evt.ResourceStatusReason})" : "")}"); } // capture failed operation as an error switch (evt.ResourceStatus) { case "CREATE_FAILED": case "ROLLBACK_FAILED": case "UPDATE_FAILED": case "DELETE_FAILED": case "UPDATE_ROLLBACK_FAILED": case "UPDATE_ROLLBACK_IN_PROGRESS": if (evt.ResourceStatusReason != "Resource creation cancelled") { logError?.Invoke($"{evt.ResourceStatus} {TranslateLogicalIdToFullName(evt.LogicalResourceId)} [{TranslateResourceTypeToFullName(evt.ResourceType)}]: {evt.ResourceStatusReason}", /*Exception*/ null); } break; } } }
public void LogError(string log) { LogErrorHandler?.Invoke($"BiangLibrary.AdvancedInventory Error: {log}"); }
public void LogError(string log) { LogErrorHandler?.Invoke($"BiangStudio.ShapedInventory Error: {log}"); }