Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

add lwrp introduction section

  • Loading branch information...
commit 32ff2003a25160b5fbff17dbdf949586a423bf53 1 parent a736953
Joshua Timberman jtimberman authored

Showing 1 changed file with 454 additions and 14 deletions. Show diff stats Hide diff stats

  1. +454 14 slides/lwrp-introduction/01_slide.md
468 slides/lwrp-introduction/01_slide.md
Source Rendered
@@ -2,48 +2,488 @@
2 2
3 3 Section Objectives:
4 4
  5 +* Components of LWRPs
  6 +* Resource DSL
  7 +* Provider DSL
  8 +* Build an LWRP from scratch
  9 +* Opscode Example LWRPs
  10 +
5 11 .notes These course materials are Copyright © 2010-2012 Opscode, Inc. All rights reserved.
6 12 This work is licensed under a Creative Commons Attribution Share Alike 3.0 United States License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/us; or send a letter to Creative Commons, 171 2nd Street, Suite 300, San Francisco, California, 94105, USA.
7 13
8 14 # When to use LWRPs
9 15
10   -# Resource DSL
  16 +Lightweight Resources and Providers are used when you want to abstract
  17 +some repeated pattern of behavior on the system with a declarative
  18 +interface that doesn't already exist in Chef.
  19 +
  20 +Examples of LWRPs use:
  21 +
  22 +* Adding configuration for a yum repository
  23 +* Managing a service with a new supervision system, e.g. bluepill
  24 +* Subsystem user management, e.g. samba
  25 +* Tools that use "conf.d" configuration inclusion
11 26
12   -action keyword
  27 +# Components of an LWRP
13 28
14   -attribute keyword
  29 +LWRPs have two components, the resource and the provider. They live in
  30 +the `resources` and `providers` directory of cookbooks, respectively.
15 31
16   -validation parameters
  32 +Chef uses the cookbook name and the resource and provider file names
  33 +to create the resource that is used in recipes. If the filename is
  34 +"default" then only the cookbook name needs to be used in the recipe.
  35 +For example, if we have a cookbook named "mouse":
17 36
18   -parts of attributes
  37 + | File | Resource Name | Generated Provider |
  38 + |-------------------------|---------------|-----------------------|
  39 + | mouse/resources/default | mouse | Chef::Resource::Mouse |
  40 + | mouse/providers/default | mouse | Chef::Provider::Mouse |
19 41
20   -setting a default action
  42 +# Using the LWRP in a Recipe
  43 +
  44 +When we use the resource in a recipe, we write something like this:
  45 +
  46 + @@@ruby
  47 + mouse "Squeak" do
  48 + action :say
  49 + end
  50 +
  51 +# Resource DSL
  52 +
  53 +The resource DSL is very simple. There are two methods used,
  54 +`actions` and `attribute`.
  55 +
  56 +The `actions` method specifies the allowed actions that can be used
  57 +in a recipe. It is a comma separated list of Ruby symbols. Each action
  58 +corresponds to an `action` method in the provider.
  59 +
  60 +The `attribute` method specifies a new parameter attribute for the
  61 +resource. The attribute's name must be the first parameter, and it
  62 +should be a Ruby symbol. Optionally, a hash of validation parameters
  63 +can be specified.
  64 +
  65 +.notes We will discuss validation parameters in more detail when we
  66 +get to building an example.
