Skip to content

Commit 9f6c185

Browse files
committed
Fill out basic complement of AWS commands, including chef-solo
provisioning, using middleware based implementation.
1 parent 9459f29 commit 9f6c185

18 files changed

+727
-95
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
.bundle
33
Gemfile.lock
44
pkg/*
5+
.rvmrc

README.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# vagrant-aws
2+
3+
`vagrant-aws` is a plugin for [Vagrant](http://vagrantup.com) which allows the user
4+
to instantiate the Vagrant environment on Amazon AWS (using EC2). This document assumes
5+
you are familiar with Vagrant, if not, the project has excellent [documentation](http://vagrantup.com/docs/index.html).
6+
7+
**NOTE:** This plugin is "pre-alpha", see below for the caveats
8+
9+
## Installing / Getting Started
10+
11+
To use this plugin, first install Vagrant, then install the plugin gem. It should be
12+
picked up automatically by vagrant. You can then use the `vagrant aws` commands.
13+
14+
`vagrant-aws` uses [fog](https://github.com/geemus/fog) internally, and you will need to
15+
specify your Amazon AWS credentials in a "fog" file. Create `~/.fog` with:
16+
17+
---
18+
default:
19+
aws_access_key_id: <YOUR ACCESS KEY>
20+
aws_secret_access_key: <YOUR SECRET KEY>
21+
22+
Additionally, although `vagrant-aws` has many useful defaults, you will need to specify your
23+
Amazon AWS key name and the path the associated private key. You can specify this on a
24+
per-environment basis (i.e., in each Vagrantfile) or in a single Vagrantfile in your
25+
`~/.vagrant` directory. In the latter case, create `~/.vagrant/Vagrantfile` with:
26+
27+
Vagrant::Config.run do |config|
28+
config.aws.key_name = "<KEY NAME>"
29+
config.aws.private_key_path = "<PATH/TO/KEY>"
30+
end
31+
32+
With the above in place you should be ready instantiate your Vagrant environment on
33+
Amazon AWS. See below for additional information on configuration, caveats, etc..
34+
35+
## Configuration
36+
37+
`vagrant-aws` defines a new configuration class for use in your Vagrantfile. An example
38+
usage (showing the defaults) would be:
39+
40+
Vagrant::Config.run do |config|
41+
config.aws.region = "us-east-1"
42+
config.aws.availability_zone = nil # Let AWS choose
43+
config.aws.image = "ami-2ec83147" # EBS-backed Ubuntu 10.04 64-bit
44+
config.aws.username = "ubuntu"
45+
config.aws.security_groups = ["default"]
46+
config.aws.flavor = "t1.micro"
47+
end
48+
49+
## Caveats
50+
51+
`vagrant-aws` is "pre-alpha" and currently only supports creation, suspension, resumption
52+
and descruction of the Vagrant environment. Provisioning should be supported for shell,
53+
chef-server and chef-solo, but has only been tested with chef-solo and on an Ubuntu guest.
54+
Only a subset of Vagrant features are supported. Currently port forwarding and shared
55+
directories are not implemented, nor is host networking (although that is less relevant for AWS).
56+
`vagrant-aws` in general has only been tested for a single VM, on OSX 10.6, with chef-solo.
57+

lib/vagrant-aws.rb

+8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
require 'vagrant'
22
require 'vagrant-aws/version'
3+
require 'vagrant-aws/errors'
4+
require 'vagrant-aws/environment'
5+
require 'vagrant-aws/vm'
36
require 'vagrant-aws/config'
7+
require 'vagrant-aws/middleware'
48
require 'vagrant-aws/command'
9+
require 'vagrant-aws/system'
10+
11+
# Add our custom translations to the load path
12+
I18n.load_path << File.expand_path("../../locales/en.yml", __FILE__)

lib/vagrant-aws/action/create.rb

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
module VagrantAWS
2+
class Action
3+
class Create
4+
def initialize(app, env)
5+
@app = app
6+
end
7+
8+
def call(env)
9+
env.ui.info "Creating VM ..."
10+
11+
server_def = server_definition(env["config"])
12+
13+
# Verify AMI is valid (and in the future enable options specific to EBS-based AMIs)
14+
ami = env["vm"].connection.images.get(server_def[:image_id])
15+
16+
env["vm"].vm = env["vm"].connection.servers.create(server_def)
17+
18+
env.ui.info("Created EC2 Server: #{env["vm"].vm.id}")
19+
env.ui.info("Waiting for server to become ready... (this may take a few minutes)")
20+
21+
env["vm"].vm.wait_for { ready? }
22+
env["vm"].connection.create_tags(env["vm"].vm.id, { "name" => env["vm"].name })
23+
24+
env.ui.info("Server available at DNS: #{env["vm"].vm.dns_name}")
25+
26+
@app.call(env)
27+
end
28+
29+
def recover(env)
30+
if env["vm"].created?
31+
return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError)
32+
33+
# Interrupted, destroy the VM
34+
env["actions"].run(:aws_destroy)
35+
end
36+
end
37+
38+
def server_definition(config)
39+
{
40+
:image_id => config.aws.image,
41+
:groups => config.aws.security_groups,
42+
:flavor_id => config.aws.flavor,
43+
:key_name => config.aws.key_name,
44+
:username => config.aws.username,
45+
:private_key_path => config.aws.private_key_path,
46+
:availability_zone => config.aws.availability_zone
47+
}
48+
end
49+
50+
end
51+
end
52+
end
53+
+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module VagrantAWS
2+
class Action
3+
class PopulateSSH
4+
def initialize(app, env)
5+
@app = app
6+
end
7+
8+
def call(env)
9+
raise VagrantAWS::Errors::PrivateKeyFileNotSpecified if env["config"].aws.private_key_path.nil?
10+
11+
env["config"].ssh.host = env["vm"].vm.dns_name
12+
env["config"].ssh.username = env["config"].aws.username
13+
env["config"].ssh.private_key_path = env["config"].aws.private_key_path
14+
env["config"].ssh.port = 22
15+
16+
# Make sure we can connect
17+
begin
18+
env["vm"].vm.wait_for { env["vm"].ssh.up? }
19+
rescue Fog::Errors::Error
20+
raise Vagrant::Errors::SSHConnectionRefused
21+
end
22+
23+
@app.call(env)
24+
end
25+
26+
end
27+
end
28+
end
29+
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
require 'archive/tar/minitar'
2+
3+
module VagrantAWS
4+
class Action
5+
class PrepareProvisioners
6+
def initialize(app, env)
7+
@app = app
8+
@env = env
9+
@env["provision.enabled"] = true if !@env.has_key?("provision.enabled")
10+
@provisioner_configs = []
11+
12+
load_provisioner_configs if provisioning_enabled?
13+
end
14+
15+
def call(env)
16+
@provisioner_configs.each do |provisioner_config|
17+
if provisioner_config.is_a?(Vagrant::Provisioners::ChefSolo::Config)
18+
env.ui.info I18n.t("vagrant.plugins.aws.actions.prepare_provisioners.uploading_chef_resources")
19+
ChefSolo.prepare(provisioner_config)
20+
end
21+
end
22+
@app.call(env)
23+
end
24+
25+
def provisioning_enabled?
26+
!@env["config"].vm.provisioners.empty? && @env["provision.enabled"]
27+
end
28+
29+
def load_provisioner_configs
30+
@env["config"].vm.provisioners.each do |provisioner|
31+
@provisioner_configs << provisioner.config
32+
end
33+
end
34+
35+
class ChefSolo
36+
37+
def self.prepare(config)
38+
my_preparer = new(config)
39+
my_preparer.bootstrap_if_needed
40+
my_preparer.chown_provisioning_folder
41+
my_preparer.copy_and_update_paths
42+
end
43+
44+
def initialize(config)
45+
@config = config
46+
end
47+
48+
def bootstrap_if_needed
49+
begin
50+
@config.env.vm.ssh.execute do |ssh|
51+
ssh.sudo!("which chef-solo")
52+
end
53+
rescue Vagrant::Errors::VagrantError
54+
# Bootstrap chef-solo
55+
@config.env.ui.info I18n.t("vagrant.plugins.aws.actions.prepare_provisioners.chef_not_detected", :binary => "chef-solo")
56+
@config.env.vm.system.bootstrap_chef
57+
end
58+
end
59+
60+
61+
def chown_provisioning_folder
62+
@config.env.vm.ssh.execute do |ssh|
63+
ssh.sudo!("mkdir -p #{@config.provisioning_path}")
64+
ssh.sudo!("chown #{@config.env.config.ssh.username} #{@config.provisioning_path}")
65+
end
66+
end
67+
68+
def copy_and_update_paths
69+
# Copy relevant host paths to remote instance and update provisioner config
70+
# to point to new "vm" paths for cookbooks, etc.
71+
%w{ cookbooks_path roles_path data_bags_path }.each do |path|
72+
copy_host_paths(@config.send(path), path)
73+
@config.send "#{path}=", strip_host_paths(@config.send(path)).push([:vm, path])
74+
end
75+
end
76+
77+
def copy_host_paths(paths, target_directory)
78+
archive = tar_host_folder_paths(paths)
79+
80+
target_d = "#{@config.provisioning_path}/#{target_directory}"
81+
target_f = target_d + '.tar'
82+
83+
@config.env.vm.ssh.upload!(archive.path, target_f)
84+
@config.env.vm.ssh.execute do |ssh|
85+
ssh.sudo!([
86+
"mkdir -p #{target_d}",
87+
"chown #{@config.env.config.ssh.username} #{target_d}",
88+
"tar -C #{target_d} -xf #{target_f}"
89+
])
90+
end
91+
92+
target_directory
93+
end
94+
95+
def tar_host_folder_paths(paths)
96+
tarf = Tempfile.new(['vagrant-chef-solo','.tar'])
97+
Archive::Tar::Minitar::Output.open(tarf) do |outp|
98+
host_folder_paths(paths).each do |full_path|
99+
Dir.chdir(full_path) do |ignored|
100+
Dir.glob("**#{File::SEPARATOR}**") { |entry| Archive::Tar::Minitar.pack_file(entry, outp) }
101+
end
102+
end
103+
end
104+
tarf
105+
end
106+
107+
108+
def host_folder_paths(paths)
109+
paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)
110+
paths.inject([]) do |acc, path|
111+
path = [:host, path] if !path.is_a?(Array)
112+
type, path = path
113+
acc << File.expand_path(path, @config.env.root_path) if type == :host
114+
acc
115+
end
116+
end
117+
118+
def strip_host_paths(paths)
119+
paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)
120+
paths.delete_if { |path| !path.is_a?(Array) || path[0] == :host }
121+
end
122+
end
123+
124+
125+
end
126+
end
127+
end

lib/vagrant-aws/action/resume.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module VagrantAWS
2+
class Action
3+
class Resume
4+
def initialize(app, env)
5+
@app = app
6+
end
7+
8+
def call(env)
9+
if env["vm"].vm.state == "stopped"
10+
raise VagrantAWS::Errors::EBSDeviceRequired, :command => "resume" if env["vm"].vm.root_device_type != "ebs"
11+
env.ui.info I18n.t("vagrant.actions.vm.resume.resuming")
12+
env["vm"].vm.start
13+
end
14+
15+
@app.call(env)
16+
end
17+
18+
end
19+
end
20+
end

lib/vagrant-aws/action/suspend.rb

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
module VagrantAWS
2+
class Action
3+
class Suspend
4+
def initialize(app, env)
5+
@app = app
6+
end
7+
8+
def call(env)
9+
if env["vm"].vm.running?
10+
raise VagrantAWS::Errors::EBSDeviceRequired, :command => "suspend" if env["vm"].vm.root_device_type != "ebs"
11+
env.ui.info I18n.t("vagrant.actions.vm.suspend.suspending")
12+
env["vm"].vm.stop
13+
end
14+
15+
@app.call(env)
16+
end
17+
18+
end
19+
end
20+
end

lib/vagrant-aws/action/terminate.rb

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module VagrantAWS
2+
class Action
3+
class Terminate
4+
def initialize(app, env)
5+
@app = app
6+
end
7+
8+
def call(env)
9+
env.ui.info I18n.t("vagrant.actions.vm.destroy.destroying")
10+
11+
env["vm"].vm.destroy
12+
env["vm"].vm = nil
13+
14+
@app.call(env)
15+
end
16+
17+
end
18+
end
19+
end
20+
21+

0 commit comments

Comments
 (0)