puppet-resource_api 1.8.9 → 1.8.14

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.dependency_decisions.yml +9 -9
  3. data/.github/dependabot.yml +12 -0
  4. data/.rubocop.yml +1 -1
  5. data/.travis.yml +12 -48
  6. data/CHANGELOG.md +138 -24
  7. data/Gemfile +39 -17
  8. data/HISTORY.md +0 -5
  9. data/README.md +6 -6
  10. data/appveyor.yml +3 -3
  11. data/contrib/pre-commit +2 -0
  12. data/docs/README.md +7 -0
  13. data/docs/hands-on-lab/01-installing-prereqs.md +16 -0
  14. data/docs/hands-on-lab/02-connecting-to-the-lightbulbs-emulator.png +0 -0
  15. data/docs/hands-on-lab/02-connecting-to-the-lightbulbs.md +26 -0
  16. data/docs/hands-on-lab/03-creating-a-new-module.md +47 -0
  17. data/docs/hands-on-lab/03-creating-a-new-module_vscode.png +0 -0
  18. data/docs/hands-on-lab/04-adding-a-new-transport.md +123 -0
  19. data/docs/hands-on-lab/05-implementing-the-transport-hints.md +19 -0
  20. data/docs/hands-on-lab/05-implementing-the-transport.md +126 -0
  21. data/docs/hands-on-lab/06-implementing-the-provider.md +227 -0
  22. data/docs/hands-on-lab/07-implementing-a-task.md +181 -0
  23. data/lib/puppet/resource_api.rb +95 -55
  24. data/lib/puppet/resource_api/base_context.rb +6 -0
  25. data/lib/puppet/resource_api/data_type_handling.rb +2 -0
  26. data/lib/puppet/resource_api/glue.rb +9 -2
  27. data/lib/puppet/resource_api/io_context.rb +2 -0
  28. data/lib/puppet/resource_api/parameter.rb +4 -2
  29. data/lib/puppet/resource_api/property.rb +65 -5
  30. data/lib/puppet/resource_api/puppet_context.rb +2 -0
  31. data/lib/puppet/resource_api/read_only_parameter.rb +2 -0
  32. data/lib/puppet/resource_api/simple_provider.rb +2 -0
  33. data/lib/puppet/resource_api/transport.rb +2 -0
  34. data/lib/puppet/resource_api/transport/wrapper.rb +2 -0
  35. data/lib/puppet/resource_api/type_definition.rb +63 -3
  36. data/lib/puppet/resource_api/value_creator.rb +2 -0
  37. data/lib/puppet/resource_api/version.rb +3 -1
  38. data/lib/puppet/util/network_device/simple/device.rb +2 -0
  39. metadata +15 -4
