Class: FogPrune

Inherits:
Object
  • Object
show all
Defined in:
lib/fog-prune.rb,
lib/fog-prune/prune.rb,
lib/fog-prune/config.rb,
lib/fog-prune/options.rb,
lib/fog-prune/version.rb

Defined Under Namespace

Classes: Config, Options

Constant Summary collapse

QUERY_ROWS =
1000
PROVIDER_ALIASES =
Mash.new(
  :ec2 => 'aws'
)
VERSION =
Gem::Version.new('0.1.2')

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeFogPrune

Returns a new instance of FogPrune.



18
19
20
21
22
# File 'lib/fog-prune/prune.rb', line 18

def initialize
  Chef::Knife.new.configure_chef
  @compute = Mash.new
  @ui = Chef::Knife::UI.new(STDOUT, STDERR, STDIN, {})
end

Instance Attribute Details

#uiObject (readonly)

Returns the value of attribute ui.



16
17
18
# File 'lib/fog-prune/prune.rb', line 16

def ui
  @ui
end

Instance Method Details

#chef?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/fog-prune/prune.rb', line 51

def chef?
  Config[:prune].include?('chef')
end

#collect_search(query) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/fog-prune/prune.rb', line 90

def collect_search(query)
  new_query = ['name:*', Config[:filter]].compact.join(' AND ')
  new_query << ' ' << query
  debug "Running search with query: #{new_query.inspect}"
  result = []
  idx = 0
  while((new_items = Chef::Search::Query.new.search(:node, new_query, 'X_CHEF_id_CHEF_X asc', QUERY_ROWS * idx, QUERY_ROWS).first).size == QUERY_ROWS)
    debug "Fetched #{new_items.size} new items from server"
    result += new_items
    idx += 1
  end
  debug "Adding last fetched items (#{new_items.size})"
  result += new_items
  result
end

#compute(provider) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/fog-prune/prune.rb', line 24

def compute(provider)
  p_key = PROVIDER_ALIASES[provider] || provider
  unless(@compute[p_key])
    raise "No Fog credentials provided for #{provider}!" unless Config[:fog][p_key]
    @compute[p_key] = []
    @compute[p_key] = Config[:fog][p_key].map do |args|
      Fog::Compute.new(
        format_fog_hash(args.merge(:provider => p_key))
      )
    end
  end
  @compute[p_key]
end

#debug(msg) ⇒ Object



204
205
206
# File 'lib/fog-prune/prune.rb', line 204

def debug(msg)
  ui.info "#{ui.color('[DEBUG]', :magenta)} #{msg}" if Config[:debug]
end

#discover_prunable_nodesObject



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/fog-prune/prune.rb', line 73

def discover_prunable_nodes
  if(Config[:nodes])
    query = Array(Config[:nodes]).flatten.map do |name|
      "name:#{name}"
    end.join(' OR ')
  else
    max_ohai_time = Time.now.to_f - Config[:chef_converge_every].to_f
    query = ["ohai_time:[0.0 TO #{max_ohai_time}]"]
    if(Config[:stale_nodes])
      max_prune_time = Time.now.to_f - Config[:stale_node_timeout].to_f
      query << ["(prune_tag_time:[0.0 TO #{max_prune_time}] NOT ohai_time:[* TO *])"]
      query = ["(#{query.join(' OR ')})"]
    end
  end
  collect_search('AND ' + query.join(' AND '))
end

#ec2_check(node) ⇒ Object

Checks



163
164
165
166
167
168
169
170
171
# File 'lib/fog-prune/prune.rb', line 163

def ec2_check(node)
  aws_node = ec2_nodes.detect{|n| n.id == node.ec2.instance_id}
  unless(aws_node)
    debug "#{node.name} returned nil from aws"
  else
    debug "#{node.name} state on aws: #{aws_node.state}"
  end
  aws_node.nil? || aws_node.state == 'terminated'
end

#ec2_nodesObject



173
174
175
176
177
178
179
180
# File 'lib/fog-prune/prune.rb', line 173

def ec2_nodes
  unless(@ec2_nodes)
    @ec2_nodes = compute(:aws).map do |compute_con|
      compute_con.servers.all
    end.flatten
  end
  @ec2_nodes
end

#filter_prunables_via_fog(nodes_to_prune) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/fog-prune/prune.rb', line 149

