vominator 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,26 @@
1
+ require 'aws-sdk'
2
+ require_relative 'vominator'
3
+ require_relative 'constants'
4
+
5
+ module Vominator
6
+ class SecurityGroups
7
+
8
+ def self.get_security_groups(environment, product, filter=false)
9
+ if PUKE_CONFIG[environment]['products'].include? product
10
+ config_file = File.expand_path("#{VOMINATOR_CONFIG['configuration_path']}/products/#{product}/security_groups.yaml")
11
+ if filter
12
+ security_groups = Array.new
13
+ YAML.load(File.read(config_file)).each do |security_group|
14
+ if filter.include? security_group.keys[0]
15
+ security_groups.push security_group
16
+ end
17
+ end
18
+ else
19
+ security_groups = YAML.load(File.read(config_file))
20
+ end
21
+ return security_groups if security_groups.kind_of?(Array)
22
+ end
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,57 @@
1
+ require 'aws-sdk'
2
+ require_relative 'constants'
3
+
4
+ module Vominator
5
+ class SSM
6
+
7
+ def self.get_documents(client,max_results=25)
8
+ resp = client.list_documents(:max_results => max_results)
9
+ documents = resp[:document_identifiers]
10
+ while resp[:next_token]
11
+ resp = client.list_documents(:next_token => resp[:next_token])
12
+ documents += resp[:document_identifiers]
13
+ end
14
+
15
+ return documents.map {|doc| doc.name }
16
+ end
17
+
18
+ def self.describe_document(client,name)
19
+ return client.describe_document(:name => name).document
20
+ end
21
+
22
+ def self.get_document(client,name)
23
+ return client.get_document(:name => name)
24
+ end
25
+
26
+ def self.put_document(client,name,data)
27
+ client.create_document(:name => name, :content => data)
28
+ sleep 2 until Vominator::SSM.describe_document(client,name).status == 'active'
29
+
30
+ if Vominator::SSM.describe_document(client,name).status == 'active'
31
+ return true
32
+ else
33
+ return false
34
+ end
35
+ end
36
+
37
+ def self.associated?(client,name,instance_id)
38
+ begin
39
+ client.describe_association(:name => name, :instance_id => instance_id)
40
+ return true
41
+ rescue Aws::SSM::Errors::AssociationDoesNotExist
42
+ return false
43
+ end
44
+ end
45
+
46
+ def self.create_association(client,name,instance_id)
47
+ client.create_association(:name => name, :instance_id => instance_id)
48
+ sleep 2 until Vominator::SSM.associated?(client,name,instance_id)
49
+
50
+ if Vominator::SSM.associated?(client,name,instance_id)
51
+ return true
52
+ else
53
+ return false
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module Vominator
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,74 @@
1
+ require 'yaml'
2
+ require 'colored'
3
+ require 'highline/import'
4
+ require_relative 'version'
5
+
6
+ module Vominator
7
+ def self.get_config(file='~/.vominator.yaml')
8
+ config_file = ENV['VOMINATOR_CONFIG'] || File.expand_path(file)
9
+ if File.exist?(config_file)
10
+ vominator_config = YAML.load(File.read(config_file))
11
+ return vominator_config if vominator_config.kind_of?(Hash)
12
+ else
13
+ LOGGER.fatal("Unable to load vominator configuration file from #{config_file}")
14
+ return false
15
+ end
16
+ end
17
+
18
+ def self.get_puke_config(puke_dir)
19
+ if File.exist?(puke_dir)
20
+ config_file = "#{puke_dir}/config.yaml"
21
+ puke_config = YAML.load(File.read(config_file))
22
+ else
23
+ raise("Unable to open puke configuration at #{puke_dir}")
24
+ end
25
+ return puke_config if puke_config.kind_of?(Hash)
26
+ end
27
+
28
+ def self.get_key_pair(vominator_config)
29
+ return vominator_config['key_pair_name']
30
+ end
31
+
32
+ def self.get_puke_variables(environment)
33
+ data = PUKE_CONFIG[environment]
34
+ return data
35
+ end
36
+
37
+ def self.yesno?(prompt: 'Continue?', default: true)
38
+ a = ''
39
+ s = default ? '[Y/n]' : '[y/N]'
40
+ d = default ? 'y' : 'n'
41
+ until %w[y n].include? a
42
+ a = ask("#{prompt} #{s} ") { |q| q.limit = 1; q.case = :downcase }
43
+ a = d if a.length == 0
44
+ end
45
+ a == 'y'
46
+ end
47
+
48
+ class Logger
49
+ def self.info(message)
50
+ puts message
51
+ end
52
+
53
+ def self.test(message)
54
+ puts message.cyan
55
+ end
56
+
57
+ def self.error(message)
58
+ puts message.red
59
+ end
60
+
61
+ def self.fatal(message)
62
+ puts message.red
63
+ exit(1)
64
+ end
65
+
66
+ def self.success(message)
67
+ puts message.green
68
+ end
69
+
70
+ def self.warning(message)
71
+ puts message.yellow
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,82 @@
1
+ require 'aws-sdk'
2
+ require_relative 'constants'
3
+
4
+ module Vominator
5
+ class VPC
6
+ def self.get_vpc(client, vpc_id)
7
+ return client.describe_vpcs(filters: [{name: 'vpc-id', values: [vpc_id]}]).vpcs.first
8
+ end
9
+ def self.get_vpc_by_cidr(client, cidr_block)
10
+ return client.describe_vpcs(filters: [{name: 'cidr', values: [cidr_block]}]).vpcs.first
11
+ end
12
+
13
+ def self.create_vpc(client, cidr_block, tenancy='default')
14
+ resp = client.create_vpc(:cidr_block => cidr_block, :instance_tenancy => tenancy)
15
+ sleep 2 until Vominator::VPC.get_vpc(client,resp.vpc.vpc_id).state == 'available'
16
+ return resp.vpc
17
+ end
18
+
19
+ def self.get_internet_gateway(client, gateway_id)
20
+ return client.describe_internet_gateways(filters: [{name: 'internet-gateway-id', values: [gateway_id]}]).internet_gateways.first
21
+ end
22
+
23
+ def self.create_internet_gateway(client)
24
+ resp = client.create_internet_gateway
25
+ return resp.internet_gateway
26
+ end
27
+
28
+ def self.attach_internet_gateway(client, gateway_id, vpc_id)
29
+ resp = client.attach_internet_gateway(internet_gateway_id: gateway_id, vpc_id: vpc_id)
30
+ sleep 2 until Vominator::VPC.get_internet_gateway(client, gateway_id).attachments.first.state == 'available'
31
+ return true
32
+ end
33
+
34
+ def self.get_nat_gateway(client, gateway_id)
35
+ return client.describe_nat_gateways(filter: [{name: 'nat-gateway-id', values: [gateway_id]}]).nat_gateways.first
36
+ end
37
+
38
+ def self.create_nat_gateway(client, subnet_id, allocation_id)
39
+ resp = client.create_nat_gateway(subnet_id: subnet_id, allocation_id: allocation_id).nat_gateway
40
+ sleep 2 until Vominator::VPC.get_nat_gateway(client, resp.nat_gateway_id).state == 'available'
41
+ return resp
42
+ end
43
+
44
+ def self.get_route_tables(client, vpc_id)
45
+ return client.describe_route_tables(filters: [{name: 'vpc-id', values: [vpc_id]}]).route_tables
46
+ end
47
+
48
+ def self.get_route_table(client, route_table_id)
49
+ return client.describe_route_tables(filters: [{name: 'route-table-id', values: [route_table_id]}]).route_tables.first
50
+ end
51
+
52
+ def self.create_internet_gateway_route(client, route_table_id, destination_cidr_block, gateway_id)
53
+ return client.create_route(route_table_id: route_table_id, destination_cidr_block: destination_cidr_block, gateway_id: gateway_id)
54
+ end
55
+
56
+ def self.create_nat_gateway_route(client, route_table_id, destination_cidr_block, nat_gateway_id)
57
+ return client.create_route(route_table_id: route_table_id, destination_cidr_block: destination_cidr_block, nat_gateway_id: nat_gateway_id)
58
+ end
59
+
60
+ def self.create_route_table(client, vpc_id)
61
+ resp = client.create_route_table(vpc_id: vpc_id).route_table
62
+ sleep 2 until Vominator::VPC.get_route_table(client, resp.route_table_id)
63
+ return resp
64
+ end
65
+
66
+ def self.get_subnet(client, subnet_id)
67
+ return client.describe_subnets(filters: [{name: 'subnet-id', values: [subnet_id]}]).subnets.first
68
+ end
69
+
70
+ def self.create_subnet(client, vpc_id, cidr_block, availability_zone)
71
+ resp = client.create_subnet(vpc_id: vpc_id, cidr_block: cidr_block, availability_zone: availability_zone).subnet
72
+ sleep 2 until Vominator::VPC.get_subnet(client, resp.subnet_id).state == 'available'
73
+ return resp
74
+ end
75
+
76
+ def self.associate_route_table(client, subnet_id, route_table_id)
77
+ return client.associate_route_table(subnet_id: subnet_id, route_table_id: route_table_id)
78
+ end
79
+
80
+
81
+ end
82
+ end
data/lib/vpc.rb ADDED
@@ -0,0 +1,8 @@
1
+ vominator_module = ARGV[1] || nil
2
+
3
+ case vominator_module
4
+ when 'create'
5
+ require_relative '../lib/vpc/create'
6
+ else
7
+ puts 'Module not found. Currently supported modules are: create'
8
+ end
data/lib/vpc/create.rb ADDED
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+ require 'colored'
4
+ require_relative '../vominator/constants'
5
+ require_relative '../vominator/aws'
6
+ require_relative '../vominator/vpc'
7
+ require_relative '../vominator/ec2'
8
+ require_relative '../vominator/route53'
9
+
10
+ options = {}
11
+
12
+ OptionParser.new do |opts|
13
+ opts.banner = 'Usage: vominate vpc create [options]'.yellow
14
+
15
+ opts.on('-eENVIRONMENT', '--environment ENVIRONMENT', 'REQUIRED: The environment which you want to create a VPC for. IE foo') do |value|
16
+ options[:environment] = value
17
+ end
18
+
19
+ opts.on('--region Region', 'REQUIRED: The AWS Region that you want to create the VPC in. IE us-east-1') do |value|
20
+ options[:region] = value
21
+ end
22
+
23
+ opts.on('--availability-zones AVAILABILITY ZONES', 'OPTIONAL: A comma delimited list of specific availability zones that you want to prepare. If you don\'t specify then we will use all that are available. IE us-east-1c,us-east-1d,us-east-1e') do |value|
24
+ options[:availability_zones] = value
25
+ end
26
+
27
+ opts.on('--parent-domain PARENT DOMAIN', 'REQUIRED: The parent domain name that will be used to create a seperate subdomain zone file for the new environment. IE, if you provide foo.org and your environment as bar, this will yield a new Route 53 zone file called bar.foo.org') do |value|
28
+ options[:parent_domain] = value
29
+ end
30
+
31
+ opts.on('--cidr-block CIDR Block', 'REQUIRED: The network block for the new environment. This must be a /16 and the second octet should be unique for this environment. IE. 10.123.0.0/16') do |value|
32
+ options[:cidr_block] = value
33
+ end
34
+
35
+ #opts.on('-t', '--test', 'OPTIONAL: Test run. Show what would be changed without making any actual changes') do
36
+ # options[:test] = true
37
+ #end
38
+
39
+ #opts.on('-l', '--list', 'OPTIONAL: List out Vominator aware VPCs') do
40
+ # options[:list] = true
41
+ #end
42
+
43
+ opts.on('-d', '--debug', 'OPTIONAL: debug output') do
44
+ options[:debug] = true
45
+ end
46
+
47
+ opts.on_tail(:NONE, '-h', '--help', 'OPTIONAL: Display this screen') do
48
+ puts opts
49
+ exit
50
+ end
51
+
52
+ begin
53
+ opts.parse!
54
+ ## Validate Data Inputs
55
+ throw Exception unless ((options.include? :environment) && (options.include? :region) && (options.include? :parent_domain) && (options.include? :cidr_block)) || options[:list]
56
+ rescue
57
+ puts opts
58
+ exit
59
+ end
60
+ end
61
+
62
+ TEST = options[:test]
63
+ def test?(message)
64
+ LOGGER.test(message) if TEST
65
+ TEST
66
+ end
67
+
68
+ unless test?('Vominator is running in test mode. It will NOT make any changes.')
69
+ LOGGER.warning('WARNING: Vominator will make changes to your environment. Please run test mode first if you are unsure.')
70
+ unless Vominator.yesno?(prompt: 'Do you wish to proceed?', default: false)
71
+ exit(1)
72
+ end
73
+ end
74
+
75
+ puke_config = Vominator.get_puke_variables(options[:environment])
76
+
77
+ if puke_config
78
+ LOGGER.fatal("An environment with the name of #{options[:environment]} is already defined. Please choose a different name")
79
+ else
80
+ puke_config = Hash.new
81
+ puke_config['region_name'] = options[:region]
82
+ end
83
+
84
+ ec2_client = Aws::EC2::Client.new(region: puke_config['region_name'])
85
+ r53_client = Aws::Route53::Client.new(region: puke_config['region_name'])
86
+
87
+
88
+ fqdn = "#{options[:environment]}.#{options[:parent_domain]}"
89
+
90
+ # Couple of sanity checks before we get started....
91
+ if Vominator::VPC.get_vpc_by_cidr(ec2_client, options[:cidr_block])
92
+ LOGGER.warning("A VPC already exists with a netblock of #{options[:cidr_block]}. Generally you want these to be unique")
93
+ unless Vominator.yesno?(prompt: 'Do you wish to proceed?', default: false)
94
+ exit(1)
95
+ end
96
+ end
97
+
98
+ if Vominator::Route53.get_zone_by_domain(r53_client, fqdn)
99
+ LOGGER.fatal("A Zonefile already exists for #{fqdn}. Please choose a different environment name")
100
+ end
101
+
102
+ if options[:availability_zones]
103
+ availability_zones = options[:availability_zones].split(',')
104
+ else
105
+ availability_zones = Vominator::AWS.get_availability_zones(ec2_client)
106
+ end
107
+
108
+ parent_zone = Vominator::Route53.get_zone_by_domain(r53_client, options[:parent_domain])
109
+
110
+ unless parent_zone
111
+ LOGGER.warning("We could not find the parent zone of #{options[:parent_domain]}. You can proceed and we will provide the settings so you can manually create the entry.")
112
+ unless Vominator.yesno?(prompt: 'Do you wish to proceed?', default: false)
113
+ exit(1)
114
+ end
115
+ end
116
+
117
+
118
+ LOGGER.info("Starting the creation process. This may take a couple of minutes")
119
+
120
+ environment_zone = Vominator::Route53.create_zone(r53_client, fqdn)
121
+
122
+ if parent_zone
123
+ Vominator::Route53.create_nameserver_records(r53_client,parent_zone.id, fqdn, environment_zone.delegation_set.name_servers)
124
+ end
125
+
126
+ vpc = Vominator::VPC.create_vpc(ec2_client,options[:cidr_block])
127
+
128
+ gateway = Vominator::VPC.create_internet_gateway(ec2_client)
129
+
130
+ Vominator::VPC.attach_internet_gateway(ec2_client, gateway.internet_gateway_id, vpc.vpc_id)
131
+
132
+ public_route_table = Vominator::VPC.get_route_tables(ec2_client, vpc.vpc_id).first
133
+ Vominator::EC2.tag_resource(ec2_client, public_route_table.route_table_id,[{key: 'Name', value: "dmz-#{options[:environment]}-#{options[:region]}"}])
134
+
135
+ Vominator::VPC.create_internet_gateway_route(ec2_client, public_route_table.route_table_id, '0.0.0.0/0', gateway.internet_gateway_id)
136
+
137
+ third_octet = 1
138
+
139
+ route_tables = Hash.new
140
+ route_tables['public'] = public_route_table.route_table_id
141
+
142
+ availability_zones.each do |zone|
143
+
144
+ private_route_table = Vominator::VPC.create_route_table(ec2_client, vpc.vpc_id)
145
+ route_tables[zone] = private_route_table.route_table_id
146
+
147
+ Vominator::EC2.tag_resource(ec2_client, private_route_table.route_table_id,[{key: 'Name', value: "nat-#{options[:environment]}-#{zone}"}])
148
+
149
+ public_subnet_cidr_block = "#{options[:cidr_block].split('.')[0]}.#{options[:cidr_block].split('.')[1]}.#{third_octet}.0/24"
150
+ public_subnet = Vominator::VPC.create_subnet(ec2_client, vpc.vpc_id, public_subnet_cidr_block, zone)
151
+
152
+ Vominator::VPC.associate_route_table(ec2_client, public_subnet.subnet_id, public_route_table.route_table_id)
153
+
154
+ public_ip = Vominator::EC2.allocate_public_ip(ec2_client)
155
+ nat_gateway = Vominator::VPC.create_nat_gateway(ec2_client, public_subnet.subnet_id, public_ip.allocation_id)
156
+
157
+ Vominator::VPC.create_nat_gateway_route(ec2_client, private_route_table.route_table_id, '0.0.0.0/0', nat_gateway.nat_gateway_id)
158
+
159
+ third_octet += 1
160
+ end
161
+
162
+ unless parent_zone
163
+ LOGGER.warning("Parent zone not was not found in Route53. Use the below information to create the proper entries in your parent zone.")
164
+ LOGGER.warning("FQDN: #{fqdn}")
165
+ LOGGER.warning("Record Type: NS")
166
+ LOGGER.warning("Nameservers: #{environment_zone.delegation_set.name_servers}")
167
+ end
168
+
169
+ config = {
170
+ options[:environment] => {
171
+ 'vpc_id' => vpc.vpc_id,
172
+ 'route_tables' => route_tables,
173
+ 'region_name' => options[:region],
174
+ 'zone' => environment_zone.hosted_zone.id.split('/')[2],
175
+ 'octet' => options[:cidr_block].split('.')[1],
176
+ 'domain' => fqdn,
177
+ 'linux_cloud_config_template' => 'EDIT_ME',
178
+ 'linux_paravirtual_base_image' => 'EDIT_ME',
179
+ 'linux_hvm_base_image' => 'EDIT_ME',
180
+ 'windows_cloud_config_template' => 'EDIT_ME',
181
+ 'windows_paravirtual_base_image' => 'EDIT_ME',
182
+ 'windows_hvm_base_image' => 'EDIT_ME',
183
+ 'chef_host' => 'EDIT_ME',
184
+ 'products' => []
185
+ }}
186
+
187
+ LOGGER.success("Append the below to your config.yaml file in your puke directory")
188
+ LOGGER.info(config.to_yaml)