@@ -0,0 +1,181 @@
1
+ # Implementing a Task
2
+
3
+ > TODO: this is NOT fine, yet
4
+
5
+ * add bolt gem
6
+ ```
7
+ Gemfile:
8
+ optional:
9
+ ':development':
10
+ - gem: 'puppet-resource_api'
11
+ - gem: 'faraday'
12
+ # add this
13
+ - gem: 'bolt'
14
+ ```
15
+
16
+
17
+ ```
18
+ david@davids:~/tmp/hue_workshop$ pdk update --force
19
+ pdk (INFO): Updating david-hue_workshop using the default template, from 1.10.0 to 1.10.0
20
+
21
+ ----------Files to be modified----------
22
+ Gemfile
23
+
24
+ ----------------------------------------
25
+
26
+ You can find a report of differences in update_report.txt.
27
+
28
+
29
+ ------------Update completed------------
30
+
31
+ 1 files modified.
32
+
33
+ david@davids:~/tmp/hue_workshop$ pdk bundle install
34
+ pdk (INFO): Using Ruby 2.5.3
35
+ pdk (INFO): Using Puppet 6.4.2
36
+ [...]
37
+ Bundle complete! 11 Gemfile dependencies, 122 gems now installed.
38
+ Use `bundle info [gemname]` to see where a bundled gem is installed.
39
+
40
+ david@davids:~/tmp/hue_workshop$
41
+ ```
42
+
43
+ * add ruby_task_helper module
44
+ <!--
45
+ ```# .fixtures.yml
46
+ ---
47
+ fixtures:
48
+ forge_modules:
49
+ ruby_task_helper: "puppetlabs/ruby_task_helper"
50
+ ```
51
+
52
+ ```
53
+ david@davids:~/tmp/hue_workshop$ pdk bundle exec rake spec_prep
54
+ pdk (INFO): Using Ruby 2.5.3
55
+ pdk (INFO): Using Puppet 6.4.2
56
+ Notice: Preparing to install into /home/david/tmp/hue_workshop/spec/fixtures/modules ...
57
+ Notice: Downloading from https://forgeapi.puppet.com ...
58
+ Notice: Installing -- do not interrupt ...
59
+ /home/david/tmp/hue_workshop/spec/fixtures/modules
60
+ └── puppetlabs-ruby_task_helper (v0.3.0)
61
+ I, [2019-06-04T13:12:04.615368 #32070] INFO -- : Creating symlink from spec/fixtures/modules/hue_workshop to /home/david/tmp/hue_workshop
62
+ david@davids:~/tmp/hue_workshop$
63
+ ``` -->
64
+
65
+ Using the development version:
66
+
67
+ ```
68
+ fixtures:
69
+ # forge_modules:
70
+ # ruby_task_helper: "puppetlabs/ruby_task_helper"
71
+ repositories:
72
+ ruby_task_helper:
73
+ repo: "git://github.com/da-ar/puppetlabs-ruby_task_helper"
74
+ ref: "38745f8e7c2521c50bbf1b8e03318006cdac7a02"
75
+ ```
76
+
77
+ ```
78
+ david@davids:~/tmp/hue_workshop$ pdk bundle exec rake spec_prep
79
+ pdk (INFO): Using Ruby 2.5.3
80
+ pdk (INFO): Using Puppet 6.4.2
81
+ HEAD is now at 38745f8 (FM-7955) Update to use Transport helper code
82
+ Cloning into 'spec/fixtures/modules/ruby_task_helper'...
83
+ I, [2019-06-04T13:43:58.577944 #9390] INFO -- : Creating symlink from spec/fixtures/modules/hue_workshop to /home/david/tmp/hue_workshop
84
+ david@davids:~/tmp/hue_workshop$
85
+ ```
86
+
87
+ * `pdk new task` based on https://github.com/puppetlabs/puppetlabs-panos/blob/main/tasks/apikey.rb
88
+
89
+ ```
90
+ david@davids:~/tmp/hue_workshop$ pdk new task alarm
91
+ pdk (INFO): Creating '/home/david/tmp/hue_workshop/tasks/alarm.sh' from template.
92
+ pdk (INFO): Creating '/home/david/tmp/hue_workshop/tasks/alarm.json' from template.
93
+ david@davids:~/tmp/hue_workshop$ mv /home/david/tmp/hue_workshop/tasks/alarm.sh /home/david/tmp/hue_workshop/tasks/alarm.rb
94
+ david@davids:~/tmp/hue_workshop$
95
+ ```
96
+
97
+ * `tasks/alarm.json`
98
+ ```json
99
+ {
100
+ "puppet_task_version": 1,
101
+ "supports_noop": false,
102
+ "remote": true,
103
+ "description": "A short description of this task",
104
+ "parameters": {
105
+ "name": {
106
+ "type": "String",
107
+ "description": "The lamp to alarm"
108
+ }
109
+ },
110
+ "files": [
111
+ "ruby_task_helper/files/task_helper.rb",
112
+ "hue_workshop/lib/puppet/transport/hue.rb",
113
+ "hue_workshop/lib/puppet/transport/schema/hue.rb"
114
+ ]
115
+ }
116
+ ```
117
+
118
+ * `tasks/alarm.rb`
119
+
120
+ ```ruby
121
+ #!/opt/puppetlabs/puppet/bin/ruby
122
+
123
+ require 'puppet'
124
+ require_relative "../../ruby_task_helper/files/task_helper.rb"
125
+
126
+ class AlarmTask < TaskHelper
127
+ def task(params = {}, remote = nil)
128
+ name = params[:name]
129
+ 5.times do |i|
130
+ remote.transport.hue_put("lights/#{name}/state",
131
+ name: name,
132
+ on: false,
133
+ )
134
+ sleep 1.0
135
+ remote.transport.hue_put("lights/#{name}/state",
136
+ name: name,
137
+ on: true,
138
+ hue: 10000*i,
139
+ sat: 255
140
+ )
141
+ sleep 1.0
142
+ end
143
+ {}
144
+ end
145
+ end
146
+
147
+ if __FILE__ == $0
148
+ AlarmTask.run
149
+ end
150
+ ```
151
+
152
+ * execute `pdk bundle exec bolt ...`
153
+
154
+ ```yaml
155
+ # inventory.yaml
156
+ ---
157
+ nodes:
158
+ - name: "192.168.43.195"
159
+ alias: hub1
160
+ config:
161
+ transport: remote
162
+ remote:
163
+ remote-transport: hue
164
+ key: "onmdTvd198bMrC6QYyVE9iasfYSeyAbAj3XyQzfL"
165
+ ```
166
+
167
+ ```
168
+ david@davids:~/tmp/hue_workshop$ pdk bundle exec bolt task run hue_workshop::alarm --modulepath spec/fixtures/modules/ --target hub1 --inventoryfile inventory.yaml
169
+ pdk (INFO): Using Ruby 2.5.3
170
+ pdk (INFO): Using Puppet 6.4.2
171
+ Started on 192.168.43.195...
172
+ Finished on 192.168.43.195:
173
+ {
174
+ }
175
+ Successful on 1 node: 192.168.43.195
176
+ Ran on 1 node in 11.32 seconds
177
+
178
+ david@davids:~/tmp/hue_workshop$
179
+ ```
180
+
181
+ * profit!
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
  require 'puppet/resource_api/data_type_handling'
