puppet 7.16.0 → 7.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +66 -5
  3. data/ext/systemd/puppet.service +1 -1
  4. data/lib/puppet/agent.rb +20 -2
  5. data/lib/puppet/application/agent.rb +3 -13
  6. data/lib/puppet/application/apply.rb +2 -2
  7. data/lib/puppet/configurer.rb +1 -1
  8. data/lib/puppet/defaults.rb +11 -1
  9. data/lib/puppet/http/client.rb +22 -2
  10. data/lib/puppet/parameter.rb +19 -4
  11. data/lib/puppet/pops/evaluator/deferred_resolver.rb +46 -6
  12. data/lib/puppet/pops/functions/dispatcher.rb +10 -6
  13. data/lib/puppet/pops/loader/ruby_legacy_function_instantiator.rb +7 -6
  14. data/lib/puppet/pops/types/type_mismatch_describer.rb +22 -1
  15. data/lib/puppet/provider/package/puppetserver_gem.rb +7 -16
  16. data/lib/puppet/provider/package/yum.rb +8 -3
  17. data/lib/puppet/provider/user/directoryservice.rb +15 -8
  18. data/lib/puppet/ssl/ssl_provider.rb +65 -12
  19. data/lib/puppet/ssl/state_machine.rb +13 -17
  20. data/lib/puppet/transaction.rb +22 -0
  21. data/lib/puppet/type/user.rb +3 -0
  22. data/lib/puppet/type.rb +20 -3
  23. data/lib/puppet/version.rb +1 -1
  24. data/lib/puppet.rb +1 -14
  25. data/man/man5/puppet.conf.5 +11 -3
  26. data/man/man8/puppet-agent.8 +2 -2
  27. data/man/man8/puppet-apply.8 +1 -1
  28. data/man/man8/puppet-catalog.8 +1 -1
  29. data/man/man8/puppet-config.8 +1 -1
  30. data/man/man8/puppet-describe.8 +1 -1
  31. data/man/man8/puppet-device.8 +1 -1
  32. data/man/man8/puppet-doc.8 +1 -1
  33. data/man/man8/puppet-epp.8 +1 -1
  34. data/man/man8/puppet-facts.8 +1 -1
  35. data/man/man8/puppet-filebucket.8 +1 -1
  36. data/man/man8/puppet-generate.8 +1 -1
  37. data/man/man8/puppet-help.8 +1 -1
  38. data/man/man8/puppet-lookup.8 +1 -1
  39. data/man/man8/puppet-module.8 +1 -1
  40. data/man/man8/puppet-node.8 +1 -1
  41. data/man/man8/puppet-parser.8 +1 -1
  42. data/man/man8/puppet-plugin.8 +1 -1
  43. data/man/man8/puppet-report.8 +1 -1
  44. data/man/man8/puppet-resource.8 +1 -1
  45. data/man/man8/puppet-script.8 +1 -1
  46. data/man/man8/puppet-ssl.8 +1 -1
  47. data/man/man8/puppet.8 +2 -2
  48. data/spec/integration/application/agent_spec.rb +157 -0
  49. data/spec/integration/application/apply_spec.rb +74 -0
  50. data/spec/integration/http/client_spec.rb +27 -10
  51. data/spec/lib/puppet_spec/https.rb +1 -1
  52. data/spec/lib/puppet_spec/puppetserver.rb +39 -2
  53. data/spec/unit/agent_spec.rb +6 -2
  54. data/spec/unit/application/agent_spec.rb +26 -16
  55. data/spec/unit/daemon_spec.rb +2 -11
  56. data/spec/unit/http/client_spec.rb +18 -0
  57. data/spec/unit/pops/evaluator/deferred_resolver_spec.rb +26 -0
  58. data/spec/unit/pops/loaders/loaders_spec.rb +1 -1
  59. data/spec/unit/pops/types/type_mismatch_describer_spec.rb +167 -1
  60. data/spec/unit/provider/package/puppetserver_gem_spec.rb +2 -2
  61. data/spec/unit/provider/user/directoryservice_spec.rb +1 -1
  62. data/spec/unit/ssl/ssl_provider_spec.rb +75 -1
  63. data/spec/unit/ssl/state_machine_spec.rb +1 -0
  64. data/tasks/generate_cert_fixtures.rake +5 -4
  65. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6c615fd23b0479166d4063106734f192102c181ea31befcb2c9dd4355b00f9b
