vagrant-aws-stsmith 0.5.0.dev

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 (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/CHANGELOG.md +76 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +8 -0
  6. data/README.md +251 -0
  7. data/Rakefile +21 -0
  8. data/dummy.box +0 -0
  9. data/example_box/README.md +13 -0
  10. data/example_box/metadata.json +3 -0
  11. data/lib/vagrant-aws.rb +18 -0
  12. data/lib/vagrant-aws/action.rb +190 -0
  13. data/lib/vagrant-aws/action/connect_aws.rb +46 -0
  14. data/lib/vagrant-aws/action/is_created.rb +18 -0
  15. data/lib/vagrant-aws/action/is_stopped.rb +18 -0
  16. data/lib/vagrant-aws/action/message_already_created.rb +16 -0
  17. data/lib/vagrant-aws/action/message_not_created.rb +16 -0
  18. data/lib/vagrant-aws/action/message_will_not_destroy.rb +16 -0
  19. data/lib/vagrant-aws/action/read_ssh_info.rb +53 -0
  20. data/lib/vagrant-aws/action/read_state.rb +38 -0
  21. data/lib/vagrant-aws/action/run_instance.rb +247 -0
  22. data/lib/vagrant-aws/action/start_instance.rb +81 -0
  23. data/lib/vagrant-aws/action/stop_instance.rb +28 -0
  24. data/lib/vagrant-aws/action/sync_folders.rb +118 -0
  25. data/lib/vagrant-aws/action/terminate_instance.rb +47 -0
  26. data/lib/vagrant-aws/action/timed_provision.rb +21 -0
  27. data/lib/vagrant-aws/action/wait_for_state.rb +41 -0
  28. data/lib/vagrant-aws/action/warn_networks.rb +19 -0
  29. data/lib/vagrant-aws/config.rb +372 -0
  30. data/lib/vagrant-aws/errors.rb +31 -0
  31. data/lib/vagrant-aws/plugin.rb +73 -0
  32. data/lib/vagrant-aws/provider.rb +50 -0
  33. data/lib/vagrant-aws/util/timer.rb +17 -0
  34. data/lib/vagrant-aws/version.rb +5 -0
  35. data/locales/en.yml +122 -0
  36. data/spec/vagrant-aws/config_spec.rb +216 -0
  37. data/vagrant-aws.gemspec +59 -0
  38. metadata +149 -0
@@ -0,0 +1,46 @@
1
+ require "fog"
2
+ require "log4r"
3
+
4
+ module VagrantPlugins
5
+ module AWS
6
+ module Action
7
+ # This action connects to AWS, verifies credentials work, and
8
+ # puts the AWS connection object into the `:aws_compute` key
9
+ # in the environment.
10
+ class ConnectAWS
11
+ def initialize(app, env)
12
+ @app = app
13
+ @logger = Log4r::Logger.new("vagrant_aws::action::connect_aws")
14
+ end
15
+
16
+ def call(env)
17
+ # Get the region we're going to booting up in
18
+ region = env[:machine].provider_config.region
19
+
20
+ # Get the configs
21
+ region_config = env[:machine].provider_config.get_region_config(region)
22
+
23
+ # Build the fog config
24
+ fog_config = {
25
+ :provider => :aws,
26
+ :region => region
27
+ }
28
+ if region_config.use_iam_profile
29
+ fog_config[:use_iam_profile] = true
30
+ else
31
+ fog_config[:aws_access_key_id] = region_config.access_key_id
32
+ fog_config[:aws_secret_access_key] = region_config.secret_access_key
33
+ end
34
+
35
+ fog_config[:endpoint] = region_config.endpoint if region_config.endpoint
36
+ fog_config[:version] = region_config.version if region_config.version
37
+
38
+ @logger.info("Connecting to AWS...")
39
+ env[:aws_compute] = Fog::Compute.new(fog_config)
40
+
41
+ @app.call(env)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,18 @@
1
+ module VagrantPlugins
2
+ module AWS
3
+ module Action
4
+ # This can be used with "Call" built-in to check if the machine
5
+ # is created and branch in the middleware.
6
+ class IsCreated
7
+ def initialize(app, env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[:result] = env[:machine].state.id != :not_created
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ module VagrantPlugins
2
+ module AWS
3
+ module Action
4
+ # This can be used with "Call" built-in to check if the machine
5
+ # is stopped and branch in the middleware.
6
+ class IsStopped
7
+ def initialize(app, env)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ env[:result] = env[:machine].state.id == :stopped
13
+ @app.call(env)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module AWS
3
+ module Action
4
+ class MessageAlreadyCreated
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info(I18n.t("vagrant_aws.already_status", :status => "created"))
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module AWS
3
+ module Action
4
+ class MessageNotCreated
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info(I18n.t("vagrant_aws.not_created"))
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module AWS
3
+ module Action
4
+ class MessageWillNotDestroy
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info(I18n.t("vagrant_aws.will_not_destroy", name: env[:machine].name))
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,53 @@
1
+ require "log4r"
2
+
3
+ module VagrantPlugins
4
+ module AWS
5
+ module Action
6
+ # This action reads the SSH info for the machine and puts it into the
7
+ # `:machine_ssh_info` key in the environment.
8
+ class ReadSSHInfo
9
+ def initialize(app, env)
10
+ @app = app
11
+ @logger = Log4r::Logger.new("vagrant_aws::action::read_ssh_info")
12
+ end
13
+
14
+ def call(env)
15
+ env[:machine_ssh_info] = read_ssh_info(env[:aws_compute], env[:machine])
16
+
17
+ @app.call(env)
18
+ end
19
+
20
+ def read_ssh_info(aws, machine)
21
+ return nil if machine.id.nil?
22
+
23
+ # Find the machine
24
+ server = aws.servers.get(machine.id)
25
+ if server.nil?
26
+ # The machine can't be found
27
+ @logger.info("Machine couldn't be found, assuming it got destroyed.")
28
+ machine.id = nil
29
+ return nil
30
+ end
31
+
32
+ # read attribute override
33
+ ssh_host_attribute = machine.provider_config.
34
+ get_region_config(machine.provider_config.region).ssh_host_attribute
35
+ # default host attributes to try. NOTE: Order matters!
36
+ ssh_attrs = [:public_ip_address, :dns_name, :private_ip_address]
37
+ ssh_attrs = (Array(ssh_host_attribute) + ssh_attrs).uniq if ssh_host_attribute
38
+ # try each attribute, get out on first value
39
+ host_value = nil
40
+ while !host_value and attr_name = ssh_attrs.shift
41
+ begin
42
+ host_value = server.send(attr_name)
43
+ rescue NoMethodError
44
+ @logger.info("SSH host attribute not found #{attr_name}")
45
+ end
46
+ end
47
+
48
+ return { :host => host_value, :port => 22 }
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,38 @@
1
+ require "log4r"
2
+
3
+ module VagrantPlugins
4
+ module AWS
5
+ module Action
6
+ # This action reads the state of the machine and puts it in the
7
+ # `:machine_state_id` key in the environment.
8
+ class ReadState
9
+ def initialize(app, env)
10
+ @app = app
11
+ @logger = Log4r::Logger.new("vagrant_aws::action::read_state")
12
+ end
13
+
14
+ def call(env)
15
+ env[:machine_state_id] = read_state(env[:aws_compute], env[:machine])
16
+
17
+ @app.call(env)
18
+ end
19
+
20
+ def read_state(aws, machine)
21
+ return :not_created if machine.id.nil?
22
+
23
+ # Find the machine
24
+ server = aws.servers.get(machine.id)
25
+ if server.nil? || [:"shutting-down", :terminated].include?(server.state.to_sym)
26
+ # The machine can't be found
27
+ @logger.info("Machine not found or terminated, assuming it got destroyed.")
28
+ machine.id = nil
29
+ return :not_created
30
+ end
31
+
32
+ # Return the state
33
+ return server.state.to_sym
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,247 @@
1
+ require "log4r"
2
+ require 'json'
3
+
4
+ require 'vagrant/util/retryable'
5
+
6
+ require 'vagrant-aws/util/timer'
7
+
8
+ module VagrantPlugins
9
+ module AWS
10
+ module Action
11
+ # This runs the configured instance.
12
+ class RunInstance
13
+ include Vagrant::Util::Retryable
14
+
15
+ def initialize(app, env)
16
+ @app = app
17
+ @logger = Log4r::Logger.new("vagrant_aws::action::run_instance")
18
+ end
19
+
20
+ def call(env)
21
+ # Initialize metrics if they haven't been
22
+ env[:metrics] ||= {}
23
+
24
+ # Get the region we're going to booting up in
25
+ region = env[:machine].provider_config.region
26
+
27
+ # Get the configs
28
+ region_config = env[:machine].provider_config.get_region_config(region)
29
+ ami = region_config.ami
30
+ availability_zone = region_config.availability_zone
31
+ instance_type = region_config.instance_type
32
+ keypair = region_config.keypair_name
33
+ private_ip_address = region_config.private_ip_address
34
+ security_groups = region_config.security_groups
35
+ subnet_id = region_config.subnet_id
36
+ tags = region_config.tags
37
+ user_data = region_config.user_data
38
+ block_device_mapping = region_config.block_device_mapping
39
+ elastic_ip = region_config.elastic_ip
40
+ terminate_on_shutdown = region_config.terminate_on_shutdown
41
+ iam_instance_profile_arn = region_config.iam_instance_profile_arn
42
+ iam_instance_profile_name = region_config.iam_instance_profile_name
43
+ monitoring = region_config.monitoring
44
+ ebs_optimized = region_config.ebs_optimized
45
+ associate_public_ip = region_config.associate_public_ip
46
+
47
+ # If there is no keypair then warn the user
48
+ if !keypair
49
+ env[:ui].warn(I18n.t("vagrant_aws.launch_no_keypair"))
50
+ end
51
+
52
+ # If there is a subnet ID then warn the user
53
+ if subnet_id && !elastic_ip
54
+ env[:ui].warn(I18n.t("vagrant_aws.launch_vpc_warning"))
55
+ end
56
+
57
+ # Launch!
58
+ env[:ui].info(I18n.t("vagrant_aws.launching_instance"))
59
+ env[:ui].info(" -- Type: #{instance_type}")
60
+ env[:ui].info(" -- AMI: #{ami}")
61
+ env[:ui].info(" -- Region: #{region}")
62
+ env[:ui].info(" -- Availability Zone: #{availability_zone}") if availability_zone
63
+ env[:ui].info(" -- Keypair: #{keypair}") if keypair
64
+ env[:ui].info(" -- Subnet ID: #{subnet_id}") if subnet_id
65
+ env[:ui].info(" -- IAM Instance Profile ARN: #{iam_instance_profile_arn}") if iam_instance_profile_arn
66
+ env[:ui].info(" -- IAM Instance Profile Name: #{iam_instance_profile_name}") if iam_instance_profile_name
67
+ env[:ui].info(" -- Private IP: #{private_ip_address}") if private_ip_address
68
+ env[:ui].info(" -- Elastic IP: #{elastic_ip}") if elastic_ip
69
+ env[:ui].info(" -- User Data: yes") if user_data
70
+ env[:ui].info(" -- Security Groups: #{security_groups.inspect}") if !security_groups.empty?
71
+ env[:ui].info(" -- User Data: #{user_data}") if user_data
72
+ env[:ui].info(" -- Block Device Mapping: #{block_device_mapping}") if block_device_mapping
73
+ env[:ui].info(" -- Terminate On Shutdown: #{terminate_on_shutdown}")
74
+ env[:ui].info(" -- Monitoring: #{monitoring}")
75
+ env[:ui].info(" -- EBS optimized: #{ebs_optimized}")
76
+ env[:ui].info(" -- Assigning a public IP address in a VPC: #{associate_public_ip}")
77
+
78
+ options = {
79
+ :availability_zone => availability_zone,
80
+ :flavor_id => instance_type,
81
+ :image_id => ami,
82
+ :key_name => keypair,
83
+ :private_ip_address => private_ip_address,
84
+ :subnet_id => subnet_id,
85
+ :iam_instance_profile_arn => iam_instance_profile_arn,
86
+ :iam_instance_profile_name => iam_instance_profile_name,
87
+ :tags => tags,
88
+ :user_data => user_data,
89
+ :block_device_mapping => block_device_mapping,
90
+ :instance_initiated_shutdown_behavior => terminate_on_shutdown == true ? "terminate" : nil,
91
+ :monitoring => monitoring,
92
+ :ebs_optimized => ebs_optimized,
93
+ :associate_public_ip => associate_public_ip
94
+ }
95
+ if !security_groups.empty?
96
+ security_group_key = options[:subnet_id].nil? ? :groups : :security_group_ids
97
+ options[security_group_key] = security_groups
98
+ end
99
+
100
+ begin
101
+ env[:ui].warn(I18n.t("vagrant_aws.warn_ssh_access")) unless allows_ssh_port?(env, security_groups, subnet_id)
102
+
103
+ server = env[:aws_compute].servers.create(options)
104
+ rescue Fog::Compute::AWS::NotFound => e
105
+ # Invalid subnet doesn't have its own error so we catch and
106
+ # check the error message here.
107
+ if e.message =~ /subnet ID/
108
+ raise Errors::FogError,
109
+ :message => "Subnet ID not found: #{subnet_id}"
110
+ end
111
+
112
+ raise
113
+ rescue Fog::Compute::AWS::Error => e
114
+ raise Errors::FogError, :message => e.message
115
+ rescue Excon::Errors::HTTPStatusError => e
116
+ raise Errors::InternalFogError,
117
+ :error => e.message,
118
+ :response => e.response.body
119
+ end
120
+
121
+ # Immediately save the ID since it is created at this point.
122
+ env[:machine].id = server.id
123
+
124
+ # Wait for the instance to be ready first
125
+ env[:metrics]["instance_ready_time"] = Util::Timer.time do
126
+ tries = region_config.instance_ready_timeout / 2
127
+
128
+ env[:ui].info(I18n.t("vagrant_aws.waiting_for_ready"))
129
+ begin
130
+ retryable(:on => Fog::Errors::TimeoutError, :tries => tries) do
131
+ # If we're interrupted don't worry about waiting
132
+ next if env[:interrupted]
133
+
134
+ # Wait for the server to be ready
135
+ server.wait_for(2) { ready? }
136
+ end
137
+ rescue Fog::Errors::TimeoutError
138
+ # Delete the instance
139
+ terminate(env)
140
+
141
+ # Notify the user
142
+ raise Errors::InstanceReadyTimeout,
143
+ timeout: region_config.instance_ready_timeout
144
+ end
145
+ end
146
+
147
+ @logger.info("Time to instance ready: #{env[:metrics]["instance_ready_time"]}")
148
+
149
+ # Allocate and associate an elastic IP if requested
150
+ if elastic_ip
151
+ domain = subnet_id ? 'vpc' : 'standard'
152
+ do_elastic_ip(env, domain, server)
153
+ end
154
+
155
+ if !env[:interrupted]
156
+ env[:metrics]["instance_ssh_time"] = Util::Timer.time do
157
+ # Wait for SSH to be ready.
158
+ env[:ui].info(I18n.t("vagrant_aws.waiting_for_ssh"))
159
+ while true
160
+ # If we're interrupted then just back out
161
+ break if env[:interrupted]
162
+ break if env[:machine].communicate.ready?
163
+ sleep 2
164
+ end
165
+ end
166
+
167
+ @logger.info("Time for SSH ready: #{env[:metrics]["instance_ssh_time"]}")
168
+
169
+ # Ready and booted!
170
+ env[:ui].info(I18n.t("vagrant_aws.ready"))
171
+ end
172
+
173
+ # Terminate the instance if we were interrupted
174
+ terminate(env) if env[:interrupted]
175
+
176
+ @app.call(env)
177
+ end
178
+
179
+ def recover(env)
180
+ return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError)
181
+
182
+ if env[:machine].provider.state.id != :not_created
183
+ # Undo the import
184
+ terminate(env)
185
+ end
186
+ end
187
+
188
+ def allows_ssh_port?(env, test_sec_groups, is_vpc)
189
+ port = 22 # TODO get ssh_info port
190
+ test_sec_groups = [ "default" ] if test_sec_groups.empty? # AWS default security group
191
+ # filter groups by name or group_id (vpc)
192
+ groups = test_sec_groups.map do |tsg|
193
+ env[:aws_compute].security_groups.all.select { |sg| tsg == (is_vpc ? sg.group_id : sg.name) }
194
+ end.flatten
195
+ # filter TCP rules
196
+ rules = groups.map { |sg| sg.ip_permissions.select { |r| r["ipProtocol"] == "tcp" } }.flatten
197
+ # test if any range includes port
198
+ !rules.select { |r| (r["fromPort"]..r["toPort"]).include?(port) }.empty?
199
+ end
200
+
201
+ def do_elastic_ip(env, domain, server)
202
+ begin
203
+ allocation = env[:aws_compute].allocate_address(domain)
204
+ rescue
205
+ @logger.debug("Could not allocate Elastic IP.")
206
+ terminate(env)
207
+ raise Errors::FogError,
208
+ :message => "Could not allocate Elastic IP."
209
+ end
210
+ @logger.debug("Public IP #{allocation.body['publicIp']}")
211
+
212
+ # Associate the address and save the metadata to a hash
213
+ if domain == 'vpc'
214
+ # VPC requires an allocation ID to assign an IP
215
+ association = env[:aws_compute].associate_address(server.id, nil, nil, allocation.body['allocationId'])
216
+ h = { :allocation_id => allocation.body['allocationId'], :association_id => association.body['associationId'], :public_ip => allocation.body['publicIp'] }
217
+ else
218
+ # Standard EC2 instances only need the allocated IP address
219
+ association = env[:aws_compute].associate_address(server.id, allocation.body['publicIp'])
220
+ h = { :public_ip => allocation.body['publicIp'] }
221
+ end
222
+
223
+ unless association.body['return']
224
+ @logger.debug("Could not associate Elastic IP.")
225
+ terminate(env)
226
+ raise Errors::FogError,
227
+ :message => "Could not allocate Elastic IP."
228
+ end
229
+
230
+ # Save this IP to the data dir so it can be released when the instance is destroyed
231
+ ip_file = env[:machine].data_dir.join('elastic_ip')
232
+ ip_file.open('w+') do |f|
233
+ f.write(h.to_json)
234
+ end
235
+ end
236
+
237
+ def terminate(env)
238
+ destroy_env = env.dup
239
+ destroy_env.delete(:interrupted)
240
+ destroy_env[:config_validate] = false
241
+ destroy_env[:force_confirm_destroy] = true
242
+ env[:action_runner].run(Action.action_destroy, destroy_env)
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end