3
5
  require 'puppet/resource_api/glue'
@@ -35,7 +37,8 @@ module Puppet::ResourceApi
35
37
  end
36
38
 
37
39
  Puppet::Type.newtype(definition[:name].to_sym) do
38
- @docs = definition[:desc]
40
+ # The :desc value is already cleaned up by the TypeDefinition validation
41
+ @doc = definition[:desc]
39
42
  @type_definition = type_def
40
43
 
41
44
  # Keeps a copy of the provider around. Weird naming to avoid clashes with puppet's own `provider` member
@@ -122,6 +125,22 @@ module Puppet::ResourceApi
122
125
  @rsapi_title
123
126
  end
124
127
 
128
+ def rsapi_canonicalized_target_state
129
+ @rsapi_canonicalized_target_state ||= begin
130
+ # skip puppet's injected metaparams
131
+ actual_params = @parameters.select { |k, _v| type_definition.attributes.key? k }
132
+ target_state = Hash[actual_params.map { |k, v| [k, v.rs_value] }]
133
+ target_state = my_provider.canonicalize(context, [target_state]).first if type_definition.feature?('canonicalize')
134
+ target_state
135
+ end
136
+ @rsapi_canonicalized_target_state
137
+ end
138
+
139
+ def rsapi_current_state
140
+ refresh_current_state unless @rsapi_current_state
141
+ @rsapi_current_state
142
+ end
143
+
125
144
  def to_resource
126
145
  to_resource_shim(super)
127
146
  end
@@ -165,6 +184,21 @@ module Puppet::ResourceApi
165
184
  raise_missing_params if @missing_params.any?
166
185
  end
167
186
 
187
+ # If the custom_insync feature is specified but no insyncable attributes are included
188
+ # in the definition, add the hidden rsapi_custom_insync_trigger property.
189
+ # This property exists *only* to allow a resource without properties to still execute an
190
+ # insync check; there's no point in specifying it in a manifest as it can only have one
191
+ # value; it cannot be specified in a type definition as it should only exist in this case.
192
+ if type_definition.feature?('custom_insync') && type_definition.insyncable_attributes.empty?
193
+ custom_insync_trigger_options = {
194
+ type: 'Enum[do_not_specify_in_manifest]',
195
+ desc: 'A hidden property which enables a type with custom insync to perform an insync check without specifying any insyncable properties',
196
+ default: 'do_not_specify_in_manifest',
197
+ }
198
+
199
+ type_definition.create_attribute_in(self, :rsapi_custom_insync_trigger, :newproperty, Puppet::ResourceApi::Property, custom_insync_trigger_options)
200
+ end
201
+
168
202
  definition[:attributes].each do |name, options|
169
203
  # puts "#{name}: #{options.inspect}"
170
204
 
@@ -188,43 +222,7 @@ module Puppet::ResourceApi
188
222
  parent = Puppet::ResourceApi::Property
189
223
  end
190
224
 
