I’m trying to write a package resource wrapper cookbook, say company_package, that will overwrite the default retries and retry_count attributes for the package resource. Of course, for the cookbook to fully support the package cookbook, I have to define the other attributes.
For notifies, which accepts 3 different parameters, what would the Ruby type, that is, what would be its :kind_of syntax in the resource DSL? I’ve read this https://docs.chef.io/resource_package.html
Chris
I am not sure what you mean: If you want to define attributes like notfies, not_if, only_if etc. yourself I am not sure if that is possible at all but it for sure is not necessary. Look at the “common functionaly” in the chef docs. Those are things that are inherited from the base classes. Look for the old documentations of HWRP if you want to know a little more about custom resources and how they work internally.
Here’s how I define my attributes in the resource/default.rb file of my LWRP
attribute :notifies, :name_attribute => false, :kind_of => ???, :required => false
What should the “kind_of” be?
It needs to ultimately support this
# my_company package is an LWRP that wraps the package resource
mycompany_package 'some_package' do
notifies :restart, 'service[some_package1]', :immediately
end
IOW it needs to be a “kind_of” that supports an array of Strings and Symbols. So should it be like this?
attribute :notifies, :name_attribute => false, :kind_of => [String, Symbol], :required => false
You should not include the base attributes such as notifies
, action
, etc. You get those “for free” from chef::Resource
and LWRPBase
derrives from that.
Thanks Matt, but that was NOT my experience. For example, I had the following in one of my recipes
mycompany_package 'java' do
flush_cache [:before, :before]
end
and I got an error that flush_cache was not defined in mycompany_package resource? That is when I went down this rabbit hole defining all the attributes the package resource has. What am I missing?
Here’s what I really (just) need.
I want to override the retries and retry_count attributes of the package resource so I can just replace package resource in ALL my recipes with mycompany_package, which will use the MY retries and retry_count values. I know I can just modify my package resource calls in my recipes to include these two values, but I have a LOT of package resource calls. Make sense?
Matt is talking about the base functionality that all resources have, i.e., the stuff documented here: https://docs.chef.io/resource_common.html It includes notifications and not_if/only_if.
In your example, flush_cache
is only relevant to a package (i.e., it would make no sense for, say, a file resource to have this property), which is why it is not in the base resource.
A note about the more general aspect of your question: the parameter validation in Chef doesn’t handle every case, it’s designed to handle simple cases easily, but for parameters that have several arguments or more complex types (e.g., a Hash
with specific kinds of keys and values or a nested structure), you can’t express the validation constraints with the built-in parameter validation. You have to either omit parameter validation or roll your own.
Thanks. In my case, I was trying to wrap a package resource, where indeed a flush_cache attribute makes sense?
To a human it makes perfect sense, but ruby has no way to know that; it just sees that you have a class MyPackage
that inherits from Chef::Resource
(which inherits from Object
) and none of those things define that method.
To do what you want, there's a few options:
- Add all the properties of package that your users want to set to your LWRP and then copy them down to the "real" package resource. This is a bit tedious, but it has a lot of benefits. Look up "composition over inheritance" if you want to read more about that.
- Use a DSL method instead of a LWRP to do the wrapping. This is probably trickier if you're new to ruby/chef, but in the end would be a pretty simple implementation. Something like this:
def mycorp_package(name, &block)
r = package(name, &block)
r.retries(5)
r.retry_delay(600)
end
You would put that in a module in a library and then include that into Chef::DSL::Recipe
3. Do a more HWRP style and have your custom package class inherit from Chef::Resource::Package
. This will get you all the package properties automatically, but getting the provider stuff wired up correctly could be a pain. Your code will also be tightly coupled to the parent class, which exposes you to additional breakage conditions compared to a composition technique.
HTH
Thanks you. I’ve been exploring your option 1, and yes the only way for this to make sense is if I include ALL the attributes of the Package resource in my resource/default.rb file. Hence I asked the question, what should this line look like?
attribute :notifies, :name_attribute => false, :kind_of => ???
I’m also now looking at your option 3 regarding doing a HWRP instead.
Maybe I’ll combine a LWRP + HWRP.
@kallistec
I’m now exploring your option #3 - HWRP. This is my first HWRP. I have fundamental questions.
I have a cookbook named mycorp_package. Hence I have a libraries/mycorp_package.rb file that may have
class Chef
class Provider
class Package
class MyCorpPackage < Chef::Provider::Package
def load_current_reource
................
end
def install_package
..............
end
def remove_package
..............
end
end
end
end
end
Questions:
-
Since I’m extending the package resource, I don’t need a corresponding libraries/some_resource.rb file?
-
How do I now call this HWRP in another cookbook? I tried the following but I get a compile error
mycorp_package ‘some_package’ end
action :install
end
NoMethodError
No resource or method named mycorp_package' for
Chef::Recipe “default”’
Hello
The resource describes what the desired state of the object (such as file, directory, package).
The provider is the bit of code that will carry out the work to make the object look likes it’s desired state.
In your recipe you are describing resources, these resources are actually Ruby classes that store the state. You have written a provider to carry out your actions to get to your desired state but have you coded a mycorp_package resource to store what the state should be?
As a first shot I would write a LWRP not a HWRP but it’s whatever you feel comfortable with.
@Matt_Wrock
Again, I need my LWRP to support this
mycompany_package 'package_name' do
action :install
notifies :restart, 'service[some_service]', :immediately
end
If I understand correctly, I need to create the ‘action’ and ‘notifies’ attributes for my resource, correct?
How do I get my LWRP to work the way I want if I don’t set a “notifies” attribute? I’ll need to do this in my resource/default.rb file…
attribute :notifies, :name_attribute => false, :kind_of => ???, :required => false
So I can do the following in my provider/default.rb, correct?
action :install do
package new_resource.name do
notifies new_resource.notifies
end
end
You should not have to implement :notifies
. I know that might seem a bit odd and you are wondering why it would “jut works”. The reason is that when you create an LWRP, chef creates a class that derives from LWRPBase
and that derrives from Chef::Resource
which provides the notifies
method (see https://github.com/chef/chef/blob/master/lib/chef/resource.rb#L243).
That said, I’m guessing that the reason you are asking this is that you have already tried this and notifies did not work. Is that a correct guess? If so, what did happen? Anything? Was there an exception or did notifies simply not fire?
I get the following when it gets to the notifies call inside my action :Install block in my provider/default.rb file
ArgumentError
-------------
wrong number of arguments (0 for 2..3)
would you be able to share your code repo link or a gist to your action :Install
?
It’s simply this, in my provider/deffault.rb file
action :install do
package new_resource.name do
action :install
retries new_resource.retries
retry_delay new_resource.retry_delay
notifies new_resource.notifies
end
end
To work with this, in my resource/default.rb file
# Package name
attribute :name, String, required: true, name_property: true
# Override the retires and retry_delay attributes, which is the whole purpose of wrapping
# the package resource into mycompany_package LWRP
# Retries
attribute :retries, Integer, required: false, default: 3
# Delay between retries
attribute :retry_delay, Integer, required: false, default: 5
You don’t need to proxy notifies
like that. It is handled for you already.
How does this
mycompany_package 'package_name' do
action :install
notifies :restart, 'service[some_service]', :immediately
end
Ultimately end up to be this?
action :install do
package new_resource.name do
action :install
retries new_resource.retries
retry_delay new_resource.retry_delay
notifies :restart, 'service[some_service]', :immediately
end
end
I’m (and I know it’s me) missing that connection!
This has to work too.
mycompany_package 'package_name' do
action :install
notifies :start, 'service[some_service]'
end
It doesn’t per se. All sub-resources within the action method get checked for their updated?
flag when the action completes, and if it any are set, it sets the flag on the custom resource. That in turn triggers notifications declared on the custom resource instance. It’s a more generic form of what you describe.