Ejemplo n.º 1
0
        protected Stack QueryStack(Func <IAmazonCloudFormation> clientFactory, StackArn stack)
        {
            try
            {
                return(clientFactory.DescribeStack(stack));
            }
            catch (AmazonServiceException ex)
            {
                if (ex.ErrorCode == "AccessDenied")
                {
                    throw new PermissionException(
                              "AWS-CLOUDFORMATION-ERROR-0004: The AWS account used to perform the operation does not have " +
                              "the required permissions to describe the CloudFormation stack. " +
                              "This means that the step is not able to generate any output variables.\n" +
                              ex.Message + "\n" +
                              "For more information visit https://g.octopushq.com/AwsCloudFormationDeploy#aws-cloudformation-error-0004",
                              ex);
                }

                throw new UnknownException(
                          "AWS-CLOUDFORMATION-ERROR-0005: An unrecognised exception was thrown while querying the CloudFormation stacks.\n" +
                          "For more information visit https://g.octopushq.com/AwsCloudFormationDeploy#aws-cloudformation-error-0005",
                          ex);
            }
        }
Ejemplo n.º 2
0
        public async Task DescribeChangeset(StackArn stack, ChangeSetArn changeSet, IVariables variables)
        {
            Guard.NotNull(stack, "The provided stack identifer or name may not be null");
            Guard.NotNull(changeSet, "The provided change set identifier or name may not be null");
            Guard.NotNull(variables, "The variable dictionary may not be null");

            try
            {
                var response = await clientFactory.DescribeChangeSetAsync(stack, changeSet);

                SetOutputVariable(variables, "ChangeCount", response.Changes.Count.ToString());
                SetOutputVariable(variables, "Changes",
                                  JsonConvert.SerializeObject(response.Changes, Formatting.Indented));
            }
            catch (AmazonCloudFormationException ex) when(ex.ErrorCode == "AccessDenied")
            {
                throw new PermissionException(
                          "The AWS account used to perform the operation does not have the required permissions to describe the change set.\n" +
                          "Please ensure the current account has permission to perfrom action 'cloudformation:DescribeChangeSet'." +
                          ex.Message + "\n");
            }
            catch (AmazonCloudFormationException ex)
            {
                throw new UnknownException("An unrecognized exception was thrown while describing the CloudFormation change set.", ex);
            }
        }
        public void DescribeChangeset(StackArn stack, ChangeSetArn changeSet, CalamariVariableDictionary variables)
        {
            Guard.NotNull(stack, "The provided stack identifer or name may not be null");
            Guard.NotNull(changeSet, "The provided change set identifier or name may not be null");
            Guard.NotNull(variables, "The variable dictionary may not be null");

            try
            {
                var response = clientFactory.DescribeChangeSet(stack, changeSet);
                SetOutputVariable(variables, "ChangeCount", response.Changes.Count.ToString());
                SetOutputVariable(variables, "Changes", JsonConvert.SerializeObject(response.Changes, Formatting.Indented));
            }
            catch (AmazonCloudFormationException ex)
            {
                if (ex.ErrorCode == "AccessDenied")
                {
                    throw new PermissionException(
                              @"AWS-CLOUDFORMATION-ERROR-0015: The AWS account used to perform the operation does not have " +
                              "the required permissions to describe the change set.\n" +
                              ex.Message + "\n" +
                              "For more information visit the [octopus docs](https://g.octopushq.com/AwsCloudFormationDeploy#aws-cloudformation-error-0015)");
                }

                throw new UnknownException(
                          "AWS-CLOUDFORMATION-ERROR-0016: An unrecognised exception was thrown while describing the CloudFormation change set.\n" +
                          "For more information visit https://g.octopushq.com/AwsCloudFormationDeploy#aws-cloudformation-error-0016",
                          ex);
            }
        }
