chef-resource 0.2.2

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +23 -0
  3. data/LICENSE +201 -0
  4. data/README.md +264 -0
  5. data/Rakefile +8 -0
  6. data/files/lib/chef_resource.rb +24 -0
  7. data/files/lib/chef_resource/camel_case.rb +23 -0
  8. data/files/lib/chef_resource/chef.rb +102 -0
  9. data/files/lib/chef_resource/chef_dsl/chef_cookbook_compiler.rb +44 -0
  10. data/files/lib/chef_resource/chef_dsl/chef_recipe.rb +10 -0
  11. data/files/lib/chef_resource/chef_dsl/chef_recipe_dsl_extensions.rb +84 -0
  12. data/files/lib/chef_resource/chef_dsl/chef_resource_base.rb +12 -0
  13. data/files/lib/chef_resource/chef_dsl/chef_resource_class_extensions.rb +30 -0
  14. data/files/lib/chef_resource/chef_dsl/chef_resource_extensions.rb +224 -0
  15. data/files/lib/chef_resource/chef_dsl/chef_resource_log.rb +54 -0
  16. data/files/lib/chef_resource/chef_dsl/resource_container_module.rb +80 -0
  17. data/files/lib/chef_resource/chef_dsl/resource_definition_dsl.rb +128 -0
  18. data/files/lib/chef_resource/constants.rb +8 -0
  19. data/files/lib/chef_resource/errors.rb +31 -0
  20. data/files/lib/chef_resource/lazy_proc.rb +82 -0
  21. data/files/lib/chef_resource/output/nested_converge.rb +91 -0
  22. data/files/lib/chef_resource/output/nested_converge/open_resource.rb +113 -0
  23. data/files/lib/chef_resource/output/region_stream.rb +145 -0
  24. data/files/lib/chef_resource/output/simple_output.rb +83 -0
  25. data/files/lib/chef_resource/resource.rb +428 -0
  26. data/files/lib/chef_resource/resource/resource_log.rb +197 -0
  27. data/files/lib/chef_resource/resource/resource_type.rb +74 -0
  28. data/files/lib/chef_resource/resource/struct_property.rb +39 -0
  29. data/files/lib/chef_resource/resource/struct_property_type.rb +185 -0
  30. data/files/lib/chef_resource/resource/struct_resource.rb +410 -0
  31. data/files/lib/chef_resource/resource/struct_resource_base.rb +11 -0
  32. data/files/lib/chef_resource/resource/struct_resource_type.rb +275 -0
  33. data/files/lib/chef_resource/simple_struct.rb +121 -0
  34. data/files/lib/chef_resource/type.rb +371 -0
  35. data/files/lib/chef_resource/types.rb +4 -0
  36. data/files/lib/chef_resource/types/boolean.rb +16 -0
  37. data/files/lib/chef_resource/types/byte_size.rb +10 -0
  38. data/files/lib/chef_resource/types/date_time_type.rb +18 -0
  39. data/files/lib/chef_resource/types/date_type.rb +18 -0
  40. data/files/lib/chef_resource/types/float_type.rb +28 -0
  41. data/files/lib/chef_resource/types/integer_type.rb +53 -0
  42. data/files/lib/chef_resource/types/interval.rb +21 -0
  43. data/files/lib/chef_resource/types/path.rb +39 -0
  44. data/files/lib/chef_resource/types/pathname_type.rb +34 -0
  45. data/files/lib/chef_resource/types/string_type.rb +16 -0
  46. data/files/lib/chef_resource/types/symbol_type.rb +18 -0
  47. data/files/lib/chef_resource/types/uri_type.rb +37 -0
  48. data/files/lib/chef_resource/version.rb +3 -0
  49. data/spec/integration/chef.rb +81 -0
  50. data/spec/integration/struct_spec.rb +611 -0
  51. data/spec/integration/struct_state_spec.rb +538 -0
  52. data/spec/integration/type_spec.rb +1123 -0
  53. data/spec/integration/validation_spec.rb +207 -0
  54. data/spec/support/spec_support.rb +7 -0
  55. metadata +167 -0