4
- data.tar.gz: 25dd46d4c27898d532d918d058d744a992959cda11d79ae5a12d902f26c4360f
3
+ metadata.gz: f250db7b7e5579935f6491aebaff508c8e95caf29eabbabaa7eb325df7f1d33e
4
+ data.tar.gz: 7705591fc0fd2ed3559c29d7d90f803a6730ac835207e919e92195145aae0a8c
5
5
  SHA512:
6
- metadata.gz: 86e905440fa92e3bc3092cf27cb4d1d928afcd41cf77ed34e6dd171b08d1e25beb7680fbbd847c7f3acdc672e8d385be61b5d916f045c76105c34afc06801f75
7
- data.tar.gz: 0e5329faafcac9f60da50eb37f6a3cf6b3637bb09adb8f48d6a190a20ecec1f3047102dfb780124a27ee1906f5e9ce84c5aa49458109cba017808974b131c5b8
6
+ metadata.gz: 1ebee9543a560c31b3f582d494f9908ebcbc2ff3c5c71eb5a86558d1c6cf13021288ee109358354a612d580a72bc5d32ceeb6d07ad172e4b30b025096c9d01e7
7
+ data.tar.gz: cef70451f5c9c09e871807b61c04d4d38270878b13bbdebb107391f6c98cc87f77d789b81fb2399358c64e86f5d1764ee8b41fab004ee5465e3f6bdc17dc46b1
data/Gemfile.lock CHANGED
@@ -1,19 +1,21 @@
1
1
  GIT
2
2
  remote: https://github.com/puppetlabs/packaging
3
- revision: 0b07772b72c5e4076e145bab3d56d42331ec342b
3
+ revision: b791353d4f81dbb1df5ce3d79e95bd008b47beb6
4
4
  branch: 1.0.x
5
5
  specs:
6
- packaging (0.106.1)
6
+ packaging (0.106.3.6.gb791353)
7
7
  apt_stage_artifacts
8
8
  artifactory (~> 3)
9
9
  csv (= 3.1.5)
10
+ google-cloud-storage
11
+ googleauth
10
12
  rake (>= 12.3)
11
13
  release-metrics
12
14
 
13
15
  PATH
14
16
  remote: .
15
17
  specs:
16
- puppet (7.16.0)
18
+ puppet (7.17.0)
17
19
  CFPropertyList (~> 2.2)
18
20
  concurrent-ruby (~> 1.0)
19
21
  deep_merge (~> 1.0)
@@ -40,12 +42,19 @@ GEM
40
42
  crack (0.4.5)
41
43
  rexml
42
44
  csv (3.1.5)
45
+ declarative (0.0.20)
43
46
  deep_merge (1.2.2)
44
47
  diff-lcs (1.5.0)
48
+ digest-crc (0.6.4)
49
+ rake (>= 12.0.0, < 14.0.0)
45
50
  docopt (0.6.1)
46
51
  facter (4.2.9)
47
52
  hocon (~> 1.3)
48
53
  thor (>= 1.0.1, < 2.0)
54
+ faraday (2.3.0)
55
+ faraday-net_http (~> 2.0)
56
+ ruby2_keywords (>= 0.0.4)
57
+ faraday-net_http (2.0.3)
49
58
  fast_gettext (1.1.2)
50
59
  ffi (1.15.5)
51
60
  gettext (3.2.9)
@@ -55,24 +64,63 @@ GEM
55
64
  fast_gettext (~> 1.1.0)
56
65
  gettext (>= 3.0.2, < 3.3.0)
57
66
  locale