def filter_prunables_via_fog(nodes_to_prune)
  nodes_to_prune.map do |node|
    if(node[:cloud] && node.cloud.provider)
      if(respond_to?(check_method = "#{node.cloud.provider}_check"))
        send(check_method, node) ? node : nil
      end
    elsif(node[:prune_tag_time] && node[:ohai_time].nil?)
      node
    end
  end.compact
end

#format_fog_hash(hash) ⇒ Object



38
39
40
41
42
43
44
# File 'lib/fog-prune/prune.rb', line 38

def format_fog_hash(hash)
  new_hash = {}
  hash.each do |k,v|
    new_hash[k.to_sym] = v
  end
  new_hash
end

#prune!Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/fog-prune/prune.rb', line 55

def prune!
  tag_stale_nodes if Config[:tag_stale_nodes]
  ui.info "Starting node pruning..."
  ui.warn "Pruning from: #{Config[:prune].join(', ')}"
  nodes_to_prune = discover_prunable_nodes
  debug "Initial nodes discovered: #{nodes_to_prune.map(&:name).sort.join(', ')}"
  debug "Initial node count: #{nodes_to_prune.size}"
  nodes_to_prune = filter_prunables_via_fog(nodes_to_prune)
  ui.warn "Nodes to prune: #{nodes_to_prune.size}"
  debug "#{nodes_to_prune.map(&:name).sort.join(', ')}"
  unless(Config[:print_only])
    ui.confirm('Destroy these nodes')
    nodes_to_prune.each do |node|
      prune_node(node)
    end
  end
end

#prune_chef(node) ⇒ Object



143
144
145
146
147
# File 'lib/fog-prune/prune.rb', line 143

def prune_chef(node)
  debug "Pruning node from chef server: #{node.name}"
  node.destroy
  Chef::ApiClient.load(node.name).destroy
end

#prune_node(node) ⇒ Object



121
122
123
124
# File 'lib/fog-prune/prune.rb', line 121

def prune_node(node)
  prune_sensu(node) if sensu?
  prune_chef(node) if chef?
end

#prune_sensu(node) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/fog-prune/prune.rb', line 126

def prune_sensu(node)
  debug "Pruning node from sensu server: #{node.name}"
  url = "http://#{args[:sensu][:host]}:#{args[:sensu][:port]}" <<
    "/clients/#{node.name}"
  begin
    timeout(30) do
      RestClient::Resource.new(
        api_url,
        :user => args[:sensu][:username],
        :password => args[:sensu][:password]
      ).delete
    end
  rescue => e
    ui.error "Failed to remove #{node.name} - Unexpected error: #{e}"
  end
end

#rackspace_check(node) ⇒ Object



182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/fog-prune/prune.rb', line 182

def rackspace_check(node)
  rackspace_node = rackspace_nodes.detect do |n|
    n.name == node.name &&
      n.ipv4_address == node[:rackspace][:public_ipv4]
  end
  unless(rackspace_node)
    debug "#{node.name} returned nil from rackspace"
  else
    debug "#{node.name} state on rackspace: #{rackspace_node.state}"
  end
  rackspace_node.nil? || rackspace_node.state == 'DELETED'
end

#rackspace_nodesObject



195
196
197
198
199
200
201
202
# File 'lib/fog-prune/prune.rb', line 195

def rackspace_nodes
  unless(@rackspace_nodes)
    @rackspace_nodes = compute(:rackspace).map do |compute_con|
      compute_con.servers.all
    end.flatten
  end
  @rackspace_nodes
end

#sensu?Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/fog-prune/prune.rb', line 46

def sensu?
  Config[:prune].include?('sensu')
end

#tag_stale_nodesObject



106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/fog-prune/prune.rb', line 106

def tag_stale_nodes
  query = '-ohai_time:[* TO *] -prune_tag_time:[* TO *]'
  nodes_to_tag = collect_search(query)
  ui.info "Tagging nodes with no ohai_time set. (#{nodes_to_tag.size} nodes)"
  debug "Nodes to be tagged: #{nodes_to_tag.map(&:name).sort.join(', ')}"
  unless(Config[:print_only])
    nodes_to_tag.each do |node|
      node.set[:prune_tag_time] = Time.now.to_f
      node.save
    end
  else
    ui.warn 'Skipping node tagging due to print only restriction'
  end
end