Esempio n. 1
0
        private async Task <bool> WaitForChangeSetBeingAvailableAsync(string changeSetId, bool updated)
        {
            try
            {
                var request = new DescribeChangeSetRequest
                {
                    ChangeSetName = changeSetId
                };

                this.Logger.WriteLine($"... Waiting for change set to be reviewed");
                DescribeChangeSetResponse response;
                do
                {
                    Thread.Sleep(POLLING_PERIOD);
                    response = await this.CloudFormationClient.DescribeChangeSetAsync(request);
                } while (response.Status == ChangeSetStatus.CREATE_IN_PROGRESS || response.Status == ChangeSetStatus.CREATE_PENDING);

                if (response.Status == ChangeSetStatus.FAILED)
                {
                    this.Logger.WriteLine($"Failed to create CloudFormation change set: {response.StatusReason}");
                    return(false);
                }

                return(true);
            }
            catch (Exception e)
            {
                throw new LambdaToolsException($"Error getting status of change set: {e.Message}", LambdaToolsException.ErrorCode.CloudFormationDescribeChangeSet, e);
            }
        }
        internal DescribeChangeSetResponse DescribeChangeSet(DescribeChangeSetRequest request)
        {
            var marshaller   = new DescribeChangeSetRequestMarshaller();
            var unmarshaller = DescribeChangeSetResponseUnmarshaller.Instance;

            return(Invoke <DescribeChangeSetRequest, DescribeChangeSetResponse>(request, marshaller, unmarshaller));
        }
Esempio n. 3
0
        private async Task WaitForChangeSetCreate(string stackId, DeployContext context, CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();
            Console.WriteLine("Waiting for changeset creation to complete...");
            var status = ChangeSetStatus.CREATE_PENDING;
            var reason = string.Empty;

            while (status != ChangeSetStatus.CREATE_COMPLETE)
            {
                cancellationToken.ThrowIfCancellationRequested();

                if (status == ChangeSetStatus.FAILED)
                {
                    throw new Exception($"Change set {context.ChangeSetName} failed: {reason}");
                }

                await Task.Delay(1000, cancellationToken);

                var request = new DescribeChangeSetRequest {
                    ChangeSetName = context.ChangeSetName, StackName = stackId
                };
                var response = await cloudformation.DescribeChangeSetAsync(request, cancellationToken);

                status = response.Status;
                reason = response.StatusReason;
            }
        }
        /// <summary>
        /// Initiates the asynchronous execution of the DescribeChangeSet operation.
        /// </summary>
        ///
        /// <param name="request">Container for the necessary parameters to execute the DescribeChangeSet operation.</param>
        /// <param name="cancellationToken">
        ///     A cancellation token that can be used by other objects or threads to receive notice of cancellation.
        /// </param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public Task <DescribeChangeSetResponse> DescribeChangeSetAsync(DescribeChangeSetRequest request, System.Threading.CancellationToken cancellationToken = default(CancellationToken))
        {
            var marshaller   = new DescribeChangeSetRequestMarshaller();
            var unmarshaller = DescribeChangeSetResponseUnmarshaller.Instance;

            return(InvokeAsync <DescribeChangeSetRequest, DescribeChangeSetResponse>(request, marshaller,
                                                                                     unmarshaller, cancellationToken));
        }
