diff options
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | DOC_CHANGES.md | 34 | ||||
-rw-r--r-- | lib/chef/config.rb | 29 | ||||
-rw-r--r-- | lib/chef/node.rb | 33 | ||||
-rw-r--r-- | spec/unit/node_spec.rb | 76 |
5 files changed, 167 insertions, 6 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index c3556f6e67..696ca05e93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ * chef-full template gets knife options to override install script url, add wget/curl cli options, and custom install commands (CHEF-4697) * knife now bootstraps node with the latest current version of chef-client. (CHEF-4911) * Add a threaded download queue for synchronizing cookbooks in parallel. (CHEF-4423) +* Add config options for attribute whitelisting in node.save. (CHEF-3811) ## Last Release: 11.12.0 RC1 (03/31/2014) * SIGTERM will once-more kill a non-daemonized chef-client (CHEF-5172) diff --git a/DOC_CHANGES.md b/DOC_CHANGES.md index 00d15f7b18..c9b4d3e86a 100644 --- a/DOC_CHANGES.md +++ b/DOC_CHANGES.md @@ -37,3 +37,37 @@ You can now modify the chef-full template with the following options in `knife b ### Parallelize cookbook synchronization You can now synchronize your cookbooks faster by parallelizing the process. You can specify the number of helper threads in your config file with `cookbook_sync_threads NUM_THREADS`. The default is 10. Increasing `NUM_THREADS` can result in gateway errors from the chef server (namely 503 and 504). If you are experiencing these often, consider decreasing `NUM_THREADS` to fewer than default. + +### New chef config options: Whitelisting for the attributes saved by the node + +You can now whitelist attributes that will be saved by the node by providing a hash with the keys you want to include. Whitelist filters are described for each attribute level: `automatic_attribute_whitelist`, `default_attribute_whitelist`, `normal_attribute_whitelist`, and `override_attribute_whitelist`. + +If your automatic attribute data looks like +```` +{ + "filesystem" => { + "/dev/disk0s2" => { + "size" => "10mb" + }, + "map - autohome" => { + "size" => "10mb" + } + } +} +```` +and your config file looks like +```` +automatic_attribute_whitelist = + { + "filesystem" => { + "/dev/disk0s2" => true + } + } +```` +then the entire `map - autohome` subtree will not be saved by the node. + +If your config file looks like `automatic_attribute_whitelist = {}`, then none of your automatic attribute data will be saved by the node. + +The default behavior is for the node to save all the attribute data. This can be ensured by setting your whitelist filter to `nil`. + +Note that only the keys in this has will be used. If the values are anything other than a hash, they are ignored. You cannot magically morph these config options into a blacklist by putting `false` as a value in the whitelist. diff --git a/lib/chef/config.rb b/lib/chef/config.rb index 35b07c24ea..92042d5248 100644 --- a/lib/chef/config.rb +++ b/lib/chef/config.rb @@ -558,6 +558,35 @@ class Chef # the number of threads will help. default :cookbook_sync_threads, 10 + # A whitelisted map of attribute keys you want sent over the wire when node + # data is saved. We will only use the keys in this hash to whitelist attributes; + # if the value are anything than another hash, we will ignore them. (You can't + # magically morph into a blacklist with "false" as a value in the whitelist.) + # + # If your data looks like: + # { "filesystem" => { + # "/dev/disk0s2" => { + # "size" => "10mb" + # }, + # "map - autohome' => { + # "size" => "10mb" + # } + # } + # } + # And your whitelist looks like: + # { "filesystem" => { + # "/dev/disk0s2" => true + # } + # } + # The entire "map - autohome" subtree will be dropped. + # + # The default setting is nil, which collects all data. Setting to {} will not + # collect any data for save. + default :automatic_attribute_whitelist, nil + default :default_attribute_whitelist, nil + default :normal_attribute_whitelist, nil + default :override_attribute_whitelist, nil + # If installed via an omnibus installer, this gives the path to the # "embedded" directory which contains all of the software packaged with # omnibus. This is used to locate the cacert.pem file on windows. diff --git a/lib/chef/node.rb b/lib/chef/node.rb index 6061dbe615..0a3a4f4297 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -520,18 +520,18 @@ class Chef if Chef::Config[:why_run] Chef::Log.warn("In whyrun mode, so NOT performing node save.") else - chef_server_rest.put_rest("nodes/#{name}", self) + chef_server_rest.put_rest("nodes/#{name}", data_for_save) end rescue Net::HTTPServerException => e raise e unless e.response.code == "404" - chef_server_rest.post_rest("nodes", self) + chef_server_rest.post_rest("nodes", data_for_save) end self end # Create the node via the REST API def create - chef_server_rest.post_rest("nodes", self) + chef_server_rest.post_rest("nodes", data_for_save) self end @@ -543,5 +543,32 @@ class Chef self.name <=> other_node.name end + private + + def data_for_save + Chef::Log.info("Whitelisting node attributes for save") + data = for_json + ["automatic", "default", "normal", "override"].each do |level| + whitelist_config_option = "#{level}_attribute_whitelist".to_sym + whitelist = Chef::Config[whitelist_config_option] + unless whitelist.nil? # nil is default, saves everything + data[level] = apply_whitelist_filter(data[level], whitelist) + end + end + data + end + + def apply_whitelist_filter(data, whitelist) + return nil if data.nil? + + new_data = data.reject { |k, v| !whitelist.keys.include? k } + whitelist.each do |k, v| + if v.kind_of? Hash + new_data[k] = apply_whitelist_filter(new_data[k], v) + end + end + new_data + end + end end diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index 832e10f645..7c8dba0b12 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -814,22 +814,25 @@ describe Chef::Node do describe "save" do it "should update a node if it already exists" do node.name("monkey") - @rest.should_receive(:put_rest).with("nodes/monkey", node).and_return("foo") + node.stub(:data_for_save).and_return({}) + @rest.should_receive(:put_rest).with("nodes/monkey", {}).and_return("foo") node.save end it "should not try and create if it can update" do node.name("monkey") - @rest.should_receive(:put_rest).with("nodes/monkey", node).and_return("foo") + node.stub(:data_for_save).and_return({}) + @rest.should_receive(:put_rest).with("nodes/monkey", {}).and_return("foo") @rest.should_not_receive(:post_rest) node.save end it "should create if it cannot update" do node.name("monkey") + node.stub(:data_for_save).and_return({}) exception = double("404 error", :code => "404") @rest.should_receive(:put_rest).and_raise(Net::HTTPServerException.new("foo", exception)) - @rest.should_receive(:post_rest).with("nodes", node) + @rest.should_receive(:post_rest).with("nodes", {}) node.save end @@ -847,6 +850,73 @@ describe Chef::Node do node.save end end + + context "with whitelisted attributes configured" do + before do + @defaults = {} + ["automatic", "default", "normal", "override"].each do |level| + option_key = "#{level}_attribute_whitelist".to_sym + @defaults[level] = Chef::Config[option_key] + end + end + + after do + @defaults.each do |level, setting| + option_key = "#{level}_attribute_whitelist".to_sym + Chef::Config[option_key] = setting + end + end + + it "should only save whitelisted attributes (and subattributes)" do + Chef::Config[:automatic_attribute_whitelist] = { + "filesystem" => { "/dev/disk0s2" => true } + } + + data = { + "automatic" => { + "filesystem" => { + "/dev/disk0s2" => { "size" => "10mb" }, + "map - autohome" => { "size" => "10mb" } + } + } + } + + selected_data = { + "automatic" => { + "filesystem" => { + "/dev/disk0s2" => { "size" => "10mb" } + } + } + } + + node.name("picky-monkey") + node.stub(:for_json).and_return(data) + @rest.should_receive(:put_rest).with("nodes/picky-monkey", selected_data).and_return("foo") + node.save + end + + it "should not save any attributes if the whitelist is empty" do + Chef::Config[:automatic_attribute_whitelist] = {} + + data = { + "automatic" => { + "filesystem" => { + "/dev/disk0s2" => { "size" => "10mb" }, + "map - autohome" => { "size" => "10mb" } + } + } + } + + selected_data = { + "automatic" => {} + } + + node.name("picky-monkey") + node.stub(:for_json).and_return(data) + @rest.should_receive(:put_rest).with("nodes/picky-monkey", selected_data).and_return("foo") + node.save + end + end end end |