diff --git a/.gitignore b/.gitignore index 5af0370..d40b55f 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,5 @@ spec/reports test/tmp test/version_tmp tmp -Vagrantfile .idea/ diff --git a/README.md b/README.md index 8ca47f9..058fe1d 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,10 @@ This provider exposes quite a few provider-specific configuration options: * `image` - The server image to boot. This can be a string matching the exact ID or name of the image, or this can be a regular expression to partially match some image. -* `endpoint` - The endpoint to hit. By default this is DFW. +* `rackspace_region` - The region to hit. By default this is :dfw. Valid options are: +:dfw, :ord, :lon. User this OR rackspace_compute_url +* `rackspace_compute_url` - The compute_url to hit. This is good for custom endpoints. +Use this OR rackspace_region. * `public_key_path` - The path to a public key to initialize with the remote server. This should be the matching pair for the private key configured with `config.ssh.private_key_path` on Vagrant. diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..bbbee1c --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,122 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + Vagrant.require_plugin "vagrant-rackspace" + # All Vagrant configuration is done here. The most common configuration + # options are documented and commented below. For a complete reference, + # please see the online documentation at vagrantup.com. + + # Every Vagrant virtual environment requires a box to build off of. + config.vm.box = "dummy" + config.vm.provider :rackspace do |rs| + rs.username = ENV['RAX_USERNAME'] + rs.api_key = ENV['RAX_API_KEY'] + end + # The url from where the 'config.vm.box' box will be fetched if it + # doesn't already exist on the user's system. + # config.vm.box_url = "http://domain.com/path/to/above.box" + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # config.vm.network :forwarded_port, guest: 80, host: 8080 + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network :private_network, ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network :public_network + + # If true, then any SSH connections made will enable agent forwarding. + # Default value: false + # config.ssh.forward_agent = true + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider :virtualbox do |vb| + # # Don't boot with headless mode + # vb.gui = true + # + # # Use VBoxManage to customize the VM. For example to change memory: + # vb.customize ["modifyvm", :id, "--memory", "1024"] + # end + # + # View the documentation for the provider you're using for more + # information on available options. + + # Enable provisioning with Puppet stand alone. Puppet manifests + # are contained in a directory path relative to this Vagrantfile. + # You will need to create the manifests directory and a manifest in + # the file base.pp in the manifests_path directory. + # + # An example Puppet manifest to provision the message of the day: + # + # # group { "puppet": + # # ensure => "present", + # # } + # # + # # File { owner => 0, group => 0, mode => 0644 } + # # + # # file { '/etc/motd': + # # content => "Welcome to your Vagrant-built virtual machine! + # # Managed by Puppet.\n" + # # } + # + # config.vm.provision :puppet do |puppet| + # puppet.manifests_path = "manifests" + # puppet.manifest_file = "init.pp" + # end + + # Enable provisioning with chef solo, specifying a cookbooks path, roles + # path, and data_bags path (all relative to this Vagrantfile), and adding + # some recipes and/or roles. + # + # config.vm.provision :chef_solo do |chef| + # chef.cookbooks_path = "../my-recipes/cookbooks" + # chef.roles_path = "../my-recipes/roles" + # chef.data_bags_path = "../my-recipes/data_bags" + # chef.add_recipe "mysql" + # chef.add_role "web" + # + # # You may also specify custom JSON attributes: + # chef.json = { :mysql_password => "foo" } + # end + + # Enable provisioning with chef server, specifying the chef server URL, + # and the path to the validation key (relative to this Vagrantfile). + # + # The Opscode Platform uses HTTPS. Substitute your organization for + # ORGNAME in the URL and validation key. + # + # If you have your own Chef Server, use the appropriate URL, which may be + # HTTP instead of HTTPS depending on your configuration. Also change the + # validation key to validation.pem. + # + # config.vm.provision :chef_client do |chef| + # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" + # chef.validation_key_path = "ORGNAME-validator.pem" + # end + # + # If you're using the Opscode platform, your validator client is + # ORGNAME-validator, replacing ORGNAME with your organization name. + # + # If you have your own Chef Server, the default validation client name is + # chef-validator, unless you changed the configuration. + # + # chef.validation_client_name = "ORGNAME-validator" +end diff --git a/features/provision.feature b/features/provision.feature new file mode 100644 index 0000000..fa70d70 --- /dev/null +++ b/features/provision.feature @@ -0,0 +1,36 @@ +@announce +@vagrant-rackspace +Feature: vagrant-rackspace fog tests + + Background: + Given I have Rackspace credentials available + And I have a "fog_mock.rb" file + + Scenario: Create a single server (with provisioning) + Given a file named "Vagrantfile" with: + """ + Vagrant.configure("2") do |config| + Vagrant.require_plugin "vagrant-rackspace" + + config.vm.box = "dummy" + config.ssh.private_key_path = "~/.ssh/id_rsa" + config.ssh.max_tries = 1 + config.ssh.timeout = 10 + + config.vm.provider :rackspace do |rs| + rs.server_name = 'vagrant-provisioned-server' + rs.username = ENV['RAX_USERNAME'] + rs.api_key = ENV['RAX_API_KEY'] + rs.rackspace_region = ENV['RAX_REGION'].downcase.to_sym + rs.flavor = /512MB/ + rs.image = /Ubuntu/ + rs.public_key_path = "~/.ssh/id_rsa.pub" + end + + config.vm.provision :shell, :inline => "echo Hello, World" + end + """ + When I successfully run `bundle exec vagrant up --provider rackspace` + # I want to capture the ID like I do in tests for other tools, but Vagrant doesn't print it! + # And I get the server from "Instance ID:" + Then the server "vagrant-provisioned-server" should be active \ No newline at end of file diff --git a/features/steps/sdk_steps.rb b/features/steps/sdk_steps.rb new file mode 100644 index 0000000..4206caf --- /dev/null +++ b/features/steps/sdk_steps.rb @@ -0,0 +1,13 @@ +Given(/^I have Rackspace credentials available$/) do + fail unless ENV['RAX_USERNAME'] && ENV['RAX_API_KEY'] +end + +Given(/^I have a "fog_mock.rb" file$/) do + script = File.open("features/support/fog_mock.rb").read + steps %Q{ + Given a file named "fog_mock.rb" with: + """ + #{script} + """ + } +end \ No newline at end of file diff --git a/features/steps/server_steps.rb b/features/steps/server_steps.rb new file mode 100644 index 0000000..3b21903 --- /dev/null +++ b/features/steps/server_steps.rb @@ -0,0 +1,25 @@ +When(/^I get the server from "(.*?)"$/) do |label| + @server_id = all_output.match(/#{label}\s([\w-]*)/)[1] + puts "Server: #{@server_id}" +end + +When(/^I load the server$/) do + @server_id = all_output.strip.lines.to_a.last + puts "Server: #{@server_id}" +end + +Then(/^the server should be active$/) do + unless Fog.mock? # unfortunately we can't assert this with Fog.mock!, since mocked objects do not persist from the subprocess + assert_active @server_id + end +end + +Then(/^the server "(.+)" should be active$/) do |server_name| + server = @compute.servers.all.find{|s| s.name == server_name} + assert_active server.id +end + +def assert_active server_id + server = @compute.servers.get server_id + server.state.should == 'ACTIVE' +end \ No newline at end of file diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000..fb553e6 --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,37 @@ +require 'fog' +require 'aruba/cucumber' + +Fog.mock! if ENV['RAX_MOCK'] == 'true' + +Before do | scenario | + @aruba_timeout_seconds = 600 + @scenario = File.basename(scenario.file) + ENV['CASSETTE'] = @scenario + + proxy_options = { + :connection_options => { + :proxy => ENV['https_proxy'], + :ssl_verify_peer => false + } + } + + connect_options = { + :provider => 'rackspace', + :rackspace_username => ENV['RAX_USERNAME'], + :rackspace_api_key => ENV['RAX_API_KEY'], + :version => :v2, # Use Next Gen Cloud Servers + :rackspace_region => ENV['RAX_REGION'].downcase.to_sym + } + connect_options.merge!(proxy_options) unless ENV['https_proxy'].nil? + @compute = Fog::Compute.new(connect_options) +end + +Around do | scenario, block | + Bundler.with_clean_env do + block.call + end +end + +After('@creates_server') do + @compute.servers.delete @server_id +end diff --git a/features/support/fog_mock.rb b/features/support/fog_mock.rb new file mode 100644 index 0000000..7503f11 --- /dev/null +++ b/features/support/fog_mock.rb @@ -0,0 +1,19 @@ +require 'fog' +if ENV['RAX_MOCK'] == 'true' + Fog.mock! + Fog::Rackspace::MockData.configure do |c| + c[:image_name_generator] = Proc.new { "Ubuntu" } + c[:ipv4_generator] = Proc.new { "10.11.12.2"} + end + connect_options = { + :provider => 'rackspace', + :rackspace_username => ENV['RAX_USERNAME'], + :rackspace_api_key => ENV['RAX_API_KEY'], + :version => :v2, # Use Next Gen Cloud Servers + :rackspace_region => :ord #Use Chicago Region + } + connect_options.merge!(proxy_options) unless ENV['https_proxy'].nil? + compute = Fog::Compute.new(connect_options) + # Force creation of Ubuntu image so it will show up in compute.images.list + compute.images.get(0) +end \ No newline at end of file diff --git a/features/vagrant-rackspace.feature b/features/vagrant-rackspace.feature new file mode 100644 index 0000000..d016704 --- /dev/null +++ b/features/vagrant-rackspace.feature @@ -0,0 +1,74 @@ +@announce +@vagrant-rackspace +Feature: vagrant-rackspace fog tests + As a Fog developer + I want to smoke (or "fog") test vagrant-rackspace. + So I am confident my upstream changes did not create downstream problems. + + Background: + Given I have Rackspace credentials available + And I have a "fog_mock.rb" file + + Scenario: Create a single server (region) + Given a file named "Vagrantfile" with: + """ + # Testing options + require File.expand_path '../fog_mock', __FILE__ + + Vagrant.configure("2") do |config| + # dev/test method of loading plugin, normally would be 'vagrant plugin install vagrant-rackspace' + Vagrant.require_plugin "vagrant-rackspace" + + config.vm.box = "dummy" + config.ssh.username = "vagrant" if Fog.mock? + config.ssh.private_key_path = "~/.ssh/id_rsa" unless Fog.mock? + config.ssh.max_tries = 1 + config.ssh.timeout = 10 + + config.vm.provider :rackspace do |rs| + rs.server_name = 'vagrant-single-server' + rs.username = ENV['RAX_USERNAME'] + rs.api_key = ENV['RAX_API_KEY'] + rs.rackspace_region = ENV['RAX_REGION'].downcase.to_sym + rs.flavor = /512MB/ + rs.image = /Ubuntu/ + rs.public_key_path = "~/.ssh/id_rsa.pub" unless Fog.mock? + end + end + """ + When I successfully run `bundle exec vagrant up --provider rackspace` + # I want to capture the ID like I do in tests for other tools, but Vagrant doesn't print it! + # And I get the server from "Instance ID:" + Then the server "vagrant-single-server" should be active + +Scenario: Create a single server (compute_url) + Given a file named "Vagrantfile" with: + """ + # Testing options + require File.expand_path '../fog_mock', __FILE__ + + Vagrant.configure("2") do |config| + # dev/test method of loading plugin, normally would be 'vagrant plugin install vagrant-rackspace' + Vagrant.require_plugin "vagrant-rackspace" + + config.vm.box = "dummy" + config.ssh.username = "vagrant" if Fog.mock? + config.ssh.private_key_path = "~/.ssh/id_rsa" unless Fog.mock? + config.ssh.max_tries = 1 + config.ssh.timeout = 10 + + config.vm.provider :rackspace do |rs| + rs.server_name = 'vagrant-single-server' + rs.username = ENV['RAX_USERNAME'] + rs.api_key = ENV['RAX_API_KEY'] + rs.compute_url = "https://#{ENV['RAX_REGION'].downcase}.servers.api.rackspacecloud.com/v2" + rs.flavor = /512MB/ + rs.image = /Ubuntu/ + rs.public_key_path = "~/.ssh/id_rsa.pub" unless Fog.mock? + end + end + """ + When I successfully run `bundle exec vagrant up --provider rackspace` + # I want to capture the ID like I do in tests for other tools, but Vagrant doesn't print it! + # And I get the server from "Instance ID:" + Then the server "vagrant-single-server" should be active diff --git a/lib/vagrant-rackspace/action.rb b/lib/vagrant-rackspace/action.rb index 2e2e2f0..ed58890 100644 --- a/lib/vagrant-rackspace/action.rb +++ b/lib/vagrant-rackspace/action.rb @@ -24,6 +24,22 @@ def self.action_destroy end end + # This action is called when `vagrant provision` is called. + def self.action_provision + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + + b2.use Provision + b2.use SyncFolders + end + end + end + # This action is called to read the SSH info of the machine. The # resulting state is expected to be put into the `:machine_ssh_info` # key. diff --git a/lib/vagrant-rackspace/action/connect_rackspace.rb b/lib/vagrant-rackspace/action/connect_rackspace.rb index be66734..db74262 100644 --- a/lib/vagrant-rackspace/action/connect_rackspace.rb +++ b/lib/vagrant-rackspace/action/connect_rackspace.rb @@ -17,17 +17,27 @@ def call(env) # Get the configs config = env[:machine].provider_config api_key = config.api_key - endpoint = config.endpoint username = config.username - @logger.info("Connecting to Rackspace...") - env[:rackspace_compute] = Fog::Compute.new({ - :provider => :rackspace, - :version => :v2, - :rackspace_api_key => api_key, - :rackspace_endpoint => endpoint, - :rackspace_username => username - }) + if config.rackspace_compute_url.nil? then + @logger.info("Connecting to Rackspace region...") + env[:rackspace_compute] = Fog::Compute.new({ + :provider => :rackspace, + :version => :v2, + :rackspace_api_key => api_key, + :rackspace_region => config.rackspace_region, + :rackspace_username => username + }) + else + @logger.info("Connecting to Rackspace compute_url...") + env[:rackspace_compute] = Fog::Compute.new({ + :provider => :rackspace, + :version => :v2, + :rackspace_api_key => api_key, + :rackspace_compute_url => config.rackspace_compute_url, + :rackspace_username => username + }) + end @app.call(env) end diff --git a/lib/vagrant-rackspace/action/create_server.rb b/lib/vagrant-rackspace/action/create_server.rb index cc16219..945ff36 100644 --- a/lib/vagrant-rackspace/action/create_server.rb +++ b/lib/vagrant-rackspace/action/create_server.rb @@ -90,6 +90,20 @@ def call(env) # Clear the line one more time so the progress is removed env[:ui].clear_line + # Wait for RackConnect to complete + if ( config.rackconnect ) + env[:ui].info(I18n.t("vagrant_rackspace.waiting_for_rackconnect")) + while true + status = server.metadata.all["rackconnect_automation_status"] + if ( !status.nil? ) + env[:ui].info( status ) + end + break if env[:interrupted] + break if (status.to_s =~ /deployed/i) + sleep 10 + end + end + # Wait for SSH to become available env[:ui].info(I18n.t("vagrant_rackspace.waiting_for_ssh")) while true diff --git a/lib/vagrant-rackspace/action/sync_folders.rb b/lib/vagrant-rackspace/action/sync_folders.rb index 5caa78b..0f97c4a 100644 --- a/lib/vagrant-rackspace/action/sync_folders.rb +++ b/lib/vagrant-rackspace/action/sync_folders.rb @@ -33,15 +33,29 @@ def call(env) # Create the guest path env[:machine].communicate.sudo("mkdir -p '#{guestpath}'") env[:machine].communicate.sudo( - "chown #{ssh_info[:username]} '#{guestpath}'") + "chown -R #{ssh_info[:username]} '#{guestpath}'") - # Rsync over to the guest path using the SSH info + # Rsync over to the guest path using the SSH info. add + # .hg/ to exclude list as that isn't covered in + # --cvs-exclude command = [ "rsync", "--verbose", "--archive", "-z", + "--cvs-exclude", + "--exclude", ".hg/", "-e", "ssh -p #{ssh_info[:port]} -i '#{ssh_info[:private_key_path]}' -o StrictHostKeyChecking=no", hostpath, "#{ssh_info[:username]}@#{ssh_info[:host]}:#{guestpath}"] + # during rsync, ignore files specified in .hgignore and + # .gitignore traditional .gitignore or .hgignore files + ignore_files = [".hgignore", ".gitignore"] + ignore_files.each do |ignore_file| + abs_ignore_file = env[:root_path].to_s + "/" + ignore_file + if File.exist?(abs_ignore_file) + command = command + ["--exclude-from", abs_ignore_file] + end + end + r = Vagrant::Util::Subprocess.execute(*command) if r.exit_code != 0 raise Errors::RsyncError, diff --git a/lib/vagrant-rackspace/config.rb b/lib/vagrant-rackspace/config.rb index d0087b8..1c3ae1c 100644 --- a/lib/vagrant-rackspace/config.rb +++ b/lib/vagrant-rackspace/config.rb @@ -8,11 +8,31 @@ class Config < Vagrant.plugin("2", :config) # @return [String] attr_accessor :api_key - # The endpoint to access RackSpace. If nil, it will default + # The region to access RackSpace. If nil, it will default # to DFW. + # (formerly know as 'endpoint') # - # @return [String] - attr_accessor :endpoint + # expected to be a symbol - :dfw (default), :ord, :lon + # + # use this OR rackspace_compute_url + attr_accessor :rackspace_region + + # The compute_url to access RackSpace. If nil, it will default + # to DFW. + # (formerly know as 'endpoint') + # + # expected to be a string url - + # 'https://dfw.servers.api.rackspacecloud.com/v2' + # 'https://ord.servers.api.rackspacecloud.com/v2' + # 'https://lon.servers.api.rackspacecloud.com/v2' + # + # alternatively, can use constants if you require 'fog/rackspace' in your Vagrantfile + # Fog::Compute::RackspaceV2::DFW_ENDPOINT + # Fog::Compute::RackspaceV2::ORD_ENDPOINT + # Fog::Compute::RackspaceV2::LON_ENDPOINT + # + # use this OR rackspace_region + attr_accessor :rackspace_compute_url # The flavor of server to launch, either the ID or name. This # can also be a regular expression to partially match a name. @@ -28,6 +48,11 @@ class Config < Vagrant.plugin("2", :config) # @return [String] attr_accessor :public_key_path + # The option that indicates RackConnect usage or not. + # + # @return [Boolean] + attr_accessor :rackconnect + # The name of the server. This defaults to the name of the machine # defined by Vagrant (via `config.vm.define`), but can be overriden # here. @@ -40,19 +65,23 @@ class Config < Vagrant.plugin("2", :config) def initialize @api_key = UNSET_VALUE - @endpoint = UNSET_VALUE + @rackspace_region = UNSET_VALUE + @rackspace_compute_url = UNSET_VALUE @flavor = UNSET_VALUE @image = UNSET_VALUE @public_key_path = UNSET_VALUE + @rackconnect = UNSET_VALUE @server_name = UNSET_VALUE @username = UNSET_VALUE end def finalize! @api_key = nil if @api_key == UNSET_VALUE - @endpoint = nil if @endpoint == UNSET_VALUE + @rackspace_region = nil if @rackspace_region == UNSET_VALUE + @rackspace_compute_url = nil if @rackspace_compute_url == UNSET_VALUE @flavor = /512MB/ if @flavor == UNSET_VALUE @image = /Ubuntu/ if @image == UNSET_VALUE + @rackconnect = false if @rackconnect == UNSET_VALUE @server_name = nil if @server_name == UNSET_VALUE @username = nil if @username == UNSET_VALUE diff --git a/locales/en.yml b/locales/en.yml index 3775f51..33f62c0 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -18,6 +18,8 @@ en: Rsyncing folder: %{hostpath} => %{guestpath} waiting_for_build: |- Waiting for the server to be built... + waiting_for_rackconnect: |- + Waiting for RackConnect to complete... waiting_for_ssh: |- Waiting for SSH to become available... warn_insecure_ssh: |- diff --git a/spec/vagrant-rackspace/config_spec.rb b/spec/vagrant-rackspace/config_spec.rb index bcf642e..44e378f 100644 --- a/spec/vagrant-rackspace/config_spec.rb +++ b/spec/vagrant-rackspace/config_spec.rb @@ -11,20 +11,24 @@ end its(:api_key) { should be_nil } - its(:endpoint) { should be_nil } + its(:rackspace_region) { should be_nil } + its(:rackspace_compute_url) { should be_nil } its(:flavor) { should eq(/512MB/) } its(:image) { should eq(/Ubuntu/) } its(:public_key_path) { should eql(vagrant_public_key) } + its(:rackconnect) { should be_nil } its(:server_name) { should be_nil } its(:username) { should be_nil } end describe "overriding defaults" do [:api_key, - :endpoint, + :rackspace_region, + :rackspace_compute_url, :flavor, :image, :public_key_path, + :rackconnect, :server_name, :username].each do |attribute| it "should not default #{attribute} if overridden" do diff --git a/vagrant-rackspace.gemspec b/vagrant-rackspace.gemspec index 5287a59..bead67f 100644 --- a/vagrant-rackspace.gemspec +++ b/vagrant-rackspace.gemspec @@ -16,6 +16,7 @@ Gem::Specification.new do |gem| gem.add_development_dependency "rake" gem.add_development_dependency "rspec", "~> 2.13.0" + gem.add_development_dependency "aruba" gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }