From 4623c6a7f504bbac9a1a847ba3c16f17d9a7e5cf Mon Sep 17 00:00:00 2001 From: Anshul Sharma Date: Tue, 19 Aug 2014 18:16:34 +0000 Subject: [Feature] encrypted flag to knife data bag create --- lib/chef/knife/data_bag_create.rb | 27 ++++++++++++++++++--------- lib/chef/knife/data_bag_edit.rb | 17 +++++++++++++---- lib/chef/knife/data_bag_show.rb | 18 +++++++++++++----- spec/unit/knife/data_bag_create_spec.rb | 8 ++++---- spec/unit/knife/data_bag_edit_spec.rb | 8 ++++---- spec/unit/knife/data_bag_show_spec.rb | 8 ++++---- 6 files changed, 56 insertions(+), 30 deletions(-) diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index bc49c68448..e8ca479fe4 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -42,6 +42,11 @@ class Chef :description => "A file containing the secret key to use to encrypt data bag item values", :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } + option :encrypted, + :long => "--encrypted", + :description => "Only encrypt data bag when specified.", + :proc => Proc.new { |e| Chef::Config[:knife][:encrypted] = e } + def read_secret if config[:secret] config[:secret] @@ -51,11 +56,15 @@ class Chef end def use_encryption - if config[:secret] && config[:secret_file] - ui.fatal("please specify only one of --secret, --secret-file") - exit(1) + if config[:encrypted] + if config[:secret] && config[:secret_file] + ui.fatal("please specify only one of --secret, --secret-file") + exit(1) + end + config[:secret] || config[:secret_file] + else + false end - config[:secret] || config[:secret_file] end def run @@ -87,11 +96,11 @@ class Chef if @data_bag_item_name create_object({ "id" => @data_bag_item_name }, "data_bag_item[#{@data_bag_item_name}]") do |output| item = Chef::DataBagItem.from_hash( - if use_encryption - Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret) - else - output - end) + if use_encryption + Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret) + else + output + end) item.data_bag(@data_bag_name) rest.post_rest("data/#{@data_bag_name}", item) end diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb index b3f53af919..2486edd5dd 100644 --- a/lib/chef/knife/data_bag_edit.rb +++ b/lib/chef/knife/data_bag_edit.rb @@ -42,6 +42,11 @@ class Chef :description => "A file containing the secret key to use to encrypt data bag item values", :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } + option :encrypted, + :long => "--encrypted", + :description => "Only encrypt data bag when specified.", + :proc => Proc.new { |e| Chef::Config[:knife][:encrypted] = e } + def read_secret if config[:secret] config[:secret] @@ -51,11 +56,15 @@ class Chef end def use_encryption - if config[:secret] && config[:secret_file] - stdout.puts "please specify only one of --secret, --secret-file" - exit(1) + if config[:encrypted] + if config[:secret] && config[:secret_file] + ui.fatal("please specify only one of --secret, --secret-file") + exit(1) + end + config[:secret] || config[:secret_file] + else + false end - config[:secret] || config[:secret_file] end def load_item(bag, item_name) diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index 519859ca2d..c236bea53b 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -42,6 +42,11 @@ class Chef :description => "A file containing the secret key to use to decrypt data bag item values", :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } + option :encrypted, + :long => "--encrypted", + :description => "Only encrypt data bag when specified.", + :proc => Proc.new { |e| Chef::Config[:knife][:encrypted] = e } + def read_secret if config[:secret] config[:secret] @@ -51,11 +56,15 @@ class Chef end def use_encryption - if config[:secret] && config[:secret_file] - stdout.puts "please specify only one of --secret, --secret-file" - exit(1) + if config[:encrypted] + if config[:secret] && config[:secret_file] + ui.fatal("please specify only one of --secret, --secret-file") + exit(1) + end + config[:secret] || config[:secret_file] + else + false end - config[:secret] || config[:secret_file] end def run @@ -80,4 +89,3 @@ class Chef end end end - diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb index 984be8e58a..2656b2b9b4 100644 --- a/spec/unit/knife/data_bag_create_spec.rb +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -100,16 +100,16 @@ describe Chef::Knife::DataBagCreate do @secret_file.unlink end - it "creates an encrypted data bag item via --secret" do - @knife.stub(:config).and_return({:secret => @secret}) + it "creates an encrypted data bag item via --secret and --encrypted" do + @knife.stub(:config).and_return({:secret => @secret, :encrypted => true}) @knife.run end - it "creates an encrypted data bag item via --secret_file" do + it "creates an encrypted data bag item via --secret_file and --encrypted" do secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") secret_file.puts(@secret) secret_file.flush - @knife.stub(:config).and_return({:secret_file => secret_file.path}) + @knife.stub(:config).and_return({:secret_file => secret_file.path, :encrypted => true}) @knife.run end end diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb index 866ca99174..ba931c1883 100644 --- a/spec/unit/knife/data_bag_edit_spec.rb +++ b/spec/unit/knife/data_bag_edit_spec.rb @@ -74,16 +74,16 @@ describe Chef::Knife::DataBagEdit do @secret_file.unlink end - it "decrypts and encrypts via --secret" do - @knife.stub(:config).and_return({:secret => @secret}) + it "decrypts and encrypts via --secret and --encrypted" do + @knife.stub(:config).and_return({:secret => @secret, :encrypted => true}) @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data) @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered @knife.run end - it "decrypts and encrypts via --secret_file" do - @knife.stub(:config).and_return({:secret_file => @secret_file.path}) + it "decrypts and encrypts via --secret_file and --encrypted" do + @knife.stub(:config).and_return({:secret_file => @secret_file.path, :encrypted => true}) @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data) @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb index 4aa642fc4b..ac368ed6da 100644 --- a/spec/unit/knife/data_bag_show_spec.rb +++ b/spec/unit/knife/data_bag_show_spec.rb @@ -91,8 +91,8 @@ describe Chef::Knife::DataBagShow do @secret_file.unlink end - it "prints the decrypted contents of an item when given --secret" do - allow(@knife).to receive(:config).and_return({:secret => @secret}) + it "prints the decrypted contents of an item when given --secret and --encrypted" do + allow(@knife).to receive(:config).and_return({:secret => @secret, :encrypted => true}) expect(Chef::EncryptedDataBagItem).to receive(:load). with('bag_name', 'item_name', @secret). and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret)) @@ -100,8 +100,8 @@ describe Chef::Knife::DataBagShow do expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(@plain_data) end - it "prints the decrypted contents of an item when given --secret_file" do - allow(@knife).to receive(:config).and_return({:secret_file => @secret_file.path}) + it "prints the decrypted contents of an item when given --secret_file and --encrypted" do + allow(@knife).to receive(:config).and_return({:secret_file => @secret_file.path, :encrypted => true}) expect(Chef::EncryptedDataBagItem).to receive(:load). with('bag_name', 'item_name', @secret). and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret)) -- cgit v1.2.1 From 92e83b3a4de78e4273b23d0c0f737c43755aff59 Mon Sep 17 00:00:00 2001 From: Anshul Sharma Date: Tue, 19 Aug 2014 19:45:02 +0000 Subject: rspec test --- spec/unit/knife/data_bag_create_spec.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb index 2656b2b9b4..606d018a3c 100644 --- a/spec/unit/knife/data_bag_create_spec.rb +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -72,6 +72,37 @@ describe Chef::Knife::DataBagCreate do @knife.run end + it "should not create an encrypted data bag item via --secret" do + @knife.name_args = ['sudoing_admins', 'ME'] + plain_data = {'login_name' => 'alphaomega', 'id' => 'ME'} + data_bag_item = Chef::DataBagItem.from_hash(plain_data) + data_bag_item.data_bag('sudoing_admins') + @knife.should_receive(:create_object).and_yield(plain_data) + @rest.should_receive(:post_rest).with('data', {'name' => 'sudoing_admins'}).ordered + @rest.should_receive(:post_rest).with('data/sudoing_admins', data_bag_item).ordered + secret = 'abc123SECRET' + @knife.stub(:config).and_return({:secret => secret}) + @knife.run + end + + it "should not create an encrypted data bag item via --secret_file " do + secret = 'abc123SECRET' + secret_file = Tempfile.new('encrypted_data_bag_secret_file_test') + secret_file.puts(secret) + secret_file.flush + @knife.name_args = ['sudoing_admins', 'ME'] + plain_data = {'login_name' => 'alphaomega', 'id' => 'ME'} + data_bag_item = Chef::DataBagItem.from_hash(plain_data) + data_bag_item.data_bag("sudoing_admins") + @knife.should_receive(:create_object).and_yield(plain_data) + @knife.stub(:config).and_return({:secret_file => secret_file.path}) + @rest.should_receive(:post_rest).with('data', {'name' => 'sudoing_admins'}).ordered + @rest.should_receive(:post_rest).with('data/sudoing_admins', data_bag_item).ordered + @knife.run + secret_file.close + secret_file.unlink + end + describe "encrypted data bag items" do before(:each) do @secret = "abc123SECRET" -- cgit v1.2.1 From 6cdcccae73325a4b22460838ca3ebcd5384ad2d9 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Fri, 22 Aug 2014 13:24:05 -0700 Subject: Create encrypted data bag from config secrets with --encrypt. --- lib/chef/knife/data_bag_create.rb | 41 +++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index e8ca479fe4..e0c7f089b6 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -42,28 +42,49 @@ class Chef :description => "A file containing the secret key to use to encrypt data bag item values", :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } - option :encrypted, - :long => "--encrypted", + option :encrypt, + :long => "--encrypt", :description => "Only encrypt data bag when specified.", - :proc => Proc.new { |e| Chef::Config[:knife][:encrypted] = e } + :boolean => true, + :default => false def read_secret - if config[:secret] - config[:secret] + if secret = config[:secret] || knife_config[:secret] || Chef::Config[:secret] + secret else - Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) + secret_file = config[:secret_file] || knife_config[:secret_file] || Chef::Config[:secret_file] + Chef::EncryptedDataBagItem.load_secret(secret_file) end end + def knife_config + Chef::Config.key?(:knife) ? Chef::Config[:knife] : {} + end + + def has_secret? + knife_config[:secret] || Chef::Config[:secret] + end + + def has_secret_file? + knife_config[:secret_file] || Chef::Config[:secret_file] + end + def use_encryption + # Ensure only one of --secret and --secret-file has been given. + if config[:secret] && config[:secret_file] + ui.fatal("Please specify only one of --secret, --secret-file") + exit(1) + end + + return true if config[:secret] || config[:secret_file] if config[:encrypted] - if config[:secret] && config[:secret_file] - ui.fatal("please specify only one of --secret, --secret-file") + unless has_secret? || has_secret_file? + ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") exit(1) end - config[:secret] || config[:secret_file] + return true else - false + return false end end -- cgit v1.2.1 From 900bc7d32c824eeb31d489914ff70b740cb720ab Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Fri, 22 Aug 2014 13:48:56 -0700 Subject: Update to RSpec 3. --- spec/unit/knife/data_bag_create_spec.rb | 182 +++++++++++++++++--------------- 1 file changed, 96 insertions(+), 86 deletions(-) diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb index 606d018a3c..f008b790b3 100644 --- a/spec/unit/knife/data_bag_create_spec.rb +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -33,116 +33,126 @@ module ChefSpecs end end - describe Chef::Knife::DataBagCreate do - before do - Chef::Config[:node_name] = "webmonkey.example.com" - @knife = Chef::Knife::DataBagCreate.new - @rest = ChefSpecs::ChefRest.new - @knife.stub(:rest).and_return(@rest) - @stdout = StringIO.new - @knife.ui.stub(:stdout).and_return(@stdout) + let(:knife) do + k = Chef::Knife::DataBagCreate.new + allow(k).to receive(:rest).and_return(rest) + allow(k.ui).to receive(:stdout).and_return(stdout) + k end + let(:rest) { ChefSpecs::ChefRest.new } + let(:stdout) { StringIO.new } - it "creates a data bag when given one argument" do - @knife.name_args = ['sudoing_admins'] - @rest.should_receive(:post_rest).with("data", {"name" => "sudoing_admins"}) - @knife.ui.should_receive(:info).with("Created data_bag[sudoing_admins]") + let(:bag_name) { "sudoing_admins" } + let(:item_name) { "ME" } - @knife.run + before do + Chef::Config[:node_name] = "webmonkey.example.com" end it "tries to create a data bag with an invalid name when given one argument" do - @knife.name_args = ['invalid&char'] - @knife.should_receive(:exit).with(1) - - @knife.run + knife.name_args = ['invalid&char'] + expect(knife).to receive(:exit).with(1) + knife.run end - it "creates a data bag item when given two arguments" do - @knife.name_args = ['sudoing_admins', 'ME'] - user_supplied_hash = {"login_name" => "alphaomega", "id" => "ME"} - data_bag_item = Chef::DataBagItem.from_hash(user_supplied_hash) - data_bag_item.data_bag("sudoing_admins") - @knife.should_receive(:create_object).and_yield(user_supplied_hash) - @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered - @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered + context "when given one argument" do + before do + knife.name_args = [bag_name] + end + + it "creates a data bag" do + expect(rest).to receive(:post_rest).with("data", {"name" => bag_name}) + expect(knife.ui).to receive(:info).with("Created data_bag[#{bag_name}]") - @knife.run + knife.run + end end - it "should not create an encrypted data bag item via --secret" do - @knife.name_args = ['sudoing_admins', 'ME'] - plain_data = {'login_name' => 'alphaomega', 'id' => 'ME'} - data_bag_item = Chef::DataBagItem.from_hash(plain_data) - data_bag_item.data_bag('sudoing_admins') - @knife.should_receive(:create_object).and_yield(plain_data) - @rest.should_receive(:post_rest).with('data', {'name' => 'sudoing_admins'}).ordered - @rest.should_receive(:post_rest).with('data/sudoing_admins', data_bag_item).ordered - secret = 'abc123SECRET' - @knife.stub(:config).and_return({:secret => secret}) - @knife.run + shared_examples_for "a data bag item" do + let(:item) do + item = Chef::DataBagItem.from_hash(raw_hash) + item.data_bag(bag_name) + item + end + + let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} + + before do + knife.name_args = [bag_name, item_name] + end + + it "creates a data bag item" do + expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(rest).to receive(:post_rest).with("data", {'name' => bag_name}).ordered + expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered + + knife.run + end end - it "should not create an encrypted data bag item via --secret_file " do - secret = 'abc123SECRET' - secret_file = Tempfile.new('encrypted_data_bag_secret_file_test') - secret_file.puts(secret) - secret_file.flush - @knife.name_args = ['sudoing_admins', 'ME'] - plain_data = {'login_name' => 'alphaomega', 'id' => 'ME'} - data_bag_item = Chef::DataBagItem.from_hash(plain_data) - data_bag_item.data_bag("sudoing_admins") - @knife.should_receive(:create_object).and_yield(plain_data) - @knife.stub(:config).and_return({:secret_file => secret_file.path}) - @rest.should_receive(:post_rest).with('data', {'name' => 'sudoing_admins'}).ordered - @rest.should_receive(:post_rest).with('data/sudoing_admins', data_bag_item).ordered - @knife.run - secret_file.close - secret_file.unlink + context "when given two arguments" do + include_examples "a data bag item" end describe "encrypted data bag items" do - before(:each) do - @secret = "abc123SECRET" - @plain_data = {"login_name" => "alphaomega", "id" => "ME"} - @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, - @secret) - @knife.name_args = ['sudoing_admins', 'ME'] - @knife.should_receive(:create_object).and_yield(@plain_data) - data_bag_item = Chef::DataBagItem.from_hash(@enc_data) - data_bag_item.data_bag("sudoing_admins") - - # Random IV is used each time the data bag item is encrypted, so values - # will not be equal if we re-encrypt. - Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_data) - - @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered - @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered - - @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") - @secret_file.puts(@secret) - @secret_file.flush + let(:secret) { "abc123SECRET" } + let(:secret_file) do + sfile = Tempfile.new("encrypted_data_bag_secret") + sfile.puts(secret) + sfile.flush end - after do - @secret_file.close - @secret_file.unlink + + let(:raw_data) {{ "login_name" => "alphaomega", "id" => item_name }} + let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, secret) } + + let(:item) do + item = Chef::DataBagItem.from_hash(encoded_data) + item.data_bag(bag_name) + item end - it "creates an encrypted data bag item via --secret and --encrypted" do - @knife.stub(:config).and_return({:secret => @secret, :encrypted => true}) - @knife.run + before do + knife.name_args = [bag_name, item_name] + allow(knife).to receive(:config).and_return(config) end - it "creates an encrypted data bag item via --secret_file and --encrypted" do - secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") - secret_file.puts(@secret) - secret_file.flush - @knife.stub(:config).and_return({:secret_file => secret_file.path, :encrypted => true}) - @knife.run + shared_examples_for "an encrypted data bag item" do + it "creates an encrypted data bag item" do + expect(knife).to receive(:create_object).and_yield(raw_data) + expect(Chef::EncryptedDataBagItem) + .to receive(:encrypt_data_bag_item) + .with(raw_data, secret) + .and_return(encoded_data) + expect(rest).to receive(:post_rest).with("data", {"name" => bag_name}).ordered + expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered + + knife.run + end + end + + context "via --secret" do + include_examples "an encrypted data bag item" do + let(:config) { {:secret => secret} } + end + end + + context "via --secret-file" do + include_examples "an encrypted data bag item" do + let(:config) { {:secret_file => secret_file} } + end end - end + context "via --secret and --secret-file" do + let(:config) { {:secret => secret, :secret_file => secret_file} } + + it "fails to create an encrypted data bag item" do + expect(knife).to receive(:create_object).and_yield(raw_data) + expect(knife).to receive(:exit).with(1) + knife.run + end + end + end end -- cgit v1.2.1 From 40c2f92437579044284f9b4cc433ccf4f1d9d391 Mon Sep 17 00:00:00 2001 From: Claire McQuin Date: Fri, 22 Aug 2014 16:21:49 -0700 Subject: Fix logic and spec out new knife data bag create behaviors. --- lib/chef/knife/data_bag_create.rb | 10 ++- spec/unit/knife/data_bag_create_spec.rb | 113 +++++++++++++++++++++----------- 2 files changed, 81 insertions(+), 42 deletions(-) diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index e0c7f089b6..afd5832ead 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -49,10 +49,14 @@ class Chef :default => false def read_secret - if secret = config[:secret] || knife_config[:secret] || Chef::Config[:secret] + if config[:secret] + config[:secret] + elsif config[:secret_file] + Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) + elsif secret = knife_config[:secret] || Chef::Config[:secret] secret else - secret_file = config[:secret_file] || knife_config[:secret_file] || Chef::Config[:secret_file] + secret_file = knife_config[:secret_file] || Chef::Config[:secret_file] Chef::EncryptedDataBagItem.load_secret(secret_file) end end @@ -77,7 +81,7 @@ class Chef end return true if config[:secret] || config[:secret_file] - if config[:encrypted] + if config[:encrypt] unless has_secret? || has_secret_file? ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") exit(1) diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb index f008b790b3..c3bcf0416f 100644 --- a/spec/unit/knife/data_bag_create_spec.rb +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -47,8 +47,21 @@ describe Chef::Knife::DataBagCreate do let(:bag_name) { "sudoing_admins" } let(:item_name) { "ME" } + let(:secret) { "abc123SECRET" } + let(:secret_file) do + sfile = Tempfile.new("encrypted_data_bag_secret") + sfile.puts(secret) + sfile.flush + end + + let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} + + let(:config) { {} } + before do Chef::Config[:node_name] = "webmonkey.example.com" + knife.name_args = [bag_name, item_name] + allow(knife).to receive(:config).and_return(config) end it "tries to create a data bag with an invalid name when given one argument" do @@ -77,8 +90,6 @@ describe Chef::Knife::DataBagCreate do item end - let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} - before do knife.name_args = [bag_name, item_name] end @@ -92,21 +103,8 @@ describe Chef::Knife::DataBagCreate do end end - context "when given two arguments" do - include_examples "a data bag item" - end - - describe "encrypted data bag items" do - let(:secret) { "abc123SECRET" } - let(:secret_file) do - sfile = Tempfile.new("encrypted_data_bag_secret") - sfile.puts(secret) - sfile.flush - end - - - let(:raw_data) {{ "login_name" => "alphaomega", "id" => item_name }} - let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, secret) } + shared_examples_for "an encrypted data bag item" do + let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) } let(:item) do item = Chef::DataBagItem.from_hash(encoded_data) @@ -114,44 +112,81 @@ describe Chef::Knife::DataBagCreate do item end + it "creates an encrypted data bag item" do + expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(Chef::EncryptedDataBagItem) + .to receive(:encrypt_data_bag_item) + .with(raw_hash, secret) + .and_return(encoded_data) + expect(rest).to receive(:post_rest).with("data", {"name" => bag_name}).ordered + expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered + + knife.run + end + end + + context "when given two arguments" do + include_examples "a data bag item" + end + + context "with secret in knife.rb" do before do - knife.name_args = [bag_name, item_name] - allow(knife).to receive(:config).and_return(config) + Chef::Config[:knife][:secret] = config_secret + end + + include_examples "a data bag item" do + let(:config_secret) { secret } end - shared_examples_for "an encrypted data bag item" do - it "creates an encrypted data bag item" do - expect(knife).to receive(:create_object).and_yield(raw_data) - expect(Chef::EncryptedDataBagItem) - .to receive(:encrypt_data_bag_item) - .with(raw_data, secret) - .and_return(encoded_data) - expect(rest).to receive(:post_rest).with("data", {"name" => bag_name}).ordered - expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered + context "with --encrypt" do + include_examples "an encrypted data bag item" do + let(:config) {{ :encrypt => true }} + let(:config_secret) { secret } + end + end - knife.run + context "with --secret" do + include_examples "an encrypted data bag item" do + let(:config) {{ :secret => secret }} + let(:config_secret) { "TERCES321cba" } end end - context "via --secret" do + context "with --secret-file" do include_examples "an encrypted data bag item" do - let(:config) { {:secret => secret} } + let(:config) {{ :secret_file => secret_file.path }} + let(:config_secret) { "TERCES321cba" } end end + end + + context "with secret_file in knife.rb" do + before do + Chef::Config[:knife][:secret_file] = config_secret_file + end + + include_examples "a data bag item" do + let(:config_secret_file) { secret_file.path } + end - context "via --secret-file" do + context "with --encrypt" do include_examples "an encrypted data bag item" do - let(:config) { {:secret_file => secret_file} } + let(:config) {{ :encrypt => true }} + let(:config_secret_file) { secret_file.path } end end - context "via --secret and --secret-file" do - let(:config) { {:secret => secret, :secret_file => secret_file} } + context "with --secret" do + include_examples "an encrypted data bag item" do + let(:config) {{ :secret => secret }} + let(:config_secret_file) { "/etc/chef/encrypted_data_bag_secret" } + end + end - it "fails to create an encrypted data bag item" do - expect(knife).to receive(:create_object).and_yield(raw_data) - expect(knife).to receive(:exit).with(1) - knife.run + context "with --secret-file" do + include_examples "an encrypted data bag item" do + let(:config) {{ :secret_file => secret_file.path }} + let(:config_secret_file) { "/etc/chef/encrypted_data_bag_secret" } end end end -- cgit v1.2.1 From 8b1866e11e8ab41543cde22151c08365f2d4e3da Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Mon, 8 Sep 2014 08:52:11 -0700 Subject: Updating tests for encrypted data bag create - found some missing coverage --- lib/chef/knife/data_bag_create.rb | 12 +++++-- spec/unit/knife/data_bag_create_spec.rb | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index afd5832ead..d54d047db4 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -80,16 +80,22 @@ class Chef exit(1) end + # TODO is there validation on the config schema? If so, this validation should go there + if has_secret? && has_secret_file? + ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config") + exit(1) + end + return true if config[:secret] || config[:secret_file] if config[:encrypt] unless has_secret? || has_secret_file? ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") exit(1) + else + return true end - return true - else - return false end + return false end def run diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb index c3bcf0416f..62a2dd8644 100644 --- a/spec/unit/knife/data_bag_create_spec.rb +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -129,6 +129,48 @@ describe Chef::Knife::DataBagCreate do include_examples "a data bag item" end + context "when provided --secret and --secret-file" do + + let(:config) {{ :secret_file => secret_file.path, :secret => secret }} + + it "throws an error" do + expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(knife).to receive(:exit).with(1) + expect(knife.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file") + + knife.run + end + + end + + context "when provided with `secret` and `secret_file` in knife.rb" do + before do + Chef::Config[:knife][:secret] = secret + Chef::Config[:knife][:secret_file] = secret_file.path + end + + it "throws an error" do + expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(knife).to receive(:exit).with(1) + expect(knife.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config") + + knife.run + end + + end + + context "when --encrypt is provided without a secret" do + let(:config) {{ :encrypt => true }} + + it "throws an error" do + expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(knife).to receive(:exit).with(1) + expect(knife.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.") + + knife.run + end + end + context "with secret in knife.rb" do before do Chef::Config[:knife][:secret] = config_secret @@ -190,4 +232,21 @@ describe Chef::Knife::DataBagCreate do end end end + + context "no secret in knife.rb" do + + include_examples "a data bag item" + + context "with --secret" do + include_examples "an encrypted data bag item" do + let(:config) {{ :secret => secret }} + end + end + + context "with --secret-file" do + include_examples "an encrypted data bag item" do + let(:config) {{ :secret_file => secret_file.path }} + end + end + end end -- cgit v1.2.1 From 61c92270be36ad93eef8e769bbbed37a97f43fb1 Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Mon, 8 Sep 2014 14:32:49 -0700 Subject: Finishing spec work for data bag UX (https://gist.github.com/sersut/94c8daad5c11369bd2e8). Tests up next, breaking into multiple commits to keep the review smaller. --- lib/chef/knife/data_bag_common.rb | 137 +++++++++++++++++++++++++++++++++++ lib/chef/knife/data_bag_create.rb | 71 +----------------- lib/chef/knife/data_bag_edit.rb | 51 +++---------- lib/chef/knife/data_bag_from_file.rb | 33 +-------- lib/chef/knife/data_bag_show.rb | 60 +++++---------- 5 files changed, 174 insertions(+), 178 deletions(-) create mode 100644 lib/chef/knife/data_bag_common.rb diff --git a/lib/chef/knife/data_bag_common.rb b/lib/chef/knife/data_bag_common.rb new file mode 100644 index 0000000000..916989cbb4 --- /dev/null +++ b/lib/chef/knife/data_bag_common.rb @@ -0,0 +1,137 @@ +# +# Author:: Tyler Ball () +# Copyright:: Copyright (c) 2014 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. +# + +require 'mixlib/cli' +# TODO these were in a `deps` call before - okay that they aren't anymore? +require 'chef/config' +#require 'chef/data_bag' +require 'chef/encrypted_data_bag_item' + +class Chef + class Knife + module DataBagSecretOptions + include Mixlib::CLI + + option :secret, + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to encrypt data bag item values", + :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } + + option :secret_file, + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to encrypt data bag item values", + :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } + + option :encrypt, + :long => "--encrypt", + :description => "Only use the secret configured in knife.rb when this is true", + :boolean => true, + :default => false + + ## + # Determine if the user has specified an appropriate secret for encrypting data bag items. + # @returns boolean + def encryption_secret_provided? + validate_secrets + + return true if config[:secret] || config[:secret_file] + + if config[:encrypt] + unless has_secret? || has_secret_file? + ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") + exit(1) + else + return true + end + end + return false + end + + def read_secret + if config[:secret] + config[:secret] + elsif config[:secret_file] + Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) + elsif secret = knife_config[:secret] || Chef::Config[:secret] + secret + else + secret_file = knife_config[:secret_file] || Chef::Config[:secret_file] + Chef::EncryptedDataBagItem.load_secret(secret_file) + end + end + + def validate_secrets + if config[:secret] && config[:secret_file] + ui.fatal("Please specify only one of --secret, --secret-file") + exit(1) + end + + # TODO is there validation on the knife.rb schema? If so, this validation should go there + if has_secret? && has_secret_file? + ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config") + exit(1) + end + end + + def has_secret? + knife_config[:secret] || Chef::Config[:secret] + end + + def has_secret_file? + knife_config[:secret_file] || Chef::Config[:secret_file] + end + + # TODO duplicated from data_query.rb + # Tries to autodetect if the item's raw hash appears to be encrypted. + def encrypted?(raw_data) + data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key. + # Assume hashes containing only the "id" key are not encrypted. + # Otherwise, remove the keys that don't appear to be encrypted and compare + # the result with the hash. If some entry has been removed, then some entry + # doesn't appear to be encrypted and we assume the entire hash is not encrypted. + data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data + end + + private + + def knife_config + Chef::Config.key?(:knife) ? Chef::Config[:knife] : {} + end + + # Checks if data looks like it has been encrypted by + # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns + # true only when there is an exact match between the VersionXEncryptor + # keys and the hash's keys. + def looks_like_encrypted?(data) + return false unless data.is_a?(Hash) && data.has_key?("version") + case data["version"] + when 1 + Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort + when 2 + Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort + when 3 + Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort + else + false # version means something else... assume not encrypted. + end + end + + end + end +end diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index d54d047db4..048d34f543 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -22,7 +22,9 @@ require 'chef/knife' class Chef class Knife class DataBagCreate < Knife + include DataBagSecretOptions + # TODO duplicating deps here and in the DataBagSecretOptions module deps do require 'chef/data_bag' require 'chef/encrypted_data_bag_item' @@ -31,73 +33,6 @@ class Chef banner "knife data bag create BAG [ITEM] (options)" category "data bag" - option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values", - :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } - - option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values", - :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } - - option :encrypt, - :long => "--encrypt", - :description => "Only encrypt data bag when specified.", - :boolean => true, - :default => false - - def read_secret - if config[:secret] - config[:secret] - elsif config[:secret_file] - Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) - elsif secret = knife_config[:secret] || Chef::Config[:secret] - secret - else - secret_file = knife_config[:secret_file] || Chef::Config[:secret_file] - Chef::EncryptedDataBagItem.load_secret(secret_file) - end - end - - def knife_config - Chef::Config.key?(:knife) ? Chef::Config[:knife] : {} - end - - def has_secret? - knife_config[:secret] || Chef::Config[:secret] - end - - def has_secret_file? - knife_config[:secret_file] || Chef::Config[:secret_file] - end - - def use_encryption - # Ensure only one of --secret and --secret-file has been given. - if config[:secret] && config[:secret_file] - ui.fatal("Please specify only one of --secret, --secret-file") - exit(1) - end - - # TODO is there validation on the config schema? If so, this validation should go there - if has_secret? && has_secret_file? - ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config") - exit(1) - end - - return true if config[:secret] || config[:secret_file] - if config[:encrypt] - unless has_secret? || has_secret_file? - ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") - exit(1) - else - return true - end - end - return false - end - def run @data_bag_name, @data_bag_item_name = @name_args @@ -127,7 +62,7 @@ class Chef if @data_bag_item_name create_object({ "id" => @data_bag_item_name }, "data_bag_item[#{@data_bag_item_name}]") do |output| item = Chef::DataBagItem.from_hash( - if use_encryption + if encryption_secret_provided? Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret) else output diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb index 2486edd5dd..13d51daee0 100644 --- a/lib/chef/knife/data_bag_edit.rb +++ b/lib/chef/knife/data_bag_edit.rb @@ -22,6 +22,7 @@ require 'chef/knife' class Chef class Knife class DataBagEdit < Knife + include DataBagSecretOptions deps do require 'chef/data_bag_item' @@ -31,46 +32,15 @@ class Chef banner "knife data bag edit BAG ITEM (options)" category "data bag" - option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values", - :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } - - option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values", - :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } - - option :encrypted, - :long => "--encrypted", - :description => "Only encrypt data bag when specified.", - :proc => Proc.new { |e| Chef::Config[:knife][:encrypted] = e } - - def read_secret - if config[:secret] - config[:secret] - else - Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) - end - end - - def use_encryption - if config[:encrypted] - if config[:secret] && config[:secret_file] - ui.fatal("please specify only one of --secret, --secret-file") - exit(1) - end - config[:secret] || config[:secret_file] - else - false - end - end - def load_item(bag, item_name) item = Chef::DataBagItem.load(bag, item_name) - if use_encryption - Chef::EncryptedDataBagItem.new(item, read_secret).to_hash + if encrypted?(item.raw_data) + if encryption_secret_provided? + Chef::EncryptedDataBagItem.new(item, read_secret).to_hash + else + ui.fatal("You cannot edit an encrypted data bag without providing the secret.") + exit(1) + end else item end @@ -78,9 +48,11 @@ class Chef def edit_item(item) output = edit_data(item) - if use_encryption + if encryption_secret_provided? + ui.info("Encrypting data bag using provided secret.") Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret) else + ui.info("Saving data bag unencrypted. To encrypt it, provide an appropriate secret.") output end end @@ -95,6 +67,7 @@ class Chef output = edit_item(item) rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", output) stdout.puts("Saved data_bag_item[#{@name_args[1]}]") + # TODO this is trying to read :print_after from the CLI, not the knife.rb ui.output(output) if config[:print_after] end end diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb index 2ff79b6256..4e61f1b53f 100644 --- a/lib/chef/knife/data_bag_from_file.rb +++ b/lib/chef/knife/data_bag_from_file.rb @@ -23,6 +23,7 @@ require 'chef/util/path_helper' class Chef class Knife class DataBagFromFile < Knife + include DataBagSecretOptions deps do require 'chef/data_bag' @@ -35,38 +36,11 @@ class Chef banner "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)" category "data bag" - option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values", - :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } - - option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values", - :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } - option :all, :short => "-a", :long => "--all", :description => "Upload all data bags or all items for specified data bags" - def read_secret - if config[:secret] - config[:secret] - else - Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) - end - end - - def use_encryption - if config[:secret] && config[:secret_file] - ui.fatal("please specify only one of --secret, --secret-file") - exit(1) - end - config[:secret] || config[:secret_file] - end - def loader @loader ||= Knife::Core::ObjectLoader.new(DataBagItem, ui) end @@ -109,9 +83,8 @@ class Chef item_paths = normalize_item_paths(items) item_paths.each do |item_path| item = loader.load_from("#{data_bags_path}", data_bag, item_path) - item = if use_encryption - secret = read_secret - Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, secret) + item = if encryption_secret_provided? + Chef::EncryptedDataBagItem.encrypt_data_bag_item(item, read_secret) else item end diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index c236bea53b..0392d417cc 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -22,6 +22,7 @@ require 'chef/knife' class Chef class Knife class DataBagShow < Knife + include DataBagSecretOptions deps do require 'chef/data_bag' @@ -31,54 +32,30 @@ class Chef banner "knife data bag show BAG [ITEM] (options)" category "data bag" - option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to decrypt data bag item values", - :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } - - option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to decrypt data bag item values", - :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } - - option :encrypted, - :long => "--encrypted", - :description => "Only encrypt data bag when specified.", - :proc => Proc.new { |e| Chef::Config[:knife][:encrypted] = e } - - def read_secret - if config[:secret] - config[:secret] - else - Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) - end - end - - def use_encryption - if config[:encrypted] - if config[:secret] && config[:secret_file] - ui.fatal("please specify only one of --secret, --secret-file") - exit(1) - end - config[:secret] || config[:secret_file] - else - false - end - end - def run display = case @name_args.length - when 2 - if use_encryption + when 2 # Bag and Item names provided + secret = read_secret + raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data + encrypted = encrypted?(raw_data) + + if encrypted && secret + # TODO If the decryption fails with provided secret, what does that look like? + # Users do not need to pass --encrypt to read data, we simply try to use the provided secret + ui.info("Encrypted data bag detected, decrypting with provided secret.") raw = Chef::EncryptedDataBagItem.load(@name_args[0], @name_args[1], - read_secret) + secret) format_for_display(raw.to_hash) + elsif encrypted && !secret + ui.warn("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.") + format_for_display(raw_data) else - format_for_display(Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data) + ui.info("Unencrypted data bag detected, ignoring any provided secret options.") + format_for_display(raw_data) end - when 1 + + when 1 # Only Bag name provided format_list_for_display(Chef::DataBag.load(@name_args[0])) else stdout.puts opt_parser @@ -86,6 +63,7 @@ class Chef end output(display) end + end end end -- cgit v1.2.1 From a2a3f6774535319532cb268038644358d6f66051 Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Mon, 8 Sep 2014 17:29:44 -0700 Subject: Refactoring the common tests out into their own spec. Removing double coverage from the create tests --- lib/chef/knife/data_bag_common.rb | 2 +- spec/unit/knife/data_bag_common_spec.rb | 139 ++++++++++++++++++++++++++++++++ spec/unit/knife/data_bag_create_spec.rb | 136 ++----------------------------- 3 files changed, 145 insertions(+), 132 deletions(-) create mode 100644 spec/unit/knife/data_bag_common_spec.rb diff --git a/lib/chef/knife/data_bag_common.rb b/lib/chef/knife/data_bag_common.rb index 916989cbb4..4d4f270139 100644 --- a/lib/chef/knife/data_bag_common.rb +++ b/lib/chef/knife/data_bag_common.rb @@ -97,7 +97,7 @@ class Chef knife_config[:secret_file] || Chef::Config[:secret_file] end - # TODO duplicated from data_query.rb + # TODO duplicated from data_query.rb, also needs test coverage when it is extracted # Tries to autodetect if the item's raw hash appears to be encrypted. def encrypted?(raw_data) data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key. diff --git a/spec/unit/knife/data_bag_common_spec.rb b/spec/unit/knife/data_bag_common_spec.rb new file mode 100644 index 0000000000..67c63a8239 --- /dev/null +++ b/spec/unit/knife/data_bag_common_spec.rb @@ -0,0 +1,139 @@ +# +# Author:: Tyler Ball () +# Copyright:: Copyright (c) 2009-2014 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. +# + +require 'spec_helper' +require 'chef/knife' +require 'chef/config' +require 'tempfile' + +class ExampleDataBag < Chef::Knife + include Chef::Knife::DataBagSecretOptions + + #banner "you must provide a banner" + #category "data bag" +end + +describe Chef::Knife::DataBagSecretOptions do + let(:example_db) do + k = ExampleDataBag.new + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + let(:stdout) { StringIO.new } + + let(:secret) { "abc123SECRET" } + let(:secret_file) do + sfile = Tempfile.new("encrypted_data_bag_secret") + sfile.puts(secret) + sfile.flush + end + + after do + Chef::Config.reset + end + + describe "#validate_secrets" do + + it "throws an error when provided with both --secret and --secret-file on the CL" do + expect(example_db).to receive(:config).exactly(2).times.and_return({ :secret_file => secret_file.path, :secret => secret }) + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file") + + example_db.validate_secrets + end + + it "throws an error when provided with `secret` and `secret_file` in knife.rb" do + Chef::Config[:knife][:secret_file] = secret_file.path + Chef::Config[:knife][:secret] = secret + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config") + + example_db.validate_secrets + end + + end + + describe "#read_secret" do + + it "returns the secret first" do + expect(example_db).to receive(:config).exactly(2).times.and_return({ :secret_file => secret_file.path, :secret => secret }) + expect(example_db.read_secret).to eq(secret) + end + + it "returns the secret_file only if secret does not exist" do + expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret_file => secret_file.path }) + expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") + expect(example_db.read_secret).to eq("secret file contents") + end + + it "returns the secret from the knife.rb config" do + expect(example_db).to receive(:config).exactly(2).times.and_return({}) + Chef::Config[:knife][:secret_file] = secret_file.path + Chef::Config[:knife][:secret] = secret + expect(example_db.read_secret).to eq(secret) + end + + it "returns the secret_file from the knife.rb config only if the secret does not exist" do + expect(example_db).to receive(:config).exactly(2).times.and_return({}) + Chef::Config[:knife][:secret_file] = secret_file.path + expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") + expect(example_db.read_secret).to eq("secret file contents") + end + + end + + describe "#encryption_secret_provided?" do + + it "returns true if the secret is passed on the CL" do + expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret => secret }) + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if the secret_file is passed on the CL" do + expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret_file => secret_file.path }) + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if --encrypt is passed on the CL and :secret is in knife.rb" do + expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + Chef::Config[:knife][:secret] = secret + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if --encrypt is passed on the CL and :secret_file is in knife.rb" do + expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + Chef::Config[:knife][:secret_file] = secret_file.path + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the knife.rb" do + expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.") + example_db.encryption_secret_provided? + end + + it "returns false if no secret is passed" do + expect(example_db).to receive(:config).exactly(4).times.and_return({}) + expect(example_db.encryption_secret_provided?).to eq(false) + end + + end + +end diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb index 62a2dd8644..d99575fa82 100644 --- a/spec/unit/knife/data_bag_create_spec.rb +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -48,11 +48,6 @@ describe Chef::Knife::DataBagCreate do let(:item_name) { "ME" } let(:secret) { "abc123SECRET" } - let(:secret_file) do - sfile = Tempfile.new("encrypted_data_bag_secret") - sfile.puts(secret) - sfile.flush - end let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} @@ -83,7 +78,7 @@ describe Chef::Knife::DataBagCreate do end end - shared_examples_for "a data bag item" do + context "no secret is specified for encryption" do let(:item) do item = Chef::DataBagItem.from_hash(raw_hash) item.data_bag(bag_name) @@ -96,6 +91,7 @@ describe Chef::Knife::DataBagCreate do it "creates a data bag item" do expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(knife).to receive(:encryption_secret_provided?).and_return(false) expect(rest).to receive(:post_rest).with("data", {'name' => bag_name}).ordered expect(rest).to receive(:post_rest).with("data/#{bag_name}", item).ordered @@ -103,7 +99,7 @@ describe Chef::Knife::DataBagCreate do end end - shared_examples_for "an encrypted data bag item" do + context "a secret is specified for encryption" do let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) } let(:item) do @@ -114,6 +110,8 @@ describe Chef::Knife::DataBagCreate do it "creates an encrypted data bag item" do expect(knife).to receive(:create_object).and_yield(raw_hash) + expect(knife).to receive(:encryption_secret_provided?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) expect(Chef::EncryptedDataBagItem) .to receive(:encrypt_data_bag_item) .with(raw_hash, secret) @@ -125,128 +123,4 @@ describe Chef::Knife::DataBagCreate do end end - context "when given two arguments" do - include_examples "a data bag item" - end - - context "when provided --secret and --secret-file" do - - let(:config) {{ :secret_file => secret_file.path, :secret => secret }} - - it "throws an error" do - expect(knife).to receive(:create_object).and_yield(raw_hash) - expect(knife).to receive(:exit).with(1) - expect(knife.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file") - - knife.run - end - - end - - context "when provided with `secret` and `secret_file` in knife.rb" do - before do - Chef::Config[:knife][:secret] = secret - Chef::Config[:knife][:secret_file] = secret_file.path - end - - it "throws an error" do - expect(knife).to receive(:create_object).and_yield(raw_hash) - expect(knife).to receive(:exit).with(1) - expect(knife.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config") - - knife.run - end - - end - - context "when --encrypt is provided without a secret" do - let(:config) {{ :encrypt => true }} - - it "throws an error" do - expect(knife).to receive(:create_object).and_yield(raw_hash) - expect(knife).to receive(:exit).with(1) - expect(knife.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.") - - knife.run - end - end - - context "with secret in knife.rb" do - before do - Chef::Config[:knife][:secret] = config_secret - end - - include_examples "a data bag item" do - let(:config_secret) { secret } - end - - context "with --encrypt" do - include_examples "an encrypted data bag item" do - let(:config) {{ :encrypt => true }} - let(:config_secret) { secret } - end - end - - context "with --secret" do - include_examples "an encrypted data bag item" do - let(:config) {{ :secret => secret }} - let(:config_secret) { "TERCES321cba" } - end - end - - context "with --secret-file" do - include_examples "an encrypted data bag item" do - let(:config) {{ :secret_file => secret_file.path }} - let(:config_secret) { "TERCES321cba" } - end - end - end - - context "with secret_file in knife.rb" do - before do - Chef::Config[:knife][:secret_file] = config_secret_file - end - - include_examples "a data bag item" do - let(:config_secret_file) { secret_file.path } - end - - context "with --encrypt" do - include_examples "an encrypted data bag item" do - let(:config) {{ :encrypt => true }} - let(:config_secret_file) { secret_file.path } - end - end - - context "with --secret" do - include_examples "an encrypted data bag item" do - let(:config) {{ :secret => secret }} - let(:config_secret_file) { "/etc/chef/encrypted_data_bag_secret" } - end - end - - context "with --secret-file" do - include_examples "an encrypted data bag item" do - let(:config) {{ :secret_file => secret_file.path }} - let(:config_secret_file) { "/etc/chef/encrypted_data_bag_secret" } - end - end - end - - context "no secret in knife.rb" do - - include_examples "a data bag item" - - context "with --secret" do - include_examples "an encrypted data bag item" do - let(:config) {{ :secret => secret }} - end - end - - context "with --secret-file" do - include_examples "an encrypted data bag item" do - let(:config) {{ :secret_file => secret_file.path }} - end - end - end end -- cgit v1.2.1 From efcaafeaae481a7b49e5e9b44b79218cee20385d Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Tue, 9 Sep 2014 16:05:38 -0700 Subject: Finishing specs for create and edit. During edit functional testing I discovered some large refactors needed. --- lib/chef/knife/data_bag_common.rb | 40 ++++++------ lib/chef/knife/data_bag_create.rb | 4 +- lib/chef/knife/data_bag_edit.rb | 2 +- lib/chef/knife/data_bag_from_file.rb | 2 +- lib/chef/knife/data_bag_show.rb | 2 +- spec/spec_helper.rb | 23 +++++++ spec/unit/knife/data_bag_common_spec.rb | 7 ++- spec/unit/knife/data_bag_create_spec.rb | 23 +------ spec/unit/knife/data_bag_edit_spec.rb | 107 ++++++++++++++++++-------------- 9 files changed, 117 insertions(+), 93 deletions(-) diff --git a/lib/chef/knife/data_bag_common.rb b/lib/chef/knife/data_bag_common.rb index 4d4f270139..93f8c98624 100644 --- a/lib/chef/knife/data_bag_common.rb +++ b/lib/chef/knife/data_bag_common.rb @@ -17,32 +17,34 @@ # require 'mixlib/cli' -# TODO these were in a `deps` call before - okay that they aren't anymore? require 'chef/config' -#require 'chef/data_bag' require 'chef/encrypted_data_bag_item' class Chef class Knife - module DataBagSecretOptions + module DataBagCommon include Mixlib::CLI - option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values", - :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } - - option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values", - :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } - - option :encrypt, - :long => "--encrypt", - :description => "Only use the secret configured in knife.rb when this is true", - :boolean => true, - :default => false + # TODO when https://github.com/opscode/mixlib-cli/pull/13 is fixed, we can make this a base class + # instead of a module + def self.included(base) + base.option :secret, + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to encrypt data bag item values", + :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } + + base.option :secret_file, + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to encrypt data bag item values", + :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } + + base.option :encrypt, + :long => "--encrypt", + :description => "Only use the secret configured in knife.rb when this is true", + :boolean => true, + :default => false + end ## # Determine if the user has specified an appropriate secret for encrypting data bag items. diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index 048d34f543..40921d7d09 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -22,9 +22,9 @@ require 'chef/knife' class Chef class Knife class DataBagCreate < Knife - include DataBagSecretOptions + include DataBagCommon - # TODO duplicating deps here and in the DataBagSecretOptions module + # TODO duplicating deps here and in the DataBagCommon module deps do require 'chef/data_bag' require 'chef/encrypted_data_bag_item' diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb index 13d51daee0..718aeedc29 100644 --- a/lib/chef/knife/data_bag_edit.rb +++ b/lib/chef/knife/data_bag_edit.rb @@ -22,7 +22,7 @@ require 'chef/knife' class Chef class Knife class DataBagEdit < Knife - include DataBagSecretOptions + include DataBagCommon deps do require 'chef/data_bag_item' diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb index 4e61f1b53f..daaede706d 100644 --- a/lib/chef/knife/data_bag_from_file.rb +++ b/lib/chef/knife/data_bag_from_file.rb @@ -23,7 +23,7 @@ require 'chef/util/path_helper' class Chef class Knife class DataBagFromFile < Knife - include DataBagSecretOptions + include DataBagCommon deps do require 'chef/data_bag' diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index 0392d417cc..72f76734e7 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -22,7 +22,7 @@ require 'chef/knife' class Chef class Knife class DataBagShow < Knife - include DataBagSecretOptions + include DataBagCommon deps do require 'chef/data_bag' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ed0a8f89f6..50289451af 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -185,3 +185,26 @@ module WEBrick end end end + +# Lifted from http://stackoverflow.com/questions/1480537/how-can-i-validate-exits-and-aborts-in-rspec +RSpec::Matchers.define :exit_with_code do |exp_code| + actual = nil + match do |block| + begin + block.call + rescue SystemExit => e + actual = e.status + end + actual and actual == exp_code + end + failure_message_for_should do |block| + "expected block to call exit(#{exp_code}) but exit" + + (actual.nil? ? " not called" : "(#{actual}) was called") + end + failure_message_for_should_not do |block| + "expected block not to call exit(#{exp_code})" + end + description do + "expect block to call exit(#{exp_code})" + end +end diff --git a/spec/unit/knife/data_bag_common_spec.rb b/spec/unit/knife/data_bag_common_spec.rb index 67c63a8239..ebba9033ba 100644 --- a/spec/unit/knife/data_bag_common_spec.rb +++ b/spec/unit/knife/data_bag_common_spec.rb @@ -22,13 +22,13 @@ require 'chef/config' require 'tempfile' class ExampleDataBag < Chef::Knife - include Chef::Knife::DataBagSecretOptions + include Chef::Knife::DataBagCommon #banner "you must provide a banner" #category "data bag" end -describe Chef::Knife::DataBagSecretOptions do +describe Chef::Knife::DataBagCommon do let(:example_db) do k = ExampleDataBag.new allow(k.ui).to receive(:stdout).and_return(stdout) @@ -42,10 +42,13 @@ describe Chef::Knife::DataBagSecretOptions do sfile = Tempfile.new("encrypted_data_bag_secret") sfile.puts(secret) sfile.flush + sfile end after do Chef::Config.reset + secret_file.close + secret_file.unlink end describe "#validate_secrets" do diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb index d99575fa82..ae34b68a13 100644 --- a/spec/unit/knife/data_bag_create_spec.rb +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -20,19 +20,6 @@ require 'spec_helper' require 'tempfile' -module ChefSpecs - class ChefRest - attr_reader :args_received - def initialize - @args_received = [] - end - - def post_rest(*args) - @args_received << args - end - end -end - describe Chef::Knife::DataBagCreate do let(:knife) do k = Chef::Knife::DataBagCreate.new @@ -41,7 +28,7 @@ describe Chef::Knife::DataBagCreate do k end - let(:rest) { ChefSpecs::ChefRest.new } + let(:rest) { double("ChefSpecs::ChefRest") } let(:stdout) { StringIO.new } let(:bag_name) { "sudoing_admins" } @@ -61,8 +48,8 @@ describe Chef::Knife::DataBagCreate do it "tries to create a data bag with an invalid name when given one argument" do knife.name_args = ['invalid&char'] - expect(knife).to receive(:exit).with(1) - knife.run + expect(Chef::DataBag).to receive(:validate_name!).with(knife.name_args[0]).and_raise(Chef::Exceptions::InvalidDataBagName) + expect {knife.run}.to exit_with_code(1) end context "when given one argument" do @@ -85,10 +72,6 @@ describe Chef::Knife::DataBagCreate do item end - before do - knife.name_args = [bag_name, item_name] - end - it "creates a data bag item" do expect(knife).to receive(:create_object).and_yield(raw_hash) expect(knife).to receive(:encryption_secret_provided?).and_return(false) diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb index ba931c1883..6a7c8b33b2 100644 --- a/spec/unit/knife/data_bag_edit_spec.rb +++ b/spec/unit/knife/data_bag_edit_spec.rb @@ -21,73 +21,86 @@ require 'tempfile' describe Chef::Knife::DataBagEdit do before do - @plain_data = {"login_name" => "alphaomega", "id" => "item_name"} - @edited_data = { - "login_name" => "rho", "id" => "item_name", - "new_key" => "new_value" } + Chef::Config[:node_name] = "webmonkey.example.com" + knife.name_args = [bag_name, item_name] + allow(knife).to receive(:config).and_return(config) + end - Chef::Config[:node_name] = "webmonkey.example.com" + let(:knife) do + k = Chef::Knife::DataBagEdit.new + allow(k).to receive(:rest).and_return(rest) + allow(k).to receive(:stdout).and_return(stdout) + k + end - @knife = Chef::Knife::DataBagEdit.new - @rest = double('chef-rest-mock') - @knife.stub(:rest).and_return(@rest) + let(:plain_hash) { {"login_name" => "alphaomega", "id" => "item_name"} } + let(:plain_db) {Chef::DataBagItem.from_hash(plain_hash)} + let(:edited_hash) { {"login_name" => "rho", "id" => "item_name", "new_key" => "new_value"} } + let(:edited_db) {Chef::DataBagItem.from_hash(edited_hash)} - @stdout = StringIO.new - @knife.stub(:stdout).and_return(@stdout) - @log = Chef::Log - @knife.name_args = ['bag_name', 'item_name'] - end + let(:rest) { double("ChefSpecs::ChefRest") } + let(:stdout) { StringIO.new } + + let(:bag_name) { "sudoing_admins" } + let(:item_name) { "ME" } + + let(:secret) { "abc123SECRET" } + + let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} + + let(:config) { {} } it "requires data bag and item arguments" do - @knife.name_args = [] - lambda { @knife.run }.should raise_error(SystemExit) - @stdout.string.should match(/^You must supply the data bag and an item to edit/) + knife.name_args = [] + expect(stdout).to receive(:puts).twice.with(anything) + expect {knife.run}.to exit_with_code(1) end it "saves edits on a data bag item" do - Chef::DataBagItem.stub(:load).with('bag_name', 'item_name').and_return(@plain_data) - @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data) - @rest.should_receive(:put_rest).with("data/bag_name/item_name", @edited_data).ordered - @knife.run + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(plain_db) + expect(knife).to receive(:encrypted?) { false } + expect(knife).to receive(:edit_data).with(plain_db).and_return(edited_db.raw_data) + expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", edited_db.raw_data).ordered + knife.run end describe "encrypted data bag items" do + let(:enc_plain_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_hash, secret) } + let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_plain_hash) } + let(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_hash, secret) } + before(:each) do - @secret = "abc123SECRET" - @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, - @secret) - @enc_edited_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@edited_data, - @secret) - Chef::DataBagItem.stub(:load).with('bag_name', 'item_name').and_return(@enc_data) - - # Random IV is used each time the data bag item is encrypted, so values - # will not be equal if we encrypt same value twice. - Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_edited_data) - - @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") - @secret_file.puts(@secret) - @secret_file.flush + allow(knife).to receive(:encrypted?) { true } + allow(knife).to receive(:encryption_secret_provided?) { true } + allow(knife).to receive(:read_secret).and_return(secret) end - after do - @secret_file.close - @secret_file.unlink + it "decrypts an encrypted data bag, edits it and rencrypts it" do + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) + expect(knife).to receive(:edit_data).with(plain_hash).and_return(edited_hash) + expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(edited_hash, secret).and_return(enc_edited_hash) + expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", enc_edited_hash).ordered + + knife.run end - it "decrypts and encrypts via --secret and --encrypted" do - @knife.stub(:config).and_return({:secret => @secret, :encrypted => true}) - @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data) - @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered + it "edits an unencrypted data bag and encrypts it" do + expect(knife).to receive(:encrypted?) { false } + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(plain_db) + expect(knife).to receive(:edit_data).with(plain_db).and_return(edited_hash) + expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(edited_hash, secret).and_return(enc_edited_hash) + expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", enc_edited_hash).ordered - @knife.run + knife.run end - it "decrypts and encrypts via --secret_file and --encrypted" do - @knife.stub(:config).and_return({:secret_file => @secret_file.path, :encrypted => true}) - @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data) - @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered + it "fails to edit an encrypted data bag if the secret is missing" do + allow(knife).to receive(:encryption_secret_provided?) { false } + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) - @knife.run + expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.") + expect {knife.run}.to exit_with_code(1) end + end end -- cgit v1.2.1 From ce03bb01bff58fac8d65b402e4f1fd424f85ca6e Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Wed, 10 Sep 2014 18:18:41 -0700 Subject: Fixing broken tests and updating all tests to (attempt) to use describe/context blocks for readability --- lib/chef/knife/data_bag_show.rb | 2 +- spec/unit/knife/data_bag_common_spec.rb | 1 - spec/unit/knife/data_bag_create_spec.rb | 2 +- spec/unit/knife/data_bag_edit_spec.rb | 7 +- spec/unit/knife/data_bag_from_file_spec.rb | 213 +++++++++++++---------------- spec/unit/knife/data_bag_show_spec.rb | 146 ++++++++++---------- 6 files changed, 174 insertions(+), 197 deletions(-) diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index 72f76734e7..2a759d1b43 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -35,7 +35,7 @@ class Chef def run display = case @name_args.length when 2 # Bag and Item names provided - secret = read_secret + secret = encryption_secret_provided? ? read_secret : nil raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data encrypted = encrypted?(raw_data) diff --git a/spec/unit/knife/data_bag_common_spec.rb b/spec/unit/knife/data_bag_common_spec.rb index ebba9033ba..3e4ebf5026 100644 --- a/spec/unit/knife/data_bag_common_spec.rb +++ b/spec/unit/knife/data_bag_common_spec.rb @@ -46,7 +46,6 @@ describe Chef::Knife::DataBagCommon do end after do - Chef::Config.reset secret_file.close secret_file.unlink end diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb index ae34b68a13..c31c88577d 100644 --- a/spec/unit/knife/data_bag_create_spec.rb +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -28,7 +28,7 @@ describe Chef::Knife::DataBagCreate do k end - let(:rest) { double("ChefSpecs::ChefRest") } + let(:rest) { double("Chef::REST") } let(:stdout) { StringIO.new } let(:bag_name) { "sudoing_admins" } diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb index 6a7c8b33b2..96bfc3cbf6 100644 --- a/spec/unit/knife/data_bag_edit_spec.rb +++ b/spec/unit/knife/data_bag_edit_spec.rb @@ -29,7 +29,7 @@ describe Chef::Knife::DataBagEdit do let(:knife) do k = Chef::Knife::DataBagEdit.new allow(k).to receive(:rest).and_return(rest) - allow(k).to receive(:stdout).and_return(stdout) + allow(k.ui).to receive(:stdout).and_return(stdout) k end @@ -38,7 +38,7 @@ describe Chef::Knife::DataBagEdit do let(:edited_hash) { {"login_name" => "rho", "id" => "item_name", "new_key" => "new_value"} } let(:edited_db) {Chef::DataBagItem.from_hash(edited_hash)} - let(:rest) { double("ChefSpecs::ChefRest") } + let(:rest) { double("Chef::REST") } let(:stdout) { StringIO.new } let(:bag_name) { "sudoing_admins" } @@ -46,14 +46,13 @@ describe Chef::Knife::DataBagEdit do let(:secret) { "abc123SECRET" } - let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} - let(:config) { {} } it "requires data bag and item arguments" do knife.name_args = [] expect(stdout).to receive(:puts).twice.with(anything) expect {knife.run}.to exit_with_code(1) + expect(stdout.string).to eq("") end it "saves edits on a data bag item" do diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb index 1ad6b4712c..150ad42148 100644 --- a/spec/unit/knife/data_bag_from_file_spec.rb +++ b/spec/unit/knife/data_bag_from_file_spec.rb @@ -26,79 +26,95 @@ Chef::Knife::DataBagFromFile.load_deps describe Chef::Knife::DataBagFromFile do before :each do - Chef::Config[:node_name] = "webmonkey.example.com" - @knife = Chef::Knife::DataBagFromFile.new - @rest = double("Chef::REST") - @knife.stub(:rest).and_return(@rest) - @stdout = StringIO.new - @knife.ui.stub(:stdout).and_return(@stdout) - @tmp_dir = Dir.mktmpdir - @db_folder = File.join(@tmp_dir, 'data_bags', 'bag_name') - FileUtils.mkdir_p(@db_folder) - @db_file = Tempfile.new(["data_bag_from_file_test", ".json"], @db_folder) - @db_file2 = Tempfile.new(["data_bag_from_file_test2", ".json"], @db_folder) - @db_folder2 = File.join(@tmp_dir, 'data_bags', 'bag_name2') - FileUtils.mkdir_p(@db_folder2) - @db_file3 = Tempfile.new(["data_bag_from_file_test3", ".json"], @db_folder2) - @plain_data = { - "id" => "item_name", - "greeting" => "hello", - "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }} - } - @db_file.write(@plain_data.to_json) - @db_file.flush - @knife.instance_variable_set(:@name_args, ['bag_name', @db_file.path]) + Chef::Config[:node_name] = "webmonkey.example.com" + FileUtils.mkdir_p([db_folder, db_folder2]) + db_file.write(plain_data.to_json) + db_file.flush + knife.name_args = [bag_name, db_file.path] + allow(knife).to receive(:config).and_return(config) end # We have to explicitly clean up Tempfile on Windows because it said so. after :each do - @db_file.close - @db_file2.close - @db_file3.close - FileUtils.rm_rf(@db_folder) - FileUtils.rm_rf(@db_folder2) - FileUtils.remove_entry_secure @tmp_dir + db_file.close + db_file2.close + db_file3.close + FileUtils.rm_rf(db_folder) + FileUtils.rm_rf(db_folder2) + FileUtils.remove_entry_secure tmp_dir end + let(:knife) do + k = Chef::Knife::DataBagFromFile.new + allow(k).to receive(:rest).and_return(rest) + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + let(:tmp_dir) { Dir.mktmpdir } + let(:db_folder) { File.join(tmp_dir, data_bags_path, bag_name) } + let(:db_file) { Tempfile.new(["data_bag_from_file_test", ".json"], db_folder) } + let(:db_file2) { Tempfile.new(["data_bag_from_file_test2", ".json"], db_folder) } + let(:db_folder2) { File.join(tmp_dir, data_bags_path, bag_name2) } + let(:db_file3) { Tempfile.new(["data_bag_from_file_test3", ".json"], db_folder2) } + + def plain_bag(b = bag_name) + data_bag = double("Chef::DataBagItem") + expect(data_bag).to receive(:data_bag).with(b) + expect(data_bag).to receive(:raw_data=).with(plain_data) + expect(data_bag).to receive(:save) + expect(data_bag).to receive(:data_bag) + expect(data_bag).to receive(:id) + data_bag + end + let(:data_bags_path) { "data_bags" } + let(:plain_data) { { + "id" => "item_name", + "greeting" => "hello", + "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }} + } } + + let(:rest) { double("Chef::REST") } + let(:stdout) { StringIO.new } + + let(:bag_name) { "sudoing_admins" } + let(:bag_name2) { "sudoing_admins2" } + let(:item_name) { "ME" } + + let(:secret) { "abc123SECRET" } + + let(:config) { {} } + it "loads from a file and saves" do - @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data) - dbag = Chef::DataBagItem.new - Chef::DataBagItem.stub(:new).and_return(dbag) - dbag.should_receive(:save) - @knife.run - - dbag.data_bag.should == 'bag_name' - dbag.raw_data.should == @plain_data + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).and_return(plain_bag) + + knife.run end - it "loads all from a mutiple files and saves" do - @knife.name_args = [ 'bag_name', @db_file.path, @db_file2.path ] - @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data) - @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data) - dbag = Chef::DataBagItem.new - Chef::DataBagItem.stub(:new).and_return(dbag) - dbag.should_receive(:save).twice - @knife.run - - dbag.data_bag.should == 'bag_name' - dbag.raw_data.should == @plain_data + it "loads all from multiple files and saves" do + knife.name_args = [ bag_name, db_file.path, db_file2.path ] + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).twice.and_return(plain_bag, plain_bag) + + knife.run end it "loads all from a folder and saves" do - @knife.name_args = [ 'bag_name', @db_folder ] - @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data) - @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data) - dbag = Chef::DataBagItem.new - Chef::DataBagItem.stub(:new).and_return(dbag) - dbag.should_receive(:save).twice - @knife.run + knife.name_args = [ bag_name, db_folder ] + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).twice.and_return(plain_bag, plain_bag) + + knife.run end describe "loading all data bags" do before do @pwd = Dir.pwd - Dir.chdir(@tmp_dir) + Dir.chdir(tmp_dir) end after do @@ -106,88 +122,49 @@ describe Chef::Knife::DataBagFromFile do end it "loads all data bags when -a or --all options is provided" do - @knife.name_args = [] - @knife.stub(:config).and_return({:all => true}) - @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file.path)). - and_return(@plain_data) - @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file2.path)). - and_return(@plain_data) - @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)). - and_return(@plain_data) - dbag = Chef::DataBagItem.new - Chef::DataBagItem.stub(:new).and_return(dbag) - dbag.should_receive(:save).exactly(3).times - @knife.run + knife.name_args = [] + config[:all] = true + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data) + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data) + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(plain_bag, plain_bag, plain_bag(bag_name2)) + + knife.run end it "loads all data bags items when -a or --all options is provided" do - @knife.name_args = ["bag_name2"] - @knife.stub(:config).and_return({:all => true}) - @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)). - and_return(@plain_data) - dbag = Chef::DataBagItem.new - Chef::DataBagItem.stub(:new).and_return(dbag) - dbag.should_receive(:save) - @knife.run - dbag.data_bag.should == 'bag_name2' - dbag.raw_data.should == @plain_data + knife.name_args = [bag_name2] + config[:all] = true + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).and_return(plain_bag(bag_name2)) + + knife.run end end describe "encrypted data bag items" do before(:each) do - @secret = "abc123SECRET" - @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, - @secret) - - # Random IV is used each time the data bag item is encrypted, so values - # will not be equal if we re-encrypt. - Chef::EncryptedDataBagItem.should_receive(:encrypt_data_bag_item).and_return(@enc_data) - - @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") - @secret_file.puts(@secret) - @secret_file.flush - end - - after do - @secret_file.close - @secret_file.unlink + expect(knife).to receive(:encryption_secret_provided?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + # TODO this should return encrypted data + expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(plain_data) end it "encrypts values when given --secret" do - @knife.stub(:config).and_return({:secret => @secret}) - - @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", @db_file.path).and_return(@plain_data) - dbag = Chef::DataBagItem.new - Chef::DataBagItem.stub(:new).and_return(dbag) - dbag.should_receive(:save) - @knife.run - dbag.data_bag.should == 'bag_name' - dbag.raw_data.should == @enc_data - end - - it "encrypts values when given --secret_file" do - @knife.stub(:config).and_return({:secret_file => @secret_file.path}) + expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(Chef::DataBagItem).to receive(:new).and_return(plain_bag) - @knife.loader.stub(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data) - dbag = Chef::DataBagItem.new - Chef::DataBagItem.stub(:new).and_return(dbag) - dbag.should_receive(:save) - @knife.run - dbag.data_bag.should == 'bag_name' - dbag.raw_data.should == @enc_data + knife.run end end describe "command line parsing" do it "prints help if given no arguments" do - @knife.instance_variable_set(:@name_args, []) - lambda { @knife.run }.should raise_error(SystemExit) - help_text = "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)" - help_text_regex = Regexp.new("^#{Regexp.escape(help_text)}") - @stdout.string.should match(help_text_regex) + knife.name_args = [bag_name] + expect {knife.run}.to exit_with_code(1) + expect(stdout.string).to start_with("knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)") end end diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb index ac368ed6da..49632a2911 100644 --- a/spec/unit/knife/data_bag_show_spec.rb +++ b/spec/unit/knife/data_bag_show_spec.rb @@ -25,97 +25,99 @@ require 'chef/json_compat' require 'tempfile' describe Chef::Knife::DataBagShow do + before do - Chef::Config[:node_name] = "webmonkey.example.com" - @knife = Chef::Knife::DataBagShow.new - @knife.config[:format] = 'json' - @rest = double("Chef::REST") - allow(@knife).to receive(:rest).and_return(@rest) - @stdout = StringIO.new - allow(@knife.ui).to receive(:stdout).and_return(@stdout) + Chef::Config[:node_name] = "webmonkey.example.com" + knife.name_args = [bag_name, item_name] + allow(knife).to receive(:config).and_return(config) end - - it "prints the ids of the data bag items when given a bag name" do - @knife.instance_variable_set(:@name_args, ['bag_o_data']) - data_bag_contents = { "baz"=>"http://localhost:4000/data/bag_o_data/baz", - "qux"=>"http://localhost:4000/data/bag_o_data/qux"} - expect(Chef::DataBag).to receive(:load).and_return(data_bag_contents) - expected = %q|[ - "baz", - "qux" -]| - @knife.run - expect(@stdout.string.strip).to eq(expected) + let(:knife) do + k = Chef::Knife::DataBagShow.new + allow(k).to receive(:rest).and_return(rest) + allow(k.ui).to receive(:stdout).and_return(stdout) + k end - it "prints the contents of the data bag item when given a bag and item name" do - @knife.instance_variable_set(:@name_args, ['bag_o_data', 'an_item']) - data_item = Chef::DataBagItem.new.tap {|item| item.raw_data = {"id" => "an_item", "zsh" => "victory_through_tabbing"}} + let(:rest) { double("Chef::REST") } + let(:stdout) { StringIO.new } - expect(Chef::DataBagItem).to receive(:load).with('bag_o_data', 'an_item').and_return(data_item) - - @knife.run - expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(data_item.raw_data) - end + let(:bag_name) { "sudoing_admins" } + let(:item_name) { "ME" } - it "should pretty print the data bag contents" do - @knife.instance_variable_set(:@name_args, ['bag_o_data', 'an_item']) - data_item = Chef::DataBagItem.new.tap {|item| item.raw_data = {"id" => "an_item", "zsh" => "victory_through_tabbing"}} + let(:data_bag_contents) { { "id" => "id", "baz"=>"http://localhost:4000/data/bag_o_data/baz", + "qux"=>"http://localhost:4000/data/bag_o_data/qux"} } + let(:enc_hash) {Chef::EncryptedDataBagItem.encrypt_data_bag_item(data_bag_contents, secret)} + let(:data_bag) {Chef::DataBagItem.from_hash(data_bag_contents)} + let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_hash) } + let(:enc_data_bag) { Chef::EncryptedDataBagItem.new(enc_hash, secret) } - expect(Chef::DataBagItem).to receive(:load).with('bag_o_data', 'an_item').and_return(data_item) + let(:secret) { "abc123SECRET" } + # + # let(:raw_hash) {{ "login_name" => "alphaomega", "id" => item_name }} + # + let(:config) { {format: "json"} } - @knife.run - expect(@stdout.string).to eql("{\n \"id\": \"an_item\",\n \"zsh\": \"victory_through_tabbing\"\n}\n") - end + context "Data bag to show is encrypted" do + before do + allow(knife).to receive(:encrypted?).and_return(true) + end - describe "encrypted data bag items" do - before(:each) do - @secret = "abc123SECRET" - @plain_data = { - "id" => "item_name", - "greeting" => "hello", - "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }} - } - @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, - @secret) - @knife.instance_variable_set(:@name_args, ['bag_name', 'item_name']) - - @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") - @secret_file.puts(@secret) - @secret_file.flush + it "decrypts and displays the encrypted data bag when the secret is provided" do + expect(knife).to receive(:encryption_secret_provided?).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) + expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.") + expect(Chef::EncryptedDataBagItem).to receive(:load).with(bag_name, item_name, secret).and_return(enc_data_bag) + + expected = %q|baz: http://localhost:4000/data/bag_o_data/baz +id: id +qux: http://localhost:4000/data/bag_o_data/qux| + knife.run + expect(stdout.string.strip).to eq(expected) end - after do - @secret_file.close - @secret_file.unlink + it "displays the encrypted data bag when the secret is not provided" do + expect(knife).to receive(:encryption_secret_provided?).and_return(false) + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) + expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.") + + knife.run + expect(stdout.string.strip).to include("baz", "qux", "cipher") end + end - it "prints the decrypted contents of an item when given --secret and --encrypted" do - allow(@knife).to receive(:config).and_return({:secret => @secret, :encrypted => true}) - expect(Chef::EncryptedDataBagItem).to receive(:load). - with('bag_name', 'item_name', @secret). - and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret)) - @knife.run - expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(@plain_data) + context "Data bag to show is not encrypted" do + before do + allow(knife).to receive(:encrypted?).and_return(false) + expect(knife).to receive(:read_secret).exactly(0).times end - it "prints the decrypted contents of an item when given --secret_file and --encrypted" do - allow(@knife).to receive(:config).and_return({:secret_file => @secret_file.path, :encrypted => true}) - expect(Chef::EncryptedDataBagItem).to receive(:load). - with('bag_name', 'item_name', @secret). - and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret)) - @knife.run - expect(Chef::JSONCompat.from_json(@stdout.string)).to eq(@plain_data) + it "displays the data bag" do + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag) + expect(knife.ui).to receive(:info).with("Unencrypted data bag detected, ignoring any provided secret options.") + + expected = %q|baz: http://localhost:4000/data/bag_o_data/baz +id: id +qux: http://localhost:4000/data/bag_o_data/qux| + knife.run + expect(stdout.string.strip).to eq(expected) end end - describe "command line parsing" do - it "prints help if given no arguments" do - @knife.instance_variable_set(:@name_args, []) - expect { @knife.run }.to raise_error(SystemExit) - expect(@stdout.string).to match(/^knife data bag show BAG \[ITEM\] \(options\)/) - end + it "displays the list of items in the data bag when only one @name_arg is provided" do + knife.name_args = [bag_name] + expect(Chef::DataBag).to receive(:load).with(bag_name).and_return({}) + + knife.run + expect(stdout.string.strip).to eq("") + end + + it "raises an error when no @name_args are provided" do + knife.name_args = [] + + expect {knife.run}.to exit_with_code(1) + expect(stdout.string).to start_with("knife data bag show BAG [ITEM] (options)") end end -- cgit v1.2.1 From 1852c179a7f84a42b591689eb5076defa90c7431 Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Thu, 11 Sep 2014 07:55:24 -0700 Subject: Refactoring matchers into their own folders/files --- spec/spec_helper.rb | 23 ------------------ spec/support/shared/matchers.rb | 17 ------------- spec/support/shared/matchers/exit_with_code.rb | 28 ++++++++++++++++++++++ .../shared/matchers/match_environment_variable.rb | 17 +++++++++++++ 4 files changed, 45 insertions(+), 40 deletions(-) delete mode 100644 spec/support/shared/matchers.rb create mode 100644 spec/support/shared/matchers/exit_with_code.rb create mode 100644 spec/support/shared/matchers/match_environment_variable.rb diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 50289451af..ed0a8f89f6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -185,26 +185,3 @@ module WEBrick end end end - -# Lifted from http://stackoverflow.com/questions/1480537/how-can-i-validate-exits-and-aborts-in-rspec -RSpec::Matchers.define :exit_with_code do |exp_code| - actual = nil - match do |block| - begin - block.call - rescue SystemExit => e - actual = e.status - end - actual and actual == exp_code - end - failure_message_for_should do |block| - "expected block to call exit(#{exp_code}) but exit" + - (actual.nil? ? " not called" : "(#{actual}) was called") - end - failure_message_for_should_not do |block| - "expected block not to call exit(#{exp_code})" - end - description do - "expect block to call exit(#{exp_code})" - end -end diff --git a/spec/support/shared/matchers.rb b/spec/support/shared/matchers.rb deleted file mode 100644 index 2e1c660c19..0000000000 --- a/spec/support/shared/matchers.rb +++ /dev/null @@ -1,17 +0,0 @@ - -require 'rspec/expectations' -require 'spec/support/platform_helpers' - -RSpec::Matchers.define :match_environment_variable do |varname| - match do |actual| - expected = if windows? && ENV[varname].nil? - # On Windows, if an environment variable is not set, the command - # `echo %VARNAME%` outputs %VARNAME% - "%#{varname}%" - else - ENV[varname].to_s - end - - actual == expected - end -end diff --git a/spec/support/shared/matchers/exit_with_code.rb b/spec/support/shared/matchers/exit_with_code.rb new file mode 100644 index 0000000000..957586c85d --- /dev/null +++ b/spec/support/shared/matchers/exit_with_code.rb @@ -0,0 +1,28 @@ +require 'rspec/expectations' + +# Lifted from http://stackoverflow.com/questions/1480537/how-can-i-validate-exits-and-aborts-in-rspec +RSpec::Matchers.define :exit_with_code do |exp_code| + actual = nil + match do |block| + begin + block.call + rescue SystemExit => e + actual = e.status + end + actual and actual == exp_code + end + + failure_message_for_should do |block| + "expected block to call exit(#{exp_code}) but exit" + + (actual.nil? ? " not called" : "(#{actual}) was called") + end + + failure_message_for_should_not do |block| + "expected block not to call exit(#{exp_code})" + end + + description do + "expect block to call exit(#{exp_code})" + end + +end diff --git a/spec/support/shared/matchers/match_environment_variable.rb b/spec/support/shared/matchers/match_environment_variable.rb new file mode 100644 index 0000000000..c8c905f44a --- /dev/null +++ b/spec/support/shared/matchers/match_environment_variable.rb @@ -0,0 +1,17 @@ + +require 'rspec/expectations' +require 'spec/support/platform_helpers' + +RSpec::Matchers.define :match_environment_variable do |varname| + match do |actual| + expected = if windows? && ENV[varname].nil? + # On Windows, if an environment variable is not set, the command + # `echo %VARNAME%` outputs %VARNAME% + "%#{varname}%" + else + ENV[varname].to_s + end + + actual == expected + end +end -- cgit v1.2.1 From 71f7c6e463220cf492d5ac38c2cfbeb96defbeba Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Thu, 11 Sep 2014 08:48:52 -0700 Subject: Addressing review comments - better naming, comments, removed some TODOs --- lib/chef/knife/data_bag_common.rb | 139 ----------------------- lib/chef/knife/data_bag_create.rb | 4 +- lib/chef/knife/data_bag_edit.rb | 3 +- lib/chef/knife/data_bag_from_file.rb | 2 + lib/chef/knife/data_bag_secret_options.rb | 139 +++++++++++++++++++++++ lib/chef/knife/data_bag_show.rb | 3 +- spec/unit/knife/data_bag_common_spec.rb | 141 ------------------------ spec/unit/knife/data_bag_secret_options_spec.rb | 138 +++++++++++++++++++++++ 8 files changed, 285 insertions(+), 284 deletions(-) delete mode 100644 lib/chef/knife/data_bag_common.rb create mode 100644 lib/chef/knife/data_bag_secret_options.rb delete mode 100644 spec/unit/knife/data_bag_common_spec.rb create mode 100644 spec/unit/knife/data_bag_secret_options_spec.rb diff --git a/lib/chef/knife/data_bag_common.rb b/lib/chef/knife/data_bag_common.rb deleted file mode 100644 index 93f8c98624..0000000000 --- a/lib/chef/knife/data_bag_common.rb +++ /dev/null @@ -1,139 +0,0 @@ -# -# Author:: Tyler Ball () -# Copyright:: Copyright (c) 2014 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. -# - -require 'mixlib/cli' -require 'chef/config' -require 'chef/encrypted_data_bag_item' - -class Chef - class Knife - module DataBagCommon - include Mixlib::CLI - - # TODO when https://github.com/opscode/mixlib-cli/pull/13 is fixed, we can make this a base class - # instead of a module - def self.included(base) - base.option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values", - :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } - - base.option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values", - :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } - - base.option :encrypt, - :long => "--encrypt", - :description => "Only use the secret configured in knife.rb when this is true", - :boolean => true, - :default => false - end - - ## - # Determine if the user has specified an appropriate secret for encrypting data bag items. - # @returns boolean - def encryption_secret_provided? - validate_secrets - - return true if config[:secret] || config[:secret_file] - - if config[:encrypt] - unless has_secret? || has_secret_file? - ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") - exit(1) - else - return true - end - end - return false - end - - def read_secret - if config[:secret] - config[:secret] - elsif config[:secret_file] - Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) - elsif secret = knife_config[:secret] || Chef::Config[:secret] - secret - else - secret_file = knife_config[:secret_file] || Chef::Config[:secret_file] - Chef::EncryptedDataBagItem.load_secret(secret_file) - end - end - - def validate_secrets - if config[:secret] && config[:secret_file] - ui.fatal("Please specify only one of --secret, --secret-file") - exit(1) - end - - # TODO is there validation on the knife.rb schema? If so, this validation should go there - if has_secret? && has_secret_file? - ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config") - exit(1) - end - end - - def has_secret? - knife_config[:secret] || Chef::Config[:secret] - end - - def has_secret_file? - knife_config[:secret_file] || Chef::Config[:secret_file] - end - - # TODO duplicated from data_query.rb, also needs test coverage when it is extracted - # Tries to autodetect if the item's raw hash appears to be encrypted. - def encrypted?(raw_data) - data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key. - # Assume hashes containing only the "id" key are not encrypted. - # Otherwise, remove the keys that don't appear to be encrypted and compare - # the result with the hash. If some entry has been removed, then some entry - # doesn't appear to be encrypted and we assume the entire hash is not encrypted. - data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data - end - - private - - def knife_config - Chef::Config.key?(:knife) ? Chef::Config[:knife] : {} - end - - # Checks if data looks like it has been encrypted by - # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns - # true only when there is an exact match between the VersionXEncryptor - # keys and the hash's keys. - def looks_like_encrypted?(data) - return false unless data.is_a?(Hash) && data.has_key?("version") - case data["version"] - when 1 - Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort - when 2 - Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort - when 3 - Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort - else - false # version means something else... assume not encrypted. - end - end - - end - end -end diff --git a/lib/chef/knife/data_bag_create.rb b/lib/chef/knife/data_bag_create.rb index 40921d7d09..f8a7619a8a 100644 --- a/lib/chef/knife/data_bag_create.rb +++ b/lib/chef/knife/data_bag_create.rb @@ -18,13 +18,13 @@ # require 'chef/knife' +require 'chef/knife/data_bag_secret_options' class Chef class Knife class DataBagCreate < Knife - include DataBagCommon + include DataBagSecretOptions - # TODO duplicating deps here and in the DataBagCommon module deps do require 'chef/data_bag' require 'chef/encrypted_data_bag_item' diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb index 718aeedc29..55f09f55c9 100644 --- a/lib/chef/knife/data_bag_edit.rb +++ b/lib/chef/knife/data_bag_edit.rb @@ -18,11 +18,12 @@ # require 'chef/knife' +require 'chef/knife/data_bag_secret_options' class Chef class Knife class DataBagEdit < Knife - include DataBagCommon + include DataBagSecretOptions deps do require 'chef/data_bag_item' diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb index daaede706d..598a935160 100644 --- a/lib/chef/knife/data_bag_from_file.rb +++ b/lib/chef/knife/data_bag_from_file.rb @@ -19,11 +19,13 @@ require 'chef/knife' require 'chef/util/path_helper' +require 'chef/knife/data_bag_secret_options' class Chef class Knife class DataBagFromFile < Knife include DataBagCommon + include DataBagSecretOptions deps do require 'chef/data_bag' diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb new file mode 100644 index 0000000000..8b9c947bac --- /dev/null +++ b/lib/chef/knife/data_bag_secret_options.rb @@ -0,0 +1,139 @@ +# +# Author:: Tyler Ball () +# Copyright:: Copyright (c) 2014 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. +# + +require 'mixlib/cli' +require 'chef/config' + +class Chef + class Knife + module DataBagSecretOptions + include Mixlib::CLI + + def self.included(base) + base.option :secret, + :short => "-s SECRET", + :long => "--secret ", + :description => "The secret key to use to encrypt data bag item values. Can also be defaulted in your knife.rb with the key 'secret'", + :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } + + base.option :secret_file, + :long => "--secret-file SECRET_FILE", + :description => "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your knife.rb with the key 'secret_file'", + :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } + + base.option :encrypt, + :long => "--encrypt", + :description => "If 'secret' or 'secret_file' is present in your knife.rb, then encrypt data bags using it", + :boolean => true, + :default => false + end + + ## + # Determine if the user has specified an appropriate secret for encrypting data bag items. + # @returns boolean + def encryption_secret_provided? + validate_secrets + + return true if config[:secret] || config[:secret_file] + + if config[:encrypt] + unless has_secret? || has_secret_file? + ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") + exit(1) + else + return true + end + end + return false + end + + def read_secret + # Moving the non 'compile-time' requires into here to speed up knife command loading + # IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item' + require 'chef/encrypted_data_bag_item' + + if config[:secret] + config[:secret] + elsif config[:secret_file] + Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) + elsif secret = knife_config[:secret] || Chef::Config[:secret] + secret + else + secret_file = knife_config[:secret_file] || Chef::Config[:secret_file] + Chef::EncryptedDataBagItem.load_secret(secret_file) + end + end + + def validate_secrets + if config[:secret] && config[:secret_file] + ui.fatal("Please specify only one of --secret, --secret-file") + exit(1) + end + + if has_secret? && has_secret_file? + ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config") + exit(1) + end + end + + def has_secret? + knife_config[:secret] || Chef::Config[:secret] + end + + def has_secret_file? + knife_config[:secret_file] || Chef::Config[:secret_file] + end + + # TODO duplicated from data_query.rb, also needs test coverage when it is extracted + # Tries to autodetect if the item's raw hash appears to be encrypted. + def encrypted?(raw_data) + data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key. + # Assume hashes containing only the "id" key are not encrypted. + # Otherwise, remove the keys that don't appear to be encrypted and compare + # the result with the hash. If some entry has been removed, then some entry + # doesn't appear to be encrypted and we assume the entire hash is not encrypted. + data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data + end + + private + + def knife_config + Chef::Config.key?(:knife) ? Chef::Config[:knife] : {} + end + + # Checks if data looks like it has been encrypted by + # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns + # true only when there is an exact match between the VersionXEncryptor + # keys and the hash's keys. + def looks_like_encrypted?(data) + return false unless data.is_a?(Hash) && data.has_key?("version") + case data["version"] + when 1 + Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort + when 2 + Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort + when 3 + Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort + else + false # version means something else... assume not encrypted. + end + end + + end + end +end diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index 2a759d1b43..05f831a62d 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -18,11 +18,12 @@ # require 'chef/knife' +require 'chef/knife/data_bag_secret_options' class Chef class Knife class DataBagShow < Knife - include DataBagCommon + include DataBagSecretOptions deps do require 'chef/data_bag' diff --git a/spec/unit/knife/data_bag_common_spec.rb b/spec/unit/knife/data_bag_common_spec.rb deleted file mode 100644 index 3e4ebf5026..0000000000 --- a/spec/unit/knife/data_bag_common_spec.rb +++ /dev/null @@ -1,141 +0,0 @@ -# -# Author:: Tyler Ball () -# Copyright:: Copyright (c) 2009-2014 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. -# - -require 'spec_helper' -require 'chef/knife' -require 'chef/config' -require 'tempfile' - -class ExampleDataBag < Chef::Knife - include Chef::Knife::DataBagCommon - - #banner "you must provide a banner" - #category "data bag" -end - -describe Chef::Knife::DataBagCommon do - let(:example_db) do - k = ExampleDataBag.new - allow(k.ui).to receive(:stdout).and_return(stdout) - k - end - - let(:stdout) { StringIO.new } - - let(:secret) { "abc123SECRET" } - let(:secret_file) do - sfile = Tempfile.new("encrypted_data_bag_secret") - sfile.puts(secret) - sfile.flush - sfile - end - - after do - secret_file.close - secret_file.unlink - end - - describe "#validate_secrets" do - - it "throws an error when provided with both --secret and --secret-file on the CL" do - expect(example_db).to receive(:config).exactly(2).times.and_return({ :secret_file => secret_file.path, :secret => secret }) - expect(example_db).to receive(:exit).with(1) - expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file") - - example_db.validate_secrets - end - - it "throws an error when provided with `secret` and `secret_file` in knife.rb" do - Chef::Config[:knife][:secret_file] = secret_file.path - Chef::Config[:knife][:secret] = secret - expect(example_db).to receive(:exit).with(1) - expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config") - - example_db.validate_secrets - end - - end - - describe "#read_secret" do - - it "returns the secret first" do - expect(example_db).to receive(:config).exactly(2).times.and_return({ :secret_file => secret_file.path, :secret => secret }) - expect(example_db.read_secret).to eq(secret) - end - - it "returns the secret_file only if secret does not exist" do - expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret_file => secret_file.path }) - expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") - expect(example_db.read_secret).to eq("secret file contents") - end - - it "returns the secret from the knife.rb config" do - expect(example_db).to receive(:config).exactly(2).times.and_return({}) - Chef::Config[:knife][:secret_file] = secret_file.path - Chef::Config[:knife][:secret] = secret - expect(example_db.read_secret).to eq(secret) - end - - it "returns the secret_file from the knife.rb config only if the secret does not exist" do - expect(example_db).to receive(:config).exactly(2).times.and_return({}) - Chef::Config[:knife][:secret_file] = secret_file.path - expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") - expect(example_db.read_secret).to eq("secret file contents") - end - - end - - describe "#encryption_secret_provided?" do - - it "returns true if the secret is passed on the CL" do - expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret => secret }) - expect(example_db.encryption_secret_provided?).to eq(true) - end - - it "returns true if the secret_file is passed on the CL" do - expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret_file => secret_file.path }) - expect(example_db.encryption_secret_provided?).to eq(true) - end - - it "returns true if --encrypt is passed on the CL and :secret is in knife.rb" do - expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) - Chef::Config[:knife][:secret] = secret - expect(example_db.encryption_secret_provided?).to eq(true) - end - - it "returns true if --encrypt is passed on the CL and :secret_file is in knife.rb" do - expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) - Chef::Config[:knife][:secret_file] = secret_file.path - expect(example_db.encryption_secret_provided?).to eq(true) - end - - it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the knife.rb" do - expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) - expect(example_db).to receive(:exit).with(1) - expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.") - example_db.encryption_secret_provided? - end - - it "returns false if no secret is passed" do - expect(example_db).to receive(:config).exactly(4).times.and_return({}) - expect(example_db.encryption_secret_provided?).to eq(false) - end - - end - -end diff --git a/spec/unit/knife/data_bag_secret_options_spec.rb b/spec/unit/knife/data_bag_secret_options_spec.rb new file mode 100644 index 0000000000..98e876a5db --- /dev/null +++ b/spec/unit/knife/data_bag_secret_options_spec.rb @@ -0,0 +1,138 @@ +# +# Author:: Tyler Ball () +# Copyright:: Copyright (c) 2009-2014 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. +# + +require 'spec_helper' +require 'chef/knife' +require 'chef/config' +require 'tempfile' + +class ExampleDataBagCommand < Chef::Knife + include Chef::Knife::DataBagSecretOptions +end + +describe Chef::Knife::DataBagSecretOptions do + let(:example_db) do + k = ExampleDataBagCommand.new + allow(k.ui).to receive(:stdout).and_return(stdout) + k + end + + let(:stdout) { StringIO.new } + + let(:secret) { "abc123SECRET" } + let(:secret_file) do + sfile = Tempfile.new("encrypted_data_bag_secret") + sfile.puts(secret) + sfile.flush + sfile + end + + after do + secret_file.close + secret_file.unlink + end + + describe "#validate_secrets" do + + it "throws an error when provided with both --secret and --secret-file on the CL" do + expect(example_db).to receive(:config).exactly(2).times.and_return({ :secret_file => secret_file.path, :secret => secret }) + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file") + + example_db.validate_secrets + end + + it "throws an error when provided with `secret` and `secret_file` in knife.rb" do + Chef::Config[:knife][:secret_file] = secret_file.path + Chef::Config[:knife][:secret] = secret + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config") + + example_db.validate_secrets + end + + end + + describe "#read_secret" do + + it "returns the secret first" do + expect(example_db).to receive(:config).exactly(2).times.and_return({ :secret_file => secret_file.path, :secret => secret }) + expect(example_db.read_secret).to eq(secret) + end + + it "returns the secret_file only if secret does not exist" do + expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret_file => secret_file.path }) + expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") + expect(example_db.read_secret).to eq("secret file contents") + end + + it "returns the secret from the knife.rb config" do + expect(example_db).to receive(:config).exactly(2).times.and_return({}) + Chef::Config[:knife][:secret_file] = secret_file.path + Chef::Config[:knife][:secret] = secret + expect(example_db.read_secret).to eq(secret) + end + + it "returns the secret_file from the knife.rb config only if the secret does not exist" do + expect(example_db).to receive(:config).exactly(2).times.and_return({}) + Chef::Config[:knife][:secret_file] = secret_file.path + expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") + expect(example_db.read_secret).to eq("secret file contents") + end + + end + + describe "#encryption_secret_provided?" do + + it "returns true if the secret is passed on the CL" do + expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret => secret }) + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if the secret_file is passed on the CL" do + expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret_file => secret_file.path }) + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if --encrypt is passed on the CL and :secret is in knife.rb" do + expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + Chef::Config[:knife][:secret] = secret + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "returns true if --encrypt is passed on the CL and :secret_file is in knife.rb" do + expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + Chef::Config[:knife][:secret_file] = secret_file.path + expect(example_db.encryption_secret_provided?).to eq(true) + end + + it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the knife.rb" do + expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + expect(example_db).to receive(:exit).with(1) + expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.") + example_db.encryption_secret_provided? + end + + it "returns false if no secret is passed" do + expect(example_db).to receive(:config).exactly(4).times.and_return({}) + expect(example_db.encryption_secret_provided?).to eq(false) + end + + end + +end -- cgit v1.2.1 From d24ec65aced97b96188ffcc439f4f3cccdd41443 Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Thu, 11 Sep 2014 14:55:55 -0700 Subject: Refactoring duplicated code into a separate module. Also making CLI options more informative --- lib/chef/dsl/data_query.rb | 31 +----- .../encrypted_data_bag_item/check_encrypted.rb | 56 ++++++++++ lib/chef/knife/data_bag_secret_options.rb | 37 +------ spec/unit/dsl/data_query_spec.rb | 118 ++------------------- .../check_encrypted_spec.rb | 95 +++++++++++++++++ 5 files changed, 166 insertions(+), 171 deletions(-) create mode 100644 lib/chef/encrypted_data_bag_item/check_encrypted.rb create mode 100644 spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb diff --git a/lib/chef/dsl/data_query.rb b/lib/chef/dsl/data_query.rb index 3dafbca6bf..e36784271a 100644 --- a/lib/chef/dsl/data_query.rb +++ b/lib/chef/dsl/data_query.rb @@ -20,6 +20,7 @@ require 'chef/search/query' require 'chef/data_bag' require 'chef/data_bag_item' require 'chef/encrypted_data_bag_item' +require 'chef/encrypted_data_bag_item/check_encrypted' class Chef module DSL @@ -28,6 +29,7 @@ class Chef # Provides DSL for querying data from the chef-server via search or data # bag. module DataQuery + include Chef::EncryptedDataBagItem::CheckEncrypted def search(*args, &block) # If you pass a block, or have at least the start argument, do raw result parsing @@ -78,35 +80,6 @@ class Chef raise end - private - - # Tries to autodetect if the item's raw hash appears to be encrypted. - def encrypted?(raw_data) - data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key. - # Assume hashes containing only the "id" key are not encrypted. - # Otherwise, remove the keys that don't appear to be encrypted and compare - # the result with the hash. If some entry has been removed, then some entry - # doesn't appear to be encrypted and we assume the entire hash is not encrypted. - data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data - end - - # Checks if data looks like it has been encrypted by - # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns - # true only when there is an exact match between the VersionXEncryptor - # keys and the hash's keys. - def looks_like_encrypted?(data) - return false unless data.is_a?(Hash) && data.has_key?("version") - case data["version"] - when 1 - Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort - when 2 - Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort - when 3 - Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort - else - false # version means something else... assume not encrypted. - end - end end end end diff --git a/lib/chef/encrypted_data_bag_item/check_encrypted.rb b/lib/chef/encrypted_data_bag_item/check_encrypted.rb new file mode 100644 index 0000000000..b7cb5841b3 --- /dev/null +++ b/lib/chef/encrypted_data_bag_item/check_encrypted.rb @@ -0,0 +1,56 @@ +# +# Author:: Tyler Ball () +# Copyright:: Copyright (c) 2010-2014 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. +# + +require 'chef/encrypted_data_bag_item/encryptor' + +class Chef::EncryptedDataBagItem + # Common code for checking if a data bag appears encrypted + module CheckEncrypted + + # Tries to autodetect if the item's raw hash appears to be encrypted. + def encrypted?(raw_data) + data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key. + # Assume hashes containing only the "id" key are not encrypted. + # Otherwise, remove the keys that don't appear to be encrypted and compare + # the result with the hash. If some entry has been removed, then some entry + # doesn't appear to be encrypted and we assume the entire hash is not encrypted. + data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data + end + + private + + # Checks if data looks like it has been encrypted by + # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns + # true only when there is an exact match between the VersionXEncryptor + # keys and the hash's keys. + def looks_like_encrypted?(data) + return false unless data.is_a?(Hash) && data.has_key?("version") + case data["version"] + when 1 + Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort + when 2 + Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort + when 3 + Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort + else + false # version means something else... assume not encrypted. + end + end + + end +end diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb index 8b9c947bac..b692fc767c 100644 --- a/lib/chef/knife/data_bag_secret_options.rb +++ b/lib/chef/knife/data_bag_secret_options.rb @@ -18,27 +18,29 @@ require 'mixlib/cli' require 'chef/config' +require 'chef/encrypted_data_bag_item/check_encrypted' class Chef class Knife module DataBagSecretOptions include Mixlib::CLI + include Chef::EncryptedDataBagItem::CheckEncrypted def self.included(base) base.option :secret, :short => "-s SECRET", :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values. Can also be defaulted in your knife.rb with the key 'secret'", + :description => "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'", :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } base.option :secret_file, :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your knife.rb with the key 'secret_file'", + :description => "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'", :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } base.option :encrypt, :long => "--encrypt", - :description => "If 'secret' or 'secret_file' is present in your knife.rb, then encrypt data bags using it", + :description => "If 'secret' or 'secret_file' is present in your config, then encrypt data bags using it", :boolean => true, :default => false end @@ -99,41 +101,12 @@ class Chef knife_config[:secret_file] || Chef::Config[:secret_file] end - # TODO duplicated from data_query.rb, also needs test coverage when it is extracted - # Tries to autodetect if the item's raw hash appears to be encrypted. - def encrypted?(raw_data) - data = raw_data.reject { |k, _| k == "id" } # Remove the "id" key. - # Assume hashes containing only the "id" key are not encrypted. - # Otherwise, remove the keys that don't appear to be encrypted and compare - # the result with the hash. If some entry has been removed, then some entry - # doesn't appear to be encrypted and we assume the entire hash is not encrypted. - data.empty? ? false : data.reject { |_, v| !looks_like_encrypted?(v) } == data - end - private def knife_config Chef::Config.key?(:knife) ? Chef::Config[:knife] : {} end - # Checks if data looks like it has been encrypted by - # Chef::EncryptedDataBagItem::Encryptor::VersionXEncryptor. Returns - # true only when there is an exact match between the VersionXEncryptor - # keys and the hash's keys. - def looks_like_encrypted?(data) - return false unless data.is_a?(Hash) && data.has_key?("version") - case data["version"] - when 1 - Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor.encryptor_keys.sort == data.keys.sort - when 2 - Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor.encryptor_keys.sort == data.keys.sort - when 3 - Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor.encryptor_keys.sort == data.keys.sort - else - false # version means something else... assume not encrypted. - end - end - end end end diff --git a/spec/unit/dsl/data_query_spec.rb b/spec/unit/dsl/data_query_spec.rb index 8a985437b7..78cd5569e8 100644 --- a/spec/unit/dsl/data_query_spec.rb +++ b/spec/unit/dsl/data_query_spec.rb @@ -86,123 +86,21 @@ describe Chef::DSL::DataQuery do end context "when the item is encrypted" do - let(:default_secret) { "abc123SECRET" } - - let(:encoded_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret) } - - let(:item) do - item = Chef::DataBagItem.new - item.data_bag(bag_name) - item.raw_data = encoded_data - item - end + let(:secret) { "abc123SECRET" } + let(:enc_data_bag) { double("Chef::EncryptedDataBagItem") } before do allow( Chef::DataBagItem ).to receive(:load).with(bag_name, item_name).and_return(item) + expect(language).to receive(:encrypted?).and_return(true) + expect( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_return(secret) end - shared_examples_for "encryption detected" do - let(:encoded_data) do - Chef::Config[:data_bag_encrypt_version] = version - Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret) - end - - before do - allow( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_return(default_secret) - end - - it "detects encrypted data bag" do - expect( encryptor ).to receive(:encryptor_keys).at_least(:once).and_call_original - expect( Chef::Log ).to receive(:debug).with(/Data bag item looks encrypted/) - language.data_bag_item(bag_name, item_name) - end - end - - context "when encryption version is 1" do - include_examples "encryption detected" do - let(:version) { 1 } - let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor } - end - end - - context "when encryption version is 2" do - include_examples "encryption detected" do - let(:version) { 2 } - let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor } - end + it "detects encrypted data bag" do + expect( Chef::EncryptedDataBagItem ).to receive(:new).with(raw_data, secret).and_return(enc_data_bag) + expect( Chef::Log ).to receive(:debug).with(/Data bag item looks encrypted/) + expect(language.data_bag_item(bag_name, item_name)).to eq(enc_data_bag) end - context "when encryption version is 3", :ruby_20_only do - include_examples "encryption detected" do - let(:version) { 3 } - let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor } - end - end - - shared_examples_for "an encrypted data bag item" do - it "returns an encrypted data bag item" do - expect( language.data_bag_item(bag_name, item_name, secret) ).to be_a_kind_of(Chef::EncryptedDataBagItem) - end - - it "decrypts the contents of the data bag item" do - expect( language.data_bag_item(bag_name, item_name, secret).to_hash ).to eql raw_data - end - end - - context "when a secret is supplied" do - include_examples "an encrypted data bag item" do - let(:secret) { default_secret } - end - end - - context "when a secret is not supplied" do - before do - allow( Chef::Config ).to receive(:[]).and_call_original - expect( Chef::Config ).to receive(:[]).with(:encrypted_data_bag_secret).and_return(path) - expect( Chef::EncryptedDataBagItem ).to receive(:load_secret).and_call_original - end - - context "when a secret is located at Chef::Config[:encrypted_data_bag_secret]" do - let(:path) { "/tmp/my_secret" } - - before do - expect( File ).to receive(:exist?).with(path).and_return(true) - expect( IO ).to receive(:read).with(path).and_return(default_secret) - end - - include_examples "an encrypted data bag item" do - let(:secret) { nil } - end - end - - shared_examples_for "no secret file" do - it "should fail to load the data bag item" do - expect( Chef::Log ).to receive(:error).with(/Failed to load secret for encrypted data bag item/) - expect( Chef::Log ).to receive(:error).with(/Failed to load data bag item/) - expect{ language.data_bag_item(bag_name, item_name) }.to raise_error(error_type, error_message) - end - end - - context "when Chef::Config[:encrypted_data_bag_secret] is not configured" do - include_examples "no secret file" do - let(:path) { nil } - let(:error_type) { ArgumentError } - let(:error_message) { /No secret specified and no secret found/ } - end - end - - context "when Chef::Config[:encrypted_data_bag_secret] does not exist" do - include_examples "no secret file" do - before do - expect( File ).to receive(:exist?).with(path).and_return(false) - end - - let(:path) { "/tmp/my_secret" } - let(:error_type) { Errno::ENOENT } - let(:error_message) { /file not found/ } - end - end - end end end end diff --git a/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb b/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb new file mode 100644 index 0000000000..1da5efb36e --- /dev/null +++ b/spec/unit/encrypted_data_bag_item/check_encrypted_spec.rb @@ -0,0 +1,95 @@ +# +# Author:: Tyler Ball () +# Copyright:: Copyright (c) 2010-2014 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. +# + +require 'spec_helper' +require 'chef/encrypted_data_bag_item/check_encrypted' + +class CheckEncryptedTester + include Chef::EncryptedDataBagItem::CheckEncrypted +end + +describe Chef::EncryptedDataBagItem::CheckEncrypted do + + let(:tester) { CheckEncryptedTester.new } + + it "detects the item is not encrypted when the data is empty" do + expect(tester.encrypted?({})).to eq(false) + end + + it "detects the item is not encrypted when the data only contains an id" do + expect(tester.encrypted?({id: "foo"})).to eq(false) + end + + context "when the item is encrypted" do + + let(:default_secret) { "abc123SECRET" } + let(:item_name) { "item_name" } + let(:raw_data) {{ + "id" => item_name, + "greeting" => "hello", + "nested" => { + "a1" => [1, 2, 3], + "a2" => { "b1" => true } + } + }} + + let(:version) { 1 } + let(:encoded_data) do + Chef::Config[:data_bag_encrypt_version] = version + Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_data, default_secret) + end + + it "does not detect encryption when the item version is unknown" do + # It shouldn't be possible for someone to normally encrypt an item with an unknown version - they would have to + # do something funky like encrypting it and then manually changing the version + modified_encoded_data = encoded_data + modified_encoded_data["greeting"]["version"] = 4 + expect(tester.encrypted?(modified_encoded_data)).to eq(false) + end + + shared_examples_for "encryption detected" do + it "detects encrypted data bag" do + expect( encryptor ).to receive(:encryptor_keys).at_least(:once).and_call_original + expect(tester.encrypted?(encoded_data)).to eq(true) + end + end + + context "when encryption version is 1" do + include_examples "encryption detected" do + let(:version) { 1 } + let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version1Encryptor } + end + end + + context "when encryption version is 2" do + include_examples "encryption detected" do + let(:version) { 2 } + let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version2Encryptor } + end + end + + context "when encryption version is 3", :ruby_20_only do + include_examples "encryption detected" do + let(:version) { 3 } + let(:encryptor) { Chef::EncryptedDataBagItem::Encryptor::Version3Encryptor } + end + end + + end + +end -- cgit v1.2.1 From 22889f0fd605a1404ad1c46562254e19bd0f7ede Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Fri, 12 Sep 2014 14:48:48 -0700 Subject: Trying to fix intermittent test failure on Travis --- spec/unit/knife/data_bag_from_file_spec.rb | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb index 150ad42148..6a55c0e0d2 100644 --- a/spec/unit/knife/data_bag_from_file_spec.rb +++ b/spec/unit/knife/data_bag_from_file_spec.rb @@ -58,21 +58,23 @@ describe Chef::Knife::DataBagFromFile do let(:db_folder2) { File.join(tmp_dir, data_bags_path, bag_name2) } let(:db_file3) { Tempfile.new(["data_bag_from_file_test3", ".json"], db_folder2) } - def plain_bag(b = bag_name) - data_bag = double("Chef::DataBagItem") + def new_bag_expects(b = bag_name, d = plain_data) + data_bag = double expect(data_bag).to receive(:data_bag).with(b) - expect(data_bag).to receive(:raw_data=).with(plain_data) + expect(data_bag).to receive(:raw_data=).with(d) expect(data_bag).to receive(:save) expect(data_bag).to receive(:data_bag) expect(data_bag).to receive(:id) data_bag end + let(:data_bags_path) { "data_bags" } let(:plain_data) { { "id" => "item_name", "greeting" => "hello", "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }} } } + let(:enc_data) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_data, secret) } let(:rest) { double("Chef::REST") } let(:stdout) { StringIO.new } @@ -87,7 +89,7 @@ describe Chef::Knife::DataBagFromFile do it "loads from a file and saves" do expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) - expect(Chef::DataBagItem).to receive(:new).and_return(plain_bag) + expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects) knife.run end @@ -96,7 +98,7 @@ describe Chef::Knife::DataBagFromFile do knife.name_args = [ bag_name, db_file.path, db_file2.path ] expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) - expect(Chef::DataBagItem).to receive(:new).twice.and_return(plain_bag, plain_bag) + expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects) knife.run end @@ -105,7 +107,7 @@ describe Chef::Knife::DataBagFromFile do knife.name_args = [ bag_name, db_folder ] expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) - expect(Chef::DataBagItem).to receive(:new).twice.and_return(plain_bag, plain_bag) + expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects) knife.run end @@ -127,7 +129,7 @@ describe Chef::Knife::DataBagFromFile do expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data) expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data) expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) - expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(plain_bag, plain_bag, plain_bag(bag_name2)) + expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(new_bag_expects, new_bag_expects, new_bag_expects(bag_name2)) knife.run end @@ -136,7 +138,7 @@ describe Chef::Knife::DataBagFromFile do knife.name_args = [bag_name2] config[:all] = true expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) - expect(Chef::DataBagItem).to receive(:new).and_return(plain_bag(bag_name2)) + expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name2)) knife.run end @@ -147,13 +149,12 @@ describe Chef::Knife::DataBagFromFile do before(:each) do expect(knife).to receive(:encryption_secret_provided?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) - # TODO this should return encrypted data - expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(plain_data) + expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(plain_data, secret).and_return(enc_data) end it "encrypts values when given --secret" do expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) - expect(Chef::DataBagItem).to receive(:new).and_return(plain_bag) + expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name, enc_data)) knife.run end -- cgit v1.2.1 From e121b47679cb38fe8a1571405d9f8f8969266122 Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Mon, 15 Sep 2014 09:46:09 -0700 Subject: Unsorted file listings were causing intermittent test failures on different boxes - mocked out the file lister so the results are consistent now --- spec/unit/knife/data_bag_from_file_spec.rb | 39 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb index 6a55c0e0d2..662af3f0d6 100644 --- a/spec/unit/knife/data_bag_from_file_spec.rb +++ b/spec/unit/knife/data_bag_from_file_spec.rb @@ -30,8 +30,8 @@ describe Chef::Knife::DataBagFromFile do FileUtils.mkdir_p([db_folder, db_folder2]) db_file.write(plain_data.to_json) db_file.flush - knife.name_args = [bag_name, db_file.path] allow(knife).to receive(:config).and_return(config) + allow(Chef::Knife::Core::ObjectLoader).to receive(:new).and_return(loader) end # We have to explicitly clean up Tempfile on Windows because it said so. @@ -68,6 +68,8 @@ describe Chef::Knife::DataBagFromFile do data_bag end + let(:loader) { double("Knife::Core::ObjectLoader") } + let(:data_bags_path) { "data_bags" } let(:plain_data) { { "id" => "item_name", @@ -88,7 +90,8 @@ describe Chef::Knife::DataBagFromFile do let(:config) { {} } it "loads from a file and saves" do - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + knife.name_args = [bag_name, db_file.path] + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects) knife.run @@ -96,8 +99,8 @@ describe Chef::Knife::DataBagFromFile do it "loads all from multiple files and saves" do knife.name_args = [ bag_name, db_file.path, db_file2.path ] - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects) knife.run @@ -105,8 +108,8 @@ describe Chef::Knife::DataBagFromFile do it "loads all from a folder and saves" do knife.name_args = [ bag_name, db_folder ] - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file2.path).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).twice.and_return(new_bag_expects, new_bag_expects) knife.run @@ -114,21 +117,15 @@ describe Chef::Knife::DataBagFromFile do describe "loading all data bags" do - before do - @pwd = Dir.pwd - Dir.chdir(tmp_dir) - end - - after do - Dir.chdir(@pwd) - end - it "loads all data bags when -a or --all options is provided" do knife.name_args = [] config[:all] = true - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data) - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data) - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) + expect(loader).to receive(:find_all_object_dirs).with("./#{data_bags_path}").and_return([bag_name, bag_name2]) + expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name}").and_return([File.basename(db_file.path), File.basename(db_file2.path)]) + expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)]) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file.path)).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, File.basename(db_file2.path)).and_return(plain_data) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).exactly(3).times.and_return(new_bag_expects, new_bag_expects, new_bag_expects(bag_name2)) knife.run @@ -137,7 +134,8 @@ describe Chef::Knife::DataBagFromFile do it "loads all data bags items when -a or --all options is provided" do knife.name_args = [bag_name2] config[:all] = true - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) + expect(loader).to receive(:find_all_objects).with("./#{data_bags_path}/#{bag_name2}").and_return([File.basename(db_file3.path)]) + expect(loader).to receive(:load_from).with(data_bags_path, bag_name2, File.basename(db_file3.path)).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name2)) knife.run @@ -153,7 +151,8 @@ describe Chef::Knife::DataBagFromFile do end it "encrypts values when given --secret" do - expect(knife.loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) + knife.name_args = [bag_name, db_file.path] + expect(loader).to receive(:load_from).with(data_bags_path, bag_name, db_file.path).and_return(plain_data) expect(Chef::DataBagItem).to receive(:new).and_return(new_bag_expects(bag_name, enc_data)) knife.run -- cgit v1.2.1 From 32310137000ca0983f3952eb3febb08c93c84bdc Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Tue, 16 Sep 2014 09:37:17 -0700 Subject: Finishing UX spec - storing CL options in different config values so we can correctly validate, updating bootstrap to use shared module --- lib/chef/knife/bootstrap.rb | 16 ++----- lib/chef/knife/core/bootstrap_context.rb | 13 ++--- lib/chef/knife/data_bag_secret_options.rb | 64 +++++++++++++++++-------- lib/chef/knife/data_bag_show.rb | 3 +- spec/unit/knife/bootstrap_spec.rb | 64 ++++--------------------- spec/unit/knife/core/bootstrap_context_spec.rb | 41 +++------------- spec/unit/knife/data_bag_secret_options_spec.rb | 57 ++++++++++++++++------ spec/unit/knife/data_bag_show_spec.rb | 4 +- 8 files changed, 110 insertions(+), 152 deletions(-) diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 36a0fc1e47..6d628f0224 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -17,11 +17,13 @@ # require 'chef/knife' +require 'chef/knife/data_bag_secret_options' require 'erubis' class Chef class Knife class Bootstrap < Knife + include DataBagSecretOptions deps do require 'chef/knife/core/bootstrap_context' @@ -157,17 +159,6 @@ class Chef Chef::Config[:knife][:hints][name] = path ? Chef::JSONCompat.parse(::File.read(path)) : Hash.new } - option :secret, - :short => "-s SECRET", - :long => "--secret ", - :description => "The secret key to use to encrypt data bag item values", - :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } - - option :secret_file, - :long => "--secret-file SECRET_FILE", - :description => "A file containing the secret key to use to encrypt data bag item values", - :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } - option :bootstrap_url, :long => "--bootstrap-url URL", :description => "URL to a custom installation script", @@ -248,7 +239,8 @@ class Chef def render_template template_file = find_template template = IO.read(template_file).chomp - context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config) + secret = encryption_secret_provided?(false) ? read_secret : nil + context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config, secret) Erubis::Eruby.new(template).evaluate(context) end diff --git a/lib/chef/knife/core/bootstrap_context.rb b/lib/chef/knife/core/bootstrap_context.rb index 03e3a42e4a..e681d7a49b 100644 --- a/lib/chef/knife/core/bootstrap_context.rb +++ b/lib/chef/knife/core/bootstrap_context.rb @@ -30,10 +30,11 @@ class Chef # class BootstrapContext - def initialize(config, run_list, chef_config) + def initialize(config, run_list, chef_config, secret) @config = config @run_list = run_list @chef_config = chef_config + @secret = secret end def bootstrap_environment @@ -45,15 +46,7 @@ class Chef end def encrypted_data_bag_secret - knife_config[:secret] || begin - secret_file_path = knife_config[:secret_file] - expanded_secret_file_path = File.expand_path(secret_file_path.to_s) - if secret_file_path && File.exist?(expanded_secret_file_path) - IO.read(expanded_secret_file_path) - else - nil - end - end + @secret end def trusted_certs diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb index b692fc767c..238d09667c 100644 --- a/lib/chef/knife/data_bag_secret_options.rb +++ b/lib/chef/knife/data_bag_secret_options.rb @@ -26,17 +26,26 @@ class Chef include Mixlib::CLI include Chef::EncryptedDataBagItem::CheckEncrypted + # The config object is populated by knife#merge_configs with knife.rb `knife[:*]` config values, but they do + # not overwrite the command line properties. It does mean, however, that `knife[:secret]` and `--secret-file` + # passed at the same time populate both `config[:secret]` and `config[:secret_file]`. We cannot differentiate + # the valid case (`knife[:secret]` in config file and `--secret-file` on CL) and the invalid case (`--secret` + # and `--secret-file` on the CL) - thats why I'm storing the CL options in a different config key if they + # are provided. + def self.included(base) base.option :secret, :short => "-s SECRET", :long => "--secret ", :description => "The secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret'", - :proc => Proc.new { |s| Chef::Config[:knife][:secret] = s } + # Need to store value from command line in separate variable - knife#merge_configs populates same keys + # on config object from + :proc => Proc.new { |s| set_cl_secret(s) } base.option :secret_file, :long => "--secret-file SECRET_FILE", :description => "A file containing the secret key to use to encrypt data bag item values. Can also be defaulted in your config with the key 'secret_file'", - :proc => Proc.new { |sf| Chef::Config[:knife][:secret_file] = sf } + :proc => Proc.new { |sf| set_cl_secret_file(sf) } base.option :encrypt, :long => "--encrypt", @@ -48,18 +57,23 @@ class Chef ## # Determine if the user has specified an appropriate secret for encrypting data bag items. # @returns boolean - def encryption_secret_provided? + def encryption_secret_provided?(need_encrypt_flag = true) validate_secrets - return true if config[:secret] || config[:secret_file] + return true if has_cl_secret? || has_cl_secret_file? - if config[:encrypt] - unless has_secret? || has_secret_file? - ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") - exit(1) - else + if need_encrypt_flag + if config[:encrypt] + unless knife_config[:secret] || knife_config[:secret_file] + ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") + exit(1) + end return true end + return false + elsif knife_config[:secret] || knife_config[:secret_file] + # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret + return true end return false end @@ -69,39 +83,47 @@ class Chef # IE, if we are not running 'knife data bag *' we don't need to load 'chef/encrypted_data_bag_item' require 'chef/encrypted_data_bag_item' - if config[:secret] + if has_cl_secret? config[:secret] - elsif config[:secret_file] + elsif has_cl_secret_file? Chef::EncryptedDataBagItem.load_secret(config[:secret_file]) - elsif secret = knife_config[:secret] || Chef::Config[:secret] + elsif secret = knife_config[:secret] secret else - secret_file = knife_config[:secret_file] || Chef::Config[:secret_file] + secret_file = knife_config[:secret_file] Chef::EncryptedDataBagItem.load_secret(secret_file) end end def validate_secrets - if config[:secret] && config[:secret_file] + if has_cl_secret? && has_cl_secret_file? ui.fatal("Please specify only one of --secret, --secret-file") exit(1) end - if has_secret? && has_secret_file? - ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config") + if knife_config[:secret] && knife_config[:secret_file] + ui.fatal("Please specify only one of 'secret' or 'secret_file' in your config file") exit(1) end end - def has_secret? - knife_config[:secret] || Chef::Config[:secret] + private + + def has_cl_secret? + Chef::Config[:knife].has_key?(:cl_secret) end - def has_secret_file? - knife_config[:secret_file] || Chef::Config[:secret_file] + def self.set_cl_secret(s) + Chef::Config[:knife][:cl_secret] = s end - private + def has_cl_secret_file? + Chef::Config[:knife].has_key?(:cl_secret_file) + end + + def self.set_cl_secret_file(sf) + Chef::Config[:knife][:cl_secret_file] = sf + end def knife_config Chef::Config.key?(:knife) ? Chef::Config[:knife] : {} diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index 05f831a62d..2f97d36ca3 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -36,12 +36,11 @@ class Chef def run display = case @name_args.length when 2 # Bag and Item names provided - secret = encryption_secret_provided? ? read_secret : nil + secret = encryption_secret_provided?(false) ? read_secret : nil raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data encrypted = encrypted?(raw_data) if encrypted && secret - # TODO If the decryption fails with provided secret, what does that look like? # Users do not need to pass --encrypt to read data, we simply try to use the provided secret ui.info("Encrypted data bag detected, decrypting with provided secret.") raw = Chef::EncryptedDataBagItem.load(@name_args[0], diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index a1c5d168dd..1b1bf3a699 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -30,6 +30,7 @@ describe Chef::Knife::Bootstrap do k.merge_configs k.ui.stub(:stderr).and_return(stderr) + allow(k).to receive(:encryption_secret_provided?).with(false).and_return(false) k end @@ -221,10 +222,6 @@ describe Chef::Knife::Bootstrap do k end - # Include a data bag secret in the options to prevent Bootstrap from - # attempting to access /etc/chef/encrypted_data_bag_secret, which - # can fail when the file exists but can't be accessed by the user - # running the tests. let(:options){ ["--bootstrap-no-proxy", setting, "-s", "foo"] } let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) } let(:rendered_template) do @@ -290,7 +287,6 @@ describe Chef::Knife::Bootstrap do describe "specifying the encrypted data bag secret key" do let(:secret) { "supersekret" } - let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') } let(:options) { [] } let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) } let(:rendered_template) do @@ -299,60 +295,18 @@ describe Chef::Knife::Bootstrap do knife.render_template end - context "via --secret" do - let(:options){ ["--secret", secret] } - - it "creates a secret file" do - rendered_template.should match(%r{#{secret}}) - end - - it "renders the client.rb with an encrypted_data_bag_secret entry" do - rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) - end - end - - context "via --secret-file" do - let(:options) { ["--secret-file", secret_file] } - let(:secret) { IO.read(secret_file) } - - it "creates a secret file" do - rendered_template.should match(%r{#{secret}}) - end - - it "renders the client.rb with an encrypted_data_bag_secret entry" do - rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) - end + it "creates a secret file" do + expect(knife).to receive(:encryption_secret_provided?).with(false).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + rendered_template.should match(%r{#{secret}}) end - context "secret via config" do - before do - Chef::Config[:knife][:secret] = secret - end - - it "creates a secret file" do - rendered_template.should match(%r{#{secret}}) - end - - it "renders the client.rb with an encrypted_data_bag_secret entry" do - rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) - end + it "renders the client.rb with an encrypted_data_bag_secret entry" do + expect(knife).to receive(:encryption_secret_provided?).with(false).and_return(true) + expect(knife).to receive(:read_secret).and_return(secret) + rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) end - context "secret-file via config" do - let(:secret) { IO.read(secret_file) } - - before do - Chef::Config[:knife][:secret_file] = secret_file - end - - it "creates a secret file" do - rendered_template.should match(%r{#{secret}}) - end - - it "renders the client.rb with an encrypted_data_bag_secret entry" do - rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) - end - end end describe "when transferring trusted certificates" do diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb index 6427071a6b..266991a7dd 100644 --- a/spec/unit/knife/core/bootstrap_context_spec.rb +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -29,9 +29,10 @@ describe Chef::Knife::Core::BootstrapContext do :validation_client_name => 'chef-validator-testing' } end - let(:secret_file) { File.join(CHEF_SPEC_DATA, 'bootstrap', 'encrypted_data_bag_secret') } - subject(:bootstrap_context) { described_class.new(config, run_list, chef_config) } + let(:secret) { nil } + + subject(:bootstrap_context) { described_class.new(config, run_list, chef_config, secret) } it "runs chef with the first-boot.json in the _default environment" do bootstrap_context.start_chef.should eq "chef-client -j /etc/chef/first-boot.json -E _default" @@ -105,39 +106,9 @@ EXPECTED end describe "when an encrypted_data_bag_secret is provided" do - context "via config[:secret]" do - let(:chef_config) do - { - :knife => {:secret => "supersekret" } - } - end - it "reads the encrypted_data_bag_secret" do - bootstrap_context.encrypted_data_bag_secret.should eq "supersekret" - end - end - - context "via config[:secret_file]" do - let(:chef_config) do - { - :knife => {:secret_file => secret_file} - } - end - it "reads the encrypted_data_bag_secret" do - bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file) - end - end - - context "via config[:secret_file] with short home path" do - let(:chef_config) do - home_path = File.expand_path("~") - shorted_secret_file_path = secret_file.gsub(home_path, "~") - { - :knife => {:secret_file => shorted_secret_file_path} - } - end - it "reads the encrypted_data_bag_secret" do - bootstrap_context.encrypted_data_bag_secret.should eq IO.read(secret_file) - end + let(:secret) { "supersekret" } + it "reads the encrypted_data_bag_secret" do + bootstrap_context.encrypted_data_bag_secret.should eq "supersekret" end end diff --git a/spec/unit/knife/data_bag_secret_options_spec.rb b/spec/unit/knife/data_bag_secret_options_spec.rb index 98e876a5db..b45a95b73a 100644 --- a/spec/unit/knife/data_bag_secret_options_spec.rb +++ b/spec/unit/knife/data_bag_secret_options_spec.rb @@ -50,7 +50,8 @@ describe Chef::Knife::DataBagSecretOptions do describe "#validate_secrets" do it "throws an error when provided with both --secret and --secret-file on the CL" do - expect(example_db).to receive(:config).exactly(2).times.and_return({ :secret_file => secret_file.path, :secret => secret }) + Chef::Config[:knife][:cl_secret_file] = secret_file.path + Chef::Config[:knife][:cl_secret] = secret expect(example_db).to receive(:exit).with(1) expect(example_db.ui).to receive(:fatal).with("Please specify only one of --secret, --secret-file") @@ -61,7 +62,7 @@ describe Chef::Knife::DataBagSecretOptions do Chef::Config[:knife][:secret_file] = secret_file.path Chef::Config[:knife][:secret] = secret expect(example_db).to receive(:exit).with(1) - expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config") + expect(example_db.ui).to receive(:fatal).with("Please specify only one of 'secret' or 'secret_file' in your config file") example_db.validate_secrets end @@ -71,25 +72,25 @@ describe Chef::Knife::DataBagSecretOptions do describe "#read_secret" do it "returns the secret first" do - expect(example_db).to receive(:config).exactly(2).times.and_return({ :secret_file => secret_file.path, :secret => secret }) + Chef::Config[:knife][:cl_secret] = secret + expect(example_db).to receive(:config).and_return({ :secret => secret }) expect(example_db.read_secret).to eq(secret) end it "returns the secret_file only if secret does not exist" do - expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret_file => secret_file.path }) + Chef::Config[:knife][:cl_secret_file] = secret_file.path + expect(example_db).to receive(:config).and_return({ :secret_file => secret_file.path }) expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") expect(example_db.read_secret).to eq("secret file contents") end it "returns the secret from the knife.rb config" do - expect(example_db).to receive(:config).exactly(2).times.and_return({}) Chef::Config[:knife][:secret_file] = secret_file.path Chef::Config[:knife][:secret] = secret expect(example_db.read_secret).to eq(secret) end it "returns the secret_file from the knife.rb config only if the secret does not exist" do - expect(example_db).to receive(:config).exactly(2).times.and_return({}) Chef::Config[:knife][:secret_file] = secret_file.path expect(Chef::EncryptedDataBagItem).to receive(:load_secret).with(secret_file.path).and_return("secret file contents") expect(example_db.read_secret).to eq("secret file contents") @@ -100,39 +101,65 @@ describe Chef::Knife::DataBagSecretOptions do describe "#encryption_secret_provided?" do it "returns true if the secret is passed on the CL" do - expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret => secret }) + Chef::Config[:knife][:cl_secret] = secret expect(example_db.encryption_secret_provided?).to eq(true) end it "returns true if the secret_file is passed on the CL" do - expect(example_db).to receive(:config).exactly(3).times.and_return({ :secret_file => secret_file.path }) + Chef::Config[:knife][:cl_secret_file] = secret_file.path expect(example_db.encryption_secret_provided?).to eq(true) end - it "returns true if --encrypt is passed on the CL and :secret is in knife.rb" do - expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + it "returns true if --encrypt is passed on the CL and :secret is in config" do + expect(example_db).to receive(:config).and_return({ :encrypt => true }) Chef::Config[:knife][:secret] = secret expect(example_db.encryption_secret_provided?).to eq(true) end - it "returns true if --encrypt is passed on the CL and :secret_file is in knife.rb" do - expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + it "returns true if --encrypt is passed on the CL and :secret_file is in config" do + expect(example_db).to receive(:config).and_return({ :encrypt => true }) Chef::Config[:knife][:secret_file] = secret_file.path expect(example_db.encryption_secret_provided?).to eq(true) end - it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the knife.rb" do - expect(example_db).to receive(:config).exactly(4).times.and_return({ :encrypt => true }) + it "throws an error if --encrypt is passed and there is not :secret or :secret_file in the config" do + expect(example_db).to receive(:config).and_return({ :encrypt => true }) expect(example_db).to receive(:exit).with(1) expect(example_db.ui).to receive(:fatal).with("No secret or secret_file specified in config, unable to encrypt item.") example_db.encryption_secret_provided? end it "returns false if no secret is passed" do - expect(example_db).to receive(:config).exactly(4).times.and_return({}) + expect(example_db).to receive(:config).and_return({}) expect(example_db.encryption_secret_provided?).to eq(false) end + it "returns false if --encrypt is not provided and :secret is in the config" do + expect(example_db).to receive(:config).and_return({}) + Chef::Config[:knife][:secret] = secret + expect(example_db.encryption_secret_provided?).to eq(false) + end + + it "returns false if --encrypt is not provided and :secret_file is in the config" do + expect(example_db).to receive(:config).and_return({}) + Chef::Config[:knife][:secret_file] = secret_file.path + expect(example_db.encryption_secret_provided?).to eq(false) + end + + it "returns true if --encrypt is not provided, :secret is in the config and need_encrypt_flag is false" do + Chef::Config[:knife][:secret] = secret + expect(example_db.encryption_secret_provided?(false)).to eq(true) + end + + it "returns true if --encrypt is not provided, :secret_file is in the config and need_encrypt_flag is false" do + Chef::Config[:knife][:secret_file] = secret_file.path + expect(example_db.encryption_secret_provided?(false)).to eq(true) + end + + it "returns false if --encrypt is not provided and need_encrypt_flag is false" do + expect(example_db.encryption_secret_provided?(false)).to eq(false) + end + end end diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb index 49632a2911..47778bdf15 100644 --- a/spec/unit/knife/data_bag_show_spec.rb +++ b/spec/unit/knife/data_bag_show_spec.rb @@ -64,7 +64,7 @@ describe Chef::Knife::DataBagShow do end it "decrypts and displays the encrypted data bag when the secret is provided" do - expect(knife).to receive(:encryption_secret_provided?).and_return(true) + expect(knife).to receive(:encryption_secret_provided?).with(false).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.") @@ -78,7 +78,7 @@ qux: http://localhost:4000/data/bag_o_data/qux| end it "displays the encrypted data bag when the secret is not provided" do - expect(knife).to receive(:encryption_secret_provided?).and_return(false) + expect(knife).to receive(:encryption_secret_provided?).with(false).and_return(false) expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.") -- cgit v1.2.1 From 4f3674c03c874afe9e0dab6f02073edac0873db9 Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Tue, 16 Sep 2014 10:09:07 -0700 Subject: Updating documentation for new data bag UX - providing examples and explanation --- DOC_CHANGES.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index 29fca72484..b79e06b4fc 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -90,6 +90,42 @@ DSL method `data_bag_item` now takes an optional String parameter `secret`, whic If the data bag item being fetched is encrypted and no `secret` is provided, Chef looks for a secret at `Chef::Config[:encrypted_data_bag_secret]`. If `secret` is provided, but the data bag item is not encrypted, then a regular data bag item is returned (no decryption is attempted). +### Encrypted data bag UX +The user can now provide a secret for data bags in 4 ways. They are, in order of descending preference: +1. Provide the secret on the command line of `knife data bag` and `knife bootstrap` commands with `--secret` +1. Provide the location of a file containing the secret on the command line of `knife data bag` and `knife bootstrap` commands with `--secret-file` +1. Add the secret to your workstation config with `knife[:secret] = ...` +1. Add the location of a file containing the secret to your workstation config with `knife[:secret-file] = ...` + +When adding the secret information to your workstation config, it will not be used for writeable operations unless `--encrypt` is also passed on the command line. +Data bag read-only operations (`knife data bag show` and `knife bootstrap`) do not require `--encrypt` to be passed, and will attempt to use an available secret for decryption. +Unencrypted data bags will not attempt to be unencrypted, even if a secret is provided. +Trying to view an encrypted data bag without providing a secret will issue a warning and show the encrypted contents. +Trying to edit or create an encrypted data bag without providing a secret will fail. + +Here are some example scenarios: + +``` +# Providing `knife[:secret_file] = ...` in knife.rb will create and encrypt the data bag +knife data bag create BAG_NAME ITEM_NAME --encrypt + +# The same command ran with --secret will use the command line secret instead of the knife.rb secret +knife data bag create ANOTHER_BAG ITEM_NAME --encrypt --secret 'ANOTHER_SECRET' + +# The next two commands will fail, because they are using the wrong secret +knife data bag edit BAG_NAME --secret 'ANOTHER_SECRET' +knife data bag edit ANOTHER_BAG --encrypt + +# The next command will unencrypt the data and show it using the `knife[:secret_file]` without passing the --encrypt flag +knife data bag show BAG_NAME + +# To create an unencrypted data bag, simply do not provide `--secret`, `--secret-file` or `--encrypt` +knife data bag create UNENCRYPTED_BAG + +# If a secret is available from any of the 4 possible entries, it will be copied to a bootstrapped node, even if `--encrypt` is not present +knife bootstrap FQDN +``` + ### Enhanced search functionality: result filtering #### Use in recipes `Chef::Search::Query#search` can take an optional `:filter_result` argument which returns search data in the form of the Hash specified. Suppose your data looks like -- cgit v1.2.1 From 9d431178e1c9d98c6caed74366e9b9abd9d2404c Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Tue, 16 Sep 2014 16:48:10 -0700 Subject: Fixing :print_after so it correctly displays the edited data bag, even if it became encrypted --- lib/chef/knife/data_bag_edit.rb | 28 +++++------ spec/unit/knife/data_bag_edit_spec.rb | 90 ++++++++++++++++++++--------------- 2 files changed, 65 insertions(+), 53 deletions(-) diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb index 55f09f55c9..7c187f56f1 100644 --- a/lib/chef/knife/data_bag_edit.rb +++ b/lib/chef/knife/data_bag_edit.rb @@ -47,29 +47,27 @@ class Chef end end - def edit_item(item) - output = edit_data(item) - if encryption_secret_provided? - ui.info("Encrypting data bag using provided secret.") - Chef::EncryptedDataBagItem.encrypt_data_bag_item(output, read_secret) - else - ui.info("Saving data bag unencrypted. To encrypt it, provide an appropriate secret.") - output - end - end - def run if @name_args.length != 2 stdout.puts "You must supply the data bag and an item to edit!" stdout.puts opt_parser exit 1 end + item = load_item(@name_args[0], @name_args[1]) - output = edit_item(item) - rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", output) + edited_item = edit_data(item) + + if encryption_secret_provided? + ui.info("Encrypting data bag using provided secret.") + item_to_save = Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_item, read_secret) + else + ui.info("Saving data bag unencrypted. To encrypt it, provide an appropriate secret.") + item_to_save = edited_item + end + + rest.put_rest("data/#{@name_args[0]}/#{@name_args[1]}", item_to_save) stdout.puts("Saved data_bag_item[#{@name_args[1]}]") - # TODO this is trying to read :print_after from the CLI, not the knife.rb - ui.output(output) if config[:print_after] + ui.output(edited_item) if config[:print_after] end end end diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb index 96bfc3cbf6..9fa97760cd 100644 --- a/spec/unit/knife/data_bag_edit_spec.rb +++ b/spec/unit/knife/data_bag_edit_spec.rb @@ -33,10 +33,9 @@ describe Chef::Knife::DataBagEdit do k end - let(:plain_hash) { {"login_name" => "alphaomega", "id" => "item_name"} } - let(:plain_db) {Chef::DataBagItem.from_hash(plain_hash)} - let(:edited_hash) { {"login_name" => "rho", "id" => "item_name", "new_key" => "new_value"} } - let(:edited_db) {Chef::DataBagItem.from_hash(edited_hash)} + let(:raw_hash) { {"login_name" => "alphaomega", "id" => "item_name"} } + let(:db) { Chef::DataBagItem.from_hash(raw_hash)} + let(:raw_edited_hash) { {"login_name" => "rho", "id" => "item_name", "new_key" => "new_value"} } let(:rest) { double("Chef::REST") } let(:stdout) { StringIO.new } @@ -48,6 +47,21 @@ describe Chef::Knife::DataBagEdit do let(:config) { {} } + let(:is_encrypted?) { false } + let(:transmitted_hash) { raw_edited_hash } + let(:data_to_edit) { db } + + shared_examples_for "editing a data bag" do + it "correctly edits then uploads the data bag" do + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db) + expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(is_encrypted?) + expect(knife).to receive(:edit_data).with(data_to_edit).and_return(raw_edited_hash) + expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", transmitted_hash).ordered + + knife.run + end + end + it "requires data bag and item arguments" do knife.name_args = [] expect(stdout).to receive(:puts).twice.with(anything) @@ -55,51 +69,51 @@ describe Chef::Knife::DataBagEdit do expect(stdout.string).to eq("") end - it "saves edits on a data bag item" do - expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(plain_db) - expect(knife).to receive(:encrypted?) { false } - expect(knife).to receive(:edit_data).with(plain_db).and_return(edited_db.raw_data) - expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", edited_db.raw_data).ordered - knife.run + context "when no secret is provided" do + include_examples "editing a data bag" end - describe "encrypted data bag items" do - let(:enc_plain_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(plain_hash, secret) } - let(:data_bag_with_encoded_hash) { Chef::DataBagItem.from_hash(enc_plain_hash) } - let(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_hash, secret) } - - before(:each) do - allow(knife).to receive(:encrypted?) { true } - allow(knife).to receive(:encryption_secret_provided?) { true } - allow(knife).to receive(:read_secret).and_return(secret) + context "when config[:print_after] is set" do + let(:config) { {:print_after => true} } + before do + expect(knife.ui).to receive(:output).with(raw_edited_hash) end - it "decrypts an encrypted data bag, edits it and rencrypts it" do - expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) - expect(knife).to receive(:edit_data).with(plain_hash).and_return(edited_hash) - expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(edited_hash, secret).and_return(enc_edited_hash) - expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", enc_edited_hash).ordered + include_examples "editing a data bag" + end + + context "when a secret is provided" do + let!(:enc_raw_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_hash, secret) } + let!(:enc_edited_hash) { Chef::EncryptedDataBagItem.encrypt_data_bag_item(raw_edited_hash, secret) } + let(:transmitted_hash) { enc_edited_hash } - knife.run + before(:each) do + expect(knife).to receive(:encryption_secret_provided?).at_least(1).times.and_return(true) + expect(knife).to receive(:read_secret).at_least(1).times.and_return(secret) + expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(raw_edited_hash, secret).and_return(enc_edited_hash) end - it "edits an unencrypted data bag and encrypts it" do - expect(knife).to receive(:encrypted?) { false } - expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(plain_db) - expect(knife).to receive(:edit_data).with(plain_db).and_return(edited_hash) - expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(edited_hash, secret).and_return(enc_edited_hash) - expect(rest).to receive(:put_rest).with("data/#{bag_name}/#{item_name}", enc_edited_hash).ordered + context "the data bag starts encrypted" do + let(:is_encrypted?) { true } + let(:db) { Chef::DataBagItem.from_hash(enc_raw_hash) } + # If the data bag is encrypted, it gets passed to `edit` as a hash. Otherwise, it gets passed as a DataBag + let (:data_to_edit) { raw_hash } - knife.run + include_examples "editing a data bag" end - it "fails to edit an encrypted data bag if the secret is missing" do - allow(knife).to receive(:encryption_secret_provided?) { false } - expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) - - expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.") - expect {knife.run}.to exit_with_code(1) + context "the data bag starts unencrypted" do + include_examples "editing a data bag" end + end + + it "fails to edit an encrypted data bag if the secret is missing" do + expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db) + expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(true) + expect(knife).to receive(:encryption_secret_provided?).and_return(false) + expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.") + expect {knife.run}.to exit_with_code(1) end + end -- cgit v1.2.1 From 75a90633b5015f91425644670d839377837fb8d3 Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Wed, 17 Sep 2014 14:54:35 -0700 Subject: Fixing bad method definition --- lib/chef/knife/bootstrap.rb | 2 +- lib/chef/knife/data_bag_secret_options.rb | 50 ++++++++++++++----------- lib/chef/knife/data_bag_show.rb | 2 +- spec/unit/knife/bootstrap_spec.rb | 6 +-- spec/unit/knife/data_bag_secret_options_spec.rb | 6 +-- spec/unit/knife/data_bag_show_spec.rb | 4 +- 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/lib/chef/knife/bootstrap.rb b/lib/chef/knife/bootstrap.rb index 6d628f0224..a992cf5779 100644 --- a/lib/chef/knife/bootstrap.rb +++ b/lib/chef/knife/bootstrap.rb @@ -239,7 +239,7 @@ class Chef def render_template template_file = find_template template = IO.read(template_file).chomp - secret = encryption_secret_provided?(false) ? read_secret : nil + secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil context = Knife::Core::BootstrapContext.new(config, config[:run_list], Chef::Config, secret) Erubis::Eruby.new(template).evaluate(context) end diff --git a/lib/chef/knife/data_bag_secret_options.rb b/lib/chef/knife/data_bag_secret_options.rb index 238d09667c..766006089e 100644 --- a/lib/chef/knife/data_bag_secret_options.rb +++ b/lib/chef/knife/data_bag_secret_options.rb @@ -54,28 +54,12 @@ class Chef :default => false end - ## - # Determine if the user has specified an appropriate secret for encrypting data bag items. - # @returns boolean - def encryption_secret_provided?(need_encrypt_flag = true) - validate_secrets - - return true if has_cl_secret? || has_cl_secret_file? + def encryption_secret_provided? + base_encryption_secret_provided? + end - if need_encrypt_flag - if config[:encrypt] - unless knife_config[:secret] || knife_config[:secret_file] - ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") - exit(1) - end - return true - end - return false - elsif knife_config[:secret] || knife_config[:secret_file] - # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret - return true - end - return false + def encryption_secret_provided_ignore_encrypt_flag? + base_encryption_secret_provided?(false) end def read_secret @@ -109,6 +93,30 @@ class Chef private + ## + # Determine if the user has specified an appropriate secret for encrypting data bag items. + # @returns boolean + def base_encryption_secret_provided?(need_encrypt_flag = true) + validate_secrets + + return true if has_cl_secret? || has_cl_secret_file? + + if need_encrypt_flag + if config[:encrypt] + unless knife_config[:secret] || knife_config[:secret_file] + ui.fatal("No secret or secret_file specified in config, unable to encrypt item.") + exit(1) + end + return true + end + return false + elsif knife_config[:secret] || knife_config[:secret_file] + # Certain situations (show and bootstrap) don't need a --encrypt flag to use the config file secret + return true + end + return false + end + def has_cl_secret? Chef::Config[:knife].has_key?(:cl_secret) end diff --git a/lib/chef/knife/data_bag_show.rb b/lib/chef/knife/data_bag_show.rb index 2f97d36ca3..36715286e8 100644 --- a/lib/chef/knife/data_bag_show.rb +++ b/lib/chef/knife/data_bag_show.rb @@ -36,7 +36,7 @@ class Chef def run display = case @name_args.length when 2 # Bag and Item names provided - secret = encryption_secret_provided?(false) ? read_secret : nil + secret = encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil raw_data = Chef::DataBagItem.load(@name_args[0], @name_args[1]).raw_data encrypted = encrypted?(raw_data) diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb index 1b1bf3a699..d5c668753e 100644 --- a/spec/unit/knife/bootstrap_spec.rb +++ b/spec/unit/knife/bootstrap_spec.rb @@ -30,7 +30,7 @@ describe Chef::Knife::Bootstrap do k.merge_configs k.ui.stub(:stderr).and_return(stderr) - allow(k).to receive(:encryption_secret_provided?).with(false).and_return(false) + allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) k end @@ -296,13 +296,13 @@ describe Chef::Knife::Bootstrap do end it "creates a secret file" do - expect(knife).to receive(:encryption_secret_provided?).with(false).and_return(true) + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) rendered_template.should match(%r{#{secret}}) end it "renders the client.rb with an encrypted_data_bag_secret entry" do - expect(knife).to receive(:encryption_secret_provided?).with(false).and_return(true) + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) rendered_template.should match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"}) end diff --git a/spec/unit/knife/data_bag_secret_options_spec.rb b/spec/unit/knife/data_bag_secret_options_spec.rb index b45a95b73a..0a2d8ca4bf 100644 --- a/spec/unit/knife/data_bag_secret_options_spec.rb +++ b/spec/unit/knife/data_bag_secret_options_spec.rb @@ -148,16 +148,16 @@ describe Chef::Knife::DataBagSecretOptions do it "returns true if --encrypt is not provided, :secret is in the config and need_encrypt_flag is false" do Chef::Config[:knife][:secret] = secret - expect(example_db.encryption_secret_provided?(false)).to eq(true) + expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true) end it "returns true if --encrypt is not provided, :secret_file is in the config and need_encrypt_flag is false" do Chef::Config[:knife][:secret_file] = secret_file.path - expect(example_db.encryption_secret_provided?(false)).to eq(true) + expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(true) end it "returns false if --encrypt is not provided and need_encrypt_flag is false" do - expect(example_db.encryption_secret_provided?(false)).to eq(false) + expect(example_db.encryption_secret_provided_ignore_encrypt_flag?).to eq(false) end end diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb index 47778bdf15..1125d99c2a 100644 --- a/spec/unit/knife/data_bag_show_spec.rb +++ b/spec/unit/knife/data_bag_show_spec.rb @@ -64,7 +64,7 @@ describe Chef::Knife::DataBagShow do end it "decrypts and displays the encrypted data bag when the secret is provided" do - expect(knife).to receive(:encryption_secret_provided?).with(false).and_return(true) + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) expect(knife).to receive(:read_secret).and_return(secret) expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) expect(knife.ui).to receive(:info).with("Encrypted data bag detected, decrypting with provided secret.") @@ -78,7 +78,7 @@ qux: http://localhost:4000/data/bag_o_data/qux| end it "displays the encrypted data bag when the secret is not provided" do - expect(knife).to receive(:encryption_secret_provided?).with(false).and_return(false) + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(data_bag_with_encoded_hash) expect(knife.ui).to receive(:warn).with("Encrypted data bag detected, but no secret provided for decoding. Displaying encrypted data.") -- cgit v1.2.1 From 049672e8335a7a3190fcf3acd59d63b42f1f0ba0 Mon Sep 17 00:00:00 2001 From: tyler-ball Date: Thu, 25 Sep 2014 15:48:10 -0700 Subject: Fixing `data bag edit` according to spec, no longer requires --encrypt --- lib/chef/knife/data_bag_edit.rb | 10 +++++----- lib/chef/knife/data_bag_from_file.rb | 1 - spec/unit/knife/data_bag_edit_spec.rb | 12 ++++++++++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/chef/knife/data_bag_edit.rb b/lib/chef/knife/data_bag_edit.rb index 7c187f56f1..6ef4b33f59 100644 --- a/lib/chef/knife/data_bag_edit.rb +++ b/lib/chef/knife/data_bag_edit.rb @@ -36,14 +36,14 @@ class Chef def load_item(bag, item_name) item = Chef::DataBagItem.load(bag, item_name) if encrypted?(item.raw_data) - if encryption_secret_provided? - Chef::EncryptedDataBagItem.new(item, read_secret).to_hash + if encryption_secret_provided_ignore_encrypt_flag? + return Chef::EncryptedDataBagItem.new(item, read_secret).to_hash, true else ui.fatal("You cannot edit an encrypted data bag without providing the secret.") exit(1) end else - item + return item, false end end @@ -54,10 +54,10 @@ class Chef exit 1 end - item = load_item(@name_args[0], @name_args[1]) + item, was_encrypted = load_item(@name_args[0], @name_args[1]) edited_item = edit_data(item) - if encryption_secret_provided? + if was_encrypted || encryption_secret_provided? ui.info("Encrypting data bag using provided secret.") item_to_save = Chef::EncryptedDataBagItem.encrypt_data_bag_item(edited_item, read_secret) else diff --git a/lib/chef/knife/data_bag_from_file.rb b/lib/chef/knife/data_bag_from_file.rb index 598a935160..d1b7daa4a2 100644 --- a/lib/chef/knife/data_bag_from_file.rb +++ b/lib/chef/knife/data_bag_from_file.rb @@ -24,7 +24,6 @@ require 'chef/knife/data_bag_secret_options' class Chef class Knife class DataBagFromFile < Knife - include DataBagCommon include DataBagSecretOptions deps do diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb index 9fa97760cd..6f19b5e63e 100644 --- a/spec/unit/knife/data_bag_edit_spec.rb +++ b/spec/unit/knife/data_bag_edit_spec.rb @@ -88,7 +88,6 @@ describe Chef::Knife::DataBagEdit do let(:transmitted_hash) { enc_edited_hash } before(:each) do - expect(knife).to receive(:encryption_secret_provided?).at_least(1).times.and_return(true) expect(knife).to receive(:read_secret).at_least(1).times.and_return(secret) expect(Chef::EncryptedDataBagItem).to receive(:encrypt_data_bag_item).with(raw_edited_hash, secret).and_return(enc_edited_hash) end @@ -99,10 +98,19 @@ describe Chef::Knife::DataBagEdit do # If the data bag is encrypted, it gets passed to `edit` as a hash. Otherwise, it gets passed as a DataBag let (:data_to_edit) { raw_hash } + before(:each) do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true) + end + include_examples "editing a data bag" end context "the data bag starts unencrypted" do + before(:each) do + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).exactly(0).times + expect(knife).to receive(:encryption_secret_provided?).and_return(true) + end + include_examples "editing a data bag" end end @@ -110,7 +118,7 @@ describe Chef::Knife::DataBagEdit do it "fails to edit an encrypted data bag if the secret is missing" do expect(Chef::DataBagItem).to receive(:load).with(bag_name, item_name).and_return(db) expect(knife).to receive(:encrypted?).with(db.raw_data).and_return(true) - expect(knife).to receive(:encryption_secret_provided?).and_return(false) + expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false) expect(knife.ui).to receive(:fatal).with("You cannot edit an encrypted data bag without providing the secret.") expect {knife.run}.to exit_with_code(1) -- cgit v1.2.1