tnargav-aws 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ require "log4r"
2
+
3
+ require "vagrant/util/subprocess"
4
+
5
+ require "vagrant/util/scoped_hash_override"
6
+
7
+ module VagrantPlugins
8
+ module AWS
9
+ module Action
10
+ # This middleware uses `rsync` to sync the folders over to the
11
+ # AWS instance.
12
+ class SyncFolders
13
+ include Vagrant::Util::ScopedHashOverride
14
+
15
+ def initialize(app, env)
16
+ @app = app
17
+ @logger = Log4r::Logger.new("vagrant_aws::action::sync_folders")
18
+ end
19
+
20
+ def call(env)
21
+ @app.call(env)
22
+
23
+ ssh_info = env[:machine].ssh_info
24
+
25
+ env[:machine].config.vm.synced_folders.each do |id, data|
26
+ data = scoped_hash_override(data, :aws)
27
+
28
+ # Ignore disabled shared folders
29
+ next if data[:disabled]
30
+
31
+ hostpath = File.expand_path(data[:hostpath], env[:root_path])
32
+ guestpath = data[:guestpath]
33
+
34
+ # Make sure there is a trailing slash on the host path to
35
+ # avoid creating an additional directory with rsync
36
+ hostpath = "#{hostpath}/" if hostpath !~ /\/$/
37
+
38
+ env[:ui].info(I18n.t("vagrant_aws.rsync_folder",
39
+ :hostpath => hostpath,
40
+ :guestpath => guestpath))
41
+
42
+ # Create the guest path
43
+ env[:machine].communicate.sudo("mkdir -p '#{guestpath}'")
44
+ env[:machine].communicate.sudo(
45
+ "chown #{ssh_info[:username]} '#{guestpath}'")
46
+
47
+ # Rsync over to the guest path using the SSH info
48
+ command = [
49
+ "rsync", "--verbose", "--archive", "-z",
50
+ "--exclude", ".vagrant/",
51
+ "-e", "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no -i '#{ssh_info[:private_key_path]}'",
52
+ hostpath,
53
+ "#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"]
54
+
55
+ r = Vagrant::Util::Subprocess.execute(*command)
56
+ if r.exit_code != 0
57
+ raise Errors::RsyncError,
58
+ :guestpath => guestpath,
59
+ :hostpath => hostpath,
60
+ :stderr => r.stderr
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,26 @@
1
+ require "log4r"
2
+
3
+ module VagrantPlugins
4
+ module AWS
5
+ module Action
6
+ # This terminates the running instance.
7
+ class TerminateInstance
8
+ def initialize(app, env)
9
+ @app = app
10
+ @logger = Log4r::Logger.new("vagrant_aws::action::terminate_instance")
11
+ end
12
+
13
+ def call(env)
14
+ server = env[:aws_compute].servers.get(env[:machine].id)
15
+
16
+ # Destroy the server and remove the tracking ID
17
+ env[:ui].info(I18n.t("vagrant_aws.terminating"))
18
+ server.destroy
19
+ env[:machine].id = nil
20
+
21
+ @app.call(env)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ require "vagrant-aws/util/timer"
2
+
3
+ module VagrantPlugins
4
+ module AWS
5
+ module Action
6
+ # This is the same as the builtin provision except it times the
7
+ # provisioner runs.
8
+ class TimedProvision < Vagrant::Action::Builtin::Provision
9
+ def run_provisioner(env, name, p)
10
+ timer = Util::Timer.time do
11
+ super
12
+ end
13
+
14
+ env[:metrics] ||= {}
15
+ env[:metrics]["provisioner_times"] ||= []
16
+ env[:metrics]["provisioner_times"] << [name, timer]
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module VagrantPlugins
2
+ module AWS
3
+ module Action
4
+ class WarnNetworks
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ if env[:machine].config.vm.networks.length > 0
11
+ env[:ui].warn(I18n.t("vagrant_aws.warn_networks"))
12
+ end
13
+
14
+ @app.call(env)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,121 @@
1
+ require "pathname"
2
+
3
+ require "vagrant/action/builder"
4
+
5
+ module VagrantPlugins
6
+ module AWS
7
+ module Action
8
+ # Include the built-in modules so we can use them as top-level things.
9
+ include Vagrant::Action::Builtin
10
+
11
+ # This action is called to terminate the remote machine.
12
+ def self.action_destroy
13
+ Vagrant::Action::Builder.new.tap do |b|
14
+ b.use ConfigValidate
15
+ b.use ConnectAWS
16
+ b.use TerminateInstance
17
+ end
18
+ end
19
+
20
+ # This action is called when `vagrant provision` is called.
21
+ def self.action_provision
22
+ Vagrant::Action::Builder.new.tap do |b|
23
+ b.use ConfigValidate
24
+ b.use Call, IsCreated do |env, b2|
25
+ if !env[:result]
26
+ b2.use MessageNotCreated
27
+ next
28
+ end
29
+
30
+ b2.use Provision
31
+ b2.use SyncFolders
32
+ end
33
+ end
34
+ end
35
+
36
+ # This action is called to read the SSH info of the machine. The
37
+ # resulting state is expected to be put into the `:machine_ssh_info`
38
+ # key.
39
+ def self.action_read_ssh_info
40
+ Vagrant::Action::Builder.new.tap do |b|
41
+ b.use ConfigValidate
42
+ b.use ConnectAWS
43
+ b.use ReadSSHInfo
44
+ end
45
+ end
46
+
47
+ # This action is called to read the state of the machine. The
48
+ # resulting state is expected to be put into the `:machine_state_id`
49
+ # key.
50
+ def self.action_read_state
51
+ Vagrant::Action::Builder.new.tap do |b|
52
+ b.use ConfigValidate
53
+ b.use ConnectAWS
54
+ b.use ReadState
55
+ end
56
+ end
57
+
58
+ # This action is called to SSH into the machine.
59
+ def self.action_ssh
60
+ Vagrant::Action::Builder.new.tap do |b|
61
+ b.use ConfigValidate
62
+ b.use Call, IsCreated do |env, b2|
63
+ if !env[:result]
64
+ b2.use MessageNotCreated
65
+ next
66
+ end
67
+
68
+ b2.use SSHExec
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.action_ssh_run
74
+ Vagrant::Action::Builder.new.tap do |b|
75
+ b.use ConfigValidate
76
+ b.use Call, IsCreated do |env, b2|
77
+ if !env[:result]
78
+ b2.use MessageNotCreated
79
+ next
80
+ end
81
+
82
+ b2.use SSHRun
83
+ end
84
+ end
85
+ end
86
+
87
+ # This action is called to bring the box up from nothing.
88
+ def self.action_up
89
+ Vagrant::Action::Builder.new.tap do |b|
90
+ b.use ConfigValidate
91
+ b.use ConnectAWS
92
+ b.use Call, IsCreated do |env, b2|
93
+ if env[:result]
94
+ b2.use MessageAlreadyCreated
95
+ next
96
+ end
97
+
98
+ b2.use TimedProvision
99
+ b2.use SyncFolders
100
+ b2.use WarnNetworks
101
+ b2.use RunInstance
102
+ end
103
+ end
104
+ end
105
+
106
+ # The autoload farm
107
+ action_root = Pathname.new(File.expand_path("../action", __FILE__))
108
+ autoload :ConnectAWS, action_root.join("connect_aws")
109
+ autoload :IsCreated, action_root.join("is_created")
110
+ autoload :MessageAlreadyCreated, action_root.join("message_already_created")
111
+ autoload :MessageNotCreated, action_root.join("message_not_created")
112
+ autoload :ReadSSHInfo, action_root.join("read_ssh_info")
113
+ autoload :ReadState, action_root.join("read_state")
114
+ autoload :RunInstance, action_root.join("run_instance")
115
+ autoload :SyncFolders, action_root.join("sync_folders")
116
+ autoload :TimedProvision, action_root.join("timed_provision")
117
+ autoload :WarnNetworks, action_root.join("warn_networks")
118
+ autoload :TerminateInstance, action_root.join("terminate_instance")
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,275 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module AWS
5
+ class Config < Vagrant.plugin("2", :config)
6
+ # The access key ID for accessing AWS.
7
+ #
8
+ # @return [String]
9
+ attr_accessor :access_key_id
10
+
11
+ # The ID of the AMI to use.
12
+ #
13
+ # @return [String]
14
+ attr_accessor :ami
15
+
16
+ # The availability zone to launch the instance into. If nil, it will
17
+ # use the default for your account.
18
+ #
19
+ # @return [String]
20
+ attr_accessor :availability_zone
21
+
22
+ # The timeout to wait for an instance to become ready.
23
+ #
24
+ # @return [Fixnum]
25
+ attr_accessor :instance_ready_timeout
26
+
27
+ # The type of instance to launch, such as "m1.small"
28
+ #
29
+ # @return [String]
30
+ attr_accessor :instance_type
31
+
32
+ # The name of the keypair to use.
33
+ #
34
+ # @return [String]
35
+ attr_accessor :keypair_name
36
+
37
+ # The private IP address to give this machine (VPC).
38
+ #
39
+ # @return [String]
40
+ attr_accessor :private_ip_address
41
+
42
+ # The name of the AWS region in which to create the instance.
43
+ #
44
+ # @return [String]
45
+ attr_accessor :region
46
+
47
+ # The EC2 endpoint to connect to
48
+ #
49
+ # @return [String]
50
+ attr_accessor :endpoint
51
+
52
+ # The version of the AWS api to use
53
+ #
54
+ # @return [String]
55
+ attr_accessor :version
56
+
57
+ # The secret access key for accessing AWS.
58
+ #
59
+ # @return [String]
60
+ attr_accessor :secret_access_key
61
+
62
+ # The security groups to set on the instance. For VPC this must
63
+ # be a list of IDs. For EC2, it can be either.
64
+ #
65
+ # @return [Array<String>]
66
+ attr_accessor :security_groups
67
+
68
+ # The subnet ID to launch the machine into (VPC).
69
+ #
70
+ # @return [String]
71
+ attr_accessor :subnet_id
72
+
73
+ # The tags for the machine.
74
+ #
75
+ # @return [Hash<String, String>]
76
+ attr_accessor :tags
77
+
78
+ # Use IAM Instance Role for authentication to AWS instead of an
79
+ # explicit access_id and secret_access_key
80
+ #
81
+ # @return [Boolean]
82
+ attr_accessor :use_iam_profile
83
+
84
+ # The user data string
85
+ #
86
+ # @return [String]
87
+ attr_accessor :user_data
88
+
89
+ def initialize(region_specific=false)
90
+ @access_key_id = UNSET_VALUE
91
+ @ami = UNSET_VALUE
92
+ @availability_zone = UNSET_VALUE
93
+ @instance_ready_timeout = UNSET_VALUE
94
+ @instance_type = UNSET_VALUE
95
+ @keypair_name = UNSET_VALUE
96
+ @private_ip_address = UNSET_VALUE
97
+ @region = UNSET_VALUE
98
+ @endpoint = UNSET_VALUE
99
+ @version = UNSET_VALUE
100
+ @secret_access_key = UNSET_VALUE
101
+ @security_groups = UNSET_VALUE
102
+ @subnet_id = UNSET_VALUE
103
+ @tags = {}
104
+ @user_data = UNSET_VALUE
105
+ @use_iam_profile = UNSET_VALUE
106
+
107
+ # Internal state (prefix with __ so they aren't automatically
108
+ # merged)
109
+ @__compiled_region_configs = {}
110
+ @__finalized = false
111
+ @__region_config = {}
112
+ @__region_specific = region_specific
113
+ end
114
+
115
+ # Allows region-specific overrides of any of the settings on this
116
+ # configuration object. This allows the user to override things like
117
+ # AMI and keypair name for regions. Example:
118
+ #
119
+ # aws.region_config "us-east-1" do |region|
120
+ # region.ami = "ami-12345678"
121
+ # region.keypair_name = "company-east"
122
+ # end
123
+ #
124
+ # @param [String] region The region name to configure.
125
+ # @param [Hash] attributes Direct attributes to set on the configuration
126
+ # as a shortcut instead of specifying a full block.
127
+ # @yield [config] Yields a new AWS configuration.
128
+ def region_config(region, attributes=nil, &block)
129
+ # Append the block to the list of region configs for that region.
130
+ # We'll evaluate these upon finalization.
131
+ @__region_config[region] ||= []
132
+
133
+ # Append a block that sets attributes if we got one
134
+ if attributes
135
+ attr_block = lambda do |config|
136
+ config.set_options(attributes)
137
+ end
138
+
139
+ @__region_config[region] << attr_block
140
+ end
141
+
142
+ # Append a block if we got one
143
+ @__region_config[region] << block if block_given?
144
+ end
145
+
146
+ #-------------------------------------------------------------------
147
+ # Internal methods.
148
+ #-------------------------------------------------------------------
149
+
150
+ def merge(other)
151
+ super.tap do |result|
152
+ # Copy over the region specific flag. "True" is retained if either
153
+ # has it.
154
+ new_region_specific = other.instance_variable_get(:@__region_specific)
155
+ result.instance_variable_set(
156
+ :@__region_specific, new_region_specific || @__region_specific)
157
+
158
+ # Go through all the region configs and prepend ours onto
159
+ # theirs.
160
+ new_region_config = other.instance_variable_get(:@__region_config)
161
+ @__region_config.each do |key, value|
162
+ new_region_config[key] ||= []
163
+ new_region_config[key] = value + new_region_config[key]
164
+ end
165
+
166
+ # Set it
167
+ result.instance_variable_set(:@__region_config, new_region_config)
168
+
169
+ # Merge in the tags
170
+ result.tags.merge!(self.tags)
171
+ result.tags.merge!(other.tags)
172
+ end
173
+ end
174
+
175
+ def finalize!
176
+ # Try to get access keys from standard AWS environment variables; they
177
+ # will default to nil if the environment variables are not present.
178
+ @access_key_id = ENV['AWS_ACCESS_KEY'] if @access_key_id == UNSET_VALUE
179
+ @secret_access_key = ENV['AWS_SECRET_KEY'] if @secret_access_key == UNSET_VALUE
180
+
181
+ # AMI must be nil, since we can't default that
182
+ @ami = nil if @ami == UNSET_VALUE
183
+
184
+ # Set the default timeout for waiting for an instance to be ready
185
+ @instance_ready_timeout = 120 if @instance_ready_timeout == UNSET_VALUE
186
+
187
+ # Default instance type is an m1.small
188
+ @instance_type = "m1.small" if @instance_type == UNSET_VALUE
189
+
190
+ # Keypair defaults to nil
191
+ @keypair_name = nil if @keypair_name == UNSET_VALUE
192
+
193
+ # Default the private IP to nil since VPC is not default
194
+ @private_ip_address = nil if @private_ip_address == UNSET_VALUE
195
+
196
+ # Default region is us-east-1. This is sensible because AWS
197
+ # generally defaults to this as well.
198
+ @region = "us-east-1" if @region == UNSET_VALUE
199
+ @availability_zone = nil if @availability_zone == UNSET_VALUE
200
+ @endpoint = nil if @endpoint == UNSET_VALUE
201
+ @version = nil if @version == UNSET_VALUE
202
+
203
+ # The security groups are empty by default.
204
+ @security_groups = [] if @security_groups == UNSET_VALUE
205
+
206
+ # Subnet is nil by default otherwise we'd launch into VPC.
207
+ @subnet_id = nil if @subnet_id == UNSET_VALUE
208
+
209
+ # By default we don't use an IAM profile
210
+ @use_iam_profile = false if @use_iam_profile == UNSET_VALUE
211
+
212
+ # User Data is nil by default
213
+ @user_data = nil if @user_data == UNSET_VALUE
214
+
215
+ # Compile our region specific configurations only within
216
+ # NON-REGION-SPECIFIC configurations.
217
+ if !@__region_specific
218
+ @__region_config.each do |region, blocks|
219
+ config = self.class.new(true).merge(self)
220
+
221
+ # Execute the configuration for each block
222
+ blocks.each { |b| b.call(config) }
223
+
224
+ # The region name of the configuration always equals the
225
+ # region config name:
226
+ config.region = region
227
+
228
+ # Finalize the configuration
229
+ config.finalize!
230
+
231
+ # Store it for retrieval
232
+ @__compiled_region_configs[region] = config
233
+ end
234
+ end
235
+
236
+ # Mark that we finalized
237
+ @__finalized = true
238
+ end
239
+
240
+ def validate(machine)
241
+ errors = []
242
+
243
+ errors << I18n.t("vagrant_aws.config.region_required") if @region.nil?
244
+
245
+ if @region
246
+ # Get the configuration for the region we're using and validate only
247
+ # that region.
248
+ config = get_region_config(@region)
249
+
250
+ if !config.use_iam_profile
251
+ errors << I18n.t("vagrant_aws.config.access_key_id_required") if \
252
+ config.access_key_id.nil?
253
+ errors << I18n.t("vagrant_aws.config.secret_access_key_required") if \
254
+ config.secret_access_key.nil?
255
+ end
256
+
257
+ errors << I18n.t("vagrant_aws.config.ami_required") if config.ami.nil?
258
+ end
259
+
260
+ { "AWS Provider" => errors }
261
+ end
262
+
263
+ # This gets the configuration for a specific region. It shouldn't
264
+ # be called by the general public and is only used internally.
265
+ def get_region_config(name)
266
+ if !@__finalized
267
+ raise "Configuration must be finalized before calling this method."
268
+ end
269
+
270
+ # Return the compiled region config
271
+ @__compiled_region_configs[name] || self
272
+ end
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,23 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module AWS
5
+ module Errors
6
+ class VagrantAWSError < Vagrant::Errors::VagrantError
7
+ error_namespace("vagrant_aws.errors")
8
+ end
9
+
10
+ class FogError < VagrantAWSError
11
+ error_key(:fog_error)
12
+ end
13
+
14
+ class InstanceReadyTimeout < VagrantAWSError
15
+ error_key(:instance_ready_timeout)
16
+ end
17
+
18
+ class RsyncError < VagrantAWSError
19
+ error_key(:rsync_error)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,73 @@
1
+ begin
2
+ require "vagrant"
3
+ rescue LoadError
4
+ raise "The Vagrant AWS plugin must be run within Vagrant."
5
+ end
6
+
7
+ # This is a sanity check to make sure no one is attempting to install
8
+ # this into an early Vagrant version.
9
+ if Vagrant::VERSION < "1.2.0"
10
+ raise "The Vagrant AWS plugin is only compatible with Vagrant 1.2+"
11
+ end
12
+
13
+ module VagrantPlugins
14
+ module AWS
15
+ class Plugin < Vagrant.plugin("2")
16
+ name "AWS"
17
+ description <<-DESC
18
+ This plugin installs a provider that allows Vagrant to manage
19
+ machines in AWS (EC2/VPC).
20
+ DESC
21
+
22
+ config(:aws, :provider) do
23
+ require_relative "config"
24
+ Config
25
+ end
26
+
27
+ provider(:aws) do
28
+ # Setup logging and i18n
29
+ setup_logging
30
+ setup_i18n
31
+
32
+ # Return the provider
33
+ require_relative "provider"
34
+ Provider
35
+ end
36
+
37
+ # This initializes the internationalization strings.
38
+ def self.setup_i18n
39
+ I18n.load_path << File.expand_path("locales/en.yml", AWS.source_root)
40
+ I18n.reload!
41
+ end
42
+
43
+ # This sets up our log level to be whatever VAGRANT_LOG is.
44
+ def self.setup_logging
45
+ require "log4r"
46
+
47
+ level = nil
48
+ begin
49
+ level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
50
+ rescue NameError
51
+ # This means that the logging constant wasn't found,
52
+ # which is fine. We just keep `level` as `nil`. But
53
+ # we tell the user.
54
+ level = nil
55
+ end
56
+
57
+ # Some constants, such as "true" resolve to booleans, so the
58
+ # above error checking doesn't catch it. This will check to make
59
+ # sure that the log level is an integer, as Log4r requires.
60
+ level = nil if !level.is_a?(Integer)
61
+
62
+ # Set the logging level on all "vagrant" namespaced
63
+ # logs as long as we have a valid level.
64
+ if level
65
+ logger = Log4r::Logger.new("vagrant_aws")
66
+ logger.outputters = Log4r::Outputter.stderr
67
+ logger.level = level
68
+ logger = nil
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,50 @@
1
+ require "log4r"
2
+ require "vagrant"
3
+
4
+ module VagrantPlugins
5
+ module AWS
6
+ class Provider < Vagrant.plugin("2", :provider)
7
+ def initialize(machine)
8
+ @machine = machine
9
+ end
10
+
11
+ def action(name)
12
+ # Attempt to get the action method from the Action class if it
13
+ # exists, otherwise return nil to show that we don't support the
14
+ # given action.
15
+ action_method = "action_#{name}"
16
+ return Action.send(action_method) if Action.respond_to?(action_method)
17
+ nil
18
+ end
19
+
20
+ def ssh_info
21
+ # Run a custom action called "read_ssh_info" which does what it
22
+ # says and puts the resulting SSH info into the `:machine_ssh_info`
23
+ # key in the environment.
24
+ env = @machine.action("read_ssh_info")
25
+ env[:machine_ssh_info]
26
+ end
27
+
28
+ def state
29
+ # Run a custom action we define called "read_state" which does
30
+ # what it says. It puts the state in the `:machine_state_id`
31
+ # key in the environment.
32
+ env = @machine.action("read_state")
33
+
34
+ state_id = env[:machine_state_id]
35
+
36
+ # Get the short and long description
37
+ short = I18n.t("vagrant_aws.states.short_#{state_id}")
38
+ long = I18n.t("vagrant_aws.states.long_#{state_id}")
39
+
40
+ # Return the MachineState object
41
+ Vagrant::MachineState.new(state_id, short, long)
42
+ end
43
+
44
+ def to_s
45
+ id = @machine.id.nil? ? "new" : @machine.id
46
+ "AWS (#{id})"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,17 @@
1
+ module VagrantPlugins
2
+ module AWS
3
+ module Util
4
+ class Timer
5
+ # A basic utility method that times the execution of the given
6
+ # block and returns it.
7
+ def self.time
8
+ start_time = Time.now.to_f
9
+ yield
10
+ end_time = Time.now.to_f
11
+
12
+ end_time - start_time
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end