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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +23 -0
- data/LICENSE +201 -0
- data/README.md +264 -0
- data/Rakefile +8 -0
- data/files/lib/chef_resource.rb +24 -0
- data/files/lib/chef_resource/camel_case.rb +23 -0
- data/files/lib/chef_resource/chef.rb +102 -0
- data/files/lib/chef_resource/chef_dsl/chef_cookbook_compiler.rb +44 -0
- data/files/lib/chef_resource/chef_dsl/chef_recipe.rb +10 -0
- data/files/lib/chef_resource/chef_dsl/chef_recipe_dsl_extensions.rb +84 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_base.rb +12 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_class_extensions.rb +30 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_extensions.rb +224 -0
- data/files/lib/chef_resource/chef_dsl/chef_resource_log.rb +54 -0
- data/files/lib/chef_resource/chef_dsl/resource_container_module.rb +80 -0
- data/files/lib/chef_resource/chef_dsl/resource_definition_dsl.rb +128 -0
- data/files/lib/chef_resource/constants.rb +8 -0
- data/files/lib/chef_resource/errors.rb +31 -0
- data/files/lib/chef_resource/lazy_proc.rb +82 -0
- data/files/lib/chef_resource/output/nested_converge.rb +91 -0
- data/files/lib/chef_resource/output/nested_converge/open_resource.rb +113 -0
- data/files/lib/chef_resource/output/region_stream.rb +145 -0
- data/files/lib/chef_resource/output/simple_output.rb +83 -0
- data/files/lib/chef_resource/resource.rb +428 -0
- data/files/lib/chef_resource/resource/resource_log.rb +197 -0
- data/files/lib/chef_resource/resource/resource_type.rb +74 -0
- data/files/lib/chef_resource/resource/struct_property.rb +39 -0
- data/files/lib/chef_resource/resource/struct_property_type.rb +185 -0
- data/files/lib/chef_resource/resource/struct_resource.rb +410 -0
- data/files/lib/chef_resource/resource/struct_resource_base.rb +11 -0
- data/files/lib/chef_resource/resource/struct_resource_type.rb +275 -0
- data/files/lib/chef_resource/simple_struct.rb +121 -0
- data/files/lib/chef_resource/type.rb +371 -0
- data/files/lib/chef_resource/types.rb +4 -0
- data/files/lib/chef_resource/types/boolean.rb +16 -0
- data/files/lib/chef_resource/types/byte_size.rb +10 -0
- data/files/lib/chef_resource/types/date_time_type.rb +18 -0
- data/files/lib/chef_resource/types/date_type.rb +18 -0
- data/files/lib/chef_resource/types/float_type.rb +28 -0
- data/files/lib/chef_resource/types/integer_type.rb +53 -0
- data/files/lib/chef_resource/types/interval.rb +21 -0
- data/files/lib/chef_resource/types/path.rb +39 -0
- data/files/lib/chef_resource/types/pathname_type.rb +34 -0
- data/files/lib/chef_resource/types/string_type.rb +16 -0
- data/files/lib/chef_resource/types/symbol_type.rb +18 -0
- data/files/lib/chef_resource/types/uri_type.rb +37 -0
- data/files/lib/chef_resource/version.rb +3 -0
- data/spec/integration/chef.rb +81 -0
- data/spec/integration/struct_spec.rb +611 -0
- data/spec/integration/struct_state_spec.rb +538 -0
- data/spec/integration/type_spec.rb +1123 -0
- data/spec/integration/validation_spec.rb +207 -0
- data/spec/support/spec_support.rb +7 -0
- 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
|