21 67
22 68 # Provider DSL
23 69
24   -action method
  70 +The provider DSL only defines a single method, `action`. You specify
  71 +the action name with a Ruby symbol, which is one of the allowed
  72 +actions defined in the resource `actions` list. The method takes a
  73 +block, which is the code that does whatever is required to configure
  74 +the resource for that particular action.
  75 +
  76 +Each provider's action methods can re-use Chef Resources, meaning you
  77 +can use existing Chef resources for free inside the action methods.
  78 +
  79 +.notes We're going to look at building an example next.
  80 +
  81 +# Build an LWRP from Scratch
  82 +
  83 +We're going to build a contrived example to illustrate the various
  84 +components of an LWRP. The resource will be named `mouse`, and we will
  85 +make use of it in `recipe[mouse]`. The recipe should be applied to a
  86 +node's run list. At each step, upload the cookbook and run Chef to
  87 +observe the output.
  88 +
  89 + > mkdir -p cookbooks/mouse/recipes
  90 + > touch cookbooks/mouse/recipes/default.rb
  91 +
  92 +Write the resource in a recipe. Since this is in the cookbook named
  93 +`mouse`, the resource is named `mouse`.
  94 +
  95 + @@@ruby
  96 + mouse "Squeak"
  97 +
  98 +# Run Chef
  99 +
  100 +Upload the cookbook, apply the recipe to a node and run Chef:
  101 +
  102 + > knife cookbook upload mouse
  103 + > knife node run list add NODENAME 'recipe[mouse]'
  104 + NODENAME$ sudo chef-client
  105 +
  106 +When Chef runs we will see this:
  107 +
  108 + FATAL: NoMethodError: undefined method `mouse' for #<Chef::Recipe:0x007fa1f4842bf8>
  109 +
  110 +# Create the Resource
  111 +
  112 +Create the resource so Chef knows about it. Resources go in the
  113 +`resources` directory in the cookbook. The file for the resource is
  114 +`default.rb`.
  115 +
  116 + > mkdir -p cookbooks/mouse/resources
  117 + > touch cookbooks/mouse/resources/default.rb
  118 +
  119 +Upload the cookbook again and run Chef; we will see this error:
  120 +
  121 + INFO: Processing mouse[Squeak] action nothing (mouse::default line 1)
  122 + ERROR: mouse[Squeak] (mouse::default line 1) has had an error
  123 + ERROR: mouse[Squeak] (cookbooks/mouse/recipes/default.rb:1:in
  124 + `from_file') had an error: mouse[Squeak] (mouse::default line 1)
  125 + had an error: ArgumentError: Cannot find a provider for
  126 + mouse[Squeak] on mac_os_x version 10.7.3
  127 +
  128 +# Provider Lookup
  129 +
  130 + INFO: Processing mouse[Squeak] action nothing (mouse::default line 1)
  131 + ERROR: mouse[Squeak] (mouse::default line 1) has had an error
  132 + ERROR: mouse[Squeak] (cookbooks/mouse/recipes/default.rb:1:in
  133 + `from_file') had an error: mouse[Squeak] (mouse::default line 1)
  134 + had an error: ArgumentError: Cannot find a provider for
  135 + mouse[Squeak] on mac_os_x version 10.7.3
  136 +
  137 +Chef looks for a provider for the resource. With LWRPs, the provider
  138 +is in the same cookbook as the resource, in the resources directory.
  139 +
  140 +The platform and version are printed because Chef tries to map the
  141 +provider to the resource based on the node's platform and platform
  142 +version.
  143 +
  144 +# Create the Provider
  145 +
  146 +Create the provider so Chef can find it for the resource. Providers go
  147 +in the `providers` directory. The file for the provider is
  148 +`default.rb` as we are using the default resource.
  149 +
  150 + > mkdir -p cookbooks/mouse/providers
  151 + > touch cookbooks/mouse/providers/default.rb
  152 +
  153 +When Chef runs we will see this:
  154 +
  155 + INFO: Processing mouse[Squeak] action nothing (mouse::default
  156 + line 1)
  157 +
  158 +The default action for a newly created resource is `nothing`.
  159 +
  160 +# Set an Action in the Recipe
  161 +
  162 +Pass the action parameter to the resource in the recipe. The mouse
  163 +should say something with the `say` action.
  164 +
  165 + @@@ruby
  166 + mouse "Squeak" do
  167 + action :say
  168 + end
  169 +
  170 +When Chef runs we will see this:
  171 +
  172 + FATAL: Chef::Exceptions::ValidationFailed: Option action must be
  173 + equal to one of: nothing! You passed :say.
  174 +
  175 +The resource does not list `say` is a valid action; the default action
  176 +for any new resource in Chef is `nothing`.
  177 +
  178 +# Create Allowed Actions List
  179 +
  180 +Use the `actions` keyword in the resource to define an allowed action.
  181 +
  182 + @@@ruby
  183 + actions :say
  184 +
  185 +The action should be a Ruby symbol (starting with `:`). Now with the
  186 +action defined as allowed, when Chef runs we see this:
  187 +
  188 + INFO: Processing mouse[Squeak] action say (mouse::default line 1)
  189 + ERROR: mouse[Squeak] (mouse::default line 1) has had an error
  190 + ERROR: mouse[Squeak] (cookbooks/mouse/recipes/default.rb:1:in
  191 + `from_file') had an error:
  192 + mouse[Squeak] (mouse::default line 1) had an error: NoMethodError:
  193 + undefined method `action_say'
  194 + for #<Chef::Provider::Mouse:0x007fb661060198>
  195 +
  196 +# Resource Calls Action Method
  197 +
  198 + INFO: Processing mouse[Squeak] action say (mouse::default line 1)
  199 + ERROR: mouse[Squeak] (mouse::default line 1) has had an error
  200 + ERROR: mouse[Squeak] (cookbooks/mouse/recipes/default.rb:1:in
  201 + `from_file') had an error:
  202 + mouse[Squeak] (mouse::default line 1) had an error: NoMethodError:
  203 + undefined method `action_say'
  204 + for #<Chef::Provider::Mouse:0x007fb661060198>
  205 +
  206 +Chef attempts to call the `say` action method. The method has not been
  207 +written in the provider yet, resulting in this error.
  208 +
  209 +# Create the Action Method
  210 +
  211 +Write a simple method that prints a string with `puts`. The name of
  212 +the method, `:say`, should be a Ruby symbol.
  213 +
  214 + @@@ruby
  215 + action :say do
  216 + puts "My name is #{new_resource.name}"
  217 + end
  218 +
  219 +When Chef runs we will see this:
  220 +
  221 + INFO: Processing mouse[Squeak] action say (mouse::default line 1)
  222 + My name is Squeak
  223 +
  224 +Chef will call the provider's `say` action as the `action_say` method,
  225 +as shown earlier with the NoMethodError.
  226 +
  227 + mouse[Squeak] (mouse::default line 1) had an error: NoMethodError:
  228 + undefined method `action_say'
  229 + for #<Chef::Provider::Mouse:0x007fb661060198>
25 230
26   -`load_current_resource`
  231 +# Inspecting the Action Method
27 232
28   -telling Chef the resource was updated
  233 +The action method takes a block. In this example, we used the Ruby
  234 +puts method to print the string to standard output.
  235 +
  236 +Our example uses Ruby's string interpolation `#{}` to print the value
  237 +of `new_resource.name`.
  238 +
  239 +In a Chef provider, an object called `new_resource` is created, which
  240 +represents the resource as it was written in the recipe.
  241 +
  242 +Again, our recipe has this resource:
  243 +
  244 + @@@ruby
  245 + mouse "Squeak" do
  246 + action :say
  247 + end
  248 +
  249 +# Inspecting the Action Method
  250 +
  251 + @@@ruby
  252 + mouse "Squeak" do
  253 + action :say
  254 + end
  255 +
  256 +Chef automatically creates an accessor attribute for all new resources
  257 +called `name`, which is "Squeak" in our example. When Chef runs, this
  258 +provider then prints out this string as that is what we put in the
  259 +action.
  260 +
  261 +# Recipe DSL in Providers
  262 +
  263 +Chef's LWRPs allow you to reuse the Chef recipe DSL, which means you
  264 +can write resources inside the provider's actions.
  265 +
  266 +Convert `puts` to a Chef `log` resource.
  267 +
  268 + @@@ruby
  269 + action :say do
  270 + log "My name is #{new_resource.name}"
  271 + end
  272 +
  273 +When Chef runs we will see this:
  274 +
  275 + INFO: Processing mouse[Squeak] action say (mouse::default line 1)
  276 + INFO: Processing log[My name is Squeak] action write
  277 + (cookbooks/mouse/providers/default.rb line 2)
  278 + INFO: My name is Squeak
  279 +
  280 +# Recipe DSL in Providers
  281 +
  282 + INFO: Processing mouse[Squeak] action say (mouse::default line 1)
  283 + INFO: Processing log[My name is Squeak] action write
  284 + (cookbooks/mouse/providers/default.rb line 2)
  285 + INFO: My name is Squeak
  286 +
  287 +Chef creates two resources here. First is the enclosing resource, the
  288 +`mouse` LWRP. Second is the `log` resource used in the provider.
  289 +
  290 +Note the `INFO:` before the printed string. The `log` resource uses
  291 +Chef's logger object, which prints messages at
  292 +`Chef::Config[:log_level]` "info" by default.
  293 +
  294 +# Create Resource Attribute Parameters
  295 +
  296 +The `mouse` resource isn't particularly descriptive. It really only
  297 +shows the name of itself in the `say` action. Add a couple new
  298 +attribute parameters to the resource to use in the provider.
  299 +
  300 + @@@ruby
  301 + actions :say
  302 +
  303 + attribute :given_name, :name_attribute => true
  304 + attribute :noise, :default => "quiet"
  305 + attribute :tail, :default => true, :kind_of => [TrueClass, FalseClass]
  306 +
  307 +These attributes use the LWRP Resource DSL's validation parameters.
  308 +
  309 +# Resource Validation Parameters
  310 +
  311 +The `attribute` method takes two parameters, the name of the attribute
  312 +and a Hash of additional parameters, also called "validation
  313 +parameters".
  314 +
  315 +These attributes are parameters passed into the resource in a recipe,
  316 +they are not node attributes.
  317 +
  318 +# Validation Parameters
  319 +
  320 +Validation parameters are used to ensure that the value of the
  321 +parameter meets the defined requirements. For example, a parameter
  322 +that is used for package name should be a string.
  323 +
  324 + | Validation | Meaning |
  325 + |-----------------+----------------------------------------|
  326 + | :callbacks | Hash of Procs, should return true |
  327 + | :default | Default value for the parameter |
  328 + | :equal_to | Match the value with == |
  329 + | :kind_of | Ensure the value is a particular class |
  330 + | :name_attribute | Set to the resource name |
  331 + | :regex | Match the value against the regex |
  332 + | :required | The parameter must be specified |
  333 + | :respond_to | Ensure the value has a given method |
  334 +
  335 +# Name and Default Attributes
  336 +
  337 + @@@ruby
  338 + attribute :given_name, :name_attribute => true
  339 + attribute :noise, :default => "quiet"
  340 + attribute :tail, :default => true, :kind_of => [TrueClass, FalseClass]
  341 +
  342 +Commonly used in the validation parameters are `:name_attribute`
  343 +and `:default`.
  344 +
  345 +For the mouse resource, the `:given_name` attribute is
  346 +the `:name_attribute`. The `:default` value of `:noise` is the string
  347 +"quiet", since a sane default for mice is that they are quiet.
  348 +
  349 +The `:tail` attribute has a default value of true. An Array of valid
  350 +Ruby classes is specified for `:kind_of`. It is reasonable to leave
  351 +out NilClass because it is `true` by default.
  352 +
  353 +# Extend the Provider's Action
  354 +
  355 +Were Chef to run again, nothing changes in the output because the
  356 +provider has not changed. Extend the provider's action with some
  357 +additional behavior.
  358 +
  359 + @@@ruby
  360 + action :say do
  361 + log "My name is #{new_resource.given_name}"
  362 + unless new_resource.noise =~ /^quiet$/
  363 + log "I say #{new_resource.noise}"
  364 + end
  365 + log "I #{new_resource.tail ? 'do' : 'do not'} have a tail"
  366 + end
  367 +
  368 +The unless statement checks whether the mouse has a noise other than
  369 +"quiet" and if so, prints a log message with the noise.
  370 +
  371 +The last line includes a Ruby ternary operator that checks whether the
  372 +tail attribute was true or false and prints whether the mouse has a
  373 +tail or not.
  374 +
  375 +# Extended Provider Output
  376 +
  377 +When Chef runs we will see:
  378 +
  379 + INFO: Processing mouse[Squeak] action say (mouse::default line 1)
  380 + INFO: Processing log[My name is Squeak] action write
  381 + (cookbooks/mouse/providers/default.rb line 2)
  382 + INFO: My name is Squeak
  383 + INFO: Processing log[I do have a tail] action write
  384 + (cookbooks/mouse/providers/default.rb line 6)
  385 + INFO: I do have a tail
  386 +
  387 +Since the default value for the `noise` attribute in the mouse
  388 +resource is `quiet`, it didn't say anything, and since the default
  389 +value for `tail` is true, the mouse does in fact have his tail.
  390 +
  391 +# Modify Resource in Recipe
  392 +
  393 +Now let's modify the resource in the recipe.
  394 +
  395 + @@@ruby
  396 + mouse "Squeak" do
  397 + noise "SQUEAK!"
  398 + tail false
  399 + action :say
  400 + end
  401 +
  402 +# Running the Modified Resource:
  403 +
  404 +When Chef runs we will see:
  405 +
  406 + INFO: Processing mouse[Squeak] action say (mouse::default line 1)
  407 + INFO: Processing log[My name is Squeak] action write
  408 + (cookbooks/mouse/providers/default.rb line 2)
  409 + INFO: My name is Squeak
  410 + INFO: Processing log[I say SQUEAK!] action write
  411 + (cookbooks/mouse/providers/default.rb line 4)
  412 + INFO: I say SQUEAK!
  413 + INFO: Processing log[I do not have a tail] action write
  414 + (cookbooks/mouse/providers/default.rb line 6)
  415 + INFO: I do not have a tail
  416 +
  417 +Clearly this mouse has squeaked loudly because his tail is missing!
  418 +
  419 +# Default Action
  420 +
  421 +The action had to be specified throughout the example recipe because
  422 +the Resource DSL does not [yet] have the ability to specify the
  423 +default action. An initialize method can be created in the
  424 +`resources/default.rb` to create a default action.
  425 +
  426 + @@@ruby
  427 + def initialize(*args)
  428 + super
  429 + @action = :say
  430 + end
  431 +
  432 +The action is an instance variable, and must begin with an `@`. The
  433 +value must be a Ruby symbol. Now we can remove the `action` in our
  434 +resource in the recipe:
  435 +
  436 + @@@ruby
  437 + mouse "Squeak" do
  438 + noise "SQUEAK!"
  439 + tail false
  440 + end
  441 +
  442 +.notes This is more idiomatic with other resources in Chef, in that they
  443 +define default actions so if one is not specified, the resource does
  444 +pretty much what you expect.
  445 +
  446 +# Contrived Example
  447 +
  448 +This is a completely contrived example, and it does not do much, nor
  449 +would it be sane to continue trying to modify our arbitrary mouse, so
  450 +we will examine some LWRPs in Opscode's cookbooks for further
  451 +examples.
  452 +
  453 +Things we'll see:
  454 +
  455 +* `load_current_resource`
  456 +* resource parameter attributes that track state
  457 +* resources that are not `default`
  458 +* more validation parameters
  459 +* more complex Ruby usage
  460 +* `new_resource.updated_by_last_action(true)`
29 461
30 462 # Opscode LWRPs
31 463
32 464 * `yum_repository`
33 465 * `bluepill_service`
34 466 * `samba_user`
  467 +* `gunicorn_config`
35 468
36   -# Write a Simple Example
37   -
38   -* configure a template
39   -* run a command
  469 +.notes Crack open the text editor in the resource and provider for
  470 +each of these LWRPs.
40 471
41 472 # Summary
42 473
  474 +* Components of LWRPs
  475 +* Resource DSL
  476 +* Provider DSL
  477 +* Build an LWRP from scratch
  478 +* Opscode Example LWRPs
43 479
44 480 # Questions
45 481
46   -*
  482 +* What are the components of an LWRP?
  483 +* What are the resource DSL's two methods?
  484 +* What method is used in providers to create actions?
  485 +* What are validation parameters? Give two examples.
  486 +* What does ":name_attribute" mean? How is it used?
47 487
48 488 # Additional Resources
49 489

0 comments on commit 32ff200

Please sign in to comment.
Something went wrong with that request. Please try again.