Class: Jets::Cfn::Status

Inherits:
Object
  • Object
show all
Includes:
AwsServices
Defined in:
lib/jets/cfn/status.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from AwsServices

#apigateway, #cfn, #lambda, #logs, #s3, #s3_resource, #sns, #sqs, #sts

Methods included from AwsServices::StackStatus

#lookup, #stack_exists?, #stack_in_progress?

Constructor Details

#initialize(options = {}) ⇒ Status

Returns a new instance of Status.



6
7
8
9
10
# File 'lib/jets/cfn/status.rb', line 6

def initialize(options={})
  @options = options
  @stack_name = Jets::Naming.parent_stack_name
  reset
end

Instance Attribute Details

#eventsObject (readonly)

Returns the value of attribute events.



5
6
7
# File 'lib/jets/cfn/status.rb', line 5

def events
  @events
end

Instance Method Details

#completedObject



73
74
75
76
77
# File 'lib/jets/cfn/status.rb', line 73

def completed
  last_event_status =~ /(_COMPLETE|_FAILED)$/ &&
  @events[0]["logical_resource_id"] == @stack_name &&
  @events[0]["resource_type"] == "AWS::CloudFormation::Stack"
end

#event_time(timestamp) ⇒ Object



120
121
122
# File 'lib/jets/cfn/status.rb', line 120

def event_time(timestamp)
  Time.parse(timestamp.to_s).localtime.strftime("%I:%M:%S%p")
end

#find_index(name) ⇒ Object



136
137
138
# File 'lib/jets/cfn/status.rb', line 136

def find_index(name)
  send("#{name}_index")
end

#find_update_failed_eventObject



162
163
164
165
166
167
168
169
170
171
# File 'lib/jets/cfn/status.rb', line 162

def find_update_failed_event
  i = @events.find_index do |event|
    event["resource_type"] == "AWS::CloudFormation::Stack" &&
    event["resource_status_reason"] == "User Initiated"
  end

  @events[0..i].reverse.find do |e|
    e["resource_status"] == "UPDATE_FAILED"
  end
end

#last_event_statusObject



79
80
81
# File 'lib/jets/cfn/status.rb', line 79

def last_event_status
  @events[0]["resource_status"]
end

#last_shown_indexObject



147
148
149
150
151
# File 'lib/jets/cfn/status.rb', line 147

def last_shown_index
  @events.find_index do |event|
    event["event_id"] == @last_shown_event_id
  end
end

#messages_mapObject



189
190
191
192
193
194
195
# File 'lib/jets/cfn/status.rb', line 189

def messages_map
  {
    /CloudFormation cannot update a stack when a custom-named resource requires replacing/ => "A workaround is to run ufo again with STATIC_NAME=0 and to switch to dynamic names for resources. Then run ufo again with STATIC_NAME=1 to get back to statically name resources. Note, there are caveats with the workaround.",
    /cannot be associated with more than one load balancer/ => "There's was an issue updating the stack. Target groups can only be associated with one load balancer at a time. The workaround for this is to use UFO_FORCE_TARGET_GROUP=1 and run the command again. This will force the recreation of the target group resource.",
    /SetSubnets is not supported for load balancers of type/ => "Changing subnets for Network Load Balancers is currently not supported. You can try workarouding this with UFO_FORCE_ELB=1 and run the command again. This will force the recreation of the elb resource."
  }
end

#pretty_time(total_seconds) ⇒ Object



198
199
200
201
202
203
204
205
206
# File 'lib/jets/cfn/status.rb', line 198

def pretty_time(total_seconds)
  minutes = (total_seconds / 60) % 60
  seconds = total_seconds % 60
  if total_seconds < 60
    "#{seconds.to_i}s"
  else
    "#{minutes.to_i}m #{seconds.to_i}s"
  end
end


107
108
109
110
111
112
113
114
115
116
117
# File 'lib/jets/cfn/status.rb', line 107

def print_event(e)
  message = [
    event_time(e["timestamp"]),
    e["resource_status"],
    e["resource_type"],
    e["logical_resource_id"],
    e["resource_status_reason"]
  ].join(" ")
  message = message.colorize(:red) if e["resource_status"] =~ /_FAILED/
  puts message
end


99
100
101
102
103
104
105
# File 'lib/jets/cfn/status.rb', line 99

