Class: Hatchet::Reaper

Inherits:
Object
  • Object
show all
Defined in:
lib/hatchet/reaper.rb

Overview

Hatchet apps are useful after the tests run for debugging purposes the reaper is designed to allow the most recent apps to stay alive while keeping the total number of apps under the global Heroku limit. Any time you’re worried about hitting the limit call @reaper.cycle

Constant Summary collapse

HEROKU_APP_LIMIT =

the number of apps heroku allows you to keep

Integer(ENV["HEROKU_APP_LIMIT"]  || 100)
HATCHET_APP_LIMT =

the number of apps hatchet keeps around

Integer(ENV["HATCHET_APP_LIMIT"] || 20)
DEFAULT_REGEX =
/^#{Regexp.escape(Hatchet::APP_PREFIX)}[a-f0-9]+/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_rate_limit:, regex: DEFAULT_REGEX) ⇒ Reaper

Returns a new instance of Reaper.



15
16
17
18
# File 'lib/hatchet/reaper.rb', line 15

def initialize(api_rate_limit: , regex: DEFAULT_REGEX)
  @api_rate_limit = api_rate_limit
  @regex        = regex
end

Instance Attribute Details

#appsObject

Returns the value of attribute apps.



13
14
15
# File 'lib/hatchet/reaper.rb', line 13

def apps
  @apps
end

Instance Method Details

#cycleObject



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/hatchet/reaper.rb', line 27

def cycle
  # we don't want multiple Hatchet processes (e.g. when using rspec-parallel) to delete apps at the same time
  # this could otherwise result in race conditions in API causing errors other than 404s, making tests fail
  mutex = File.open("#{Dir.tmpdir()}/hatchet_reaper_mutex", File::CREAT)
  mutex.flock(File::LOCK_EX)

  # update list of apps once
  get_apps

  return unless over_limit?

  while over_limit?
    if @hatchet_apps.count > 1
      # remove our own apps until we are below limit
      destroy_oldest
    else
      puts "Warning: Reached Heroku app limit of #{HEROKU_APP_LIMIT}."
      break
    end
  end

# If the app is already deleted an exception
# will be raised, if the app cannot be found
# assume it is already deleted and try again
rescue Excon::Error::NotFound => e
  body = e.response.body
  if body =~ /Couldn\'t find that app./
    puts "#{@message}, but looks like it was already deleted"
    mutex.close # ensure only gets called on block exit and not on `retry`
    retry
  end
  raise e
ensure
  # don't forget to close the mutex; this also releases our lock
  mutex.close
end

#destroy_allObject



69
70
71
72
73
74
# File 'lib/hatchet/reaper.rb', line 69

def destroy_all
  get_apps
  @hatchet_apps.each do |app|
    destroy_by_id(name: app["name"], id: app["id"])
  end
end

#destroy_by_id(name:, id:, details: "") ⇒ Object



76
77
78
79
80
# File 'lib/hatchet/reaper.rb', line 76

def destroy_by_id(name:, id:, details: "")
  @message = "Destroying #{name.inspect}: #{id}. #{details}"
  puts @message
  @api_rate_limit.call.app.delete(id)
end

#destroy_oldestObject



64
65
66
67
# File 'lib/hatchet/reaper.rb', line 64

def destroy_oldest
  oldest = @hatchet_apps.pop
  destroy_by_id(name: oldest["name"], id: oldest["id"], details: "Hatchet app limit: #{HATCHET_APP_LIMT}")
end

#get_appsObject

Ascending order, oldest is last



21
22
23
24
25
# File 'lib/hatchet/reaper.rb', line 21

def get_apps
  apps          = @api_rate_limit.call.app.list.sort_by { |app| DateTime.parse(app["created_at"]) }.reverse
  @app_count    = apps.count
  @hatchet_apps = apps.select {|app| app["name"].match(@regex) }
end