Guest User

Untitled

a guest
May 5th, 2016
52
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 8.38 KB | None | 0 0
  1. # 12.5 style custom resources
  2.  
  3. This is by far the most recommended way of writing resources for all users. There are two gotchas which we're working through:
  4.  
  5. 1. For helper functions that you used to write in your provider code or used to mixin to your provider code, you have to use an `action_class do ... end` block.
  6. 2. The 12.5 resources allow for a shorthand notation in provider code where you can refer to properties by their bare name `my_property` and it works _most of the time_. Since it does not work all the time (because of the way ruby scopes things), its recommended to stick to referring to properties by `new_resource.my_property`.
  7.  
  8. You cannot subclass, and must use mixins for code-sharing (which is really a best practice anyway -- e.g. see languages like rust which do not support subclassing).
  9.  
  10. in resources/whatever.rb:
  11.  
  12. ```ruby
  13. resource_name :my_resource
  14. provides :my_resource
  15.  
  16. property :foo, String, name_property: true
  17. extend MyResourceHelperFunctions # probably only used for common properties which is why you extend with class methods
  18.  
  19. action_class do
  20. include MyProviderHelperFunctions
  21.  
  22. def a_helper
  23. end
  24. end
  25.  
  26. action :run do
  27. # helpers must be defined inside the action_class block
  28. a_helper()
  29. # you will save yourself some pain by referring to properties with `new_resource.foo` and not `foo`
  30. # since the latter works most of the time, but will troll you with odd scoping problems, while the
  31. # former just works.
  32. puts new_resource.foo
  33. end
  34. ```
  35.  
  36. # Old school "LWRPS":
  37.  
  38. This method is preferable to writing library providers. It has the same functionality as library providers, only you cannot subclass and must use mixins for code sharing (which is good).
  39.  
  40. in resources/my_resource.rb:
  41.  
  42. ```ruby
  43. resource_name :my_resource
  44. provides :my_resource
  45.  
  46. property :foo, String, name_property: true
  47. extend MyResourceHelperFunctions # probably only used for common properties which is why you extend with class methods
  48. ```
  49.  
  50. in providers/my_resource.rb:
  51.  
  52. ```ruby
  53. use_inline_resources # you still have to do this, if you don't notifications off of this resource will be broken
  54.  
  55. # you have to worry about this
  56. def whyrun_supported?
  57. true
  58. end
  59.  
  60. include MyProviderHelperFunctions
  61.  
  62. def a_helper
  63. end
  64.  
  65. action :run do
  66. a_helper()
  67. # here you have to use new_resource.foo
  68. puts new_resource.foo
  69. end
  70. ```
  71.  
  72. # Library Resources and Providers
  73.  
  74. Library resources are discouraged since you can more easily shoot yourself in the foot. They used to be encouraged back before Chef 12.0 `provides` was introduced since it allowed for renaming the resource so that it didn't have to be prefixed by the cookbook name.
  75.  
  76. There are many ways to go wrong writing library providers. One of the biggest issues is that internal chef-client code superficially looks like a library provider but is not. Our internal resources do not inherit from LWRPBase and we've had to manually create resources directly through `Chef::Resource::File.new()`, we also have not been able to use_inline_resources and not had access to other nicities that cookbook authors have had access to for years now. We've got some modernization of internal chef cookbook code now and resources like apt_update and apt_repository in core have started to be written more like cookbook code should be written, but core resources are actually behind the curve and are bad code examples.
  77.  
  78. in libraries/resource_my_resource.rb:
  79.  
  80. ```ruby
  81. class MyBaseClass
  82. class Resource
  83. class MyResource < Chef::Resource::LWRPBase # it is very important to inherit from LWRPBase
  84. resource_name :my_resource
  85. provides :my_resource
  86.  
  87. property :foo, String, name_property: true
  88. extend MyResourceHelperFunctions # probably only used for common properties which is why you extend with class methods
  89. end
  90. end
  91. end
  92. ```
  93.  
  94. in libraries/resource_my_resource.rb:
  95.  
  96. ```ruby
  97. class MyBaseClass
  98. class Resource
  99. class MyProvider < Chef::Provider::LWRPBase # it is very important to inherit from LWRPBase
  100.  
  101. use_inline_resources # you still have to do this, if you don't notifications off of this resource will be broken
  102.  
  103. # you have to worry about this
  104. def whyrun_supported?
  105. true
  106. end
  107.  
  108. include MyProviderHelperFunctions
  109.  
  110. def a_helper
  111. end
  112.  
  113. # NEVER use `def action_run` here -- you defeat use_inline_resources and will break notifications if you (and recent foodcritic will tell you that you are wrong)
  114. # If you don't understand how use_inline_resources is built and why you have to use the `action` method, and what the implications are and how resource notifications
  115. # break if use_inline_resources is not used and/or is broken, then you should really not be using library providers+resources. You might feel "closer to the metal",
  116. # but you're now using a chainsaw without any guard...
  117. action :run do
  118. a_helper()
  119. # here you have to use new_resource.foo
  120. puts new_resource.foo
  121. end
  122. end
  123. end
  124. end
  125. ```
  126.  
  127. # A Note on "updated_by_last_action"
  128.  
  129. Modern chef-client code (since Chef 11.0.0) should never have provider code_which directly sets updated_by_last_action itself.
  130.  
  131. THIS CODE IS WRONG:
  132.  
  133. ```ruby
  134. action :run do
  135. t = file "/tmp/foo" do
  136. content "foo"
  137. end
  138. t.run_action(:install)
  139. # This is Chef 10 code which fell through a timewarp into 2016 -- never use updated_by_last_action in modern Chef 11.x/12.x code
  140. t.new_resource.updated_by_last_action(true) if t.updated_by_last_action?
  141. end
  142. ```
  143.  
  144. That used to be kinda-correct-code-with-awful-edge-cases back in Chef 10. If you're not using Chef 10 you should stop writing actions this way.
  145.  
  146. THIS IS CORRECT:
  147.  
  148. ```ruby
  149. use_inline_resources
  150.  
  151. def whyrun_supported?
  152. true
  153. end
  154.  
  155. action :run do
  156. file "/tmp/foo" do
  157. content "foo"
  158. end
  159. end
  160. ```
  161.  
  162. That is the magic of use_inline_resources (and why use_inline_resources is turned on by default in Chef 12.5 resources). The subresources are defined in a sub-resource collection which is compiled and converged as part of the provider executing. Any resources that update in the sub-resource collection cause the resource itself to be updated automatically. Notifications then fire normally off the resource. It also works to arbitrary levels of nesting of sub-sub-sub-resources being updating causing the wrapping resources to update and fire notifications.
  163.  
  164. This also gets the why-run case correct. If all the /work/ that you do in your resource is done by calling sub-resources, then why-run should work automatically. All your sub-resources will be NO-OP'd and will report what they /would/ have done instead of doing it.
  165.  
  166. If you do need to write code which mutates the system through pure-ruby then you should do so like this:
  167.  
  168. ```ruby
  169. use_inline_resources
  170.  
  171. def whyrun_supported?
  172. true
  173. end
  174.  
  175. action :run do
  176. unless File.exist?("/tmp/foo")
  177. converge_by("touch /tmp/foo") do
  178. ::FileUtils.touch "/tmp/foo"
  179. end
  180. end
  181. end
  182. ```
  183.  
  184. The converge_by block gets why-run correct and will just pring "would touch /tmp/foo" instead of actually doing it. The converge_by block is also responsible for setting update_by_last_action.
  185.  
  186. In order to use converge_by correctly you must ensure that you wrap the converge_by with an idempotency check otherwise your resource will be updated every time it is used and will always fire notifications on every run.
  187.  
  188. ```ruby
  189. action :run do
  190. # This code is bad, it lacks an idempotency check here.
  191. # It will always be updated
  192. # chef-client runs will always report a resource being updated
  193. # It will run the code in the block on every run
  194. converge_by("touch /tmp/foo") do
  195. ::FileUtils.touch "/tmp/foo"
  196. end
  197. end
  198. ```
  199.  
  200. Of course it is vastly simpler to just use chef-resources when you can, compare the equivalent implementations:
  201.  
  202. ```ruby
  203. # this:
  204. action :run do
  205. file "/tmp/foo"
  206. end
  207.  
  208. # is basically the same as this:
  209. action :run do
  210. unless File.exist?("/tmp/foo")
  211. converge_by("touch /tmp/foo") do
  212. ::FileUtils.touch "/tmp/foo"
  213. end
  214. end
  215. end
  216. ```
  217.  
  218. You may see a lot of converge_by and updated_by_last_action in the core chef resources. Part of that is that we're writing a declarative language with an imperative language, and someone has to take the first step and write the declarative file resources in imperative ruby. The other part of that is just that our core resources have been lagging behind. They were written long ago, and if they work, nobody rewrites them. They are typically bad code examples. Do not emulate them thinking that is how the "experts" clearly write things.
Add Comment
Please, Sign In to add comment