Esempio n. 5
0
        /// <summary>
        /// Provides information about a given change set.
        /// </summary>
        /// <param name="request">Container for the necessary parameters to execute the DescribeChangeSet service method.</param>
        /// <param name="cancellationToken">
        ///     A cancellation token that can be used by other objects or threads to receive notice of cancellation.
        /// </param>
        ///
        /// <returns>The response from the DescribeChangeSet service method, as returned by MarketplaceCatalog.</returns>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.AccessDeniedException">
        /// Access is denied.
        /// </exception>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.InternalServiceException">
        /// There was an internal service exception.
        /// </exception>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.ResourceNotFoundException">
        /// The specified resource wasn't found.
        /// </exception>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.ThrottlingException">
        /// Too many requests.
        /// </exception>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.ValidationException">
        /// An error occurred during validation.
        /// </exception>
        /// <seealso href="http://docs.aws.amazon.com/goto/WebAPI/marketplace-catalog-2018-09-17/DescribeChangeSet">REST API Reference for DescribeChangeSet Operation</seealso>
        public virtual Task <DescribeChangeSetResponse> DescribeChangeSetAsync(DescribeChangeSetRequest request, System.Threading.CancellationToken cancellationToken = default(CancellationToken))
        {
            var options = new InvokeOptions();

            options.RequestMarshaller    = DescribeChangeSetRequestMarshaller.Instance;
            options.ResponseUnmarshaller = DescribeChangeSetResponseUnmarshaller.Instance;

            return(InvokeAsync <DescribeChangeSetResponse>(request, options, cancellationToken));
        }
Esempio n. 6
0
        /// <summary>
        /// Provides information about a given change set.
        /// </summary>
        /// <param name="request">Container for the necessary parameters to execute the DescribeChangeSet service method.</param>
        ///
        /// <returns>The response from the DescribeChangeSet service method, as returned by MarketplaceCatalog.</returns>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.AccessDeniedException">
        /// Access is denied.
        /// </exception>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.InternalServiceException">
        /// There was an internal service exception.
        /// </exception>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.ResourceNotFoundException">
        /// The specified resource wasn't found.
        /// </exception>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.ThrottlingException">
        /// Too many requests.
        /// </exception>
        /// <exception cref="Amazon.MarketplaceCatalog.Model.ValidationException">
        /// An error occurred during validation.
        /// </exception>
        /// <seealso href="http://docs.aws.amazon.com/goto/WebAPI/marketplace-catalog-2018-09-17/DescribeChangeSet">REST API Reference for DescribeChangeSet Operation</seealso>
        public virtual DescribeChangeSetResponse DescribeChangeSet(DescribeChangeSetRequest request)
        {
            var options = new InvokeOptions();

            options.RequestMarshaller    = DescribeChangeSetRequestMarshaller.Instance;
            options.ResponseUnmarshaller = DescribeChangeSetResponseUnmarshaller.Instance;

            return(Invoke <DescribeChangeSetResponse>(request, options));
        }
        /// <summary>
        /// Initiates the asynchronous execution of the DescribeChangeSet operation.
        /// </summary>
        ///
        /// <param name="request">Container for the necessary parameters to execute the DescribeChangeSet operation on AmazonMarketplaceCatalogClient.</param>
        /// <param name="callback">An AsyncCallback delegate that is invoked when the operation completes.</param>
        /// <param name="state">A user-defined state object that is passed to the callback procedure. Retrieve this object from within the callback
        ///          procedure using the AsyncState property.</param>
        ///
        /// <returns>An IAsyncResult that can be used to poll or wait for results, or both; this value is also needed when invoking EndDescribeChangeSet
        ///         operation.</returns>
        /// <seealso href="http://docs.aws.amazon.com/goto/WebAPI/marketplace-catalog-2018-09-17/DescribeChangeSet">REST API Reference for DescribeChangeSet Operation</seealso>
        public virtual IAsyncResult BeginDescribeChangeSet(DescribeChangeSetRequest request, AsyncCallback callback, object state)
        {
            var options = new InvokeOptions();

            options.RequestMarshaller    = DescribeChangeSetRequestMarshaller.Instance;
            options.ResponseUnmarshaller = DescribeChangeSetResponseUnmarshaller.Instance;

            return(BeginInvoke(request, options, callback, state));
        }
