clc-cheffish 0.8.clc

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 (63) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +4 -0
  4. data/Rakefile +23 -0
  5. data/lib/chef/provider/chef_acl.rb +434 -0
  6. data/lib/chef/provider/chef_client.rb +48 -0
  7. data/lib/chef/provider/chef_container.rb +50 -0
  8. data/lib/chef/provider/chef_data_bag.rb +50 -0
  9. data/lib/chef/provider/chef_data_bag_item.rb +273 -0
  10. data/lib/chef/provider/chef_environment.rb +78 -0
  11. data/lib/chef/provider/chef_group.rb +78 -0
  12. data/lib/chef/provider/chef_mirror.rb +138 -0
  13. data/lib/chef/provider/chef_node.rb +82 -0
  14. data/lib/chef/provider/chef_organization.rb +150 -0
  15. data/lib/chef/provider/chef_resolved_cookbooks.rb +41 -0
  16. data/lib/chef/provider/chef_role.rb +79 -0
  17. data/lib/chef/provider/chef_user.rb +53 -0
  18. data/lib/chef/provider/private_key.rb +219 -0
  19. data/lib/chef/provider/public_key.rb +82 -0
  20. data/lib/chef/resource/chef_acl.rb +65 -0
  21. data/lib/chef/resource/chef_client.rb +44 -0
  22. data/lib/chef/resource/chef_container.rb +18 -0
  23. data/lib/chef/resource/chef_data_bag.rb +18 -0
  24. data/lib/chef/resource/chef_data_bag_item.rb +114 -0
  25. data/lib/chef/resource/chef_environment.rb +71 -0
  26. data/lib/chef/resource/chef_group.rb +49 -0
  27. data/lib/chef/resource/chef_mirror.rb +47 -0
  28. data/lib/chef/resource/chef_node.rb +18 -0
  29. data/lib/chef/resource/chef_organization.rb +64 -0
  30. data/lib/chef/resource/chef_resolved_cookbooks.rb +31 -0
  31. data/lib/chef/resource/chef_role.rb +104 -0
  32. data/lib/chef/resource/chef_user.rb +51 -0
  33. data/lib/chef/resource/private_key.rb +44 -0
  34. data/lib/chef/resource/public_key.rb +21 -0
  35. data/lib/cheffish.rb +222 -0
  36. data/lib/cheffish/actor_provider_base.rb +131 -0
  37. data/lib/cheffish/basic_chef_client.rb +115 -0
  38. data/lib/cheffish/chef_provider_base.rb +231 -0
  39. data/lib/cheffish/chef_run_data.rb +19 -0
  40. data/lib/cheffish/chef_run_listener.rb +28 -0
  41. data/lib/cheffish/key_formatter.rb +109 -0
  42. data/lib/cheffish/merged_config.rb +94 -0
  43. data/lib/cheffish/recipe_dsl.rb +147 -0
  44. data/lib/cheffish/server_api.rb +52 -0
  45. data/lib/cheffish/version.rb +3 -0
  46. data/lib/cheffish/with_pattern.rb +21 -0
  47. data/spec/functional/fingerprint_spec.rb +64 -0
  48. data/spec/functional/merged_config_spec.rb +20 -0
  49. data/spec/integration/chef_acl_spec.rb +914 -0
  50. data/spec/integration/chef_client_spec.rb +110 -0
  51. data/spec/integration/chef_container_spec.rb +34 -0
  52. data/spec/integration/chef_group_spec.rb +324 -0
  53. data/spec/integration/chef_mirror_spec.rb +244 -0
  54. data/spec/integration/chef_node_spec.rb +211 -0
  55. data/spec/integration/chef_organization_spec.rb +244 -0
  56. data/spec/integration/chef_user_spec.rb +90 -0
  57. data/spec/integration/private_key_spec.rb +446 -0
  58. data/spec/integration/recipe_dsl_spec.rb +29 -0
  59. data/spec/support/key_support.rb +29 -0
  60. data/spec/support/repository_support.rb +103 -0
  61. data/spec/support/spec_support.rb +176 -0
  62. data/spec/unit/get_private_key_spec.rb +93 -0
  63. metadata +162 -0
