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