chef 12.4.3-universal-mingw32 → 12.5.1-universal-mingw32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +21 -25
  3. data/Gemfile +46 -0
  4. data/README.md +4 -4
  5. data/Rakefile +4 -110
  6. data/bin/chef-service-manager +3 -1
  7. data/distro/common/html/knife_cookbook_site.html +18 -18
  8. data/distro/common/man/man1/knife-cookbook-site.1 +11 -11
  9. data/lib/chef.rb +1 -1
  10. data/lib/chef/application.rb +1 -1
  11. data/lib/chef/application/apply.rb +19 -1
  12. data/lib/chef/application/client.rb +11 -5
  13. data/lib/chef/application/knife.rb +2 -2
  14. data/lib/chef/application/solo.rb +1 -1
  15. data/lib/chef/application/windows_service_manager.rb +19 -12
  16. data/lib/chef/chef_class.rb +46 -0
  17. data/lib/chef/chef_fs/config.rb +22 -24
  18. data/lib/chef/chef_fs/data_handler/client_data_handler.rb +3 -1
  19. data/lib/chef/chef_fs/file_pattern.rb +4 -15
  20. data/lib/chef/chef_fs/file_system/acl_dir.rb +3 -4
  21. data/lib/chef/chef_fs/file_system/acls_dir.rb +5 -1
  22. data/lib/chef/chef_fs/file_system/base_fs_dir.rb +0 -5
  23. data/lib/chef/chef_fs/file_system/base_fs_object.rb +5 -2
  24. data/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_dir.rb +2 -9
  25. data/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbook_entry.rb +2 -9
  26. data/lib/chef/chef_fs/file_system/chef_repository_file_system_cookbooks_dir.rb +10 -17
  27. data/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb +1 -12
  28. data/lib/chef/chef_fs/file_system/chef_repository_file_system_root_dir.rb +15 -11
  29. data/lib/chef/chef_fs/file_system/chef_server_root_dir.rb +8 -2
  30. data/lib/chef/chef_fs/file_system/cookbook_dir.rb +4 -4
  31. data/lib/chef/chef_fs/file_system/cookbooks_acl_dir.rb +1 -1
  32. data/lib/chef/chef_fs/file_system/cookbooks_dir.rb +3 -11
  33. data/lib/chef/chef_fs/file_system/data_bags_dir.rb +3 -5
  34. data/lib/chef/chef_fs/file_system/environments_dir.rb +1 -1
  35. data/lib/chef/chef_fs/file_system/file_system_entry.rb +7 -4
  36. data/lib/chef/chef_fs/file_system/memory_dir.rb +2 -3
  37. data/lib/chef/chef_fs/file_system/multiplexed_dir.rb +15 -0
  38. data/lib/chef/chef_fs/file_system/nodes_dir.rb +1 -1
  39. data/lib/chef/chef_fs/file_system/organization_members_entry.rb +2 -2
  40. data/lib/chef/chef_fs/file_system/rest_list_dir.rb +4 -9
  41. data/lib/chef/chef_fs/knife.rb +35 -7
  42. data/lib/chef/chef_fs/path_utils.rb +65 -34
  43. data/lib/chef/client.rb +2 -3
  44. data/lib/chef/config.rb +34 -2
  45. data/lib/chef/{mixin/wstring.rb → constants.rb} +9 -13
  46. data/lib/chef/cookbook/metadata.rb +25 -3
  47. data/lib/chef/cookbook/synchronizer.rb +1 -1
  48. data/lib/chef/cookbook_site_streaming_uploader.rb +1 -1
  49. data/lib/chef/cookbook_version.rb +3 -3
  50. data/lib/chef/delayed_evaluator.rb +21 -0
  51. data/lib/chef/deprecation/mixin/template.rb +1 -2
  52. data/lib/chef/deprecation/provider/cookbook_file.rb +1 -1
  53. data/lib/chef/deprecation/provider/file.rb +1 -1
  54. data/lib/chef/deprecation/provider/remote_directory.rb +52 -0
  55. data/lib/chef/deprecation/provider/remote_file.rb +1 -2
  56. data/lib/chef/deprecation/provider/template.rb +1 -1
  57. data/lib/chef/deprecation/warnings.rb +3 -4
  58. data/lib/chef/dsl/reboot_pending.rb +3 -2
  59. data/lib/chef/dsl/recipe.rb +26 -7
  60. data/lib/chef/dsl/resources.rb +2 -2
  61. data/lib/chef/event_dispatch/base.rb +51 -22
  62. data/lib/chef/event_dispatch/dispatcher.rb +21 -6
  63. data/lib/chef/event_dispatch/dsl.rb +64 -0
  64. data/lib/chef/exceptions.rb +28 -1
  65. data/lib/chef/file_content_management/tempfile.rb +1 -1
  66. data/lib/chef/formatters/base.rb +3 -0
  67. data/lib/chef/formatters/doc.rb +56 -6
  68. data/lib/chef/formatters/error_inspectors/compile_error_inspector.rb +36 -0
  69. data/lib/chef/formatters/minimal.rb +2 -2
  70. data/lib/chef/guard_interpreter/resource_guard_interpreter.rb +3 -1
  71. data/lib/chef/http/http_request.rb +1 -1
  72. data/lib/chef/knife.rb +35 -55
  73. data/lib/chef/knife/bootstrap.rb +41 -0
  74. data/lib/chef/knife/bootstrap/chef_vault_handler.rb +1 -0
  75. data/lib/chef/knife/bootstrap/client_builder.rb +16 -0
  76. data/lib/chef/knife/bootstrap/templates/README.md +3 -4
  77. data/lib/chef/knife/bootstrap/templates/chef-full.erb +1 -1
  78. data/lib/chef/knife/cookbook_create.rb +1 -1
  79. data/lib/chef/knife/cookbook_site_download.rb +1 -1
  80. data/lib/chef/knife/cookbook_site_install.rb +1 -1
  81. data/lib/chef/knife/cookbook_site_share.rb +6 -6
  82. data/lib/chef/knife/cookbook_site_unshare.rb +2 -2
  83. data/lib/chef/knife/core/bootstrap_context.rb +12 -4
  84. data/lib/chef/knife/core/custom_manifest_loader.rb +69 -0
  85. data/lib/chef/knife/core/gem_glob_loader.rb +138 -0
  86. data/lib/chef/knife/core/hashed_command_loader.rb +80 -0
  87. data/lib/chef/knife/core/node_presenter.rb +24 -1
  88. data/lib/chef/knife/core/object_loader.rb +1 -0
  89. data/lib/chef/knife/core/subcommand_loader.rb +131 -146
  90. data/lib/chef/knife/node_run_list_remove.rb +12 -1
  91. data/lib/chef/knife/null.rb +10 -0
  92. data/lib/chef/knife/rehash.rb +62 -0
  93. data/lib/chef/knife/search.rb +3 -3
  94. data/lib/chef/knife/ssh.rb +52 -30
  95. data/lib/chef/knife/ssl_check.rb +3 -2
  96. data/lib/chef/knife/user_edit.rb +1 -2
  97. data/lib/chef/local_mode.rb +5 -0
  98. data/lib/chef/log.rb +5 -1
  99. data/lib/chef/mixin/deprecation.rb +8 -8
  100. data/lib/chef/mixin/params_validate.rb +362 -135
  101. data/lib/chef/mixin/template.rb +48 -0
  102. data/lib/chef/mixin/which.rb +1 -1
  103. data/lib/chef/mixin/wide_string.rb +72 -0
  104. data/lib/chef/mixin/windows_architecture_helper.rb +15 -39
  105. data/lib/chef/mixin/windows_env_helper.rb +4 -1
  106. data/lib/chef/monkey_patches/webrick-utils.rb +51 -0
  107. data/lib/chef/monkey_patches/win32/registry.rb +72 -0
  108. data/lib/chef/node.rb +116 -3
  109. data/lib/chef/node_map.rb +2 -2
  110. data/lib/chef/platform/handler_map.rb +0 -5
  111. data/lib/chef/platform/provider_mapping.rb +5 -6
  112. data/lib/chef/platform/query_helpers.rb +46 -4
  113. data/lib/chef/platform/rebooter.rb +1 -1
  114. data/lib/chef/platform/service_helpers.rb +30 -32
  115. data/lib/chef/policy_builder.rb +1 -8
  116. data/lib/chef/policy_builder/dynamic.rb +186 -0
  117. data/lib/chef/policy_builder/expand_node_object.rb +30 -15
  118. data/lib/chef/policy_builder/policyfile.rb +155 -18
  119. data/lib/chef/property.rb +568 -0
  120. data/lib/chef/provider.rb +222 -13
  121. data/lib/chef/provider/batch.rb +8 -0
  122. data/lib/chef/provider/deploy.rb +5 -7
  123. data/lib/chef/provider/directory.rb +14 -2
  124. data/lib/chef/provider/dsc_resource.rb +5 -9
  125. data/lib/chef/provider/group/pw.rb +1 -1
  126. data/lib/chef/provider/ifconfig.rb +2 -2
  127. data/lib/chef/provider/lwrp_base.rb +1 -75
  128. data/lib/chef/provider/mount.rb +7 -3
  129. data/lib/chef/provider/package.rb +1 -1
  130. data/lib/chef/provider/package/dpkg.rb +5 -11
  131. data/lib/chef/provider/package/rpm.rb +2 -2
  132. data/lib/chef/provider/package/rubygems.rb +1 -1
  133. data/lib/chef/provider/package/windows/msi.rb +2 -2
  134. data/lib/chef/provider/package/yum.rb +17 -5
  135. data/lib/chef/provider/powershell_script.rb +59 -23
  136. data/lib/chef/provider/registry_key.rb +5 -5
  137. data/lib/chef/provider/remote_directory.rb +190 -102
  138. data/lib/chef/provider/service.rb +12 -2
  139. data/lib/chef/provider/service/aix.rb +1 -1
  140. data/lib/chef/provider/service/debian.rb +3 -5
  141. data/lib/chef/provider/service/freebsd.rb +1 -1
  142. data/lib/chef/provider/service/gentoo.rb +3 -3
  143. data/lib/chef/provider/service/init.rb +3 -3
  144. data/lib/chef/provider/service/insserv.rb +2 -4
  145. data/lib/chef/provider/service/invokercd.rb +2 -4
  146. data/lib/chef/provider/service/macosx.rb +5 -1
  147. data/lib/chef/provider/service/openbsd.rb +2 -1
  148. data/lib/chef/provider/service/redhat.rb +52 -16
  149. data/lib/chef/provider/service/simple.rb +2 -2
  150. data/lib/chef/provider/service/systemd.rb +3 -5
  151. data/lib/chef/provider/service/upstart.rb +4 -6
  152. data/lib/chef/provider/subversion.rb +13 -7
  153. data/lib/chef/provider/template/content.rb +16 -6
  154. data/lib/chef/provider/user/solaris.rb +32 -4
  155. data/lib/chef/provider/windows_script.rb +3 -5
  156. data/lib/chef/provider_resolver.rb +2 -2
  157. data/lib/chef/recipe.rb +1 -8
  158. data/lib/chef/resource.rb +563 -90
  159. data/lib/chef/resource/action_class.rb +83 -0
  160. data/lib/chef/resource/chef_gem.rb +3 -3
  161. data/lib/chef/resource/deploy.rb +8 -2
  162. data/lib/chef/resource/dsc_script.rb +2 -0
  163. data/lib/chef/resource/file/verification.rb +7 -1
  164. data/lib/chef/resource/lwrp_base.rb +1 -7
  165. data/lib/chef/resource/registry_key.rb +1 -1
  166. data/lib/chef/resource/service.rb +10 -2
  167. data/lib/chef/resource/subversion.rb +5 -0
  168. data/lib/chef/resource/windows_script.rb +6 -2
  169. data/lib/chef/resource/yum_package.rb +10 -1
  170. data/lib/chef/resource_resolver.rb +3 -3
  171. data/lib/chef/run_context.rb +402 -83
  172. data/lib/chef/run_list/versioned_recipe_list.rb +15 -0
  173. data/lib/chef/run_lock.rb +30 -21
  174. data/lib/chef/util/powershell/ps_credential.rb +4 -0
  175. data/lib/chef/util/windows.rb +0 -32
  176. data/lib/chef/util/windows/net_group.rb +85 -106
  177. data/lib/chef/util/windows/net_use.rb +35 -71
  178. data/lib/chef/util/windows/net_user.rb +0 -1
  179. data/lib/chef/util/windows/volume.rb +19 -19
  180. data/lib/chef/version.rb +3 -3
  181. data/lib/chef/win32/api.rb +1 -0
  182. data/lib/chef/win32/api/file.rb +20 -0
  183. data/lib/chef/win32/api/net.rb +163 -43
  184. data/lib/chef/win32/api/registry.rb +51 -0
  185. data/lib/chef/win32/api/system.rb +23 -0
  186. data/lib/chef/win32/api/unicode.rb +0 -43
  187. data/lib/chef/win32/crypto.rb +2 -1
  188. data/lib/chef/win32/file.rb +28 -3
  189. data/lib/chef/win32/mutex.rb +1 -2
  190. data/lib/chef/win32/net.rb +162 -8
  191. data/lib/chef/win32/process.rb +13 -0
  192. data/lib/chef/win32/registry.rb +35 -30
  193. data/lib/chef/win32/security.rb +1 -1
  194. data/lib/chef/win32/security/token.rb +1 -1
  195. data/lib/chef/win32/system.rb +62 -0
  196. data/lib/chef/win32/unicode.rb +7 -2
  197. data/lib/chef/win32/version.rb +0 -4
  198. data/lib/chef/workstation_config_loader.rb +3 -158
  199. data/spec/data/cookbooks/openldap/templates/default/helpers.erb +14 -0
  200. data/spec/data/cookbooks/openldap/templates/default/nested_openldap_partials.erb +1 -0
  201. data/spec/data/cookbooks/openldap/templates/default/nested_partial.erb +1 -0
  202. data/spec/data/dsc_lcm.pfx +0 -0
  203. data/spec/data/run_context/cookbooks/include/recipes/default.rb +24 -0
  204. data/spec/data/run_context/cookbooks/include/recipes/includee.rb +3 -0
  205. data/spec/functional/dsl/reboot_pending_spec.rb +33 -43
  206. data/spec/functional/knife/cookbook_delete_spec.rb +17 -7
  207. data/spec/functional/knife/ssh_spec.rb +16 -0
  208. data/spec/functional/rebooter_spec.rb +1 -1
  209. data/spec/functional/resource/deploy_revision_spec.rb +1 -1
  210. data/spec/functional/resource/dsc_resource_spec.rb +2 -0
  211. data/spec/functional/resource/dsc_script_spec.rb +91 -2
  212. data/spec/functional/resource/group_spec.rb +67 -44
  213. data/spec/functional/resource/{powershell_spec.rb → powershell_script_spec.rb} +107 -18
  214. data/spec/functional/resource/windows_service_spec.rb +1 -1
  215. data/spec/functional/run_lock_spec.rb +368 -189
  216. data/spec/functional/win32/{registry_helper_spec.rb → registry_spec.rb} +16 -23
  217. data/spec/functional/win32/service_manager_spec.rb +2 -2
  218. data/spec/integration/client/client_spec.rb +51 -0
  219. data/spec/integration/knife/chef_repo_path_spec.rb +13 -11
  220. data/spec/integration/knife/download_spec.rb +4 -0
  221. data/spec/integration/knife/list_spec.rb +8 -0
  222. data/spec/integration/knife/upload_spec.rb +1 -1
  223. data/spec/integration/recipes/recipe_dsl_spec.rb +1 -16
  224. data/spec/integration/recipes/remote_directory.rb +74 -0
  225. data/spec/integration/recipes/resource_action_spec.rb +363 -0
  226. data/spec/integration/recipes/resource_converge_if_changed_spec.rb +423 -0
  227. data/spec/integration/recipes/resource_load_spec.rb +206 -0
  228. data/spec/spec_helper.rb +9 -0
  229. data/spec/support/platform_helpers.rb +13 -0
  230. data/spec/support/shared/context/win32.rb +34 -0
  231. data/spec/support/shared/functional/win32_service.rb +2 -1
  232. data/spec/support/shared/functional/windows_script.rb +63 -26
  233. data/spec/support/shared/unit/mock_shellout.rb +46 -0
  234. data/spec/support/shared/unit/provider/file.rb +10 -4
  235. data/spec/unit/application/client_spec.rb +16 -3
  236. data/spec/unit/application/knife_spec.rb +2 -2
  237. data/spec/unit/application/solo_spec.rb +4 -3
  238. data/spec/unit/chef_class_spec.rb +23 -4
  239. data/spec/unit/chef_fs/path_util_spec.rb +108 -0
  240. data/spec/unit/client_spec.rb +6 -1
  241. data/spec/unit/config_spec.rb +31 -0
  242. data/spec/unit/cookbook/metadata_spec.rb +23 -3
  243. data/spec/unit/cookbook/syntax_check_spec.rb +3 -0
  244. data/spec/unit/deprecation_spec.rb +3 -6
  245. data/spec/unit/dsl/reboot_pending_spec.rb +12 -6
  246. data/spec/unit/event_dispatch/dispatcher_spec.rb +65 -3
  247. data/spec/unit/event_dispatch/dsl_spec.rb +83 -0
  248. data/spec/unit/formatters/doc_spec.rb +32 -0
  249. data/spec/unit/formatters/error_inspectors/compile_error_inspector_spec.rb +26 -0
  250. data/spec/unit/json_compat_spec.rb +4 -3
  251. data/spec/unit/knife/bootstrap/client_builder_spec.rb +27 -0
  252. data/spec/unit/knife/bootstrap_spec.rb +55 -3
  253. data/spec/unit/knife/cookbook_site_share_spec.rb +3 -3
  254. data/spec/unit/knife/core/bootstrap_context_spec.rb +21 -4
  255. data/spec/unit/knife/core/custom_manifest_loader_spec.rb +41 -0
  256. data/spec/unit/knife/core/gem_glob_loader_spec.rb +210 -0
  257. data/spec/unit/knife/core/hashed_command_loader_spec.rb +93 -0
  258. data/spec/unit/knife/core/subcommand_loader_spec.rb +16 -192
  259. data/spec/unit/knife/node_run_list_remove_spec.rb +17 -0
  260. data/spec/unit/knife/ssl_check_spec.rb +4 -0
  261. data/spec/unit/mixin/enforce_ownership_and_permissions_spec.rb +10 -10
  262. data/spec/unit/mixin/params_validate_spec.rb +4 -2
  263. data/spec/unit/mixin/template_spec.rb +5 -1
  264. data/spec/unit/mixin/windows_architecture_helper_spec.rb +13 -8
  265. data/spec/unit/node_spec.rb +220 -0
  266. data/spec/unit/platform/query_helpers_spec.rb +146 -3
  267. data/spec/unit/policy_builder/dynamic_spec.rb +275 -0
  268. data/spec/unit/policy_builder/expand_node_object_spec.rb +37 -38
  269. data/spec/unit/policy_builder/policyfile_spec.rb +260 -46
  270. data/spec/unit/property/state_spec.rb +506 -0
  271. data/spec/unit/property/validation_spec.rb +663 -0
  272. data/spec/unit/property_spec.rb +1094 -0
  273. data/spec/unit/provider/deploy_spec.rb +5 -5
  274. data/spec/unit/provider/directory_spec.rb +35 -0
  275. data/spec/unit/provider/dsc_resource_spec.rb +3 -10
  276. data/spec/unit/provider/ifconfig_spec.rb +22 -2
  277. data/spec/unit/provider/mount/aix_spec.rb +2 -1
  278. data/spec/unit/provider/mount/mount_spec.rb +6 -0
  279. data/spec/unit/provider/mount/windows_spec.rb +14 -0
  280. data/spec/unit/provider/mount_spec.rb +12 -1
  281. data/spec/unit/provider/package/dpkg_spec.rb +8 -1
  282. data/spec/unit/provider/package/rpm_spec.rb +18 -1
  283. data/spec/unit/provider/package/rubygems_spec.rb +18 -0
  284. data/spec/unit/provider/package/yum_spec.rb +97 -24
  285. data/spec/unit/provider/powershell_script_spec.rb +106 -0
  286. data/spec/unit/provider/registry_key_spec.rb +12 -0
  287. data/spec/unit/provider/remote_directory_spec.rb +1 -2
  288. data/spec/unit/provider/service/aix_service_spec.rb +3 -3
  289. data/spec/unit/provider/service/gentoo_service_spec.rb +4 -4
  290. data/spec/unit/provider/service/macosx_spec.rb +4 -4
  291. data/spec/unit/provider/service/openbsd_service_spec.rb +10 -8
  292. data/spec/unit/provider/service/redhat_spec.rb +88 -8
  293. data/spec/unit/provider/service/upstart_service_spec.rb +11 -7
  294. data/spec/unit/provider/service/windows_spec.rb +211 -200
  295. data/spec/unit/provider/subversion_spec.rb +50 -31
  296. data/spec/unit/provider/template/content_spec.rb +93 -2
  297. data/spec/unit/provider/user/solaris_spec.rb +66 -9
  298. data/spec/unit/provider_resolver_spec.rb +707 -650
  299. data/spec/unit/provider_spec.rb +1 -3
  300. data/spec/unit/recipe_spec.rb +0 -4
  301. data/spec/unit/resource/deploy_spec.rb +7 -1
  302. data/spec/unit/resource/dsc_script_spec.rb +4 -0
  303. data/spec/unit/resource/file/verification_spec.rb +33 -5
  304. data/spec/unit/resource/{powershell_spec.rb → powershell_script_spec.rb} +17 -13
  305. data/spec/unit/resource/service_spec.rb +4 -4
  306. data/spec/unit/resource/subversion_spec.rb +4 -0
  307. data/spec/unit/resource/yum_package_spec.rb +10 -1
  308. data/spec/unit/resource_spec.rb +2 -2
  309. data/spec/unit/run_context/child_run_context_spec.rb +133 -0
  310. data/spec/unit/run_context_spec.rb +7 -0
  311. data/spec/unit/run_list/versioned_recipe_list_spec.rb +5 -0
  312. data/spec/unit/win32/registry_spec.rb +394 -0
  313. data/tasks/external_tests.rb +47 -23
  314. data/tasks/maintainers.rb +155 -14
  315. metadata +64 -53
  316. data/lib/chef/knife/bootstrap/templates/archlinux-gems.erb +0 -76
  317. data/lib/chef/knife/bootstrap/templates/chef-aix.erb +0 -72
  318. data/spec/unit/provider/powershell_spec.rb +0 -80
  319. data/spec/unit/registry_helper_spec.rb +0 -376
  320. data/spec/unit/workstation_config_loader_spec.rb +0 -283