@@ -0,0 +1,131 @@
1
+ require 'cheffish/key_formatter'
2
+ require 'cheffish/chef_provider_base'
3
+
4
+ class Cheffish::ActorProviderBase < Cheffish::ChefProviderBase
5
+
6
+ def create_actor
7
+ if new_resource.before
8
+ new_resource.before.call(new_resource)
9
+ end
10
+
11
+ # Create or update the client/user
12
+ current_public_key = new_json['public_key']
13
+ differences = json_differences(current_json, new_json)
14
+ if current_resource_exists?
15
+ # Update the actor if it's different
16
+ if differences.size > 0
17
+ description = [ "update #{actor_type} #{new_resource.name} at #{actor_path}" ] + differences
18
+ converge_by description do
19
+ result = rest.put("#{actor_path}/#{new_resource.name}", normalize_for_put(new_json))
20
+ current_public_key, current_public_key_format = Cheffish::KeyFormatter.decode(result['public_key']) if result['public_key']
21
+ end
22
+ end
23
+ else
24
+ # Create the actor if it's missing
25
+ if !new_public_key
26
+ raise "You must specify a public key to create a #{actor_type}! Use the private_key resource to create a key, and pass it in with source_key_path."
27
+ end
28
+ description = [ "create #{actor_type} #{new_resource.name} at #{actor_path}" ] + differences
29
+ converge_by description do
30
+ result = rest.post("#{actor_path}", normalize_for_post(new_json))
31
+ current_public_key, current_public_key_format = Cheffish::KeyFormatter.decode(result['public_key']) if result['public_key']
32
+ end
33
+ end
34
+
35
+ # Write out the public key
36
+ if new_resource.output_key_path
37
+ # TODO use inline_resource
38
+ key_content = Cheffish::KeyFormatter.encode(current_public_key, { :format => new_resource.output_key_format })
39
+ if !current_resource.output_key_path
40
+ action = 'create'
41
+ elsif key_content != IO.read(current_resource.output_key_path)
42
+ action = 'overwrite'
43
+ else
44
+ action = nil
45
+ end
46
+ if action
47
+ converge_by "#{action} public key #{new_resource.output_key_path}" do
48
+ IO.write(new_resource.output_key_path, key_content)
49
+ end
50
+ end
51
+ # TODO permissions?
52
+ end
53
+
54
+ if new_resource.after
55
+ new_resource.after.call(self, new_json, server_private_key, server_public_key)
56
+ end
57
+ end
58
+
59
+ def delete_actor
60
+ if current_resource_exists?
61
+ converge_by "delete #{actor_type} #{new_resource.name} at #{actor_path}" do
62
+ rest.delete("#{actor_path}/#{new_resource.name}")
63
+ Chef::Log.info("#{new_resource} deleted #{actor_type} #{new_resource.name} at #{rest.url}")
64
+ end
65
+ end
66
+ if current_resource.output_key_path
67
+ converge_by "delete public key #{current_resource.output_key_path}" do
68
+ ::File.unlink(current_resource.output_key_path)
69
+ end
70
+ end
71
+ end
72
+
73
+ def new_public_key
74
+ @new_public_key ||= begin
75
+ if new_resource.source_key
76
+ if new_resource.source_key.is_a?(String)
77
+ key, key_format = Cheffish::KeyFormatter.decode(new_resource.source_key)
78
+
79
+ if key.private?
80
+ key.public_key
81
+ else
82
+ key
83
+ end
84
+ elsif new_resource.source_key.private?
85
+ new_resource.source_key.public_key
86
+ else
87
+ new_resource.source_key
88
+ end
89
+ elsif new_resource.source_key_path
90
+ source_key_path = new_resource.source_key_path
91
+ if Pathname.new(source_key_path).relative?
92
+ source_key_str, source_key_path = Cheffish.get_private_key_with_path(source_key_path, run_context.config)
93
+ else
94
+ source_key_str = IO.read(source_key_path)
95
+ end
96
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(source_key_str, new_resource.source_key_pass_phrase, source_key_path)
97
+ if source_key.private?
98
+ source_key.public_key
99
+ else
100
+ source_key
101
+ end
102
+ else
103
+ nil
104
+ end
105
+ end
106
+ end
107
+
108
+ def augment_new_json(json)
109
+ if new_public_key
110
+ json['public_key'] = new_public_key.to_pem
111
+ end
112
+ json
113
+ end
114
+
115
+ def load_current_resource
116
+ begin
117
+ json = rest.get("#{actor_path}/#{new_resource.name}")
118
+ @current_resource = json_to_resource(json)
119
+ rescue Net::HTTPServerException => e
120
+ if e.response.code == "404"
121
+ @current_resource = not_found_resource
122
+ else
123
+ raise
124
+ end
125
+ end
126
+
127
+ if new_resource.output_key_path && ::File.exist?(new_resource.output_key_path)
128
+ current_resource.output_key_path = new_resource.output_key_path
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,115 @@
1
+ require 'cheffish/version'
2
+ require 'chef/dsl/recipe'
3
+ require 'chef/event_dispatch/base'
4
+ require 'chef/event_dispatch/dispatcher'
5
+ require 'chef/node'
6
+ require 'chef/run_context'
7
+ require 'chef/runner'
8
+ require 'forwardable'
9
+ require 'chef/providers'
10
+ require 'chef/resources'
11
+
12
+ module Cheffish
13
+ class BasicChefClient
14
+ include Chef::DSL::Recipe
15
+
16
+ def initialize(node = nil, events = nil)
17
+ if !node
18
+ node = Chef::Node.new
19
+ node.name 'basic_chef_client'
20
+ node.automatic[:platform] = 'basic_chef_client'
21
+ node.automatic[:platform_version] = Cheffish::VERSION
22
+ end
23
+
24
+ @event_catcher = BasicChefClientEvents.new
25
+ dispatcher = Chef::EventDispatch::Dispatcher.new(@event_catcher)
26
+ dispatcher.register(events) if events
27
+ @run_context = Chef::RunContext.new(node, {}, dispatcher)
28
+ @updated = []
29
+ @cookbook_name = 'basic_chef_client'
30
+ end
31
+
32
+ extend Forwardable
33
+
34
+ # Stuff recipes need
35
+ attr_reader :run_context
36
+ attr_accessor :cookbook_name
37
+ attr_accessor :recipe_name
38
+ def_delegators :@run_context, :resource_collection, :immediate_notifications, :delayed_notifications
39
+
40
+ def add_resource(resource)
41
+ resource.run_context = run_context
42
+ run_context.resource_collection.insert(resource)
43
+ end
44
+
45
+ def load_block(&block)
46
+ @recipe_name = 'block'
47
+ instance_eval(&block)
48
+ end
49
+
50
+ def converge
51
+ Chef::Runner.new(self).converge
52
+ end
53
+
54
+ def updates
55
+ @event_catcher.updates
56
+ end
57
+
58
+ def updated?
59
+ @event_catcher.updates.size > 0
60
+ end
61
+
62
+ # Builds a resource sans context, which can be later used in a new client's
63
+ # add_resource() method.
64
+ def self.build_resource(type, name, created_at=nil, &resource_attrs_block)
65
+ created_at ||= caller[0]
66
+ result = BasicChefClient.new.build_resource(type, name, created_at, &resource_attrs_block)
67
+ result
68
+ end
69
+
70
+ def self.inline_resource(provider, provider_action, *resources, &block)
71
+ events = ProviderEventForwarder.new(provider, provider_action)
72
+ client = BasicChefClient.new(provider.node, events)
73
+ resources.each do |resource|
74
+ client.add_resource(resource)
75
+ end
76
+ client.load_block(&block) if block
77
+ client.converge
78
+ client.updated?
79
+ end
80
+
81
+ def self.converge_block(node = nil, events = nil, &block)
82
+ client = BasicChefClient.new(node, events)
83
+ client.load_block(&block)
84
+ client.converge
85
+ client.updated?
86
+ end
87
+
88
+ class BasicChefClientEvents < Chef::EventDispatch::Base
89
+ def initialize
90
+ @updates = []
91
+ end
92
+
93
+ attr_reader :updates
94
+
95
+ # Called after a resource has been completely converged.
96
+ def resource_updated(resource, action)
97
+ updates << [ resource, action ]
98
+ end
99
+ end
100
+
101
+ class ProviderEventForwarder < Chef::EventDispatch::Base
102
+ def initialize(provider, provider_action)
103
+ @provider = provider
104
+ @provider_action = provider_action
105
+ end
106
+
107
+ attr_reader :provider
108
+ attr_reader :provider_action
109
+
110
+ def resource_update_applied(resource, action, update)
111
+ provider.run_context.events.resource_update_applied(provider.new_resource, provider_action, update)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,231 @@
1
+ require 'chef/config'
2
+ require 'chef/run_list'
3
+ require 'chef/provider/lwrp_base'
4
+
5
+ module Cheffish
6
+ class ChefProviderBase < Chef::Provider::LWRPBase
7
+ def rest
8
+ @rest ||= Cheffish.chef_server_api(new_resource.chef_server)
9
+ end
10
+
11
+ def current_resource_exists?
12
+ Array(current_resource.action) != [ :delete ]
13
+ end
14
+
15
+ def not_found_resource
16
+ resource = resource_class.new(new_resource.name, run_context)
17
+ resource.action :delete
18
+ resource
19
+ end
20
+
21
+ def normalize_for_put(json)
22
+ data_handler.normalize_for_put(json, fake_entry)
23
+ end
24
+
25
+ def normalize_for_post(json)
26
+ data_handler.normalize_for_post(json, fake_entry)
27
+ end
28
+
29
+ def new_json
30
+ @new_json ||= begin
31
+ if new_resource.complete
32
+ result = normalize(resource_to_json(new_resource))
33
+ else
34
+ # If resource is incomplete, use current json to fill any holes
35
+ result = Chef::Mixin::DeepMerge.hash_only_merge(current_json, resource_to_json(new_resource))
36
+ end
37
+ augment_new_json(result)
38
+ end
39
+ end
40
+
41
+ # Meant to be overridden
42
+ def augment_new_json(json)
43
+ json
44
+ end
45
+
46
+ def current_json
47
+ @current_json ||= begin
48
+ result = normalize(resource_to_json(current_resource))
49
+ result = augment_current_json(result)
50
+ result
51
+ end
52
+ end
53
+
54
+ # Meant to be overridden
55
+ def augment_current_json(json)
56
+ json
57
+ end
58
+
59
+ def resource_to_json(resource)
60
+ json = resource.raw_json || {}
61
+ keys.each do |json_key, resource_key|
62
+ value = resource.send(resource_key)
63
+ json[json_key] = value if value
64
+ end
65
+ json
66
+ end
67
+
68
+ def json_to_resource(json)
69
+ resource = resource_class.new(new_resource.name, run_context)
70
+ keys.each do |json_key, resource_key|
71
+ resource.send(resource_key, json.delete(json_key))
72
+ end
73
+ # Set the leftover to raw_json
74
+ resource.raw_json json
75
+ resource
76
+ end
77
+
78
+ def normalize(json)
79
+ data_handler.normalize(json, fake_entry)
80
+ end
81
+
82
+ def json_differences(old_json, new_json, print_values=true, name = '', result = nil)
83
+ result ||= []
84
+ json_differences_internal(old_json, new_json, print_values, name, result)
85
+ result
86
+ end
87
+
88
+ def json_differences_internal(old_json, new_json, print_values, name, result)
89
+ if old_json.kind_of?(Hash) && new_json.kind_of?(Hash)
90
+ removed_keys = old_json.keys.inject({}) { |hash, key| hash[key] = true; hash }
91
+ new_json.each_pair do |new_key, new_value|
92
+ if old_json.has_key?(new_key)
93
+ removed_keys.delete(new_key)
94
+ if new_value != old_json[new_key]
95
+ json_differences_internal(old_json[new_key], new_value, print_values, name == '' ? new_key : "#{name}.#{new_key}", result)
96
+ end
97
+ else
98
+ if print_values
99
+ result << " add #{name == '' ? new_key : "#{name}.#{new_key}"} = #{new_value.inspect}"
100
+ else
101
+ result << " add #{name == '' ? new_key : "#{name}.#{new_key}"}"
102
+ end
103
+ end
104
+ end
105
+ removed_keys.keys.each do |removed_key|
106
+ result << " remove #{name == '' ? removed_key : "#{name}.#{removed_key}"}"
107
+ end
108
+ else
109
+ old_json = old_json.to_s if old_json.kind_of?(Symbol)
110
+ new_json = new_json.to_s if new_json.kind_of?(Symbol)
111
+ if old_json != new_json
112
+ if print_values
113
+ result << " update #{name} from #{old_json.inspect} to #{new_json.inspect}"
114
+ else
115
+ result << " update #{name}"
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ def apply_modifiers(modifiers, json)
122
+ return json if !modifiers || modifiers.size == 0
123
+
124
+ # If the attributes have nothing, set them to {} so we have something to add to
125
+ if json
126
+ json = Marshal.load(Marshal.dump(json)) # Deep copy
127
+ else
128
+ json = {}
129
+ end
130
+
131
+ modifiers.each do |path, value|
132
+ path = [path] if !path.kind_of?(Array)
133
+ path = path.map { |path_part| path_part.to_s }
134
+ parent = path[0..-2].inject(json) { |hash, path_part| hash ? hash[path_part] : nil }
135
+ existing_value = parent ? parent[path[-1]] : nil
136
+
137
+ if value.is_a?(Proc)
138
+ value = value.call(existing_value)
139
+ end
140
+ if value == :delete
141
+ parent.delete(path[-1]) if parent
142
+ # TODO clean up parent chain if hash is completely emptied
143
+ else
144
+ if !parent
145
+ # Create parent if necessary
146
+ parent = path[0..-2].inject(json) do |hash, path_part|
147
+ hash[path_part] = {} if !hash[path_part]
148
+ hash[path_part]
149
+ end
150
+ end
151
+ parent[path[-1]] = value
152
+ end
153
+ end
154
+ json
155
+ end
156
+
157
+ def apply_run_list_modifiers(add_to_run_list, delete_from_run_list, run_list)
158
+ return run_list if (!add_to_run_list || add_to_run_list.size == 0) && (!delete_from_run_list || !delete_from_run_list.size)
159
+ delete_from_run_list ||= []
160
+ add_to_run_list ||= []
161
+
162
+ run_list = Chef::RunList.new(*run_list)
163
+
164
+ result = []
165
+ add_to_run_list_index = 0
166
+ run_list_index = 0
167
+ while run_list_index < run_list.run_list_items.size do
168
+ # See if the desired run list has this item
169
+ found_desired = add_to_run_list.index { |item| same_run_list_item(item, run_list[run_list_index]) }
170
+ if found_desired
171
+ # If so, copy all items up to that desired run list (to preserve order).
172
+ # If a run list item is out of order (run_list = X, B, Y, A, Z and desired = A, B)
173
+ # then this will give us X, A, B. When A is found later, nothing will be copied
174
+ # because found_desired will be less than add_to_run_list_index. The result will
175
+ # be X, A, B, Y, Z.
176
+ if found_desired >= add_to_run_list_index
177
+ result += add_to_run_list[add_to_run_list_index..found_desired].map { |item| item.to_s }
178
+ add_to_run_list_index = found_desired+1
179
+ end
180
+ else
181
+ # If not, just copy it in
182
+ unless delete_from_run_list.index { |item| same_run_list_item(item, run_list[run_list_index]) }
183
+ result << run_list[run_list_index].to_s
184
+ end
185
+ end
186
+ run_list_index += 1
187
+ end
188
+
189
+ # Copy any remaining desired items at the end
190
+ result += add_to_run_list[add_to_run_list_index..-1].map { |item| item.to_s }
191
+ result
192
+ end
193
+
194
+ def same_run_list_item(a, b)
195
+ a_name = a.name
196
+ b_name = b.name
197
+ # Handle "a::default" being the same as "a"
198
+ if a.type == :recipe && a_name =~ /(.+)::default$/
199
+ a_name = $1
200
+ elsif b.type == :recipe && b_name =~ /(.+)::default$/
201
+ b_name = $1
202
+ end
203
+
204
+ a_name == b_name && a.type == b.type # We want to replace things with same name and different version
205
+ end
206
+
207
+ private
208
+
209
+ # Needed to be able to use DataHandler classes
210
+ def fake_entry
211
+ FakeEntry.new("#{new_resource.send(keys.values.first)}.json")
212
+ end
213
+
214
+ class FakeEntry
215
+ def initialize(name, parent = nil)
216
+ @name = name
217
+ @parent = parent
218
+ @org = nil
219
+ end
220
+
221
+ attr_reader :name
222
+ attr_reader :parent
223
+ attr_reader :org
224
+ end
225
+ end
226
+
227
+ # We are not interested in Chef's cloning behavior here.
228
+ def load_prior_resource
229
+ Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
230
+ end
231
+ end