module Puppet::Pal
This is the main entry point for “Puppet As a Library” PAL. This file should be required instead of “puppet” Initially, this will require ALL of puppet - over time this will change as the monolithical “puppet” is broken up into smaller components.
@example Running a snippet of Puppet Language code
require 'puppet_pal' result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: ['/tmp/testmodules']) do |pal| pal.evaluate_script_string('1+2+3') end # The result is the value 6
@example Calling a function
require 'puppet_pal' result = Puppet::Pal.in_tmp_environment('pal_env', modulepath: ['/tmp/testmodules']) do |pal| pal.call_function('mymodule::myfunction', 10, 20) end # The result is what 'mymodule::myfunction' returns
@api public
Constants
- T_ANY_ARRAY
- T_BOOLEAN
- T_GENERIC_TASK_HASH
- T_STRING
- T_STRING_ARRAY
Public Class Methods
# File lib/puppet/pal/pal_impl.rb 568 def self.assert_non_empty_string(s, what, allow_nil=false) 569 assert_type(T_STRING, s, what, allow_nil) 570 end
# File lib/puppet/pal/pal_impl.rb 564 def self.assert_type(type, value, what, allow_nil=false) 565 Puppet::Pops::Types::TypeAsserter.assert_instance_of(nil, type, value, allow_nil) { _('Puppet Pal: %{what}') % {what: what} } 566 end
# File lib/puppet/pal/pal_impl.rb 545 def self.create_internal_compiler(compiler_class_reference, node) 546 case compiler_class_reference 547 when :script 548 Puppet::Parser::ScriptCompiler.new(node.environment, node.name) 549 when :catalog 550 Puppet::Parser::CatalogCompiler.new(node) 551 else 552 raise ArgumentError, "Internal Error: Invalid compiler type requested." 553 end 554 end
Evaluates a Puppet Language script (.pp) file. @param manifest_file [String] a file with Puppet Language source code @return [Object] what the Puppet Language manifest_file contents evaluates to @deprecated Use {#with_script_compiler} and then evaluate_file on the given compiler - to be removed in 1.0 version
# File lib/puppet/pal/pal_impl.rb 129 def self.evaluate_script_manifest(manifest_file) 130 with_script_compiler do |compiler| 131 compiler.evaluate_file(manifest_file) 132 end 133 end
Evaluates a Puppet Language script string. @param code_string [String] a snippet of Puppet Language source code @return [Object] what the Puppet Language code_string evaluates to @deprecated Use {#with_script_compiler} and then evaluate_string on the given compiler - to be removed in 1.0 version
# File lib/puppet/pal/pal_impl.rb 116 def self.evaluate_script_string(code_string) 117 # prevent the default loading of Puppet[:manifest] which is the environment's manifest-dir by default settings 118 # by setting code_string to 'undef' 119 with_script_compiler do |compiler| 120 compiler.evaluate_string(code_string) 121 end 122 end
Defines the context in which to perform puppet operations (evaluation, etc) The code to evaluate in this context is given in a block.
The name of an environment (env_name) is always given. The location of that environment on disk is then either constructed by:
-
searching a given envpath where name is a child of a directory on that path, or
-
it is the directory given in env_dir (which must exist).
The env_dir and envpath options are mutually exclusive.
@param env_name [String] the name of an existing environment @param modulepath [Array<String>] an array of directory paths containing Puppet modules, overrides the modulepath of an existing env.
Defaults to `{env_dir}/modules` if `env_dir` is given,
@param pre_modulepath [Array<String>] like modulepath, but is prepended to the modulepath @param post_modulepath [Array<String>] like modulepath, but is appended to the modulepath @param settings_hash [Hash] a hash of settings - currently not used for anything, defaults to empty hash @param env_dir [String] a reference to a directory being the named environment (mutually exclusive with `envpath`) @param envpath [String] a path of directories in which there are environments to search for `env_name` (mutually exclusive with `env_dir`).
Should be a single directory, or several directories separated with platform specific `File::PATH_SEPARATOR` character.
@param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation) @param variables [Hash] optional map of fully qualified variable name to value @return [Object] returns what the given block returns @yieldparam [Puppet::Pal] context, a context that responds to Puppet::Pal methods
@api public
# File lib/puppet/pal/pal_impl.rb 280 def self.in_environment(env_name, 281 modulepath: nil, 282 pre_modulepath: [], 283 post_modulepath: [], 284 settings_hash: {}, 285 env_dir: nil, 286 envpath: nil, 287 facts: nil, 288 variables: {}, 289 &block 290 ) 291 # TRANSLATORS terms in the assertions below are names of terms in code 292 assert_non_empty_string(env_name, 'env_name') 293 assert_optionally_empty_array(modulepath, 'modulepath', true) 294 assert_optionally_empty_array(pre_modulepath, 'pre_modulepath', false) 295 assert_optionally_empty_array(post_modulepath, 'post_modulepath', false) 296 assert_mutually_exclusive(env_dir, envpath, 'env_dir', 'envpath') 297 298 unless block_given? 299 raise ArgumentError, _("A block must be given to 'in_environment'") # TRANSLATORS 'in_environment' is a name, do not translate 300 end 301 302 if env_dir 303 unless Puppet::FileSystem.exist?(env_dir) 304 raise ArgumentError, _("The environment directory '%{env_dir}' does not exist") % { env_dir: env_dir } 305 end 306 307 # a nil modulepath for env_dir means it should use its ./modules directory 308 mid_modulepath = modulepath.nil? ? [Puppet::FileSystem.expand_path(File.join(env_dir, 'modules'))] : modulepath 309 310 env = Puppet::Node::Environment.create(env_name, pre_modulepath + mid_modulepath + post_modulepath) 311 environments = Puppet::Environments::StaticDirectory.new(env_name, env_dir, env) # The env being used is the only one... 312 else 313 assert_non_empty_string(envpath, 'envpath') 314 315 # The environment is resolved against the envpath. This is setup without a basemodulepath 316 # The modulepath defaults to the 'modulepath' in the found env when "Directories" is used 317 # 318 if envpath.is_a?(String) && envpath.include?(File::PATH_SEPARATOR) 319 # potentially more than one directory to search 320 env_loaders = Puppet::Environments::Directories.from_path(envpath, []) 321 environments = Puppet::Environments::Combined.new(*env_loaders) 322 else 323 environments = Puppet::Environments::Directories.new(envpath, []) 324 end 325 env = environments.get(env_name) 326 if env.nil? 327 raise ArgumentError, _("No directory found for the environment '%{env_name}' on the path '%{envpath}'") % { env_name: env_name, envpath: envpath } 328 end 329 # A given modulepath should override the default 330 mid_modulepath = modulepath.nil? ? env.modulepath : modulepath 331 env_path = env.configuration.path_to_env 332 env = env.override_with(:modulepath => pre_modulepath + mid_modulepath + post_modulepath) 333 # must configure this in case logic looks up the env by name again (otherwise the looked up env does 334 # not have the same effective modulepath). 335 environments = Puppet::Environments::StaticDirectory.new(env_name, env_path, env) # The env being used is the only one... 336 end 337 in_environment_context(environments, env, facts, variables, &block) 338 end
Defines the context in which to perform puppet operations (evaluation, etc) The code to evaluate in this context is given in a block.
@param env_name [String] a name to use for the temporary environment - this only shows up in errors @param modulepath [Array<String>] an array of directory paths containing Puppet modules, may be empty, defaults to empty array @param settings_hash [Hash] a hash of settings - currently not used for anything, defaults to empty hash @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation) @param variables [Hash] optional map of fully qualified variable name to value @return [Object] returns what the given block returns @yieldparam [Puppet::Pal] context, a context that responds to Puppet::Pal methods
# File lib/puppet/pal/pal_impl.rb 232 def self.in_tmp_environment(env_name, 233 modulepath: [], 234 settings_hash: {}, 235 facts: nil, 236 variables: {}, 237 &block 238 ) 239 assert_non_empty_string(env_name, _("temporary environment name")) 240 # TRANSLATORS: do not translate variable name string in these assertions 241 assert_optionally_empty_array(modulepath, 'modulepath') 242 243 unless block_given? 244 raise ArgumentError, _("A block must be given to 'in_tmp_environment'") # TRANSLATORS 'in_tmp_environment' is a name, do not translate 245 end 246 247 env = Puppet::Node::Environment.create(env_name, modulepath) 248 249 in_environment_context( 250 Puppet::Environments::Static.new(env), # The tmp env is the only known env 251 env, facts, variables, &block 252 ) 253 end
Defines a context in which multiple operations in an env with a catalog producing compiler can be performed in a given block. The calls that takes place to PAL inside of the given block are all with the same instance of the compiler. The parameter `configured_by_env` makes it possible to either use the configuration in the environment, or specify `manifest_file` or `code_string` manually. If neither is given, an empty `code_string` is used.
@example define a catalog compiler without any initial logic
pal.with_catalog_compiler do | compiler | # do things with compiler end
@example define a catalog compiler with a code_string containing initial logic
pal.with_catalog_compiler(code_string: '$myglobal_var = 42') do | compiler | # do things with compiler end
@param configured_by_env [Boolean] when true the environment's settings are used, otherwise the
given `manifest_file` or `code_string`
@param manifest_file [String] a Puppet Language file to load and evaluate before calling the given block, mutually exclusive
with `code_string`
@param code_string [String] a Puppet Language source string to load and evaluate before calling the given block, mutually
exclusive with `manifest_file`
@param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation)
If given at the environment level, the facts given here are merged with higher priority.
@param variables [Hash] optional map of fully qualified variable name to value. If given at the environment level, the variables
given here are merged with higher priority.
@param block [Proc] the block performing operations on compiler @return [Object] what the block returns @yieldparam [Puppet::Pal::CatalogCompiler] compiler, a CatalogCompiler to perform operations on.
# File lib/puppet/pal/pal_impl.rb 165 def self.with_catalog_compiler( 166 configured_by_env: false, 167 manifest_file: nil, 168 code_string: nil, 169 facts: {}, 170 variables: {}, 171 target_variables: {}, 172 &block 173 ) 174 # TRANSLATORS: do not translate variable name strings in these assertions 175 assert_mutually_exclusive(manifest_file, code_string, 'manifest_file', 'code_string') 176 assert_non_empty_string(manifest_file, 'manifest_file', true) 177 assert_non_empty_string(code_string, 'code_string', true) 178 assert_type(T_BOOLEAN, configured_by_env, "configured_by_env", false) 179 180 if configured_by_env 181 unless manifest_file.nil? && code_string.nil? 182 # TRANSLATORS: do not translate the variable names in this error message 183 raise ArgumentError, _("manifest_file or code_string cannot be given when configured_by_env is true") 184 end 185 # Use the manifest setting 186 manifest_file = Puppet[:manifest] 187 else 188 # An "undef" code_string is the only way to override Puppet[:manifest] & Puppet[:code] settings since an 189 # empty string is taken as Puppet[:code] not being set. 190 # 191 if manifest_file.nil? && code_string.nil? 192 code_string = 'undef' 193 end 194 end 195 196 # We need to make sure to set these back when we're done 197 previous_tasks_value = Puppet[:tasks] 198 previous_code_value = Puppet[:code] 199 200 Puppet[:tasks] = false 201 # After the assertions, if code_string is non nil - it has the highest precedence 202 Puppet[:code] = code_string unless code_string.nil? 203 204 # If manifest_file is nil, the #main method will use the env configured manifest 205 # to do things in the block while a Script Compiler is in effect 206 main( 207 manifest: manifest_file, 208 facts: facts, 209 variables: variables, 210 target_variables: target_variables, 211 internal_compiler_class: :catalog, 212 set_local_facts: false, 213 &block 214 ) 215 ensure 216 # Clean up after ourselves 217 Puppet[:tasks] = previous_tasks_value 218 Puppet[:code] = previous_code_value 219 end
Defines a context in which multiple operations in an env with a script compiler can be performed in a given block. The calls that takes place to PAL inside of the given block are all with the same instance of the compiler. The parameter `configured_by_env` makes it possible to either use the configuration in the environment, or specify `manifest_file` or `code_string` manually. If neither is given, an empty `code_string` is used.
@example define a script compiler without any initial logic
pal.with_script_compiler do | compiler | # do things with compiler end
@example define a script compiler with a code_string containing initial logic
pal.with_script_compiler(code_string: '$myglobal_var = 42') do | compiler | # do things with compiler end
@param configured_by_env [Boolean] when true the environment's settings are used, otherwise the given `manifest_file` or `code_string` @param manifest_file [String] a Puppet Language file to load and evaluate before calling the given block, mutually exclusive with `code_string` @param code_string [String] a Puppet Language source string to load and evaluate before calling the given block, mutually exclusive with `manifest_file` @param facts [Hash] optional map of fact name to fact value - if not given will initialize the facts (which is a slow operation)
If given at the environment level, the facts given here are merged with higher priority.
@param variables [Hash] optional map of fully qualified variable name to value. If given at the environment level, the variables
given here are merged with higher priority.
@param set_local_facts [Boolean] when true, the $facts, $server_facts, and $trusted variables are set for the scope. @param block [Proc] the block performing operations on compiler @return [Object] what the block returns @yieldparam [Puppet::Pal::ScriptCompiler] compiler, a ScriptCompiler to perform operations on.
# File lib/puppet/pal/pal_impl.rb 59 def self.with_script_compiler( 60 configured_by_env: false, 61 manifest_file: nil, 62 code_string: nil, 63 facts: {}, 64 variables: {}, 65 set_local_facts: true, 66 &block 67 ) 68 # TRANSLATORS: do not translate variable name strings in these assertions 69 assert_mutually_exclusive(manifest_file, code_string, 'manifest_file', 'code_string') 70 assert_non_empty_string(manifest_file, 'manifest_file', true) 71 assert_non_empty_string(code_string, 'code_string', true) 72 assert_type(T_BOOLEAN, configured_by_env, "configured_by_env", false) 73 74 if configured_by_env 75 unless manifest_file.nil? && code_string.nil? 76 # TRANSLATORS: do not translate the variable names in this error message 77 raise ArgumentError, _("manifest_file or code_string cannot be given when configured_by_env is true") 78 end 79 # Use the manifest setting 80 manifest_file = Puppet[:manifest] 81 else 82 # An "undef" code_string is the only way to override Puppet[:manifest] & Puppet[:code] settings since an 83 # empty string is taken as Puppet[:code] not being set. 84 # 85 if manifest_file.nil? && code_string.nil? 86 code_string = 'undef' 87 end 88 end 89 90 previous_tasks_value = Puppet[:tasks] 91 previous_code_value = Puppet[:code] 92 Puppet[:tasks] = true 93 # After the assertions, if code_string is non nil - it has the highest precedence 94 Puppet[:code] = code_string unless code_string.nil? 95 96 # If manifest_file is nil, the #main method will use the env configured manifest 97 # to do things in the block while a Script Compiler is in effect 98 main( 99 manifest: manifest_file, 100 facts: facts, 101 variables: variables, 102 internal_compiler_class: :script, 103 set_local_facts: set_local_facts, 104 &block 105 ) 106 ensure 107 Puppet[:tasks] = previous_tasks_value 108 Puppet[:code] = previous_code_value 109 end
Private Class Methods
# File lib/puppet/pal/pal_impl.rb 378 def self.add_variables(scope, variables) 379 return if variables.nil? 380 unless variables.is_a?(Hash) 381 raise ArgumentError, _("Given variables must be a hash, got %{type}") % { type: variables.class } 382 end 383 384 rich_data_t = Puppet::Pops::Types::TypeFactory.rich_data 385 variables.each_pair do |k,v| 386 unless k =~ Puppet::Pops::Patterns::VAR_NAME 387 raise ArgumentError, _("Given variable '%{varname}' has illegal name") % { varname: k } 388 end 389 390 unless rich_data_t.instance?(v) 391 raise ArgumentError, _("Given value for '%{varname}' has illegal type - got: %{type}") % { varname: k, type: v.class } 392 end 393 394 scope.setvar(k, v) 395 end 396 end
# File lib/puppet/pal/pal_impl.rb 584 def self.assert_block_given(block) 585 if block.nil? 586 raise ArgumentError, _("A block must be given") 587 end 588 end
# File lib/puppet/pal/pal_impl.rb 577 def self.assert_mutually_exclusive(a, b, a_term, b_term) 578 if a && b 579 raise ArgumentError, _("Cannot use '%{a_term}' and '%{b_term}' at the same time") % { a_term: a_term, b_term: b_term } 580 end 581 end
# File lib/puppet/pal/pal_impl.rb 572 def self.assert_optionally_empty_array(a, what, allow_nil=false) 573 assert_type(T_STRING_ARRAY, a, what, allow_nil) 574 end
Prepares the puppet context with pal information - and delegates to the block No set up is performed at this step - it is delayed until it is known what the operation is going to be (for example - using a ScriptCompiler).
# File lib/puppet/pal/pal_impl.rb 344 def self.in_environment_context(environments, env, facts, variables, &block) 345 # Create a default node to use (may be overridden later) 346 node = Puppet::Node.new(Puppet[:node_name_value], :environment => env) 347 348 Puppet.override( 349 environments: environments, # The env being used is the only one... 350 pal_env: env, # provide as convenience 351 pal_current_node: node, # to allow it to be picked up instead of created 352 pal_variables: variables, # common set of variables across several inner contexts 353 pal_facts: facts # common set of facts across several inner contexts (or nil) 354 ) do 355 # DELAY: prepare_node_facts(node, facts) 356 return block.call(self) 357 end 358 end
The main routine for script compiler Picks up information from the puppet context and configures a script compiler which is given to the provided block
# File lib/puppet/pal/pal_impl.rb 403 def self.main( 404 manifest: nil, 405 facts: {}, 406 variables: {}, 407 target_variables: {}, 408 internal_compiler_class: nil, 409 set_local_facts: true 410 ) 411 # Configure the load path 412 env = Puppet.lookup(:pal_env) 413 env.each_plugin_directory do |dir| 414 $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) 415 end 416 417 # Puppet requires Facter, which initializes its lookup paths. Reset Facter to 418 # pickup the new $LOAD_PATH. 419 Puppet.runtime[:facter].reset 420 421 node = Puppet.lookup(:pal_current_node) 422 pal_facts = Puppet.lookup(:pal_facts) 423 pal_variables = Puppet.lookup(:pal_variables) 424 425 overrides = {} 426 427 unless facts.nil? || facts.empty? 428 pal_facts = pal_facts.merge(facts) 429 overrides[:pal_facts] = pal_facts 430 end 431 432 prepare_node_facts(node, pal_facts) 433 434 configured_environment = node.environment || Puppet.lookup(:current_environment) 435 436 apply_environment = manifest ? 437 configured_environment.override_with(:manifest => manifest) : 438 configured_environment 439 440 # Modify the node descriptor to use the special apply_environment. 441 # It is based on the actual environment from the node, or the locally 442 # configured environment if the node does not specify one. 443 # If a manifest file is passed on the command line, it overrides 444 # the :manifest setting of the apply_environment. 445 node.environment = apply_environment 446 447 # TRANSLATORS, the string "For puppet PAL" is not user facing 448 Puppet.override({:current_environment => apply_environment}, "For puppet PAL") do 449 begin 450 node.sanitize() 451 compiler = create_internal_compiler(internal_compiler_class, node) 452 453 case internal_compiler_class 454 when :script 455 pal_compiler = ScriptCompiler.new(compiler) 456 overrides[:pal_script_compiler] = overrides[:pal_compiler] = pal_compiler 457 when :catalog 458 pal_compiler = CatalogCompiler.new(compiler) 459 overrides[:pal_catalog_compiler] = overrides[:pal_compiler] = pal_compiler 460 end 461 462 # When scripting the trusted data are always local; default is to set them anyway 463 # When compiling for a catalog, the catalog compiler does this 464 if set_local_facts 465 compiler.topscope.set_trusted(node.trusted_data) 466 467 # Server facts are always about the local node's version etc. 468 compiler.topscope.set_server_facts(node.server_facts) 469 470 # Set $facts for the node running the script 471 facts_hash = node.facts.nil? ? {} : node.facts.values 472 compiler.topscope.set_facts(facts_hash) 473 474 # create the $settings:: variables 475 compiler.topscope.merge_settings(node.environment.name, false) 476 end 477 478 # Make compiler available to Puppet#lookup and injection in functions 479 # TODO: The compiler instances should be available under non PAL use as well! 480 # TRANSLATORS: Do not translate, symbolic name 481 Puppet.override(overrides, "PAL::with_#{internal_compiler_class}_compiler") do 482 compiler.compile do | compiler_yield | 483 # In case the variables passed to the compiler are PCore types defined in modules, they 484 # need to be deserialized and added from within the this scope, so that loaders are 485 # available during deserizlization. 486 pal_variables = Puppet::Pops::Serialization::FromDataConverter.convert(pal_variables) 487 variables = Puppet::Pops::Serialization::FromDataConverter.convert(variables) 488 489 # Merge together target variables and plan variables. This will also shadow any 490 # collisions with facts and emit a warning. 491 topscope_vars = pal_variables.merge(merge_vars(target_variables, variables, node.facts.values)) 492 493 add_variables(compiler.topscope, topscope_vars) 494 # wrap the internal compiler to prevent it from leaking in the PAL API 495 if block_given? 496 yield(pal_compiler) 497 end 498 end 499 end 500 501 rescue Puppet::Error 502 # already logged and handled by the compiler, including Puppet::ParseErrorWithIssue 503 raise 504 505 rescue => detail 506 Puppet.log_exception(detail) 507 raise 508 end 509 end 510 end
Warn and remove variables that will be shadowed by facts of the same name, which are set in scope earlier.
# File lib/puppet/pal/pal_impl.rb 515 def self.merge_vars(target_vars, vars, facts) 516 # First, shadow plan and target variables by facts of the same name 517 vars = shadow_vars(facts || {}, vars, 'fact', 'plan variable') 518 target_vars = shadow_vars(facts || {}, target_vars, 'fact', 'target variable') 519 # Then, shadow target variables by plan variables of the same name 520 target_vars = shadow_vars(vars, target_vars, 'plan variable', 'target variable') 521 522 target_vars.merge(vars) 523 end
Prepares the node for use by giving it node_facts (if given) If a hash of facts values is given, then the operation of creating a node with facts is much speeded up (as getting a fresh set of facts is avoided in a later step).
# File lib/puppet/pal/pal_impl.rb 365 def self.prepare_node_facts(node, facts) 366 # Prepare the node with facts if it does not already have them 367 if node.facts.nil? 368 node_facts = facts.nil? ? nil : Puppet::Node::Facts.new(Puppet[:node_name_value], facts) 369 node.fact_merge(node_facts) 370 # Add server facts so $server_facts[environment] exists when doing a puppet script 371 # SCRIPT TODO: May be needed when running scripts under orchestrator. Leave it for now. 372 # 373 node.add_server_facts({}) 374 end 375 end
# File lib/puppet/pal/pal_impl.rb 526 def self.shadow_vars(vars, other_vars, vars_type, other_vars_type) 527 collisions, valid = other_vars.partition do |k, _| 528 vars.include?(k) 529 end 530 531 if collisions.any? 532 names = collisions.map { |k, _| "$#{k}" }.join(', ') 533 plural = collisions.length == 1 ? '' : 's' 534 535 Puppet.warning( 536 "#{other_vars_type.capitalize}#{plural} #{names} will be overridden by "\ 537 "#{vars_type}#{plural} of the same name in the apply block" 538 ) 539 end 540 541 valid.to_h 542 end