Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
        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);
        }
Exemplo n.º 3
0
        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;
                }
            }
        }
Exemplo n.º 4
0
 public void LogError(string log)
 {
     LogErrorHandler?.Invoke($"BiangLibrary.AdvancedInventory Error: {log}");
 }
Exemplo n.º 5
0
 public void LogError(string log)
 {
     LogErrorHandler?.Invoke($"BiangStudio.ShapedInventory Error: {log}");
 }