67
+ google-apis-core (0.5.0)
68
+ addressable (~> 2.5, >= 2.5.1)
69
+ googleauth (>= 0.16.2, < 2.a)
70
+ httpclient (>= 2.8.1, < 3.a)
71
+ mini_mime (~> 1.0)
72
+ representable (~> 3.0)
73
+ retriable (>= 2.0, < 4.a)
74
+ rexml
75
+ webrick
76
+ google-apis-iamcredentials_v1 (0.10.0)
77
+ google-apis-core (>= 0.4, < 2.a)
78
+ google-apis-storage_v1 (0.14.0)
79
+ google-apis-core (>= 0.4, < 2.a)
80
+ google-cloud-core (1.6.0)
81
+ google-cloud-env (~> 1.0)
82
+ google-cloud-errors (~> 1.0)
83
+ google-cloud-env (1.6.0)
84
+ faraday (>= 0.17.3, < 3.0)
85
+ google-cloud-errors (1.2.0)
86
+ google-cloud-storage (1.36.2)
87
+ addressable (~> 2.8)
88
+ digest-crc (~> 0.4)
89
+ google-apis-iamcredentials_v1 (~> 0.1)
90
+ google-apis-storage_v1 (~> 0.1)
91
+ google-cloud-core (~> 1.6)
92
+ googleauth (>= 0.16.2, < 2.a)
93
+ mini_mime (~> 1.0)
94
+ googleauth (1.1.3)
95
+ faraday (>= 0.17.3, < 3.a)
96
+ jwt (>= 1.4, < 3.0)
97
+ memoist (~> 0.16)
98
+ multi_json (~> 1.11)
99
+ os (>= 0.9, < 2.0)
100
+ signet (>= 0.16, < 2.a)
58
101
  hashdiff (1.0.1)
59
- hiera (3.8.0)
60
- hiera-eyaml (3.2.2)
102
+ hiera (3.9.0)
103
+ hiera-eyaml (3.3.0)
61
104
  highline
62
105
  optimist
63
106
  highline (2.0.3)
64
107
  hocon (1.3.1)
65
108
  hpricot (0.8.6)
109
+ httpclient (2.8.3)
66
110
  json-schema (2.8.1)
67
111
  addressable (>= 2.4)
112
+ jwt (2.3.0)
68
113
  locale (2.1.3)
114
+ memoist (0.16.2)
69
115
  memory_profiler (1.0.0)
70
116
  method_source (1.0.0)
117
+ mini_mime (1.1.2)
71
118
  minitar (0.9)
72
119
  msgpack (1.5.1)
73
120
  multi_json (1.15.0)
74
121
  mustache (1.1.1)
75
122
  optimist (3.0.1)
123
+ os (1.1.4)
76
124
  parallel (1.22.1)
77
125
  parser (2.7.2.0)
78
126
  ast (~> 2.4.1)
@@ -94,6 +142,11 @@ GEM
94
142
  release-metrics (1.1.0)
95
143
  csv
96
144
  docopt
145
+ representable (3.2.0)
146
+ declarative (< 0.1.0)
147
+ trailblazer-option (>= 0.1.1, < 0.2.0)
148
+ uber (< 0.2.0)
149
+ retriable (3.1.2)
97
150
  rexml (3.2.5)
98
151
  ronn (0.7.3)
99
152
  hpricot (>= 0.8.2)
@@ -126,10 +179,18 @@ GEM
126
179
  rubocop (~> 0.49.0)
127
180
  ruby-prof (1.4.3)
128
181
  ruby-progressbar (1.11.0)
182
+ ruby2_keywords (0.0.5)
129
183
  scanf (1.0.0)
130
184
  semantic_puppet (1.0.4)
185
+ signet (0.16.1)
186
+ addressable (~> 2.8)
187
+ faraday (>= 0.17.5, < 3.0)
188
+ jwt (>= 1.5, < 3.0)
189
+ multi_json (~> 1.10)
131
190
  text (1.3.1)
132
191
  thor (1.2.1)
192
+ trailblazer-option (0.1.2)
193
+ uber (0.1.0)
133
194
  unicode-display_width (1.8.0)
134
195
  vcr (5.1.0)
135
196
  webmock (3.14.0)
@@ -11,7 +11,7 @@
11
11
  [Unit]
12
12
  Description=Puppet agent
13
13
  Wants=basic.target
14
- After=basic.target network.target
14
+ After=basic.target network.target network-online.target
15
15
 
16
16
  [Service]
17
17
  EnvironmentFile=-/etc/sysconfig/puppetagent
data/lib/puppet/agent.rb CHANGED
@@ -45,11 +45,19 @@ class Puppet::Agent
45
45
  result = nil
46
46
  wait_for_lock_deadline = nil
47
47
  block_run = Puppet::Application.controlled_run do
48
- splay client_options.fetch :splay, Puppet[:splay]
48
+ # splay may sleep for awhile!
49
+ splay(client_options.fetch(:splay, Puppet[:splay]))
50
+
51
+ # waiting for certs may sleep for awhile depending on onetime, waitforcert and maxwaitforcert!
52
+ # this needs to happen before forking so that if we fail to obtain certs and try to exit, then
53
+ # we exit the main process and not the forked child.
54
+ ssl_context = wait_for_certificates(client_options)
55
+
49
56
  result = run_in_fork(should_fork) do