Esempio n. 8
0
        private async Task <List <Change> > WaitForChangeSetAsync(string changeSetId)
        {
            // wait until change-set if available
            var changeSetRequest = new DescribeChangeSetRequest {
                ChangeSetName = changeSetId
            };
            var changes = new List <Change>();

            while (true)
            {
                await Task.Delay(TimeSpan.FromSeconds(3));

                var changeSetResponse = await Settings.CfnClient.DescribeChangeSetAsync(changeSetRequest);

                if (changeSetResponse.Status == ChangeSetStatus.CREATE_PENDING)
                {
                    // wait until the change-set is CREATE_COMPLETE
                    continue;
                }
                if (changeSetResponse.Status == ChangeSetStatus.CREATE_IN_PROGRESS)
                {
                    // wait until the change-set is CREATE_COMPLETE
                    continue;
                }
                if (changeSetResponse.Status == ChangeSetStatus.CREATE_COMPLETE)
                {
                    changes.AddRange(changeSetResponse.Changes);
                    if (changeSetResponse.NextToken != null)
                    {
                        changeSetRequest.NextToken = changeSetResponse.NextToken;
                        continue;
                    }
                    return(changes);
                }
                if (changeSetResponse.Status == ChangeSetStatus.FAILED)
                {
                    if (changeSetResponse.StatusReason.StartsWith("The submitted information didn't contain changes.", StringComparison.Ordinal))
                    {
                        return(new List <Change>());
                    }
                    LogError($"change-set failed: {changeSetResponse.StatusReason}");
                    return(null);
                }
                LogError($"unexpected change-set status: {changeSetResponse.ExecutionStatus}");
                return(null);
            }
        }
