puppet 6.0.0 → 6.1.0

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 (259) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/Gemfile.lock +20 -20
  4. data/Guardfile.example +76 -0
  5. data/LICENSE +189 -4
  6. data/README.md +3 -3
  7. data/Rakefile +5 -2
  8. data/ext/solaris/smf/svc-puppetd +8 -1
  9. data/ext/solaris/smf/svc-puppetmasterd +8 -1
  10. data/lib/puppet/application/apply.rb +100 -59
  11. data/lib/puppet/application/cert.rb +4 -114
  12. data/lib/puppet/application/device.rb +48 -32
  13. data/lib/puppet/application/script.rb +1 -1
  14. data/lib/puppet/application/ssl.rb +135 -51
  15. data/lib/puppet/application.rb +5 -0
  16. data/lib/puppet/configurer.rb +2 -3
  17. data/lib/puppet/defaults.rb +33 -49
  18. data/lib/puppet/etc.rb +20 -0
  19. data/lib/puppet/face/config.rb +3 -3
  20. data/lib/puppet/face/key.rb +1 -1
  21. data/lib/puppet/face/node/clean.rb +0 -1
  22. data/lib/puppet/feature/base.rb +1 -1
  23. data/lib/puppet/file_bucket/dipper.rb +1 -1
  24. data/lib/puppet/file_serving/fileset.rb +1 -1
  25. data/lib/puppet/functions/abs.rb +1 -1
  26. data/lib/puppet/functions/new.rb +1 -1
  27. data/lib/puppet/module/task.rb +29 -38
  28. data/lib/puppet/pal/catalog_compiler.rb +96 -0
  29. data/lib/puppet/pal/compiler.rb +221 -0
  30. data/lib/puppet/pal/function_signature.rb +52 -0
  31. data/lib/puppet/pal/json_catalog_encoder.rb +67 -0
  32. data/lib/puppet/pal/pal_api.rb +15 -0
  33. data/lib/puppet/pal/pal_impl.rb +523 -0
  34. data/lib/puppet/pal/plan_signature.rb +71 -0
  35. data/lib/puppet/pal/script_compiler.rb +71 -0
  36. data/lib/puppet/pal/task_signature.rb +58 -0
  37. data/lib/puppet/parser/catalog_compiler.rb +24 -0
  38. data/lib/puppet/parser/compiler.rb +3 -1
  39. data/lib/puppet/parser/functions/generate.rb +7 -6
  40. data/lib/puppet/plugins/configuration.rb +5 -1
  41. data/lib/puppet/pops/evaluator/collectors/abstract_collector.rb +3 -1
  42. data/lib/puppet/pops/evaluator/deferred_resolver.rb +3 -0
  43. data/lib/puppet/pops/evaluator/evaluator_impl.rb +5 -1
  44. data/lib/puppet/pops/evaluator/external_syntax_support.rb +3 -3
  45. data/lib/puppet/pops/evaluator/runtime3_converter.rb +2 -2
  46. data/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +19 -10
  47. data/lib/puppet/pops/loader/task_instantiator.rb +13 -70
  48. data/lib/puppet/pops/parser/heredoc_support.rb +1 -2
  49. data/lib/puppet/pops/parser/lexer2.rb +1 -1
  50. data/lib/puppet/pops/pcore.rb +10 -33
  51. data/lib/puppet/pops/serialization/to_data_converter.rb +9 -1
  52. data/lib/puppet/pops/serialization.rb +1 -0
  53. data/lib/puppet/pops/types/p_timespan_type.rb +2 -2
  54. data/lib/puppet/pops/types/string_converter.rb +18 -4
  55. data/lib/puppet/pops/validation/checker4_0.rb +17 -2
  56. data/lib/puppet/provider/exec.rb +59 -57
  57. data/lib/puppet/provider/group/aix.rb +18 -2
  58. data/lib/puppet/provider/group/windows_adsi.rb +7 -3
  59. data/lib/puppet/provider/nameservice.rb +1 -25
  60. data/lib/puppet/provider/package/dnf.rb +2 -0
  61. data/lib/puppet/provider/package/pkgng.rb +37 -21
  62. data/lib/puppet/provider/package/portage.rb +2 -2
  63. data/lib/puppet/provider/package/windows/exe_package.rb +16 -10
  64. data/lib/puppet/provider/package/windows/msi_package.rb +8 -0
  65. data/lib/puppet/provider/package/windows/package.rb +9 -1
  66. data/lib/puppet/provider/package/windows.rb +2 -2
  67. data/lib/puppet/provider/package/yum.rb +1 -1
  68. data/lib/puppet/provider/parsedfile.rb +25 -4
  69. data/lib/puppet/provider/service/launchd.rb +19 -3
  70. data/lib/puppet/provider/service/smf.rb +54 -0
  71. data/lib/puppet/provider/service/systemd.rb +19 -0
  72. data/lib/puppet/provider/service/upstart.rb +1 -0
  73. data/lib/puppet/provider/service/windows.rb +22 -5
  74. data/lib/puppet/provider/user/aix.rb +48 -2
  75. data/lib/puppet/provider/user/useradd.rb +2 -10
  76. data/lib/puppet/provider/user/windows_adsi.rb +9 -1
  77. data/lib/puppet/resource/catalog.rb +1 -5
  78. data/lib/puppet/resource.rb +3 -1
  79. data/lib/puppet/ssl/host.rb +26 -16
  80. data/lib/puppet/syntax_checkers/epp.rb +34 -0
  81. data/lib/puppet/syntax_checkers/pp.rb +34 -0
  82. data/lib/puppet/transaction/event_manager.rb +13 -1
  83. data/lib/puppet/transaction/persistence.rb +1 -1
  84. data/lib/puppet/transaction/resource_harness.rb +3 -1
  85. data/lib/puppet/type/group.rb +29 -1
  86. data/lib/puppet/type/package.rb +5 -1
  87. data/lib/puppet/type/resources.rb +12 -2
  88. data/lib/puppet/type/schedule.rb +7 -0
  89. data/lib/puppet/type/service.rb +12 -0
  90. data/lib/puppet/type/user.rb +5 -2
  91. data/lib/puppet/type.rb +6 -0
  92. data/lib/puppet/util/command_line.rb +2 -3
  93. data/lib/puppet/util/execution.rb +15 -1
  94. data/lib/puppet/util/filetype.rb +56 -8
  95. data/lib/puppet/util/log/destinations.rb +3 -2
  96. data/lib/puppet/util/logging.rb +3 -2
  97. data/lib/puppet/util/posix.rb +15 -0
  98. data/lib/puppet/util/selinux.rb +1 -1
  99. data/lib/puppet/util/storage.rb +12 -0
  100. data/lib/puppet/util/windows/adsi.rb +79 -2
  101. data/lib/puppet/util/windows/api_types.rb +1 -1
  102. data/lib/puppet/util/windows/process.rb +23 -3
  103. data/lib/puppet/util/windows/registry.rb +29 -5
  104. data/lib/puppet/util/windows/security.rb +14 -0
  105. data/lib/puppet/util/windows/service.rb +294 -74
  106. data/lib/puppet/util/windows/user.rb +3 -5
  107. data/lib/puppet/util.rb +7 -3
  108. data/lib/puppet/version.rb +1 -1
  109. data/lib/puppet.rb +1 -0
  110. data/lib/puppet_pal.rb +5 -1099
  111. data/locales/ja/puppet.po +952 -986
  112. data/locales/puppet.pot +592 -347
  113. data/man/man5/puppet.conf.5 +26 -31
  114. data/man/man8/puppet-agent.8 +1 -1
  115. data/man/man8/puppet-apply.8 +1 -1
  116. data/man/man8/puppet-catalog.8 +1 -1
  117. data/man/man8/puppet-config.8 +4 -4
  118. data/man/man8/puppet-describe.8 +1 -1
  119. data/man/man8/puppet-device.8 +23 -19
  120. data/man/man8/puppet-doc.8 +1 -1
  121. data/man/man8/puppet-epp.8 +1 -1
  122. data/man/man8/puppet-facts.8 +1 -1
  123. data/man/man8/puppet-filebucket.8 +1 -1
  124. data/man/man8/puppet-generate.8 +1 -1
  125. data/man/man8/puppet-help.8 +1 -1
  126. data/man/man8/puppet-key.8 +2 -2
  127. data/man/man8/puppet-lookup.8 +1 -1
  128. data/man/man8/puppet-man.8 +1 -1
  129. data/man/man8/puppet-module.8 +1 -1
  130. data/man/man8/puppet-node.8 +1 -1
  131. data/man/man8/puppet-parser.8 +1 -1
  132. data/man/man8/puppet-plugin.8 +1 -1
  133. data/man/man8/puppet-report.8 +1 -1
  134. data/man/man8/puppet-resource.8 +1 -1
  135. data/man/man8/puppet-script.8 +1 -1
  136. data/man/man8/puppet-ssl.8 +26 -3
  137. data/man/man8/puppet-status.8 +1 -1
  138. data/man/man8/puppet.8 +2 -2
  139. data/spec/fixtures/unit/pops/loaders/loaders/mix_4x_and_3x_functions/usee/lib/puppet/parser/functions/bad_func_load.rb +11 -0
  140. data/spec/fixtures/unit/provider/package/pkgng/pkg.query +7 -1
  141. data/spec/fixtures/unit/provider/package/pkgng/pkg.query.zsh +1 -0
  142. data/spec/fixtures/unit/provider/service/smf/svcs_fmri.out +6 -0
  143. data/spec/fixtures/unit/provider/service/smf/svcs_multiple_fmris.out +13 -0
  144. data/spec/integration/application/apply_spec.rb +4 -1
  145. data/spec/integration/parser/collection_spec.rb +4 -8
  146. data/spec/integration/provider/service/windows_spec.rb +5 -5
  147. data/spec/integration/ssl/key_spec.rb +0 -4
  148. data/spec/integration/transaction_spec.rb +559 -0
  149. data/spec/integration/type/file_spec.rb +6 -6
  150. data/spec/integration/util/windows/registry_spec.rb +39 -0
  151. data/spec/integration/util/windows/security_spec.rb +10 -7
  152. data/spec/integration/util/windows/user_spec.rb +37 -17
  153. data/spec/lib/puppet/test_ca.rb +1 -1
  154. data/spec/spec_helper.rb +1 -1
  155. data/spec/unit/agent_spec.rb +2 -2
  156. data/spec/unit/application/apply_spec.rb +55 -2
  157. data/spec/unit/application/device_spec.rb +10 -7
  158. data/spec/unit/application/face_base_spec.rb +1 -1
  159. data/spec/unit/application/lookup_spec.rb +2 -1
  160. data/spec/unit/application/ssl_spec.rb +178 -109
  161. data/spec/unit/application_spec.rb +29 -11
  162. data/spec/unit/configurer/downloader_spec.rb +1 -1
  163. data/spec/unit/configurer_spec.rb +16 -5
  164. data/spec/unit/etc_spec.rb +25 -0
  165. data/spec/unit/face/node_spec.rb +1 -3
  166. data/spec/unit/face/parser_spec.rb +15 -0
  167. data/spec/unit/file_serving/fileset_spec.rb +11 -11
  168. data/spec/unit/functions/logging_spec.rb +10 -3
  169. data/spec/unit/indirector/catalog/json_spec.rb +9 -3
  170. data/spec/unit/indirector/yaml_spec.rb +1 -1
  171. data/spec/unit/network/http/connection_spec.rb +2 -2
  172. data/spec/unit/pops/evaluator/evaluating_parser_spec.rb +36 -0
  173. data/spec/unit/pops/evaluator/runtime3_converter_spec.rb +22 -4
  174. data/spec/unit/pops/loaders/loader_spec.rb +9 -17
  175. data/spec/unit/pops/loaders/loaders_spec.rb +48 -0
  176. data/spec/unit/pops/loaders/module_loaders_spec.rb +7 -7
  177. data/spec/unit/pops/parser/parse_heredoc_spec.rb +16 -0
  178. data/spec/unit/pops/serialization/to_from_hr_spec.rb +9 -0
  179. data/spec/unit/pops/types/p_timespan_type_spec.rb +22 -0
  180. data/spec/unit/pops/types/p_timestamp_type_spec.rb +19 -0
  181. data/spec/unit/pops/types/string_converter_spec.rb +25 -1
  182. data/spec/unit/pops/types/task_spec.rb +42 -116
  183. data/spec/unit/pops/validator/validator_spec.rb +24 -10
  184. data/spec/unit/provider/exec/posix_spec.rb +24 -0
  185. data/spec/unit/provider/group/aix_spec.rb +20 -6
  186. data/spec/unit/provider/group/ldap_spec.rb +22 -25
  187. data/spec/unit/provider/group/pw_spec.rb +7 -10
  188. data/spec/unit/provider/group/windows_adsi_spec.rb +42 -33
  189. data/spec/unit/provider/nameservice/directoryservice_spec.rb +97 -103
  190. data/spec/unit/provider/package/aix_spec.rb +5 -8
  191. data/spec/unit/provider/package/apt_spec.rb +3 -6
  192. data/spec/unit/provider/package/dnf_spec.rb +38 -25
  193. data/spec/unit/provider/package/dpkg_spec.rb +18 -21
  194. data/spec/unit/provider/package/freebsd_spec.rb +4 -7
  195. data/spec/unit/provider/package/gem_spec.rb +41 -41
  196. data/spec/unit/provider/package/hpux_spec.rb +7 -10
  197. data/spec/unit/provider/package/macports_spec.rb +13 -15
  198. data/spec/unit/provider/package/nim_spec.rb +3 -10
  199. data/spec/unit/provider/package/openbsd_spec.rb +14 -17
  200. data/spec/unit/provider/package/pip3_spec.rb +3 -6
  201. data/spec/unit/provider/package/pip_spec.rb +45 -49
  202. data/spec/unit/provider/package/pkgin_spec.rb +13 -18
  203. data/spec/unit/provider/package/pkgng_spec.rb +32 -27
  204. data/spec/unit/provider/package/puppet_gem_spec.rb +6 -9
  205. data/spec/unit/provider/package/tdnf_spec.rb +9 -12
  206. data/spec/unit/provider/package/windows/exe_package_spec.rb +3 -3
  207. data/spec/unit/provider/package/windows_spec.rb +4 -4
  208. data/spec/unit/provider/package/yum_spec.rb +30 -16
  209. data/spec/unit/provider/package/zypper_spec.rb +17 -19
  210. data/spec/unit/provider/service/bsd_spec.rb +7 -9
  211. data/spec/unit/provider/service/daemontools_spec.rb +12 -16
  212. data/spec/unit/provider/service/debian_spec.rb +6 -10
  213. data/spec/unit/provider/service/freebsd_spec.rb +2 -2
  214. data/spec/unit/provider/service/launchd_spec.rb +19 -0
  215. data/spec/unit/provider/service/openbsd_spec.rb +13 -17
  216. data/spec/unit/provider/service/rcng_spec.rb +2 -4
  217. data/spec/unit/provider/service/redhat_spec.rb +12 -11
  218. data/spec/unit/provider/service/runit_spec.rb +7 -14
  219. data/spec/unit/provider/service/smf_spec.rb +77 -13
  220. data/spec/unit/provider/service/src_spec.rb +11 -16
  221. data/spec/unit/provider/service/systemd_spec.rb +39 -1
  222. data/spec/unit/provider/service/windows_spec.rb +39 -7
  223. data/spec/unit/provider/user/aix_spec.rb +43 -2
  224. data/spec/unit/provider/user/hpux_spec.rb +3 -5
  225. data/spec/unit/provider/user/ldap_spec.rb +29 -32
  226. data/spec/unit/provider/user/pw_spec.rb +10 -13
  227. data/spec/unit/provider/user/useradd_spec.rb +2 -2
  228. data/spec/unit/provider/user/windows_adsi_spec.rb +4 -4
  229. data/spec/unit/puppet_pal_2pec.rb +6 -6
  230. data/spec/unit/puppet_pal_catalog_spec.rb +58 -0
  231. data/spec/unit/resource/catalog_spec.rb +2 -2
  232. data/spec/unit/resource_spec.rb +31 -0
  233. data/spec/unit/ssl/host_spec.rb +22 -1
  234. data/spec/unit/ssl/key_spec.rb +2 -4
  235. data/spec/unit/task_spec.rb +50 -5
  236. data/spec/unit/transaction/event_manager_spec.rb +12 -1
  237. data/spec/unit/transaction/persistence_spec.rb +4 -4
  238. data/spec/unit/transaction/resource_harness_spec.rb +18 -0
  239. data/spec/unit/type/group_spec.rb +11 -3
  240. data/spec/unit/type/resources_spec.rb +18 -0
  241. data/spec/unit/type/service_spec.rb +27 -1
  242. data/spec/unit/util/execution_spec.rb +77 -0
  243. data/spec/unit/util/filetype_spec.rb +13 -5
  244. data/spec/unit/util/log/destinations_spec.rb +10 -0
  245. data/spec/unit/util/logging_spec.rb +58 -41
  246. data/spec/unit/util/monkey_patches_spec.rb +18 -5
  247. data/spec/unit/util/posix_spec.rb +28 -0
  248. data/spec/unit/util/selinux_spec.rb +4 -0
  249. data/spec/unit/util/storage_spec.rb +107 -0
  250. data/spec/unit/util/suidmanager_spec.rb +1 -3
  251. data/spec/unit/util/windows/adsi_spec.rb +95 -0
  252. data/spec/unit/util/windows/service_spec.rb +377 -131
  253. metadata +24 -11
  254. data/ext/autotest/Rakefile +0 -8
  255. data/ext/autotest/config +0 -43
  256. data/ext/autotest/readme.rst +0 -16
  257. data/spec/fixtures/unit/provider/package/pkgng/pkg.info +0 -7
  258. data/spec/fixtures/unit/provider/package/pkgng/pkg.query_absent +0 -1
  259. data/spec/watchr.rb +0 -137
