Class: Furnish::Provisioner::SecurityGroup

Inherits:
AWS
  • Object
show all
Defined in:
lib/furnish/provisioners/security_group.rb

Overview

Provision an EC2 Security Group. Currently receives no input and yields the group id created as output.

See the attributes and constructor for more information about creating this provisioner.

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from AWS

#access_key, #check_aws_settings, #check_region, #ec2, #region, #secret_key

Constructor Details

#initialize(args) ⇒ SecurityGroup

Construct a new security group provisioner.

Example:

obj = Furnish::Provisioners::SecurityGroup.new(
  :access_key => "foo",
  :secret_key => "bar",
  :region => "my-region"
)

See Furnish::Provisioner::AWS for how constructor arguments and attributes interact.



139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/furnish/provisioners/security_group.rb', line 139

def initialize(args)
  super
  check_region

  @ingress        ||= { :authorize => [], :revoke => [] }
  @egress         ||= { :authorize => [], :revoke => [] }
  @allow_ping     ||= []
  @disallow_ping  ||= []
  @group_name     ||= :auto
  @group_prefix   ||= "furnish-auto-"
  @kill_instances   = args.has_key?(:kill_instances) ? args[:kill_instances] : true
  @group_id         = nil
end

Instance Attribute Details

#group_idObject (readonly)

the group_id as returned from EC2, only set after provisioning has occurred.



123
124
125
# File 'lib/furnish/provisioners/security_group.rb', line 123

def group_id
  @group_id
end

Instance Method Details

#allow_pingObject

:attr: allow_ping

If true, allow ping from all hosts. If an array of strings, allow ping from that set of CIDR networks.



109
110
# File 'lib/furnish/provisioners/security_group.rb', line 109

furnish_property :allow_ping,
"If true, allow ping from all hosts, if array of strings, treats them as CIDR networks."

#apply_group_rules(group) ⇒ Object

