vagrant-openstack-plugin 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .vagrant
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ Vagrantfile
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.0 (March 14, 2013)
2
+
3
+ * Initial release.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ # We depend on Vagrant for development, but we don't add it as a
7
+ # gem dependency because we expect to be installed within the
8
+ # Vagrant environment itself using `vagrant plugin`.
9
+ #gem "vagrant", :git => "git://github.com/mitchellh/vagrant.git"
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2013 Mitchell Hashimoto
2
+ Copyright (c) 2013 cloudbau GmbH
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,160 @@
1
+ # Vagrant OpenStack Cloud Provider
2
+
3
+ This is a [Vagrant](http://www.vagrantup.com) 1.1+ plugin that adds a
4
+ [OpenStack Cloud](http://www.openstack.org) provider to Vagrant,
5
+ allowing Vagrant to control and provision machines within an OpenStack
6
+ cloud.
7
+
8
+ This plugin started as a fork of the Vagrant RackSpace provider.
9
+
10
+ **Note:** This plugin requires Vagrant 1.1+.
11
+
12
+ ## Features
13
+
14
+ * Boot OpenStack Cloud instances.
15
+ * SSH into the instances.
16
+ * Provision the instances with any built-in Vagrant provisioner.
17
+ * Minimal synced folder support via `rsync`.
18
+
19
+ ## Usage
20
+
21
+ Install using standard Vagrant 1.1+ plugin installation methods. After
22
+ installing, `vagrant up` and specify the `openstack` provider. An example is
23
+ shown below.
24
+
25
+ ```
26
+ $ vagrant plugin install vagrant-openstack
27
+ ...
28
+ $ vagrant up --provider=openstack
29
+ ...
30
+ ```
31
+
32
+ Of course prior to doing this, you'll need to obtain an OpenStack-compatible
33
+ box file for Vagrant.
34
+
35
+ ## Quick Start
36
+
37
+ After installing the plugin (instructions above), the quickest way to get
38
+ started is to actually use a dummy OpenStack box and specify all the details
39
+ manually within a `config.vm.provider` block. So first, add the dummy
40
+ box using any name you want:
41
+
42
+ ```
43
+ $ vagrant box add dummy https://github.com/cloudbau/vagrant-openstack/raw/master/dummy.box
44
+ ...
45
+ ```
46
+
47
+ And then make a Vagrantfile that looks like the following, filling in
48
+ your information where necessary.
49
+
50
+ ```
51
+ require 'vagrant-openstack'
52
+
53
+ Vagrant.configure("2") do |config|
54
+ config.vm.box = "dummy"
55
+
56
+ config.vm.provider :openstack do |os| # e.g.
57
+ os.username = "YOUR USERNAME" # "#{ENV['OS_USERNAME']}"
58
+ os.api_key = "YOUR API KEY" # "#{ENV['OS_PASSWORD']}"
59
+ os.flavor = /m1.tiny/
60
+ os.image = /Ubuntu/
61
+ os.endpoint = "KEYSTONE AUTH URL" # "#{ENV['OS_AUTH_URL']}/tokens"
62
+ os.keypair_name = "YOUR KEYPAIR NAME"
63
+ os.ssh_username = "SSH USERNAME"
64
+ end
65
+ end
66
+ ```
67
+
68
+ And then run `vagrant up --provider=openstack`.
69
+
70
+ This will start a tiny Ubuntu instance in your OpenStack installation within
71
+ your tenant. And assuming your SSH information was filled in properly
72
+ within your Vagrantfile, SSH and provisioning will work as well.
73
+
74
+ Note that normally a lot of this boilerplate is encoded within the box
75
+ file, but the box file used for the quick start, the "dummy" box, has
76
+ no preconfigured defaults.
77
+
78
+ ## Box Format
79
+
80
+ Every provider in Vagrant must introduce a custom box format. This
81
+ provider introduces `openstack` boxes. You can view an example box in
82
+ the [example_box/ directory](https://github.com/cloudbau/vagrant-openstack/tree/master/example_box).
83
+ That directory also contains instructions on how to build a box.
84
+
85
+ The box format is basically just the required `metadata.json` file
86
+ along with a `Vagrantfile` that does default settings for the
87
+ provider-specific configuration for this provider.
88
+
89
+ ## Configuration
90
+
91
+ This provider exposes quite a few provider-specific configuration options:
92
+
93
+ * `api_key` - The API key for accessing OpenStack.
94
+ * `flavor` - The server flavor to boot. This can be a string matching
95
+ the exact ID or name of the server, or this can be a regular expression
96
+ to partially match some server flavor.
97
+ * `image` - The server image to boot. This can be a string matching the
98
+ exact ID or name of the image, or this can be a regular expression to
99
+ partially match some image.
100
+ * `endpoint` - The keystone authentication URL of your OpenStack installation.
101
+ * `server_name` - The name of the server within the OpenStack Cloud. This
102
+ defaults to the name of the Vagrant machine (via `config.vm.define`), but
103
+ can be overridden with this.
104
+ * `username` - The username with which to access OpenStack.
105
+ * `keypair_name` - The name of the keypair to access the machine.
106
+ * `ssh_username` - The username to access the machine.
107
+
108
+ These can be set like typical provider-specific configuration:
109
+
110
+ ```ruby
111
+ Vagrant.configure("2") do |config|
112
+ # ... other stuff
113
+
114
+ config.vm.provider :openstack do |rs|
115
+ rs.username = "mitchellh"
116
+ rs.api_key = "foobarbaz"
117
+ end
118
+ end
119
+ ```
120
+
121
+ ## Networks
122
+
123
+ Networking features in the form of `config.vm.network` are not
124
+ supported with `vagrant-openstack`, currently. If any of these are
125
+ specified, Vagrant will emit a warning, but will otherwise boot
126
+ the OpenStack server.
127
+
128
+ ## Synced Folders
129
+
130
+ There is minimal support for synced folders. Upon `vagrant up`,
131
+ `vagrant reload`, and `vagrant provision`, the OpenStack provider will use
132
+ `rsync` (if available) to uni-directionally sync the folder to
133
+ the remote machine over SSH.
134
+
135
+ This is good enough for all built-in Vagrant provisioners (shell,
136
+ chef, and puppet) to work!
137
+
138
+ ## Development
139
+
140
+ To work on the `vagrant-openstack` plugin, clone this repository out, and use
141
+ [Bundler](http://gembundler.com) to get the dependencies:
142
+
143
+ ```
144
+ $ bundle
145
+ ```
146
+
147
+ Once you have the dependencies, verify the unit tests pass with `rake`:
148
+
149
+ ```
150
+ $ bundle exec rake
151
+ ```
152
+
153
+ If those pass, you're ready to start developing the plugin. You can test
154
+ the plugin without installing it into your Vagrant environment by just
155
+ creating a `Vagrantfile` in the top level of this directory (it is gitignored)
156
+ that uses it, and uses bundler to execute Vagrant:
157
+
158
+ ```
159
+ $ bundle exec vagrant up --provider=openstack
160
+ ```
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rspec/core/rake_task'
4
+
5
+ # Immediately sync all stdout so that tools like buildbot can
6
+ # immediately load in the output.
7
+ $stdout.sync = true
8
+ $stderr.sync = true
9
+
10
+ # Change to the directory of this file.
11
+ Dir.chdir(File.expand_path("../", __FILE__))
12
+
13
+ # This installs the tasks that help with gem creation and
14
+ # publishing.
15
+ Bundler::GemHelper.install_tasks
16
+
17
+ # Install the `spec` task so that we can run tests.
18
+ RSpec::Core::RakeTask.new
19
+
20
+ # Default task is to run the unit tests
21
+ task :default => "spec"
data/dummy.box ADDED
Binary file
@@ -0,0 +1,13 @@
1
+ # Vagrant OpenStack Cloud Example Box
2
+
3
+ Vagrant providers each require a custom provider-specific box format.
4
+ This folder shows the example contents of a box for the `openstack` provider.
5
+ To turn this into a box:
6
+
7
+ ```
8
+ $ tar cvzf openstack.box ./metadata.json ./Vagrantfile
9
+ ```
10
+
11
+ This box works by using Vagrant's built-in Vagrantfile merging to setup
12
+ defaults for OpenStack. These defaults can easily be overwritten by higher-level
13
+ Vagrantfiles (such as project root Vagrantfiles).
@@ -0,0 +1,3 @@
1
+ {
2
+ "provider": "openstack"
3
+ }
@@ -0,0 +1,53 @@
1
+ require "pathname"
2
+
3
+ require "vagrant-openstack/plugin"
4
+
5
+ module VagrantPlugins
6
+ module OpenStack
7
+ lib_path = Pathname.new(File.expand_path("../vagrant-openstack", __FILE__))
8
+ autoload :Errors, lib_path.join("errors")
9
+
10
+ # This initializes the i18n load path so that the plugin-specific
11
+ # translations work.
12
+ def self.init_i18n
13
+ I18n.load_path << File.expand_path("locales/en.yml", source_root)
14
+ I18n.reload!
15
+ end
16
+
17
+ # This initializes the logging so that our logs are outputted at
18
+ # the same level as Vagrant core logs.
19
+ def self.init_logging
20
+ # Initialize logging
21
+ level = nil
22
+ begin
23
+ level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
24
+ rescue NameError
25
+ # This means that the logging constant wasn't found,
26
+ # which is fine. We just keep `level` as `nil`. But
27
+ # we tell the user.
28
+ level = nil
29
+ end
30
+
31
+ # Some constants, such as "true" resolve to booleans, so the
32
+ # above error checking doesn't catch it. This will check to make
33
+ # sure that the log level is an integer, as Log4r requires.
34
+ level = nil if !level.is_a?(Integer)
35
+
36
+ # Set the logging level on all "vagrant" namespaced
37
+ # logs as long as we have a valid level.
38
+ if level
39
+ logger = Log4r::Logger.new("vagrant_openstack")
40
+ logger.outputters = Log4r::Outputter.stderr
41
+ logger.level = level
42
+ logger = nil
43
+ end
44
+ end
45
+
46
+ # This returns the path to the source of this plugin.
47
+ #
48
+ # @return [Pathname]
49
+ def self.source_root
50
+ @source_root ||= Pathname.new(File.expand_path("../../", __FILE__))
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,110 @@
1
+ require "pathname"
2
+
3
+ require "vagrant/action/builder"
4
+
5
+ module VagrantPlugins
6
+ module OpenStack
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 destroy the remote machine.
12
+ def self.action_destroy
13
+ Vagrant::Action::Builder.new.tap do |b|
14
+ b.use ConfigValidate
15
+ b.use Call, IsCreated do |env, b2|
16
+ if !env[:result]
17
+ b2.use MessageNotCreated
18
+ next
19
+ end
20
+
21
+ b2.use ConnectOpenStack
22
+ b2.use DeleteServer
23
+ end
24
+ end
25
+ end
26
+
27
+ # This action is called to read the SSH info of the machine. The
28
+ # resulting state is expected to be put into the `:machine_ssh_info`
29
+ # key.
30
+ def self.action_read_ssh_info
31
+ Vagrant::Action::Builder.new.tap do |b|
32
+ b.use ConfigValidate
33
+ b.use ConnectOpenStack
34
+ b.use ReadSSHInfo
35
+ end
36
+ end
37
+
38
+ # This action is called to read the state of the machine. The
39
+ # resulting state is expected to be put into the `:machine_state_id`
40
+ # key.
41
+ def self.action_read_state
42
+ Vagrant::Action::Builder.new.tap do |b|
43
+ b.use ConfigValidate
44
+ b.use ConnectOpenStack
45
+ b.use ReadState
46
+ end
47
+ end
48
+
49
+ def self.action_ssh
50
+ Vagrant::Action::Builder.new.tap do |b|
51
+ b.use ConfigValidate
52
+ b.use Call, IsCreated do |env, b2|
53
+ if !env[:result]
54
+ b2.use MessageNotCreated
55
+ next
56
+ end
57
+
58
+ b2.use SSHExec
59
+ end
60
+ end
61
+ end
62
+
63
+ def self.action_up
64
+ Vagrant::Action::Builder.new.tap do |b|
65
+ b.use ConfigValidate
66
+ b.use Call, IsCreated do |env, b2|
67
+ if env[:result]
68
+ b2.use MessageAlreadyCreated
69
+ next
70
+ end
71
+
72
+ b2.use ConnectOpenStack
73
+ b2.use Provision
74
+ b2.use SyncFolders
75
+ b2.use WarnNetworks
76
+ b2.use CreateServer
77
+ end
78
+ end
79
+ end
80
+
81
+ def self.action_provision
82
+ Vagrant::Action::Builder.new.tap do |b|
83
+ b.use ConfigValidate
84
+ b.use Call, IsCreated do |env, b2|
85
+ if !env[:result]
86
+ b2.use MessageNotCreated
87
+ next
88
+ end
89
+
90
+ b2.use Provision
91
+ b2.use SyncFolders
92
+ end
93
+ end
94
+ end
95
+
96
+ # The autoload farm
97
+ action_root = Pathname.new(File.expand_path("../action", __FILE__))
98
+ autoload :ConnectOpenStack, action_root.join("connect_openstack")
99
+ autoload :CreateServer, action_root.join("create_server")
100
+ autoload :DeleteServer, action_root.join("delete_server")
101
+ autoload :IsCreated, action_root.join("is_created")
102
+ autoload :MessageAlreadyCreated, action_root.join("message_already_created")
103
+ autoload :MessageNotCreated, action_root.join("message_not_created")
104
+ autoload :ReadSSHInfo, action_root.join("read_ssh_info")
105
+ autoload :ReadState, action_root.join("read_state")
106
+ autoload :SyncFolders, action_root.join("sync_folders")
107
+ autoload :WarnNetworks, action_root.join("warn_networks")
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,36 @@
1
+ require "fog"
2
+ require "log4r"
3
+
4
+ module VagrantPlugins
5
+ module OpenStack
6
+ module Action
7
+ # This action connects to OpenStack, verifies credentials work, and
8
+ # puts the OpenStack connection object into the `:openstack_compute` key
9
+ # in the environment.
10
+ class ConnectOpenStack
11
+ def initialize(app, env)
12
+ @app = app
13
+ @logger = Log4r::Logger.new("vagrant_openstack::action::connect_openstack")
14
+ end
15
+
16
+ def call(env)
17
+ # Get the configs
18
+ config = env[:machine].provider_config
19
+ api_key = config.api_key
20
+ endpoint = config.endpoint
21
+ username = config.username
22
+
23
+ @logger.info("Connecting to OpenStack...")
24
+ env[:openstack_compute] = Fog::Compute.new({
25
+ :provider => :openstack,
26
+ :openstack_username => username,
27
+ :openstack_api_key => api_key,
28
+ :openstack_auth_url => endpoint
29
+ })
30
+
31
+ @app.call(env)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,117 @@
1
+ require "fog"
2
+ require "log4r"
3
+
4
+ require 'vagrant/util/retryable'
5
+
6
+ module VagrantPlugins
7
+ module OpenStack
8
+ module Action
9
+ # This creates the OpenStack server.
10
+ class CreateServer
11
+ include Vagrant::Util::Retryable
12
+
13
+ def initialize(app, env)
14
+ @app = app
15
+ @logger = Log4r::Logger.new("vagrant_openstack::action::create_server")
16
+ end
17
+
18
+ def call(env)
19
+ # Get the configs
20
+ config = env[:machine].provider_config
21
+
22
+ # Find the flavor
23
+ env[:ui].info(I18n.t("vagrant_openstack.finding_flavor"))
24
+ flavor = find_matching(env[:openstack_compute].flavors.all, config.flavor)
25
+ raise Errors::NoMatchingFlavor if !flavor
26
+
27
+ # Find the image
28
+ env[:ui].info(I18n.t("vagrant_openstack.finding_image"))
29
+ image = find_matching(env[:openstack_compute].images, config.image)
30
+ raise Errors::NoMatchingImage if !image
31
+
32
+ # Figure out the name for the server
33
+ server_name = config.server_name || env[:machine].name
34
+
35
+ # Output the settings we're going to use to the user
36
+ env[:ui].info(I18n.t("vagrant_openstack.launching_server"))
37
+ env[:ui].info(" -- Flavor: #{flavor.name}")
38
+ env[:ui].info(" -- Image: #{image.name}")
39
+ env[:ui].info(" -- Name: #{server_name}")
40
+
41
+ # Build the options for launching...
42
+ options = {
43
+ :flavor_ref => flavor.id,
44
+ :image_ref => image.id,
45
+ :name => server_name,
46
+ :key_name => config.keypair_name,
47
+ :user_data_encoded => Base64.encode64(config.user_data)
48
+ }
49
+
50
+ # Create the server
51
+ server = env[:openstack_compute].servers.create(options)
52
+
53
+ # Store the ID right away so we can track it
54
+ env[:machine].id = server.id
55
+
56
+ # Wait for the server to finish building
57
+ env[:ui].info(I18n.t("vagrant_openstack.waiting_for_build"))
58
+ retryable(:on => Timeout::Error, :tries => 200) do
59
+ # If we're interrupted don't worry about waiting
60
+ next if env[:interrupted]
61
+
62
+ # Set the progress
63
+ env[:ui].clear_line
64
+ env[:ui].report_progress(server.progress, 100, false)
65
+
66
+ # Wait for the server to be ready
67
+ begin
68
+ server.wait_for(5) { ready? }
69
+ rescue RuntimeError => e
70
+ # If we don't have an error about a state transition, then
71
+ # we just move on.
72
+ raise if e.message !~ /should have transitioned/
73
+ raise Errors::CreateBadState, :state => server.state
74
+ end
75
+ end
76
+
77
+ if !env[:interrupted]
78
+ # Clear the line one more time so the progress is removed
79
+ env[:ui].clear_line
80
+
81
+ # Wait for SSH to become available
82
+ env[:ui].info(I18n.t("vagrant_openstack.waiting_for_ssh"))
83
+ while true
84
+ begin
85
+ # If we're interrupted then just back out
86
+ break if env[:interrupted]
87
+ break if env[:machine].communicate.ready?
88
+ rescue Errno::ENETUNREACH
89
+ end
90
+ sleep 2
91
+ end
92
+
93
+ env[:ui].info(I18n.t("vagrant_openstack.ready"))
94
+ end
95
+
96
+ @app.call(env)
97
+ end
98
+
99
+ protected
100
+
101
+ # This method finds a matching _thing_ in a collection of
102
+ # _things_. This works matching if the ID or NAME equals to
103
+ # `name`. Or, if `name` is a regexp, a partial match is chosen
104
+ # as well.
105
+ def find_matching(collection, name)
106
+ collection.each do |single|
107
+ return single if single.id == name
108
+ return single if single.name == name
109
+ return single if name.is_a?(Regexp) && name =~ single.name
110
+ end
111
+
112
+ nil
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,26 @@
1
+ require "log4r"
2
+
3
+ module VagrantPlugins
4
+ module OpenStack
5
+ module Action
6
+ # This deletes the running server, if there is one.
7
+ class DeleteServer
8
+ def initialize(app, env)
9
+ @app = app
10
+ @logger = Log4r::Logger.new("vagrant_openstack::action::delete_server")
11
+ end
12
+
13
+ def call(env)
14
+ if env[:machine].id
15
+ env[:ui].info(I18n.t("vagrant_openstack.deleting_server"))
16
+ server = env[:openstack_compute].servers.get(env[:machine].id)
17
+ server.destroy
18
+ env[:machine].id = nil
19
+ end
20
+
21
+ @app.call(env)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module OpenStack
3
+ module Action
4
+ class IsCreated
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:result] = env[:machine].state.id != :not_created
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module OpenStack
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_openstack.already_created"))
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module VagrantPlugins
2
+ module OpenStack
3
+ module Action
4
+ class MessageNotCreated
5
+ def initialize(app, env)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[:ui].info(I18n.t("vagrant_openstack.not_created"))
11
+ @app.call(env)
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ require "log4r"
2
+
3
+ module VagrantPlugins
4
+ module OpenStack
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_openstack::action::read_ssh_info")
12
+ end
13
+
14
+ def call(env)
15
+ env[:machine_ssh_info] = read_ssh_info(env[:openstack_compute], env[:machine])
16
+
17
+ @app.call(env)
18
+ end
19
+
20
+ def read_ssh_info(openstack, machine)
21
+ return nil if machine.id.nil?
22
+
23
+ # Find the machine
24
+ server = openstack.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
+ config = machine.provider_config
33
+
34
+ host = server.addresses['public'].last['addr'] rescue nil
35
+ # Read the DNS info
36
+ return {
37
+ # Usually there should only be one public IP
38
+ :host => host,
39
+ :port => 22,
40
+ :username => config.ssh_username
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ require "log4r"
2
+
3
+ module VagrantPlugins
4
+ module OpenStack
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_openstack::action::read_state")
12
+ end
13
+
14
+ def call(env)
15
+ env[:machine_state_id] = read_state(env[:openstack_compute], env[:machine])
16
+
17
+ @app.call(env)
18
+ end
19
+
20
+ def read_state(openstack, machine)
21
+ return :not_created if machine.id.nil?
22
+
23
+ # Find the machine
24
+ server = openstack.servers.get(machine.id)
25
+ if server.nil? || server.state == "DELETED"
26
+ # The machine can't be found
27
+ @logger.info("Machine not found or deleted, assuming it got destroyed.")
28
+ machine.id = nil
29
+ return :not_created
30
+ end
31
+
32
+ # Return the state
33
+ return server.state.downcase.to_sym
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,57 @@
1
+ require "log4r"
2
+
3
+ require "vagrant/util/subprocess"
4
+
5
+ module VagrantPlugins
6
+ module OpenStack
7
+ module Action
8
+ # This middleware uses `rsync` to sync the folders over to the
9
+ # remote instance.
10
+ class SyncFolders
11
+ def initialize(app, env)
12
+ @app = app
13
+ @logger = Log4r::Logger.new("vagrant_openstack::action::sync_folders")
14
+ end
15
+
16
+ def call(env)
17
+ @app.call(env)
18
+
19
+ ssh_info = env[:machine].ssh_info
20
+
21
+ env[:machine].config.vm.synced_folders.each do |id, data|
22
+ hostpath = File.expand_path(data[:hostpath], env[:root_path])
23
+ guestpath = data[:guestpath]
24
+
25
+ # Make sure there is a trailing slash on the host path to
26
+ # avoid creating an additional directory with rsync
27
+ hostpath = "#{hostpath}/" if hostpath !~ /\/$/
28
+
29
+ env[:ui].info(I18n.t("vagrant_openstack.rsync_folder",
30
+ :hostpath => hostpath,
31
+ :guestpath => guestpath))
32
+
33
+ # Create the guest path
34
+ env[:machine].communicate.sudo("mkdir -p '#{guestpath}'")
35
+ env[:machine].communicate.sudo(
36
+ "chown #{ssh_info[:username]} '#{guestpath}'")
37
+
38
+ # Rsync over to the guest path using the SSH info
39
+ command = [
40
+ "rsync", "--verbose", "--archive", "-z",
41
+ "-e", "ssh -p #{ssh_info[:port]} -i '#{ssh_info[:private_key_path]}'",
42
+ hostpath,
43
+ "#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"]
44
+
45
+ r = Vagrant::Util::Subprocess.execute(*command)
46
+ if r.exit_code != 0
47
+ raise Errors::RsyncError,
48
+ :guestpath => guestpath,
49
+ :hostpath => hostpath,
50
+ :stderr => r.stderr
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ module VagrantPlugins
2
+ module OpenStack
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_openstack.warn_networks"))
12
+ end
13
+
14
+ @app.call(env)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,92 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module OpenStack
5
+ class Config < Vagrant.plugin("2", :config)
6
+ # The API key to access OpenStack.
7
+ #
8
+ # @return [String]
9
+ attr_accessor :api_key
10
+
11
+ # The endpoint to access OpenStack. If nil, it will default
12
+ # to DFW.
13
+ #
14
+ # @return [String]
15
+ attr_accessor :endpoint
16
+
17
+ # The flavor of server to launch, either the ID or name. This
18
+ # can also be a regular expression to partially match a name.
19
+ attr_accessor :flavor
20
+
21
+ # The name or ID of the image to use. This can also be a regular
22
+ # expression to partially match a name.
23
+ attr_accessor :image
24
+
25
+ # The name of the server. This defaults to the name of the machine
26
+ # defined by Vagrant (via `config.vm.define`), but can be overriden
27
+ # here.
28
+ attr_accessor :server_name
29
+
30
+ # The username to access OpenStack.
31
+ #
32
+ # @return [String]
33
+ attr_accessor :username
34
+
35
+ # The name of the keypair to use.
36
+ #
37
+ # @return [String]
38
+ attr_accessor :keypair_name
39
+
40
+ # The SSH username to use with this OpenStack instance. This overrides
41
+ # the `config.ssh.username` variable.
42
+ #
43
+ # @return [String]
44
+ attr_accessor :ssh_username
45
+
46
+ # User data to be sent to the newly created OpenStack instance. Use this
47
+ # e.g. to inject a script at boot time.
48
+ #
49
+ # @return [String]
50
+ attr_accessor :user_data
51
+
52
+ def initialize
53
+ @api_key = UNSET_VALUE
54
+ @endpoint = UNSET_VALUE
55
+ @flavor = UNSET_VALUE
56
+ @image = UNSET_VALUE
57
+ @server_name = UNSET_VALUE
58
+ @username = UNSET_VALUE
59
+ @keypair_name = UNSET_VALUE
60
+ @ssh_username = UNSET_VALUE
61
+ @user_data = UNSET_VALUE
62
+ end
63
+
64
+ def finalize!
65
+ @api_key = nil if @api_key == UNSET_VALUE
66
+ @endpoint = nil if @endpoint == UNSET_VALUE
67
+ @flavor = /m1.tiny/ if @flavor == UNSET_VALUE
68
+ @image = /cirros/ if @image == UNSET_VALUE
69
+ @server_name = nil if @server_name == UNSET_VALUE
70
+ @username = nil if @username == UNSET_VALUE
71
+
72
+ # Keypair defaults to nil
73
+ @keypair_name = nil if @keypair_name == UNSET_VALUE
74
+
75
+ # The SSH values by default are nil, and the top-level config
76
+ # `config.ssh` values are used.
77
+ @ssh_username = nil if @ssh_username == UNSET_VALUE
78
+
79
+ @user_data = "" if @user_data == UNSET_VALUE
80
+ end
81
+
82
+ def validate(machine)
83
+ errors = []
84
+
85
+ errors << I18n.t("vagrant_openstack.config.api_key_required") if !@api_key
86
+ errors << I18n.t("vagrant_openstack.config.username_required") if !@username
87
+
88
+ { "OpenStack Provider" => errors }
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,27 @@
1
+ require "vagrant"
2
+
3
+ module VagrantPlugins
4
+ module OpenStack
5
+ module Errors
6
+ class VagrantOpenStackError < Vagrant::Errors::VagrantError
7
+ error_namespace("vagrant_openstack.errors")
8
+ end
9
+
10
+ class CreateBadState < VagrantOpenStackError
11
+ error_key(:create_bad_state)
12
+ end
13
+
14
+ class NoMatchingFlavor < VagrantOpenStackError
15
+ error_key(:no_matching_flavor)
16
+ end
17
+
18
+ class NoMatchingImage < VagrantOpenStackError
19
+ error_key(:no_matching_image)
20
+ end
21
+
22
+ class RsyncError < VagrantOpenStackError
23
+ error_key(:rsync_error)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ begin
2
+ require "vagrant"
3
+ rescue LoadError
4
+ raise "The OpenStack Cloud provider 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.1.0"
10
+ raise "OpenStack Cloud provider is only compatible with Vagrant 1.1+"
11
+ end
12
+
13
+ module VagrantPlugins
14
+ module OpenStack
15
+ class Plugin < Vagrant.plugin("2")
16
+ name "OpenStack Cloud"
17
+ description <<-DESC
18
+ This plugin enables Vagrant to manage machines in OpenStack Cloud.
19
+ DESC
20
+
21
+ config(:openstack, :provider) do
22
+ require_relative "config"
23
+ Config
24
+ end
25
+
26
+ provider(:openstack) do
27
+ # Setup some things
28
+ OpenStack.init_i18n
29
+ OpenStack.init_logging
30
+
31
+ # Load the actual provider
32
+ require_relative "provider"
33
+ Provider
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ require "vagrant"
2
+
3
+ require "vagrant-openstack/action"
4
+
5
+ module VagrantPlugins
6
+ module OpenStack
7
+ class Provider < Vagrant.plugin("2", :provider)
8
+ def initialize(machine)
9
+ @machine = machine
10
+ end
11
+
12
+ def action(name)
13
+ # Attempt to get the action method from the Action class if it
14
+ # exists, otherwise return nil to show that we don't support the
15
+ # given action.
16
+ action_method = "action_#{name}"
17
+ return Action.send(action_method) if Action.respond_to?(action_method)
18
+ nil
19
+ end
20
+
21
+ def ssh_info
22
+ # Run a custom action called "read_ssh_info" which does what it
23
+ # says and puts the resulting SSH info into the `:machine_ssh_info`
24
+ # key in the environment.
25
+ env = @machine.action("read_ssh_info")
26
+ env[:machine_ssh_info]
27
+ end
28
+
29
+ def state
30
+ # Run a custom action we define called "read_state" which does
31
+ # what it says. It puts the state in the `:machine_state_id`
32
+ # key in the environment.
33
+ env = @machine.action("read_state")
34
+
35
+ state_id = env[:machine_state_id]
36
+
37
+ # Get the short and long description
38
+ short = I18n.t("vagrant_openstack.states.short_#{state_id}")
39
+ long = I18n.t("vagrant_openstack.states.long_#{state_id}")
40
+
41
+ # Return the MachineState object
42
+ Vagrant::MachineState.new(state_id, short, long)
43
+ end
44
+
45
+ def to_s
46
+ "OpenStack Cloud"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ module VagrantPlugins
2
+ module OpenStack
3
+ VERSION = "0.1.1"
4
+ end
5
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,73 @@
1
+ en:
2
+ vagrant_openstack:
3
+ already_created: |-
4
+ The server is already created.
5
+ deleting_server: |-
6
+ Deleting server...
7
+ finding_flavor: |-
8
+ Finding flavor for server...
9
+ finding_image: |-
10
+ Finding image for server...
11
+ launching_server: |-
12
+ Launching a server with the following settings...
13
+ not_created: |-
14
+ The server hasn't been created yet. Run `vagrant up` first.
15
+ ready: |-
16
+ The server is ready!
17
+ rsync_folder: |-
18
+ Rsyncing folder: %{hostpath} => %{guestpath}
19
+ waiting_for_build: |-
20
+ Waiting for the server to be built...
21
+ waiting_for_ssh: |-
22
+ Waiting for SSH to become available...
23
+ warn_networks: |-
24
+ Warning! The OpenStack provider doesn't support any of the Vagrant
25
+ high-level network configurations (`config.vm.network`). They
26
+ will be silently ignored.
27
+
28
+ config:
29
+ api_key_required: |-
30
+ An API key is required.
31
+ username_required: |-
32
+ A username is required.
33
+
34
+ errors:
35
+ create_bad_state: |-
36
+ While creating the server, it transitioned to an unexpected
37
+ state: '%{state}', instead of properly booting. Run `vagrant status`
38
+ to find out what can be done about this state, or `vagrant destroy`
39
+ if you want to start over.
40
+ no_matching_flavor: |-
41
+ No matching flavor was found! Please check your flavor setting
42
+ to make sure you have a valid flavor chosen.
43
+ no_matching_image: |-
44
+ No matching image was found! Please check your image setting to
45
+ make sure you have a valid image chosen.
46
+ rsync_error: |-
47
+ There was an error when attemping to rsync a share folder.
48
+ Please inspect the error message below for more info.
49
+
50
+ Host path: %{hostpath}
51
+ Guest path: %{guestpath}
52
+ Error: %{stderr}
53
+
54
+ states:
55
+ short_active: |-
56
+ active
57
+ long_active: |-
58
+ The server is up and running. Run `vagrant ssh` to access it.
59
+ short_build: |-
60
+ building
61
+ long_build: |-
62
+ The server is currently being built. You must wait for this to
63
+ complete before you can access it. You can delete the server, however,
64
+ by running `vagrant destroy`.
65
+ short_error: |-
66
+ error
67
+ long_error: |-
68
+ The server is in an erroneous state. Destroy the machine with
69
+ `vagrant destroy`.
70
+ short_not_created: |-
71
+ not created
72
+ long_not_created: |-
73
+ The server is not created. Run `vagrant up` to create it.
@@ -0,0 +1,67 @@
1
+ require "vagrant-openstack/config"
2
+
3
+ describe VagrantPlugins::OpenStack::Config do
4
+ describe "defaults" do
5
+ let(:vagrant_public_key) { Vagrant.source_root.join("keys/vagrant.pub") }
6
+
7
+ subject do
8
+ super().tap do |o|
9
+ o.finalize!
10
+ end
11
+ end
12
+
13
+ its(:api_key) { should be_nil }
14
+ its(:endpoint) { should be_nil }
15
+ its(:flavor) { should eq(/m1.tiny/) }
16
+ its(:image) { should eq(/cirros/) }
17
+ its(:server_name) { should be_nil }
18
+ its(:username) { should be_nil }
19
+ its(:keypair_name) { should be_nil }
20
+ its(:ssh_username) { should be_nil }
21
+ end
22
+
23
+ describe "overriding defaults" do
24
+ [:api_key,
25
+ :endpoint,
26
+ :flavor,
27
+ :image,
28
+ :server_name,
29
+ :username,
30
+ :keypair_name,
31
+ :ssh_username].each do |attribute|
32
+ it "should not default #{attribute} if overridden" do
33
+ subject.send("#{attribute}=".to_sym, "foo")
34
+ subject.finalize!
35
+ subject.send(attribute).should == "foo"
36
+ end
37
+ end
38
+ end
39
+
40
+ describe "validation" do
41
+ let(:machine) { double("machine") }
42
+
43
+ subject do
44
+ super().tap do |o|
45
+ o.finalize!
46
+ end
47
+ end
48
+
49
+ context "with good values" do
50
+ it "should validate"
51
+ end
52
+
53
+ context "the API key" do
54
+ it "should error if not given"
55
+ end
56
+
57
+ context "the public key path" do
58
+ it "should have errors if the key doesn't exist"
59
+ it "should not have errors if the key exists with an absolute path"
60
+ it "should not have errors if the key exists with a relative path"
61
+ end
62
+
63
+ context "the username" do
64
+ it "should error if not given"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'vagrant-openstack/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "vagrant-openstack-plugin"
8
+ gem.version = VagrantPlugins::OpenStack::VERSION
9
+ gem.authors = ["Mitchell Hashimoto"]
10
+ gem.email = ["mitchell@hashicorp.com"]
11
+ gem.description = "Enables Vagrant to manage machines in OpenStack Cloud."
12
+ gem.summary = "Enables Vagrant to manage machines in OpenStack Cloud."
13
+ gem.homepage = "http://www.vagrantup.com"
14
+
15
+ gem.add_runtime_dependency "fog", "~> 1.6.0"
16
+
17
+ gem.add_development_dependency "rake"
18
+ gem.add_development_dependency "rspec", "~> 2.13.0"
19
+
20
+ gem.files = `git ls-files`.split($/)
21
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
22
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
23
+ gem.require_paths = ["lib"]
24
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vagrant-openstack-plugin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mitchell Hashimoto
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fog
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.6.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.6.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: 2.13.0
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 2.13.0
62
+ description: Enables Vagrant to manage machines in OpenStack Cloud.
63
+ email:
64
+ - mitchell@hashicorp.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - CHANGELOG.md
71
+ - Gemfile
72
+ - LICENSE.txt
73
+ - README.md
74
+ - Rakefile
75
+ - dummy.box
76
+ - example_box/README.md
77
+ - example_box/metadata.json
78
+ - lib/vagrant-openstack.rb
79
+ - lib/vagrant-openstack/action.rb
80
+ - lib/vagrant-openstack/action/connect_openstack.rb
81
+ - lib/vagrant-openstack/action/create_server.rb
82
+ - lib/vagrant-openstack/action/delete_server.rb
83
+ - lib/vagrant-openstack/action/is_created.rb
84
+ - lib/vagrant-openstack/action/message_already_created.rb
85
+ - lib/vagrant-openstack/action/message_not_created.rb
86
+ - lib/vagrant-openstack/action/read_ssh_info.rb
87
+ - lib/vagrant-openstack/action/read_state.rb
88
+ - lib/vagrant-openstack/action/sync_folders.rb
89
+ - lib/vagrant-openstack/action/warn_networks.rb
90
+ - lib/vagrant-openstack/config.rb
91
+ - lib/vagrant-openstack/errors.rb
92
+ - lib/vagrant-openstack/plugin.rb
93
+ - lib/vagrant-openstack/provider.rb
94
+ - lib/vagrant-openstack/version.rb
95
+ - locales/en.yml
96
+ - spec/vagrant-openstack/config_spec.rb
97
+ - vagrant-openstack.gemspec
98
+ homepage: http://www.vagrantup.com
99
+ licenses: []
100
+ post_install_message:
101
+ rdoc_options: []
102
+ require_paths:
103
+ - lib
104
+ required_ruby_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ segments:
111
+ - 0
112
+ hash: -650629708573648818
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ segments:
120
+ - 0
121
+ hash: -650629708573648818
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 1.8.24
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Enables Vagrant to manage machines in OpenStack Cloud.
128
+ test_files:
129
+ - spec/vagrant-openstack/config_spec.rb