puppet-resource_api 1.8.9 → 1.8.14

Sign up to get free protection for your applications and to get access to all the features.
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