tnargav-aws 0.2.2

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.
@@ -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