From 9800d9b2a400587726b901ba235fdbdfd4e71f70 Mon Sep 17 00:00:00 2001 From: Tommy Bishop Date: Wed, 16 May 2012 14:30:05 -0700 Subject: [PATCH] first pass at edit --- lib/chef/knife/solo_data_bag_edit.rb | 72 +++++++++++++ lib/knife-solo_data_bag.rb | 1 + spec/contexts/bag_path_is_not_valid.rb | 3 +- ...tring_and_secret_file_are_both_provided.rb | 5 +- spec/unit/solo_data_bag_edit_spec.rb | 101 ++++++++++++++++++ 5 files changed, 179 insertions(+), 3 deletions(-) create mode 100644 lib/chef/knife/solo_data_bag_edit.rb create mode 100644 spec/unit/solo_data_bag_edit_spec.rb diff --git a/lib/chef/knife/solo_data_bag_edit.rb b/lib/chef/knife/solo_data_bag_edit.rb new file mode 100644 index 0000000..e4ecc30 --- /dev/null +++ b/lib/chef/knife/solo_data_bag_edit.rb @@ -0,0 +1,72 @@ +module KnifeSoloDataBag + + class SoloDataBagEdit < Chef::Knife + + require 'chef/knife/helpers' + + include KnifeSoloDataBag::Helpers + + banner 'knife solo data bag edit BAG ITEM (options)' + category 'solo data bag' + + attr_reader :bag_name, :item_name + + option :secret, + :short => '-s SECRET', + :long => '--secret SECRET', + :description => 'The secret key to use to encrypt data bag item values' + + option :secret_file, + :long => '--secret-file SECRET_FILE', + :description => 'A file containing the secret key to use to encrypt data bag item values' + + def edit_content + updated_content = edit_data existing_bag_item_content + item = Chef::DataBagItem.from_hash format_editted_content(updated_content) + item.data_bag bag_name + persist_bag_item item + end + + def existing_bag_item_content + content = Chef::DataBagItem.load(bag_name, item_name) + + return content unless should_be_encrypted? + Chef::EncryptedDataBagItem.new(content, secret_key).to_hash + end + + def format_editted_content(content) + return content unless should_be_encrypted? + Chef::EncryptedDataBagItem.encrypt_data_bag_item content, secret_key + end + + def run + Chef::Config[:solo] = true + @bag_name, @item_name = @name_args + ensure_valid_arguments + edit_content + end + + def ensure_valid_arguments + validate_bag_name_provided + validate_item_name_provided + validate_bags_path_exists + validate_multiple_secrets_were_not_provided + end + + def persist_bag_item(item) + File.open bag_item_path, 'w' do |f| + f.write item.to_json + end + end + + def validate_item_name_provided + unless item_name + show_usage + ui.fatal 'You must supply a name for the item' + exit 1 + end + end + + end + +end diff --git a/lib/knife-solo_data_bag.rb b/lib/knife-solo_data_bag.rb index 6e5e40b..6682bef 100644 --- a/lib/knife-solo_data_bag.rb +++ b/lib/knife-solo_data_bag.rb @@ -2,6 +2,7 @@ require 'chef/knife' require 'chef/knife/helpers' require 'chef/knife/solo_data_bag_create' +require 'chef/knife/solo_data_bag_edit' require 'chef/knife/solo_data_bag_show' require 'knife-solo_data_bag/version' diff --git a/spec/contexts/bag_path_is_not_valid.rb b/spec/contexts/bag_path_is_not_valid.rb index a0ab985..6dd6e4a 100644 --- a/spec/contexts/bag_path_is_not_valid.rb +++ b/spec/contexts/bag_path_is_not_valid.rb @@ -1,8 +1,9 @@ -shared_context 'bag_path_is_not_valid' do +shared_context 'bag_path_is_not_valid' do |args| context 'when the data bag path is not valid' do before do File.stub(:directory?).and_return(false) @knife.name_args = ['foo'] + @knife.name_args.concat Array(args) end it 'should raise an invalid data bag path exception' do diff --git a/spec/contexts/secret_string_and_secret_file_are_both_provided.rb b/spec/contexts/secret_string_and_secret_file_are_both_provided.rb index 946af4f..029894a 100644 --- a/spec/contexts/secret_string_and_secret_file_are_both_provided.rb +++ b/spec/contexts/secret_string_and_secret_file_are_both_provided.rb @@ -1,7 +1,8 @@ -shared_context 'secret_string_and_secret_file_are_both_provided' do +shared_context 'secret_string_and_secret_file_are_both_provided' do |args| context 'when specifying -s and --secret-file' do before do - @knife.name_args = 'foo' + @knife.name_args = ['foo'] + @knife.name_args.concat Array(args) @knife.config[:secret] = 'foobar' @knife.config[:secret_file] = 'secret.txt' File.stub(:directory?).and_return(true) diff --git a/spec/unit/solo_data_bag_edit_spec.rb b/spec/unit/solo_data_bag_edit_spec.rb new file mode 100644 index 0000000..377fdef --- /dev/null +++ b/spec/unit/solo_data_bag_edit_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe KnifeSoloDataBag::SoloDataBagEdit do + before do + @knife = subject + end + + include_context 'stubbed_out_stdout_and_stderr' + + describe 'run' do + include_context 'bag_name_not_provided' + include_context 'bag_path_is_not_valid', ['foo'] + include_context 'secret_string_and_secret_file_are_both_provided', ['bar'] + + context 'when an item name is not provided' do + before do + @knife.name_args = ['bag_1'] + end + + it 'should exit with an error message' do + lambda { + @knife.run + }.should raise_error SystemExit + @stdout.string.should match /usage/i + @stderr.string.should match /name for the item/ + end + end + + context 'with valid arguments' do + before do + @bags_path = '/var/chef/data_bags' + @bag_path = "#{@bags_path}/bag_1" + @item_path = "#{@bag_path}/foo.json" + @knife.name_args = ['bag_1', 'foo'] + @orig_data = {'id' => 'foo', 'who' => 'bob'} + @updated_data = {'id' => 'foo', 'who' => 'sue'} + @bag_item_foo = Chef::DataBagItem.from_hash @orig_data + @bag_item_foo.data_bag 'bag_1' + @updated_bag_item = Chef::DataBagItem.from_hash @updated_data + @updated_bag_item.data_bag 'bag_1' + + FakeFS.activate! + FileUtils.mkdir_p @bag_path + + Chef::DataBagItem.should_receive(:load).with('bag_1', 'foo'). + and_return(@bag_item_foo) + @knife.stub(:edit_data).and_return(@updated_data) + Chef::Config[:data_bag_path] = @bags_path + end + + after do + FakeFS.deactivate! + FakeFS::FileSystem.clear + end + + it 'should edit the data bag item' do + @knife.run + JSON.parse(File.read(@item_path)).raw_data.should == @updated_data + end + + context 'when encrypting with -s or --secret' do + before do + @knife.config[:secret] = 'secret_key' + Chef::EncryptedDataBagItem.should_receive(:new). + with(@bag_item_foo, 'secret_key'). + and_return(@updated_data) + end + + it 'should edit the encrypted data bag item' do + @knife.run + content = JSON.parse(File.read(@item_path)).raw_data + content['who'].should_not == @orig_data['who'] + content['who'].should_not be_nil + end + end + + context 'when encrypting with --secret-file' do + before do + @secret_path = '/var/chef/secret.txt' + @knife.config[:secret_file] = @secret_path + Chef::EncryptedDataBagItem.stub(:load_secret). + with(@secret_path). + and_return('psst') + Chef::EncryptedDataBagItem.should_receive(:new). + with(@bag_item_foo, 'psst'). + and_return(@updated_data) + end + + it 'should edit the encrypted data bag item' do + @knife.run + content = JSON.parse(File.read(@item_path)).raw_data + content['who'].should_not == @orig_data['who'] + content['who'].should_not be_nil + end + end + + end + + end + +end