Ejemplo n.º 4
0
        public static ICloudFormationRequestBuilder Create(ITemplateResolver templateResolver,
                                                           string templateFile,
                                                           string templateParameterFile,
                                                           bool filesInPackage,
                                                           ICalamariFileSystem fileSystem,
                                                           IVariables variables,
                                                           string stackName,
                                                           List <string> capabilities,
                                                           bool disableRollback,
                                                           string roleArn,
                                                           IEnumerable <KeyValuePair <string, string> > tags,
                                                           StackArn stack,
                                                           Func <IAmazonCloudFormation> clientFactory)
        {
            var resolvedTemplate   = templateResolver.Resolve(templateFile, filesInPackage, variables);
            var resolvedParameters = templateResolver.MaybeResolve(templateParameterFile, filesInPackage, variables);

            if (!string.IsNullOrWhiteSpace(templateParameterFile) && !resolvedParameters.Some())
            {
                throw new CommandException("Could not find template parameters file: " + templateParameterFile);
            }

            return(new CloudFormationTemplate(() => variables.Evaluate(fileSystem.ReadFile(resolvedTemplate.Value)),
                                              CloudFormationParametersFile.Create(resolvedParameters, fileSystem, variables),
                                              stackName,
                                              capabilities,
                                              disableRollback,
                                              roleArn,
                                              tags,
                                              stack,
                                              clientFactory,
                                              variables));
        }
Ejemplo n.º 5
0
        private void DeployCloudFormation(RunningDeployment deployment, StackArn stack, CloudFormationTemplate template)
        {
            Guard.NotNull(deployment, "deployment can not be null");

            clientFactory.WaitForStackToComplete(CloudFormationDefaults.StatusWaitPeriod, stack, LogAndThrowRollbacks(clientFactory, stack, false));
            DeployStack(deployment, stack, template);
        }
Ejemplo n.º 6
0
        public BaseTemplate(IEnumerable <Parameter> inputs,
                            string stackName,
                            List <string> iamCapabilities,
                            bool disableRollback,
                            string roleArn,
                            IEnumerable <KeyValuePair <string, string> > tags,
                            StackArn stack,
                            Func <IAmazonCloudFormation> clientFactory,
                            IVariables variables)
        {
            Inputs = inputs;

            this.stackName       = stackName;
            this.disableRollback = disableRollback;
            this.stack           = stack;
            this.roleArn         = roleArn;
            this.clientFactory   = clientFactory;
            this.variables       = variables;
            this.tags            = tags?.Select(x => new Tag {
                Key = x.Key, Value = x.Value
            }).ToList();

            var(validCapabilities, _) = ExcludeAndLogUnknownIamCapabilities(iamCapabilities);
            capabilities = validCapabilities.ToList();
        }
Ejemplo n.º 7
0
 /// <summary>
 /// Deletes the stack
 /// </summary>
 private void DeleteCloudFormation(StackArn stack)
 {
     WithAmazonServiceExceptionHandling(() =>
     {
         clientFactory.DeleteStack(stack);
         Log.Info($"Deleted stack called {stackName} in region {awsEnvironmentGeneration.AwsRegion.SystemName}");
     });
 }
