diff options
author | tylercloke <tylercloke@gmail.com> | 2015-04-28 18:25:54 -0700 |
---|---|---|
committer | tylercloke <tylercloke@gmail.com> | 2015-04-29 10:14:17 -0700 |
commit | 9850147d638bae25d4fc2e1166e486d807ecdd9f (patch) | |
tree | 3143b320046ba8eca2de5099c82a2800f21b1df6 | |
parent | 8385125e5bf3bd4139f75c7f4d5d2cb030ebaa98 (diff) | |
download | chef-9850147d638bae25d4fc2e1166e486d807ecdd9f.tar.gz |
Implemented `knife user key list` and `knife client key list`.tc/key-list
Abstracted out a bunch of common tests with the key create code to reduce redundancy.
-rw-r--r-- | lib/chef/knife/client_key_list.rb | 69 | ||||
-rw-r--r-- | lib/chef/knife/key_list.rb | 88 | ||||
-rw-r--r-- | lib/chef/knife/key_list_base.rb | 45 | ||||
-rw-r--r-- | lib/chef/knife/user_key_list.rb | 69 | ||||
-rw-r--r-- | spec/support/key_helpers.rb | 74 | ||||
-rw-r--r-- | spec/unit/knife/key_create_spec.rb | 50 | ||||
-rw-r--r-- | spec/unit/knife/key_helper.rb | 74 | ||||
-rw-r--r-- | spec/unit/knife/key_list_spec.rb | 214 |
8 files changed, 644 insertions, 39 deletions
diff --git a/lib/chef/knife/client_key_list.rb b/lib/chef/knife/client_key_list.rb new file mode 100644 index 0000000000..f6f29ae03f --- /dev/null +++ b/lib/chef/knife/client_key_list.rb @@ -0,0 +1,69 @@ +# +# Author:: Tyler Cloke (tyler@chef.io) +# Copyright:: Copyright (c) 2015 Chef Software, 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. +# + +require 'chef/knife' +require 'chef/knife/key_list_base' + +class Chef + class Knife + # Implements knife user key list using Chef::Knife::KeyList + # as a service class. + # + # @author Tyler Cloke + # + # @attr_reader [String] actor the name of the client that this key is for + class ClientKeyList < Knife + include Chef::Knife::KeyListBase + + banner "knife client key list CLIENT (options)" + + attr_reader :actor + + def initialize(argv=[]) + super(argv) + @service_object = nil + end + + def run + apply_params!(@name_args) + service_object.run + end + + def list_method + :list_by_client + end + + def actor_missing_error + 'You must specify a client name' + end + + def service_object + @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config) + end + + def apply_params!(params) + @actor = params[0] + if @actor.nil? + show_usage + ui.fatal(actor_missing_error) + exit 1 + end + end + end + end +end diff --git a/lib/chef/knife/key_list.rb b/lib/chef/knife/key_list.rb new file mode 100644 index 0000000000..e96a27161f --- /dev/null +++ b/lib/chef/knife/key_list.rb @@ -0,0 +1,88 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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. +# + +require 'chef/key' +require 'chef/json_compat' +require 'chef/exceptions' + +class Chef + class Knife + # Service class for UserKeyList and ClientKeyList, used to list keys. + # Implements common functionality of knife [user | org client] key list. + # + # @author Tyler Cloke + # + # @attr_accessor [Hash] cli input, see UserKeyList and ClientKeyList for what could populate it + class KeyList + + attr_accessor :config + + def initialize(actor, list_method, ui, config) + @actor = actor + @list_method = list_method + @ui = ui + @config = config + end + + def expired_and_non_expired_msg +<<EOS +You cannot pass both --only-expired and --only-non-expired. +Please pass one or none. +EOS + end + + def display_info(string) + @ui.output(string) + end + + def colorize(string) + @ui.color(string, :cyan) + end + + def run + if @config[:only_expired] && @config[:only_non_expired] + raise Chef::Exceptions::KeyCommandInputError, expired_and_non_expired_msg + end + + # call proper list function + keys = Chef::Key.send(@list_method, @actor) + if @config[:with_details] + max_length = 0 + keys.each do |key| + key['name'] = key['name'] + ":" + max_length = key['name'].length if key['name'].length > max_length + end + keys.each do |key| + next if !key['expired'] && @config[:only_expired] + next if key['expired'] && @config[:only_non_expired] + display = "#{colorize(key['name'].ljust(max_length))} #{key['uri']}" + display = "#{display} (expired)" if key["expired"] + display_info(display) + end + else + keys.each do |key| + next if !key['expired'] && @config[:only_expired] + next if key['expired'] && @config[:only_non_expired] + display_info(key['name']) + end + end + end + + end + end +end diff --git a/lib/chef/knife/key_list_base.rb b/lib/chef/knife/key_list_base.rb new file mode 100644 index 0000000000..861db0d75a --- /dev/null +++ b/lib/chef/knife/key_list_base.rb @@ -0,0 +1,45 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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 + # Extendable module that class_eval's common options into UserKeyList and ClientKeyList + # + # @author Tyler Cloke + module KeyListBase + def self.included(includer) + includer.class_eval do + option :with_details, + :short => "-w", + :long => "--with-details", + :description => "Show corresponding URIs and whether the key has expired or not." + + option :only_expired, + :short => "-e", + :long => "--only-expired", + :description => "Only show expired keys." + + option :only_non_expired, + :short => "-n", + :long => "--only-non-expired", + :description => "Only show non-expired keys." + end + end + end + end +end diff --git a/lib/chef/knife/user_key_list.rb b/lib/chef/knife/user_key_list.rb new file mode 100644 index 0000000000..a73f59c86f --- /dev/null +++ b/lib/chef/knife/user_key_list.rb @@ -0,0 +1,69 @@ +# +# Author:: Tyler Cloke (tyler@chef.io) +# Copyright:: Copyright (c) 2015 Chef Software, 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. +# + +require 'chef/knife' +require 'chef/knife/key_list_base' + +class Chef + class Knife + # Implements knife user key list using Chef::Knife::KeyList + # as a service class. + # + # @author Tyler Cloke + # + # @attr_reader [String] actor the name of the client that this key is for + class UserKeyList < Knife + include Chef::Knife::KeyListBase + + banner "knife user key list USER (options)" + + attr_reader :actor + + def initialize(argv=[]) + super(argv) + @service_object = nil + end + + def run + apply_params!(@name_args) + service_object.run + end + + def list_method + :list_by_user + end + + def actor_missing_error + 'You must specify a user name' + end + + def service_object + @service_object ||= Chef::Knife::KeyList.new(@actor, list_method, ui, config) + end + + def apply_params!(params) + @actor = params[0] + if @actor.nil? + show_usage + ui.fatal(actor_missing_error) + exit 1 + end + end + end + end +end diff --git a/spec/support/key_helpers.rb b/spec/support/key_helpers.rb new file mode 100644 index 0000000000..36ababc09a --- /dev/null +++ b/spec/support/key_helpers.rb @@ -0,0 +1,74 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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. +# + +require 'spec_helper' + +shared_examples_for "a knife key command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "before apply_params! is called" do + context "when apply_params! is called with invalid args" do + it "shows the usage" do + expect(command).to receive(:show_usage) + expect { command.apply_params!(params) }.to exit_with_code(1) + end + + it "outputs the proper error" do + expect { command.apply_params!(params) }.to exit_with_code(1) + expect(stderr.string).to include(command.actor_missing_error) + end + + it "exits 1" do + expect { command.apply_params!(params) }.to exit_with_code(1) + end + end + end # before apply_params! is called + + context "after apply_params! is called with valid args" do + let(:params) { ["charmander"] } + before do + command.apply_params!(params) + end + + it "properly defines the actor" do + expect(command.actor).to eq("charmander") + end + end # after apply_params! is called with valid args + + context "when the command is run" do + before do + allow(command).to receive(:service_object).and_return(service_object) + allow(command).to receive(:name_args).and_return(["charmander"]) + end + + context "when the command is successful" do + before do + expect(service_object).to receive(:run) + end + end + end +end # a knife key command diff --git a/spec/unit/knife/key_create_spec.rb b/spec/unit/knife/key_create_spec.rb index 1bdd0f3ba1..c24f57c9ac 100644 --- a/spec/unit/knife/key_create_spec.rb +++ b/spec/unit/knife/key_create_spec.rb @@ -23,12 +23,10 @@ require 'chef/knife/key_create' require 'chef/key' describe "key create commands that inherit knife" do - - let(:stderr) { StringIO.new } - let(:params) { [] } - let(:service_object) { instance_double(Chef::Knife::KeyCreate) } - shared_examples_for "a key create command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } let(:command) do c = described_class.new([]) c.ui.config[:disable_editing] = true @@ -38,34 +36,12 @@ describe "key create commands that inherit knife" do c end - context "before apply_params! is called" do - context "when apply_params! is called with invalid args" do - it "shows the usage" do - expect(command).to receive(:show_usage) - expect { command.apply_params!(params) }.to exit_with_code(1) - end - - it "outputs the proper error" do - expect { command.apply_params!(params) }.to exit_with_code(1) - expect(stderr.string).to include(command.actor_missing_error) - end - - it "exits 1" do - expect { command.apply_params!(params) }.to exit_with_code(1) - end - end - end # before apply_params! is called - context "after apply_params! is called with valid args" do let(:params) { ["charmander"] } before do command.apply_params!(params) end - it "properly defines the actor" do - expect(command.actor).to eq("charmander") - end - context "when the service object is called" do it "creates a new instance of Chef::Knife::KeyCreate with the correct args" do expect(Chef::Knife::KeyCreate).to receive(:new). @@ -75,26 +51,22 @@ describe "key create commands that inherit knife" do end end # when the service object is called end # after apply_params! is called with valid args - context "when the command is run" do - before do - allow(command).to receive(:service_object).and_return(service_object) - allow(command).to receive(:name_args).and_return(["charmander"]) - end - - context "when the command is successful" do - before do - expect(service_object).to receive(:run) - end - end - end end # a key create command describe Chef::Knife::UserKeyCreate do it_should_behave_like "a key create command" + # defined in key_helper.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } + end end describe Chef::Knife::ClientKeyCreate do it_should_behave_like "a key create command" + # defined in key_helper.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyCreate) } + end end end diff --git a/spec/unit/knife/key_helper.rb b/spec/unit/knife/key_helper.rb new file mode 100644 index 0000000000..36ababc09a --- /dev/null +++ b/spec/unit/knife/key_helper.rb @@ -0,0 +1,74 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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. +# + +require 'spec_helper' + +shared_examples_for "a knife key command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "before apply_params! is called" do + context "when apply_params! is called with invalid args" do + it "shows the usage" do + expect(command).to receive(:show_usage) + expect { command.apply_params!(params) }.to exit_with_code(1) + end + + it "outputs the proper error" do + expect { command.apply_params!(params) }.to exit_with_code(1) + expect(stderr.string).to include(command.actor_missing_error) + end + + it "exits 1" do + expect { command.apply_params!(params) }.to exit_with_code(1) + end + end + end # before apply_params! is called + + context "after apply_params! is called with valid args" do + let(:params) { ["charmander"] } + before do + command.apply_params!(params) + end + + it "properly defines the actor" do + expect(command.actor).to eq("charmander") + end + end # after apply_params! is called with valid args + + context "when the command is run" do + before do + allow(command).to receive(:service_object).and_return(service_object) + allow(command).to receive(:name_args).and_return(["charmander"]) + end + + context "when the command is successful" do + before do + expect(service_object).to receive(:run) + end + end + end +end # a knife key command diff --git a/spec/unit/knife/key_list_spec.rb b/spec/unit/knife/key_list_spec.rb new file mode 100644 index 0000000000..b65697a105 --- /dev/null +++ b/spec/unit/knife/key_list_spec.rb @@ -0,0 +1,214 @@ +# +# Author:: Tyler Cloke (<tyler@chef.io>) +# Copyright:: Copyright (c) 2015 Chef Software, 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. +# + +require 'spec_helper' +require 'chef/knife/user_key_list' +require 'chef/knife/client_key_list' +require 'chef/knife/key_list' +require 'chef/key' + +describe "key list commands that inherit knife" do + shared_examples_for "a key list command" do + let(:stderr) { StringIO.new } + let(:params) { [] } + let(:service_object) { instance_double(Chef::Knife::KeyList) } + let(:command) do + c = described_class.new([]) + c.ui.config[:disable_editing] = true + allow(c.ui).to receive(:stderr).and_return(stderr) + allow(c.ui).to receive(:stdout).and_return(stderr) + allow(c).to receive(:show_usage) + c + end + + context "after apply_params! is called with valid args" do + let(:params) { ["charmander"] } + before do + command.apply_params!(params) + end + + context "when the service object is called" do + it "creates a new instance of Chef::Knife::KeyList with the correct args" do + expect(Chef::Knife::KeyList).to receive(:new). + with("charmander", command.list_method, command.ui, command.config). + and_return(service_object) + command.service_object + end + end # when the service object is called + end # after apply_params! is called with valid args + end # a key list command + + describe Chef::Knife::UserKeyList do + it_should_behave_like "a key list command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyList) } + end + end + + describe Chef::Knife::ClientKeyList do + it_should_behave_like "a key list command" + # defined in key_helpers.rb + it_should_behave_like "a knife key command" do + let(:service_object) { instance_double(Chef::Knife::KeyList) } + end + end +end + +describe Chef::Knife::KeyList do + let(:config) { Hash.new } + let(:actor) { "charmander" } + let(:ui) { instance_double("Chef::Knife::UI") } + + shared_examples_for "key list run command" do + let(:key_list_object) { + described_class.new(actor, list_method, ui, config) + } + + before do + allow(Chef::Key).to receive(list_method).and_return(http_response) + allow(key_list_object).to receive(:display_info) + # simply pass the string though that colorize takes in + allow(key_list_object).to receive(:colorize).with(kind_of(String)) do |input| + input + end + end + + context "when only_expired and only_non_expired were both passed" do + before do + key_list_object.config[:only_expired] = true + key_list_object.config[:only_non_expired] = true + end + + it "raises a Chef::Exceptions::KeyCommandInputError with the proper error message" do + expect{ key_list_object.run }.to raise_error(Chef::Exceptions::KeyCommandInputError, key_list_object.expired_and_non_expired_msg) + end + end + + context "when the command is run" do + before do + key_list_object.config[:only_expired] = false + key_list_object.config[:only_non_expired] = false + key_list_object.config[:with_details] = false + end + + it "calls Chef::Key with the proper list command and input" do + expect(Chef::Key).to receive(list_method).with(actor) + key_list_object.run + end + + it "displays all the keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).twice + expect(key_list_object).to receive(:display_info).with(/out-of-date/).once + key_list_object.run + end + + context "when only_expired is called" do + before do + key_list_object.config[:only_expired] = true + end + + it "excludes displaying non-expired keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).exactly(0).times + key_list_object.run + end + + it "displays the expired keys" do + expect(key_list_object).to receive(:display_info).with(/out-of-date/).once + key_list_object.run + end + end # when only_expired is called + + context "when only_non_expired is called" do + before do + key_list_object.config[:only_non_expired] = true + end + + it "excludes displaying expired keys" do + expect(key_list_object).to receive(:display_info).with(/out-of-date/).exactly(0).times + key_list_object.run + end + + it "displays the non-expired keys" do + expect(key_list_object).to receive(:display_info).with(/non-expired/).twice + key_list_object.run + end + end # when only_expired is called + + context "when with_details is false" do + before do + key_list_object.config[:with_details] = false + end + + it "does not display the uri" do + expect(key_list_object).to receive(:display_info).with(/https/).exactly(0).times + key_list_object.run + end + + it "does not display the expired status" do + expect(key_list_object).to receive(:display_info).with(/\(expired\)/).exactly(0).times + key_list_object.run + end + end # when with_details is false + + context "when with_details is true" do + before do + key_list_object.config[:with_details] = true + end + + it "displays the uri" do + expect(key_list_object).to receive(:display_info).with(/https/).exactly(3).times + key_list_object.run + end + + it "displays the expired status" do + expect(key_list_object).to receive(:display_info).with(/\(expired\)/).once + key_list_object.run + end + end # when with_details is true + + end # when the command is run + + end # key list run command + + context "when list_method is :list_by_user" do + it_should_behave_like "key list run command" do + let(:list_method) { :list_by_user } + let(:http_response) { + [ + {"uri"=>"https://api.opscode.piab/users/charmander/keys/non-expired1", "name"=>"non-expired1", "expired"=>false}, + {"uri"=>"https://api.opscode.piab/users/charmander/keys/non-expired2", "name"=>"non-expired2", "expired"=>false}, + {"uri"=>"https://api.opscode.piab/users/mary/keys/out-of-date", "name"=>"out-of-date", "expired"=>true} + ] + } + end + end + + context "when list_method is :list_by_client" do + it_should_behave_like "key list run command" do + let(:list_method) { :list_by_client } + let(:http_response) { + [ + {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired1", "name"=>"non-expired1", "expired"=>false}, + {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/charmander/keys/non-expired2", "name"=>"non-expired2", "expired"=>false}, + {"uri"=>"https://api.opscode.piab/organizations/pokemon/clients/mary/keys/out-of-date", "name"=>"out-of-date", "expired"=>true} + ] + } + end + end +end |