// Service Update takes a map of CF Parameter changes and applies on top of // the existing parameters and the newest template. // The CLI / Client / Server delegates everything to CloudFormation, and // makes no guarantees of service uptime during update. In fact, most datastore // updates guarantee resource replacement which will cause database downtime. func (s *Service) Update(changes map[string]string) error { var req *cloudformation.UpdateStackInput var err error switch s.Type { case "papertrail": return fmt.Errorf("can not update papertrail") case "webhook": return fmt.Errorf("can not update webhook") case "s3", "sns", "sqs": req, err = s.UpdateIAMService() default: req, err = s.UpdateDatastore() } if err != nil { return err } params := map[string]string{} // copy existing parameters for key, value := range s.Parameters { params[key] = value } // update changes for key, value := range changes { params[key] = value } fp, err := formationParameters(*req.TemplateBody) if err != nil { return err } // remove params that don't exist in the template for key := range params { if _, ok := fp[key]; !ok { delete(params, key) } } // pass through service parameters as Cloudformation Parameters for key, value := range params { req.Parameters = append(req.Parameters, &cloudformation.Parameter{ ParameterKey: aws.String(key), ParameterValue: aws.String(value), }) } _, err = CloudFormation().UpdateStack(req) return err }
// executeStackUpdate performs a stack update. func (s *Scheduler) executeStackUpdate(input *cloudformation.UpdateStackInput) error { stack, err := s.stack(input.StackName) if err != nil { return err } // If we're updating a stack, without changing the template, merge in // existing parameters with their previous value. if input.UsePreviousTemplate != nil && *input.UsePreviousTemplate == true { // The parameters that the stack defines. We need to make sure that we // provide all parameters in the update (lame). definedParams := make(map[string]bool) for _, p := range stack.Parameters { definedParams[*p.ParameterKey] = true } // The parameters that are provided in this update. providedParams := make(map[string]bool) for _, p := range input.Parameters { providedParams[*p.ParameterKey] = true } // Fill in any parameters that weren't provided with their default // value. for k := range definedParams { if !providedParams[k] { input.Parameters = append(input.Parameters, &cloudformation.Parameter{ ParameterKey: aws.String(k), UsePreviousValue: aws.Bool(true), }) } } } _, err = s.cloudformation.UpdateStack(input) if err != nil { if err, ok := err.(awserr.Error); ok { if err.Code() == "ValidationError" && err.Message() == "No updates are to be performed." { return nil } } return fmt.Errorf("error updating stack: %v", err) } return nil }
// UsePreviousTemplate sets the use previous tempalte on a UpdateStackInput func UsePreviousTemplate(params *cfn.UpdateStackInput) { params.UsePreviousTemplate = aws.Bool(true) }
createStackError = awserr.New("code", "message", errors.New("operation failed")) }) It("returns the proper error", func() { err := stack.Create(stackName, stackDetails) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("code: message")) }) }) }) }) var _ = Describe("Modify", func() { var ( stackDetails StackDetails updateStackInput *cloudformation.UpdateStackInput updateStackError error ) BeforeEach(func() { stackDetails = StackDetails{ StackName: stackName, TemplateURL: "test-template-url", } updateStackInput = &cloudformation.UpdateStackInput{ StackName: aws.String(stackName), TemplateURL: aws.String("test-template-url"), } updateStackError = nil })