50
57
  with_client(client_options[:transaction_uuid], client_options[:job_id]) do |client|
51
58
  client_args = client_options.merge(:pluginsync => Puppet::Configurer.should_pluginsync?)
52
59
  begin
60
+ # lock may sleep for awhile depending on waitforlock and maxwaitforlock!
53
61
  lock do
54
62
  # NOTE: Timeout is pretty heinous as the location in which it
55
63
  # throws an error is entirely unpredictable, which means that
@@ -57,7 +65,9 @@ class Puppet::Agent
57
65
  # sanity. The only thing a Puppet agent should do after this
58
66
  # error is thrown is die with as much dignity as possible.
59
67
  Timeout.timeout(Puppet[:runtimeout], RunTimeoutError) do
60
- client.run(client_args)
68
+ Puppet.override(ssl_context: ssl_context) do
69
+ client.run(client_args)
70
+ end
61
71
  end
62
72
  end
63
73
  rescue Puppet::LockError
@@ -84,6 +94,8 @@ class Puppet::Agent
84
94
  rescue StandardError => detail
85
95
  Puppet.log_exception(detail, _("Could not run %{client_class}: %{detail}") % { client_class: client_class, detail: detail })
86
96
  nil
97
+ ensure
98
+ Puppet.runtime[:http].close
87
99
  end
88
100
  end
89
101
  end
@@ -137,4 +149,10 @@ class Puppet::Agent
137
149
  ensure
138
150
  @client = nil
139
151
  end
152
+
153
+ def wait_for_certificates(options)
154
+ waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert])
155
+ sm = Puppet::SSL::StateMachine.new(waitforcert: waitforcert, onetime: Puppet[:onetime])
156
+ sm.ensure_client_certificate
157
+ end
140
158
  end
@@ -158,7 +158,7 @@ applying the whole thing.
158
158
  '--fingerprint' is a one-time flag. In this mode 'puppet agent' runs
159
159
  once and displays on the console (and in the log) the current certificate
160
160
  (or certificate request) fingerprint. Providing the '--digest' option
161
- allows to use a different digest algorithm to generate the fingerprint.
161
+ allows you to use a different digest algorithm to generate the fingerprint.
162
162
  The main use is to verify that before signing a certificate request on
163
163
  the master, the certificate request the master received is the same as
164
164
  the one the client sent (to prevent against man-in-the-middle attacks
@@ -383,15 +383,11 @@ Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License
383
383
 
384
384
  log_config if Puppet[:daemonize]
385
385
 
386
- # run ssl state machine, waiting if needed
387
- ssl_context = wait_for_certificates
388
-
389
386
  # Each application is responsible for pushing loaders onto the context.
390
387
  # Use the current environment that has already been established, though
391
388
  # it may change later during the configurer run.
392
389
  env = Puppet.lookup(:current_environment)
393
- Puppet.override(ssl_context: ssl_context,
394
- current_environment: env,
390
+ Puppet.override(current_environment: env,
395
391
  loaders: Puppet::Pops::Loaders.new(env, true)) do
396
392
  if Puppet[:onetime]
397
393
  onetime(daemon)
@@ -434,7 +430,7 @@ Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License
434
430
 
435
431
  def onetime(daemon)
436
432
  begin
437
- exitstatus = daemon.agent.run({:job_id => options[:job_id], :start_time => options[:start_time]})
433
+ exitstatus = daemon.agent.run({:job_id => options[:job_id], :start_time => options[:start_time], :waitforcert => options[:waitforcert]})
438
434
  rescue => detail
439
435
  Puppet.log_exception(detail)
440
436
  end
@@ -524,10 +520,4 @@ Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License
524
520
 
525
521
  daemon
526
522
  end
527
-
528
- def wait_for_certificates
529
- waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert])
530
- sm = Puppet::SSL::StateMachine.new(waitforcert: waitforcert)
531
- sm.ensure_client_certificate
532
- end
533
523
  end
@@ -241,7 +241,7 @@ Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License
241
241
  end
242
242
 
243
243
  # Resolve all deferred values and replace them / mutate the catalog
244
- Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(node.facts, catalog, apply_environment)
244
+ Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(node.facts, catalog, apply_environment, Puppet[:preprocess_deferred])
245
245
 
246
246
  # Translate it to a RAL catalog