Applies network rules (see #ingress, #egress, #allow_ping, #disallow_ping attributes) to a created security group.

Raises ArgumentError if security groups do not belong to a VPC and egress filters are set.



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/furnish/provisioners/security_group.rb', line 191

def apply_group_rules(group)
  # XXX this meta programs passing the contents set on the object to the
  #     group object.
  [:allow_ping, :disallow_ping].each do |sym|
    if send(sym).kind_of?(Array)
      ary = send(sym)

      if_debug(3) do
        puts "group #{group.id}/#{group.name}: applying #{sym} to #{ary.inspect}"
      end

      group.send(sym, *ary)
    elsif send(sym)
      if_debug(3) do
        puts "group #{group.id}/#{group.name}: applying #{sym} to all instances"
      end

      group.send(sym) # XXX "everybody"
    end
  end

  # XXX this mess is similar to above, but generates the call_name as well.
  # e.g.: "authorize_egress" comes from @egress[:authorize], and each
  # inner array in the data structure is expanded as args in the call.
  [:authorize, :revoke].each do |part|
    [:ingress, :egress].each do |type|
      if send(type) and ary = send(type)[part] and ary.kind_of?(Array) and !ary.empty?
        if type == :egress and !group.vpc?
          raise ArgumentError, "egress filtering was configured for #{group.id}/#{group.name} and it is not a VPC security group"
        end

        ary.each do |rule|
          if_debug(3) do
            puts "group #{group.id}/#{group.name}: applying #{part}_#{type} with values #{rule.inspect}"
          end

          group.send("#{part}_#{type}", *rule.flatten)
        end
      end
    end
  end
end

#descriptionObject

:attr: description

String description of the group. Optional.



46
47
48
# File 'lib/furnish/provisioners/security_group.rb', line 46

furnish_property :description,
"Description of the group. Optional",
String

#disallow_pingObject

:attr: disallow_ping

Inverse of #allow_ping.



117
118
# File 'lib/furnish/provisioners/security_group.rb', line 117

furnish_property :disallow_ping,
"Inverse of :allow_ping."

#egressObject

:attr: egress

Same as #ingress but for egress rules. Note that egress rules only work against groups that belong to a VPCs.



99
100
101
# File 'lib/furnish/provisioners/security_group.rb', line 99

furnish_property :egress,
"Same as :ingress, but only work on VPCs.",
Hash

#find_group_by_name(group_name) ⇒ Object

Searches for a group by its name, returns the AWS::EC2::SecurityGroup instance.



246
247
248
249
# File 'lib/furnish/provisioners/security_group.rb', line 246

def find_group_by_name(group_name)
  return nil unless group_name
  ec2.security_groups.filter('group-name', [group_name.to_s]).first
end

#find_secgroup_running_instances(group) ⇒ Object

Queries the API for all instances, returns any that are not in the terminated state.



238
239
240
# File 'lib/furnish/provisioners/security_group.rb', line 238

def find_secgroup_running_instances(group)
  group.instances.select { |i| i.status != :terminated }
end

#generate_group_nameObject

Generate a group name (only used if :auto is assigned to #group_name). Ensures generated names are not already in use. Returns the value it ends up with.



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/furnish/provisioners/security_group.rb', line 158

def generate_group_name
  unless group_prefix
    raise ArgumentError, "group_prefix must be set when auto-generating security group names"
  end

  tmp_name = nil

  loop do
    tmp_name = group_prefix + (0..rand(10).to_i).map { rand(0..9).to_s }.join("")

    if_debug(3) do
      puts "Seeing if security group name #{tmp_name} is taken"
    end

    break unless find_group_by_name(tmp_name)

    if_debug(3) do
      puts "group exists, waiting to try again"
    end

    sleep 0.3
  end

  return tmp_name
end

#group_nameObject

:attr: group_name

Name of the group. Supply a string to name it explicitly, or :auto (the default). If :auto, will pick a name for you based on #group_prefix and a random string of numbers that do not already correspond to a group.



28
29
# File 'lib/furnish/provisioners/security_group.rb', line 28

furnish_property :group_name,
"Name of the group. Supply a string to name it. Default is :auto -- will pick a name for you and overwrite this accessor."

#group_prefixObject

:attr: group_prefix

If #group_name is :auto, will use this value as a prefix. See #group_name for more information.



37
38
39
# File 'lib/furnish/provisioners/security_group.rb', line 37

furnish_property :group_prefix,
"If :group_name is :auto, will use this value as prefix, and a string of random numbers as the postfix. Default is 'furnish-auto-'",
String

#ingressObject

:attr: ingress

Hash containing two keys: :authorize and :revoke, which each contain array of arrays. Passed to authorize_ingress and revoke_ingress respectively.

Example:

Furnish::Provisioners::SecurityGroup.new(
  :ingress => {
    :authorize => [
      [:tcp, 22]
      [:tcp, (1024..1234)]
      [:tcp, 53, "127.0.0.1/32"]
    ]
  }
)


89
90
91
# File 'lib/furnish/provisioners/security_group.rb', line 89

furnish_property :ingress,
"Hash containing two keys: :authorize and :revoke, which each contain array of arrays. Passed to authorize_ingress and revoke_ingress respectively.",
Hash

#kill_instancesObject

:attr: kill_instances

If true (the default), at shutdown time will terminate any instances that belong to the group provisioned by this provisioner so the security group can be destroyed.



67
68
# File 'lib/furnish/provisioners/security_group.rb', line 67

furnish_property :kill_instances,
"If true (default), on shutdown will force termination of all instances in the security group."

#load_group_for_shutdownObject

Only used during shutdown; returns the group if it exists, otherwise logs and returns false. Intended to isolate idempotent function.



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/furnish/provisioners/security_group.rb', line 255

def load_group_for_shutdown
  unless group_id
    prov_name = furnish_group_name
    this_group_name = group_name

    if_debug(2) do
      puts "group id for #{this_group_name} did not exist during provisioner shutdown for #{prov_name || "unknown"} -- did this get provisioned?"
    end

    return false
  end

  group = ec2.security_groups[group_id]

  unless group and group.exists?
    this_group_name = group_name

    if_debug(2) do
      puts "group #{this_group_name} did not exist during shutdown; not attempting to deprovision it and returning success"
    end

    return false
  end

  return group
end

#reportObject

Report method as required by Furnish: yields the name of the group, it’s group id, and any vpc information if VPC is in use.



374
375
376
377
# File 'lib/furnish/provisioners/security_group.rb', line 374

def report
  vpc_str = vpc ? " vpc_id: #{vpc}" : ""
  ["name: #{group_name}, id: #{group_id}#{vpc_str}"]
end

#shutdown(args = {}) ⇒ Object

Deprovision a security group. If the group does not exist, immediately returns a true result.

If #kill_instances is true, it will terminate (and wait for the termination to succeed) any machines that are using the security group that are not yet terminated.

After deleting the group via the API, it will return true if the group no longer exists.



361
362
363
364
365
366
367
368
# File 'lib/furnish/provisioners/security_group.rb', line 361

def shutdown(args={})
  return { } unless group = load_group_for_shutdown

  terminate_instances_for_shutdown(group)
  group.delete

  return group.exists? ? false : { }
end

#startup(args = {}) ⇒ Object

Provision a security group. Group name generation happens here if #group_name is set to :auto, if the group already exists, it will be used and then rules will be applied to it. Returns (and sets on the object) the group_id as returned by EC2.



321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/furnish/provisioners/security_group.rb', line 321

def startup(args={})
  # FIXME VPC as argument?

  @group_name = generate_group_name if group_name == :auto

  begin
    group =
      ec2.security_groups.create(
        group_name,
        :description => description,
        :vpc => vpc
      )
  rescue ::AWS::EC2::Errors::InvalidGroup::Duplicate => e
    group = find_group_by_name(group_name)
    raise e unless group

    my_name = furnish_group_name # yay instance_eval

    if_debug(1) do
      puts "#{group.id}/#{group.name} already existed during #{my_name || "ungrouped"} provision -- applying rules and continuing"
    end
  end

  apply_group_rules(group)
  @group_id = group.id

  return({ :security_group_ids => (args[:security_group_ids] || []) + [group_id] })
end

#terminate_instances_for_shutdown(group) ⇒ Object

only used during shutdown; if kill_instances is true, terminates all the non-terminated machines in the group so it can be deleted. Will keep trying this until all machines are in a terminated state.



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/furnish/provisioners/security_group.rb', line 287

def terminate_instances_for_shutdown(group)
  running_instances = find_secgroup_running_instances(group)

  if running_instances.count > 0
    if kill_instances
      if_debug(3) do
        puts "group #{group.id}/#{group.name} is flagged to kill instances in its group on deprovision"
      end

      until (instances = find_secgroup_running_instances(group)).empty?
        if_debug(1) do
          puts "Trying to destroy security group #{group.name}, but instances are still bound to it."
          puts instances.map(&:id).inspect
          puts "Terminating instances, sleeping, and trying again."
        end

        instances.each do |i|
          i.terminate rescue nil
        end

        sleep 10
      end
    else
      raise "instances #{running_instances.map(&:id).inspect} are still running for group #{group.id}/#{group.name} and kill_instances is off. Cannot continue."
    end
  end
end

#vpcObject

:attr: vpc

VPC identifier. Optional, has significant effect on how security groups work. See the EC2 and VPC documentation for details.



56
57
58
# File 'lib/furnish/provisioners/security_group.rb', line 56

furnish_property :vpc,
"VPC identifier. Optional, see EC2 and VPC documentation for details.",
String