vominator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/.rspec +5 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +116 -0
  7. data/Rakefile +12 -0
  8. data/bin/vominate +12 -0
  9. data/circle.yml +5 -0
  10. data/lib/ec2.rb +12 -0
  11. data/lib/ec2/instances.rb +362 -0
  12. data/lib/ec2/security_groups.rb +314 -0
  13. data/lib/ec2/ssm.rb +81 -0
  14. data/lib/vominator/aws.rb +15 -0
  15. data/lib/vominator/constants.rb +53 -0
  16. data/lib/vominator/ec2.rb +308 -0
  17. data/lib/vominator/instances.rb +34 -0
  18. data/lib/vominator/route53.rb +125 -0
  19. data/lib/vominator/security_groups.rb +26 -0
  20. data/lib/vominator/ssm.rb +57 -0
  21. data/lib/vominator/version.rb +3 -0
  22. data/lib/vominator/vominator.rb +74 -0
  23. data/lib/vominator/vpc.rb +82 -0
  24. data/lib/vpc.rb +8 -0
  25. data/lib/vpc/create.rb +188 -0
  26. data/spec/lib/instances_spec.rb +2 -0
  27. data/spec/lib/vominator/aws_spec.rb +6 -0
  28. data/spec/lib/vominator/ec2_spec.rb +783 -0
  29. data/spec/lib/vominator/instances_spec.rb +96 -0
  30. data/spec/lib/vominator/route53_spec.rb +64 -0
  31. data/spec/lib/vominator/ssm_spec.rb +95 -0
  32. data/spec/lib/vominator/vominator_spec.rb +209 -0
  33. data/spec/spec_helper.rb +103 -0
  34. data/spec/support/matchers/exit_with_code.rb +24 -0
  35. data/test/puke/cloud-configs/.gitkeep +0 -0
  36. data/test/puke/cloud-configs/cloud-config-example.erb +63 -0
  37. data/test/puke/config.yaml +16 -0
  38. data/test/puke/products/sample-api/instances.yaml +37 -0
  39. data/test/puke/products/sample-api/security_groups.yaml +19 -0
  40. data/test/vominator.yaml +7 -0
  41. data/vominator.gemspec +34 -0
  42. metadata +259 -0