247
247
  catalog = catalog.to_ral
@@ -350,7 +350,7 @@ Copyright (c) 2011 Puppet Inc., LLC Licensed under the Apache 2.0 License
350
350
  raise Puppet::Error, _("Could not deserialize catalog from %{format}: %{detail}") % { format: format, detail: detail }, detail.backtrace
351
351
  end
352
352
  # Resolve all deferred values and replace them / mutate the catalog
353
- Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(node.facts, catalog, configured_environment)
353
+ Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(node.facts, catalog, configured_environment, Puppet[:preprocess_deferred])
354
354
 
355
355
  catalog.to_ral
356
356
  end
@@ -112,7 +112,7 @@ class Puppet::Configurer
112
112
  catalog_conversion_time = thinmark do
113
113
  # Will mutate the result and replace all Deferred values with resolved values
114
114
  if facts
115
- Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(facts, result, Puppet.lookup(:current_environment))
115
+ Puppet::Pops::Evaluator::DeferredResolver.resolve_and_replace(facts, result, Puppet.lookup(:current_environment), Puppet[:preprocess_deferred])
116
116
  end
117
117
 
118
118
  catalog = result.to_ral
@@ -1534,7 +1534,7 @@ EOT
1534
1534
  :type => :file,
1535
1535
  :mode => "0640",
1536
1536
  :desc => "Transactional storage file for persisting data between
1537
- transactions for the purposes of infering information (such as
1537
+ transactions for the purposes of inferring information (such as
1538
1538
  corrective_change) on new data received."
1539
1539
  },
1540
1540
  :clientyamldir => {
@@ -2021,6 +2021,16 @@ EOT
2021
2021
  being evaluated. This allows you to interactively see exactly
2022
2022
  what is being done.",
2023
2023
  },
