chef-resource 0.2.2

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