@@ -0,0 +1,308 @@
1
+ require 'aws-sdk'
2
+ require 'base64'
3
+ require_relative 'constants'
4
+
5
+ module Vominator
6
+ class EC2
7
+ def self.get_virt_type(instance_type)
8
+ begin
9
+ return EC2_INSTANCE_METADATA[instance_type.to_sym][:virtualization_type]
10
+ rescue NoMethodError
11
+ raise ArgumentError, 'You must specify a valid instance type'
12
+ end
13
+ end
14
+
15
+ def self.get_ephemeral_dev_count(instance_type)
16
+ return EC2_INSTANCE_METADATA[instance_type.to_sym][:ephemeral_devices]
17
+ end
18
+
19
+ def self.get_instances(resource)
20
+ instances = Hash.new
21
+ resource.instances.each do |instance|
22
+ instances[instance.private_ip_address] = {:instance_id => instance.id, :security_groups => instance.security_groups.map { |sg| sg.group_name}}
23
+ end
24
+ return instances
25
+ end
26
+
27
+ def self.get_instance(resource, instance_id)
28
+ return resource.instances(filters: [{name: 'instance-id', values: [instance_id]}]).first
29
+ end
30
+
31
+ def self.get_security_group_name_ids_hash(resource, vpc_id)
32
+ security_groups = Hash.new
33
+ resource.vpcs(filters: [{name: 'vpc-id', values: [vpc_id]}]).first.security_groups.each do |security_group|
34
+ security_groups[security_group.group_name] = security_group.id
35
+ end
36
+ return security_groups
37
+ end
38
+
39
+ def self.get_subnets(resource, vpc_id)
40
+ subnets = Hash.new
41
+ resource.vpcs(filters: [{name: 'vpc-id', values: [vpc_id]}]).first.subnets.each do |subnet|
42
+ subnets[subnet.cidr_block] = subnet
43
+ end
44
+ return subnets
45
+ end
46
+
47
+ def self.get_ami(puke_config, instance_type, family)
48
+ if Vominator::EC2.get_virt_type(instance_type) == 'hvm'
49
+ ami = puke_config['linux_hvm_base_image'] if family == 'linux'
50
+ ami = puke_config['windows_hvm_base_image'] if family == 'windows'
51
+ else
52
+ ami = puke_config['linux_paravirtual_base_image'] if family == 'linux'
53
+ ami = puke_config['windows_paravirtual_base_image'] if family == 'windows'
54
+ end
55
+ return ami
56
+ end
57
+
58
+ def self.create_subnet(resource, subnet, az, vpc_id, route_table_id=nil)
59
+ subnet = resource.vpcs(filters: [{name: 'vpc-id', values: [vpc_id]}]).first.create_subnet(:cidr_block => subnet, :availability_zone => az)
60
+ if route_table_id
61
+ route_table = Vominator::EC2.get_route_table(resource,route_table_id)
62
+ route_table.associate_with_subnet(subnet_id: subnet.subnet_id)
63
+ end
64
+ return subnet
65
+ end
66
+
67
+ def self.get_route_table(resource, route_table_id)
68
+ return resource.route_tables(filters: [{name: 'route-table-id', values: [route_table_id]}]).first
69
+ end
70
+
71
+ def self.get_termination_protection(client, instance_id)
72
+ return client.describe_instance_attribute(:instance_id => instance_id, :attribute => 'disableApiTermination').disable_api_termination.value
73
+ end
74
+
75
+ def self.set_termination_protection(client, instance_id, state)
76
+ client.modify_instance_attribute(:instance_id => instance_id, :disable_api_termination => { :value => state })
77
+ return client.describe_instance_attribute(:instance_id => instance_id, :attribute => 'disableApiTermination').disable_api_termination.value
78
+ end
79
+
80
+ def self.get_instance_state(resource, instance_id)
81
+ instance = Vominator::EC2.get_instance(resource,instance_id)
82
+
83
+ return instance.state.name
84
+ end
85
+
86
+ def self.set_instance_type(resource, instance_id, type, fqdn)
87
+ instance = Vominator::EC2.get_instance(resource,instance_id)
88
+ instance.stop
89
+
90
+ # TODO: Add a timed break?
91
+ sleep 5 until Vominator::EC2.get_instance_state(resource, instance_id) == 'stopped'
92
+ instance.modify_attribute(:attribute => 'instanceType', :value => type)
93
+
94
+ # TODO: We should add in a sleep (with a timed break) and verify the instance goes back to running
95
+ begin
96
+ instance.start
97
+ rescue Aws::EC2::Errors::Unsupported
98
+ LOGGER.warning("Disabling EBS Optimization for #{fqdn} because #{type} does not support this functionality")
99
+ instance.ebs_optimized = false
100
+ instance.start
101
+ end
102
+ return Vominator::EC2.get_instance(resource,instance_id).instance_type
103
+ end
104
+
105
+
106
+ def self.allocate_public_ip(client, domain='vpc')
107
+ return client.allocate_address(domain: domain)
108
+ end
109
+
110
+ def self.assign_public_ip(client, instance_id)
111
+ eip = client.allocate_address(:domain => 'vpc')
112
+ tries ||= 3
113
+ begin
114
+ sleep(0.25)
115
+ association = client.associate_address(:instance_id => instance_id, :allocation_id => eip.allocation_id)
116
+ rescue Aws::EC2::Errors::InvalidAllocationIDNotFound
117
+ retry unless (tries -= 1).zero?
118
+ end
119
+
120
+ if association
121
+ return eip.public_ip
122
+ else
123
+ return nil
124
+ end
125
+ end
126
+
127
+ def self.remove_public_ip(client, instance_id)
128
+ allocation_id = client.describe_addresses(filters: [{name: 'instance-id', values: [instance_id]}]).first['addresses'].first['association_id']
129
+ if client.disassociate_address(:association_id => allocation_id)
130
+ return true
131
+ else
132
+ return false
133
+ end
134
+ end
135
+
136
+ def self.set_ebs_optimized(resource, instance_id, state, fqdn)
137
+ instance = Vominator::EC2.get_instance(resource,instance_id)
138
+ instance.stop
139
+
140
+ # TODO: Add a timed break?
141
+ sleep 5 until Vominator::EC2.get_instance_state(resource, instance_id) == 'stopped'
142
+
143
+ # TODO: We should add in a sleep (with a timed break) and verify the instance goes back to running
144
+ begin
145
+ instance.modify_attribute(:ebs_optimized => {:value => state})
146
+ instance.start
147
+ return state
148
+ rescue Aws::EC2::Errors::Unsupported
149
+ LOGGER.error("#{fqdn} does not support setting EBS Optimization to #{state} as this is not supported for #{instance.instance_type}")
150
+ instance.modify_attribute(:ebs_optimized => {:value => false})
151
+ instance.start
152
+ return instance.ebs_optimized
153
+ end
154
+ end
155
+
156
+ def self.set_source_dest_check(resource, instance_id, state)
157
+ instance = Vominator::EC2.get_instance(resource,instance_id)
158
+ if instance.modify_attribute(:source_dest_check => {:value => state})
159
+ return state
160
+ end
161
+ end
162
+
163
+ def self.set_security_groups(resource, instance_id, security_groups, vpc_security_groups, append=true)
164
+ instance = Vominator::EC2.get_instance(resource,instance_id)
165
+ if append
166
+ security_groups = security_groups + instance.security_groups.map {|sg| sg[:group_name]}
167
+ end
168
+ group_ids = security_groups.map { |sg| vpc_security_groups[sg] }
169
+ instance.modify_attribute(:groups => group_ids.compact)
170
+ return Vominator::EC2.get_instance(resource,instance_id).security_groups.map {|sg| sg[:group_name]}
171
+ end
172
+
173
+ def self.get_ebs_volume(resource, ebs_volume_id)
174
+ return resource.volumes(filters: [{name: 'volume-id', values: [ebs_volume_id]}]).first
175
+ end
176
+
177
+ def self.get_instance_ebs_volumes(resource, instance_id)
178
+ instance = Vominator::EC2.get_instance(resource,instance_id)
179
+ return instance.block_device_mappings.map {|vol| vol[:device_name]}
180
+ end
181
+
182
+ def self.add_ebs_volume(resource, instance_id, volume_type, volume_size, mount_point, iops=false, encrypted=false)
183
+ instance = Vominator::EC2.get_instance(resource,instance_id)
184
+ availability_zone = instance.placement[:availability_zone]
185
+
186
+ case volume_type
187
+ when 'magnetic'
188
+ volume = resource.create_volume(:availability_zone => availability_zone, :volume_type => 'standard', :size => volume_size, :encrypted => encrypted)
189
+ when 'gp'
190
+ volume = resource.create_volume(:availability_zone => availability_zone, :volume_type => 'gp2', :size => volume_size, :encrypted => encrypted)
191
+ when 'piops'
192
+ iops = volume_size * 15 unless iops
193
+ volume = resource.create_volume(:availability_zone => availability_zone, :volume_type => 'io1', :size => volume_size, :iops => iops, :encrypted => encrypted)
194
+ else
195
+ volume = nil
196
+ LOGGER.fatal("#{volume_type} is unsupported")
197
+ end
198
+ LOGGER.info("Waiting for #{volume.id} to be provisioned and become available")
199
+
200
+ sleep 3 until Vominator::EC2.get_ebs_volume(resource, volume.id).state == 'available'
201
+
202
+ LOGGER.info("Attaching #{volume.id} to the instance and waiting for it to be attached.")
203
+ instance.attach_volume(:device => mount_point, :volume_id => volume.id)
204
+ sleep 3 until Vominator::EC2.get_ebs_volume(resource, volume.id).state == 'in-use'
205
+
206
+ return volume
207
+ end
208
+
209
+ def self.get_ephemeral_devices(instance_type)
210
+ device_count = Vominator::EC2.get_ephemeral_dev_count(instance_type)
211
+ devices = Hash.new
212
+ for i in 1..device_count
213
+ mount_point = (65 + (i)).chr.downcase
214
+ devices["/dev/sd#{mount_point}"] = "ephemeral#{i-1}"
215
+ end
216
+ return devices
217
+ end
218
+
219
+ def self.create_instance(resource, hostname, environment, ami_id, subnet_id, instance_type, key_name, private_ip_address, az, security_group_ids, user_data, ebs_optimized, iam_profile)
220
+ begin
221
+ LOGGER.info("Creating instance for #{hostname}.#{environment}")
222
+ instance = resource.create_instances(:min_count => 1, :max_count => 1, :image_id => ami_id, :subnet_id => subnet_id, :instance_type => instance_type, :key_name => key_name, :private_ip_address => private_ip_address, :placement => {:availability_zone => az}, :security_group_ids => security_group_ids, :user_data => Base64.encode64(user_data), :ebs_optimized => ebs_optimized, :iam_instance_profile => {:name => iam_profile}).first
223
+ rescue Aws::EC2::Errors::InvalidIPAddressInUse
224
+ LOGGER.fatal("Unable to create the instance as #{private_ip_address} is in use")
225
+ rescue Aws::EC2::Errors::InternalError
226
+ LOGGER.fatal("Unable to create instance due to an AWS internal server error. Try again in a bit")
227
+ end
228
+ LOGGER.info('Waiting for the instance to come online.')
229
+ sleep 10
230
+ sleep 2 until Vominator::EC2.get_instance_state(resource, instance.id) != 'pending'
231
+
232
+ instance.create_tags(:tags => [{:key => 'Name', :value => "#{hostname}.#{environment}"},{:key => 'Environment', :value => environment},{:key => 'Provisioned_By', :value => 'Vominator'}])
233
+
234
+ if Vominator::EC2.get_instance_state(resource, instance.id) == 'running'
235
+ return instance
236
+ else
237
+ return nil
238
+ end
239
+ end
240
+
241
+ def self.terminate_instance(resource, instance_id)
242
+ instance = Vominator::EC2.get_instance(resource,instance_id)
243
+ instance.terminate
244
+ sleep 2 until Vominator::EC2.get_instance_state(resource, instance.id) == 'terminated'
245
+ return true
246
+ end
247
+
248
+ def self.tag_resource(client, resource_id, tags)
249
+ client.create_tags(resources: [resource_id], tags: tags)
250
+ end
251
+
252
+ def self.get_security_groups(client, vpc_id)
253
+ return client.describe_security_groups(filters: [{name: 'vpc-id', values: [vpc_id]}]).first.security_groups
254
+ end
255
+
256
+ def self.create_security_group(client, name, vpc_id, description=nil)
257
+ return client.create_security_group(group_name: name, description: description || name, vpc_id: vpc_id)
258
+ end
259
+
260
+ def self.create_security_group_rule(client,type,group_id,rule)
261
+ case type
262
+ when 'ingress'
263
+ if rule[:source_security_group_id]
264
+ client.authorize_security_group_ingress({group_id: group_id, ip_permissions: [{ip_protocol: rule[:ip_protocol], from_port: rule[:from_port], to_port: rule[:to_port], user_id_group_pairs: [{group_id: rule[:source_security_group_id]}]}]})
265
+ end
266
+
267
+ if rule[:cidr_ip]
268
+ client.authorize_security_group_ingress({group_id: group_id, cidr_ip: rule[:cidr_ip], ip_protocol: rule[:ip_protocol], from_port: rule[:from_port], to_port: rule[:to_port]})
269
+ end
270
+
271
+ when 'egress'
272
+ if rule[:source_security_group_id]
273
+ client.authorize_security_group_egress({group_id: group_id, ip_permissions: [{ip_protocol: rule[:ip_protocol], from_port: rule[:from_port], to_port: rule[:to_port], user_id_group_pairs: [{group_id: rule[:source_security_group_id]}]}]})
274
+ end
275
+
276
+ if rule[:cidr_ip]
277
+ client.authorize_security_group_egress({group_id: group_id, cidr_ip: rule[:cidr_ip], ip_protocol: rule[:ip_protocol], from_port: rule[:from_port], to_port: rule[:to_port]})
278
+ end
279
+ else
280
+ return false
281
+ end
282
+ end
283
+
284
+ def self.delete_security_group_rule(client,type,group_id,rule)
285
+ case type
286
+ when 'ingress'
287
+ if rule[:source_security_group_id]
288
+ client.revoke_security_group_ingress({group_id: group_id, ip_permissions: [{ip_protocol: rule[:ip_protocol], from_port: rule[:from_port], to_port: rule[:to_port], user_id_group_pairs: [{group_id: rule[:source_security_group_id]}]}]})
289
+ end
290
+
291
+ if rule[:cidr_ip]
292
+ client.revoke_security_group_ingress({group_id: group_id, ip_permissions: [{ip_protocol: rule[:ip_protocol], from_port: rule[:from_port], to_port: rule[:to_port], ip_ranges: [{cidr_ip: rule[:cidr_ip]}]}]})
293
+ end
294
+
295
+ when 'egress'
296
+ if rule[:source_security_group_id]
297
+ client.revoke_security_group_egress({group_id: group_id, ip_permissions: [{ip_protocol: rule[:ip_protocol], from_port: rule[:from_port], to_port: rule[:to_port], user_id_group_pairs: [{group_id: rule[:source_security_group_id]}]}]})
298
+ end
299
+
300
+ if rule[:cidr_ip]
301
+ client.revoke_security_group_egress({group_id: group_id, ip_permissions: [{ip_protocol: rule[:ip_protocol], from_port: rule[:from_port], to_port: rule[:to_port], ip_ranges: [{cidr_ip: rule[:cidr_ip]}]}]})
302
+ end
303
+ else
304
+ return false
305
+ end
306
+ end
307
+ end
308
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'constants'
2
+ require 'erubis'
3
+
4
+ module Vominator
5
+ class Instances
6
+ def self.get_instances(environment, product, filter=false)
7
+ if PUKE_CONFIG[environment]['products'].include? product
8
+ config_file = File.expand_path("#{VOMINATOR_CONFIG['configuration_path']}/products/#{product}/instances.yaml")
9
+ if filter
10
+ instances = Array.new
11
+ YAML.load(File.read(config_file)).each do |instance|
12
+ if filter.include? instance.keys[0]
13
+ instances.push instance
14
+ end
15
+ end
16
+ else
17
+ instances = YAML.load(File.read(config_file))
18
+ end
19
+ return instances if instances.kind_of?(Array)
20
+ end
21
+ end
22
+
23
+ def self.generate_cloud_config(hostname, environment, family, roles, recipes)
24
+ template = "#{family}_cloud_config_template"
25
+ begin
26
+ cloud_config_template = File.read("#{VOMINATOR_CONFIG['configuration_path']}/cloud-configs/#{PUKE_CONFIG[environment][template]}")
27
+ rescue Errno::EISDIR
28
+ LOGGER.fatal("Unable to find #{template} in your cloud-config directory. Check that this file exists in #{VOMINATOR_CONFIG['configuration_path']}/cloud-configs/")
29
+ end
30
+ cloud_config = Erubis::Eruby.new(cloud_config_template)
31
+ return cloud_config.result(:hostname => hostname, :env => environment, :roles => roles, :recipes => recipes)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,125 @@
1
+ require 'aws-sdk'
2
+ require_relative 'constants'
3
+
4
+ module Vominator
5
+ class Route53
6
+ def self.get_zone_by_id(client, zone_id)
7
+ return client.get_hosted_zone(id: zone_id).hosted_zone
8
+ end
9
+
10
+ def self.get_zone_by_domain(client, domain_name)
11
+ zones = client.list_hosted_zones_by_name(dns_name: domain_name).hosted_zones
12
+ zone = nil
13
+ zones.each do |z|
14
+ zone = z if z.name == "#{domain_name}."
15
+ end
16
+
17
+ return zone
18
+ end
19
+
20
+ def self.create_zone(client, domain_name)
21
+ return client.create_hosted_zone(name: domain_name, caller_reference: "#{domain_name} + #{Time.now.to_i}")
22
+ end
23
+
24
+ def self.get_records(client, zone, max_items=50)
25
+ resp = client.list_resource_record_sets(:hosted_zone_id => zone, :max_items => max_items)
26
+ records = resp[:resource_record_sets]
27
+ while resp[:is_truncated]
28
+ resp = client.list_resource_record_sets(:hosted_zone_id => zone, :max_items => max_items, :start_record_name => resp[:next_record_name])
29
+ records += resp[:resource_record_sets]
30
+ end
31
+ names = Array.new
32
+ records.each do |record|
33
+ names.push record[:name]
34
+ end
35
+ return names
36
+ end
37
+
38
+ def self.create_nameserver_records(client, zone, fqdn, nameservers, ttl=172800)
39
+ resource_records = Array.new
40
+ nameservers.each do |nameserver|
41
+ hash = Hash.new
42
+ hash[:value] = nameserver
43
+ resource_records.push hash
44
+ end
45
+
46
+ resp = client.change_resource_record_sets(
47
+ :hosted_zone_id => zone,
48
+ :change_batch => {
49
+ :changes => [
50
+ {
51
+ :action => "CREATE",
52
+ :resource_record_set => {
53
+ :name => fqdn,
54
+ :type => 'NS',
55
+ :ttl => ttl,
56
+ :resource_records => resource_records
57
+ }
58
+ }
59
+ ]
60
+ }
61
+ )
62
+
63
+ if resp.change_info.status == 'PENDING'
64
+ return true
65
+ else
66
+ return false
67
+ end
68
+ end
69
+
70
+ def self.create_record(client, zone, fqdn, ip, type='A', ttl=600)
71
+ resp = client.change_resource_record_sets(
72
+ :hosted_zone_id => "/hostedzone/#{zone}",
73
+ :change_batch => {
74
+ :changes => [
75
+ {
76
+ :action => "CREATE",
77
+ :resource_record_set => {
78
+ :name => "#{fqdn}.",
79
+ :type => type,
80
+ :ttl => ttl,
81
+ :resource_records => [{
82
+ :value => "#{ip}"
83
+ }]
84
+ }
85
+ }
86
+ ]
87
+ }
88
+ )
89
+
90
+ if resp.change_info.status == 'PENDING'
91
+ return true
92
+ else
93
+ return false
94
+ end
95
+ end
96
+
97
+ def self.delete_record(client, zone, fqdn, ip, type='A', ttl=600)
98
+ resp = client.change_resource_record_sets(
99
+ :hosted_zone_id => "/hostedzone/#{zone}",
100
+ :change_batch => {
101
+ :changes => [
102
+ {
103
+ :action => "DELETE",
104
+ :resource_record_set => {
105
+ :name => "#{fqdn}.",
106
+ :type => type,
107
+ :ttl => ttl,
108
+ :resource_records => [{
109
+ :value => ip
110
+ }]
111
+ }
112
+
113
+ }
114
+ ]
115
+ }
116
+ )
117
+ if resp.change_info.status == 'PENDING'
118
+ return true
119
+ else
120
+ return false
121
+ end
122
+ end
123
+
124
+ end
125
+ end