Esempio n. 9
0
        private static async Task <DeployStackResult> WaitForChangeSetBeingAvailableAsync(ILogger log, IAmazonCloudFormation cloudformation, string changeSetId)
        {
            try
            {
                var request = new DescribeChangeSetRequest
                {
                    ChangeSetName = changeSetId
                };

                log.Information("... Waiting for change set to be reviewed");

                DescribeChangeSetResponse response;
                do
                {
                    await Task.Delay(POLLING_PERIOD).ConfigureAwait(false);

                    response = await cloudformation.DescribeChangeSetAsync(request).ConfigureAwait(false);
                } while (response.Status == ChangeSetStatus.CREATE_IN_PROGRESS || response.Status == ChangeSetStatus.CREATE_PENDING);

                if (response.Status == ChangeSetStatus.FAILED)
                {
                    if (response.StatusReason == "The submitted information didn't contain changes. Submit different information to create a change set.")
                    {
                        log.Information("Cloudformation update not needed.");
                        return(DeployStackResult.NoChanges);
                    }

                    log.Information($"Failed to create CloudFormation change set: {response.StatusReason}");
                    return(DeployStackResult.Failed);
                }

                return(DeployStackResult.Ok);
            }
            catch (Exception e)
            {
                log.Error(e, "Error getting status of change set");
                throw;
            }
        }
        /// <summary>Updates a stack.</summary>
        /// <param name="confirmationFunc">
        /// A callback that should return <c>true</c> or <c>false</c> as to whether to whether the caller confirms the given change set and therefore to continue with the stack update.
        /// If this parameter is <c>null</c>, then <c>true</c> is assumed.
        /// </param>
        /// <exception cref="StackOperationException">Change set creation failed for reasons other than 'no change'</exception>
        /// <returns>Operation result.</returns>
        public async Task <CloudFormationResult> UpdateStackAsync(Func <DescribeChangeSetResponse, bool> confirmationFunc)
        {
            var stack = await this.stackOperations.GetStackAsync(this.stackName);

            // Check stack state first
            var operationalState = await this.stackOperations.GetStackOperationalStateAsync(stack.StackId);

            // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
            switch (operationalState)
            {
            case StackOperationalState.Deleting:
            case StackOperationalState.DeleteFailed:

                throw new StackOperationException(stack, operationalState);

            case StackOperationalState.Broken:

                this.context.Logger.LogWarning("Stack is in a failed state from previous operation. Update may fail.");
                break;

            case StackOperationalState.Busy:

                if (this.waitForInProgressUpdate)
                {
                    // Track stack until update completes
                    // Wait for previous update to complete
                    // Time from which to start polling events
                    this.lastEventTime = await this.GetMostRecentStackEvent(stack.StackId);

                    this.context.Logger.LogInformation(
                        "Stack {0} is currently being updated by another process",
                        stack.StackName);
                    this.context.Logger.LogInformation("Following its progress while waiting...\n");
                    stack = await this.WaitStackOperationAsync(stack.StackId, false);

                    this.context.Logger.LogInformation(string.Empty);

                    var currentState = await this.stackOperations.GetStackOperationalStateAsync(stack.StackId);

                    if (currentState != StackOperationalState.Ready)
                    {
                        throw new StackOperationException(stack, currentState);
                    }
                }
                else
                {
                    throw new StackOperationException(stack, StackOperationalState.Busy);
                }

                break;
            }

            // If we get here, stack is in Ready state
            var changeSetName     = CreateChangeSetName();
            var resourcesToImport = await this.GetResourcesToImportAsync();

            var changeSetRequest = new CreateChangeSetRequest
            {
                ChangeSetName         = changeSetName,
                ChangeSetType         = resourcesToImport != null ? ChangeSetType.IMPORT : ChangeSetType.UPDATE,
                Parameters            = this.GetStackParametersForUpdate(this.templateResolver, stack),
                Capabilities          = this.capabilities.Select(c => c.Value).ToList(),
                StackName             = stack.StackId,
                ClientToken           = this.clientToken,
                RoleARN               = this.roleArn,
                NotificationARNs      = this.notificationARNs,
                RollbackConfiguration = this.rollbackConfiguration,
                ResourceTypes         = this.resourceType,
                Tags = this.tags,
                ResourcesToImport = resourcesToImport,
                TemplateBody      = this.templateResolver.ArtifactContent,
                TemplateURL       =
                    this.usePreviousTemplate ? null : this.templateResolver.ArtifactUrl,
                UsePreviousTemplate = this.usePreviousTemplate,
                IncludeNestedStacks = this.includeNestedStacks
            };

            this.context.Logger.LogInformation($"Creating changeset {changeSetName} for {this.GetStackNameWithDescription()}");

            if (this.includeNestedStacks)
            {
                this.context.Logger.LogInformation("IncludeNestedChangesets is enabled. This may take some time...");
            }

            var changesetArn = (await this.client.CreateChangeSetAsync(changeSetRequest)).Id;

            var stat = ChangeSetStatus.CREATE_PENDING;
            var describeChangeSetRequest = new DescribeChangeSetRequest {
                ChangeSetName = changesetArn
            };

            DescribeChangeSetResponse describeChangeSetResponse = null;

            while (!(stat == ChangeSetStatus.CREATE_COMPLETE || stat == ChangeSetStatus.FAILED))
            {
                Thread.Sleep(this.waitPollTime / 2);
                describeChangeSetResponse = await this.client.DescribeChangeSetAsync(describeChangeSetRequest);

                stat = describeChangeSetResponse.Status;
            }

            if (stat == ChangeSetStatus.FAILED)
            {
                // ReSharper disable once PossibleNullReferenceException - we will go round the above loop at least once
                var reason = describeChangeSetResponse.StatusReason;

                if (reason.Contains("Access Denied") && this.usePreviousTemplate)
                {
                    // Likely that lifecycle policy has removed the previous template.
                    throw new
                          StackOperationException("Unable to create changeset: It is probable that the template has been explicitly deleted or removed by lifecycle policy on your bucket. Please retry specifying the path to the template file");
                }

                if (!NoChangeMessages.Any(msg => reason.StartsWith(msg)))
                {
                    throw new StackOperationException($"Unable to create changeset: {reason}");
                }

                this.context.Logger.LogInformation("No changes to stack were detected.");

                if (this.deleteNoopChangeSet)
                {
                    await this.client.DeleteChangeSetAsync(
                        new DeleteChangeSetRequest { ChangeSetName = changesetArn });

                    this.context.Logger.LogInformation($"Deleted changeset {changeSetName}");
                }

                return(new CloudFormationResult
                {
                    ChangesetResponse = this.deleteNoopChangeSet ? null : describeChangeSetResponse,
                    StackArn = stack.StackId,
                    StackOperationResult = StackOperationResult.NoChange
                });
            }

            // If we get here, emit details, then apply the changeset.
            // ReSharper disable once PossibleNullReferenceException - we will go round the above loop at least once
            if (this.includeNestedStacks)
            {
                // Base stack
                this.context.Logger.LogInformation($"Root Stack: {this.stackName}");
                this.context.Logger.LogChangeset(describeChangeSetResponse);

                // Walk all nested stacks and emit changes for each
                await EmitNestedStackChangesets(describeChangeSetResponse);
            }
            else
            {
                this.context.Logger.LogChangeset(describeChangeSetResponse);
            }

            if (this.changesetOnly)
            {
                this.context.Logger.LogInformation(
                    // ReSharper disable once PossibleNullReferenceException - 'response' cannot be null. DescribeChangeSetAsync has been called at least once to make it here.
                    $"Changeset {describeChangeSetResponse.ChangeSetName} created for stack {stack.StackName}");
                this.context.Logger.LogInformation("Not updating stack since CreateChangesetOnly = true");
                return(new CloudFormationResult
                {
                    ChangesetResponse = describeChangeSetResponse,
                    StackArn = stack.StackId,
                    StackOperationResult = StackOperationResult.NoChange
                });
            }

            if (confirmationFunc != null)
            {
                // Confirm the changeset before proceeding
                if (!confirmationFunc(describeChangeSetResponse))
                {
                    return(new CloudFormationResult
                    {
                        ChangesetResponse = describeChangeSetResponse,
                        StackArn = stack.StackId,
                        StackOperationResult = StackOperationResult.NoChange
                    });
                }
            }

            // Check nobody else has jumped in before us
            var currentState2 = await this.stackOperations.GetStackOperationalStateAsync(stack.StackId);

            if (currentState2 != StackOperationalState.Ready)
            {
                throw new StackOperationException(stack, currentState2);
            }

            // Time from which to start polling events
            this.lastEventTime = await this.GetMostRecentStackEvent(stack.StackId);

            this.context.Logger.LogInformation($"Updating {this.GetStackNameWithDescription()}\n");

            if (resourcesToImport != null)
            {
                // Have to do this by changeset
                await this.client.ExecuteChangeSetAsync(new ExecuteChangeSetRequest { ChangeSetName = changesetArn });
            }
            else
            {
                await this.client.UpdateStackAsync(await this.GetUpdateRequestWithPolicyFromChangesetRequestAsync(changeSetRequest));
            }

            if (this.followOperation)
            {
                await this.WaitStackOperationAsync(stack.StackId, true);

                return(new CloudFormationResult
                {
                    ChangesetResponse = describeChangeSetResponse,
                    StackArn = stack.StackId,
                    StackOperationResult = StackOperationResult.StackUpdated
                });
            }

            return(new CloudFormationResult
            {
                ChangesetResponse = describeChangeSetResponse,
                StackArn = stack.StackId,
                StackOperationResult = StackOperationResult.StackUpdateInProgress
            });

            async Task EmitNestedStackChangesets(DescribeChangeSetResponse parentChangeSetResponse)
            {
                // Recursively discover nested stacks and emit changesets for each
                foreach (var nested in parentChangeSetResponse.Changes.Where(
                             c => c.ResourceChange.ResourceType == "AWS::CloudFormation::Stack"))
                {
                    // Locate nested stack's changeset. It's parent ID will be set to the ID in parentChangeSetResponse
                    var summary = (await this.client.ListChangeSetsAsync(
                                       new ListChangeSetsRequest
                    {
                        StackName = nested.ResourceChange.PhysicalResourceId
                    })).Summaries.First(
                        s => s.ParentChangeSetId == parentChangeSetResponse.ChangeSetId);

                    var nestedResponse = await this.client.DescribeChangeSetAsync(
                        new DescribeChangeSetRequest
                    {
                        ChangeSetName = summary.ChangeSetName,
                        StackName     = nested.ResourceChange.PhysicalResourceId
                    });

                    this.context.Logger.LogInformation($"Nested Stack: {nested.ResourceChange.LogicalResourceId}");
                    this.context.Logger.LogChangeset(nestedResponse);
                    await EmitNestedStackChangesets(nestedResponse);
                }
            }
        }