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