@@ -0,0 +1,113 @@
1
+ require 'highline/import'
2
+ require 'set'
3
+
4
+ module ChefResource
5
+ module Chef
6
+ module Output
7
+ class NestedConverge
8
+ class ResourceFormat
9
+ def initialize(output, resource)
10
+ @output = output
11
+ @resource = resource
12
+ @largest_child_prefix = 0
13
+ @parent = output.open_resources[resource]
14
+ @parent.add_child(self)
15
+ end
16
+
17
+ attr_reader :output
18
+ attr_reader :parent
19
+ attr_reader :resource
20
+ attr_reader :open_children
21
+ attr_accessor :largest_child_prefix
22
+ def open_children
23
+ @open_children ||= Hash.new
24
+ end
25
+ def indent
26
+ parent ? parent.indent + output.indent_step : 0
27
+ end
28
+
29
+ def add_child(resource)
30
+ if resource.prefix.length >
31
+ open_children << resource
32
+ end
33
+ end
34
+ def remove_child(child)
35
+ open_children.delete(child)
36
+ end
37
+
38
+ def resource_event(event, *args)
39
+ case event
40
+ when :identity_defined
41
+ print_line("Opened", output.style.opened)
42
+ when :fully_defined
43
+ print_line("Defined", output.style.defined)
44
+ when :updating
45
+ print_line("updating ...", output.style.updating)
46
+ when :updated
47
+ print_line(resource.description, output.style.updated)
48
+ print_line(resource.change_description, output.style.updated)
49
+ close
50
+ when :update_failed
51
+ print_line("Failed", output.style.update_failed)
52
+ close
53
+ when :unchanged
54
+ print_line("Unchanged", output.style.unchanged)
55
+ close
56
+ when :debug, :info, :warn, :error, :fatal
57
+ print_line(args[0], output.style.public_send(event))
58
+ when :stdout, :stderr
59
+ print_stream(event, args[0], output.style.public_send(event))
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def close
66
+ parent.remove_child(self)
67
+ output.resource_closed(self)
68
+ end
69
+
70
+ def print_header
71
+ if output.current_resource != parent
72
+ end
73
+ end
74
+
75
+ def print_header_line(line, style)
76
+ if parallel_header?
77
+ while
78
+ end
79
+ end
80
+ if output.current_resource != parent
81
+ parent.print_header
82
+ parent.print_line("", output.style.updating)
83
+ end
84
+ output.print_line("#{line_prefix}#{line}", style)
85
+ output.current_resource = self
86
+ end
87
+
88
+ def print_line(str, style)
89
+ str.lines.each do |line|
90
+ output.take do |is_current|
91
+ if is_current
92
+ output.print_line("#{empty_prefix}#{line}", style)
93
+ else
94
+ print_header_line(line, style)
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ def print_stream(stream, str, style)
101
+ output.print(self, current, str, style)
102
+ end
103
+
104
+ def print_color(color, str)
105
+ str.lines.each do |line|
106
+ @out.print HighLine.color(line, *options[:colors])
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,145 @@
1
+ module ChefResource
2
+ module Chef
3
+ module Output
4
+ class RegionText
5
+
6
+ end
7
+
8
+ #
9
+ # A region of the screen.
10
+ #
11
+ class ScreenRegion
12
+ def initialize(parent)
13
+ @parent = parent
14
+ @width = 0
15
+ @height = 0
16
+ end
17
+
18
+ #
19
+ # The parent region
20
+ #
21
+ property :parent
22
+
23
+ #
24
+ # The top y coordinate of the region
25
+ #
26
+ property :top
27
+
28
+ #
29
+ # The left hand coordinate of the region
30
+ #
31
+ property :height
32
+
33
+ #
34
+ # The width of the region.
35
+ #
36
+ property :width
37
+
38
+ #
39
+ # The height of the region
40
+ #
41
+ property :height
42
+ end
43
+
44
+ #
45
+ # A list of regions
46
+ #
47
+ class ScreenRegionList < ScreenRegion
48
+ def regions
49
+ @regions ||= []
50
+ end
51
+ def add_region(region)
52
+ regions << region if !regions.include?(region)
53
+ end
54
+ def remove_region(region)
55
+ regions.delete(region)
56
+ end
57
+ end
58
+
59
+ #
60
+ # A horizontal list of regions
61
+ #
62
+ class HorizontalRegionList < ScreenRegionList
63
+ end
64
+
65
+ #
66
+ # A vertical list of regions
67
+ #
68
+ class VerticalRegionList
69
+ end
70
+
71
+ #
72
+ # A nested display like so:
73
+ #
74
+ # cookbook::recipe_name
75
+ # |-- Machine Batch: web
76
+ # | |-- Machine web1
77
+ # | | |-- Node web1
78
+ # | | | |-- Property apache2.port changed from 80 to 8080
79
+ # | |-- Machine web2 - Updated
80
+ # |-- Header3
81
+ # |-- Header4
82
+ #
83
+ # In the future, we may even switch it out so you can expand or collapse.
84
+ #
85
+ # A single ChildrenTree has these regions:
86
+ #
87
+ # |---------------------------------------|
88
+ # | Gutter | Sub-Region |
89
+ # | | |
90
+ # | |------------------------------|
91
+ # | | Sub-Region |
92
+ # | | |
93
+ # | |------------------------------|
94
+ # | | Sub-Region |
95
+ # | | |
96
+ # |--------|------------------------------|
97
+
98
+ class ChildrenTree < ScreenRegionList
99
+ def initialize(parent)
100
+ @parent = parent
101
+ parent.add_region(self) if parent.is_a?(ScreenRegionList)
102
+ end
103
+
104
+ def height
105
+ gutter.height
106
+ end
107
+
108
+ attr_reader :parent
109
+ def children
110
+ @children ||= []
111
+ end
112
+
113
+ def header
114
+ @header ||= StreamRegion.new()
115
+ end
116
+ end
117
+
118
+ #
119
+ # Limits itself to a region of the screen, cutting off display on the right
120
+ #
121
+ # Splits up the screen like this:
122
+ #
123
+ class RegionStream < ScreenRegion
124
+ attr_accessor :width
125
+ attr_accessor :height
126
+ attr_accessor :header_lines
127
+ attr_accessor :lines
128
+ end
129
+
130
+ #
131
+ # Prints lines like
132
+ # [x.txt] updating ...
133
+ #
134
+ #
135
+ class ParallelRegionStream < RegionStream
136
+ attr_accessor :
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ chef_run
143
+ recipe x
144
+ file /x/y/z.txt
145
+ execute ls /x/y/z.txt
@@ -0,0 +1,83 @@
1
+ module ChefResource
2
+ module Chef
3
+ module Output
4
+ class SimpleOutput
5
+ def initialize
6
+ at_beginning_of_line = true
7
+ end
8
+
9
+ attr_accessor :current_resource
10
+ attr_accessor :at_beginning_of_line
11
+
12
+ def resource_event(resource, event, *args)
13
+ # Print header for resource
14
+ indent += 2
15
+
16
+ case event
17
+ when :stdout, :stderr
18
+ with_stream(resource, event) do
19
+ print_str(str)
20
+ end
21
+
22
+ when :debug, :info, :warn, :error, :fatal
23
+ with_stream(resource, event) do
24
+ begin_line
25
+ print_str(args[0])
26
+ begin_line
27
+ end
28
+
29
+ else
30
+ with_stream(resource, :status) do
31
+ begin_line
32
+ print_str(event)
33
+ begin_line
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def with_stream(resource, stream)
42
+ mutex.synchronize do
43
+ if current_resource != resource
44
+ begin_line
45
+ puts "#{' '*indent_for(resource.parent))}#{resource.short_name}"
46
+ at_beginning_of_line = true
47
+ current_resource = resource
48
+ end
49
+
50
+ yield
51
+ end
52
+ end
53
+
54
+ def begin_line
55
+ if !at_beginning_of_line
56
+ puts ""
57
+ at_beginning_of_line = true
58
+ end
59
+ end
60
+
61
+ def print_str(str)
62
+ lines = str.lines
63
+ if !at_beginning_of_line
64
+ print lines.shift
65
+ end
66
+ lines.each do |line|
67
+ puts "#{' '*indent_for(current_resource)}#{line}"
68
+ end
69
+ at_beginning_of_line = str.end_with("\n")
70
+ end
71
+
72
+ def indent_for(resource)
73
+ indent = 0
74
+ find_parent = resource
75
+ while find_parent && find_parent = find_parent.parent_resource
76
+ indent += 2
77
+ end
78
+ indent
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,428 @@
1
+ require 'chef_resource'
2
+ require 'chef_resource/resource/resource_log'
3
+ require 'chef_resource/simple_struct'
4
+
5
+ module ChefResource
6
+ #
7
+ # Represents a real thing which can be read and updated.
8
+ #
9
+ # When you call YourResource.open(...), it gives you back the Resource's
10
+ # current value. This value will often be lazy-loaded, to avoid the often
11
+ # high performance penalty of accessing real things over network or disk).
12
+ # You may make modifications to this value (append to it, set properties,
13
+ # etc.), and then call `update` at the end to save your changes.
14
+ #
15
+ # Resources are initialized with `YourResource.open(<identity properties>)`.
16
+ # After this, the Resource object will have methods and properties that get
17
+ # you the actual value. If you want to update the value, the Resource object
18
+ # lets you modify the values as well; then you call `update` with no
19
+ # parameters to actually perform the update.
20
+ #
21
+ # In general, a Resource:
22
+ # - MUST have an initial value equal to the actual value (GET).
23
+ # - SHOULD allow the user to modify the values on the Resource.
24
+ # - MUST reflect any changes on the Resource itself--i.e. if you set
25
+ # file.mode = 0664, then file.mode should == 0664, even if you haven't
26
+ # called `update` yet and the actual file has mode `0777`.
27
+ # - MAY cache actual values on first retrieve for efficiency reasons.
28
+ # - MUST NOT make any changes to the actual Resource until `update` is called.
29
+ # - MUST make all user-requested changes in `update`, or raise an error if
30
+ # they cannot be fulfilled.
31
+ # - SHOULD be atomic in that all changes represented in the commit will be
32
+ # made available to users at the same time (all switched over at once).
33
+ # - MAY be transactional in that nested resources are not committed until
34
+ # the parent resource is committed. While this is ideal for many reasons,
35
+ # many Resources don't do it because of the difficulty of implementing it.
36
+ #
37
+ # ## Defining a Resource Type
38
+ #
39
+ # ### Struct
40
+ #
41
+ # The simplest way to create a new Resource is with Struct:
42
+ #
43
+ # class MyFile < ChefResource::StructResource
44
+ # property :path, Path, identity: true
45
+ # property :content, String
46
+ # def load
47
+ # File.exist?(path) ? IO.read(content) : nil
48
+ # end
49
+ # def update
50
+ # converge :content do
51
+ # IO.write(path.to_s, content)
52
+ # end
53
+ # end
54
+ # end
55
+ #
56
+ # ### Self-Defined Resource class
57
+ #
58
+ # If you did all of this yourself, it would look like this:
59
+ #
60
+ # ```ruby
61
+ # class MyFile
62
+ # def initialize(path)
63
+ # @path = path
64
+ # end
65
+ #
66
+ # attr_reader :path
67
+ # attr_writer :content
68
+ # def content
69
+ # if !defined?(@content)
70
+ # begin
71
+ # @content = IO.read(path)
72
+ # rescue
73
+ # @content = nil
74
+ # end
75
+ # @original_content = @content
76
+ # end
77
+ # end
78
+ # def exists?
79
+ # content.nil?
80
+ # end
81
+ #
82
+ # def update
83
+ # if @content != @original_content
84
+ # if !exists?
85
+ # puts "#{path} does not exist. Creating content ..."
86
+ # else
87
+ # puts "#{path}.content modified. Updating ..."
88
+ # end
89
+ # IO.write(path, content)
90
+ # end
91
+ # end
92
+ # end
93
+ # ```
94
+ #
95
+ # Resources provide change detection (for idempotence), events, validation,
96
+ # coercion, lazy loading, resource nesting and compatibility, and automatic
97
+ # Chef compatibility along with a consistent interface, all wrapped up in a
98
+ # simple class definition.
99
+ #
100
+ # A Resource generally goes through these phases (represented by resource_state):
101
+ # 1. :created - the Resource object has been created but its identity values are
102
+ # not yet filled in. Because the identity is not yet complete,
103
+ # `current_resource` cannot be retrieved: defaults and actual loaded values
104
+ # are unavailable.
105
+ # 2. :identity_defined - The identity of this Resource--the information needed to be
106
+ # able to retrieve its actual value--is set. Identity values are now set
107
+ # in stone and can no longer be changed. `current_resource` is now available,
108
+ # and the actual value (get) and default values can now be accessed.
109
+ # Note: even though identity is now readonly on the open Resource object,
110
+ # the current_resource can set its *own* identity values during `load`, which
111
+ # will become the default for those properties.
112
+ #
113
+ # Because the desired value of the Resource is not yet fully known (it
114
+ # can still be set), `update` cannot be called in this state.
115
+ # 3. :fully_defined - This Resource's desired values are now complete. The
116
+ # Resource is now readonly. `update` is now available.
117
+ #
118
+ # TODO thread safety on calling load and update, and on changing and
119
+ # checking resource_state
120
+ #
121
+ module Resource
122
+ def initialize(*args, &block)
123
+ super
124
+ resource_created
125
+ end
126
+
127
+ #
128
+ # Updates the real resource with desired changes
129
+ #
130
+ def update_resource
131
+ raise NotImplementedError, "#{self.class}.update_resource"
132
+ end
133
+
134
+ #
135
+ # Load this resource with actual values. Must set resource_exists = false if the
136
+ # resource does not exist.
137
+ #
138
+ # @raise Various errors if the resource could not be loaded and it is not
139
+ # known whether the resource actually exists.
140
+ #
141
+ def load
142
+ end
143
+
144
+ #
145
+ # The remaining methods you don't generally have to explicitly override.
146
+ #
147
+
148
+ extend ChefResource::SimpleStruct
149
+
150
+ #
151
+ # Indicates whether this is the "current resource," an instance loaded in
152
+ # by accessing `current_resource` on a Resource.
153
+ #
154
+ # This affects loading behavior: if the user asks for a value on `current_resource`
155
+ # that needs to be loaded with `load_value`, it will load the value onto
156
+ # the current resource if this is set.
157
+ #
158
+ boolean_property :is_current_resource
159
+
160
+ #
161
+ # The underlying value of this resource. Any values the user has not
162
+ # filled in will be based on this.
163
+ #
164
+ # This method may return the actual value, or the default value (if the
165
+ # actual_value does not exist).
166
+ #
167
+ # The first time this is called, it will attempt to load the actual value,
168
+ # caching it or recording the fact that it does not exist.
169
+ #
170
+ def current_resource(resource=NOT_PASSED)
171
+ if resource != NOT_PASSED
172
+ @current_resource = resource
173
+ else
174
+ # If this is the first time we've been called, calculate current_resource as
175
+ # either the current value, or the default value if there is no current
176
+ # value.
177
+ if !defined?(@current_resource) && !is_current_resource?
178
+
179
+ if resource_state == :created
180
+ raise ResourceStateError.new("Resource cannot be loaded (and defaults cannot be read) until the identity is defined", self)
181
+ end
182
+
183
+ @current_resource = load_current_resource
184
+ end
185
+
186
+ @current_resource
187
+ end
188
+ end
189
+
190
+
191
+ #
192
+ # Loads the current actual value of this Resources into a new struct, storing
193
+ # the resulting value in `current_resource`.
194
+ #
195
+ # The default implementation calls `reopen_resource` and then calls `load`
196
+ # on the new Resource object.
197
+ #
198
+ def load_current_resource
199
+ # Reopen the resource (it's in :identity_defined state) with
200
+ # `identity` values copied over.
201
+ loading_resource = reopen_resource
202
+ loading_resource.is_current_resource = true
203
+ # We store it as soon as we have it, to ensure loops can't happen.
204
+ @current_resource = loading_resource
205
+
206
+ # Run "load"
207
+ log.load_started
208
+ begin
209
+ loading_resource.load
210
+ log.load_succeeded
211
+ @current_resource
212
+
213
+ rescue
214
+ log.load_failed($!)
215
+ raise
216
+ end
217
+ end
218
+
219
+ #
220
+ # Whether the current resource has been loaded.
221
+ #
222
+ # @return [Boolean] Whether the current resource has been loaded. This will
223
+ # be true if the attempt was made, even if it failed to load or does not
224
+ # actually exist.
225
+ #
226
+ def current_resource_loaded?
227
+ defined?(@current_resource)
228
+ end
229
+
230
+ #
231
+ # Get a new copy of the Resource with only identity values set.
232
+ #
233
+ # Note: the Resource remains in :created state, not :identity_defined as
234
+ # one would get from `open`. Call resource_identity_defined if you want
235
+ # to be able to retrieve actual values.
236
+ #
237
+ # This method is used by ResourceType.get() and Resource.reload.
238
+ #
239
+ def reopen_resource
240
+ raise NotImplementedError, "#{self.class}.reopen_resource"
241
+ end
242
+
243
+ #
244
+ # Set the actual value.
245
+ #
246
+ def current_resource=(resource)
247
+ @current_resource = resource
248
+ end
249
+
250
+ #
251
+ # Set whether this resource exists or not.
252
+ #
253
+ def resource_exists=(value)
254
+ @resource_exists = value
255
+ end
256
+
257
+ #
258
+ # Get/set whether this resource exists.
259
+ #
260
+ def resource_exists(value=NOT_PASSED)
261
+ if value == NOT_PASSED
262
+ if defined?(@resource_exists)
263
+ @resource_exists
264
+ elsif current_resource
265
+ current_resource.resource_exists?
266
+ else
267
+ # Defaults to true if there is no current_resource.
268
+ true
269
+ end
270
+ else
271
+ @resource_exists = value
272
+ end
273
+ end
274
+
275
+ #
276
+ # Whether this resource exists or not.
277
+ #
278
+ alias :resource_exists? :resource_exists
279
+
280
+ #
281
+ # Resets the Resource so that `update` will make no changes and its value
282
+ # will be the same as the actual value.
283
+ #
284
+ def reset
285
+ raise NotImplementedError, "#{self.class}.reset"
286
+ end
287
+
288
+ #
289
+ # An object with log methods: `debug`, `info`, `warn`, `error`, `fatal`, `opened`, `defined`, `updated`, `failed`
290
+ #
291
+ # @example Logging directly (info level)
292
+ # your_resource.log('hi there')
293
+ # @example Using the log object
294
+ # your_resource.log.error "Oh noes"
295
+ #
296
+ def log(str=nil)
297
+ @resource_log ||= ResourceLog.new(self)
298
+ if str
299
+ @resource_log.info(str)
300
+ @resource_log
301
+ else
302
+ @resource_log
303
+ end
304
+ end
305
+
306
+ #
307
+ # The state of this resource. It moves through four phases:
308
+ # - :created - has been created, but not fully opened (still initializing).
309
+ # Only identity values are writeable in this state.
310
+ # - :identity_defined - has been opened (has enough data to retrieve the actual value)
311
+ # Identity properties are readonly in this state.
312
+ # - :fully_defined - has been fully defined (properties are now readonly)
313
+ # All properties are readonly in this state.
314
+ #
315
+ def resource_state
316
+ @resource_state
317
+ end
318
+
319
+ #
320
+ # Notify the resource that it has been created (and is now open to write
321
+ # identity properties). This happens automatically during initialize and
322
+ # before any identity properties are set.
323
+ #
324
+ def resource_created
325
+ @resource_state = :created
326
+ log.created
327
+ end
328
+
329
+ #
330
+ # Notify the resource that it is fully opened and ready to read and write.
331
+ #
332
+ # Identity properties are readonly in this state.
333
+ #
334
+ def resource_identity_defined
335
+ case resource_state
336
+ when :created
337
+ @resource_state = :identity_defined
338
+ log.identity_defined
339
+ when :identity_defined
340
+ else
341
+ raise "Cannot move a resource from #{@resource_state} to open"
342
+ end
343
+ end
344
+
345
+ #
346
+ # Shut down the definition of the resource.
347
+ #
348
+ # The entire resource is readonly in this state.
349
+ #
350
+ def resource_fully_defined
351
+ case resource_state
352
+ when :created
353
+ resource_identity_defined
354
+ @resource_state = :fully_defined
355
+ log.fully_defined
356
+ when :identity_defined
357
+ @resource_state = :fully_defined
358
+ log.fully_defined
359
+ when :fully_defined
360
+ else
361
+ raise "Cannot move a resource from #{@resource_state} to defined"
362
+ end
363
+ end
364
+
365
+ #
366
+ # Update events and print stuff
367
+ #
368
+
369
+ #
370
+ # Take an action that will update the resource.
371
+ #
372
+ # @param description [String] The action being taken.
373
+ # @yield A block that will perform the actual update.
374
+ # @raise Any error raised by the block is passed through.
375
+ #
376
+ def take_action(description, &action_block)
377
+ log.action_started(description)
378
+ begin
379
+ instance_eval(&action_block)
380
+ rescue
381
+ log.action_failed($!)
382
+ raise
383
+ end
384
+ log.action_succeeded
385
+ end
386
+
387
+ #
388
+ # Take an action that may or may not update the resource.
389
+ #
390
+ # @param description [String] The action being attempted.
391
+ # @yield A block that will perform the actual update.
392
+ # @yieldreturn [Boolean String] `true` or a String describing the update if
393
+ # the resource was updated; `false` if the resource did not need to be
394
+ # updated.
395
+ # @raise Any error raised by the block is passed through.
396
+ #
397
+ def try_action(description, &action_block)
398
+ log.action_started(description, update_guaranteed: false)
399
+ begin
400
+ result = instance_eval(&action_block)
401
+ rescue
402
+ log.action_failed($!)
403
+ raise
404
+ end
405
+
406
+ if result.is_a?(String)
407
+ log.action_succeeded(updated: true, update_description: result)
408
+ elsif result
409
+ log.action_succeeded(updated: true)
410
+ else
411
+ log.action_succeeded(updated: false)
412
+ end
413
+ end
414
+
415
+ #
416
+ # Record the fact that we skipped an action.
417
+ #
418
+ def skip_action(description)
419
+ log.action_started(description, update_guaranteed: false)
420
+ log.action_succeeded(updated: false)
421
+ end
422
+
423
+ # A short name for this resource for output formatters
424
+ def resource_short_name
425
+ raise NotImplementedError
426
+ end
427
+ end
428
+ end