@@ -0,0 +1,523 @@
1
+ # Puppet as a Library "PAL"
2
+
3
+ # Yes, this requires all of puppet for now because 'settings' and many other things...
4
+ require 'puppet'
5
+ require 'puppet/parser/script_compiler'
6
+ require 'puppet/parser/catalog_compiler'
7
+
8
+ # This is the main entry point for "Puppet As a Library" PAL.
9
+ # This file should be required instead of "puppet"
10
+ # Initially, this will require ALL of puppet - over time this will change as the monolithical "puppet" is broken up
11
+ # into smaller components.
12
+ #
13
+ # @example Running a snippet of Puppet Language code
14
+ # require 'puppet_pal'
15
+ # result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: ['/tmp/testmodules']) do |pal|
16
+ # pal.evaluate_script_string('1+2+3')
17
+ # end
18
+ # # The result is the value 6
19
+ #
20
+ # @example Calling a function
21
+ # require 'puppet_pal'
22
+ # result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: ['/tmp/testmodules']) do |pal|
23
+ # pal.call_function('mymodule::myfunction', 10, 20)
24
+ # end
25
+ # # The result is what 'mymodule::myfunction' returns
26
+ #
27
+ module Puppet
28
+ module Pal
29
+
30
+ # Defines a context in which multiple operations in an env with a script compiler can be performed in a given block.
31
+ # The calls that takes place to PAL inside of the given block are all with the same instance of the compiler.
32
+ # The parameter `configured_by_env` makes it possible to either use the configuration in the environment, or specify
33
+ # `manifest_file` or `code_string` manually. If neither is given, an empty `code_string` is used.
34
+ #
35
+ # @example define a script compiler without any initial logic
36
+ # pal.with_script_compiler do | compiler |
37
+ # # do things with compiler
38
+ # end
39
+ #
40
+ # @example define a script compiler with a code_string containing initial logic
41
+ # pal.with_script_compiler(code_string: '$myglobal_var = 42') do | compiler |
42
+ # # do things with compiler
43
+ # end
44
+ #
45
+ # @param configured_by_env [Boolean] when true the environment's settings are used, otherwise the given `manifest_file` or `code_string`
46
+ # @param manifest_file [String] a Puppet Language file to load and evaluate before calling the given block, mutually exclusive with `code_string`
47
+ # @param code_string [String] a Puppet Language source string to load and evaluate before calling the given block, mutually exclusive with `manifest_file`
48
+ # @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation)
49
+ # If given at the environment level, the facts given here are merged with higher priority.
50
+ # @param variables [Hash] optional map of fully qualified variable name to value. If given at the environment level, the variables
51
+ # given here are merged with higher priority.
52
+ # @param block [Proc] the block performing operations on compiler
53
+ # @return [Object] what the block returns
54
+ # @yieldparam [Puppet::Pal::ScriptCompiler] compiler, a ScriptCompiler to perform operations on.
55
+ #
56
+ def self.with_script_compiler(
57
+ configured_by_env: false,
58
+ manifest_file: nil,
59
+ code_string: nil,
60
+ facts: nil,
61
+ variables: nil,
62
+ &block
63
+ )
64
+ # TRANSLATORS: do not translate variable name strings in these assertions
65
+ assert_mutually_exclusive(manifest_file, code_string, 'manifest_file', 'code_string')
66
+ assert_non_empty_string(manifest_file, 'manifest_file', true)
67
+ assert_non_empty_string(code_string, 'code_string', true)
68
+ assert_type(T_BOOLEAN, configured_by_env, "configured_by_env", false)
69
+
70
+ if configured_by_env
71
+ unless manifest_file.nil? && code_string.nil?
72
+ # TRANSLATORS: do not translate the variable names in this error message
73
+ raise ArgumentError, _("manifest_file or code_string cannot be given when configured_by_env is true")
74
+ end
75
+ # Use the manifest setting
76
+ manifest_file = Puppet[:manifest]
77
+ else
78
+ # An "undef" code_string is the only way to override Puppet[:manifest] & Puppet[:code] settings since an
79
+ # empty string is taken as Puppet[:code] not being set.
80
+ #
81
+ if manifest_file.nil? && code_string.nil?
82
+ code_string = 'undef'
83
+ end
84
+ end
85
+
86
+ Puppet[:tasks] = true
87
+ # After the assertions, if code_string is non nil - it has the highest precedence
88
+ Puppet[:code] = code_string unless code_string.nil?
89
+
90
+ # If manifest_file is nil, the #main method will use the env configured manifest
91
+ # to do things in the block while a Script Compiler is in effect
92
+ main(manifest_file, facts, variables, :script, &block)
93
+ end
94
+
95
+ # Evaluates a Puppet Language script string.
96
+ # @param code_string [String] a snippet of Puppet Language source code
97
+ # @return [Object] what the Puppet Language code_string evaluates to
98
+ # @deprecated Use {#with_script_compiler} and then evaluate_string on the given compiler - to be removed in 1.0 version
99
+ #
100
+ def self.evaluate_script_string(code_string)
101
+ # prevent the default loading of Puppet[:manifest] which is the environment's manifest-dir by default settings
102
+ # by setting code_string to 'undef'
103
+ with_script_compiler do |compiler|
104
+ compiler.evaluate_string(code_string)
105
+ end
106
+ end
107
+
108
+ # Evaluates a Puppet Language script (.pp) file.
109
+ # @param manifest_file [String] a file with Puppet Language source code
110
+ # @return [Object] what the Puppet Language manifest_file contents evaluates to
111
+ # @deprecated Use {#with_script_compiler} and then evaluate_file on the given compiler - to be removed in 1.0 version
112
+ #
113
+ def self.evaluate_script_manifest(manifest_file)
114
+ with_script_compiler do |compiler|
115
+ compiler.evaluate_file(manifest_file)
116
+ end
117
+ end
118
+
119
+ # Defines a context in which multiple operations in an env with a catalog producing compiler can be performed
120
+ # in a given block.
121
+ # The calls that takes place to PAL inside of the given block are all with the same instance of the compiler.
122
+ # The parameter `configured_by_env` makes it possible to either use the configuration in the environment, or specify
123
+ # `manifest_file` or `code_string` manually. If neither is given, an empty `code_string` is used.
124
+ #
125
+ # @example define a catalog compiler without any initial logic
126
+ # pal.with_catalog_compiler do | compiler |
127
+ # # do things with compiler
128
+ # end
129
+ #
130
+ # @example define a catalog compiler with a code_string containing initial logic
131
+ # pal.with_catalog_compiler(code_string: '$myglobal_var = 42') do | compiler |
132
+ # # do things with compiler
133
+ # end
134
+ #
135
+ # @param configured_by_env [Boolean] when true the environment's settings are used, otherwise the
136
+ # given `manifest_file` or `code_string`
137
+ # @param manifest_file [String] a Puppet Language file to load and evaluate before calling the given block, mutually exclusive
138
+ # with `code_string`
139
+ # @param code_string [String] a Puppet Language source string to load and evaluate before calling the given block, mutually
140
+ # exclusive with `manifest_file`
141
+ # @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation)
142
+ # If given at the environment level, the facts given here are merged with higher priority.
143
+ # @param variables [Hash] optional map of fully qualified variable name to value. If given at the environment level, the variables
144
+ # given here are merged with higher priority.
145
+ # @param block [Proc] the block performing operations on compiler
146
+ # @return [Object] what the block returns
147
+ # @yieldparam [Puppet::Pal::CatalogCompiler] compiler, a CatalogCompiler to perform operations on.
148
+ #
149
+ def self.with_catalog_compiler(
150
+ configured_by_env: false,
151
+ manifest_file: nil,
152
+ code_string: nil,
153
+ facts: nil,
154
+ variables: nil,
155
+ &block
156
+ )
157
+ # TRANSLATORS: do not translate variable name strings in these assertions
158
+ assert_mutually_exclusive(manifest_file, code_string, 'manifest_file', 'code_string')
159
+ assert_non_empty_string(manifest_file, 'manifest_file', true)
160
+ assert_non_empty_string(code_string, 'code_string', true)
161
+ assert_type(T_BOOLEAN, configured_by_env, "configured_by_env", false)
162
+
163
+ if configured_by_env
164
+ unless manifest_file.nil? && code_string.nil?
165
+ # TRANSLATORS: do not translate the variable names in this error message
166
+ raise ArgumentError, _("manifest_file or code_string cannot be given when configured_by_env is true")
167
+ end
168
+ # Use the manifest setting
169
+ manifest_file = Puppet[:manifest]
170
+ else
171
+ # An "undef" code_string is the only way to override Puppet[:manifest] & Puppet[:code] settings since an
172
+ # empty string is taken as Puppet[:code] not being set.
173
+ #
174
+ if manifest_file.nil? && code_string.nil?
175
+ code_string = 'undef'
176
+ end
177
+ end
178
+
179
+ Puppet[:tasks] = false
180
+ # After the assertions, if code_string is non nil - it has the highest precedence
181
+ Puppet[:code] = code_string unless code_string.nil?
182
+
183
+ # If manifest_file is nil, the #main method will use the env configured manifest
184
+ # to do things in the block while a Script Compiler is in effect
185
+ main(manifest_file, facts, variables, :catalog, &block)
186
+ end
187
+
188
+ # Defines the context in which to perform puppet operations (evaluation, etc)
189
+ # The code to evaluate in this context is given in a block.
190
+ #
191
+ # @param env_name [String] a name to use for the temporary environment - this only shows up in errors
192
+ # @param modulepath [Array<String>] an array of directory paths containing Puppet modules, may be empty, defaults to empty array
193
+ # @param settings_hash [Hash] a hash of settings - currently not used for anything, defaults to empty hash
194
+ # @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation)
195
+ # @param variables [Hash] optional map of fully qualified variable name to value
196
+ # @return [Object] returns what the given block returns
197
+ # @yieldparam [Puppet::Pal] context, a context that responds to Puppet::Pal methods
198
+ #
199
+ def self.in_tmp_environment(env_name,
200
+ modulepath: [],
201
+ settings_hash: {},
202
+ facts: nil,
203
+ variables: {},
204
+ &block
205
+ )
206
+ assert_non_empty_string(env_name, _("temporary environment name"))
207
+ # TRANSLATORS: do not translate variable name string in these assertions
208
+ assert_optionally_empty_array(modulepath, 'modulepath')
209
+
210
+ unless block_given?
211
+ raise ArgumentError, _("A block must be given to 'in_tmp_environment'") # TRANSLATORS 'in_tmp_environment' is a name, do not translate
212
+ end
213
+
214
+ env = Puppet::Node::Environment.create(env_name, modulepath)
215
+
216
+ in_environment_context(
217
+ Puppet::Environments::Static.new(env), # The tmp env is the only known env
218
+ env, facts, variables, &block
219
+ )
220
+ end
221
+
222
+ # Defines the context in which to perform puppet operations (evaluation, etc)
223
+ # The code to evaluate in this context is given in a block.
224
+ #
225
+ # The name of an environment (env_name) is always given. The location of that environment on disk
226
+ # is then either constructed by:
227
+ # * searching a given envpath where name is a child of a directory on that path, or
228
+ # * it is the directory given in env_dir (which must exist).
229
+ #
230
+ # The env_dir and envpath options are mutually exclusive.
231
+ #
232
+ # @param env_name [String] the name of an existing environment
233
+ # @param modulepath [Array<String>] an array of directory paths containing Puppet modules, overrides the modulepath of an existing env.
234
+ # Defaults to `{env_dir}/modules` if `env_dir` is given,
235
+ # @param pre_modulepath [Array<String>] like modulepath, but is prepended to the modulepath
236
+ # @param post_modulepath [Array<String>] like modulepath, but is appended to the modulepath
237
+ # @param settings_hash [Hash] a hash of settings - currently not used for anything, defaults to empty hash
238
+ # @param env_dir [String] a reference to a directory being the named environment (mutually exclusive with `envpath`)
239
+ # @param envpath [String] a path of directories in which there are environments to search for `env_name` (mutually exclusive with `env_dir`).
240
+ # Should be a single directory, or several directories separated with platform specific `File::PATH_SEPARATOR` character.
241
+ # @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation)
242
+ # @param variables [Hash] optional map of fully qualified variable name to value
243
+ # @return [Object] returns what the given block returns
244
+ # @yieldparam [Puppet::Pal] context, a context that responds to Puppet::Pal methods
245
+ #
246
+ def self.in_environment(env_name,
247
+ modulepath: nil,
248
+ pre_modulepath: [],
249
+ post_modulepath: [],
250
+ settings_hash: {},
251
+ env_dir: nil,
252
+ envpath: nil,
253
+ facts: nil,
254
+ variables: {},
255
+ &block
256
+ )
257
+ # TRANSLATORS terms in the assertions below are names of terms in code
258
+ assert_non_empty_string(env_name, 'env_name')
259
+ assert_optionally_empty_array(modulepath, 'modulepath', true)
260
+ assert_optionally_empty_array(pre_modulepath, 'pre_modulepath', false)
261
+ assert_optionally_empty_array(post_modulepath, 'post_modulepath', false)
262
+ assert_mutually_exclusive(env_dir, envpath, 'env_dir', 'envpath')
263
+
264
+ unless block_given?
265
+ raise ArgumentError, _("A block must be given to 'in_environment'") # TRANSLATORS 'in_environment' is a name, do not translate
266
+ end
267
+
268
+ if env_dir
269
+ unless Puppet::FileSystem.exist?(env_dir)
270
+ raise ArgumentError, _("The environment directory '%{env_dir}' does not exist") % { env_dir: env_dir }
271
+ end
272
+
273
+ # a nil modulepath for env_dir means it should use its ./modules directory
274
+ mid_modulepath = modulepath.nil? ? [Puppet::FileSystem.expand_path(File.join(env_dir, 'modules'))] : modulepath
275
+
276
+ env = Puppet::Node::Environment.create(env_name, pre_modulepath + mid_modulepath + post_modulepath)
277
+ environments = Puppet::Environments::StaticDirectory.new(env_name, env_dir, env) # The env being used is the only one...
278
+ else
279
+ assert_non_empty_string(envpath, 'envpath')
280
+
281
+ # The environment is resolved against the envpath. This is setup without a basemodulepath
282
+ # The modulepath defaults to the 'modulepath' in the found env when "Directories" is used
283
+ #
284
+ if envpath.is_a?(String) && envpath.include?(File::PATH_SEPARATOR)
285
+ # potentially more than one directory to search
286
+ env_loaders = Puppet::Environments::Directories.from_path(envpath, [])
287
+ environments = Puppet::Environments::Combined.new(*env_loaders)
288
+ else
289
+ environments = Puppet::Environments::Directories.new(envpath, [])
290
+ end
291
+ env = environments.get(env_name)
292
+ if env.nil?
293
+ raise ArgumentError, _("No directory found for the environment '%{env_name}' on the path '%{envpath}'") % { env_name: env_name, envpath: envpath }
294
+ end
295
+ # A given modulepath should override the default
296
+ mid_modulepath = modulepath.nil? ? env.modulepath : modulepath
297
+ env_path = env.configuration.path_to_env
298
+ env = env.override_with(:modulepath => pre_modulepath + mid_modulepath + post_modulepath)
299
+ # must configure this in case logic looks up the env by name again (otherwise the looked up env does
300
+ # not have the same effective modulepath).
301
+ environments = Puppet::Environments::StaticDirectory.new(env_name, env_path, env) # The env being used is the only one...
302
+ end
303
+ in_environment_context(environments, env, facts, variables, &block)
304
+ end
305
+
306
+ # Prepares the puppet context with pal information - and delegates to the block
307
+ # No set up is performed at this step - it is delayed until it is known what the
308
+ # operation is going to be (for example - using a ScriptCompiler).
309
+ #
310
+ def self.in_environment_context(environments, env, facts, variables, &block)
311
+ # Create a default node to use (may be overridden later)
312
+ node = Puppet::Node.new(Puppet[:node_name_value], :environment => env)
313
+
314
+ Puppet.override(
315
+ environments: environments, # The env being used is the only one...
316
+ pal_env: env, # provide as convenience
317
+ pal_current_node: node, # to allow it to be picked up instead of created
318
+ pal_variables: variables, # common set of variables across several inner contexts
319
+ pal_facts: facts # common set of facts across several inner contexts (or nil)
320
+ ) do
321
+ # DELAY: prepare_node_facts(node, facts)
322
+ return block.call(self)
323
+ end
324
+ end
325
+ private_class_method :in_environment_context
326
+
327
+ # Prepares the node for use by giving it node_facts (if given)
328
+ # If a hash of facts values is given, then the operation of creating a node with facts is much
329
+ # speeded up (as getting a fresh set of facts is avoided in a later step).
330
+ #
331
+ def self.prepare_node_facts(node, facts)
332
+ # Prepare the node with facts if it does not already have them
333
+ if node.facts.nil?
334
+ node_facts = facts.nil? ? nil : Puppet::Node::Facts.new(Puppet[:node_name_value], facts)
335
+ node.fact_merge(node_facts)
336
+ # Add server facts so $server_facts[environment] exists when doing a puppet script
337
+ # SCRIPT TODO: May be needed when running scripts under orchestrator. Leave it for now.
338
+ #
339
+ node.add_server_facts({})
340
+ end
341
+ end
342
+ private_class_method :prepare_node_facts
343
+
344
+ def self.add_variables(scope, variables)
345
+ return if variables.nil?
346
+ unless variables.is_a?(Hash)
347
+ raise ArgumentError, _("Given variables must be a hash, got %{type}") % { type: variables.class }
348
+ end
349
+
350
+ rich_data_t = Puppet::Pops::Types::TypeFactory.rich_data
351
+ variables.each_pair do |k,v|
352
+ unless k =~ Puppet::Pops::Patterns::VAR_NAME
353
+ raise ArgumentError, _("Given variable '%{varname}' has illegal name") % { varname: k }
354
+ end
355
+
356
+ unless rich_data_t.instance?(v)
357
+ raise ArgumentError, _("Given value for '%{varname}' has illegal type - got: %{type}") % { varname: k, type: v.class }
358
+ end
359
+
360
+ scope.setvar(k, v)
361
+ end
362
+ end
363
+ private_class_method :add_variables
364
+
365
+ # The main routine for script compiler
366
+ # Picks up information from the puppet context and configures a script compiler which is given to
367
+ # the provided block
368
+ #
369
+ def self.main(manifest, facts, variables, internal_compiler_class)
370
+ # Configure the load path
371
+ env = Puppet.lookup(:pal_env)
372
+ env.each_plugin_directory do |dir|
373
+ $LOAD_PATH << dir unless $LOAD_PATH.include?(dir)
374
+ end
375
+
376
+ # Puppet requires Facter, which initializes its lookup paths. Reset Facter to
377
+ # pickup the new $LOAD_PATH.
378
+ Facter.reset
379
+
380
+ node = Puppet.lookup(:pal_current_node)
381
+ pal_facts = Puppet.lookup(:pal_facts)
382
+ pal_variables = Puppet.lookup(:pal_variables)
383
+
384
+ overrides = {}
385
+ unless facts.nil? || facts.empty?
386
+ pal_facts = pal_facts.merge(facts)
387
+ overrides[:pal_facts] = pal_facts
388
+ end
389
+ unless variables.nil? || variables.empty?
390
+ pal_variables = pal_variables.merge(variables)
391
+ overrides[:pal_variables] = pal_variables
392
+ end
393
+
394
+ prepare_node_facts(node, pal_facts)
395
+
396
+ configured_environment = node.environment || Puppet.lookup(:current_environment)
397
+
398
+ apply_environment = manifest ?
399
+ configured_environment.override_with(:manifest => manifest) :
400
+ configured_environment
401
+
402
+ # Modify the node descriptor to use the special apply_environment.
403
+ # It is based on the actual environment from the node, or the locally
404
+ # configured environment if the node does not specify one.
405
+ # If a manifest file is passed on the command line, it overrides
406
+ # the :manifest setting of the apply_environment.
407
+ node.environment = apply_environment
408
+
409
+ # TRANSLATORS, the string "For puppet PAL" is not user facing
410
+ Puppet.override({:current_environment => apply_environment}, "For puppet PAL") do
411
+ begin
412
+ # support the following features when evaluating puppet code
413
+ # * $facts with facts from host running the script
414
+ # * $settings with 'settings::*' namespace populated, and '$settings::all_local' hash
415
+ # * $trusted as setup when using puppet apply
416
+ # * an environment
417
+ #
418
+
419
+ # fixup trusted information
420
+ node.sanitize()
421
+
422
+ compiler = create_internal_compiler(internal_compiler_class, node)
423
+ # compiler = Puppet::Parser::ScriptCompiler.new(node.environment, node.name)
424
+ topscope = compiler.topscope
425
+
426
+ # When scripting the trusted data are always local, but set them anyway
427
+ # When compiling for a catalog, the catalog compiler does this
428
+ unless internal_compiler_class == :catalog
429
+ topscope.set_trusted(node.trusted_data)
430
+
431
+ # Server facts are always about the local node's version etc.
432
+ topscope.set_server_facts(node.server_facts)
433
+
434
+ # Set $facts for the node running the script
435
+ facts_hash = node.facts.nil? ? {} : node.facts.values
436
+ topscope.set_facts(facts_hash)
437
+
438
+ # create the $settings:: variables
439
+ topscope.merge_settings(node.environment.name, false)
440
+ end
441
+
442
+ add_variables(topscope, pal_variables)
443
+
444
+ case internal_compiler_class
445
+ when :script
446
+ pal_compiler = ScriptCompiler.new(compiler)
447
+ overrides[:pal_script_compiler] = overrides[:pal_compiler] = pal_compiler
448
+ when :catalog
449
+ pal_compiler = CatalogCompiler.new(compiler)
450
+ overrides[:pal_catalog_compiler] = overrides[:pal_compiler] = pal_compiler
451
+ end
452
+
453
+ # Make compiler available to Puppet#lookup and injection in functions
454
+ # TODO: The compiler instances should be available under non PAL use as well!
455
+ # TRANSLATORS: Do not translate, symbolic name
456
+ Puppet.override(overrides, "PAL::with_#{internal_compiler_class}_compiler") do
457
+ compiler.compile do | compiler_yield |
458
+ # wrap the internal compiler to prevent it from leaking in the PAL API
459
+ if block_given?
460
+ yield(pal_compiler)
461
+ end
462
+ end
463
+ end
464
+
465
+ rescue Puppet::ParseErrorWithIssue, Puppet::Error
466
+ # already logged and handled by the compiler for these two cases
467
+ raise
468
+
469
+ rescue => detail
470
+ Puppet.log_exception(detail)
471
+ raise
472
+ end
473
+ end
474
+ end
475
+ private_class_method :main
476
+
477
+ def self.create_internal_compiler(compiler_class_reference, node)
478
+ case compiler_class_reference
479
+ when :script
480
+ Puppet::Parser::ScriptCompiler.new(node.environment, node.name)
481
+ when :catalog
482
+ Puppet::Parser::CatalogCompiler.new(node)
483
+ else
484
+ raise ArgumentError, "Internal Error: Invalid compiler type requested."
485
+ end
486
+ end
487
+
488
+ T_STRING = Puppet::Pops::Types::PStringType::NON_EMPTY
489
+ T_STRING_ARRAY = Puppet::Pops::Types::TypeFactory.array_of(T_STRING)
490
+ T_ANY_ARRAY = Puppet::Pops::Types::TypeFactory.array_of_any
491
+ T_BOOLEAN = Puppet::Pops::Types::PBooleanType::DEFAULT
492
+
493
+ T_GENERIC_TASK_HASH = Puppet::Pops::Types::TypeFactory.hash_kv(
494
+ Puppet::Pops::Types::TypeFactory.pattern(/\A[a-z][a-z0-9_]*\z/), Puppet::Pops::Types::TypeFactory.data)
495
+
496
+ def self.assert_type(type, value, what, allow_nil=false)
497
+ Puppet::Pops::Types::TypeAsserter.assert_instance_of(nil, type, value, allow_nil) { _('Puppet Pal: %{what}') % {what: what} }
498
+ end
499
+
500
+ def self.assert_non_empty_string(s, what, allow_nil=false)
501
+ assert_type(T_STRING, s, what, allow_nil)
502
+ end
503
+
504
+ def self.assert_optionally_empty_array(a, what, allow_nil=false)
505
+ assert_type(T_STRING_ARRAY, a, what, allow_nil)
506
+ end
507
+ private_class_method :assert_optionally_empty_array
508
+
509
+ def self.assert_mutually_exclusive(a, b, a_term, b_term)
510
+ if a && b
511
+ raise ArgumentError, _("Cannot use '%{a_term}' and '%{b_term}' at the same time") % { a_term: a_term, b_term: b_term }
512
+ end
513
+ end
514
+ private_class_method :assert_mutually_exclusive
515
+
516
+ def self.assert_block_given(block)
517
+ if block.nil?
518
+ raise ArgumentError, _("A block must be given")
519
+ end
520
+ end
521
+ private_class_method :assert_block_given
522
+ end
523
+ end
@@ -0,0 +1,71 @@
1
+ module Puppet
2
+ module Pal
3
+ # A PlanSignature is returned from `plan_signature`. Its purpose is to answer questions about the plans's parameters
4
+ # and if it can be called with a hash of named parameters.
5
+ #
6
+ # @api public
7
+ #
8
+ class PlanSignature
9
+ def initialize(plan_function)
10
+ @plan_func = plan_function
11
+ end
12
+
13
+ # Returns true or false depending on if the given PlanSignature is callable with a set of named arguments or not
14
+ # In addition to returning the boolean outcome, if a block is given, it is called with a string of formatted
15
+ # error messages that describes the difference between what was given and what is expected. The error message may
16
+ # have multiple lines of text, and each line is indented one space.
17
+ #
18
+ # @example Checking if signature is acceptable
19
+ #
20
+ # signature = pal.plan_signature('myplan')
21
+ # signature.callable_with?({x => 10}) { |errors| raise ArgumentError("Ooops: given arguments does not match\n#{errors}") }
22
+ #
23
+ # @api public
24
+ #
25
+ def callable_with?(args_hash)
26
+ dispatcher = @plan_func.class.dispatcher.dispatchers[0]
27
+
28
+ param_scope = {}
29
+ # Assign all non-nil values, even those that represent non-existent parameters.
30
+ args_hash.each { |k, v| param_scope[k] = v unless v.nil? }
31
+ dispatcher.parameters.each do |p|
32
+ name = p.name
33
+ arg = args_hash[name]
34
+ if arg.nil?
35
+ # Arg either wasn't given, or it was undef
36
+ if p.value.nil?
37
+ # No default. Assign nil if the args_hash included it
38
+ param_scope[name] = nil if args_hash.include?(name)
39
+ else
40
+ # parameter does not have a default value, it will be assigned its default when being called
41
+ # we assume that the default value is of the correct type and therefore simply skip
42
+ # checking this
43
+ # param_scope[name] = param_scope.evaluate(name, p.value, closure_scope, @evaluator)
44
+ end
45
+ end
46
+ end
47
+
48
+ errors = Puppet::Pops::Types::TypeMismatchDescriber.singleton.describe_struct_signature(dispatcher.params_struct, param_scope).flatten
49
+ return true if errors.empty?
50
+ if block_given?
51
+ yield errors.map {|e| e.format }.join("\n")
52
+ end
53
+ false
54
+ end
55
+
56
+ # Returns a PStructType describing the parameters as a puppet Struct data type
57
+ # Note that a `to_s` on the returned structure will result in a human readable Struct datatype as a
58
+ # description of what a plan expects.
59
+ #
60
+ # @return [Puppet::Pops::Types::PStructType] a struct data type describing the parameters and their types
61
+ #
62
+ # @api public
63
+ #
64
+ def params_type
65
+ dispatcher = @plan_func.class.dispatcher.dispatchers[0]
66
+ dispatcher.params_struct
67
+ end
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,71 @@
1
+ module Puppet
2
+ module Pal
3
+
4
+ class ScriptCompiler < Compiler
5
+ # Returns the signature of the given plan name
6
+ # @param plan_name [String] the name of the plan to get the signature of
7
+ # @return [Puppet::Pal::PlanSignature, nil] returns a PlanSignature, or nil if plan is not found
8
+ #
9
+ def plan_signature(plan_name)
10
+ loader = internal_compiler.loaders.private_environment_loader
11
+ if func = loader.load(:plan, plan_name)
12
+ return PlanSignature.new(func)
13
+ end
14
+ # Could not find plan
15
+ nil
16
+ end
17
+
18
+ # Returns an array of TypedName objects for all plans, optionally filtered by a regular expression.
19
+ # The returned array has more information than just the leaf name - the typical thing is to just get
20
+ # the name as showing the following example.
21
+ #
22
+ # Errors that occur during plan discovery will either be logged as warnings or collected by the optional
23
+ # `error_collector` array. When provided, it will receive {Puppet::DataTypes::Error} instances describing
24
+ # each error in detail and no warnings will be logged.
25
+ #
26
+ # @example getting the names of all plans
27
+ # compiler.list_plans.map {|tn| tn.name }
28
+ #
29
+ # @param filter_regex [Regexp] an optional regexp that filters based on name (matching names are included in the result)
30
+ # @param error_collector [Array<Puppet::DataTypes::Error>] an optional array that will receive errors during load
31
+ # @return [Array<Puppet::Pops::Loader::TypedName>] an array of typed names
32
+ #
33
+ def list_plans(filter_regex = nil, error_collector = nil)
34
+ list_loadable_kind(:plan, filter_regex, error_collector)
35
+ end
36
+
37
+ # Returns the signature callable of the given task (the arguments it accepts, and the data type it returns)
38
+ # @param task_name [String] the name of the task to get the signature of
39
+ # @return [Puppet::Pal::TaskSignature, nil] returns a TaskSignature, or nil if task is not found
40
+ #
41
+ def task_signature(task_name)
42
+ loader = internal_compiler.loaders.private_environment_loader
43
+ if task = loader.load(:task, task_name)
44
+ return TaskSignature.new(task)
45
+ end
46
+ # Could not find task
47
+ nil
48
+ end
49
+
50
+ # Returns an array of TypedName objects for all tasks, optionally filtered by a regular expression.
51
+ # The returned array has more information than just the leaf name - the typical thing is to just get
52
+ # the name as showing the following example.
53
+ #
54
+ # @example getting the names of all tasks
55
+ # compiler.list_tasks.map {|tn| tn.name }
56
+ #
57
+ # Errors that occur during task discovery will either be logged as warnings or collected by the optional
58
+ # `error_collector` array. When provided, it will receive {Puppet::DataTypes::Error} instances describing
59
+ # each error in detail and no warnings will be logged.
60
+ #
61
+ # @param filter_regex [Regexp] an optional regexp that filters based on name (matching names are included in the result)
62
+ # @param error_collector [Array<Puppet::DataTypes::Error>] an optional array that will receive errors during load
63
+ # @return [Array<Puppet::Pops::Loader::TypedName>] an array of typed names
64
+ #
65
+ def list_tasks(filter_regex = nil, error_collector = nil)
66
+ list_loadable_kind(:task, filter_regex, error_collector)
67
+ end
68
+ end
69
+
70
+ end
71
+ end