Ejemplo n.º 8
0
 private Task DeleteCloudFormation(StackArn stack)
 {
     Guard.NotNull(stack, "Stack must not be null");
     return(WithAmazonServiceExceptionHandling(async() =>
     {
         await clientFactory.DeleteStackAsync(stack);
     }));
 }
 /// <summary>
 /// Deletes the stack
 /// </summary>
 private Task DeleteCloudFormation(StackArn stack)
 {
     return(WithAmazonServiceExceptionHandling(async() =>
     {
         await clientFactory.DeleteStackAsync(stack);
         Log.Info($"Deleted stack called {stackName} in region {awsEnvironmentGeneration.AwsRegion.SystemName}");
     }));
 }
        private async Task DeployCloudFormation(RunningDeployment deployment, StackArn stack, ICloudFormationRequestBuilder template)
        {
            Guard.NotNull(deployment, "deployment can not be null");

            var deploymentStartTime = DateTime.Now;

            await clientFactory.WaitForStackToComplete(CloudFormationDefaults.StatusWaitPeriod, stack, LogAndThrowRollbacks(clientFactory, stack, false, filter : FilterStackEventsSince(deploymentStartTime)));

            await DeployStack(deployment, stack, template, deploymentStartTime);
        }
        /// <summary>
        /// Updates the stack and returns the stack ID
        /// </summary>
        /// <param name="stack">The stack name or id</param>
        /// <param name="deployment">The current deployment</param>
        /// <param name="template">The CloudFormation template</param>
        /// <returns>stackId</returns>
        private async Task <string> UpdateCloudFormation(
            RunningDeployment deployment,
            StackArn stack,
            CloudFormationTemplate template)
        {
            Guard.NotNull(deployment, "deployment can not be null");
            try
            {
                var result = await ClientHelpers.CreateCloudFormationClient(awsEnvironmentGeneration).UpdateStackAsync(new UpdateStackRequest
                {
                    StackName    = stackName,
                    TemplateBody = template.Content,
                    Parameters   = template.Inputs.ToList(),
                    Capabilities = capabilities,
                    RoleARN      = roleArnProvider(deployment)
                });

                Log.Info(
                    $"Updated stack with id {result.StackId} in region {awsEnvironmentGeneration.AwsRegion.SystemName}");

                return(result.StackId);
            }
            catch (AmazonCloudFormationException ex)
            {
                // Some stack states indicate that we can delete the stack and start again. Otherwise we have some other
                // exception that needs to be dealt with.
                if (!(await StackMustBeDeleted(stack)).SelectValueOrDefault(x => x))
                {
                    // Is this an unrecoverable state, or just a stack that has nothing to update?
                    if (DealWithUpdateException(ex))
                    {
                        // There was nothing to update, but we return the id for consistency anyway
                        var result = await QueryStackAsync(clientFactory, stack);

                        return(result.StackId);
                    }
                }

                // If the stack exists, is in a ROLLBACK_COMPLETE state, and was never successfully
                // created in the first place, we can end up here. In this case we try to create
                // the stack from scratch.
                await DeleteCloudFormation(stack);

                await clientFactory.WaitForStackToComplete(CloudFormationDefaults.StatusWaitPeriod, stack, LogAndThrowRollbacks(clientFactory, stack, false));

                return(await CreateCloudFormation(deployment, template));
            }
            catch (AmazonServiceException ex)
            {
                LogAmazonServiceException(ex);
                throw ex;
            }
        }
 /// <summary>
 /// Check whether the stack must be deleted in order to recover.
 /// </summary>
 /// <param name="stack">The stack id or name</param>
 /// <returns>true if this status indicates that the stack has to be deleted, and false otherwise</returns>
 private async Task <Maybe <bool> > StackMustBeDeleted(StackArn stack)
 {
     try
     {
         return((await StackEvent(stack)).Select(x => x.StackIsUnrecoverable()));
     }
     catch (PermissionException)
     {
         // If we can't get the stack status, assume it is not in a state that we can recover from
         return(Maybe <bool> .None);
     }
 }
        public static ICloudFormationRequestBuilder Create(string templateS3Url,
                                                           string templateParameterS3Url,
                                                           ICalamariFileSystem fileSystem,
                                                           IVariables variables,
                                                           ILog log,
                                                           string stackName,
                                                           List <string> capabilities,
                                                           bool disableRollback,
                                                           string roleArn,
                                                           IEnumerable <KeyValuePair <string, string> > tags,
                                                           StackArn stack,
                                                           Func <IAmazonCloudFormation> clientFactory)
        {
            if (!string.IsNullOrWhiteSpace(templateParameterS3Url) && !templateParameterS3Url.StartsWith("http"))
            {
                throw new CommandException("Parameters file must start with http: " + templateParameterS3Url);
            }

            if (!string.IsNullOrWhiteSpace(templateS3Url) && !templateS3Url.StartsWith("http"))
            {
                throw new CommandException("Template file must start with http: " + templateS3Url);
            }

            var templatePath = string.IsNullOrWhiteSpace(templateParameterS3Url)
                ? Maybe <ResolvedTemplatePath> .None
                : new ResolvedTemplatePath(ParametersFile).AsSome();

            if (templatePath.Some())
            {
                DownloadS3(variables, log, templateParameterS3Url);
            }

            var parameters = CloudFormationParametersFile.Create(templatePath, fileSystem, variables);

            return(new CloudFormationS3Template(parameters,
                                                templateS3Url,
                                                stackName,
                                                capabilities,
                                                disableRollback,
                                                roleArn,
                                                tags,
                                                stack,
                                                clientFactory,
                                                variables));
        }
        /// <summary>
        /// Update or create the stack
        /// </summary>
        /// <param name="deployment">The current deployment</param>
        /// <param name="stack"></param>
        /// <param name="template"></param>
        private async Task DeployStack(RunningDeployment deployment, StackArn stack, CloudFormationTemplate template)
        {
            Guard.NotNull(deployment, "deployment can not be null");

            var stackId = await template.Inputs
                          // Use the parameters to either create or update the stack
                          .Map(async parameters => await StackExists(stack, StackStatus.DoesNotExist) != StackStatus.DoesNotExist
                               ?await UpdateCloudFormation(deployment, stack, template)
                               : await CreateCloudFormation(deployment, template));

            if (waitForComplete)
            {
                await clientFactory.WaitForStackToComplete(CloudFormationDefaults.StatusWaitPeriod, stack, LogAndThrowRollbacks(clientFactory, stack));
            }

            // Take the stack ID returned by the create or update events, and save it as an output variable
            Log.SetOutputVariable("AwsOutputs[StackId]", stackId ?? "", deployment.Variables);
            Log.Info(
                $"Saving variable \"Octopus.Action[{deployment.Variables["Octopus.Action.Name"]}].Output.AwsOutputs[StackId]\"");
        }
