github.com/franciscocpg/up@v0.1.10/platform/lambda/stack/stack.go (about) 1 package stack 2 3 import ( 4 "encoding/json" 5 "time" 6 7 "github.com/aws/aws-sdk-go/aws" 8 "github.com/aws/aws-sdk-go/aws/session" 9 "github.com/aws/aws-sdk-go/service/cloudformation" 10 "github.com/pkg/errors" 11 12 "github.com/apex/up" 13 "github.com/apex/up/internal/util" 14 "github.com/apex/up/platform/event" 15 ) 16 17 // TODO: refactor New's hackiness 18 19 // Stack represents a single CloudFormation stack. 20 type Stack struct { 21 client *cloudformation.CloudFormation 22 events event.Events 23 config *up.Config 24 } 25 26 // New stack. 27 func New(c *up.Config, events event.Events, region string) *Stack { 28 sess := session.New(aws.NewConfig().WithRegion(region)) 29 return &Stack{ 30 client: cloudformation.New(sess), 31 events: events, 32 config: c, 33 } 34 } 35 36 // Create the stack. 37 func (s *Stack) Create(version string) error { 38 c := s.config 39 tmpl := template(c) 40 name := c.Name 41 42 b, err := json.MarshalIndent(tmpl, "", " ") 43 if err != nil { 44 return errors.Wrap(err, "marshaling") 45 } 46 47 _, err = s.client.CreateStack(&cloudformation.CreateStackInput{ 48 StackName: &name, 49 TemplateBody: aws.String(string(b)), 50 TimeoutInMinutes: aws.Int64(15), 51 DisableRollback: aws.Bool(true), 52 Capabilities: aws.StringSlice([]string{"CAPABILITY_NAMED_IAM"}), 53 Parameters: []*cloudformation.Parameter{ 54 { 55 ParameterKey: aws.String("Name"), 56 ParameterValue: &name, 57 }, 58 { 59 ParameterKey: aws.String("FunctionName"), 60 ParameterValue: &name, 61 }, 62 { 63 ParameterKey: aws.String("FunctionVersion"), 64 ParameterValue: &version, 65 }, 66 }, 67 }) 68 69 if err != nil { 70 return errors.Wrap(err, "creating stack") 71 } 72 73 if err := s.report("create"); err != nil { 74 return errors.Wrap(err, "reporting events") 75 } 76 77 stack, err := s.getStack() 78 if err != nil { 79 return errors.Wrap(err, "fetching stack") 80 } 81 82 status := Status(*stack.StackStatus) 83 if status.State() == Failure { 84 return errors.New(*stack.StackStatusReason) 85 } 86 87 return nil 88 } 89 90 // Delete the stack, optionally waiting for completion. 91 func (s *Stack) Delete(wait bool) error { 92 _, err := s.client.DeleteStack(&cloudformation.DeleteStackInput{ 93 StackName: &s.config.Name, 94 }) 95 96 if err != nil { 97 return errors.Wrap(err, "deleting") 98 } 99 100 if wait { 101 if err := s.report("delete"); err != nil { 102 return errors.Wrap(err, "reporting") 103 } 104 } 105 106 return nil 107 } 108 109 // Show resources. 110 func (s *Stack) Show() error { 111 defer s.events.Time("platform.stack.show", nil)() 112 113 stack, err := s.getStack() 114 if err != nil { 115 return errors.Wrap(err, "fetching stack") 116 } 117 118 s.events.Emit("platform.stack.show.stack", event.Fields{ 119 "stack": stack, 120 }) 121 122 events, err := s.getLatestEvents() 123 if err != nil { 124 return errors.Wrap(err, "fetching latest events") 125 } 126 127 for _, e := range events { 128 s.events.Emit("platform.stack.show.event", event.Fields{ 129 "event": e, 130 }) 131 } 132 133 return nil 134 } 135 136 // report events. 137 func (s *Stack) report(state string) error { 138 hit := make(map[string]bool) 139 tmpl := template(s.config) 140 141 defer s.events.Time("platform.stack."+state, event.Fields{ 142 "resources": len(tmpl["Resources"].(Map)), 143 })() 144 145 for range time.Tick(time.Second) { 146 stack, err := s.getStack() 147 148 if util.IsNotFound(err) { 149 return nil 150 } 151 152 if err != nil { 153 return errors.Wrap(err, "fetching stack") 154 } 155 156 status := Status(*stack.StackStatus) 157 158 if status.IsDone() { 159 return nil 160 } 161 162 events, err := s.getEvents() 163 164 if util.IsNotFound(err) { 165 return nil 166 } 167 168 if err != nil { 169 return errors.Wrap(err, "fetching events") 170 } 171 172 for _, e := range events { 173 if hit[*e.EventId] { 174 continue 175 } 176 hit[*e.EventId] = true 177 178 s.events.Emit("platform.stack."+state+".event", event.Fields{ 179 "event": e, 180 }) 181 } 182 } 183 184 return nil 185 } 186 187 // getStack returns the stack. 188 func (s *Stack) getStack() (*cloudformation.Stack, error) { 189 res, err := s.client.DescribeStacks(&cloudformation.DescribeStacksInput{ 190 StackName: &s.config.Name, 191 }) 192 193 if err != nil { 194 return nil, err 195 } 196 197 stack := res.Stacks[0] 198 return stack, nil 199 } 200 201 // getLatestEvents returns the latest events for each resource. 202 func (s *Stack) getLatestEvents() (v []*cloudformation.StackEvent, err error) { 203 events, err := s.getEvents() 204 if err != nil { 205 return 206 } 207 208 hit := make(map[string]bool) 209 210 for _, e := range events { 211 id := *e.LogicalResourceId 212 if hit[id] { 213 continue 214 } 215 216 hit[id] = true 217 v = append(v, e) 218 } 219 220 return 221 } 222 223 // getEvents returns events. 224 func (s *Stack) getEvents() (events []*cloudformation.StackEvent, err error) { 225 var next *string 226 227 for { 228 res, err := s.client.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{ 229 StackName: &s.config.Name, 230 NextToken: next, 231 }) 232 233 if err != nil { 234 return nil, err 235 } 236 237 for _, e := range res.StackEvents { 238 events = append(events, e) 239 } 240 241 next = res.NextToken 242 243 if next == nil { 244 break 245 } 246 } 247 248 return 249 } 250 251 // getEventsByState returns events by state. 252 func (s *Stack) getEventsByState(state State) (v []*cloudformation.StackEvent, err error) { 253 events, err := s.getEvents() 254 if err != nil { 255 return 256 } 257 258 for _, e := range events { 259 s := Status(*e.ResourceStatus) 260 if s.State() == state { 261 v = append(v, e) 262 } 263 } 264 265 return 266 }