├── example_box ├── metadata.json └── README.md ├── dummy.box ├── lib ├── vagrant-rackspace │ ├── version.rb │ ├── action │ │ ├── is_created.rb │ │ ├── message_not_created.rb │ │ ├── message_already_created.rb │ │ ├── list_flavors.rb │ │ ├── list_images.rb │ │ ├── delete_server.rb │ │ ├── read_state.rb │ │ ├── read_ssh_info.rb │ │ ├── connect_rackspace.rb │ │ ├── create_image.rb │ │ ├── sync_folders.rb │ │ └── create_server.rb │ ├── command │ │ ├── flavors.rb │ │ ├── list_images.rb │ │ ├── create_image.rb │ │ ├── images.rb │ │ └── root.rb │ ├── errors.rb │ ├── plugin.rb │ ├── provider.rb │ ├── action.rb │ └── config.rb └── vagrant-rackspace.rb ├── .travis.yml ├── .gitignore ├── gemfiles ├── latest_stable.gemfile ├── oldest_current.gemfile └── previous_release.gemfile ├── features ├── steps │ ├── sdk_steps.rb │ └── server_steps.rb ├── support │ ├── fog_mock.rb │ └── env.rb ├── provision.feature └── vagrant-rackspace.feature ├── Appraisals ├── Gemfile ├── Vagrantfile.multi ├── Rakefile ├── spec ├── spec_helper.rb └── vagrant-rackspace │ ├── actions │ ├── list_images_spec.rb │ └── list_flavors_spec.rb │ └── config_spec.rb ├── RELEASE.md ├── vagrant-rackspace.gemspec ├── LICENSE.txt ├── CHANGELOG.md ├── locales └── en.yml ├── Vagrantfile └── README.md /example_box/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "rackspace" 3 | } 4 | -------------------------------------------------------------------------------- /dummy.box: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rackspace/vagrant-rackspace/master/dummy.box -------------------------------------------------------------------------------- /lib/vagrant-rackspace/version.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 3 | VERSION = "0.1.10dev" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.9.3 4 | - 2.0.0 5 | - 2.1.0 6 | script: "appraisal rake" 7 | gemfile: 8 | - Gemfile 9 | - gemfiles/latest_stable.gemfile 10 | - gemfiles/oldest_current.gemfile 11 | - gemfiles/previous_release.gemfile 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .vagrant 6 | .yardoc 7 | Gemfile.lock 8 | gemfiles/*.lock 9 | InstalledFiles 10 | _yardoc 11 | coverage 12 | doc/ 13 | lib/bundler/man 14 | pkg 15 | rdoc 16 | spec/reports 17 | test/tmp 18 | test/version_tmp 19 | tmp 20 | 21 | .idea/ 22 | -------------------------------------------------------------------------------- /gemfiles/latest_stable.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "appraisal", "1.0.0.beta2" 6 | gem "vagrant", :git=>"git://github.com/mitchellh/vagrant.git", :branch=>"v1.4.2" 7 | 8 | group :development do 9 | gem "coveralls", :require=>false 10 | end 11 | 12 | gemspec :path=>".././" 13 | -------------------------------------------------------------------------------- /gemfiles/oldest_current.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "appraisal", "1.0.0.beta2" 6 | gem "vagrant", :git=>"git://github.com/mitchellh/vagrant.git", :branch=>"v1.4.0" 7 | 8 | group :development do 9 | gem "coveralls", :require=>false 10 | end 11 | 12 | gemspec :path=>".././" 13 | -------------------------------------------------------------------------------- /gemfiles/previous_release.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "appraisal", "1.0.0.beta2" 6 | gem "vagrant", :git=>"git://github.com/mitchellh/vagrant.git", :branch=>"v1.3.5" 7 | 8 | group :development do 9 | gem "coveralls", :require=>false 10 | end 11 | 12 | gemspec :path=>".././" 13 | -------------------------------------------------------------------------------- /features/steps/sdk_steps.rb: -------------------------------------------------------------------------------- 1 | Given(/^I have Rackspace credentials available$/) do 2 | fail unless ENV['RAX_USERNAME'] && ENV['RAX_API_KEY'] 3 | end 4 | 5 | Given(/^I have a "fog_mock.rb" file$/) do 6 | script = File.open("features/support/fog_mock.rb").read 7 | steps %Q{ 8 | Given a file named "fog_mock.rb" with: 9 | """ 10 | #{script} 11 | """ 12 | } 13 | end -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/is_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 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 17 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/message_not_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 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_rackspace.not_created")) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/message_already_created.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 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_rackspace.already_created")) 11 | @app.call(env) 12 | end 13 | end 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "latest-stable" do 2 | gem "vagrant", :git => 'git://github.com/mitchellh/vagrant.git', :branch => 'v1.4.2' 3 | end 4 | 5 | # Oldest (current release) 6 | appraise "oldest-current" do 7 | gem "vagrant", :git => 'git://github.com/mitchellh/vagrant.git', :branch => 'v1.4.0' 8 | end 9 | 10 | # Latest patch (previous release) 11 | appraise "previous-release" do 12 | gem "vagrant", :git => 'git://github.com/mitchellh/vagrant.git', :branch => 'v1.3.5' 13 | end 14 | 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | 4 | group :plugins do 5 | gemspec 6 | gem 'vagrant', :git => 'https://github.com/mitchellh/vagrant' 7 | end 8 | 9 | gem "appraisal", "1.0.0.beta2" 10 | 11 | group :development do 12 | # We depend on Vagrant for development, but we don't add it as a 13 | # gem dependency because we expect to be installed within the 14 | # Vagrant environment itself using `vagrant plugin`. 15 | gem 'coveralls', require: false 16 | gem 'pry' 17 | end 18 | 19 | -------------------------------------------------------------------------------- /example_box/README.md: -------------------------------------------------------------------------------- 1 | # Vagrant RackSpace 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 `rackspace` provider. 5 | To turn this into a box: 6 | 7 | ``` 8 | $ tar cvzf rackspace.box ./metadata.json ./Vagrantfile 9 | ``` 10 | 11 | This box works by using Vagrant's built-in Vagrantfile merging to setup 12 | defaults for RackSpace. These defaults can easily be overwritten by higher-level 13 | Vagrantfiles (such as project root Vagrantfiles). 14 | -------------------------------------------------------------------------------- /Vagrantfile.multi: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | REGIONS = [:dfw, :ord, :syd] 8 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 9 | Vagrant.require_plugin "vagrant-rackspace" 10 | 11 | REGIONS.each do |region| 12 | config.vm.define region do |machine| 13 | machine.vm.provider :rackspace do |rs| 14 | rs.username = ENV['RAX_USERNAME'] 15 | rs.api_key = ENV['RAX_API_KEY'] 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/list_flavors.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 3 | module Action 4 | class ListFlavors 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | compute_service = env[:rackspace_compute] 11 | env[:ui].info ('%-36s %s' % ['Flavor ID', 'Flavor Name']) 12 | compute_service.flavors.sort_by(&:id).each do |flavor| 13 | env[:ui].info ('%-36s %s' % [flavor.id, flavor.name]) 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/list_images.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 3 | module Action 4 | class ListImages 5 | def initialize(app, env) 6 | @app = app 7 | end 8 | 9 | def call(env) 10 | compute_service = env[:rackspace_compute] 11 | env[:ui].info ('%-36s %s' % ['Image ID', 'Image Name']) 12 | compute_service.images.sort_by(&:name).each do |image| 13 | env[:ui].info ('%-36s %s' % [image.id.to_s, image.name]) 14 | end 15 | @app.call(env) 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/command/flavors.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 3 | module Command 4 | class Flavors < Vagrant.plugin("2", :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = "Usage: vagrant rackspace flavors [options]" 9 | end 10 | 11 | argv = parse_options(opts) 12 | return if !argv 13 | 14 | with_target_vms(argv, :provider => :rackspace) do |machine| 15 | machine.action('list_flavors') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 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" 22 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/command/list_images.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 3 | module Command 4 | class ListImages < Vagrant.plugin("2", :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = "Usage: vagrant rackspace images list [options]" 9 | end 10 | 11 | argv = parse_options(opts) 12 | return if !argv 13 | 14 | with_target_vms(argv, :provider => :rackspace) do |machine| 15 | machine.action('list_images') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/command/create_image.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 3 | module Command 4 | class CreateImage < Vagrant.plugin("2", :command) 5 | def execute 6 | options = {} 7 | opts = OptionParser.new do |o| 8 | o.banner = "Usage: vagrant rackspace images create [options]" 9 | end 10 | 11 | argv = parse_options(opts) 12 | return if !argv 13 | 14 | with_target_vms(argv, :provider => :rackspace) do |machine| 15 | machine.action('create_image') 16 | end 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['COVERAGE'] != 'false' 2 | require 'simplecov' 3 | require 'coveralls' 4 | SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ 5 | SimpleCov::Formatter::HTMLFormatter, 6 | Coveralls::SimpleCov::Formatter 7 | ] 8 | SimpleCov.start 9 | 10 | # Normally classes are lazily loaded, so any class without a test 11 | # is missing from the report. This ensures they show up so we can 12 | # see uncovered methods. 13 | require 'vagrant' 14 | Dir["lib/**/*.rb"].each do|file| 15 | require_string = file.match(/lib\/(.*)\.rb/)[1] 16 | require require_string 17 | end 18 | end 19 | 20 | require 'fog' 21 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | This is vagrant-rackspace's current release process, documented so people know what is 4 | currently done. 5 | 6 | ## Prepare the release 7 | 8 | * Update the version in "lib/vagrant-rackspace/version.rb" 9 | * Update the version in CHANGELOG.md 10 | * Use "rake release". This will make sure to tag that commit and push it RubyGems. 11 | * Create new [github release](https://github.com/mitchellh/vagrant-rackspace/releases) 12 | * Update the version again in both files to a dev version for working again. 13 | 14 | The CHANGELOG.md should be maintained in a similar format to Vagrant: 15 | 16 | https://github.com/mitchellh/vagrant/blob/master/CHANGELOG.md 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/errors.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | module VagrantPlugins 4 | module Rackspace 5 | module Errors 6 | class VagrantRackspaceError < Vagrant::Errors::VagrantError 7 | error_namespace("vagrant_rackspace.errors") 8 | end 9 | 10 | class CreateBadState < VagrantRackspaceError 11 | error_key(:create_bad_state) 12 | end 13 | 14 | class NoMatchingFlavor < VagrantRackspaceError 15 | error_key(:no_matching_flavor) 16 | end 17 | 18 | class NoMatchingImage < VagrantRackspaceError 19 | error_key(:no_matching_image) 20 | end 21 | 22 | class RsyncError < VagrantRackspaceError 23 | error_key(:rsync_error) 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/delete_server.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | module VagrantPlugins 4 | module Rackspace 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_rackspace::action::delete_server") 11 | end 12 | 13 | def call(env) 14 | if env[:machine].id 15 | env[:ui].info(I18n.t("vagrant_rackspace.deleting_server")) 16 | server = env[:rackspace_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 27 | -------------------------------------------------------------------------------- /features/support/fog_mock.rb: -------------------------------------------------------------------------------- 1 | require 'fog' 2 | if ENV['RAX_MOCK'] == 'true' 3 | Fog.mock! 4 | Fog::Rackspace::MockData.configure do |c| 5 | c[:image_name_generator] = Proc.new { "Ubuntu" } 6 | c[:ipv4_generator] = Proc.new { "10.11.12.2"} 7 | end 8 | connect_options = { 9 | :provider => 'rackspace', 10 | :rackspace_username => ENV['RAX_USERNAME'], 11 | :rackspace_api_key => ENV['RAX_API_KEY'], 12 | :version => :v2, # Use Next Gen Cloud Servers 13 | :rackspace_region => :ord #Use Chicago Region 14 | } 15 | connect_options.merge!(proxy_options) unless ENV['https_proxy'].nil? 16 | compute = Fog::Compute.new(connect_options) 17 | # Force creation of Ubuntu image so it will show up in compute.images.list 18 | compute.images.get(0) 19 | end -------------------------------------------------------------------------------- /features/steps/server_steps.rb: -------------------------------------------------------------------------------- 1 | When(/^I get the server from "(.*?)"$/) do |label| 2 | @server_id = all_output.match(/#{label}\s([\w-]*)/)[1] 3 | puts "Server: #{@server_id}" 4 | end 5 | 6 | When(/^I load the server$/) do 7 | @server_id = all_output.strip.lines.to_a.last 8 | puts "Server: #{@server_id}" 9 | end 10 | 11 | Then(/^the server should be active$/) do 12 | unless Fog.mock? # unfortunately we can't assert this with Fog.mock!, since mocked objects do not persist from the subprocess 13 | assert_active @server_id 14 | end 15 | end 16 | 17 | Then(/^the server "(.+)" should be active$/) do |server_name| 18 | server = @compute.servers.all.find{|s| s.name == server_name} 19 | assert_active server.id 20 | end 21 | 22 | def assert_active server_id 23 | server = @compute.servers.get server_id 24 | server.state.should == 'ACTIVE' 25 | end -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require 'fog' 2 | require 'aruba/cucumber' 3 | 4 | Fog.mock! if ENV['RAX_MOCK'] == 'true' 5 | 6 | Before do | scenario | 7 | @aruba_timeout_seconds = 600 8 | @scenario = File.basename(scenario.file) 9 | ENV['CASSETTE'] = @scenario 10 | 11 | proxy_options = { 12 | :connection_options => { 13 | :proxy => ENV['https_proxy'], 14 | :ssl_verify_peer => false 15 | } 16 | } 17 | 18 | connect_options = { 19 | :provider => 'rackspace', 20 | :rackspace_username => ENV['RAX_USERNAME'], 21 | :rackspace_api_key => ENV['RAX_API_KEY'], 22 | :version => :v2, # Use Next Gen Cloud Servers 23 | :rackspace_region => ENV['RAX_REGION'].downcase.to_sym 24 | } 25 | connect_options.merge!(proxy_options) unless ENV['https_proxy'].nil? 26 | @compute = Fog::Compute.new(connect_options) 27 | end 28 | 29 | Around do | scenario, block | 30 | Bundler.with_clean_env do 31 | block.call 32 | end 33 | end 34 | 35 | After('@creates_server') do 36 | @compute.servers.delete @server_id 37 | end 38 | -------------------------------------------------------------------------------- /vagrant-rackspace.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'vagrant-rackspace/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = "vagrant-rackspace" 8 | gem.version = VagrantPlugins::Rackspace::VERSION 9 | gem.authors = ["Mitchell Hashimoto"] 10 | gem.email = ["mitchell@hashicorp.com"] 11 | gem.description = "Enables Vagrant to manage machines in RackSpace Cloud." 12 | gem.summary = "Enables Vagrant to manage machines in RackSpace Cloud." 13 | gem.homepage = "http://www.vagrantup.com" 14 | 15 | gem.add_runtime_dependency "fog", "~> 1.22" 16 | 17 | gem.add_development_dependency "rake" 18 | gem.add_development_dependency "rspec", "~> 2.14.0" 19 | gem.add_development_dependency "aruba", "~> 0.5.4" 20 | 21 | gem.files = `git ls-files`.split($/) 22 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 23 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 24 | gem.require_paths = ["lib"] 25 | end 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Mitchell Hashimoto 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/vagrant-rackspace/plugin.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require "vagrant" 3 | rescue LoadError 4 | raise "The RackSpace 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 "RackSpace Cloud provider is only compatible with Vagrant 1.1+" 11 | end 12 | 13 | module VagrantPlugins 14 | module Rackspace 15 | class Plugin < Vagrant.plugin("2") 16 | name "RackSpace Cloud" 17 | description <<-DESC 18 | This plugin enables Vagrant to manage machines in RackSpace Cloud. 19 | DESC 20 | 21 | config(:rackspace, :provider) do 22 | require_relative "config" 23 | Config 24 | end 25 | 26 | provider(:rackspace) do 27 | # Setup some things 28 | Rackspace.init_i18n 29 | Rackspace.init_logging 30 | 31 | # Load the actual provider 32 | require_relative "provider" 33 | Provider 34 | end 35 | 36 | command('rackspace') do 37 | require_relative "command/root" 38 | Command::Root 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/read_state.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | module VagrantPlugins 4 | module Rackspace 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_rackspace::action::read_state") 12 | end 13 | 14 | def call(env) 15 | env[:machine_state_id] = read_state(env[:rackspace_compute], env[:machine]) 16 | 17 | @app.call(env) 18 | end 19 | 20 | def read_state(rackspace, machine) 21 | return :not_created if machine.id.nil? 22 | 23 | # Find the machine 24 | server = rackspace.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 39 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/read_ssh_info.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | 3 | module VagrantPlugins 4 | module Rackspace 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_rackspace::action::read_ssh_info") 12 | end 13 | 14 | def call(env) 15 | env[:machine_ssh_info] = read_ssh_info(env[:rackspace_compute], env[:machine]) 16 | 17 | @app.call(env) 18 | end 19 | 20 | def read_ssh_info(rackspace, machine) 21 | return nil if machine.id.nil? 22 | 23 | # Find the machine 24 | server = rackspace.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 | # Read the DNS info 33 | return { 34 | :host => server.ipv4_address, 35 | :port => 22, 36 | :username => "root" 37 | } 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /features/provision.feature: -------------------------------------------------------------------------------- 1 | @announce 2 | @vagrant-rackspace 3 | Feature: vagrant-rackspace fog tests 4 | 5 | Background: 6 | Given I have Rackspace credentials available 7 | And I have a "fog_mock.rb" file 8 | 9 | Scenario: Create a single server (with provisioning) 10 | Given a file named "Vagrantfile" with: 11 | """ 12 | Vagrant.configure("2") do |config| 13 | Vagrant.require_plugin "vagrant-rackspace" 14 | 15 | config.vm.box = "dummy" 16 | config.ssh.private_key_path = "~/.ssh/id_rsa" 17 | 18 | 19 | config.vm.provider :rackspace do |rs| 20 | rs.server_name = 'vagrant-provisioned-server' 21 | rs.username = ENV['RAX_USERNAME'] 22 | rs.api_key = ENV['RAX_API_KEY'] 23 | rs.rackspace_region = ENV['RAX_REGION'].downcase.to_sym 24 | rs.flavor = /1 GB Performance/ 25 | rs.image = /Ubuntu/ 26 | rs.public_key_path = "~/.ssh/id_rsa.pub" 27 | end 28 | 29 | config.vm.provision :shell, :inline => "echo Hello, World" 30 | end 31 | """ 32 | When I successfully run `bundle exec vagrant up --provider rackspace` 33 | # I want to capture the ID like I do in tests for other tools, but Vagrant doesn't print it! 34 | # And I get the server from "Instance ID:" 35 | Then the server "vagrant-provisioned-server" should be active -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/connect_rackspace.rb: -------------------------------------------------------------------------------- 1 | require "fog" 2 | require "log4r" 3 | 4 | module VagrantPlugins 5 | module Rackspace 6 | module Action 7 | # This action connects to Rackspace, verifies credentials work, and 8 | # puts the Rackspace connection object into the `:rackspace_compute` key 9 | # in the environment. 10 | class ConnectRackspace 11 | def initialize(app, env) 12 | @app = app 13 | @logger = Log4r::Logger.new("vagrant_rackspace::action::connect_rackspace") 14 | end 15 | 16 | def call(env) 17 | # Get the configs 18 | config = env[:machine].provider_config 19 | api_key = config.api_key 20 | username = config.username 21 | 22 | params = { 23 | :provider => :rackspace, 24 | :version => :v2, 25 | :rackspace_api_key => api_key, 26 | :rackspace_username => username, 27 | :rackspace_auth_url => config.rackspace_auth_url 28 | } 29 | 30 | if config.rackspace_compute_url 31 | @logger.info("Connecting to Rackspace compute_url...") 32 | params[:rackspace_compute_url] = config.rackspace_compute_url 33 | else 34 | @logger.info("Connecting to Rackspace region...") 35 | params[:rackspace_region] = config.rackspace_region 36 | end 37 | 38 | env[:rackspace_compute] = Fog::Compute.new params 39 | 40 | @app.call(env) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /spec/vagrant-rackspace/actions/list_images_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require "vagrant-rackspace/action/list_images" 3 | 4 | describe VagrantPlugins::Rackspace::Action::ListImages do 5 | let(:app) { lambda { |env| } } 6 | let(:ui) { Vagrant::UI::Silent.new } 7 | let(:images) { 8 | Fog.mock! 9 | Fog::Compute.new({ 10 | :provider => :rackspace, 11 | :rackspace_region => :dfw, 12 | :rackspace_api_key => 'anything', 13 | :rackspace_username => 'anything', 14 | }).images 15 | } 16 | let(:compute_connection) { double('fog connection') } 17 | let(:env) do 18 | { 19 | :rackspace_compute => compute_connection, 20 | :ui => ui 21 | } 22 | end 23 | 24 | subject(:action) { described_class.new(app, env) } 25 | 26 | before do 27 | allow(compute_connection).to receive(:images).and_return images 28 | end 29 | 30 | it 'get images from Fog' do 31 | expect(compute_connection).to receive(:images).and_return images 32 | action.call(env) 33 | end 34 | 35 | it 'writes a sorted, formatted image table to Vagrant::UI' do 36 | header_line = '%-36s %s' % ['Image ID', 'Image Name'] 37 | expect(ui).to receive(:info).with(header_line) 38 | images.sort_by(&:name).each do |image| 39 | formatted_line = '%-36s %s' % [image.id.to_s, image.name] 40 | expect(ui).to receive(:info).with formatted_line 41 | end 42 | action.call(env) 43 | end 44 | 45 | it 'continues the middleware chain' do 46 | expect(app).to receive(:call).with(env) 47 | action.call(env) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/vagrant-rackspace/actions/list_flavors_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require "vagrant-rackspace/action/list_flavors" 3 | 4 | describe VagrantPlugins::Rackspace::Action::ListFlavors do 5 | let(:app) { lambda { |env| } } 6 | let(:ui) { Vagrant::UI::Silent.new } 7 | let(:flavors) { 8 | Fog.mock! 9 | Fog::Compute.new({ 10 | :provider => :rackspace, 11 | :rackspace_region => :dfw, 12 | :rackspace_api_key => 'anything', 13 | :rackspace_username => 'anything', 14 | }).flavors 15 | } 16 | let(:compute_connection) { double('fog connection') } 17 | let(:env) do 18 | { 19 | :rackspace_compute => compute_connection, 20 | :ui => ui 21 | } 22 | end 23 | 24 | subject(:action) { described_class.new(app, env) } 25 | 26 | before do 27 | allow(compute_connection).to receive(:flavors).and_return flavors 28 | end 29 | 30 | it 'get flavors from Fog' do 31 | expect(compute_connection).to receive(:flavors).and_return flavors 32 | action.call(env) 33 | end 34 | 35 | it 'writes a sorted, formatted flavor table to Vagrant::UI' do 36 | header_line = '%-36s %s' % ['Flavor ID', 'Flavor Name'] 37 | expect(ui).to receive(:info).with(header_line) 38 | flavors.sort_by(&:id).each do |flavor| 39 | formatted_line = '%-36s %s' % [flavor.id, flavor.name] 40 | expect(ui).to receive(:info).with formatted_line 41 | end 42 | action.call(env) 43 | end 44 | 45 | it 'continues the middleware chain' do 46 | expect(app).to receive(:call).with(env) 47 | action.call(env) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/provider.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | 3 | require "vagrant-rackspace/action" 4 | 5 | module VagrantPlugins 6 | module Rackspace 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_rackspace.states.short_#{state_id}") 39 | long = I18n.t("vagrant_rackspace.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 | "RackSpace Cloud" 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/create_image.rb: -------------------------------------------------------------------------------- 1 | require "fog/rackspace" 2 | require "log4r" 3 | 4 | require 'vagrant/util/retryable' 5 | 6 | module VagrantPlugins 7 | module Rackspace 8 | module Action 9 | # Creates an Image 10 | class CreateImage 11 | include Vagrant::Util::Retryable 12 | 13 | attr_reader :env 14 | 15 | def initialize(app, env) 16 | @app, @env = app, env 17 | end 18 | 19 | def call(env) 20 | env[:ui].info(I18n.t("vagrant_rackspace.creating_image")) 21 | 22 | server = env[:rackspace_compute].servers.get(env[:machine].id) 23 | 24 | config = env[:machine].provider_config 25 | image_name = config.server_name || env[:machine].name 26 | 27 | image = server.create_image(image_name) 28 | 29 | retryable(:on => Fog::Errors::TimeoutError, :tries => 200) do 30 | # If we're interrupted don't worry about waiting 31 | next if env[:interrupted] 32 | 33 | env[:ui].clear_line 34 | env[:ui].report_progress(image.progress, 100, false) 35 | 36 | begin 37 | image.wait_for(5) { ready? } 38 | rescue RuntimeError => e 39 | # If we don't have an error about a state transition, then 40 | # we just move on. 41 | raise if e.message !~ /should have transitioned/ 42 | raise Errors::CreateBadState, :state => server.state 43 | end 44 | end 45 | 46 | env[:ui].info(I18n.t("vagrant_rackspace.image_ready")) 47 | 48 | @app.call(env) 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | require "vagrant-rackspace/plugin" 4 | 5 | module VagrantPlugins 6 | module Rackspace 7 | lib_path = Pathname.new(File.expand_path("../vagrant-rackspace", __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_rackspace") 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 54 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/command/images.rb: -------------------------------------------------------------------------------- 1 | module VagrantPlugins 2 | module Rackspace 3 | module Command 4 | class Images < Vagrant.plugin("2", :command) 5 | def initialize(argv, env) 6 | @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) 7 | 8 | @subcommands = Vagrant::Registry.new 9 | @subcommands.register(:list) do 10 | require File.expand_path("../list_images", __FILE__) 11 | ListImages 12 | end 13 | @subcommands.register(:create) do 14 | require File.expand_path("../create_image", __FILE__) 15 | CreateImage 16 | end 17 | 18 | super(argv, env) 19 | end 20 | 21 | def execute 22 | if @main_args.include?("-h") || @main_args.include?("--help") 23 | # Print the help for all the rackspace commands. 24 | return help 25 | end 26 | 27 | command_class = @subcommands.get(@sub_command.to_sym) if @sub_command 28 | return help if !command_class || !@sub_command 29 | @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}") 30 | 31 | # Initialize and execute the command class 32 | command_class.new(@sub_args, @env).execute 33 | end 34 | 35 | def help 36 | opts = OptionParser.new do |opts| 37 | opts.banner = "Usage: vagrant rackspace images []" 38 | opts.separator "" 39 | opts.separator "Available subcommands:" 40 | 41 | # Add the available subcommands as separators in order to print them 42 | # out as well. 43 | keys = [] 44 | @subcommands.each { |key, value| keys << key.to_s } 45 | 46 | keys.sort.each do |key| 47 | opts.separator " #{key}" 48 | end 49 | 50 | opts.separator "" 51 | opts.separator "For help on any individual subcommand run `vagrant rackspace images -h`" 52 | end 53 | 54 | @env.ui.info(opts.help, :prefix => false) 55 | end 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/command/root.rb: -------------------------------------------------------------------------------- 1 | require 'vagrant-rackspace/action' 2 | 3 | module VagrantPlugins 4 | module Rackspace 5 | module Command 6 | class Root < Vagrant.plugin("2", :command) 7 | def self.synopsis 8 | "query Rackspace for available images or flavors" 9 | end 10 | 11 | def initialize(argv, env) 12 | @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) 13 | 14 | @subcommands = Vagrant::Registry.new 15 | @subcommands.register(:images) do 16 | require File.expand_path("../images", __FILE__) 17 | Images 18 | end 19 | @subcommands.register(:flavors) do 20 | require File.expand_path("../flavors", __FILE__) 21 | Flavors 22 | end 23 | 24 | super(argv, env) 25 | end 26 | 27 | def execute 28 | if @main_args.include?("-h") || @main_args.include?("--help") 29 | # Print the help for all the rackspace commands. 30 | return help 31 | end 32 | 33 | command_class = @subcommands.get(@sub_command.to_sym) if @sub_command 34 | return help if !command_class || !@sub_command 35 | @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}") 36 | 37 | # Initialize and execute the command class 38 | command_class.new(@sub_args, @env).execute 39 | end 40 | 41 | def help 42 | opts = OptionParser.new do |opts| 43 | opts.banner = "Usage: vagrant rackspace []" 44 | opts.separator "" 45 | opts.separator "Available subcommands:" 46 | 47 | # Add the available subcommands as separators in order to print them 48 | # out as well. 49 | keys = [] 50 | @subcommands.each { |key, value| keys << key.to_s } 51 | 52 | keys.sort.each do |key| 53 | opts.separator " #{key}" 54 | end 55 | 56 | opts.separator "" 57 | opts.separator "For help on any individual subcommand run `vagrant rackspace -h`" 58 | end 59 | 60 | @env.ui.info(opts.help, :prefix => false) 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.9 (June 24, 2014) 2 | 3 | FEATURES: 4 | 5 | - Support for userdata and drive config [GH-100] 6 | 7 | BUG FIXES: 8 | 9 | - Fixed the ability to specify strings and integers for flavors and images in VagrantFile 10 | 11 | # 0.1.8 (June 20, 2014) 12 | 13 | FEATURES: 14 | 15 | - Use new synced folder support found in Vagrant 1.4+ if available [GH-104] 16 | - Ability to set admin password [GH-105] [GH-107] 17 | 18 | IMPROVEMENTS: 19 | 20 | - Copy symlinks when syncing folders using older versions of vagrant [GH-89] 21 | - Update vagrant to allow users to specify non-standard images and flavors [GH-101] 22 | 23 | BUG FIXES: 24 | 25 | - Fixed windows path issue when syncing folders older versions of vagrant [GH-96] 26 | 27 | 28 | # 0.1.7 (March 13, 2014) 29 | 30 | FEATURES: 31 | 32 | - Adds commands to list available images and flavors [GH-73] 33 | - Adds support for creating images from running VMs [GH-74] 34 | 35 | IMPROVEMENTS: 36 | 37 | - Adds support for rsync --include directives [GH-56] 38 | - Adds ability to add server metadata [GH-72] 39 | - Cleans up chef node and client from chef server [GH-67] 40 | 41 | # 0.1.6 (January 8, 2014) 42 | 43 | BUG FIXES: 44 | 45 | - Downgraded fog to 1.18 in order to be more compatible with vagrant-aws 46 | 47 | # 0.1.5 (January 8, 2014) 48 | 49 | IMPROVEMENTS: 50 | 51 | - Fix Vagrant 1.4 compatibility and support multiple SSH keys [GH-58] 52 | - Add uploaded keypair support [GH-53] 53 | - Add ssh_run support, for vagrant ssh -c [GH-57] 54 | - Requires fog 1.19 [GH-65] 55 | 56 | BUG FIXES: 57 | 58 | - Remove networks warning as vagrant-rackspace now supports networks [GH-64] 59 | 60 | # 0.1.4 (October 15, 2013) 61 | 62 | IMPROVEMENTS: 63 | 64 | - Adds endpoint validation (rackspace_compute_url, rackspace_auth_url) [GH-39] 65 | 66 | FEATURES: 67 | - Adds ability to configure networks [GH-37] 68 | 69 | # 0.1.3 (September 6, 2013) 70 | 71 | IMPROVEMENTS: 72 | 73 | - Adds ability to specify authentication endpoint; Support for UK Cloud! [GH-32] 74 | - Adds ability to specify disk configuration (disk_conf) [GH-33] 75 | 76 | # 0.1.2 (August 22, 2013) 77 | 78 | FEATURES: 79 | 80 | - Add provision support [GH-16] 81 | 82 | IMPROVEMENTS: 83 | 84 | - Adds option to allow provisioning after RackConnect scripts complete. [GH-18] 85 | - Remove Fog deprecation warnings [GH-11] 86 | - Bypass rsync's StrictHostKeyCheck [GH-5] 87 | - Make chown'ing of synced folder perms recursive (for ssh user) [GH-24] 88 | - Use /cygdrive when rsyncing on Windows [GH-17] 89 | 90 | 91 | # 0.1.1 (March 18, 2013) 92 | 93 | * Up fog dependency for Vagrant 1.1.1 94 | 95 | # 0.1.0 (March 14, 2013) 96 | 97 | * Initial release. 98 | -------------------------------------------------------------------------------- /features/vagrant-rackspace.feature: -------------------------------------------------------------------------------- 1 | @announce 2 | @vagrant-rackspace 3 | Feature: vagrant-rackspace fog tests 4 | As a Fog developer 5 | I want to smoke (or "fog") test vagrant-rackspace. 6 | So I am confident my upstream changes did not create downstream problems. 7 | 8 | Background: 9 | Given I have Rackspace credentials available 10 | And I have a "fog_mock.rb" file 11 | 12 | Scenario: Create a single server (region) 13 | Given a file named "Vagrantfile" with: 14 | """ 15 | # Testing options 16 | require File.expand_path '../fog_mock', __FILE__ 17 | 18 | Vagrant.configure("2") do |config| 19 | # dev/test method of loading plugin, normally would be 'vagrant plugin install vagrant-rackspace' 20 | Vagrant.require_plugin "vagrant-rackspace" 21 | 22 | config.vm.box = "dummy" 23 | config.ssh.username = "vagrant" if Fog.mock? 24 | config.ssh.private_key_path = "~/.ssh/id_rsa" unless Fog.mock? 25 | 26 | config.vm.provider :rackspace do |rs| 27 | rs.server_name = 'vagrant-single-server' 28 | rs.username = ENV['RAX_USERNAME'] 29 | rs.api_key = ENV['RAX_API_KEY'] 30 | rs.rackspace_region = ENV['RAX_REGION'].downcase.to_sym 31 | rs.flavor = /1 GB Performance/ 32 | rs.image = /Ubuntu/ 33 | rs.public_key_path = "~/.ssh/id_rsa.pub" unless Fog.mock? 34 | end 35 | end 36 | """ 37 | When I successfully run `bundle exec vagrant up --provider rackspace` 38 | # I want to capture the ID like I do in tests for other tools, but Vagrant doesn't print it! 39 | # And I get the server from "Instance ID:" 40 | Then the server "vagrant-single-server" should be active 41 | 42 | Scenario: Create a single server (rackspace_compute_url) 43 | Given a file named "Vagrantfile" with: 44 | """ 45 | # Testing options 46 | require File.expand_path '../fog_mock', __FILE__ 47 | 48 | Vagrant.configure("2") do |config| 49 | # dev/test method of loading plugin, normally would be 'vagrant plugin install vagrant-rackspace' 50 | Vagrant.require_plugin "vagrant-rackspace" 51 | 52 | config.vm.box = "dummy" 53 | config.ssh.username = "vagrant" if Fog.mock? 54 | config.ssh.private_key_path = "~/.ssh/id_rsa" unless Fog.mock? 55 | 56 | config.vm.provider :rackspace do |rs| 57 | rs.server_name = 'vagrant-single-server' 58 | rs.username = ENV['RAX_USERNAME'] 59 | rs.api_key = ENV['RAX_API_KEY'] 60 | rs.rackspace_compute_url = "https://#{ENV['RAX_REGION'].downcase}.servers.api.rackspacecloud.com/v2/#{ENV['RAX_TENANT_ID']}" 61 | rs.flavor = /1 GB Performance/ 62 | rs.image = /Ubuntu/ 63 | rs.public_key_path = "~/.ssh/id_rsa.pub" unless Fog.mock? 64 | end 65 | end 66 | """ 67 | When I successfully run `bundle exec vagrant up --provider rackspace` 68 | # I want to capture the ID like I do in tests for other tools, but Vagrant doesn't print it! 69 | # And I get the server from "Instance ID:" 70 | Then the server "vagrant-single-server" should be active 71 | -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | vagrant_rackspace: 3 | already_created: |- 4 | The server is already created. 5 | creating_image: |- 6 | Creating image... 7 | deleting_server: |- 8 | Deleting server... 9 | finding_flavor: |- 10 | Finding flavor for server... 11 | finding_image: |- 12 | Finding image for server... 13 | image_ready: |- 14 | Image created 15 | launching_server: |- 16 | Launching a server with the following settings... 17 | not_created: |- 18 | The server hasn't been created yet. Run `vagrant up` first. 19 | ready: |- 20 | The server is ready! 21 | rsync_folder: |- 22 | Rsyncing folder: %{hostpath} => %{guestpath} 23 | waiting_for_build: |- 24 | Waiting for the server to be built... 25 | waiting_for_rackconnect: |- 26 | Waiting for RackConnect to complete... 27 | waiting_for_ssh: |- 28 | Waiting for SSH to become available... 29 | warn_insecure_ssh: |- 30 | Warning! By not specifying a custom public/private keypair, 31 | Vagrant is defaulting to use the insecure keypair that ships with 32 | Vagrant. While this isn't much of a big deal for local development, 33 | this is quite insecure for remote servers. Please specify a custom 34 | public/private keypair. 35 | warn_networks: |- 36 | Warning! The Rackspace provider doesn't support any of the Vagrant 37 | high-level network configurations (`config.vm.network`). They 38 | will be silently ignored. 39 | sync_folders: |- 40 | Rackspace support for Vagrant 1.3 has been deprecated. Please 41 | upgrade to the latest version of vagrant for continued support. 42 | 43 | config: 44 | api_key_required: |- 45 | An API key is required. 46 | public_key_not_found: |- 47 | The public key file could not be found. Please make sure 48 | you specified a valid path. 49 | username_required: |- 50 | A username is required. 51 | invalid_uri: |- 52 | The value for %{key} is not a valid URI: %{uri} 53 | metadata_must_be_hash: |- 54 | Metadata must be a hash. 55 | 56 | errors: 57 | create_bad_state: |- 58 | While creating the server, it transitioned to an unexpected 59 | state: '%{state}', instead of properly booting. Run `vagrant status` 60 | to find out what can be done about this state, or `vagrant destroy` 61 | if you want to start over. 62 | no_matching_flavor: |- 63 | No matching flavor was found! Please check your flavor setting 64 | to make sure you have a valid flavor chosen. 65 | no_matching_image: |- 66 | No matching image was found! Please check your image setting to 67 | make sure you have a valid image chosen. 68 | rsync_error: |- 69 | There was an error when attemping to rsync a share folder. 70 | Please inspect the error message below for more info. 71 | 72 | Host path: %{hostpath} 73 | Guest path: %{guestpath} 74 | Error: %{stderr} 75 | 76 | states: 77 | short_active: |- 78 | active 79 | long_active: |- 80 | The server is up and running. Run `vagrant ssh` to access it. 81 | short_build: |- 82 | building 83 | long_build: |- 84 | The server is currently being built. You must wait for this to 85 | complete before you can access it. You can delete the server, however, 86 | by running `vagrant destroy`. 87 | short_error: |- 88 | error 89 | long_error: |- 90 | The server is in an erroneous state. Contact RackSpace support 91 | or destroy the machine with `vagrant destroy`. 92 | short_not_created: |- 93 | not created 94 | long_not_created: |- 95 | The server is not created. Run `vagrant up` to create it. 96 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/sync_folders.rb: -------------------------------------------------------------------------------- 1 | require "log4r" 2 | require 'rbconfig' 3 | require "vagrant/util/subprocess" 4 | 5 | module VagrantPlugins 6 | module Rackspace 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_rackspace::action::sync_folders") 14 | @host_os = RbConfig::CONFIG['host_os'] 15 | end 16 | 17 | def call(env) 18 | @app.call(env) 19 | env[:ui].warn(I18n.t("vagrant_rackspace.sync_folders")) 20 | 21 | ssh_info = env[:machine].ssh_info 22 | 23 | config = env[:machine].provider_config 24 | rsync_includes = config.rsync_includes.to_a 25 | 26 | env[:machine].config.vm.synced_folders.each do |id, data| 27 | hostpath = File.expand_path(data[:hostpath], env[:root_path]) 28 | guestpath = data[:guestpath] 29 | 30 | # Make sure there is a trailing slash on the host path to 31 | # avoid creating an additional directory with rsync 32 | hostpath = "#{hostpath}/" if hostpath !~ /\/$/ 33 | 34 | # If on Windows, modify the path to work with cygwin rsync 35 | if @host_os =~ /mswin|mingw|cygwin/ 36 | hostpath = hostpath.sub(/^([A-Za-z]):\//) { "/cygdrive/#{$1.downcase}/" } 37 | end 38 | 39 | env[:ui].info(I18n.t("vagrant_rackspace.rsync_folder", 40 | :hostpath => hostpath, 41 | :guestpath => guestpath)) 42 | 43 | # Create the guest path 44 | env[:machine].communicate.sudo("mkdir -p '#{guestpath}'") 45 | env[:machine].communicate.sudo( 46 | "chown -R #{ssh_info[:username]} '#{guestpath}'") 47 | 48 | # Generate rsync include commands 49 | includes = rsync_includes.each_with_object([]) { |incl, incls| 50 | incls << "--include" 51 | incls << incl 52 | } 53 | 54 | # Rsync over to the guest path using the SSH info. add 55 | # .hg/ to exclude list as that isn't covered in 56 | # --cvs-exclude 57 | command = [ 58 | "rsync", "--verbose", "--archive", "-z", "-L", 59 | "--cvs-exclude", 60 | "--exclude", ".hg/", 61 | *includes, 62 | "-e", "ssh -p #{ssh_info[:port]} -o StrictHostKeyChecking=no #{ssh_key_options(ssh_info)}", 63 | hostpath, 64 | "#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"] 65 | command.compact! 66 | 67 | # during rsync, ignore files specified in .hgignore and 68 | # .gitignore traditional .gitignore or .hgignore files 69 | ignore_files = [".hgignore", ".gitignore"] 70 | ignore_files.each do |ignore_file| 71 | abs_ignore_file = env[:root_path].to_s + "/" + ignore_file 72 | if File.exist?(abs_ignore_file) 73 | command = command + ["--exclude-from", abs_ignore_file] 74 | end 75 | end 76 | 77 | r = Vagrant::Util::Subprocess.execute(*command) 78 | if r.exit_code != 0 79 | raise Errors::RsyncError, 80 | :guestpath => guestpath, 81 | :hostpath => hostpath, 82 | :stderr => r.stderr 83 | end 84 | end 85 | end 86 | 87 | private 88 | 89 | def ssh_key_options(ssh_info) 90 | # Ensure that `private_key_path` is an Array (for Vagrant < 1.4) 91 | Array(ssh_info[:private_key_path]).map { |path| "-i '#{path}' " }.join 92 | end 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 5 | VAGRANTFILE_API_VERSION = "2" 6 | 7 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 8 | Vagrant.require_plugin "vagrant-rackspace" 9 | # All Vagrant configuration is done here. The most common configuration 10 | # options are documented and commented below. For a complete reference, 11 | # please see the online documentation at vagrantup.com. 12 | 13 | # Every Vagrant virtual environment requires a box to build off of. 14 | config.vm.box = "dummy" 15 | config.vm.provider :rackspace do |rs| 16 | rs.username = ENV['RAX_USERNAME'] 17 | rs.admin_password = ENV['VAGRANT_ADMIN_PASSWORD'] 18 | rs.api_key = ENV['RAX_API_KEY'] 19 | rs.flavor = /1 GB Performance/ 20 | rs.image = /Ubuntu/ 21 | rs.rackspace_region = :iad 22 | 23 | # rs.rsync_include 'PATTERN' # per man page for rsync 24 | end 25 | # The url from where the 'config.vm.box' box will be fetched if it 26 | # doesn't already exist on the user's system. 27 | # config.vm.box_url = "http://domain.com/path/to/above.box" 28 | 29 | # Create a forwarded port mapping which allows access to a specific port 30 | # within the machine from a port on the host machine. In the example below, 31 | # accessing "localhost:8080" will access port 80 on the guest machine. 32 | # config.vm.network :forwarded_port, guest: 80, host: 8080 33 | 34 | # Create a private network, which allows host-only access to the machine 35 | # using a specific IP. 36 | # config.vm.network :private_network, ip: "192.168.33.10" 37 | 38 | # Create a public network, which generally matched to bridged network. 39 | # Bridged networks make the machine appear as another physical device on 40 | # your network. 41 | # config.vm.network :public_network 42 | 43 | # If true, then any SSH connections made will enable agent forwarding. 44 | # Default value: false 45 | # config.ssh.forward_agent = true 46 | 47 | # Share an additional folder to the guest VM. The first argument is 48 | # the path on the host to the actual folder. The second argument is 49 | # the path on the guest to mount the folder. And the optional third 50 | # argument is a set of non-required options. 51 | # config.vm.synced_folder "../data", "/vagrant_data" 52 | 53 | # Provider-specific configuration so you can fine-tune various 54 | # backing providers for Vagrant. These expose provider-specific options. 55 | # Example for VirtualBox: 56 | # 57 | # config.vm.provider :virtualbox do |vb| 58 | # # Don't boot with headless mode 59 | # vb.gui = true 60 | # 61 | # # Use VBoxManage to customize the VM. For example to change memory: 62 | # vb.customize ["modifyvm", :id, "--memory", "1024"] 63 | # end 64 | # 65 | # View the documentation for the provider you're using for more 66 | # information on available options. 67 | 68 | # Enable provisioning with Puppet stand alone. Puppet manifests 69 | # are contained in a directory path relative to this Vagrantfile. 70 | # You will need to create the manifests directory and a manifest in 71 | # the file base.pp in the manifests_path directory. 72 | # 73 | # An example Puppet manifest to provision the message of the day: 74 | # 75 | # # group { "puppet": 76 | # # ensure => "present", 77 | # # } 78 | # # 79 | # # File { owner => 0, group => 0, mode => 0644 } 80 | # # 81 | # # file { '/etc/motd': 82 | # # content => "Welcome to your Vagrant-built virtual machine! 83 | # # Managed by Puppet.\n" 84 | # # } 85 | # 86 | # config.vm.provision :puppet do |puppet| 87 | # puppet.manifests_path = "manifests" 88 | # puppet.manifest_file = "init.pp" 89 | # end 90 | 91 | # Enable provisioning with chef solo, specifying a cookbooks path, roles 92 | # path, and data_bags path (all relative to this Vagrantfile), and adding 93 | # some recipes and/or roles. 94 | # 95 | # config.vm.provision :chef_solo do |chef| 96 | # chef.cookbooks_path = "../my-recipes/cookbooks" 97 | # chef.roles_path = "../my-recipes/roles" 98 | # chef.data_bags_path = "../my-recipes/data_bags" 99 | # chef.add_recipe "mysql" 100 | # chef.add_role "web" 101 | # 102 | # # You may also specify custom JSON attributes: 103 | # chef.json = { :mysql_password => "foo" } 104 | # end 105 | 106 | # Enable provisioning with chef server, specifying the chef server URL, 107 | # and the path to the validation key (relative to this Vagrantfile). 108 | # 109 | # The Opscode Platform uses HTTPS. Substitute your organization for 110 | # ORGNAME in the URL and validation key. 111 | # 112 | # If you have your own Chef Server, use the appropriate URL, which may be 113 | # HTTP instead of HTTPS depending on your configuration. Also change the 114 | # validation key to validation.pem. 115 | # 116 | # config.vm.provision :chef_client do |chef| 117 | # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" 118 | # chef.validation_key_path = "ORGNAME-validator.pem" 119 | # end 120 | # 121 | # If you're using the Opscode platform, your validator client is 122 | # ORGNAME-validator, replacing ORGNAME with your organization name. 123 | # 124 | # If you have your own Chef Server, the default validation client name is 125 | # chef-validator, unless you changed the configuration. 126 | # 127 | # chef.validation_client_name = "ORGNAME-validator" 128 | end 129 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action.rb: -------------------------------------------------------------------------------- 1 | require "pathname" 2 | 3 | require "vagrant/action/builder" 4 | 5 | module VagrantPlugins 6 | module Rackspace 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 ConnectRackspace 22 | b2.use DeleteServer 23 | b2.use ProvisionerCleanup if defined?(ProvisionerCleanup) 24 | end 25 | end 26 | end 27 | 28 | # This action is called when `vagrant provision` is called. 29 | def self.action_provision 30 | Vagrant::Action::Builder.new.tap do |b| 31 | b.use ConfigValidate 32 | b.use Call, IsCreated do |env, b2| 33 | if !env[:result] 34 | b2.use MessageNotCreated 35 | next 36 | end 37 | 38 | b2.use Provision 39 | if defined?(SyncedFolders) 40 | b2.use SyncedFolders 41 | else 42 | b2.use SyncFolders 43 | end 44 | end 45 | end 46 | end 47 | 48 | # This action is called to read the SSH info of the machine. The 49 | # resulting state is expected to be put into the `:machine_ssh_info` 50 | # key. 51 | def self.action_read_ssh_info 52 | Vagrant::Action::Builder.new.tap do |b| 53 | b.use ConfigValidate 54 | b.use ConnectRackspace 55 | b.use ReadSSHInfo 56 | end 57 | end 58 | 59 | # This action is called to read the state of the machine. The 60 | # resulting state is expected to be put into the `:machine_state_id` 61 | # key. 62 | def self.action_read_state 63 | Vagrant::Action::Builder.new.tap do |b| 64 | b.use ConfigValidate 65 | b.use ConnectRackspace 66 | b.use ReadState 67 | end 68 | end 69 | 70 | def self.action_ssh 71 | Vagrant::Action::Builder.new.tap do |b| 72 | b.use ConfigValidate 73 | b.use Call, IsCreated do |env, b2| 74 | if !env[:result] 75 | b2.use MessageNotCreated 76 | next 77 | end 78 | 79 | b2.use SSHExec 80 | end 81 | end 82 | end 83 | 84 | def self.action_ssh_run 85 | Vagrant::Action::Builder.new.tap do |b| 86 | b.use ConfigValidate 87 | b.use Call, IsCreated do |env, b2| 88 | if !env[:result] 89 | b2.use MessageNotCreated 90 | next 91 | end 92 | 93 | b2.use SSHRun 94 | end 95 | end 96 | end 97 | 98 | def self.action_up 99 | Vagrant::Action::Builder.new.tap do |b| 100 | b.use ConfigValidate 101 | b.use Call, IsCreated do |env, b2| 102 | if env[:result] 103 | b2.use MessageAlreadyCreated 104 | next 105 | end 106 | 107 | b2.use ConnectRackspace 108 | b2.use Provision 109 | if defined?(SyncedFolders) 110 | b2.use SyncedFolders 111 | else 112 | b2.use SyncFolders 113 | end 114 | b2.use CreateServer 115 | end 116 | end 117 | end 118 | 119 | def self.action_create_image 120 | Vagrant::Action::Builder.new.tap do |b| 121 | b.use ConfigValidate 122 | b.use Call, IsCreated do |env, b2| 123 | created = env[:result] 124 | 125 | if !created 126 | b2.use MessageNotCreated 127 | next 128 | end 129 | 130 | b2.use ConnectRackspace 131 | b2.use CreateImage 132 | end 133 | end 134 | end 135 | 136 | # Extended actions 137 | def self.action_list_images 138 | Vagrant::Action::Builder.new.tap do |b| 139 | # b.use ConfigValidate # is this per machine? 140 | b.use ConnectRackspace 141 | b.use ListImages 142 | end 143 | end 144 | 145 | def self.action_list_flavors 146 | Vagrant::Action::Builder.new.tap do |b| 147 | # b.use ConfigValidate # is this per machine? 148 | b.use ConnectRackspace 149 | b.use ListFlavors 150 | end 151 | end 152 | 153 | # The autoload farm 154 | action_root = Pathname.new(File.expand_path("../action", __FILE__)) 155 | autoload :ConnectRackspace, action_root.join("connect_rackspace") 156 | autoload :CreateServer, action_root.join("create_server") 157 | autoload :DeleteServer, action_root.join("delete_server") 158 | autoload :IsCreated, action_root.join("is_created") 159 | autoload :MessageAlreadyCreated, action_root.join("message_already_created") 160 | autoload :MessageNotCreated, action_root.join("message_not_created") 161 | autoload :ReadSSHInfo, action_root.join("read_ssh_info") 162 | autoload :ReadState, action_root.join("read_state") 163 | autoload :SyncFolders, action_root.join("sync_folders") 164 | autoload :CreateImage, action_root.join("create_image") 165 | autoload :ListImages, action_root.join("list_images") 166 | autoload :ListFlavors, action_root.join("list_flavors") 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/action/create_server.rb: -------------------------------------------------------------------------------- 1 | require "fog" 2 | require "log4r" 3 | 4 | require 'vagrant/util/retryable' 5 | 6 | module VagrantPlugins 7 | module Rackspace 8 | module Action 9 | # This creates the Rackspace server. 10 | class CreateServer 11 | include Vagrant::Util::Retryable 12 | 13 | def initialize(app, env) 14 | @app = app 15 | @logger = Log4r::Logger.new("vagrant_rackspace::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_rackspace.finding_flavor")) 24 | flavor = find_matching(env[:rackspace_compute].flavors, config.flavor) 25 | raise Errors::NoMatchingFlavor if !flavor 26 | 27 | # Find the image 28 | env[:ui].info(I18n.t("vagrant_rackspace.finding_image")) 29 | image = find_matching(env[:rackspace_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 | # If we are using a key name, can ignore the public key path 36 | if not config.key_name 37 | # If we're using the default keypair, then show a warning 38 | default_key_path = Vagrant.source_root.join("keys/vagrant.pub").to_s 39 | public_key_path = File.expand_path(config.public_key_path, env[:root_path]) 40 | 41 | if default_key_path == public_key_path 42 | env[:ui].warn(I18n.t("vagrant_rackspace.warn_insecure_ssh")) 43 | end 44 | end 45 | 46 | # Output the settings we're going to use to the user 47 | env[:ui].info(I18n.t("vagrant_rackspace.launching_server")) 48 | env[:ui].info(" -- Flavor: #{flavor.name}") 49 | env[:ui].info(" -- Image: #{image.name}") 50 | env[:ui].info(" -- Disk Config: #{config.disk_config}") if config.disk_config 51 | env[:ui].info(" -- Networks: #{config.networks}") if config.networks 52 | env[:ui].info(" -- Name: #{server_name}") 53 | 54 | # Build the options for launching... 55 | options = { 56 | :flavor_id => flavor.id, 57 | :image_id => image.id, 58 | :name => server_name, 59 | :metadata => config.metadata 60 | } 61 | 62 | if config.admin_password 63 | options[:password] = config.admin_password 64 | end 65 | 66 | if config.user_data 67 | options[:user_data] = File.read(config.user_data) 68 | end 69 | 70 | if config.config_drive 71 | options[:config_drive] = config.config_drive 72 | end 73 | 74 | if config.key_name 75 | options[:key_name] = config.key_name 76 | env[:ui].info(" -- Key Name: #{config.key_name}") 77 | else 78 | options[:personality] = [ 79 | { 80 | :path => "/root/.ssh/authorized_keys", 81 | :contents => Base64.encode64(File.read(public_key_path)) 82 | } 83 | ] 84 | end 85 | 86 | options[:disk_config] = config.disk_config if config.disk_config 87 | options[:networks] = config.networks if config.networks 88 | 89 | # Create the server 90 | server = env[:rackspace_compute].servers.create(options) 91 | 92 | # Store the ID right away so we can track it 93 | env[:machine].id = server.id 94 | 95 | # Wait for the server to finish building 96 | env[:ui].info(I18n.t("vagrant_rackspace.waiting_for_build")) 97 | retryable(:on => Fog::Errors::TimeoutError, :tries => 200) do 98 | # If we're interrupted don't worry about waiting 99 | next if env[:interrupted] 100 | 101 | # Set the progress 102 | env[:ui].clear_line 103 | env[:ui].report_progress(server.progress, 100, false) 104 | 105 | # Wait for the server to be ready 106 | begin 107 | server.wait_for(5) { ready? } 108 | rescue RuntimeError => e 109 | # If we don't have an error about a state transition, then 110 | # we just move on. 111 | raise if e.message !~ /should have transitioned/ 112 | raise Errors::CreateBadState, :state => server.state 113 | end 114 | end 115 | 116 | if !env[:interrupted] 117 | # Clear the line one more time so the progress is removed 118 | env[:ui].clear_line 119 | 120 | # Wait for RackConnect to complete 121 | if ( config.rackconnect ) 122 | env[:ui].info(I18n.t("vagrant_rackspace.waiting_for_rackconnect")) 123 | while true 124 | status = server.metadata.all["rackconnect_automation_status"] 125 | if ( !status.nil? ) 126 | env[:ui].info( status ) 127 | end 128 | break if env[:interrupted] 129 | break if (status.to_s =~ /deployed/i) 130 | sleep 10 131 | end 132 | end 133 | 134 | # Wait for SSH to become available 135 | env[:ui].info(I18n.t("vagrant_rackspace.waiting_for_ssh")) 136 | while true 137 | # If we're interrupted then just back out 138 | break if env[:interrupted] 139 | break if env[:machine].communicate.ready? 140 | sleep 2 141 | end 142 | 143 | env[:ui].info(I18n.t("vagrant_rackspace.ready")) 144 | end 145 | 146 | @app.call(env) 147 | end 148 | 149 | protected 150 | 151 | def escape_name_if_necessary(name) 152 | # The next release of fog should url escape these values. 153 | return name if Gem::Version.new(Fog::VERSION) > Gem::Version.new("1.22.1") 154 | 155 | Fog::Rackspace.escape(name) 156 | end 157 | 158 | # This method finds a matching _thing_ in a collection of 159 | # _things_. This works matching if the ID or NAME equals to 160 | # `name`. Or, if `name` is a regexp, a partial match is chosen 161 | # as well. 162 | def find_matching(collection, name) 163 | item = collection.find do |single| 164 | single.id == name || 165 | single.name == name || 166 | (name.is_a?(Regexp) && name =~ single.name) 167 | end 168 | 169 | # If it is not present in collection, it might be a non-standard image/flavor 170 | if item.nil? && !name.is_a?(Regexp) 171 | item = collection.get escape_name_if_necessary(name) 172 | end 173 | 174 | item 175 | end 176 | 177 | 178 | end 179 | end 180 | end 181 | end 182 | -------------------------------------------------------------------------------- /spec/vagrant-rackspace/config_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require "vagrant-rackspace/config" 3 | 4 | describe VagrantPlugins::Rackspace::Config do 5 | describe "defaults" do 6 | let(:vagrant_public_key) { Vagrant.source_root.join("keys/vagrant.pub") } 7 | 8 | subject do 9 | super().tap do |o| 10 | o.finalize! 11 | end 12 | end 13 | 14 | its(:api_key) { should be_nil } 15 | its(:rackspace_region) { should be_nil } 16 | its(:rackspace_compute_url) { should be_nil } 17 | its(:rackspace_auth_url) { should be_nil } 18 | its(:flavor) { should eq(/512MB/) } 19 | its(:image) { should eq(/Ubuntu/) } 20 | its(:public_key_path) { should eql(vagrant_public_key) } 21 | its(:rackconnect) { should be_nil } 22 | its(:server_name) { should be_nil } 23 | its(:username) { should be_nil } 24 | its(:disk_config) { should be_nil } 25 | its(:networks) { should be_nil } 26 | its(:rsync_includes) { should be_nil } 27 | its(:admin_password) { should be_nil } 28 | end 29 | 30 | describe "overriding defaults" do 31 | [:api_key, 32 | :rackspace_region, 33 | :rackspace_compute_url, 34 | :rackspace_auth_url, 35 | :flavor, 36 | :image, 37 | :public_key_path, 38 | :rackconnect, 39 | :server_name, 40 | :disk_config, 41 | :username, 42 | :admin_password].each do |attribute| 43 | it "should not default #{attribute} if overridden" do 44 | subject.send("#{attribute}=".to_sym, "foo") 45 | subject.finalize! 46 | subject.send(attribute).should == "foo" 47 | end 48 | end 49 | 50 | it "should not default networks if overridden" do 51 | net_id = "deadbeef-0000-0000-0000-000000000000" 52 | subject.send(:network, net_id) 53 | subject.finalize! 54 | subject.send(:networks).should include(net_id) 55 | subject.send(:networks).should include(VagrantPlugins::Rackspace::Config::PUBLIC_NET_ID) 56 | subject.send(:networks).should include(VagrantPlugins::Rackspace::Config::SERVICE_NET_ID) 57 | end 58 | 59 | it "should not default rsync_includes if overridden" do 60 | inc = "core" 61 | subject.send(:rsync_include, inc) 62 | subject.finalize! 63 | subject.send(:rsync_includes).should include(inc) 64 | end 65 | end 66 | 67 | describe "validation" do 68 | let(:machine) { double("machine") } 69 | let(:validation_errors) { subject.validate(machine)['RackSpace Provider'] } 70 | let(:error_message) { double("error message") } 71 | 72 | before(:each) do 73 | machine.stub_chain(:env, :root_path).and_return '/' 74 | subject.username = 'foo' 75 | subject.api_key = 'bar' 76 | end 77 | 78 | subject do 79 | super().tap do |o| 80 | o.finalize! 81 | end 82 | end 83 | 84 | context "with invalid key" do 85 | it "should raise an error" do 86 | subject.nonsense1 = true 87 | subject.nonsense2 = false 88 | I18n.should_receive(:t).with('vagrant.config.common.bad_field', 89 | { :fields => 'nonsense1, nonsense2' }) 90 | .and_return error_message 91 | validation_errors.first.should == error_message 92 | end 93 | end 94 | context "with good values" do 95 | it "should validate" do 96 | validation_errors.should be_empty 97 | end 98 | end 99 | 100 | context "the API key" do 101 | it "should error if not given" do 102 | subject.api_key = nil 103 | I18n.should_receive(:t).with('vagrant_rackspace.config.api_key_required').and_return error_message 104 | validation_errors.first.should == error_message 105 | end 106 | end 107 | 108 | context "the public key path" do 109 | it "should have errors if the key doesn't exist" do 110 | subject.public_key_path = 'missing' 111 | I18n.should_receive(:t).with('vagrant_rackspace.config.public_key_not_found').and_return error_message 112 | validation_errors.first.should == error_message 113 | end 114 | it "should not have errors if the key exists with an absolute path" do 115 | subject.public_key_path = File.expand_path 'locales/en.yml', Dir.pwd 116 | validation_errors.should be_empty 117 | end 118 | it "should not have errors if the key exists with a relative path" do 119 | machine.stub_chain(:env, :root_path).and_return '.' 120 | subject.public_key_path = 'locales/en.yml' 121 | validation_errors.should be_empty 122 | end 123 | end 124 | 125 | context "the username" do 126 | it "should error if not given" do 127 | subject.username = nil 128 | I18n.should_receive(:t).with('vagrant_rackspace.config.username_required').and_return error_message 129 | validation_errors.first.should == error_message 130 | end 131 | end 132 | 133 | [:rackspace_compute_url, :rackspace_auth_url].each do |url| 134 | context "the #{url}" do 135 | it "should not validate if the URL is invalid" do 136 | subject.send "#{url}=", 'baz' 137 | I18n.should_receive(:t).with('vagrant_rackspace.config.invalid_uri', {:key => url, :uri => 'baz'}).and_return error_message 138 | validation_errors.first.should == error_message 139 | end 140 | end 141 | end 142 | end 143 | 144 | describe "rackspace_auth_url" do 145 | it "should return UNSET_VALUE if rackspace_auth_url and rackspace_region are UNSET" do 146 | subject.rackspace_auth_url.should == VagrantPlugins::Rackspace::Config::UNSET_VALUE 147 | end 148 | it "should return UNSET_VALUE if rackspace_auth_url is UNSET and rackspace_region is :ord" do 149 | subject.rackspace_region = :ord 150 | subject.rackspace_auth_url.should == VagrantPlugins::Rackspace::Config::UNSET_VALUE 151 | end 152 | it "should return UK Authentication endpoint if rackspace_auth_url is UNSET and rackspace_region is :lon" do 153 | subject.rackspace_region = :lon 154 | subject.rackspace_auth_url.should == Fog::Rackspace::UK_AUTH_ENDPOINT 155 | end 156 | it "should return custom endpoint if supplied and rackspace_region is :lon" do 157 | my_endpoint = 'http://custom-endpoint.com' 158 | subject.rackspace_region = :lon 159 | subject.rackspace_auth_url = my_endpoint 160 | subject.rackspace_auth_url.should == my_endpoint 161 | end 162 | it "should return custom endpoint if supplied and rackspace_region is UNSET" do 163 | my_endpoint = 'http://custom-endpoint.com' 164 | subject.rackspace_auth_url = my_endpoint 165 | subject.rackspace_auth_url.should == my_endpoint 166 | end 167 | end 168 | 169 | 170 | describe "lon_region?" do 171 | it "should return false if rackspace_region is UNSET_VALUE" do 172 | subject.rackspace_region = VagrantPlugins::Rackspace::Config::UNSET_VALUE 173 | subject.send(:lon_region?).should be_false 174 | end 175 | it "should return false if rackspace_region is nil" do 176 | subject.rackspace_region = nil 177 | subject.send(:lon_region?).should be_false 178 | end 179 | it "should return false if rackspace_region is :ord" do 180 | subject.rackspace_region = :ord 181 | subject.send(:lon_region?).should be_false 182 | end 183 | it "should return true if rackspace_region is 'lon'" do 184 | subject.rackspace_region = 'lon' 185 | subject.send(:lon_region?).should be_true 186 | end 187 | it "should return true if rackspace_Region is :lon" do 188 | subject.rackspace_region = :lon 189 | subject.send(:lon_region?).should be_true 190 | end 191 | end 192 | 193 | describe "network" do 194 | it "should remove SERVICE_NET_ID if :service_net is detached" do 195 | subject.send(:network, :service_net, :attached => false) 196 | subject.send(:networks).should_not include(VagrantPlugins::Rackspace::Config::SERVICE_NET_ID) 197 | end 198 | 199 | it "should not allow duplicate networks" do 200 | net_id = "deadbeef-0000-0000-0000-000000000000" 201 | subject.send(:network, net_id) 202 | subject.send(:network, net_id) 203 | subject.send(:networks).count(net_id).should == 1 204 | end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vagrant Rackspace Cloud Provider 2 | 3 | This is a [Vagrant](http://www.vagrantup.com) 1.1+ plugin that adds a 4 | [Rackspace Cloud](http://www.rackspace.com/cloud) provider to Vagrant, 5 | allowing Vagrant to control and provision machines within Rackspace 6 | cloud. 7 | 8 | **Note:** This plugin requires Vagrant 1.1+. 9 | 10 | ## Features 11 | 12 | * Boot Rackspace Cloud instances. 13 | * SSH into the instances. 14 | * Provision the instances with any built-in Vagrant provisioner. 15 | * Minimal synced folder support via `rsync`. 16 | 17 | ## Usage 18 | 19 | Install using standard Vagrant 1.1+ plugin installation methods. After 20 | installing, `vagrant up` and specify the `rackspace` provider. An example is 21 | shown below. 22 | 23 | ``` 24 | $ vagrant plugin install vagrant-rackspace 25 | ... 26 | $ vagrant up --provider=rackspace 27 | ... 28 | ``` 29 | 30 | Of course prior to doing this, you'll need to obtain an Rackspace-compatible 31 | box file for Vagrant. 32 | 33 | ### CentOS / RHEL (sudo: sorry, you must have a tty to run sudo) 34 | 35 | The default configuration of the RHEL family of Linux distributions requires a tty in order to run sudo. Vagrant does not connect with a tty by default, so you may experience the error: 36 | > sudo: sorry, you must have a tty to run sudo 37 | 38 | The best way to deal with this error is to upgrade to Vagrant 1.4 or later, and enable: 39 | ```ruby 40 | config.ssh.pty = true 41 | ``` 42 | 43 | ## Quick Start 44 | 45 | After installing the plugin (instructions above), the quickest way to get 46 | started is to actually use a dummy Rackspace box and specify all the details 47 | manually within a `config.vm.provider` block. So first, add the dummy 48 | box using any name you want: 49 | 50 | ``` 51 | $ vagrant box add dummy https://github.com/mitchellh/vagrant-rackspace/raw/master/dummy.box 52 | ... 53 | ``` 54 | 55 | And then make a Vagrantfile that looks like the following, filling in 56 | your information where necessary. 57 | 58 | ```ruby 59 | Vagrant.configure("2") do |config| 60 | config.vm.box = "dummy" 61 | 62 | config.vm.provider :rackspace do |rs| 63 | rs.username = "YOUR USERNAME" 64 | rs.api_key = "YOUR API KEY" 65 | rs.flavor = /1 GB Performance/ 66 | rs.image = /Ubuntu/ 67 | rs.metadata = {"key" => "value"} # optional 68 | end 69 | end 70 | ``` 71 | 72 | And then run `vagrant up --provider=rackspace`. 73 | 74 | This will start an Ubuntu 12.04 instance in the DFW datacenter region within 75 | your account. And assuming your SSH information was filled in properly 76 | within your Vagrantfile, SSH and provisioning will work as well. 77 | 78 | Note that normally a lot of this boilerplate is encoded within the box 79 | file, but the box file used for the quick start, the "dummy" box, has 80 | no preconfigured defaults. 81 | 82 | ### Flavors / Images 83 | 84 | To determine what flavors and images are avliable in your region refer to the [Custom Commands](#custom-commands) section. 85 | 86 | ### RackConnect 87 | 88 | If you are using RackConnect with vagrant, you will need to add the following line to the `config.vm.provider` section to prevent timeouts. 89 | 90 | ``` 91 | rs.rackconnect = true 92 | ``` 93 | 94 | ## Custom Commands 95 | 96 | The plugin includes several Rackspace-specific vagrant commands. You can get the 97 | list of available commands with `vagrant rackspace -h`. 98 | 99 | If you want to know what images or flavors are available for a machine, you can use: 100 | 101 | ``` 102 | $ vagrant rackspace images 103 | $ vagrant rackspace flavors 104 | ``` 105 | 106 | In a multi-machine Vagrantfile you can also query for a single machine: 107 | ``` 108 | $ vagrant rackspace images 109 | $ vagrant rackspace flavors 110 | ``` 111 | 112 | These command will connect to Rackspace using the settings associated with the machine, 113 | and query the region to get the list of available images or flavors. 114 | 115 | ## Box Format 116 | 117 | Every provider in Vagrant must introduce a custom box format. This 118 | provider introduces `rackspace` boxes. You can view an example box in 119 | the [example_box/ directory](https://github.com/mitchellh/vagrant-rackspace/tree/master/example_box). 120 | That directory also contains instructions on how to build a box. 121 | 122 | The box format is basically just the required `metadata.json` file 123 | along with a `Vagrantfile` that does default settings for the 124 | provider-specific configuration for this provider. 125 | 126 | ## Configuration 127 | 128 | This provider exposes quite a few provider-specific configuration options: 129 | 130 | * `api_key` - The API key for accessing Rackspace. 131 | * `flavor` - The server flavor to boot. This can be a string matching 132 | the exact ID or name of the server, or this can be a regular expression 133 | to partially match some server flavor. Flavors are listed [here](#flavors). 134 | * `image` - The server image to boot. This can be a string matching the 135 | exact ID or name of the image, or this can be a regular expression to 136 | partially match some image. 137 | * `rackspace_region` - The region to hit. By default this is :dfw. Valid options are: 138 | :dfw, :ord, :lon, :iad, :syd. Users should preference using this setting over `rackspace_compute_url` setting. 139 | * `rackspace_compute_url` - The compute_url to hit. This is good for custom endpoints. 140 | * `rackspace_auth_url` - The endpoint to authentication against. By default, vagrant will use the global 141 | rackspace authentication endpoint for all regions with the exception of :lon. IF :lon region is specified 142 | vagrant will authenticate against the UK authentication endpoint. 143 | * `public_key_path` - The path to a public key to initialize with the remote 144 | server. This should be the matching pair for the private key configured 145 | with `config.ssh.private_key_path` on Vagrant. 146 | * `key_name` - If a public key has been [uploaded to the account already](http://docs.rackspace.com/servers/api/v2/cs-devguide/content/ServersKeyPairs-d1e2545.html), the uploaded key can be used to initialize the remote server by providing its name. The uploaded public key should be the matching pair for the private key configured 147 | with `config.ssh.private_key_path` on Vagrant. 148 | * `server_name` - The name of the server within RackSpace Cloud. This 149 | defaults to the name of the Vagrant machine (via `config.vm.define`), but 150 | can be overridden with this. 151 | * `username` - The username with which to access Rackspace. 152 | * `disk_config` - Disk Configuration 'AUTO' or 'MANUAL' 153 | * `metadata` - A set of key pair values that will be passed to the instance 154 | for configuration. 155 | 156 | These can be set like typical provider-specific configuration: 157 | 158 | ```ruby 159 | Vagrant.configure("2") do |config| 160 | # ... other stuff 161 | 162 | config.vm.provider :rackspace do |rs| 163 | rs.username = "mitchellh" 164 | rs.api_key = "foobarbaz" 165 | end 166 | end 167 | ``` 168 | 169 | ## Networks 170 | 171 | Networking features in the form of `config.vm.network` are not 172 | supported with `vagrant-rackspace`, currently. If any of these are 173 | specified, Vagrant will emit a warning, but will otherwise boot 174 | the Rackspace server. 175 | 176 | However, you may attach a VM to an isolated [Cloud Network](http://www.rackspace.com/knowledge_center/article/getting-started-with-cloud-networks) (or Networks) using the `network` configuration option. Here's an example which adds two Cloud Networks and disables ServiceNet with the `:attach => false` option: 177 | 178 | ```ruby 179 | config.vm.provider :rackspace do |rs| 180 | rs.username = "mitchellh" 181 | rs.api_key = "foobarbaz" 182 | rs.network '443aff42-be57-effb-ad30-c097c1e4503f' 183 | rs.network '5e738e11-def2-4a75-ad1e-05bbe3b49efe' 184 | rs.network :service_net, :attached => false 185 | end 186 | ``` 187 | 188 | ## Synced Folders 189 | 190 | There is minimal support for synced folders. Upon `vagrant up`, 191 | `vagrant reload`, and `vagrant provision`, the Rackspace provider will use 192 | `rsync` (if available) to uni-directionally sync the folder to 193 | the remote machine over SSH. 194 | 195 | This is good enough for all built-in Vagrant provisioners (shell, 196 | chef, and puppet) to work! 197 | 198 | ## Development 199 | 200 | To work on the `vagrant-rackspace` plugin, clone this repository out, and use 201 | [Bundler](http://gembundler.com) to get the dependencies: 202 | 203 | ``` 204 | $ bundle 205 | ``` 206 | 207 | Once you have the dependencies, verify the unit tests pass with `rake`: 208 | 209 | ``` 210 | $ bundle exec rake 211 | ``` 212 | 213 | If those pass, you're ready to start developing the plugin. You can test 214 | the plugin without installing it into your Vagrant environment by just 215 | creating a `Vagrantfile` in the top level of this directory (it is gitignored) 216 | that uses it, and uses bundler to execute Vagrant: 217 | 218 | ``` 219 | $ bundle exec vagrant up --provider=rackspace 220 | ``` 221 | -------------------------------------------------------------------------------- /lib/vagrant-rackspace/config.rb: -------------------------------------------------------------------------------- 1 | require "vagrant" 2 | require "fog" 3 | 4 | module VagrantPlugins 5 | module Rackspace 6 | class Config < Vagrant.plugin("2", :config) 7 | # The API key to access RackSpace. 8 | # 9 | # @return [String] 10 | attr_accessor :api_key 11 | 12 | # The region to access RackSpace. If nil, it will default 13 | # to DFW. 14 | # (formerly know as 'endpoint') 15 | # 16 | # expected to be a symbol - :dfw (default), :ord, :lon 17 | # 18 | # Users should preference the rackspace_region setting over rackspace_compute_url 19 | attr_accessor :rackspace_region 20 | 21 | # The compute_url to access RackSpace. If nil, it will default 22 | # to DFW. 23 | # (formerly know as 'endpoint') 24 | # 25 | # expected to be a string url - 26 | # 'https://dfw.servers.api.rackspacecloud.com/v2' 27 | # 'https://ord.servers.api.rackspacecloud.com/v2' 28 | # 'https://lon.servers.api.rackspacecloud.com/v2' 29 | # 30 | # alternatively, can use constants if you require 'fog/rackspace' in your Vagrantfile 31 | # Fog::Compute::RackspaceV2::DFW_ENDPOINT 32 | # Fog::Compute::RackspaceV2::ORD_ENDPOINT 33 | # Fog::Compute::RackspaceV2::LON_ENDPOINT 34 | # 35 | # Users should preference the rackspace_region setting over rackspace_compute_url 36 | attr_accessor :rackspace_compute_url 37 | 38 | # The authenication endpoint. This defaults to Rackspace's global authentication endpoint. 39 | # Users of the London data center should specify the following: 40 | # https://lon.identity.api.rackspacecloud.com/v2.0 41 | attr_writer :rackspace_auth_url 42 | 43 | # The flavor of server to launch, either the ID or name. This 44 | # can also be a regular expression to partially match a name. 45 | attr_accessor :flavor 46 | 47 | # The name or ID of the image to use. This can also be a regular 48 | # expression to partially match a name. 49 | attr_accessor :image 50 | 51 | # The path to the public key to set up on the remote server for SSH. 52 | # This should match the private key configured with `config.ssh.private_key_path`. 53 | # 54 | # @return [String] 55 | attr_accessor :public_key_path 56 | 57 | # Alternately, if a keypair were already uploaded to Rackspace, 58 | # the key name could be provided. 59 | # 60 | # @return [String] 61 | attr_accessor :key_name 62 | 63 | # The path to the file containing user data for cloud init 64 | # 65 | # @ return [String] 66 | attr_accessor :user_data 67 | 68 | # Whether to attach a read-only configuration drive 69 | # 70 | # @ return [Boolean] 71 | attr_accessor :config_drive 72 | 73 | # A Hash of metadata that will be sent to the instance for configuration 74 | # 75 | # @return [Hash] 76 | attr_accessor :metadata 77 | 78 | # The option that indicates RackConnect usage or not. 79 | # 80 | # @return [Boolean] 81 | attr_accessor :rackconnect 82 | 83 | # The name of the server. This defaults to the name of the machine 84 | # defined by Vagrant (via `config.vm.define`), but can be overriden 85 | # here. 86 | attr_accessor :server_name 87 | 88 | # The username to access RackSpace. 89 | # 90 | # @return [String] 91 | attr_accessor :username 92 | 93 | # The disk configuration value. 94 | # * AUTO - The server is built with a single partition the size of the target flavor disk. The file system is automatically adjusted to fit the entire partition. 95 | # This keeps things simple and automated. AUTO is valid only for images and servers with a single partition that use the EXT3 file system. 96 | # This is the default setting for applicable Rackspace base images. 97 | # 98 | # * MANUAL - The server is built using whatever partition scheme and file system is in the source image. If the target flavor disk is larger, 99 | # the remaining disk space is left unpartitioned. This enables images to have non-EXT3 file systems, multiple partitions, 100 | # and so on, and enables you to manage the disk configuration. 101 | # 102 | # This defaults to MANUAL 103 | attr_accessor :disk_config 104 | 105 | # Cloud Networks to attach to the server 106 | # 107 | # @return [Array] 108 | attr_accessor :networks 109 | 110 | # Opt files/directories in to the rsync operation performed by this provider 111 | # 112 | # @return [Array] 113 | attr_accessor :rsync_includes 114 | 115 | # Password to set for root (on Linux) or Administrator (on Windows) 116 | # A random password will be generated if admin_password is not set or 117 | # does not meet the password requirements of the operating system. 118 | # 119 | # @return [String] 120 | attr_accessor :admin_password 121 | 122 | # Default Rackspace Cloud Network IDs 123 | SERVICE_NET_ID = '11111111-1111-1111-1111-111111111111' 124 | PUBLIC_NET_ID = '00000000-0000-0000-0000-000000000000' 125 | 126 | def initialize 127 | @api_key = UNSET_VALUE 128 | @rackspace_region = UNSET_VALUE 129 | @rackspace_compute_url = UNSET_VALUE 130 | @rackspace_auth_url = UNSET_VALUE 131 | @flavor = UNSET_VALUE 132 | @image = UNSET_VALUE 133 | @public_key_path = UNSET_VALUE 134 | @rackconnect = UNSET_VALUE 135 | @server_name = UNSET_VALUE 136 | @user_data = UNSET_VALUE 137 | @config_drive = UNSET_VALUE 138 | @username = UNSET_VALUE 139 | @disk_config = UNSET_VALUE 140 | @networks = [] 141 | @rsync_includes = [] 142 | end 143 | 144 | def finalize! 145 | @api_key = nil if @api_key == UNSET_VALUE 146 | @rackspace_region = nil if @rackspace_region == UNSET_VALUE 147 | @rackspace_compute_url = nil if @rackspace_compute_url == UNSET_VALUE 148 | @rackspace_auth_url = nil if @rackspace_auth_url == UNSET_VALUE 149 | @flavor = /512MB/ if @flavor == UNSET_VALUE 150 | @image = /Ubuntu/ if @image == UNSET_VALUE 151 | @rackconnect = nil if @rackconnect == UNSET_VALUE 152 | @server_name = nil if @server_name == UNSET_VALUE 153 | @user_data = nil if @user_data == UNSET_VALUE 154 | @config_drive = nil if @config_drive == UNSET_VALUE 155 | @metadata = nil if @metadata == UNSET_VALUE 156 | @username = nil if @username == UNSET_VALUE 157 | @disk_config = nil if @disk_config == UNSET_VALUE 158 | @networks = nil if @networks.empty? 159 | @rsync_includes = nil if @rsync_includes.empty? 160 | 161 | if @public_key_path == UNSET_VALUE 162 | @public_key_path = Vagrant.source_root.join("keys/vagrant.pub") 163 | end 164 | end 165 | 166 | # @note Currently, you must authenticate against the UK authenication endpoint to access the London Data center. 167 | # Hopefully this method makes the experience more seemless for users of the UK cloud. 168 | def rackspace_auth_url 169 | if (@rackspace_auth_url.nil? || @rackspace_auth_url == UNSET_VALUE) && lon_region? 170 | Fog::Rackspace::UK_AUTH_ENDPOINT 171 | else 172 | @rackspace_auth_url 173 | end 174 | end 175 | 176 | def network(net_id, options={}) 177 | # Eventually, this should accept options for network configuration, 178 | # primarily the IP address, but at the time of writing these 179 | # options are unsupported by Cloud Networks. 180 | options = {:attached => true}.merge(options) 181 | 182 | # Add the default Public and ServiceNet networks 183 | if @networks.empty? 184 | @networks = [PUBLIC_NET_ID, SERVICE_NET_ID] 185 | end 186 | 187 | net_id = SERVICE_NET_ID if net_id == :service_net 188 | 189 | if options[:attached] 190 | @networks << net_id unless @networks.include? net_id 191 | else 192 | @networks.delete net_id 193 | end 194 | end 195 | 196 | def rsync_include(inc) 197 | @rsync_includes << inc 198 | end 199 | 200 | def validate(machine) 201 | errors = _detected_errors 202 | 203 | errors << I18n.t("vagrant_rackspace.config.api_key_required") if !@api_key 204 | errors << I18n.t("vagrant_rackspace.config.username_required") if !@username 205 | errors << I18n.t("one of vagrant.rackspace.config.key_name or vagrant.rackspace.config.public_key_path required") if !@key_name && !@public_key_path 206 | 207 | { 208 | :rackspace_compute_url => @rackspace_compute_url, 209 | :rackspace_auth_url => @rackspace_auth_url 210 | }.each_pair do |key, value| 211 | errors << I18n.t("vagrant_rackspace.config.invalid_uri", :key => key, :uri => value) unless value.nil? || valid_uri?(value) 212 | end 213 | 214 | if !@key_name 215 | public_key_path = File.expand_path(@public_key_path, machine.env.root_path) 216 | if !File.file?(public_key_path) 217 | errors << I18n.t("vagrant_rackspace.config.public_key_not_found") 218 | end 219 | end 220 | 221 | { "RackSpace Provider" => errors } 222 | end 223 | 224 | private 225 | 226 | def lon_region? 227 | rackspace_region && rackspace_region != UNSET_VALUE && rackspace_region.to_sym == :lon 228 | end 229 | 230 | private 231 | 232 | def valid_uri? value 233 | uri = URI.parse value 234 | uri.kind_of?(URI::HTTP) 235 | end 236 | end 237 | end 238 | end 239 | --------------------------------------------------------------------------------