191
- # This call creates a new parameter or property with all work-arounds or
192
- # customizations required by the Resource API applied. Under the hood,
193
- # this maps to the relevant DSL methods in Puppet::Type. See
194
- # https://puppet.com/docs/puppet/6.0/custom_types.html#reference-5883
195
- # for details.
196
- send(param_or_property, name.to_sym, parent: parent) do
197
- if options[:desc]
198
- desc "#{options[:desc]} (a #{options[:type]})"
199
- end
200
-
201
- # The initialize method is called when puppet core starts building up
202
- # type objects. The core passes in a hash of shape { resource:
203
- # #<Puppet::Type::TypeName> }. We use this to pass through the
204
- # required configuration data to the parent (see
205
- # Puppet::ResourceApi::Property, Puppet::ResourceApi::Parameter and
206
- # Puppet::ResourceApi::ReadOnlyParameter).
207
- define_method(:initialize) do |resource_hash|
208
- super(definition[:name], self.class.data_type, name, resource_hash)
209
- end
210
-
211
- # get pops data type object for this parameter or property
212
- define_singleton_method(:data_type) do
213
- @rsapi_data_type ||= Puppet::ResourceApi::DataTypeHandling.parse_puppet_type(
214
- name,
215
- options[:type],
216
- )
217
- end
218
-
219
- # from ValueCreator call create_values which makes alias values and
220
- # default values for properties and params
221
- Puppet::ResourceApi::ValueCreator.create_values(
222
- self,
223
- data_type,
224
- param_or_property,
225
- options,
226
- )
227
- end
225
+ type_definition.create_attribute_in(self, name, param_or_property, parent, options)
228
226
  end
229
227
 
230
228
  def self.instances
@@ -260,7 +258,7 @@ module Puppet::ResourceApi
260
258
 
261
259
  if @rsapi_current_state
262
260
  type_definition.check_schema(@rsapi_current_state)
263
- strict_check(@rsapi_current_state) if type_definition.feature?('canonicalize')
261
+ strict_check(@rsapi_current_state)
264
262
  else
265
263
  @rsapi_current_state = if rsapi_title.is_a? Hash
266
264
  rsapi_title.dup
@@ -274,15 +272,13 @@ module Puppet::ResourceApi
274
272
  # Use this to set the current state from the `instances` method
275
273
  def cache_current_state(resource_hash)
276
274
  @rsapi_current_state = resource_hash
277
- strict_check(@rsapi_current_state) if type_definition.feature?('canonicalize')
275
+ strict_check(@rsapi_current_state)
278
276
  end
279
277
 
280
278
  def retrieve
281
- refresh_current_state unless @rsapi_current_state
282
-
283
- Puppet.debug("Current State: #{@rsapi_current_state.inspect}")
279
+ Puppet.debug("Current State: #{rsapi_current_state.inspect}")
284
280
 
285
- result = Puppet::Resource.new(self.class, title, parameters: @rsapi_current_state)
281
+ result = Puppet::Resource.new(self.class, title, parameters: rsapi_current_state)
286
282
  # puppet needs ensure to be a symbol
287
283
  result[:ensure] = result[:ensure].to_sym if type_definition.ensurable? && result[:ensure].is_a?(String)
288
284
 
@@ -301,10 +297,7 @@ module Puppet::ResourceApi
301
297
  raise_missing_attrs
302
298
 
303
299
  # puts 'flush'
304
- # skip puppet's injected metaparams
305
- actual_params = @parameters.select { |k, _v| type_definition.attributes.key? k }
306
- target_state = Hash[actual_params.map { |k, v| [k, v.rs_value] }]
307
- target_state = my_provider.canonicalize(context, [target_state]).first if type_definition.feature?('canonicalize')
300
+ target_state = rsapi_canonicalized_target_state
308
301
 
309
302
  retrieve unless @rsapi_current_state
310
303
 
@@ -331,7 +324,10 @@ module Puppet::ResourceApi
331
324
  else
332
325
  my_provider.set(context, rsapi_title => { is: @rsapi_current_state, should: target_state }) unless noop?
333
326
  end
334
- raise 'Execution encountered an error' if context.failed?
327
+ if context.failed?
328
+ context.reset_failed
329
+ raise 'Execution encountered an error'
330
+ end
335
331
 
336
332
  # remember that we have successfully reached our desired state
337
333
  @rsapi_current_state = target_state
@@ -350,6 +346,22 @@ module Puppet::ResourceApi
350
346
  def strict_check(current_state)
351
347
  return if Puppet.settings[:strict] == :off
352
348
 
349
+ strict_check_canonicalize(current_state) if type_definition.feature?('canonicalize')
350
+ strict_check_title_parameter(current_state) if type_definition.namevars.size > 1 && !type_definition.title_patterns.empty?
351
+
352
+ nil
353
+ end
354
+
355
+ def strict_message(message)
356
+ case Puppet.settings[:strict]
357
+ when :warning
358
+ Puppet.warning(message)
359
+ when :error
360
+ raise Puppet::DevError, message
361
+ end
362
+ end
363
+
364
+ def strict_check_canonicalize(current_state)
353
365
  # if strict checking is on we must notify if the values are changed by canonicalize
