summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--DOC_CHANGES.md34
-rw-r--r--lib/chef/config.rb29
-rw-r--r--lib/chef/node.rb33
-rw-r--r--spec/unit/node_spec.rb76
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