Chef resources inherit not_if and only_if from previous copies

Greetings fellow chefs,

I believe I’ve found a bug in the way that resources are copied from one to
the next during a recipe run. This bug only manifests itself when you are
re-using a resource with the same name. My opinion is that only_if and
not_if should be used to control the flow of actions that you use on the
current_resource, not inherited from previous resources. The primary
example I use is the service “mysql” resource. Since this service is likely
to be called multiple times in a recipe with different actions it can be
confusing when it inherits a conditional from a prior action … I’ve filed
a bug here: http://tickets.opscode.com/browse/CHEF-894 and I’m pasting an
example below that’s taken from the Rightscale public cookbooks for
db_mysql.

Do the maintainers approve this change? Any alternatives or suggestions?
Right now I have to be really careful with my not/only_ifs and override the
un-wanted copies to get the behavior I need…

=== SNIP from
http://github.com/rightscale/cookbooks_public/blob/master/cookbooks/db_mysql/recipes/install_mysql.rb#L125===>

service “mysql” do
only_if do
right_platform = node[:platform] == “ubuntu” &&
(node[:platform_version] == “8.04” ||
node[:platform_version] == “8.10”)

right_platform && node[:db_mysql][:kill_bug_mysqld_safe]

end

action :stop
end

ruby_block “fix buggy mysqld_safe” do
only_if do
right_platform = node[:platform] == “ubuntu” &&
(node[:platform_version] == “8.04” ||
node[:platform_version] == “8.10”)

right_platform && node[:db_mysql][:kill_bug_mysqld_safe]

end
block do

node[:db_mysql][:kill_bug_mysqld_safe] = false
end
end

service “mysql” do

override this back to the default for future copies of the resource

only_if do true end
not_if do ::File.symlink?(node[:db_mysql][:datadir]) end
action :stop
end

… more recipe code …

service “mysql” do

override this back to the default for future copies of the resource

not_if do false end
Chef::Log.info "Attempting to start mysql service"
action :start
end

On Thu, Feb 11, 2010 at 4:22 PM, Jeremy Deininger jeremy@rightscale.com wrote:

Greetings fellow chefs,

I believe I've found a bug in the way that resources are copied from one to
the next during a recipe run. This bug only manifests itself when you are
re-using a resource with the same name. My opinion is that only_if and
not_if should be used to control the flow of actions that you use on the
current_resource, not inherited from previous resources. The primary
example I use is the service "mysql" resource. Since this service is likely
to be called multiple times in a recipe with different actions it can be
confusing when it inherits a conditional from a prior action .. I've filed
a bug here: http://tickets.opscode.com/browse/CHEF-894 and I'm pasting an
example below that's taken from the Rightscale public cookbooks for
db_mysql.

Do the maintainers approve this change? Any alternatives or suggestions?
Right now I have to be really careful with my not/only_ifs and override the
un-wanted copies to get the behavior I need..

=== SNIP from
http://github.com/rightscale/cookbooks_public/blob/master/cookbooks/db_mysql/recipes/install_mysql.rb#L125
===>

service "mysql" do
only_if do
right_platform = node[:platform] == "ubuntu" &&
(node[:platform_version] == "8.04" ||
node[:platform_version] == "8.10")

right_platform && node[:db_mysql][:kill_bug_mysqld_safe]

end

action :stop
end

ruby_block "fix buggy mysqld_safe" do
only_if do
right_platform = node[:platform] == "ubuntu" &&
(node[:platform_version] == "8.04" ||
node[:platform_version] == "8.10")

right_platform && node[:db_mysql][:kill_bug_mysqld_safe]

end
block do

...
node[:db_mysql][:kill_bug_mysqld_safe] = false

end
end

service "mysql" do

override this back to the default for future copies of the resource

only_if do true end
not_if do ::File.symlink?(node[:db_mysql][:datadir]) end
action :stop
end

... more recipe code ...

service "mysql" do

override this back to the default for future copies of the resource

not_if do false end
Chef::Log.info "Attempting to start mysql service"
action :start
end

Hrm - in general, I think of not_if and only_if less as conditional
controls (although they absolutely are that) and more as idempotency
controls. For example:

service "mysql" do
only_if do
right_platform = node[:platform] == "ubuntu" &&

                (node[:platform_version] == "8.04" ||

                 node[:platform_version] == "8.10")
right_platform && node[:db_mysql][:kill_bug_mysqld_safe

end
action :stop
end

Always creates an entry in the resource collection that wants to stop
service[mysql], regardless of platform. You could achieve the same
thing with:

if platform?("ubuntu") && (node[:platform_version] == "8.04" ||
node[:platform_version] == "8.10") &&
node[:db_mysql][:kill_bug_mysqld_safe]
service "mysql" do
action :stop
end
end

Which will result in no resource with a stop action being added to the
resource collection at all.

In general that's going to work, assuming your guard is not reliant on
the system state being altered by actually executing the resource
collection.

When not_if/only_if are used to control idempotence, inheritance is
the right thing to do - because the assumption is that the provider in
question isn't smart enough to figure out whether or not the action
should be taken.

So in general, I think it's better to move the guard statement outside
of the not_if/only_if block, and up into a legitimate guard statement,
and have the resource note even appear in the collection.

Adam

--
Opscode, Inc.
Adam Jacob, CTO
T: (206) 508-7449 E: adam@opscode.com