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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/CHANGELOG.md +76 -0
- data/Gemfile +10 -0
- data/LICENSE +8 -0
- data/README.md +251 -0
- data/Rakefile +21 -0
- data/dummy.box +0 -0
- data/example_box/README.md +13 -0
- data/example_box/metadata.json +3 -0
- data/lib/vagrant-aws.rb +18 -0
- data/lib/vagrant-aws/action.rb +190 -0
- data/lib/vagrant-aws/action/connect_aws.rb +46 -0
- data/lib/vagrant-aws/action/is_created.rb +18 -0
- data/lib/vagrant-aws/action/is_stopped.rb +18 -0
- data/lib/vagrant-aws/action/message_already_created.rb +16 -0
- data/lib/vagrant-aws/action/message_not_created.rb +16 -0
- data/lib/vagrant-aws/action/message_will_not_destroy.rb +16 -0
- data/lib/vagrant-aws/action/read_ssh_info.rb +53 -0
- data/lib/vagrant-aws/action/read_state.rb +38 -0
- data/lib/vagrant-aws/action/run_instance.rb +247 -0
- data/lib/vagrant-aws/action/start_instance.rb +81 -0
- data/lib/vagrant-aws/action/stop_instance.rb +28 -0
- data/lib/vagrant-aws/action/sync_folders.rb +118 -0
- data/lib/vagrant-aws/action/terminate_instance.rb +47 -0
- data/lib/vagrant-aws/action/timed_provision.rb +21 -0
- data/lib/vagrant-aws/action/wait_for_state.rb +41 -0
- data/lib/vagrant-aws/action/warn_networks.rb +19 -0
- data/lib/vagrant-aws/config.rb +372 -0
- data/lib/vagrant-aws/errors.rb +31 -0
- data/lib/vagrant-aws/plugin.rb +73 -0
- data/lib/vagrant-aws/provider.rb +50 -0
- data/lib/vagrant-aws/util/timer.rb +17 -0
- data/lib/vagrant-aws/version.rb +5 -0
- data/locales/en.yml +122 -0
- data/spec/vagrant-aws/config_spec.rb +216 -0
- data/vagrant-aws.gemspec +59 -0
- 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 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
|