diff --git a/lib/chef/knife/cloud/list_resource_command.rb b/lib/chef/knife/cloud/list_resource_command.rb index b25197c..aae2447 100644 --- a/lib/chef/knife/cloud/list_resource_command.rb +++ b/lib/chef/knife/cloud/list_resource_command.rb @@ -52,6 +52,20 @@ def is_resource_filtered?(attribute, value) end false end + + # Derived class can override this to add more functionality. + def get_resource_col_val(resource) + resource_filtered = false + list = [] + @columns_with_info.each do |col_info| + value = (col_info[:value_callback].nil? ? resource.send(col_info[:key]).to_s : col_info[:value_callback].call(resource.send(col_info[:key]))) + if !config[:disable_filter] + resource_filtered = true if is_resource_filtered?(col_info[:key], value) + end + list << value + end + return list unless resource_filtered + end # When @columns_with_info is nil display all def list(resources) @@ -59,17 +73,9 @@ def list(resources) begin resource_list = @columns_with_info.map { |col_info| ui.color(col_info[:label], :bold) } if @columns_with_info.length > 0 resources.sort_by(&@sort_by_field.to_sym).each do |resource| - resource_filtered = false if @columns_with_info.length > 0 - list = [] - @columns_with_info.each do |col_info| - value = (col_info[:value_callback].nil? ? resource.send(col_info[:key]).to_s : col_info[:value_callback].call(resource.send(col_info[:key]))) - if !config[:disable_filter] - resource_filtered = true if is_resource_filtered?(col_info[:key], value) - end - list << value - end - resource_list.concat(list) unless resource_filtered + list = get_resource_col_val(resource) + resource_list.concat(list) unless list.nil? else puts resource.to_json puts "\n" diff --git a/lib/chef/knife/cloud/server/list_command.rb b/lib/chef/knife/cloud/server/list_command.rb index 28bbd22..f1e96ce 100644 --- a/lib/chef/knife/cloud/server/list_command.rb +++ b/lib/chef/knife/cloud/server/list_command.rb @@ -1,10 +1,63 @@ require 'chef/knife/cloud/list_resource_command' +require 'chef/knife/cloud/exceptions' class Chef class Knife class Cloud class ServerListCommand < ResourceListCommand + + def before_exec_command + if config[:chef_data] + begin + # Chef::Node.list(inflate = true) to use Solr search. + @node_list = Chef::Node.list(true) + rescue Errno::ECONNREFUSED => e + raise e + end + + @chef_data_col_info = [ + {:label => 'Chef Node Name', :key => 'name'}, + {:label => 'Environment', :key => 'chef_environment'}, + {:label => 'FQDN', :key => 'fqdn'}, + {:label => 'Runlist', :key => 'run_list'}, + {:label => 'Tags', :key => 'tags'}, + {:label => 'Platform', :key => 'platform'}, + ] + + if config[:chef_node_attribute] + @chef_data_col_info << {:label => "#{config[:chef_node_attribute]}", :key => "#{config[:chef_node_attribute]}"} + end + @columns_with_info.concat(@chef_data_col_info) + end + end + + # Override from base to display chef node data along with server list display. + def get_resource_col_val(server) + list = [] + @columns_with_info.each do |col_info| + if config[:chef_data] && @chef_data_col_info.include?(col_info) + if @node_list.include?(server.name) + node = @node_list[server.name] + # Raise serverlisting error on invalid chef_node_attribute. + if col_info[:key] == config[:chef_node_attribute] && ! node.attribute?(col_info[:key]) + error_message = "The Node does not have a #{col_info[:key]} attribute." + ui.error(error_message) + raise CloudExceptions::ServerListingError, error_message + else + value = (col_info[:value_callback].nil? ? node.send(col_info[:key]).to_s : col_info[:value_callback].call(node.send(col_info[:key]))) + end + else + # Set chef data value for those server which is not part chef server. + value = "" + end + else + value = (col_info[:value_callback].nil? ? server.send(col_info[:key]).to_s : col_info[:value_callback].call(server.send(col_info[:key]))) + end + list << value + end + list + end def query_resource @service.list_servers diff --git a/lib/chef/knife/cloud/server/list_options.rb b/lib/chef/knife/cloud/server/list_options.rb new file mode 100644 index 0000000..476a6ae --- /dev/null +++ b/lib/chef/knife/cloud/server/list_options.rb @@ -0,0 +1,43 @@ +# +# Author:: Siddheshwar More () +# Copyright:: Copyright (c) 2013 Opscode, Inc. +# 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. +# + +class Chef + class Knife + class Cloud + module ServerListOptions + def self.included(includer) + includer.class_eval do + + option :chef_data, + :long => "--chef-data", + :boolean => true, + :default => false, + :description => "Display chef node data which include chef node name, environment name, fqdn, platform, runlist and tags." + + option :chef_node_attribute, + :long => "--chef-node-attribute CHEF_NODE_ATTRIBUTE_NAME", + :description => "Used with --chef-data option. It display node attributes details by adding new column in server list display.", + :proc => Proc.new { |i| Chef::Config[:knife][:chef_node_attribute] = i } + + end + end + end + end + end +end + diff --git a/spec/unit/server_list_command_spec.rb b/spec/unit/server_list_command_spec.rb new file mode 100644 index 0000000..69abeb9 --- /dev/null +++ b/spec/unit/server_list_command_spec.rb @@ -0,0 +1,75 @@ +# Author:: Siddheshwar More () +# Copyright:: Copyright (c) 2013 Opscode, Inc. + +require 'spec_helper' +require 'support/shared_examples_for_command' +require 'chef/knife/cloud/server/list_command' +require 'chef/node' + +describe Chef::Knife::Cloud::ServerListCommand do + it_behaves_like Chef::Knife::Cloud::Command, Chef::Knife::Cloud::ServerListCommand.new + + describe "#before_exec_command" do + it "set chef data columns info on chef data options" do + instance = Chef::Knife::Cloud::ServerListCommand.new + instance.config[:chef_data] = true + Chef::Node.should_receive(:list).with(true) + instance.before_exec_command.should include({:label => "Chef Node Name", :key => "name"}) + end + + it "set chef data columns info on chef-data and chef-node-attribute options" do + chef_node_attribute = "platform_family" + instance = Chef::Knife::Cloud::ServerListCommand.new + instance.config[:chef_data] = true + instance.config[:chef_node_attribute] = chef_node_attribute + Chef::Node.should_receive(:list).with(true) + instance.before_exec_command.should include({:label => chef_node_attribute, :key => chef_node_attribute}) + end + + it "not set chef data columns info if chef-data option is not set" do + instance = Chef::Knife::Cloud::ServerListCommand.new + Chef::Node.should_not_receive(:list).with(true) + instance.before_exec_command.should be(nil) + end + + it "not set chef data columns info on chef-node-attribute option set but chef-data option is not set" do + instance = Chef::Knife::Cloud::ServerListCommand.new + instance.config[:chef_node_attribute] = "platform_family" + Chef::Node.should_not_receive(:list).with(true) + instance.before_exec_command.should be(nil) + end + end + + describe "#get_resource_col_val" do + let (:resources) {[ TestResource.new({:id => "server-1", :name => "server-1", :os => "ubuntu"})]} + before do + class DerivedServerList < Chef::Knife::Cloud::ServerListCommand + attr_accessor :node + def before_exec_command + @columns_with_info = [ { :key => 'id', :label => 'Instance ID' }, {:label => 'Environment', :key => 'chef_environment'}, {:label => 'platform_family', :key => 'platform_family'} ] + @chef_data_col_info = [ {:label => 'Environment', :key => 'chef_environment'}, {:label => 'platform_family', :key => 'platform_family'} ] + @node = TestResource.new({:id => "server-1", :name => "server-1", + :chef_environment => "_default", :platform_family => "debian"}) + @node.define_singleton_method(:attribute?) do |attribute| + end + @node_list = {"server-1" => @node} + end + end + @derived_instance = DerivedServerList.new + @derived_instance.config[:chef_data] = true + @derived_instance.config[:chef_node_attribute] = "platform_family" + @derived_instance.before_exec_command + end + + it "return columns_with_info values" do + @derived_instance.node.should_receive(:attribute?).with("platform_family").and_return(true) + @derived_instance.get_resource_col_val(resources.first).should eq(["server-1", "_default", "debian"]) + end + + it "raise error on invalide chef_node_attribute" do + @derived_instance.ui.stub(:error) + @derived_instance.node.should_receive(:attribute?).with("platform_family").and_return(false) + expect { @derived_instance.get_resource_col_val(resources.first) }.to raise_error(Chef::Knife::Cloud::CloudExceptions::ServerListingError, "The Node does not have a platform_family attribute.") + end + end +end