diff --git a/.gitignore b/.gitignore
index a9532d7..5fa358d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,3 +19,4 @@ tmp
.rbenv-version
.ruby-version
.rbx/
+bin/
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 4441241..dd1f557 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,15 +1,14 @@
language: ruby
rvm:
-- 1.9.3
-- 2.0.0
-- 1.9.2
-- ruby-head
+ - 1.9.3
+ - 2.0.0
+ - 2.1.0
env:
-- "CHEF_VERSION=11.4.0"
-- "CHEF_VERSION=10.24.0"
-- "CHEF_VERSION=0.10.10"
+ - "CHEF_VERSION=11.4.0"
+ - "CHEF_VERSION=10.24.0"
+ - "CHEF_VERSION=0.10.10"
matrix:
allow_failures:
- - rvm: ruby-head
+ - rvm: ruby-head
diff --git a/README.md b/README.md
index e54dc4b..9e78904 100644
--- a/README.md
+++ b/README.md
@@ -50,6 +50,18 @@ knife server bootstrap linode \
--ssh-password 'testing1234'
```
+To spin up your Chef Server on [OpenStack][openstack]:
+
+```bash
+knife server bootstrap openstack \
+ --node-name openstack.example.com \
+ --openstack-node-name openstack \
+ --openstack-username $OS_USERNAME \
+ --openstack-password $OS_PASSWORD \
+ --openstack-auth-url $OS_AUTH_URL \
+ --ssh-password 'testing1234'
+``
+
Or maybe you want to try out a Chef Server using [Vagrant][vagrant_site]?
```bash
@@ -403,6 +415,15 @@ to make future cloud adapter support easier to add.
Provisions a Linode instance and sets up an Open Source Chef Server as
described [above](#knife-server-bootstrap).
+### knife server bootstrap openstack
+
+**Note:** You must install the [knife-openstack gem][knife-openstack] to use this
+subcommand. This was done to keep the dependencies of this library lighter and
+to make future cloud adapter support easier to add.
+
+Provisions a Openstack instance and sets up an Open Source Chef Server as
+described [above](#knife-server-bootstrap).
+
#### Configuration
This subcommand imports all relavent options from the knife-linode gem. For
@@ -622,5 +643,7 @@ Apache License, Version 2.0 (see [LICENSE][license])
[install_chef]: http://www.opscode.com/chef/install/
[knife-ec2]: https://github.com/opscode/knife-ec2
[knife-linode]: https://github.com/opscode/knife-linode
+[knife-openstack]: https://github.com/opscode/knife-openstack
[stevendanna]: https://github.com/stevendanna
[vagrant_site]: http://vagrantup.com/
+[openstack]: http://openstack.org
diff --git a/knife-server.gemspec b/knife-server.gemspec
index 544a558..a550e3f 100644
--- a/knife-server.gemspec
+++ b/knife-server.gemspec
@@ -23,6 +23,7 @@ Gem::Specification.new do |gem|
gem.add_development_dependency "knife-ec2", ">= 0.5.12"
gem.add_development_dependency "knife-linode"
+ gem.add_development_dependency "knife-openstack"
gem.add_development_dependency "rspec", "~> 2.13.0"
gem.add_development_dependency "fakefs", "~> 0.4.0"
diff --git a/lib/chef/knife/server_bootstrap_ec2.rb b/lib/chef/knife/server_bootstrap_ec2.rb
index 07582a0..48487f1 100644
--- a/lib/chef/knife/server_bootstrap_ec2.rb
+++ b/lib/chef/knife/server_bootstrap_ec2.rb
@@ -72,7 +72,8 @@ def ec2_bootstrap
ENV['NO_TEST'] = "1" if config[:no_test]
bootstrap = Chef::Knife::Ec2ServerCreate.new
Chef::Knife::Ec2ServerCreate.options.keys.each do |attr|
- bootstrap.config[attr] = config_val(attr)
+ value = config_val(attr)
+ bootstrap.config[attr] = value unless ((value.is_a?(Array) and value[1].nil?) or value.nil?)
end
bootstrap.config[:tags] = bootstrap_tags
bootstrap.config[:distro] = bootstrap_distro
diff --git a/lib/chef/knife/server_bootstrap_linode.rb b/lib/chef/knife/server_bootstrap_linode.rb
index ef61a36..b6972da 100644
--- a/lib/chef/knife/server_bootstrap_linode.rb
+++ b/lib/chef/knife/server_bootstrap_linode.rb
@@ -63,7 +63,8 @@ def linode_bootstrap
ENV['NO_TEST'] = "1" if config[:no_test]
bootstrap = Chef::Knife::LinodeServerCreate.new
Chef::Knife::LinodeServerCreate.options.keys.each do |attr|
- bootstrap.config[attr] = config_val(attr)
+ value = config_val(attr)
+ bootstrap.config[attr] = value unless ((value.is_a?(Array) and value[1].nil?) or value.nil?)
end
bootstrap.config[:distro] = bootstrap_distro
bootstrap
diff --git a/lib/chef/knife/server_bootstrap_openstack.rb b/lib/chef/knife/server_bootstrap_openstack.rb
new file mode 100644
index 0000000..7bc3580
--- /dev/null
+++ b/lib/chef/knife/server_bootstrap_openstack.rb
@@ -0,0 +1,122 @@
+#
+# Author:: John Bellone ()
+# Copyright:: Copyright (c) 2014 Bloomberg Finance L.P.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife/server_bootstrap_base'
+
+class Chef
+ class Knife
+ class ServerBootstrapOpenstack < Knife
+
+ banner "knife server bootstrap openstack (options)"
+
+ include Knife::ServerBootstrapBase
+
+ deps do
+ require 'knife/server/ssh'
+ require 'knife/server/credentials'
+
+ begin
+ require 'chef/knife/openstack_server_create'
+ require 'fog'
+ Chef::Knife::OpenstackServerCreate.load_deps
+
+ current_options = self.options
+ self.options = Chef::Knife::OpenstackServerCreate.options.dup
+ self.options.merge!(current_options)
+ rescue LoadError => ex
+ ui.error [
+ "Knife plugin knife-openstack could not be loaded.",
+ "Please add the knife-openstack gem to your Gemfile or",
+ "install the gem manually with `gem install knife-openstack'.",
+ "(#{ex.message})"
+ ].join(" ")
+ exit 1
+ end
+ end
+
+ def run
+ validate!
+ openstack_bootstrap.run
+ fetch_validation_key
+ create_root_client
+ install_client_key
+ end
+
+ def openstack_bootstrap
+ ENV['WEBUI_PASSWORD'] = config_val(:webui_password)
+ ENV['AMQP_PASSWORD'] = config_val(:amqp_password)
+ ENV['NO_TEST'] = "1" if config[:no_test]
+ bootstrap = Chef::Knife::OpenstackServerCreate.new
+ Chef::Knife::OpenstackServerCreate.options.keys.each do |attr|
+ value = config_val(attr)
+ bootstrap.config[attr] = value unless ((value.is_a?(Array) and value[1].nil?) or value.nil?)
+ end
+ bootstrap.config[:distro] = bootstrap_distro
+ bootstrap
+ end
+
+ def openstack_connection
+ @openstack_connection ||= Fog::Compute.new(
+ :provider => :openstack,
+ :openstack_username => config_val(:openstack_username),
+ :openstack_password => config_val(:openstack_password),
+ :openstack_auth_url => config_val(:openstack_auth_url),
+ :openstack_tenant => config_val(:openstack_tenant),
+ :openstack_region => config_val(:openstack_region)
+ )
+ end
+
+ def server_ip_address
+ server = openstack_connection.servers.find do |s|
+ s.status == 1 && s.name == config_val(:openstack_node_name)
+ end
+
+ server && server.public_ip_address
+ end
+
+ private
+
+ def validate!
+ if config[:chef_node_name].nil?
+ ui.error "You did not provide a valid --node-name value."
+ exit 1
+ end
+ if config_val(:platform) == "auto"
+ ui.error "Auto platform mode cannot be used with knife-openstack plugin"
+ exit 1
+ end
+ end
+
+ def ssh_connection
+ opts = {
+ :host => server_ip_address,
+ :user => config_val(:ssh_user),
+ :port => config_val(:ssh_port),
+ :keys => [config_val(:identity_file)].compact,
+ :password => config_val(:ssh_password)
+ }
+ if config_val(:host_key_verify) == false
+ opts[:user_known_hosts_file] = "/dev/null"
+ opts[:paranoid] = false
+ end
+
+ ::Knife::Server::SSH.new(opts)
+ end
+ end
+ end
+end
diff --git a/lib/chef/knife/server_bootstrap_standalone.rb b/lib/chef/knife/server_bootstrap_standalone.rb
index 6c7194e..8862006 100644
--- a/lib/chef/knife/server_bootstrap_standalone.rb
+++ b/lib/chef/knife/server_bootstrap_standalone.rb
@@ -58,7 +58,8 @@ def standalone_bootstrap
bootstrap = Chef::Knife::Bootstrap.new
bootstrap.name_args = [ config[:host] ]
Chef::Knife::Bootstrap.options.keys.each do |attr|
- bootstrap.config[attr] = config_val(attr)
+ value = config_val(attr)
+ bootstrap.config[attr] = value unless ((value.is_a?(Array) and value[1].nil?) or value.nil?)
end
bootstrap.ui = self.ui
bootstrap.config[:distro] = bootstrap_distro
diff --git a/spec/chef/knife/server_bootstrap_openstack_spec.rb b/spec/chef/knife/server_bootstrap_openstack_spec.rb
new file mode 100644
index 0000000..99f180d
--- /dev/null
+++ b/spec/chef/knife/server_bootstrap_openstack_spec.rb
@@ -0,0 +1,280 @@
+#
+# Author:: John Bellone ()
+# Copyright:: Copyright (c) 2014 Bloomberg Finance L.P.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'chef/knife/server_bootstrap_openstack'
+require 'chef/knife/ssh'
+require 'fakefs/spec_helpers'
+require 'net/ssh'
+Chef::Knife::ServerBootstrapOpenstack.load_deps
+
+describe Chef::Knife::ServerBootstrapOpenstack do
+ include FakeFS::SpecHelpers
+
+ before do
+ Chef::Log.logger = Logger.new(StringIO.new)
+ @knife = Chef::Knife::ServerBootstrapOpenstack.new
+ @stdout = StringIO.new
+ @knife.ui.stub!(:stdout).and_return(@stdout)
+ @stderr = StringIO.new
+ @knife.ui.stub!(:stderr).and_return(@stderr)
+ @knife.config[:chef_node_name] = "yakky"
+ @knife.config[:platform] = "omnibus"
+ @knife.config[:ssh_user] = "root"
+ end
+
+ let(:connection) { mock(Fog::Compute::AWS) }
+
+ describe "#openstack_bootstrap" do
+
+ before do
+ @knife.config[:chef_node_name] = "shave.yak"
+ @knife.config[:ssh_user] = "jdoe"
+ @knife.config[:identity_file] = "~/.ssh/mykey_dsa"
+ @knife.config[:openstack_password] = "openstack123"
+ @knife.config[:distro] = "distro-praha"
+ @knife.config[:webui_password] = "daweb"
+ @knife.config[:amqp_password] = "queueitup"
+
+ ENV['_SPEC_WEBUI_PASSWORD'] = ENV['WEBUI_PASSWORD']
+ ENV['_SPEC_AMQP_PASSWORD'] = ENV['AMQP_PASSWORD']
+ end
+
+ after do
+ ENV['WEBUI_PASSWORD'] = ENV.delete('_SPEC_WEBUI_PASSWORD')
+ ENV['AMQP_PASSWORD'] = ENV.delete('_SPEC_AMQP_PASSWORD')
+ end
+
+ let(:bootstrap) { @knife.openstack_bootstrap }
+
+ it "returns a OpenstackServerCreate instance" do
+ bootstrap.should be_a(Chef::Knife::OpenstackServerCreate)
+ end
+
+ it "configs the bootstrap's chef_node_name" do
+ bootstrap.config[:chef_node_name].should eq("shave.yak")
+ end
+
+ it "configs the bootstrap's ssh_user" do
+ bootstrap.config[:ssh_user].should eq("jdoe")
+ end
+
+ it "configs the bootstrap's identity_file" do
+ bootstrap.config[:identity_file].should eq("~/.ssh/mykey_dsa")
+ end
+
+ it "configs the bootstrap's openstack_password" do
+ bootstrap.config[:openstack_password].should eq("openstack123")
+ end
+
+ it "configs the bootstrap's distro" do
+ bootstrap.config[:distro].should eq("distro-praha")
+ end
+
+ it "configs the bootstrap's distro to chef11/omnibus by default" do
+ @knife.config.delete(:distro)
+
+ bootstrap.config[:distro].should eq("chef11/omnibus")
+ end
+
+ it "configs the bootstrap's distro value driven off platform value" do
+ @knife.config.delete(:distro)
+ @knife.config[:platform] = "freebsd"
+
+ bootstrap.config[:distro].should eq("chef11/freebsd")
+ end
+
+ it "configs the bootstrap's distro based on bootstrap_version and platform" do
+ @knife.config.delete(:distro)
+ @knife.config[:platform] = "freebsd"
+ @knife.config[:bootstrap_version] = "10"
+
+ bootstrap.config[:distro].should eq("chef10/freebsd")
+ end
+
+ it "configs the bootstrap's ENV with the webui password" do
+ bootstrap
+ ENV['WEBUI_PASSWORD'].should eq("daweb")
+ end
+
+ it "configs the bootstrap's ENV with the amqp password" do
+ bootstrap
+ ENV['AMQP_PASSWORD'].should eq("queueitup")
+ end
+ end
+
+ describe "#openstack_connection" do
+
+ before do
+ @before_config = Hash.new
+ @before_config[:knife] = Chef::Config[:knife]
+
+ Chef::Config[:knife] = {
+ :openstack_username => 'jbellone',
+ :openstack_password => 'key',
+ :openstack_auth_url => 'http://0.0.0.0',
+ :openstack_tenant => 'slumlord',
+ :openstack_region => 'newyork'
+ }
+ end
+
+ after do
+ Chef::Config[:knife] = @before_config[:knife]
+ end
+
+ it "constructs a connection" do
+ Fog::Compute.should_receive(:new).with({
+ :provider => :openstack,
+ :openstack_username => 'jbellone',
+ :openstack_password => 'key',
+ :openstack_auth_url => 'http://0.0.0.0',
+ :openstack_tenant => 'slumlord',
+ :openstack_region => 'newyork'
+ })
+
+ @knife.openstack_connection
+ end
+ end
+
+ describe "#server_ip_address" do
+
+ before do
+ @knife.config[:openstack_node_name] = 'yak'
+ @knife.stub(:openstack_connection) { connection }
+ end
+
+ context "when server is found" do
+
+ before do
+ connection.stub(:servers) { [server] }
+ end
+
+ let(:server) do
+ stub(:name => 'yak', :status => 1, :public_ip_address => '10.11.12.13')
+ end
+
+ it "returns the provisioned ip address" do
+ @knife.server_ip_address.should eq('10.11.12.13')
+ end
+
+ it "ignores terminated instances" do
+ server.stub(:status) { 0 }
+
+ @knife.server_ip_address.should be_nil
+ end
+ end
+
+ context "when server is not found" do
+ before do
+ connection.stub(:servers) { [] }
+ end
+
+ it "returns nil" do
+ @knife.server_ip_address.should be_nil
+ end
+ end
+ end
+
+ describe "#run" do
+
+ before do
+ @before_config = Hash.new
+ [:node_name, :client_key].each do |attr|
+ @before_config[attr] = Chef::Config[attr]
+ end
+ Chef::Config[:node_name] = "smithers"
+ Chef::Config[:client_key] = "/var/tmp/myclientkey.pem"
+
+ @knife.config[:validation_key] = "/var/tmp/validation.pem"
+ @knife.config[:identity_file] = "~/.ssh/mykey_dsa"
+ @knife.config[:ssh_password] = "booboo"
+ @knife.stub(:openstack_connection) { connection }
+ @knife.stub(:server_ip_address) { "11.11.11.13" }
+ Chef::Knife::OpenstackServerCreate.stub(:new) { bootstrap }
+ Knife::Server::SSH.stub(:new) { ssh }
+ Knife::Server::Credentials.stub(:new) { credentials }
+ credentials.stub(:install_validation_key)
+ credentials.stub(:create_root_client)
+ end
+
+ after do
+ [:node_name, :client_key].each do |attr|
+ Chef::Config[attr] = @before_config[attr]
+ end
+ end
+
+ let(:bootstrap) { stub(:run => true, :config => Hash.new) }
+ let(:ssh) { stub }
+ let(:credentials) { stub.as_null_object }
+
+ it "exits if node_name option is missing" do
+ @knife.config.delete(:chef_node_name)
+
+ lambda { @knife.run }.should raise_error(SystemExit)
+ end
+
+ it "exits if platform is set to auto" do
+ @knife.config[:platform] = "auto"
+
+ lambda { @knife.run }.should raise_error(SystemExit)
+ end
+
+ it "bootstraps a openstack server" do
+ bootstrap.should_receive(:run)
+
+ @knife.run
+ end
+
+ it "installs a new validation.pem key from the chef 10 server" do
+ @knife.config[:bootstrap_version] = "10"
+ Knife::Server::SSH.should_receive(:new).with({
+ :host => "11.11.11.13", :user => "root",
+ :port => "22", :keys => ["~/.ssh/mykey_dsa"], :password => "booboo"
+ })
+ Knife::Server::Credentials.should_receive(:new).
+ with(ssh, "/etc/chef/validation.pem", {})
+ credentials.should_receive(:install_validation_key)
+
+ @knife.run
+ end
+
+ it "installs a new validation.pem key from the omnibus server" do
+ Knife::Server::SSH.should_receive(:new).with({
+ :host => "11.11.11.13", :user => "root",
+ :port => "22", :keys => ["~/.ssh/mykey_dsa"], :password => "booboo"
+ })
+ Knife::Server::Credentials.should_receive(:new).
+ with(ssh, "/etc/chef/validation.pem", {:omnibus => true})
+ credentials.should_receive(:install_validation_key)
+
+ @knife.run
+ end
+
+ it "create a root client key" do
+ credentials.should_receive(:create_root_client)
+
+ @knife.run
+ end
+
+ it "installs a client key" do
+ credentials.should_receive(:install_client_key).
+ with("smithers", "/var/tmp/myclientkey.pem")
+
+ @knife.run
+ end
+ end
+end