vagrant-aws 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in vagrant-aws.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,65 @@
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
+ If you already have an Amazon key pair (created when you signed-up, or
23
+ someother time), you can specify the key name and the path the associated
24
+ private key. Alternately, if no key name is specified, `vagrant-aws` will
25
+ automatically create and register a key pair for you with the name
26
+ `vagrant_<MAC ADDRESS>`. If you want to use your pre-existing key, you can
27
+ specify the key name and path on a per-environment basis (i.e., in each
28
+ Vagrantfile) or in a single Vagrantfile in your `~/.vagrant` directory. In the
29
+ latter case, create `~/.vagrant/Vagrantfile` with:
30
+
31
+ Vagrant::Config.run do |config|
32
+ config.aws.key_name = "<KEY NAME>"
33
+ config.aws.private_key_path = "<PATH/TO/KEY>"
34
+ end
35
+
36
+ With the above in place you should be ready instantiate your Vagrant environment on
37
+ Amazon AWS. See below for additional information on configuration, caveats, etc..
38
+
39
+ ## Configuration and Image Boxes
40
+
41
+ `vagrant-aws` defines a new configuration class for use in your Vagrantfile. An example
42
+ usage (showing the defaults) would be:
43
+
44
+ Vagrant::Config.run do |config|
45
+ config.aws.region = "us-east-1"
46
+ config.aws.availability_zone = nil # Let AWS choose
47
+ config.aws.image = "ami-2ec83147" # EBS-backed Ubuntu 10.04 64-bit
48
+ config.aws.username = "ubuntu"
49
+ config.aws.security_groups = ["default"]
50
+ config.aws.flavor = "t1.micro"
51
+ end
52
+
53
+ Alternately you can work with "image boxes" using the `vagrant aws box_*` commands. These are
54
+ similar to Vagrant's native boxes, and have a similar API, but wrap AWS ami IDs. The major
55
+ difference is that box creation and removal can optionally reregister and deregister AMIs
56
+ with AWS. Note that AMI creation is only supported for EBS-backed instances.
57
+
58
+ ## Caveats
59
+
60
+ `vagrant-aws` is "pre-alpha" and currently only supports creation, suspension, resumption
61
+ and descruction of the Vagrant environment. Provisioning should be supported for shell,
62
+ chef-server and chef-solo, but has only been tested with chef-solo and on an Ubuntu guest.
63
+ Only a subset of Vagrant features are supported. Currently port forwarding and shared
64
+ directories are not implemented, nor is host networking (although that is less relevant for AWS).
65
+ `vagrant-aws` in general has only been tested for a single VM, on OSX 10.6, with chef-solo.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.pattern = 'test/**/*_test.rb'
9
+ end
10
+
@@ -0,0 +1,14 @@
1
+ require 'vagrant'
2
+ require 'vagrant-aws/version'
3
+ require 'vagrant-aws/errors'
4
+ require 'vagrant-aws/environment'
5
+ require 'vagrant-aws/vm'
6
+ require 'vagrant-aws/config'
7
+ require 'vagrant-aws/middleware'
8
+ require 'vagrant-aws/command'
9
+ require 'vagrant-aws/system'
10
+ require 'vagrant-aws/box'
11
+ require 'vagrant-aws/box_collection'
12
+
13
+ # Add our custom translations to the load path
14
+ I18n.load_path << File.expand_path("../../locales/en.yml", __FILE__)
@@ -0,0 +1,56 @@
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
+ raise Errors::KeyNameNotSpecified if env["config"].aws.key_name.nil?
10
+
11
+ env.ui.info I18n.t("vagrant.plugins.aws.actions.create.creating")
12
+
13
+ server_def = server_definition(env["config"])
14
+
15
+ # Verify AMI is valid (and in the future enable options specific to EBS-based AMIs)
16
+ image = env["vm"].connection.images.get(server_def[:image_id])
17
+ image.wait_for { state == 'available' }
18
+
19
+ env["vm"].vm = env["vm"].connection.servers.create(server_def)
20
+ raise VagrantAWS::Errors::VMCreateFailure if env["vm"].vm.nil? || env["vm"].vm.id.nil?
21
+
22
+ env.ui.info I18n.t("vagrant.plugins.aws.actions.create.created", :id => env["vm"].vm.id)
23
+
24
+ env["vm"].vm.wait_for { ready? }
25
+ env["vm"].connection.create_tags(env["vm"].vm.id, { "name" => env["vm"].name })
26
+
27
+ env.ui.info I18n.t("vagrant.plugins.aws.actions.create.available", :dns => env["vm"].vm.dns_name)
28
+
29
+ @app.call(env)
30
+ end
31
+
32
+ def recover(env)
33
+ if env["vm"].created?
34
+ return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError)
35
+
36
+ # Interrupted, destroy the VM
37
+ env["actions"].run(:aws_destroy)
38
+ end
39
+ end
40
+
41
+ def server_definition(config)
42
+ {
43
+ :image_id => config.aws.image,
44
+ :groups => config.aws.security_groups,
45
+ :flavor_id => config.aws.flavor,
46
+ :key_name => config.aws.key_name,
47
+ :username => config.aws.username,
48
+ :private_key_path => config.aws.private_key_path,
49
+ :availability_zone => config.aws.availability_zone
50
+ }
51
+ end
52
+
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,106 @@
1
+ require 'fileutils'
2
+
3
+ # Path vagrant to not delete pre-existing package file (pull request submitted upstream)
4
+
5
+ module Vagrant
6
+ class Action
7
+ module General
8
+
9
+ class Package
10
+ def recover(env)
11
+ unless env["vagrant.error"].is_a?(Errors::PackageOutputExists)
12
+ # Cleanup any packaged files if the packaging failed at some point.
13
+ File.delete(tar_path) if File.exist?(tar_path)
14
+ end
15
+ end
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+
22
+ module VagrantAWS
23
+ class Action
24
+ class CreateImage
25
+ include Vagrant::Util
26
+
27
+ PACKAGE_VAGRANTFILE = <<EOT
28
+ Vagrant::Config.run do |config|
29
+ # This Vagrantfile is auto-generated by `vagrant aws create_image` to contain
30
+ # the image id and region. Custom configuration should be placed in
31
+ # the actual `Vagrantfile` in this box.
32
+ config.vm.base_mac = "000000000000" # Unneeded
33
+ config.aws.image = "<%= image %>"
34
+ config.aws.region = "<%= region %>"
35
+ end
36
+ EOT
37
+
38
+ attr_reader :temp_dir, :image
39
+
40
+ def initialize(app, env)
41
+ @app = app
42
+ @env = env
43
+ end
44
+
45
+ def call(env)
46
+ @env = env
47
+
48
+ raise Vagrant::Errors::VMNotCreatedError if !@env["vm"].created?
49
+ raise Vagrant::Errors::VMNotRunningError if !@env["vm"].vm.running?
50
+ raise VagrantAWS::Errors::EBSDeviceRequired, :command => "box_create" if @env["image.register"] and @env["vm"].vm.root_device_type != "ebs"
51
+
52
+ if @env["image.register"]
53
+ @env.ui.info I18n.t("vagrant.plugins.aws.actions.create_image.creating")
54
+ @image = @env["vm"].connection.create_image(@env["vm"].vm.id, @env['image.name'], @env['image.desc'])
55
+ @image = @env["vm"].connection.images.new({ :id => @image.body['imageId'] })
56
+ @image.wait_for { state == "available" }
57
+ else
58
+ @image = @env["vm"].connection.images.get(@env["vm"].vm.image_id)
59
+ end
60
+
61
+ setup_temp_dir
62
+ export
63
+
64
+ @app.call(@env)
65
+
66
+ cleanup_temp_dir
67
+ end
68
+
69
+ def recover(env)
70
+ if env["image.register"]
71
+ env.ui.info I18n.t("vagrant.plugins.aws.actions.deregister_image.deregistering", :image => @image.id)
72
+ @image.deregister(!@image.root_device_name.nil?) # Don't try to delete backing snapshot if it was not created
73
+ end
74
+ cleanup_temp_dir
75
+ end
76
+
77
+ def cleanup_temp_dir
78
+ if temp_dir && File.exist?(temp_dir)
79
+ FileUtils.rm_rf(temp_dir)
80
+ end
81
+ end
82
+
83
+ def setup_temp_dir
84
+ @env.ui.info I18n.t("vagrant.actions.vm.export.create_dir")
85
+ @temp_dir = @env["export.temp_dir"] = @env.env.tmp_path.join(Time.now.to_i.to_s)
86
+ FileUtils.mkpath(@env["export.temp_dir"])
87
+ end
88
+
89
+ # Write a Vagrantfile with the relevant information
90
+ def export
91
+ File.open(File.join(@env["export.temp_dir"], 'Vagrantfile'), "w") do |f|
92
+ f.write(TemplateRenderer.render_string(PACKAGE_VAGRANTFILE, {
93
+ :image => @image.id,
94
+ :region => @env["config"].aws.region
95
+ }))
96
+ end
97
+ File.open(File.join(@env["export.temp_dir"], 'image.json'), "w") do |f|
98
+ f.write(@image.to_json)
99
+ end
100
+ end
101
+
102
+
103
+ end
104
+ end
105
+ end
106
+
@@ -0,0 +1,39 @@
1
+ require 'Macaddr'
2
+
3
+ module VagrantAWS
4
+ class Action
5
+ class CreateSSHKey
6
+ def initialize(app, env)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ @env = env
12
+
13
+ if @env["config"].aws.key_name.nil?
14
+ # Do we have a previously created key available on AWS?
15
+ key = @env["vm"].connection.key_pairs.all('key-name' => @env.env.ssh_keys).first
16
+ if key.nil?
17
+ # Create and save key
18
+ key = @env["vm"].connection.key_pairs.create(:name => "vagrantaws_#{Mac.addr.gsub(':','')}")
19
+ env.ui.info I18n.t("vagrant.plugins.aws.actions.create_ssh_key.created", :name => key.name)
20
+ File.open(local_key_path(key.name), File::WRONLY|File::TRUNC|File::CREAT, 0600) { |f| f.write(key.private_key) }
21
+ end
22
+
23
+ @env["config"].aws.key_name = key.name
24
+ @env["config"].aws.private_key_path = local_key_path(key.name)
25
+ end
26
+
27
+ @app.call(env)
28
+ end
29
+
30
+ def local_key_path(name)
31
+ @env.env.ssh_keys_path.join(name)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+
@@ -0,0 +1,27 @@
1
+ module VagrantAWS
2
+ class Action
3
+ class DeregisterImage
4
+ def initialize(app, env)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ if env['image.deregister']
10
+ image = Fog::Compute.new(:provider => 'AWS').images.new(load_image(env))
11
+ env.ui.info I18n.t("vagrant.plugins.aws.actions.deregister_image.deregistering", :image => image.id)
12
+ image.reload
13
+ image.deregister(true) # Delete snapshot when deregistering
14
+ end
15
+ @app.call(env)
16
+ end
17
+
18
+ def load_image(env)
19
+ File.open(File.join(env["box"].directory, "image.json"), "r") do |f|
20
+ JSON.parse(f.read)
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+
@@ -0,0 +1,41 @@
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
+ @env = env
10
+
11
+ if @env["config"].aws.private_key_path.nil?
12
+ # See if we are using a key vagrant aws generated
13
+ @env["config"].aws.private_key_path = local_key_path(env["vm"].vm.key_name) if env["vm"].vm.key_name =~ /^vagrantaws_[0-9a-f]{12}/
14
+ end
15
+
16
+ raise VagrantAWS::Errors::PrivateKeyFileNotSpecified if env["config"].aws.private_key_path.nil? || !File.exists?(env["config"].aws.private_key_path)
17
+
18
+ env["config"].ssh.host = env["vm"].vm.dns_name
19
+ env["config"].ssh.username = env["config"].aws.username
20
+ env["config"].ssh.private_key_path = env["config"].aws.private_key_path
21
+ env["config"].ssh.port = 22
22
+
23
+ # Make sure we can connect
24
+ begin
25
+ env["vm"].vm.wait_for { env["vm"].ssh.up? }
26
+ rescue Fog::Errors::Error
27
+ raise Vagrant::Errors::SSHConnectionRefused
28
+ end
29
+
30
+ @app.call(env)
31
+ end
32
+
33
+ def local_key_path(name)
34
+ @env.env.ssh_keys_path.join(name)
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+
41
+
@@ -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