354
366
  # make a deep copy to perform the operation on and to compare against later
355
367
  state_clone = Marshal.load(Marshal.dump(current_state))
@@ -366,15 +378,43 @@ Returned values: #{current_state.inspect}
366
378
  Canonicalized values: #{state_clone.inspect}
367
379
  MESSAGE
368
380
  #:nocov:
381
+ strict_message(message)
382
+ end
369
383
 
370
- case Puppet.settings[:strict]
371
- when :warning
372
- Puppet.warning(message)
373
- when :error
374
- raise Puppet::DevError, message
384
+ def strict_check_title_parameter(current_state)
385
+ unless current_state.key?(:title)
386
+ strict_message("#{type_definition.name}[#{@title}]#get has not provided a title attribute.")
387
+ return
375
388
  end
376
389
 
377
- nil
390
+ # Logic borrowed from Puppet::Resource.parse_title
391
+ title_hash = {}
392
+ self.class.title_patterns.each do |regexp, symbols|
393
+ captures = regexp.match(current_state[:title])
394
+ next if captures.nil?
395
+ symbols.zip(captures[1..-1]).each do |symbol_and_lambda, capture|
396
+ # The Resource API does not support passing procs in title_patterns
397
+ # so, unlike Puppet::Resource, we do not need to handle that here.
398
+ symbol = symbol_and_lambda[0]
399
+ title_hash[symbol] = capture
400
+ end
401
+ break
402
+ end
403
+
404
+ return if title_hash == rsapi_title
405
+
406
+ namevars = type_definition.namevars.reject { |namevar| title_hash[namevar] == rsapi_title[namevar] }
407
+
408
+ #:nocov:
409
+ # codecov fails to register this multiline as covered, even though simplecov does.
410
+ message = <<MESSAGE.strip
411
+ #{type_definition.name}[#{@title}]#get has provided a title attribute which does not match all namevars.
412
+ Namevars which do not match: #{namevars.inspect}
413
+ Returned parsed title hash: #{title_hash.inspect}
414
+ Expected hash: #{rsapi_title.inspect}
415
+ MESSAGE
416
+ #:nocov:
417
+ strict_message(message)
378
418
  end
379
419
 
380
420
  define_singleton_method(:context) do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet/resource_api/type_definition'
2
4
 
3
5
  # rubocop:disable Style/Documentation
@@ -33,6 +35,10 @@ class Puppet::ResourceApi::BaseContext
33
35
  @failed
34
36
  end
35
37
 
38
+ def reset_failed
39
+ @failed = false
40
+ end
41
+
36
42
  def feature_support?(feature)
37
43
  Puppet.deprecation_warning('context.feature_support? is deprecated. Please use context.type.feature? instead.')
38
44
  type.feature?(feature)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Puppet; module ResourceApi; end; end # predeclare the main module # rubocop:disable Style/Documentation,Style/ClassAndModuleChildren
2
4
 
3
5
  # This module is used to handle data inside types, contains methods for munging
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module Puppet; end # rubocop:disable Style/Documentation
@@ -35,6 +37,11 @@ module Puppet::ResourceApi
35
37
  end + ['}']).compact.join("\n")
36
38
  end
37
39
 
40
+ # Required to enable `puppet device --resource ... --to_yaml` workflow
41
+ def to_hiera_hash
42
+ to_hierayaml
43
+ end
44
+
38
45
  # Convert our resource to yaml for Hiera purposes.
39
46
  def to_hierayaml
40
47
  attributes = Hash[filtered_keys.map { |k| [k.to_s, values[k]] }]
@@ -52,9 +59,9 @@ module Puppet::ResourceApi
52
59
  values
53
60
  end
54
61
 
55
- # attribute names that are not title or namevars
62
+ # attribute names that are not title, namevars, or rsapi_custom_insync_trigger
56
63
  def filtered_keys
57
- values.keys.reject { |k| k == :title || !attr_def[k] || (attr_def[k][:behaviour] == :namevar && @namevars.size == 1) }
64
+ values.keys.reject { |k| k == :title || k == :rsapi_custom_insync_trigger || !attr_def[k] || (attr_def[k][:behaviour] == :namevar && @namevars.size == 1) }
58
65
  end
59
66
  end
60
67