diff options
author | Claire McQuin <claire@getchef.com> | 2014-06-05 14:43:42 -0700 |
---|---|---|
committer | Claire McQuin <claire@getchef.com> | 2014-06-05 14:44:22 -0700 |
commit | 133f6b2758a728d2aeeb2d88c243566eee20f4f6 (patch) | |
tree | 72b53c1524d22ffabd4e3047ee7eb6217755aa7c | |
parent | e7273d0b27c25ac218c968c1abf0bf5d332420c9 (diff) | |
download | chef-133f6b2758a728d2aeeb2d88c243566eee20f4f6.tar.gz |
send md5 checksummed registry key if data type is binary/dword/qword
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | lib/chef/provider/registry_key.rb | 9 | ||||
-rw-r--r-- | lib/chef/resource/registry_key.rb | 36 | ||||
-rw-r--r-- | spec/unit/provider/registry_key_spec.rb | 120 | ||||
-rw-r--r-- | spec/unit/resource/registry_key_spec.rb | 28 | ||||
-rw-r--r-- | spec/unit/resource_reporter_spec.rb | 49 |
6 files changed, 170 insertions, 73 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 000e223290..26ea1e39fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ * Set proxy environment variables if preset in config. (CHEF-4712) * Automatically enable verify_api_cert when running chef-client in local-mode. (Chef Issues 1464) * Add helper to warn for broken [windows] paths. (CHEF-5322) +* Send md5 checksummed data for registry key if data type is binary, dword, or qword. (Chef-5323) ## Release: 11.12.4 (04/30/2014) http://www.getchef.com/blog/2014/04/30/release-chef-client-11-12-4-ohai-7-0-4/ diff --git a/lib/chef/provider/registry_key.rb b/lib/chef/provider/registry_key.rb index b7bcdd908d..3b5be93ba1 100644 --- a/lib/chef/provider/registry_key.rb +++ b/lib/chef/provider/registry_key.rb @@ -54,7 +54,7 @@ class Chef if registry.key_exists?(@new_resource.key) @current_resource.values(registry.get_values(@new_resource.key)) end - values_to_hash(@current_resource.values) + values_to_hash(@current_resource.unscrubbed_values) @current_resource end @@ -99,7 +99,7 @@ class Chef registry.create_key(@new_resource.key, @new_resource.recursive) end end - @new_resource.values.each do |value| + @new_resource.unscrubbed_values.each do |value| if @name_hash.has_key?(value[:name]) current_value = @name_hash[value[:name]] unless current_value[:type] == value[:type] && current_value[:data] == value[:data] @@ -121,7 +121,7 @@ class Chef registry.create_key(@new_resource.key, @new_resource.recursive) end end - @new_resource.values.each do |value| + @new_resource.unscrubbed_values.each do |value| unless @name_hash.has_key?(value[:name]) converge_by("create value #{value}") do registry.set_value(@new_resource.key, value) @@ -132,7 +132,7 @@ class Chef def action_delete if registry.key_exists?(@new_resource.key) - @new_resource.values.each do |value| + @new_resource.unscrubbed_values.each do |value| if @name_hash.has_key?(value[:name]) converge_by("delete value #{value}") do registry.delete_value(@new_resource.key, value) @@ -153,4 +153,3 @@ class Chef end end end - diff --git a/lib/chef/resource/registry_key.rb b/lib/chef/resource/registry_key.rb index 2b5d077f4c..f7924ceb36 100644 --- a/lib/chef/resource/registry_key.rb +++ b/lib/chef/resource/registry_key.rb @@ -17,6 +17,7 @@ # require 'chef/provider/registry_key' require 'chef/resource' +require 'chef/digester' class Chef class Resource @@ -25,6 +26,8 @@ class Chef identity_attr :key state_attrs :values + attr_reader :unscrubbed_values + def initialize(name, run_context=nil) super @resource_name = :registry_key @@ -32,7 +35,7 @@ class Chef @architecture = :machine @recursive = false @key = name - @values = [] + @values, @unscrubbed_values = [], [] @allowed_actions.push(:create, :create_if_missing, :delete, :delete_key) end @@ -43,6 +46,7 @@ class Chef :kind_of => String ) end + def values(arg=nil) if not arg.nil? if arg.is_a?(Hash) @@ -52,6 +56,7 @@ class Chef else raise ArgumentError, "Bad type for RegistryKey resource, use Hash or Array" end + @values.each do |v| raise ArgumentError, "Missing name key in RegistryKey values hash" unless v.has_key?(:name) raise ArgumentError, "Missing type key in RegistryKey values hash" unless v.has_key?(:type) @@ -62,10 +67,12 @@ class Chef raise ArgumentError, "Type of name => #{v[:name]} should be string" unless v[:name].is_a?(String) raise Argument Error "Type of type => #{v[:name]} should be symbol" unless v[:type].is_a?(Symbol) end - elsif self.instance_variable_defined?(:@values) == true - @values + @unscrubbed_values = @values + elsif self.instance_variable_defined?(:@values) + scrub_values(@values) end end + def recursive(arg=nil) set_or_return( :recursive, @@ -73,6 +80,7 @@ class Chef :kind_of => [TrueClass, FalseClass] ) end + def architecture(arg=nil) set_or_return( :architecture, @@ -81,6 +89,28 @@ class Chef ) end + private + + def scrub_values(values) + scrubbed = [] + values.each do |value| + scrubbed_value = value.dup + if needs_checksum?(scrubbed_value) + data_io = StringIO.new(scrubbed_value[:data]) + scrubbed_value[:data] = Chef::Digester.instance.generate_md5_checksum(data_io) + end + scrubbed << scrubbed_value + end + scrubbed + end + + # Some data types may raise errors when sent as json. Returns true if this + # value's data may need to be converted to a checksum. + def needs_checksum?(value) + unsafe_types = [:binary, :dword, :dword_big_endian, :qword] + unsafe_types.include?(value[:type]) + end + end end end diff --git a/spec/unit/provider/registry_key_spec.rb b/spec/unit/provider/registry_key_spec.rb index f88d4c0a87..2cfbcf98f1 100644 --- a/spec/unit/provider/registry_key_spec.rb +++ b/spec/unit/provider/registry_key_spec.rb @@ -18,21 +18,14 @@ require 'spec_helper' -describe Chef::Provider::RegistryKey do - - let(:testval1) { { :name => "one", :type => :string, :data => "1" } } - let(:testval1_wrong_type) { { :name => "one", :type => :multi_string, :data => "1" } } - let(:testval1_wrong_data) { { :name => "one", :type => :string, :data => "2" } } - let(:testval2) { { :name => "two", :type => :string, :data => "2" } } - let(:testkey1) { 'HKLM\Software\Opscode\Testing' } - +shared_examples_for "a registry key" do before(:each) do @node = Chef::Node.new @events = Chef::EventDispatch::Dispatcher.new @run_context = Chef::RunContext.new(@node, {}, @events) @new_resource = Chef::Resource::RegistryKey.new("windows is fun", @run_context) - @new_resource.key testkey1 + @new_resource.key keyname @new_resource.values( testval1 ) @new_resource.recursive false @@ -49,8 +42,8 @@ describe Chef::Provider::RegistryKey do describe "executing load_current_resource" do describe "when the key exists" do before(:each) do - @double_registry.should_receive(:key_exists?).with(testkey1).and_return(true) - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval2 ) + @double_registry.should_receive(:key_exists?).with(keyname).and_return(true) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval2 ) @provider.load_current_resource end @@ -66,14 +59,14 @@ describe Chef::Provider::RegistryKey do @provider.current_resource.recursive.should == @new_resource.recursive end - it "should set the values of the current resource to the values it got from the registry" do - @provider.current_resource.values.should == [ testval2 ] + it "should set the unscrubbed values of the current resource to the values it got from the registry" do + @provider.current_resource.unscrubbed_values.should == [ testval2 ] end end describe "when the key does not exist" do before(:each) do - @double_registry.should_receive(:key_exists?).with(testkey1).and_return(false) + @double_registry.should_receive(:key_exists?).with(keyname).and_return(false) @provider.load_current_resource end @@ -86,29 +79,29 @@ describe Chef::Provider::RegistryKey do describe "action_create" do context "when the key exists" do before(:each) do - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(true) end it "should do nothing if the key and the value both exist" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 ) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1 ) @double_registry.should_not_receive(:set_value) @provider.load_current_resource @provider.action_create end it "should create the value if the key exists but the value does not" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval2 ) - @double_registry.should_receive(:set_value).with(testkey1, testval1) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval2 ) + @double_registry.should_receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create end it "should set the value if the key exists but the data does not match" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_data ) - @double_registry.should_receive(:set_value).with(testkey1, testval1) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1_wrong_data ) + @double_registry.should_receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create end it "should set the value if the key exists but the type does not match" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_type ) - @double_registry.should_receive(:set_value).with(testkey1, testval1) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1_wrong_type ) + @double_registry.should_receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create end @@ -116,8 +109,8 @@ describe Chef::Provider::RegistryKey do context "when the key exists and the values in the new resource are empty" do it "when a value is in the key, it should do nothing" do @provider.new_resource.values([]) - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true) - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 ) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(true) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1 ) @double_registry.should_not_receive(:create_key) @double_registry.should_not_receive(:set_value) @provider.load_current_resource @@ -125,8 +118,8 @@ describe Chef::Provider::RegistryKey do end it "when no value is in the key, it should do nothing" do @provider.new_resource.values([]) - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true) - @double_registry.should_receive(:get_values).with(testkey1).and_return( nil ) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(true) + @double_registry.should_receive(:get_values).with(keyname).and_return( nil ) @double_registry.should_not_receive(:create_key) @double_registry.should_not_receive(:set_value) @provider.load_current_resource @@ -135,11 +128,11 @@ describe Chef::Provider::RegistryKey do end context "when the key does not exist" do before(:each) do - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(false) end it "should create the key and the value" do - @double_registry.should_receive(:create_key).with(testkey1, false) - @double_registry.should_receive(:set_value).with(testkey1, testval1) + @double_registry.should_receive(:create_key).with(keyname, false) + @double_registry.should_receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create end @@ -147,8 +140,8 @@ describe Chef::Provider::RegistryKey do context "when the key does not exist and the values in the new resource are empty" do it "should create the key" do @new_resource.values([]) - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false) - @double_registry.should_receive(:create_key).with(testkey1, false) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(false) + @double_registry.should_receive(:create_key).with(keyname, false) @double_registry.should_not_receive(:set_value) @provider.load_current_resource @provider.action_create @@ -159,28 +152,28 @@ describe Chef::Provider::RegistryKey do describe "action_create_if_missing" do context "when the key exists" do before(:each) do - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(true) end it "should do nothing if the key and the value both exist" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 ) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1 ) @double_registry.should_not_receive(:set_value) @provider.load_current_resource @provider.action_create_if_missing end it "should create the value if the key exists but the value does not" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval2 ) - @double_registry.should_receive(:set_value).with(testkey1, testval1) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval2 ) + @double_registry.should_receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create_if_missing end it "should not set the value if the key exists but the data does not match" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_data ) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1_wrong_data ) @double_registry.should_not_receive(:set_value) @provider.load_current_resource @provider.action_create_if_missing end it "should not set the value if the key exists but the type does not match" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_type ) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1_wrong_type ) @double_registry.should_not_receive(:set_value) @provider.load_current_resource @provider.action_create_if_missing @@ -188,11 +181,11 @@ describe Chef::Provider::RegistryKey do end context "when the key does not exist" do before(:each) do - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(false) end it "should create the key and the value" do - @double_registry.should_receive(:create_key).with(testkey1, false) - @double_registry.should_receive(:set_value).with(testkey1, testval1) + @double_registry.should_receive(:create_key).with(keyname, false) + @double_registry.should_receive(:set_value).with(keyname, testval1) @provider.load_current_resource @provider.action_create_if_missing end @@ -202,28 +195,28 @@ describe Chef::Provider::RegistryKey do describe "action_delete" do context "when the key exists" do before(:each) do - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(true) end it "deletes the value when the value exists" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 ) - @double_registry.should_receive(:delete_value).with(testkey1, testval1) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1 ) + @double_registry.should_receive(:delete_value).with(keyname, testval1) @provider.load_current_resource @provider.action_delete end it "deletes the value when the value exists, but the type is wrong" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_type ) - @double_registry.should_receive(:delete_value).with(testkey1, testval1) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1_wrong_type ) + @double_registry.should_receive(:delete_value).with(keyname, testval1) @provider.load_current_resource @provider.action_delete end it "deletes the value when the value exists, but the data is wrong" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1_wrong_data ) - @double_registry.should_receive(:delete_value).with(testkey1, testval1) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1_wrong_data ) + @double_registry.should_receive(:delete_value).with(keyname, testval1) @provider.load_current_resource @provider.action_delete end it "does not delete the value when the value does not exist" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval2 ) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval2 ) @double_registry.should_not_receive(:delete_value) @provider.load_current_resource @provider.action_delete @@ -231,7 +224,7 @@ describe Chef::Provider::RegistryKey do end context "when the key does not exist" do before(:each) do - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(false) end it "does nothing" do @double_registry.should_not_receive(:delete_value) @@ -244,18 +237,18 @@ describe Chef::Provider::RegistryKey do describe "action_delete_key" do context "when the key exists" do before(:each) do - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(true) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(true) end it "deletes the key" do - @double_registry.should_receive(:get_values).with(testkey1).and_return( testval1 ) - @double_registry.should_receive(:delete_key).with(testkey1, false) + @double_registry.should_receive(:get_values).with(keyname).and_return( testval1 ) + @double_registry.should_receive(:delete_key).with(keyname, false) @provider.load_current_resource @provider.action_delete_key end end context "when the key does not exist" do before(:each) do - @double_registry.should_receive(:key_exists?).twice.with(testkey1).and_return(false) + @double_registry.should_receive(:key_exists?).twice.with(keyname).and_return(false) end it "does nothing" do @double_registry.should_not_receive(:delete_key) @@ -267,3 +260,24 @@ describe Chef::Provider::RegistryKey do end +describe Chef::Provider::RegistryKey do + context "when the key data is safe" do + let(:keyname) { 'HKLM\Software\Opscode\Testing\Safe' } + let(:testval1) { { :name => "one", :type => :string, :data => "1" } } + let(:testval1_wrong_type) { { :name => "one", :type => :multi_string, :data => "1" } } + let(:testval1_wrong_data) { { :name => "one", :type => :string, :data => "2" } } + let(:testval2) { { :name => "two", :type => :string, :data => "2" } } + + it_should_behave_like "a registry key" + end + + context "when the key data is unsafe" do + let(:keyname) { 'HKLM\Software\Opscode\Testing\Unsafe' } + let(:testval1) { { :name => "one", :type => :binary, :data => 255.chr * 1 } } + let(:testval1_wrong_type) { { :name => "one", :type => :string, :data => 255.chr * 1 } } + let(:testval1_wrong_data) { { :name => "one", :type => :binary, :data => 254.chr * 1 } } + let(:testval2) { { :name => "two", :type => :binary, :data => 0.chr * 1 } } + + it_should_behave_like "a registry key" + end +end diff --git a/spec/unit/resource/registry_key_spec.rb b/spec/unit/resource/registry_key_spec.rb index 3f227e26b6..00c301d61d 100644 --- a/spec/unit/resource/registry_key_spec.rb +++ b/spec/unit/resource/registry_key_spec.rb @@ -89,6 +89,11 @@ describe Chef::Resource::RegistryKey, "values" do @resource.values.should eql([ { :name => 'poosh', :type => :string, :data => 'carmen' } ]) end + it "should return checksummed data if the type is unsafe" do + @resource.values( { :name => 'poosh', :type => :binary, :data => 255.chr * 1 }) + @resource.values.should eql([ { :name => 'poosh', :type => :binary, :data => "00594fd4f42ba43fc1ca0427a0576295" } ]) + end + it "should throw an exception if the name field is missing" do lambda { @resource.values [ { :type => :string, :data => 'carmen' } ] }.should raise_error(ArgumentError) end @@ -169,3 +174,26 @@ describe Chef::Resource::RegistryKey, "architecture" do lambda { @resource.architecture(100) }.should raise_error(ArgumentError) end end + +describe Chef::Resource::RegistryKey, ":unscrubbed_values" do + before(:each) do + @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') + end + + it "should return unsafe data as-is" do + key_values = [ { :name => 'poosh', :type => :binary, :data => 255.chr * 1 } ] + @resource.values(key_values) + @resource.unscrubbed_values.should eql(key_values) + end +end + +describe Chef::Resource::RegistryKey, "state" do + before(:each) do + @resource = Chef::Resource::RegistryKey.new('HKCU\Software\Raxicoricofallapatorius') + end + + it "should return scrubbed values" do + @resource.values([ { :name => 'poosh', :type => :binary, :data => 255.chr * 1 } ]) + @resource.state.should eql( { :values => [{ :name => 'poosh', :type => :binary, :data => "00594fd4f42ba43fc1ca0427a0576295" }] } ) + end +end diff --git a/spec/unit/resource_reporter_spec.rb b/spec/unit/resource_reporter_spec.rb index e5f246a2f6..a7ec665495 100644 --- a/spec/unit/resource_reporter_spec.rb +++ b/spec/unit/resource_reporter_spec.rb @@ -39,7 +39,8 @@ describe Chef::ResourceReporter do @rest_client.stub(:post_rest).and_return(true) @resource_reporter = Chef::ResourceReporter.new(@rest_client) @new_resource = Chef::Resource::File.new("/tmp/a-file.txt") - @new_resource.cookbook_name = "monkey" + @cookbook_name = "monkey" + @new_resource.cookbook_name = @cookbook_name @cookbook_version = double("Cookbook::Version", :version => "1.2.3") @new_resource.stub(:cookbook_version).and_return(@cookbook_version) @current_resource = Chef::Resource::File.new("/tmp/a-file.txt") @@ -316,8 +317,7 @@ describe Chef::ResourceReporter do end end - - context "for a successful client run" do + shared_examples_for "a successful client run" do before do # TODO: add inputs to generate expected output. @@ -349,10 +349,10 @@ describe Chef::ResourceReporter do # "status" : "success" # "data" : "" # } - @resource_reporter.resource_action_start(@new_resource, :create) - @resource_reporter.resource_current_state_loaded(@new_resource, :create, @current_resource) - @resource_reporter.resource_updated(@new_resource, :create) - @resource_reporter.resource_completed(@new_resource) + @resource_reporter.resource_action_start(new_resource, :create) + @resource_reporter.resource_current_state_loaded(new_resource, :create, current_resource) + @resource_reporter.resource_updated(new_resource, :create) + @resource_reporter.resource_completed(new_resource) @run_status.stop_clock @report = @resource_reporter.prepare_run_data @first_update_report = @report["resources"].first @@ -371,19 +371,19 @@ describe Chef::ResourceReporter do end it "includes an updated resource's initial state" do - @first_update_report["before"].should == @current_resource.state + @first_update_report["before"].should == current_resource.state end it "includes an updated resource's final state" do - @first_update_report["after"].should == @new_resource.state + @first_update_report["after"].should == new_resource.state end it "includes the resource's name" do - @first_update_report["name"].should == @new_resource.name + @first_update_report["name"].should == new_resource.name end it "includes the resource's id attribute" do - @first_update_report["id"].should == @new_resource.identity + @first_update_report["id"].should == new_resource.identity end it "includes the elapsed time for the resource to converge" do @@ -400,7 +400,7 @@ describe Chef::ResourceReporter do it "includes the cookbook name of the resource" do @first_update_report.should have_key("cookbook_name") - @first_update_report["cookbook_name"].should == "monkey" + @first_update_report["cookbook_name"].should == @cookbook_name end it "includes the cookbook version of the resource" do @@ -430,6 +430,31 @@ describe Chef::ResourceReporter do end + context "when the resource is a File" do + let(:new_resource) { @new_resource } + let(:current_resource) { @current_resource } + + it_should_behave_like "a successful client run" + end + + context "when the resource is a RegistryKey with binary data" do + let(:new_resource) do + resource = Chef::Resource::RegistryKey.new('Wubba\Lubba\Dub\Dubs') + resource.values([ { :name => 'rick', :type => :binary, :data => 255.chr * 1 } ]) + resource.stub(:cookbook_name).and_return(@cookbook_name) + resource.stub(:cookbook_version).and_return(@cookbook_version) + resource + end + + let(:current_resource) do + resource = Chef::Resource::RegistryKey.new('Wubba\Lubba\Dub\Dubs') + resource.values([ { :name => 'rick', :type => :binary, :data => 255.chr * 1 } ]) + resource + end + + it_should_behave_like "a successful client run" + end + context "for an unsuccessful run" do before do |