Ejemplo n.º 15
0
 public CloudFormationTemplate(Func <string> content,
                               ITemplateInputs <Parameter> parameters,
                               string stackName,
                               List <string> iamCapabilities,
                               bool disableRollback,
                               string roleArn,
                               IEnumerable <KeyValuePair <string, string> > tags,
                               StackArn stack,
                               Func <IAmazonCloudFormation> clientFactory,
                               IVariables variables) : base(parameters.Inputs,
                                                            stackName,
                                                            iamCapabilities,
                                                            disableRollback,
                                                            roleArn,
                                                            tags,
                                                            stack,
                                                            clientFactory,
                                                            variables)
 {
     this.content = content;
 }
        private async Task <Maybe <RunningChangeSet> > ExecuteChangeset(Func <IAmazonCloudFormation> factory, StackArn stack,
                                                                        ChangeSetArn changeSet)
        {
            try
            {
                var changes = await factory.WaitForChangeSetCompletion(CloudFormationDefaults.StatusWaitPeriod,
                                                                       new RunningChangeSet(stack, changeSet));

                if (changes.Status == ChangeSetStatus.FAILED &&
                    string.Compare(changes.StatusReason, "No updates are to be performed.",
                                   StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    //We don't need the failed changeset to hang around if there are no changes
                    await factory().DeleteChangeSetAsync(new DeleteChangeSetRequest
                    {
                        ChangeSetName = changeSet.Value,
                        StackName     = stack.Value
                    });

                    return(Maybe <RunningChangeSet> .None);
                }

                if (changes.Status == ChangeSetStatus.FAILED)
                {
                    throw new UnknownException($"The changeset failed to create.\n{changes.StatusReason}");
                }

                await factory().ExecuteChangeSetAsync(new ExecuteChangeSetRequest
                {
                    ChangeSetName = changeSet.Value,
                    StackName     = stack.Value
                });

                return(new RunningChangeSet(stack, changeSet).AsSome());
            }
            catch (AmazonCloudFormationException exception) when(exception.ErrorCode == "AccessDenied")
            {
                throw new PermissionException(
                          "The AWS account used to perform the operation does not have the required permission to execute the changeset.\n" +
                          "Please ensure the current account has permission to perfrom action 'cloudformation:ExecuteChangeSet'.\n" +
                          exception.Message + "\n");
            }
            catch (AmazonServiceException exception)
            {
                LogAmazonServiceException(exception);
                throw;
            }
        }
Ejemplo n.º 17
0
 protected Task <Stack> QueryStackAsync(Func <IAmazonCloudFormation> clientFactory, StackArn stack)
 {
     try
     {
         return(clientFactory.DescribeStackAsync(stack));
     }
     catch (AmazonServiceException ex) when(ex.ErrorCode == "AccessDenied")
     {
         throw new PermissionException(
                   "The AWS account used to perform the operation does not have the required permissions to describe the CloudFormation stack. " +
                   "This means that the step is not able to generate any output variables.\n " +
                   "Please ensure the current account has permission to perform action 'cloudformation:DescribeStacks'.\n" +
                   ex.Message + "\n" +
                   ex);
     }
     catch (AmazonServiceException ex)
     {
         throw new Exception("An unrecognised exception was thrown while querying the CloudFormation stacks.", ex);
     }
 }
Ejemplo n.º 18
0
 private CreateChangeSetRequest CreateChangesetRequest(StackStatus status, string changesetName, StackArn stack, string roleArn, CloudFormationTemplate template, List <string> capabilities)
 {
     return(new CreateChangeSetRequest
     {
         StackName = stack.Value,
         TemplateBody = template.Content,
         Parameters = template.Inputs.ToList(),
         ChangeSetName = changesetName,
         ChangeSetType = status == StackStatus.DoesNotExist ? ChangeSetType.CREATE : ChangeSetType.UPDATE,
         Capabilities = capabilities,
         RoleARN = roleArn
     });
 }
Ejemplo n.º 19
0
        private Maybe <RunningChangeSet> ExecuteChangeset(Func <IAmazonCloudFormation> factory, StackArn stack,
                                                          ChangeSetArn changeSet)
        {
            try
            {
                var changes = factory.WaitForChangeSetCompletion(CloudFormationDefaults.StatusWaitPeriod,
                                                                 new RunningChangeSet(stack, changeSet));

                if (changes.Status == ChangeSetStatus.FAILED &&
                    string.Compare(changes.StatusReason, "No updates are to be performed.",
                                   StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    //We don't need the failed changeset to hang around if there are no changes
                    factory().DeleteChangeSet(new DeleteChangeSetRequest
                    {
                        ChangeSetName = changeSet.Value,
                        StackName     = stack.Value
                    });

                    return(Maybe <RunningChangeSet> .None);
                }

                if (changes.Status == ChangeSetStatus.FAILED)
                {
                    throw new UnknownException($"AWS-CLOUDFORMATION-ERROR-0019: The changeset failed to create.\n{changes.StatusReason}");
                }


                factory().ExecuteChangeSet(new ExecuteChangeSetRequest
                {
                    ChangeSetName = changeSet.Value,
                    StackName     = stack.Value
                });

                return(new RunningChangeSet(stack, changeSet).AsSome());
            }
            catch (AmazonCloudFormationException exception)
            {
                if (exception.ErrorCode == "AccessDenied")
                {
                    throw new PermissionException(
                              "AWS-CLOUDFORMATION-ERROR-0011: The AWS account used to perform the operation does not have " +
                              "the required permissions to update the stack.\n" +
                              exception.Message + "\n" +
                              "For more information visit https://g.octopushq.com/AwsCloudFormationDeploy#aws-cloudformation-error-0011");
                }

                throw new UnknownException(
                          "AWS-CLOUDFORMATION-ERROR-0011: An unrecognised exception was thrown while updating a CloudFormation stack.\n" +
                          "For more information visit https://g.octopushq.com/AwsCloudFormationDeploy#aws-cloudformation-error-0011",
                          exception);
            }
            catch (AmazonServiceException exception)
            {
                HandleAmazonServiceException(exception);
                throw;
            }
        }
Ejemplo n.º 20
0
        private async Task <RunningChangeSet> ExecuteChangeset(Func <IAmazonCloudFormation> factory, StackArn stack,
                                                               ChangeSetArn changeSet)
        {
            try
            {
                var changes = await factory.WaitForChangeSetCompletion(CloudFormationDefaults.StatusWaitPeriod,
                                                                       new RunningChangeSet(stack, changeSet));


                if (changes.Status == ChangeSetStatus.FAILED)
                {
                    throw new UnknownException($"The changeset failed to create.\n{changes.StatusReason}");
                }

                await factory().ExecuteChangeSetAsync(new ExecuteChangeSetRequest
                {
                    ChangeSetName = changeSet.Value,
                    StackName     = stack.Value
                });

                return(new RunningChangeSet(stack, changeSet));
            }
            catch (AmazonCloudFormationException exception) when(exception.ErrorCode == "AccessDenied")
            {
                throw new PermissionException(
                          "The AWS account used to perform the operation does not have the required permission to execute the changeset.\n" +
                          "Please ensure the current account has permission to perfrom action 'cloudformation:ExecuteChangeSet'.\n" +
                          exception.Message + "\n");
            }
            catch (AmazonServiceException exception)
            {
                LogAmazonServiceException(exception);
                throw;
            }
        }
 /// <summary>
 /// Check to see if the stack name exists.
 /// </summary>
 /// <param name="defaultValue">The return value when the user does not have the permissions to query the stacks</param>
 /// <returns>The current status of the stack</returns>
 async Task <StackStatus> StackExists(StackArn stack, StackStatus defaultValue)
 {
     return(await WithAmazonServiceExceptionHandling(async() => await clientFactory.StackExistsAsync(stack, defaultValue)));
 }
Ejemplo n.º 22
0
 /// <summary>
 /// Creates a handler which will log stack events and throw on common rollback events
 /// </summary>
 /// <param name="clientFactory">The client factory</param>
 /// <param name="stack">The stack to query</param>
 /// <param name="filter">The filter for stack events</param>
 /// <param name="expectSuccess">Whether we expected a success</param>
 /// <param name="missingIsFailure"></param>
 /// <returns>Stack event handler</returns>
 protected Action <Maybe <StackEvent> > LogAndThrowRollbacks(Func <IAmazonCloudFormation> clientFactory, StackArn stack, bool expectSuccess = true, bool missingIsFailure = true, Func <StackEvent, bool> filter = null)
 {
     return(@event =>
     {
         try
         {
             Logger.Log(@event);
             Logger.LogRollbackError(@event, x =>
                                     WithAmazonServiceExceptionHandling(() => clientFactory.GetLastStackEvent(stack, x)),
                                     expectSuccess,
                                     missingIsFailure);
         }
         catch (PermissionException exception)
         {
             Log.Warn(exception.Message);
         }
     });
 }
 /// <summary>
 /// Check to see if the stack name exists.
 /// </summary>
 /// <param name="defaultValue">The return value when the user does not have the permissions to query the stacks</param>
 /// <returns>The current status of the stack</returns>
 private Task <StackStatus> StackExists(StackArn stack, StackStatus defaultValue)
 {
     return(WithAmazonServiceExceptionHandling(() => clientFactory.StackExistsAsync(stack, defaultValue)));
 }
 /// <summary>
 /// Gets the last stack event by timestamp, optionally filtered by a predicate
 /// </summary>
 /// <param name="predicate">The optional predicate used to filter events</param>
 /// <returns>The stack event</returns>
 private Task <Maybe <StackEvent> > StackEvent(StackArn stack, Func <StackEvent, bool> predicate = null)
 {
     return(WithAmazonServiceExceptionHandling(
                async() => await clientFactory.GetLastStackEvent(stack, predicate)));
 }
 private void DeleteCloudFormation(StackArn stack)
 {
     Guard.NotNull(stack, "Stack must not be null");
     WithAmazonServiceExceptionHandling(() => clientFactory.DeleteStack(stack));
 }