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,54 @@
1
+ require 'chef/log'
2
+
3
+ module ChefResource
4
+ module ChefDSL
5
+ #
6
+ # Handles events from the ChefResource Resource model and spits the data out
7
+ # to Chef for pretty green text.
8
+ #
9
+ class ChefResourceLog < ChefResource::Resource::ResourceLog
10
+ def log(level, str)
11
+ Chef::Log.public_send(level, "#{resource.resource_short_name} #{str}")
12
+ end
13
+
14
+ def action
15
+ resource.action[0]
16
+ end
17
+
18
+ # When load happens, notify Chef that the resource's current state is loaded.
19
+ def load_succeeded
20
+ super
21
+ resource.events.resource_current_state_loaded(resource, action, resource.current_resource)
22
+ end
23
+
24
+ # When an update succeeds, we mark the resource
25
+ def update_succeeded
26
+ super
27
+
28
+ if resource.updated_by_last_action?
29
+ resource.events.resource_updated(resource, action)
30
+ else
31
+ resource.events.resource_up_to_date(resource, action)
32
+ end
33
+ end
34
+
35
+ def action_skipped(description, update_guaranteed: true)
36
+ super
37
+
38
+ if update_guaranteed
39
+ resource.events.resource_update_applied(resource, action, description)
40
+ resource.updated_by_last_action true
41
+ end
42
+ end
43
+
44
+ # When an action succeeds, we mark the resource updated if it did anything.
45
+ def action_succeeded(**args)
46
+ description, updated = super
47
+ if updated
48
+ resource.events.resource_update_applied(resource, action, description)
49
+ resource.updated_by_last_action true
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,80 @@
1
+ module ChefResource
2
+ module ChefDSL
3
+ module ResourceContainerModule
4
+ attr_reader :recipe_dsl_module
5
+
6
+ def resource_types
7
+ @resource_types ||= {}
8
+ end
9
+
10
+ # TODO also run this after libraries/ are parsed, and perhaps when method_missing
11
+ # is triggered.
12
+ def update_resource_definition_methods!
13
+ const_module = self
14
+
15
+ # Go through the constants in the module, trawling for Resources
16
+ seen = []
17
+ const_module.constants.each do |class_name|
18
+ resource_class = const_module.const_get(class_name)
19
+ next if !resource_class.is_a?(Class)
20
+ next if !(resource_class <= Chef::Resource)
21
+
22
+ resource_name = resource_class.dsl_name
23
+
24
+ seen << resource_name
25
+
26
+ # Detect conflicts: two Resources with the same resource_name
27
+ current_class_name = resource_types[resource_name]
28
+ if current_class_name != class_name
29
+ if current_class_name && const_module.const_defined?(current_class_name)
30
+ current_resource = const_module.const_get(current_class_name)
31
+ if current_resource != resource_class && current_resource.is_a?(Class) && current_resource <= Chef::Resource && current_resource_name == resource_name
32
+ raise "Both #{current_class_name} and #{class_name} map to #{resource_name}. Choose a different dsl_name for one of them!"
33
+ end
34
+ end
35
+
36
+ # We don't overwrite real methods in the recipe DSL (if any?)
37
+ if !current_class_name && method_defined?(resource_name)
38
+ raise "Method #{resource_name} already exists in #{recipe_dsl_module}! Not overwriting with resource definition for #{class_name}."
39
+ end
40
+
41
+ # Create / overwrite the current methods
42
+ emit_resource_definition_method(resource_name, class_name, resource_class)
43
+
44
+ resource_types[resource_name] = class_name
45
+ end
46
+ end
47
+
48
+ # Remove methods for constants that no longer exist
49
+ (seen - resource_types.keys).each do |resource_name|
50
+ const_module.remove_method(resource_name)
51
+ resource_types.delete(resource_name)
52
+ end
53
+ end
54
+
55
+ def emit_resource_definition_method(resource_name, class_name, actual_class)
56
+ # TODO handle definitions too?
57
+ if actual_class <= ChefResource::Resource
58
+ recipe_dsl_module.module_eval <<-EOM, __FILE__, __LINE__+1
59
+ def #{resource_name}(*identity, &update_block)
60
+ # TODO fix Chef: let declare_resource take the resource class
61
+ if update_block
62
+ declare_resource(#{actual_class.name}, *identity, caller[0], &update_block)
63
+ else
64
+ # If you don't pass a block, we assume you just wanted to construct
65
+ # a resource to use for reading.
66
+ build_resource(#{actual_class.name}, *identity, caller[0])
67
+ end
68
+ end
69
+ EOM
70
+ # else
71
+ # recipe_dsl_module.module_eval <<-EOM, __FILE__, __LINE__+1
72
+ # def #{resource_name}(name, &block)
73
+ # declare_resource(#{resource_name.inspect}, name, caller[0], &block)
74
+ # end
75
+ # EOM
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,128 @@
1
+ require 'chef_resource/chef_dsl/chef_resource_extensions'
2
+ require 'chef_resource/chef_dsl/chef_resource_class_extensions'
3
+ require 'chef_resource/chef_dsl/chef_resource_base'
4
+ require 'chef_resource/camel_case'
5
+ require 'chef_resource/resource'
6
+ require 'chef/dsl/recipe'
7
+ require 'chef/resource'
8
+
9
+ module ChefResource
10
+ module ChefDSL
11
+ #
12
+ # DSL for defining resources
13
+ #
14
+ module ResourceDefinitionDSL
15
+ #
16
+ # Create a new resource type under `Chef::Resource`.
17
+ #
18
+ # @param name [Symbol,String] The name of the resource (e.g. :my_resource)
19
+ # @param base_resource_class [Class,Symbol,String] The base resource class.
20
+ # The new resource will derive from this base resource, and will have
21
+ # all the same properties. If a symbol or a string is passed, the name
22
+ # (e.g. `:resource_name`) is searched for under Chef::Resource.
23
+ # @param class_name [Symbol,String] The name of the class to create under
24
+ # Chef::Resource. If not specified, `name` is converted to a class name
25
+ # (e.g. resource_name -> ResourceName).
26
+ # @param overwrite_resource [Boolean] Whether to overwrite the resource if
27
+ # it already exists. If set to `true`, and the resource class already
28
+ # exists, it will be removed and re-created according to the new resource
29
+ # definition. Defaults to false.
30
+ # @param override_block A block that will be run in the context of the new
31
+ # class, allowing you to type `property :name ...` and `recipe do`,
32
+ # as well as `def self.blah` and `def blah`.
33
+ #
34
+ # @raise If the resource class already exists and `overwrite_resource` is
35
+ # set to `false`.
36
+ #
37
+ # @example
38
+ #
39
+ # resource :resource_name, Chef::Resource::File do
40
+ # property :mode, Fixnum, default: 0666
41
+ # end
42
+ #
43
+ def resource(name, base_resource_class=nil, class_name: nil, overwrite_resource: false, &override_block)
44
+ base_resource_class = ResourceDefinitionDSL.base_resource_class_for(base_resource_class)
45
+
46
+ name = name.to_sym
47
+ class_name ||= CamelCase.from_snake_case(name)
48
+
49
+ if Chef::Resource.const_defined?(class_name, false)
50
+ if overwrite_resource
51
+ Chef::Resource.const_set(class_name, nil)
52
+ else
53
+ raise "Cannot redefine resource #{name}, because Chef::Resource::#{class_name} already exists! Pass overwrite_resource: true if you really meant to overwrite."
54
+ end
55
+ end
56
+
57
+ resource_class = Chef::Resource.class_eval <<-EOM, __FILE__, __LINE__+1
58
+ class #{class_name} < base_resource_class
59
+ if !(self <= ChefResource::ChefDSL::ChefResourceExtensions)
60
+ include ChefResource::ChefDSL::ChefResourceExtensions
61
+ extend ChefResource::ChefDSL::ChefResourceClassExtensions
62
+ end
63
+ self
64
+ end
65
+ EOM
66
+ resource_class.class_eval(&override_block)
67
+ Chef::Resource.update_resource_definition_methods!
68
+ resource_class
69
+ end
70
+
71
+ #
72
+ # ChefResource.defaults :my_file, :file, mode: 0666, owner: 'jkeiser'
73
+ #
74
+ def defaults(name, old_name=name, **defaults)
75
+ base_resource_class = ResourceDefinitionDSL.base_resource_class_for(old_name)
76
+ if base_resource_class.is_a?(ChefResource::Resource)
77
+ resource(name, base_resource_class, overwrite_resource: true) do
78
+ defaults.each do |name, value|
79
+ property name, default: value
80
+ end
81
+ end
82
+ else
83
+ Chef::DSL::Recipe.send(:define_method, name) do |name, &block|
84
+ declare_resource(old_name, name, caller[0]) do
85
+ defaults.each do |name, value|
86
+ public_send(name, value)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ #
94
+ # ChefResource.define :resource_name, a: 1, b: 2 do
95
+ # file 'x.txt' do
96
+ # content 'hi'
97
+ # end
98
+ # end
99
+ #
100
+ def define(name, *identity_params, overwrite_resource: true, **params, &recipe_block)
101
+ resource name do
102
+ identity_params.each do |name|
103
+ property name, identity: true
104
+ end
105
+ params.each do |name, value|
106
+ property name, default: value
107
+ end
108
+ recipe(&recipe_block)
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def self.base_resource_class_for(base_resource_class)
115
+ case base_resource_class
116
+ when Class
117
+ # resource_class is a-ok if it's already a Class
118
+ base_resource_class
119
+ when nil
120
+ ChefResource::ChefDSL::ChefResourceBase
121
+ else
122
+ resource_class_name = CamelCase.from_snake_case(base_resource_class.to_s)
123
+ eval("Chef::Resource::#{resource_class_name}")
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,8 @@
1
+ module ChefResource
2
+ NOT_PASSED = Object.new
3
+ NOT_PASSED.instance_eval do
4
+ def to_s
5
+ "NOT_PASSED"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,31 @@
1
+ module ChefResource
2
+ class ValidationError < StandardError
3
+ def initialize(message, value)
4
+ super("#{value} #{message}")
5
+ @value = value
6
+ end
7
+ attr_reader :value
8
+ end
9
+
10
+ class MustNotBeNullError < ValidationError
11
+ end
12
+
13
+ class ResourceStateError < StandardError
14
+ def initialize(message, resource)
15
+ super(message)
16
+ @resource = resource
17
+ end
18
+ attr_reader :resource
19
+ end
20
+
21
+ class ResourceCannotBeOpenedError < ResourceStateError
22
+ end
23
+
24
+ class PropertyDefinedError < ResourceStateError
25
+ def initialize(message, resource, property_type)
26
+ super(message, resource)
27
+ @property_type = property_type
28
+ end
29
+ attr_reader :property_type
30
+ end
31
+ end
@@ -0,0 +1,82 @@
1
+ require 'chef_resource/constants'
2
+ require 'chef_resource/simple_struct'
3
+
4
+ module ChefResource
5
+ class LazyProc < Proc
6
+ #
7
+ # Create a new LazyProc
8
+ #
9
+ # @param switches A list of switches to turn on.
10
+ # - :should_instance_eval: true if instance_eval is expected, false if
11
+ # disallowed. Default: false, except for type attributes like "default"
12
+ # where it gets flipped to true.
13
+ # @param should_instance_eval [Boolean] true if instance_eval is expected, false if
14
+ # disallowed. Default: false, except for type attributes like "default"
15
+ # where it gets flipped to true.
16
+ # @param block The block to run on get()
17
+ #
18
+ def initialize(*switches, should_instance_eval: NOT_PASSED, &block)
19
+ super(&block)
20
+ switches.each do |switch|
21
+ case switch
22
+ when :should_instance_eval
23
+ @should_instance_eval = true
24
+ else
25
+ raise ArgumentError, "Unrecognized argument #{switch.inspect}"
26
+ end
27
+ end
28
+ @should_instance_eval = should_instance_eval if should_instance_eval != NOT_PASSED
29
+ end
30
+
31
+ extend SimpleStruct
32
+
33
+ #
34
+ # Whether to use instance_eval on this lazy instance.
35
+ #
36
+ # Defaults to `false`.
37
+ #
38
+ boolean_property :should_instance_eval, default: "false"
39
+
40
+ #
41
+ # Get the value of this LazyProc.
42
+ #
43
+ # If #instance_eval? is true:
44
+ # - If the proc takes no arguments, runs `instance_eval()` with no parameters
45
+ # - If the proc takes arguments, runs `instance_exec(*args)`
46
+ # If #instance_eval? is false:
47
+ # - If the proc takes no arguments, runs `call()` with no arguments
48
+ # - If the proc takes exactly one mandatory argument, runs `call(instance)` with no other arguments
49
+ # - Otherwise, runs `call(instance, *args)`
50
+ #
51
+ # @param instance The instance to instance_eval against.
52
+ # @param args [Array] Arguments to pass to the function. Do not pass (or pass `nil`)
53
+ # to indicate the function never takes arguments.
54
+ #
55
+ # @return The computed value
56
+ #
57
+ def get(instance: nil, args: nil, instance_eval_by_default: NOT_PASSED)
58
+ if instance_eval_by_default != NOT_PASSED && !defined?(@should_instance_eval)
59
+ should_instance_eval = instance_eval_by_default
60
+ else
61
+ should_instance_eval = self.should_instance_eval?
62
+ end
63
+
64
+ if should_instance_eval
65
+ if arity == 0
66
+ instance.instance_eval(&self)
67
+ else
68
+ instance.instance_exec(*args, &self)
69
+ end
70
+ else
71
+ case arity
72
+ when 0
73
+ call()
74
+ when 1
75
+ call(instance)
76
+ else
77
+ call(instance, *args)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,91 @@
1
+ require 'chef_resource/output/nested_converge/styles'
2
+ require 'chef_resource/output/nested_converge/open_resource'
3
+ require 'chef_resource/resource/resource_events'
4
+ require 'chef_resource/constants'
5
+
6
+ module ChefResource
7
+ module Chef
8
+ module Output
9
+ class NestedConverge < StructResource
10
+ include ResourceEvents
11
+
12
+ property :indent_step, default: 2
13
+ property :styles, Styles, default: {
14
+ default nil
15
+ updated :green
16
+ warn [ :timestamp, :gray ]
17
+ error [ :timestamp, :red ]
18
+ fatal [ :timestamp, :on_red, :white ]
19
+ }
20
+
21
+ class Styles < StructResource
22
+ property :default
23
+ property :identity_defined { default }
24
+ property :fully_defined { default }
25
+ property :updating { default }
26
+ property :updated { default }
27
+ property :not_updated { default }
28
+ property :updated { default }
29
+ property :update_failed { default }
30
+ property :debug { default }
31
+ property :info { default }
32
+ property :warn { default }
33
+ property :error { default }
34
+ property :fatal { default }
35
+ end
36
+
37
+ def open_resources
38
+ @open_resources ||= {}
39
+ end
40
+
41
+ def output_mutex
42
+ @mutex ||= Mutex.new
43
+ end
44
+
45
+ attr_accessor :current_resource
46
+ attr_accessor :current_stream
47
+ attr_accessor :at_line_begin
48
+
49
+ def print(open_resource, stream, str=NOT_PASSED, style)
50
+ if str == NOT_PASSED
51
+ str, stream = stream, :default
52
+ end
53
+ output_mutex.synchronize do
54
+ lines = str.lines
55
+ switch_stream(open_resource, stream, lines)
56
+ end
57
+ end
58
+
59
+ def switch_stream(open_resource, stream, lines, style)
60
+ if current_resource != resource
61
+ current_resource = resource
62
+ current_stream = stream
63
+ open_resource.header_lines(lines.shift, style)
64
+ elsif current_stream != stream
65
+ current_stream = stream
66
+ # TODO look at parents
67
+ puts open_resource.header_line(lines.shift)
68
+ end
69
+ output_mutex.synchronize do
70
+ if current_resource != resource
71
+ prev_resource, current_resource = current_resource, resource
72
+ prev_stream, current_stream = current_stream, stream
73
+ elsif current_stream != stream
74
+ prev_stream, current_stream = current_stream, stream
75
+ end
76
+ block.call(prev_resource, prev_stream)
77
+ end
78
+ end
79
+
80
+ def resource_event(resource, event, *args)
81
+ open_resources[resource] ||= ResourceFormat.new(self, resource)
82
+ open_resources[resource].resource_event(resource, event, *args)
83
+ end
84
+
85
+ def resource_closed(resource)
86
+ open_resources.delete(resource)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end