5

I'm trying to set the default value of one resource attribute to the value of another attribute.

I'm defining a resource with the following definitions in it for a tomcat cookbook I'm building. I want to have "name" and "service_name" attributes that can be set independently. When service name is not set, I want it to default to whatever is provided for "name."

The following does not work as I would expect:

attribute :name,         :kind_of => String, :required => true, :name_attribute => true
attribute :service_name, :kind_of => String, :default => :name

Notice the ":default => :name" at the end of the second line. When I refer to my resource in a new block in a recipe as such

my_tomcat "install tomcat" do
  name "foo_bar"
end

The attribute values get assigned as

 name = "foo_bar"
 service_name = "name"

which is not what I expected. I wanted service_name to be "foo_bar" unless it was explicitly set.

I've tried

attribute :service_name, :kind_of => String, :default => new_resource.name
attribute :service_name, :kind_of => String, :default => @new_resource.name

but those don't compile.

Is there a way to do what I'm trying to do?

Rich Mills
  • 99
  • 1
  • 6

2 Answers2

8

Since those are class-level methods, you need to use the lazy attribute:

attribute :service_name, kind_of: String, default: lazy { |r| r.name }

It's also worth noting that:

attribute :name, kind_of: String, required: true, name_attribute: true

is entirely redundant. That's the default...

sethvargo
  • 26,739
  • 10
  • 86
  • 156
  • Thanks. Just tried it, but it fails to compile: "undefined method `lazy'". However, it led me to the fact that the syntax is just "name" and not ":name" or "new_resource.name" in this context. Using just "name" however results in the lazy evaluation problem. Do I need to convert it into a dynamic assignment for 'lazy' to become acceptable here? – Rich Mills Jun 04 '14 at 15:47
  • Not sure. Maybe you're on an old version of Chef? http://docs.opscode.com/resource_common.html#lazy-attribute-evaluation – sethvargo Jun 04 '14 at 15:58
  • Chef 11.12. I can use 'lazy' in as part of a resource (similar to the example), but not in default: clause the way you suggested. However, based on your help, I think I figured out the answer using a dynamic accessor method. I'll post it below. – Rich Mills Jun 04 '14 at 17:21
  • @sethvargo this completely failed for me when I tried it, as the commentator above says. I thought it said `lazy` was undefined, but now it works, and if I supply a block param it works exactly as wanted: `... :default => lazy {|r| "blah #{r.name}" }`. Sorry! I tried to retract the downvote but it says I can't unless the answer is edited. – Andrew France Sep 11 '14 at 15:47
  • Is r the run context object? – red888 Oct 21 '16 at 17:52
2

I was unable to use Seth's "lazy" evaluation, but was able to simulate by creating a dynamic accessor method.

This other post was useful: How to implement a dynamic attribute default in chef LWRP definition

First, the definition in my resource definition file:

attribute :service_name,        :kind_of => String, default: nil

Next, the accessor block at the bottom of the same resource definition file:

def service_name( arg=nil )
  if arg.nil? and @service_name.nil?
    set_or_return( :service_name, @name, :kind_of => String)
  else
    set_or_return( :service_name, arg, :kind_of => String )
  end
end

Which effectively sets the value of "service_name" the first time it is used in my provider code.

This works for all combinations of

resource "title" do
  # name defaults to "title"
  # service_name defaults to "title"
end
resource "title" do
  name "my_name"
  # service_name defaults to "my_name"
end
resource "title" do
  name "my_name"
  service_name "my_service_name"
end

Thanks, again, for your help. I hope someone else finds this useful in the future.

Rich

Community
  • 1
  • 1
Rich Mills
  • 99
  • 1
  • 6