2024
+ :preprocess_deferred => {
2025
+ :default => true,
2026
+ :type => :boolean,
2027
+ :desc => "Whether puppet should call deferred functions before applying
2028
+ the catalog. If set to `true`, then all prerequisites needed for the
2029
+ deferred function must be satified prior to puppet running. If set to
2030
+ `false`, then deferred functions will follow puppet relationships and
2031
+ ordering. This allows puppet to install prerequisites needed for a
2032
+ deferred function and call the deferred function in the same run."
2033
+ },
2024
2034
  :summarize => {
2025
2035
  :default => false,
2026
2036
  :type => :boolean,
@@ -98,7 +98,7 @@ class Puppet::HTTP::Client
98
98
  # used if :include_system_store is set to true
99
99
  # @param [Integer] redirect_limit default number of HTTP redirections to allow
100
100
  # in a given request. Can also be specified per-request.
101
- # @param [Integer] retry_limit number of HTTP reties allowed in a given
101
+ # @param [Integer] retry_limit number of HTTP retries allowed in a given
102
102
  # request
103
103
  #
104
104
  def initialize(pool: Puppet::HTTP::Pool.new(Puppet[:http_keepalive_timeout]), ssl_context: nil, system_ssl_context: nil, redirect_limit: 10, retry_limit: 100)
@@ -300,6 +300,24 @@ class Puppet::HTTP::Client
300
300
  # @api public
301
301
  def close
302
302
  @pool.close
303
+ @default_ssl_context = nil
304
+ @default_system_ssl_context = nil
305
+ end
306
+
307
+ def default_ssl_context
308
+ cert = Puppet::X509::CertProvider.new
309
+ password = cert.load_private_key_password
310
+
311
+ ssl = Puppet::SSL::SSLProvider.new
312
+ ctx = ssl.load_context(certname: Puppet[:certname], password: password)
313
+ ssl.print(ctx)
314
+ ctx
315
+ rescue => e
316
+ # TRANSLATORS: `message` is an already translated string of why SSL failed to initialize
317
+ Puppet.log_exception(e, _("Failed to initialize SSL: %{message}") % { message: e.message })
318
+ # TRANSLATORS: `puppet agent -t` is a command and should not be translated
319
+ Puppet.err(_("Run `puppet agent -t`"))
320
+ raise e
303
321
  end
304
322
 
305
323
  protected
@@ -458,7 +476,9 @@ class Puppet::HTTP::Client
458
476
  cacerts = cert_provider.load_cacerts || []
459
477
 
460
478
  ssl = Puppet::SSL::SSLProvider.new
461
- @default_system_ssl_context = ssl.create_system_context(cacerts: cacerts)
479
+ @default_system_ssl_context = ssl.create_system_context(cacerts: cacerts, include_client_cert: true)
480
+ ssl.print(@default_system_ssl_context)
481
+ @default_system_ssl_context
462
482
  end
463
483
 
464
484
  def apply_auth(request, basic_auth)
@@ -177,15 +177,15 @@ class Puppet::Parameter
177
177
  end
178
178
 
179
179
  # @overload unmunge {|| ... }
180
- # Defines an optional method used to convert the parameter value to DSL/string form from an internal form.
180
+ # Defines an optional method used to convert the parameter value from internal form to DSL/string form.
181
181
  # If an `unmunge` method is not defined, the internal form is used.
182
182
  # @see munge
183
- # @note This adds a method with the name `unmunge` in the created parameter class.
183
+ # @note This adds a method with the name `unsafe_unmunge` in the created parameter class.
184
184
  # @dsl type
185
185
  # @api public
186
186
  #
187
187
  def unmunge(&block)
188
- define_method(:unmunge, &block)
188
+ define_method(:unsafe_unmunge, &block)
189
189
  end
190
190
 
191
191
  # Sets a marker indicating that this parameter is the _namevar_ (unique identifier) of the type
@@ -415,10 +415,21 @@ class Puppet::Parameter
415
415
  # @return [Object] the unmunged value
416
416
  #
417
417
  def unmunge(value)
418
+ return value if value.is_a?(Puppet::Pops::Evaluator::DeferredValue)
419
+
420
+ unsafe_unmunge(value)
421
+ end
422
+
423
+ # This is the default implementation of `unmunge` that simply produces the value (if it is valid).
424
+ # The DSL method {unmunge} should be used to define an overriding method if unmunging is required.
425
+ #
426
+ # @api private
427
+ #
428
+ def unsafe_unmunge(value)
418
429
  value
419
430
  end
420
431
 
421
- # Munges the value to internal form.
432
+ # Munges the value from DSL form to internal form.
422
433
  # This implementation of `munge` provides exception handling around the specified munging of this parameter.
423
434
  # @note This method should not be overridden. Use the DSL method {munge} to define a munging method
424
435
  # if required.
@@ -426,6 +437,8 @@ class Puppet::Parameter
426
437
  # @return [Object] the munged (internal) value
427
438
  #
428
439
  def munge(value)
440
+ return value if value.is_a?(Puppet::Pops::Evaluator::DeferredValue)
441
+
429
442
  begin
430
443
  ret = unsafe_munge(value)
431
444
  rescue Puppet::Error => detail
@@ -459,6 +472,8 @@ class Puppet::Parameter
459
472
  # @api public
460
473
  #
461
474
  def validate(value)
475
+ return if value.is_a?(Puppet::Pops::Evaluator::DeferredValue)
476
+
462
477
  begin
463
478
  unsafe_validate(value)
464
479
  rescue ArgumentError => detail
@@ -3,6 +3,16 @@ require_relative '../../../puppet/parser/script_compiler'
3
3
  module Puppet::Pops
4
4
  module Evaluator
5
5
 
6
+ class DeferredValue
7
+ def initialize(proc)
8
+ @proc = proc
9
+ end
10
+
11
+ def resolve
12
+ @proc.call
13
+ end
14
+ end
15
+
6
16
  # Utility class to help resolve instances of Puppet::Pops::Types::PDeferredType::Deferred
7
17
  #
8
18
  class DeferredResolver
@@ -20,9 +30,9 @@ class DeferredResolver
20
30
  # are to be mixed into the scope
21
31
  # @return [nil] does not return anything - the catalog is modified as a side effect
22
32
  #
23
- def self.resolve_and_replace(facts, catalog, environment = catalog.environment_instance)
24
- compiler = Puppet::Parser::ScriptCompiler.new(environment, catalog.name, true)
25
- resolver = new(compiler)
33
+ def self.resolve_and_replace(facts, catalog, environment = catalog.environment_instance, preprocess_deferred = true)
34
+ compiler = Puppet::Parser::ScriptCompiler.new(environment, catalog.name, preprocess_deferred)
35
+ resolver = new(compiler, preprocess_deferred)
26
36
  resolver.set_facts_variable(facts)
27
37
  # TODO:
28
38
  # # When scripting the trusted data are always local, but set them anyway
@@ -53,11 +63,12 @@ class DeferredResolver
53
63
  resolver.resolve(value)
54
64
  end
55
65
 
56
- def initialize(compiler)
66
+ def initialize(compiler, preprocess_deferred = true)
57
67
  @compiler = compiler
58
68
  # Always resolve in top scope
59
69
  @scope = @compiler.topscope
60
70
  @deferred_class = Puppet::Pops::Types::TypeFactory.deferred.implementation_class
71
+ @preprocess_deferred = preprocess_deferred
61
72
  end
62
73
 
63
74
  # @param facts [Puppet::Node::Facts] the facts to set in $facts in the compiler's topscope
@@ -106,6 +117,24 @@ class DeferredResolver
106
117
  end
107
118
  end
108
119
 
120
+ def resolve_lazy_args(x)
121
+ if x.is_a?(DeferredValue)
122
+ x.resolve
123
+ elsif x.is_a?(Array)
124
+ x.map {|v| resolve_lazy_args(v) }
125
+ elsif x.is_a?(Hash)
126
+ result = {}
127
+ x.each_pair {|k,v| result[k] = resolve_lazy_args(v) }
128
+ result
129
+ elsif x.is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
130
+ # rewrap in a new Sensitive after resolving any nested deferred values
131
+ Puppet::Pops::Types::PSensitiveType::Sensitive.new(resolve_lazy_args(x.unwrap))
132
+ else
133
+ x
134
+ end
135
+ end
136
+ private :resolve_lazy_args
137
+
109
138
  def resolve_future(f)
110
139
  # If any of the arguments to a future is a future it needs to be resolved first
111
140
  func_name = f.name
@@ -117,8 +146,19 @@ class DeferredResolver
117
146
  mapped_arguments.insert(0, @scope[var_name])
118
147
  end
119
148
 
120
- # call the function (name in deferred, or 'dig' for a variable)
121
- @scope.call_function(func_name, mapped_arguments)
149
+ if @preprocess_deferred
150
+ # call the function (name in deferred, or 'dig' for a variable)
151
+ @scope.call_function(func_name, mapped_arguments)
152
+ else
153
+ # call the function later
154
+ DeferredValue.new(
155
+ Proc.new {
156
+ # deferred functions can have nested deferred arguments
157
+ resolved_arguments = mapped_arguments.map { |arg| resolve_lazy_args(arg) }
158
+ @scope.call_function(func_name, resolved_arguments)
159
+ }
160
+ )
161
+ end
122
162
  end
123
163
 
124
164
  def map_arguments(args)
@@ -19,6 +19,10 @@ class Puppet::Pops::Functions::Dispatcher
19
19
  @dispatchers.empty?
20
20
  end
21
21
 
22
+ def find_matching_dispatcher(args, &block)
23
+ @dispatchers.find { |d| d.type.callable_with?(args, block) }
24
+ end
25
+
22
26
  # Dispatches the call to the first found signature (entry with matching type).
23
27
  #
24
28
  # @param instance [Puppet::Functions::Function] - the function to call
@@ -28,19 +32,19 @@ class Puppet::Pops::Functions::Dispatcher
28
32
  #
29
33
  # @api private
30
34
  def dispatch(instance, calling_scope, args, &block)
31
- found = @dispatchers.find { |d| d.type.callable_with?(args, block) }
32
- unless found
35
+
36
+ dispatcher = find_matching_dispatcher(args, &block)
37
+ unless dispatcher
33
38
  args_type = Puppet::Pops::Types::TypeCalculator.singleton.infer_set(block_given? ? args + [block] : args)
34
39
  raise ArgumentError, Puppet::Pops::Types::TypeMismatchDescriber.describe_signatures(instance.class.name, signatures, args_type)
35
40
  end
36
-
37
- if found.argument_mismatch_handler?
38
- msg = found.invoke(instance, calling_scope, args)
41
+ if dispatcher.argument_mismatch_handler?
42
+ msg = dispatcher.invoke(instance, calling_scope, args)
39
43
  raise ArgumentError, "'#{instance.class.name}' #{msg}"
40
44
  end
41
45
 
42
46
  catch(:next) do
43
- found.invoke(instance, calling_scope, args, &block)
47
+ dispatcher.invoke(instance, calling_scope, args, &block)
44
48
  end
45
49
  end
46
50
 
@@ -18,7 +18,7 @@ class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
18
18
  def self.create(loader, typed_name, source_ref, ruby_code_string)
19
19
  # Assert content of 3x function by parsing
20
20
  assertion_result = []
21
- if assert_code(ruby_code_string, assertion_result)
21
+ if assert_code(ruby_code_string, source_ref, assertion_result)
22
22
  unless ruby_code_string.is_a?(String) && assertion_result.include?(:found_newfunction)
23
23
  raise ArgumentError, _("The code loaded from %{source_ref} does not seem to be a Puppet 3x API function - no 'newfunction' call.") % { source_ref: source_ref }
24
24
  end
@@ -69,15 +69,15 @@ class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
69
69
  end
70
70
  private_class_method :get_binding
71
71
 
72
- def self.assert_code(code_string, result)
72
+ def self.assert_code(code_string, source_ref, result)
73
73
  ripped = Ripper.sexp(code_string)
74
74
  return false if ripped.nil? # Let the next real parse crash and tell where and what is wrong
75
- ripped.each {|x| walk(x, result) }
75
+ ripped.each {|x| walk(x, source_ref, result) }
76
76
  true
77
77
  end
78
78
  private_class_method :assert_code
79
79
 
80
- def self.walk(x, result)
80
+ def self.walk(x, source_ref, result)
81
81
  return unless x.is_a?(Array)
82
82
  first = x[0]
83
83
  case first
@@ -89,13 +89,14 @@ class Puppet::Pops::Loader::RubyLegacyFunctionInstantiator
89
89
  when :def, :defs
90
90
  # There should not be any calls to def in a 3x function
91
91
  mname, mline = extract_name_line(find_identity(x))
92
- raise SecurityError, _("Illegal method definition of method '%{method_name}' on line %{line} in legacy function. See %{url} for more information") % {
92
+ raise SecurityError, _("Illegal method definition of method '%{method_name}' in source %{source_ref} on line %{line} in legacy function. See %{url} for more information") % {
93
93
  method_name: mname,
94
+ source_ref: source_ref,
94
95
  line: mline,
95
96
  url: "https://puppet.com/docs/puppet/latest/functions_refactor_legacy.html"
96
97
  }
97
98
  end
98
- x.each {|v| walk(v, result) }
99
+ x.each {|v| walk(v, source_ref, result) }
99
100
  end
100
101
  private_class_method :walk
101
102
 
@@ -581,6 +581,15 @@ module Types
581
581
  end
582
582
  end
583
583
 
584
+ def get_deferred_function_return_type(value)
585
+ func = Puppet.lookup(:loaders).private_environment_loader.
586
+ load(:function, value.name)
587
+ dispatcher = func.class.dispatcher.find_matching_dispatcher(value.arguments)
588
+ raise ArgumentError, "No matching arity found for #{func.class.name} with arguments #{value.arguments}" unless dispatcher
589
+ dispatcher.type.return_type
590
+ end
591
+ private :get_deferred_function_return_type
592
+
584
593
  # Validates that all entries in the _param_hash_ exists in the given param_struct, that their type conforms
585
594
  # with the corresponding param_struct element and that all required values are provided.
586
595
  # An error message is created for each problem found.
@@ -598,7 +607,19 @@ module Types
598
607
  value = param_hash[name]
599
608
  value_type = elem.value_type
600
609
  if param_hash.include?(name)
601
- result << describe(value_type, TypeCalculator.singleton.infer_set(value), [ParameterPathElement.new(name)]) unless value_type.instance?(value)
610
+ if Puppet::Pops::Types::TypeFactory.deferred.implementation_class == value.class
611
+ if (df_return_type = get_deferred_function_return_type(value))
612
+ result << describe(value_type, df_return_type, [ParameterPathElement.new(name)]) unless value_type.generalize.assignable?(df_return_type.generalize)
613
+ else
614
+ warning_text = _("Deferred function %{function_name} has no return_type, unable to guarantee value type during compilation.") %
615
+ {function_name: value.name }
616
+ Puppet.warn_once('deprecations',
617
+ "#{value.name}_deferred_warning",
618
+ warning_text)
619
+ end
620
+ else
621
+ result << describe(value_type, TypeCalculator.singleton.infer_set(value), [ParameterPathElement.new(name)]) unless value_type.instance?(value)
622
+ end
602
623
  else
603
624
  result << MissingParameter.new(nil, name) unless missing_ok || elem.key_type.is_a?(POptionalType)
604
625
  end