@@ -0,0 +1,363 @@
1
+ require 'support/shared/integration/integration_helper'
2
+
3
+ describe "Resource.action" do
4
+ include IntegrationSupport
5
+
6
+ shared_context "ActionJackson" do
7
+ it "the default action is the first declared action" do
8
+ converge <<-EOM, __FILE__, __LINE__+1
9
+ #{resource_dsl} 'hi' do
10
+ foo 'foo!'
11
+ end
12
+ EOM
13
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
14
+ expect(ActionJackson.succeeded).to eq true
15
+ end
16
+
17
+ it "the action can access recipe DSL" do
18
+ converge <<-EOM, __FILE__, __LINE__+1
19
+ #{resource_dsl} 'hi' do
20
+ foo 'foo!'
21
+ action :access_recipe_dsl
22
+ end
23
+ EOM
24
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
25
+ expect(ActionJackson.succeeded).to eq true
26
+ end
27
+
28
+ it "the action can access attributes" do
29
+ converge <<-EOM, __FILE__, __LINE__+1
30
+ #{resource_dsl} 'hi' do
31
+ foo 'foo!'
32
+ action :access_attribute
33
+ end
34
+ EOM
35
+ expect(ActionJackson.ran_action).to eq :access_attribute
36
+ expect(ActionJackson.succeeded).to eq 'foo!'
37
+ end
38
+
39
+ it "the action can access public methods" do
40
+ converge <<-EOM, __FILE__, __LINE__+1
41
+ #{resource_dsl} 'hi' do
42
+ foo 'foo!'
43
+ action :access_method
44
+ end
45
+ EOM
46
+ expect(ActionJackson.ran_action).to eq :access_method
47
+ expect(ActionJackson.succeeded).to eq 'foo_public!'
48
+ end
49
+
50
+ it "the action can access protected methods" do
51
+ converge <<-EOM, __FILE__, __LINE__+1
52
+ #{resource_dsl} 'hi' do
53
+ foo 'foo!'
54
+ action :access_protected_method
55
+ end
56
+ EOM
57
+ expect(ActionJackson.ran_action).to eq :access_protected_method
58
+ expect(ActionJackson.succeeded).to eq 'foo_protected!'
59
+ end
60
+
61
+ it "the action cannot access private methods" do
62
+ expect {
63
+ converge(<<-EOM, __FILE__, __LINE__+1)
64
+ #{resource_dsl} 'hi' do
65
+ foo 'foo!'
66
+ action :access_private_method
67
+ end
68
+ EOM
69
+ }.to raise_error(NameError)
70
+ expect(ActionJackson.ran_action).to eq :access_private_method
71
+ end
72
+
73
+ it "the action cannot access resource instance variables" do
74
+ converge <<-EOM, __FILE__, __LINE__+1
75
+ #{resource_dsl} 'hi' do
76
+ foo 'foo!'
77
+ action :access_instance_variable
78
+ end
79
+ EOM
80
+ expect(ActionJackson.ran_action).to eq :access_instance_variable
81
+ expect(ActionJackson.succeeded).to be_nil
82
+ end
83
+
84
+ it "the action does not compile until the prior resource has converged" do
85
+ converge <<-EOM, __FILE__, __LINE__+1
86
+ ruby_block 'wow' do
87
+ block do
88
+ ActionJackson.ruby_block_converged = 'ruby_block_converged!'
89
+ end
90
+ end
91
+
92
+ #{resource_dsl} 'hi' do
93
+ foo 'foo!'
94
+ action :access_class_method
95
+ end
96
+ EOM
97
+ expect(ActionJackson.ran_action).to eq :access_class_method
98
+ expect(ActionJackson.succeeded).to eq 'ruby_block_converged!'
99
+ end
100
+
101
+ it "the action's resources converge before the next resource converges" do
102
+ converge <<-EOM, __FILE__, __LINE__+1
103
+ #{resource_dsl} 'hi' do
104
+ foo 'foo!'
105
+ action :access_attribute
106
+ end
107
+
108
+ ruby_block 'wow' do
109
+ block do
110
+ ActionJackson.ruby_block_converged = ActionJackson.succeeded
111
+ end
112
+ end
113
+ EOM
114
+ expect(ActionJackson.ran_action).to eq :access_attribute
115
+ expect(ActionJackson.succeeded).to eq 'foo!'
116
+ expect(ActionJackson.ruby_block_converged).to eq 'foo!'
117
+ end
118
+ end
119
+
120
+ context "With resource 'action_jackson'" do
121
+ before(:context) {
122
+ class ActionJackson < Chef::Resource
123
+ use_automatic_resource_name
124
+ def foo(value=nil)
125
+ @foo = value if value
126
+ @foo
127
+ end
128
+ def blarghle(value=nil)
129
+ @blarghle = value if value
130
+ @blarghle
131
+ end
132
+
133
+ class <<self
134
+ attr_accessor :ran_action
135
+ attr_accessor :succeeded
136
+ attr_accessor :ruby_block_converged
137
+ end
138
+
139
+ public
140
+ def foo_public
141
+ 'foo_public!'
142
+ end
143
+ protected
144
+ def foo_protected
145
+ 'foo_protected!'
146
+ end
147
+ private
148
+ def foo_private
149
+ 'foo_private!'
150
+ end
151
+
152
+ public
153
+ action :access_recipe_dsl do
154
+ ActionJackson.ran_action = :access_recipe_dsl
155
+ ruby_block 'hi there' do
156
+ block do
157
+ ActionJackson.succeeded = true
158
+ end
159
+ end
160
+ end
161
+ action :access_attribute do
162
+ ActionJackson.ran_action = :access_attribute
163
+ ActionJackson.succeeded = foo
164
+ ActionJackson.succeeded += " #{blarghle}" if blarghle
165
+ ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
166
+ end
167
+ action :access_attribute2 do
168
+ ActionJackson.ran_action = :access_attribute2
169
+ ActionJackson.succeeded = foo
170
+ ActionJackson.succeeded += " #{blarghle}" if blarghle
171
+ ActionJackson.succeeded += " #{bar}" if respond_to?(:bar)
172
+ end
173
+ action :access_method do
174
+ ActionJackson.ran_action = :access_method
175
+ ActionJackson.succeeded = foo_public
176
+ end
177
+ action :access_protected_method do
178
+ ActionJackson.ran_action = :access_protected_method
179
+ ActionJackson.succeeded = foo_protected
180
+ end
181
+ action :access_private_method do
182
+ ActionJackson.ran_action = :access_private_method
183
+ ActionJackson.succeeded = foo_private
184
+ end
185
+ action :access_instance_variable do
186
+ ActionJackson.ran_action = :access_instance_variable
187
+ ActionJackson.succeeded = @foo
188
+ end
189
+ action :access_class_method do
190
+ ActionJackson.ran_action = :access_class_method
191
+ ActionJackson.succeeded = ActionJackson.ruby_block_converged
192
+ end
193
+ end
194
+ }
195
+ before(:each) {
196
+ ActionJackson.ran_action = :error
197
+ ActionJackson.succeeded = :error
198
+ ActionJackson.ruby_block_converged = :error
199
+ }
200
+
201
+ it_behaves_like "ActionJackson" do
202
+ let(:resource_dsl) { :action_jackson }
203
+ end
204
+
205
+ it "Can retrieve ancestors of action class without crashing" do
206
+ converge { action_jackson 'hi' }
207
+ expect { ActionJackson.action_class.ancestors.join(",") }.not_to raise_error
208
+ end
209
+
210
+ context "And 'action_jackgrandson' inheriting from ActionJackson and changing nothing" do
211
+ before(:context) {
212
+ class ActionJackgrandson < ActionJackson
213
+ use_automatic_resource_name
214
+ end
215
+ }
216
+
217
+ it_behaves_like "ActionJackson" do
218
+ let(:resource_dsl) { :action_jackgrandson }
219
+ end
220
+ end
221
+
222
+ context "And 'action_jackalope' inheriting from ActionJackson with an extra attribute, action and custom method" do
223
+ before(:context) {
224
+ class ActionJackalope < ActionJackson
225
+ use_automatic_resource_name
226
+
227
+ def foo(value=nil)
228
+ @foo = "#{value}alope" if value
229
+ @foo
230
+ end
231
+ def bar(value=nil)
232
+ @bar = "#{value}alope" if value
233
+ @bar
234
+ end
235
+ class <<self
236
+ attr_accessor :load_current_resource_ran
237
+ attr_accessor :jackalope_ran
238
+ end
239
+ action :access_jackalope do
240
+ ActionJackalope.jackalope_ran = :access_jackalope
241
+ ActionJackalope.succeeded = "#{foo} #{blarghle} #{bar}"
242
+ end
243
+ action :access_attribute do
244
+ super()
245
+ ActionJackalope.jackalope_ran = :access_attribute
246
+ ActionJackalope.succeeded = ActionJackson.succeeded
247
+ end
248
+ end
249
+ }
250
+ before do
251
+ ActionJackalope.jackalope_ran = nil
252
+ ActionJackalope.load_current_resource_ran = nil
253
+ end
254
+
255
+ context "action_jackson still behaves the same" do
256
+ it_behaves_like "ActionJackson" do
257
+ let(:resource_dsl) { :action_jackson }
258
+ end
259
+ end
260
+
261
+ it "the default action remains the same even though new actions were specified first" do
262
+ converge {
263
+ action_jackalope 'hi' do
264
+ foo 'foo!'
265
+ bar 'bar!'
266
+ end
267
+ }
268
+ expect(ActionJackson.ran_action).to eq :access_recipe_dsl
269
+ expect(ActionJackson.succeeded).to eq true
270
+ end
271
+
272
+ it "new actions run, and can access overridden, new, and overridden attributes" do
273
+ converge {
274
+ action_jackalope 'hi' do
275
+ foo 'foo!'
276
+ bar 'bar!'
277
+ blarghle 'blarghle!'
278
+ action :access_jackalope
279
+ end
280
+ }
281
+ expect(ActionJackalope.jackalope_ran).to eq :access_jackalope
282
+ expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
283
+ end
284
+
285
+ it "overridden actions run, call super, and can access overridden, new, and overridden attributes" do
286
+ converge {
287
+ action_jackalope 'hi' do
288
+ foo 'foo!'
289
+ bar 'bar!'
290
+ blarghle 'blarghle!'
291
+ action :access_attribute
292
+ end
293
+ }
294
+ expect(ActionJackson.ran_action).to eq :access_attribute
295
+ expect(ActionJackson.succeeded).to eq "foo!alope blarghle! bar!alope"
296
+ expect(ActionJackalope.jackalope_ran).to eq :access_attribute
297
+ expect(ActionJackalope.succeeded).to eq "foo!alope blarghle! bar!alope"
298
+ end
299
+
300
+ it "non-overridden actions run and can access overridden and non-overridden variables (but not necessarily new ones)" do
301
+ converge {
302
+ action_jackalope 'hi' do
303
+ foo 'foo!'
304
+ bar 'bar!'
305
+ blarghle 'blarghle!'
306
+ action :access_attribute2
307
+ end
308
+ }
309
+ expect(ActionJackson.ran_action).to eq :access_attribute2
310
+ expect(ActionJackson.succeeded).to eq("foo!alope blarghle! bar!alope").or(eq("foo!alope blarghle!"))
311
+ end
312
+ end
313
+ end
314
+
315
+ context "With a resource with no actions" do
316
+ before(:context) {
317
+ class NoActionJackson < Chef::Resource
318
+ use_automatic_resource_name
319
+
320
+ def foo(value=nil)
321
+ @foo = value if value
322
+ @foo
323
+ end
324
+
325
+ class <<self
326
+ attr_accessor :action_was
327
+ end
328
+ end
329
+ }
330
+ it "the default action is :nothing" do
331
+ converge {
332
+ no_action_jackson 'hi' do
333
+ foo 'foo!'
334
+ NoActionJackson.action_was = action
335
+ end
336
+ }
337
+ expect(NoActionJackson.action_was).to eq [:nothing]
338
+ end
339
+ end
340
+
341
+ context "With a resource with action a-b-c d" do
342
+ before(:context) {
343
+ class WeirdActionJackson < Chef::Resource
344
+ use_automatic_resource_name
345
+
346
+ class <<self
347
+ attr_accessor :action_was
348
+ end
349
+
350
+ action "a-b-c d" do
351
+ WeirdActionJackson.action_was = action
352
+ end
353
+ end
354
+ }
355
+
356
+ it "Running the action works" do
357
+ expect_recipe {
358
+ weird_action_jackson 'hi'
359
+ }.to be_up_to_date
360
+ expect(WeirdActionJackson.action_was).to eq :"a-b-c d"
361
+ end
362
+ end
363
+ end
@@ -0,0 +1,423 @@
1
+ require 'support/shared/integration/integration_helper'
2
+
3
+ describe "Resource::ActionClass#converge_if_changed" do
4
+ include IntegrationSupport
5
+
6
+ module Namer
7
+ extend self
8
+ attr_accessor :current_index
9
+ def incrementing_value
10
+ @incrementing_value += 1
11
+ @incrementing_value
12
+ end
13
+ attr_writer :incrementing_value
14
+ end
15
+
16
+ before(:all) { Namer.current_index = 1 }
17
+ before { Namer.current_index += 1 }
18
+ before { Namer.incrementing_value = 0 }
19
+
20
+ context "when the resource has identity, state and control properties" do
21
+ let(:resource_name) { :"converge_if_changed_dsl#{Namer.current_index}" }
22
+ let(:resource_class) {
23
+ result = Class.new(Chef::Resource) do
24
+ def self.to_s; resource_name; end
25
+ def self.inspect; resource_name.inspect; end
26
+ property :identity1, identity: true, default: 'default_identity1'
27
+ property :control1, desired_state: false, default: 'default_control1'
28
+ property :state1, default: 'default_state1'
29
+ property :state2, default: 'default_state2'
30
+ attr_accessor :converged
31
+ def initialize(*args)
32
+ super
33
+ @converged = 0
34
+ end
35
+ end
36
+ result.resource_name resource_name
37
+ result
38
+ }
39
+ let(:converged_recipe) { converge(converge_recipe) }
40
+ let(:resource) { converged_recipe.resources.first }
41
+
42
+ context "and converge_if_changed with no parameters" do
43
+ before :each do
44
+ resource_class.action :create do
45
+ converge_if_changed do
46
+ new_resource.converged += 1
47
+ end
48
+ end
49
+ end
50
+
51
+ context "and current_resource with state1=current, state2=current" do
52
+ before :each do
53
+ resource_class.load_current_value do
54
+ state1 'current_state1'
55
+ state2 'current_state2'
56
+ end
57
+ end
58
+
59
+ context "and nothing is set" do
60
+ let(:converge_recipe) { "#{resource_name} 'blah'" }
61
+
62
+ it "the resource updates nothing" do
63
+ expect(resource.converged).to eq 0
64
+ expect(resource.updated?).to be_falsey
65
+ expect(converged_recipe.stdout).to eq <<-EOM
66
+ * #{resource_name}[blah] action create (up to date)
67
+ EOM
68
+ end
69
+ end
70
+
71
+ context "and state1 is set to a new value" do
72
+ let(:converge_recipe) {
73
+ <<-EOM
74
+ #{resource_name} 'blah' do
75
+ state1 'new_state1'
76
+ end
77
+ EOM
78
+ }
79
+
80
+ it "the resource updates state1" do
81
+ expect(resource.converged).to eq 1
82
+ expect(resource.updated?).to be_truthy
83
+ expect(converged_recipe.stdout).to eq <<-EOM
84
+ * #{resource_name}[blah] action create
85
+ - update default_identity1
86
+ - set state1 to "new_state1" (was "current_state1")
87
+ EOM
88
+ end
89
+ end
90
+
91
+ context "and state1 and state2 are set to new values" do
92
+ let(:converge_recipe) {
93
+ <<-EOM
94
+ #{resource_name} 'blah' do
95
+ state1 'new_state1'
96
+ state2 'new_state2'
97
+ end
98
+ EOM
99
+ }
100
+
101
+ it "the resource updates state1 and state2" do
102
+ expect(resource.converged).to eq 1
103
+ expect(resource.updated?).to be_truthy
104
+ expect(converged_recipe.stdout).to eq <<-EOM
105
+ * #{resource_name}[blah] action create
106
+ - update default_identity1
107
+ - set state1 to "new_state1" (was "current_state1")
108
+ - set state2 to "new_state2" (was "current_state2")
109
+ EOM
110
+ end
111
+ end
112
+
113
+ context "and state1 is set to its current value but state2 is set to a new value" do
114
+ let(:converge_recipe) {
115
+ <<-EOM
116
+ #{resource_name} 'blah' do
117
+ state1 'current_state1'
118
+ state2 'new_state2'
119
+ end
120
+ EOM
121
+ }
122
+
123
+ it "the resource updates state2" do
124
+ expect(resource.converged).to eq 1
125
+ expect(resource.updated?).to be_truthy
126
+ expect(converged_recipe.stdout).to eq <<-EOM
127
+ * #{resource_name}[blah] action create
128
+ - update default_identity1
129
+ - set state2 to "new_state2" (was "current_state2")
130
+ EOM
131
+ end
132
+ end
133
+
134
+ context "and state1 and state2 are set to their current values" do
135
+ let(:converge_recipe) {
136
+ <<-EOM
137
+ #{resource_name} 'blah' do
138
+ state1 'current_state1'
139
+ state2 'current_state2'
140
+ end
141
+ EOM
142
+ }
143
+
144
+ it "the resource updates nothing" do
145
+ expect(resource.converged).to eq 0
146
+ expect(resource.updated?).to be_falsey
147
+ expect(converged_recipe.stdout).to eq <<-EOM
148
+ * #{resource_name}[blah] action create (up to date)
149
+ EOM
150
+ end
151
+ end
152
+
153
+ context "and identity1 and control1 are set to new values" do
154
+ let(:converge_recipe) {
155
+ <<-EOM
156
+ #{resource_name} 'blah' do
157
+ identity1 'new_identity1'
158
+ control1 'new_control1'
159
+ end
160
+ EOM
161
+ }
162
+
163
+ # Because the identity value is copied over to the new resource, by
164
+ # default they do not register as "changed"
165
+ it "the resource updates nothing" do
166
+ expect(resource.converged).to eq 0
167
+ expect(resource.updated?).to be_falsey
168
+ expect(converged_recipe.stdout).to eq <<-EOM
169
+ * #{resource_name}[blah] action create (up to date)
170
+ EOM
171
+ end
172
+ end
173
+ end
174
+
175
+ context "and current_resource with identity1=current, control1=current" do
176
+ before :each do
177
+ resource_class.load_current_value do
178
+ identity1 'current_identity1'
179
+ control1 'current_control1'
180
+ end
181
+ end
182
+
183
+ context "and identity1 and control1 are set to new values" do
184
+ let(:converge_recipe) {
185
+ <<-EOM
186
+ #{resource_name} 'blah' do
187
+ identity1 'new_identity1'
188
+ control1 'new_control1'
189
+ end
190
+ EOM
191
+ }
192
+
193
+ # Control values are not desired state and are therefore not considered
194
+ # a reason for converging.
195
+ it "the resource updates identity1" do
196
+ expect(resource.converged).to eq 1
197
+ expect(resource.updated?).to be_truthy
198
+ expect(converged_recipe.stdout).to eq <<-EOM
199
+ * #{resource_name}[blah] action create
200
+ - update current_identity1
201
+ - set identity1 to "new_identity1" (was "current_identity1")
202
+ EOM
203
+ end
204
+ end
205
+ end
206
+
207
+ context "and has no current_resource" do
208
+ before :each do
209
+ resource_class.load_current_value do
210
+ current_value_does_not_exist!
211
+ end
212
+ end
213
+
214
+ context "and nothing is set" do
215
+ let(:converge_recipe) { "#{resource_name} 'blah'" }
216
+
217
+ it "the resource is created" do
218
+ expect(resource.converged).to eq 1
219
+ expect(resource.updated?).to be_truthy
220
+ expect(converged_recipe.stdout).to eq <<-EOM
221
+ * #{resource_name}[blah] action create
222
+ - create default_identity1
223
+ - set identity1 to "default_identity1" (default value)
224
+ - set state1 to "default_state1" (default value)
225
+ - set state2 to "default_state2" (default value)
226
+ EOM
227
+ end
228
+ end
229
+
230
+ context "and state1 and state2 are set" do
231
+ let(:converge_recipe) {
232
+ <<-EOM
233
+ #{resource_name} 'blah' do
234
+ state1 'new_state1'
235
+ state2 'new_state2'
236
+ end
237
+ EOM
238
+ }
239
+
240
+ it "the resource is created" do
241
+ expect(resource.converged).to eq 1
242
+ expect(resource.updated?).to be_truthy
243
+ expect(converged_recipe.stdout).to eq <<-EOM
244
+ * #{resource_name}[blah] action create
245
+ - create default_identity1
246
+ - set identity1 to "default_identity1" (default value)
247
+ - set state1 to "new_state1"
248
+ - set state2 to "new_state2"
249
+ EOM
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ context "and separate converge_if_changed :state1 and converge_if_changed :state2" do
256
+ before :each do
257
+ resource_class.action :create do
258
+ converge_if_changed :state1 do
259
+ new_resource.converged += 1
260
+ end
261
+ converge_if_changed :state2 do
262
+ new_resource.converged += 1
263
+ end
264
+ end
265
+ end
266
+
267
+ context "and current_resource with state1=current, state2=current" do
268
+ before :each do
269
+ resource_class.load_current_value do
270
+ state1 'current_state1'
271
+ state2 'current_state2'
272
+ end
273
+ end
274
+
275
+ context "and nothing is set" do
276
+ let(:converge_recipe) { "#{resource_name} 'blah'" }
277
+
278
+ it "the resource updates nothing" do
279
+ expect(resource.converged).to eq 0
280
+ expect(resource.updated?).to be_falsey
281
+ expect(converged_recipe.stdout).to eq <<-EOM
282
+ * #{resource_name}[blah] action create (up to date)
283
+ EOM
284
+ end
285
+ end
286
+
287
+ context "and state1 is set to a new value" do
288
+
289
+ let(:converge_recipe) {
290
+ <<-EOM
291
+ #{resource_name} 'blah' do
292
+ state1 'new_state1'
293
+ end
294
+ EOM
295
+ }
296
+
297
+ it "the resource updates state1" do
298
+ expect(resource.converged).to eq 1
299
+ expect(resource.updated?).to be_truthy
300
+ expect(converged_recipe.stdout).to eq <<-EOM
301
+ * #{resource_name}[blah] action create
302
+ - update default_identity1
303
+ - set state1 to "new_state1" (was "current_state1")
304
+ EOM
305
+ end
306
+ end
307
+
308
+ context "and state1 and state2 are set to new values" do
309
+ let(:converge_recipe) {
310
+ <<-EOM
311
+ #{resource_name} 'blah' do
312
+ state1 'new_state1'
313
+ state2 'new_state2'
314
+ end
315
+ EOM
316
+ }
317
+
318
+ it "the resource updates state1 and state2" do
319
+ expect(resource.converged).to eq 2
320
+ expect(resource.updated?).to be_truthy
321
+ expect(converged_recipe.stdout).to eq <<-EOM
322
+ * #{resource_name}[blah] action create
323
+ - update default_identity1
324
+ - set state1 to "new_state1" (was "current_state1")
325
+ - update default_identity1
326
+ - set state2 to "new_state2" (was "current_state2")
327
+ EOM
328
+ end
329
+ end
330
+
331
+ context "and state1 is set to its current value but state2 is set to a new value" do
332
+ let(:converge_recipe) {
333
+ <<-EOM
334
+ #{resource_name} 'blah' do
335
+ state1 'current_state1'
336
+ state2 'new_state2'
337
+ end
338
+ EOM
339
+ }
340
+
341
+ it "the resource updates state2" do
342
+ expect(resource.converged).to eq 1
343
+ expect(resource.updated?).to be_truthy
344
+ expect(converged_recipe.stdout).to eq <<-EOM
345
+ * #{resource_name}[blah] action create
346
+ - update default_identity1
347
+ - set state2 to "new_state2" (was "current_state2")
348
+ EOM
349
+ end
350
+ end
351
+
352
+ context "and state1 and state2 are set to their current values" do
353
+ let(:converge_recipe) {
354
+ <<-EOM
355
+ #{resource_name} 'blah' do
356
+ state1 'current_state1'
357
+ state2 'current_state2'
358
+ end
359
+ EOM
360
+ }
361
+
362
+ it "the resource updates nothing" do
363
+ expect(resource.converged).to eq 0
364
+ expect(resource.updated?).to be_falsey
365
+ expect(converged_recipe.stdout).to eq <<-EOM
366
+ * #{resource_name}[blah] action create (up to date)
367
+ EOM
368
+ end
369
+ end
370
+ end
371
+
372
+ context "and no current_resource" do
373
+ before :each do
374
+ resource_class.load_current_value do
375
+ current_value_does_not_exist!
376
+ end
377
+ end
378
+
379
+ context "and nothing is set" do
380
+ let(:converge_recipe) {
381
+ "#{resource_name} 'blah'"
382
+ }
383
+
384
+ it "the resource is created" do
385
+ expect(resource.converged).to eq 2
386
+ expect(resource.updated?).to be_truthy
387
+ expect(converged_recipe.stdout).to eq <<-EOM
388
+ * #{resource_name}[blah] action create
389
+ - create default_identity1
390
+ - set state1 to "default_state1" (default value)
391
+ - create default_identity1
392
+ - set state2 to "default_state2" (default value)
393
+ EOM
394
+ end
395
+ end
396
+
397
+ context "and state1 and state2 are set to new values" do
398
+ let(:converge_recipe) {
399
+ <<-EOM
400
+ #{resource_name} 'blah' do
401
+ state1 'new_state1'
402
+ state2 'new_state2'
403
+ end
404
+ EOM
405
+ }
406
+
407
+ it "the resource is created" do
408
+ expect(resource.converged).to eq 2
409
+ expect(resource.updated?).to be_truthy
410
+ expect(converged_recipe.stdout).to eq <<-EOM
411
+ * #{resource_name}[blah] action create
412
+ - create default_identity1
413
+ - set state1 to "new_state1"
414
+ - create default_identity1
415
+ - set state2 to "new_state2"
416
+ EOM
417
+ end
418
+ end
419
+ end
420
+ end
421
+
422
+ end
423
+ end