chef-resource 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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,197 @@
|
|
1
|
+
module ChefResource
|
2
|
+
module Resource
|
3
|
+
class ResourceLog
|
4
|
+
def initialize(resource)
|
5
|
+
@resource = resource
|
6
|
+
end
|
7
|
+
attr_reader :resource
|
8
|
+
|
9
|
+
# Keep track of descriptions of the current load/update/action
|
10
|
+
attr_accessor :current_load
|
11
|
+
attr_accessor :current_update
|
12
|
+
def current_load_values
|
13
|
+
@current_load_values ||= {}
|
14
|
+
end
|
15
|
+
def current_action_stack
|
16
|
+
@current_action_stack ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def debug(str)
|
20
|
+
log(:debug, str)
|
21
|
+
end
|
22
|
+
def info(str)
|
23
|
+
log(:info, str)
|
24
|
+
end
|
25
|
+
def warn(str)
|
26
|
+
log(:warn, str)
|
27
|
+
end
|
28
|
+
def error(str)
|
29
|
+
log(:error, str)
|
30
|
+
end
|
31
|
+
def fatal(str)
|
32
|
+
log(:fatal, str)
|
33
|
+
end
|
34
|
+
def log(level, str)
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Resource phases
|
39
|
+
#
|
40
|
+
|
41
|
+
#
|
42
|
+
# Fired when the resource is created (but identity is not yet defined)
|
43
|
+
#
|
44
|
+
def created
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Fired when the resource identity is defined
|
49
|
+
#
|
50
|
+
def identity_defined
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Fired when the resource desired state is fully defined
|
55
|
+
#
|
56
|
+
def fully_defined
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Resource.load and load_value
|
61
|
+
#
|
62
|
+
|
63
|
+
#
|
64
|
+
# Fired when Resource.load starts
|
65
|
+
#
|
66
|
+
def load_started
|
67
|
+
self.current_load = true
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Fired when Resource.load succeeds
|
72
|
+
#
|
73
|
+
# @param exists if the resource exists, false if it doesn't
|
74
|
+
#
|
75
|
+
def load_succeeded(exists: true)
|
76
|
+
raise "load succeeded when load was never started!" if !current_load
|
77
|
+
self.current_load = false
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Fired when Resource.load fails.
|
82
|
+
#
|
83
|
+
# @param error The error that was raised.
|
84
|
+
#
|
85
|
+
def load_failed(error)
|
86
|
+
raise "load failed when load was never started!" if !current_load
|
87
|
+
self.current_load = false
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Fired when load_value for a given property starts
|
92
|
+
#
|
93
|
+
def load_value_started(name)
|
94
|
+
raise "load_value(#{name}) started twice!" if current_load_values[name]
|
95
|
+
self.current_load_values[name] = true
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Fired when load_value for a given property succeeds
|
100
|
+
#
|
101
|
+
# @param name The name of the property
|
102
|
+
#
|
103
|
+
def load_value_succeeded(name)
|
104
|
+
raise "load_value(name) succeeded but was never started!" if !current_load_values[name]
|
105
|
+
current_load_values.delete(name)
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# Fired when Resource.load_value for a given property fails
|
110
|
+
#
|
111
|
+
# @param name The name of the property
|
112
|
+
# @param error The error that was raised
|
113
|
+
#
|
114
|
+
def load_value_failed(name, error)
|
115
|
+
raise "load_value(name) failed but was never started!" if !current_load_values[name]
|
116
|
+
current_load_values.delete(name)
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Resource.update and actions
|
121
|
+
#
|
122
|
+
|
123
|
+
#
|
124
|
+
# Fired when update starts.
|
125
|
+
#
|
126
|
+
def update_started
|
127
|
+
raise "update started twice!" if current_update
|
128
|
+
self.current_update = true
|
129
|
+
end
|
130
|
+
|
131
|
+
#
|
132
|
+
# Fired when the update succeeds.
|
133
|
+
#
|
134
|
+
def update_succeeded
|
135
|
+
raise "update succeeded, but was never started!" if !current_update
|
136
|
+
raise "update succeeded when actions are still running!" if !current_action_stack.empty?
|
137
|
+
self.current_update = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Fired when the update fails.
|
142
|
+
#
|
143
|
+
# @param error [String] The error that was raised
|
144
|
+
#
|
145
|
+
def update_failed(error)
|
146
|
+
raise "update failed when no update is taking place!" if !current_update
|
147
|
+
self.current_action_stack.clear
|
148
|
+
self.current_update = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
#
|
152
|
+
# Fired when an action is skipped due to why-run.
|
153
|
+
#
|
154
|
+
# @param description [String] A description of the action being taken.
|
155
|
+
#
|
156
|
+
def action_skipped(description, update_guaranteed: true)
|
157
|
+
raise "action started when no update is taking place!" if !current_update
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Fired when an action starts.
|
162
|
+
#
|
163
|
+
# @param description [String] A description of the action being taken.
|
164
|
+
# @param update_guaranteed [Boolean] Whether an update is guaranteed on success or not.
|
165
|
+
#
|
166
|
+
def action_started(description, update_guaranteed: true)
|
167
|
+
raise "action started when no update is taking place!" if !current_update
|
168
|
+
current_action_stack.push [ description, update_guaranteed ]
|
169
|
+
end
|
170
|
+
|
171
|
+
#
|
172
|
+
# Fired when an action succeeds.
|
173
|
+
#
|
174
|
+
# @param description [String] A more detailed description of what the action did.
|
175
|
+
# @param updated [Boolean] Whether anything was actually updated.
|
176
|
+
# @return [String, Boolean] The action that succeeded (a pair of [ description, updated ])
|
177
|
+
#
|
178
|
+
def action_succeeded(description: nil, updated: true)
|
179
|
+
raise "action succeeded when no action was started" if current_action_stack.empty?
|
180
|
+
original_description, _ = current_action_stack.pop
|
181
|
+
[ description || original_description, updated ]
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# Fired when an action fails.
|
186
|
+
#
|
187
|
+
# @param error [String] The error that was raised.
|
188
|
+
# @return [String] The description of the action that failed.
|
189
|
+
#
|
190
|
+
def action_failed(error)
|
191
|
+
raise "action failed when no action was started" if current_action_stack.empty?
|
192
|
+
original_description, _ = current_action_stack.pop
|
193
|
+
original_description
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Resource
|
5
|
+
#
|
6
|
+
# The methods on a Resource class. Generally extended into the class, and
|
7
|
+
# become class-level methods (Class.default(x))
|
8
|
+
#
|
9
|
+
module ResourceType
|
10
|
+
include Type
|
11
|
+
|
12
|
+
#
|
13
|
+
# Opens a resource for reading and updating.
|
14
|
+
#
|
15
|
+
# This method should be passed enough data to uniquely identify the
|
16
|
+
# resource so it can be retrieved.
|
17
|
+
#
|
18
|
+
# The resulting object is generally *lazily* loaded, and may not hit the
|
19
|
+
# network or file until you read a value. This allows you to update
|
20
|
+
# values in some cases without incurring the cost of a read.
|
21
|
+
#
|
22
|
+
# @return An open Resource which:
|
23
|
+
# - Is a Resource
|
24
|
+
# - Is a Resource even if the actual value does not exist
|
25
|
+
# - Can be modified for update
|
26
|
+
# - `type.is_valid?(resource, resource)` is true
|
27
|
+
#
|
28
|
+
def open(&define_identity_block)
|
29
|
+
resource = new
|
30
|
+
resource.instance_eval(&define_identity_block)
|
31
|
+
resource.resource_identity_defined if resource.resource_state == :created
|
32
|
+
resource
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Gets the current value of a resource (immediately). This method must
|
37
|
+
# be passed enough data to uniquely identify the resource so it can be
|
38
|
+
# retrieved.
|
39
|
+
#
|
40
|
+
# The default implementation (ResourceType.get) calls open(*args) { load }
|
41
|
+
#
|
42
|
+
# @return A readonly resource value with values filled in; or nil if the
|
43
|
+
# resource does not exist.
|
44
|
+
#
|
45
|
+
def get(*args, &define_identity_block)
|
46
|
+
resource = open(*args) do
|
47
|
+
instance_eval(&define_identity_block) if define_identity_block
|
48
|
+
load
|
49
|
+
end
|
50
|
+
resource.resource_fully_defined
|
51
|
+
resource
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Updates a resource. By default, opens the resource, evaluates the
|
56
|
+
# block, and calls update().
|
57
|
+
#
|
58
|
+
# @param open_args Arguments to pass to open()
|
59
|
+
# @param update_block The block to run instance_eval on.
|
60
|
+
#
|
61
|
+
# @example
|
62
|
+
# FileResource.update('/x/y.txt') do
|
63
|
+
# mode 0777
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
def update(*open_args, &update_block)
|
67
|
+
resource = open(*args)
|
68
|
+
resource.instance_eval(&update_block)
|
69
|
+
resource.resource_fully_defined
|
70
|
+
resource.update_resource
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'chef_resource/simple_struct'
|
2
|
+
|
3
|
+
module ChefResource
|
4
|
+
module Resource
|
5
|
+
#
|
6
|
+
# Struct properties all create a class for each property. This is
|
7
|
+
# included in that class, and StructPropertyType is extended in that class.
|
8
|
+
#
|
9
|
+
module StructProperty
|
10
|
+
#
|
11
|
+
# The struct containing this property.
|
12
|
+
#
|
13
|
+
extend SimpleStruct
|
14
|
+
property :parent_struct
|
15
|
+
|
16
|
+
#
|
17
|
+
# The actual value defaults to parent.current_resource.attr_name. If
|
18
|
+
# parent.current_resource is `nil`, current_resource defaults to `nil`.
|
19
|
+
#
|
20
|
+
def current_resource
|
21
|
+
actual_struct = parent_struct.current_resource
|
22
|
+
actual_struct.public_send(self.class.property_name) if actual_struct
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# This property exists if its parent does.
|
27
|
+
#
|
28
|
+
def resource_exists?
|
29
|
+
parent_struct.resource_exists?
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def initialize(parent_struct)
|
35
|
+
self.parent_struct = parent_struct
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'chef_resource/type'
|
2
|
+
require 'chef_resource/simple_struct'
|
3
|
+
|
4
|
+
module ChefResource
|
5
|
+
module Resource
|
6
|
+
#
|
7
|
+
# Struct properties all create a class for each property. This is
|
8
|
+
# included in that class, and StructPropertyType is extended in that class.
|
9
|
+
#
|
10
|
+
module StructPropertyType
|
11
|
+
include Type
|
12
|
+
|
13
|
+
extend SimpleStruct
|
14
|
+
|
15
|
+
#
|
16
|
+
# The name of this property
|
17
|
+
#
|
18
|
+
property :property_name
|
19
|
+
|
20
|
+
#
|
21
|
+
# The type of this property
|
22
|
+
#
|
23
|
+
property :property_type
|
24
|
+
|
25
|
+
#
|
26
|
+
# The parent type (struct type) of this property
|
27
|
+
#
|
28
|
+
property :property_parent_type
|
29
|
+
|
30
|
+
#
|
31
|
+
# True if this is an identity property.
|
32
|
+
#
|
33
|
+
boolean_property :identity
|
34
|
+
|
35
|
+
#
|
36
|
+
# True if this property is required.
|
37
|
+
#
|
38
|
+
# Required identity properties can be specified positionally in `open`,
|
39
|
+
# like so: `FileResource.open('/x/y.txt')` is equivalent to
|
40
|
+
# `FileResource.open(path: '/x/y.txt')`
|
41
|
+
#
|
42
|
+
# By default, this is false for most properties, but true for identity
|
43
|
+
# properties that have no default set.
|
44
|
+
#
|
45
|
+
boolean_property :required, default: "identity? && !defined?(@default)"
|
46
|
+
|
47
|
+
#
|
48
|
+
# A block which loads the property from reality.
|
49
|
+
#
|
50
|
+
# @param value The load proc. If this is a LazyProc, the block will
|
51
|
+
# be run in the context of the struct (`struct.instance_eval`) unless
|
52
|
+
# the block is explicitly set to `should_instance_eval: false`. If it is not,
|
53
|
+
# it will always be instance_eval'd.
|
54
|
+
#
|
55
|
+
block_property :load_value
|
56
|
+
|
57
|
+
#
|
58
|
+
# Return a value of this type by coercion or construction.
|
59
|
+
#
|
60
|
+
# @param args The value to coerce or the values to construct with.
|
61
|
+
# @return A value of this Type.
|
62
|
+
#
|
63
|
+
def coerce(parent, *args)
|
64
|
+
property_type.is_a?(ResourceType) ? property_type.coerce(*args) : super
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Emit property methods into the struct class (property_parent_type)
|
69
|
+
#
|
70
|
+
def emit_property_methods
|
71
|
+
name = property_name
|
72
|
+
class_name = CamelCase.from_snake_case(name)
|
73
|
+
property_parent_type.class_eval <<-EOM, __FILE__, __LINE__+1
|
74
|
+
def #{name}(*args)
|
75
|
+
if args.empty?
|
76
|
+
#{class_name}.get_property(self)
|
77
|
+
else
|
78
|
+
#{class_name}.set_property(self, *args)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
EOM
|
82
|
+
|
83
|
+
property_parent_type.class_eval <<-EOM, __FILE__, __LINE__+1
|
84
|
+
def #{name}=(value)
|
85
|
+
#{class_name}.set_property(self, value)
|
86
|
+
end
|
87
|
+
EOM
|
88
|
+
end
|
89
|
+
|
90
|
+
#
|
91
|
+
# Set the property value.
|
92
|
+
#
|
93
|
+
def set_property(struct, *args)
|
94
|
+
if identity?
|
95
|
+
if struct.resource_state && struct.resource_state != :created
|
96
|
+
raise PropertyDefinedError.new("Cannot modify identity property #{property_name} of #{struct.class}: identity properties cannot be modified after the resource's identity is defined. (State: #{struct.resource_state})", struct, self)
|
97
|
+
end
|
98
|
+
else
|
99
|
+
if struct.resource_state && struct.resource_state != :created && struct.resource_state != :identity_defined
|
100
|
+
raise PropertyDefinedError.new("Cannot modify property #{property_name} of #{struct.class}: identity properties cannot be modified after the resource's identity is defined. (State: #{struct.resource_state})", struct, self)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
if args.size == 1 && args[0].is_a?(ChefResource::LazyProc)
|
104
|
+
struct.explicit_property_values[property_name] = args[0]
|
105
|
+
else
|
106
|
+
struct.explicit_property_values[property_name] = coerce(struct, *args)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Get the property value from the struct.
|
112
|
+
#
|
113
|
+
# First tries to get the desired value. If there is none, tries to get
|
114
|
+
# the actual value from the struct. If the actual value doesn't have
|
115
|
+
# the value, it looks for a load_value method. Finally, if that isn't
|
116
|
+
# there, it runs default.
|
117
|
+
#
|
118
|
+
def get_property(struct)
|
119
|
+
value = struct.explicit_property_values.fetch(property_name) do
|
120
|
+
return current_property_value(struct)
|
121
|
+
end
|
122
|
+
coerce_to_user(struct, value)
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Get the current property value from the struct (i.e. the value not
|
127
|
+
# counting anything the user has actually set--including actual value
|
128
|
+
# and default value).
|
129
|
+
#
|
130
|
+
# @param struct The struct we're loading from
|
131
|
+
#
|
132
|
+
def current_property_value(struct)
|
133
|
+
# Try to grab a known (non-default) value from current_resource
|
134
|
+
has_value, value = explicit_current_property_value(struct)
|
135
|
+
if !has_value
|
136
|
+
value = default(parent: struct)
|
137
|
+
end
|
138
|
+
coerce_to_user(struct, value)
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Ensure the current value of the property is loaded and set.
|
143
|
+
#
|
144
|
+
# @param struct The struct we're loading from
|
145
|
+
# @return [Boolean, Value] `true, <value>` if the current_resource exists and has that explicit value, `false, nil` if not
|
146
|
+
# @raise Any error raised by load_value or load will pass through.
|
147
|
+
#
|
148
|
+
def explicit_current_property_value(struct)
|
149
|
+
# First get current_struct
|
150
|
+
current_struct = struct.is_current_resource? ? struct : struct.current_resource
|
151
|
+
if !current_struct
|
152
|
+
return [ false, nil ]
|
153
|
+
end
|
154
|
+
|
155
|
+
# First, check quickly if we already have it.
|
156
|
+
if current_struct.explicit_property_values.has_key?(property_name)
|
157
|
+
return [ true, current_struct.public_send(property_name) ]
|
158
|
+
end
|
159
|
+
|
160
|
+
if current_struct.resource_exists?
|
161
|
+
# Since we were already brought up, we must already be loaded, yet the
|
162
|
+
# property isn't there. Use load_value if it has it.
|
163
|
+
if !load_value
|
164
|
+
return [ false, nil ]
|
165
|
+
end
|
166
|
+
|
167
|
+
struct.log.load_value_started(property_name)
|
168
|
+
|
169
|
+
begin
|
170
|
+
value = load_value.get(instance: current_struct, instance_eval_by_default: true)
|
171
|
+
# Set the value (if it gets coerced, catch the result)
|
172
|
+
value = current_struct.public_send(property_name, value)
|
173
|
+
struct.log.load_value_succeeded(property_name)
|
174
|
+
return [ true, value ]
|
175
|
+
rescue
|
176
|
+
# short circuit this from happening again
|
177
|
+
current_struct.explicit_property_values[property_name] = nil
|
178
|
+
struct.log.load_value_failed(property_name, $!)
|
179
|
+
raise
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|