def print_events(i)
  @events[0..i].reverse.each do |e|
    print_event(e)
  end
  @last_shown_event_id = @events[0]["event_id"]
  # puts "@last_shown_event_id #{@last_shown_event_id.inspect}"
end

#refresh_eventsObject

refreshes the loaded events in memory



125
126
127
128
129
130
131
132
133
134
# File 'lib/jets/cfn/status.rb', line 125

def refresh_events
  resp = cfn.describe_stack_events(stack_name: @stack_name)
  @events = resp["stack_events"]
rescue Aws::CloudFormation::Errors::ValidationError => e
  if e.message =~ /Stack .* does not exis/
    @stack_deletion_completed = true
  else
    raise
  end
end

#resetObject



36
37
38
39
40
# File 'lib/jets/cfn/status.rb', line 36

def reset
  @events = [] # constantly replaced with recent events
  @last_shown_event_id = nil
  @stack_deletion_completed = nil
end

#rollback_error_messageObject



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/jets/cfn/status.rb', line 173

def rollback_error_message
  return unless update_rollback?

  event = find_update_failed_event
  return unless event

  reason = event["resource_status_reason"]
  messages_map.each do |pattern, message|
    if reason =~ pattern
      return message
    end
  end

  reason # default message is original reason if not found in messages map
end

#runObject

used for the jets status command



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/jets/cfn/status.rb', line 13

def run
  unless stack_exists?(@stack_name)
    puts "The stack #{@stack_name.colorize(:green)} does not exist."
    return
  end

  resp = cfn.describe_stacks(stack_name: @stack_name)
  stack = resp.stacks.first

  puts "The current status for the stack #{@stack_name.colorize(:green)} is #{stack.stack_status.colorize(:green)}"
  if stack.stack_status =~ /_IN_PROGRESS$/
    puts "Stack events (tailing):"
    # tail all events until done
    @hide_time_took = true
    wait
  else
    puts "Stack events:"
    # show the last events that was user initiated
    refresh_events
    show_events(true)
  end
end

#show_events(final = false) ⇒ Object

Only shows new events



84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/jets/cfn/status.rb', line 84

def show_events(final=false)
  if @last_shown_event_id.nil?
    i = find_index(:start)
    print_events(i)
  else
    i = find_index(:last_shown)
    # puts "last_shown index #{i}"
    print_events(i-1) unless i == 0
  end

  return if final
  sleep 5 unless ENV['TEST']
  refresh_events
end

#start_indexObject



140
141
142
143
144
145
# File 'lib/jets/cfn/status.rb', line 140

def start_index
  @events.find_index do |event|
    event["resource_type"] == "AWS::CloudFormation::Stack" &&
    event["resource_status_reason"] == "User Initiated"
  end
end

#success?Boolean

Returns:

  • (Boolean)


153
154
155
156
# File 'lib/jets/cfn/status.rb', line 153

def success?
  resource_status = @events[0]["resource_status"]
  %w[CREATE_COMPLETE UPDATE_COMPLETE].include?(resource_status)
end

#update_rollback?Boolean

Returns:

  • (Boolean)


158
159
160
# File 'lib/jets/cfn/status.rb', line 158

def update_rollback?
  @events[0]["resource_status"] == "UPDATE_ROLLBACK_COMPLETE"
end

#waitObject

check for /(_COMPLETE|_FAILED)$/ status



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/jets/cfn/status.rb', line 43

def wait
  start_time = Time.now

  refresh_events
  until completed || @stack_deletion_completed
    show_events
  end
  show_events(true) # show the final event

  if @stack_deletion_completed
    puts "Stack #{@stack_name} deleted."
    return
  end

  if last_event_status =~ /_FAILED/
    puts "Stack failed: #{last_event_status}".colorize(:red)
    puts "Stack reason #{@events[0]["resource_status_reason"]}".colorize(:red)
  elsif last_event_status =~ /_ROLLBACK_/
    puts "Stack rolled back: #{last_event_status}".colorize(:red)
  else # success
    puts "Stack success status: #{last_event_status}".colorize(:green)
  end

  # Never gets here when deleting a stack because the describe stack returns nothing
  # once the stack is deleted. Gets here for stack create and update though.
  return if @hide_time_took # set in run
  took = Time.now - start_time
  puts "Time took for stack deployment: #{pretty_time(took).green}."
end