Kenneth Baltrinic
Kenneth Baltrinic

Reputation: 2981

How to invoke resources from within an LWRP Provider

Let me start off by admitting that I am having a very hard time getting my head around the whole resource authoring tech stack. There are LWRP's, Library Resources, Heavyweight resources and I can't seem to find the end of the string so to speak to start unravelling this ball of yarn. Therefore, this is a somewhat expansive multi-part question.

The crux of it is that I have a working recipe that invokes a series of resources in order to partition, format and mount linux devices. Now I want to turn it into a LWRP (or if LWRPs are not suitable, any suitable R/P approach) so that I can invoke it multiple times in a parameterized fashion.

Here is my initial attempt, questions about it to follow:

# Resource:: mount_drive_as
#
# Will idempotently find a physical drive (/dev/sdb, /dev/sdc, etc), 
# create a primary partition using the entire drive and formated to ext4 
# and mount it at the specified mount point.
# An fstab entry is also created to auto mount the partition.

actions :create
default_action :create

#Path to the location where the drive will be mounted, e.g. /data
attribute :mount_point,
          :name_attribute => true,
          :kind_of => String,
          :required => true

attribute :device,
          :regex => [ /^sd[a-z]$/ ]
          :default => 'sdb'

And my provider (mostly just a cut and past of the original recipe at this point):

#ensures parted is installed.
run_context.include_recipe 'parted'

def whyrun_supported?
  true
end

action :create do
  parted_disk "/dev/#{@new_resource.device}" do
    label_type "gpt"
    action :mklabel
  end

  parted_disk "/dev/#{@new_resource.device}" do
    part_type   "primary"
    file_system "ext4"
    action :mkpart
  end

  parted_disk "/dev/#{@new_resource.device}1" do
    file_system "ext4"
    action :mkfs
  end

  replace_or_add "add /dev/#{@new_resource.device}1 to /etc/fstab" do
    path "/etc/fstab"
    pattern "^/dev/#{@new_resource.device}1"
    line "/dev/#{@new_resource.device}1   #{@new_resource.mount_point}   ext4   defaults   0 0"
  end

  directory @new_resource.mount_point

  execute 'mount /dev/#{@new_resource.device}1'

end

So I have two basic questions:

  1. How do I correctly invoke these other resources from within the :create action.
  2. How to I aggregate the result of all of their updated_by_last_action() invocations so that I can correctly call updated_by_last_action() in my code.
  3. Said another way, how do I correctly support whyrun in this scenario?

I realize that some of the recipes I am using above (in particular replace_or_add from the line cookbook and probably execute) are probably better done other ways from with a resource but I can address that later. It is really the parted resources that I am most interested in leveraging here.

Upvotes: 1

Views: 1125

Answers (1)

Tejay Cardon
Tejay Cardon

Reputation: 4223

To address your questions: 1. If you add use_inline_resources you can leave your code as is 2. If you add use_inline_resources then this is taken care of for you (in this case) 3. Same as the other two.

Now, to the issue of the big ball of yarn, hopefully this will help.

OK, so there are four ways that I know of to create resource/providers. In order of appearance: 1. Heavy Weight 2. LWRP 3. Middle Weight?? 4. Poise

Heavy Weight

Originally, this was the only way to do it. You add the new classes into your libraries directory, and they are made available. Traditionally, any time you use resources in these, you will set those resources' action to :nothing, and then run_action immediately to get the effect.

file 'somefile' do
  action :nothing
end.run_action(:create)

However, this resulted in dummy resources being added to the stack. You also had to them query the resource for it's updated state, and set accordingly.

f = file 'somefile' do
  action :nothing
end.run_action(:create)
@new_resource.updated_by_last_action f.updated_by_last_action

Add to this the fact that the files were much larger than they really needed to be, we got the LWRP concept.

LWRP

LWRP was a new set of libraries that allowed you to put files in resources and providers. These files were evaluated with the LWRP library, and allowed you to define resources and providers in a more DSL style. So you could define attributes, for example, by calling a method. Initially they shared many of the drawbacks of HWRP, but the files were much cleaner and smaller. The major downside was that you couldn't do any form of inheritance.

Eventually, the idea of use_inline_resources was introduced. By putting this method in your LWRP provider, you could stop worrying about updated_by_last_action. It also had other benefits that you can read about in the documentation. In short, all resources declared in a provider are executed in a 'mini' chef run. If any of them are updated, then the LWRP resource is marked as updated automatically. Furthermore, because of this inner chef run, none of these resources are added to the external chef run's resource stack. (this is both good and bad as we'll see more about in Poise)

MWRP

Some people wanted the best of both worlds, and realized that if they extended the LWRPBase class, they could define a "base" resource and provider and then inherit from those, while still getting the clean methods from LWRP. Not much more to say about that. It's cool, but basically just a blend of HWRP and LWRP. These go in the libraries directory

Poise

Noah K. decided that none of these was right. If I remember correctly, he was deeply involved in writing LWRP, but had felt some of its shortcomings. So about a year ago (I think) he released the Poise cookbook. This is really a library for writing a new kind of resource/provider. With poise, you get a nice balance of LWRP and MWRP. You simple require poise in your library file and depends poise in your metadata.rb. Poise implements all of your favorite methods from the LWRP library, plus some other really useful things.

Rather than use_inline_resources, poise uses notifying_block. The major difference between the two is that notifying_block resources ARE added to the main chef run stack. This is a big deal, because it allows other resources to notify them. (for example to restart a service). It also allows other resources to subscribe to them.

You can see the full documentation for Poise at the github project.

Personally, I am doing all my new LWRPs with poise because I think it's the best option thus far.

Upvotes: 1

Related Questions