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,48 @@
1
+ require 'cheffish/actor_provider_base'
2
+ require 'chef/resource/chef_client'
3
+ require 'chef/chef_fs/data_handler/client_data_handler'
4
+
5
+ class Chef::Provider::ChefClient < Cheffish::ActorProviderBase
6
+
7
+ def whyrun_supported?
8
+ true
9
+ end
10
+
11
+ def actor_type
12
+ 'client'
13
+ end
14
+
15
+ def actor_path
16
+ 'clients'
17
+ end
18
+
19
+ action :create do
20
+ create_actor
21
+ end
22
+
23
+ action :delete do
24
+ delete_actor
25
+ end
26
+
27
+ #
28
+ # Helpers
29
+ #
30
+
31
+ def resource_class
32
+ Chef::Resource::ChefClient
33
+ end
34
+
35
+ def data_handler
36
+ Chef::ChefFS::DataHandler::ClientDataHandler.new
37
+ end
38
+
39
+ def keys
40
+ {
41
+ 'name' => :name,
42
+ 'admin' => :admin,
43
+ 'validator' => :validator,
44
+ 'public_key' => :source_key
45
+ }
46
+ end
47
+
48
+ end
@@ -0,0 +1,50 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_container'
3
+ require 'chef/chef_fs/data_handler/container_data_handler'
4
+
5
+ class Chef::Provider::ChefContainer < Cheffish::ChefProviderBase
6
+
7
+ def whyrun_supported?
8
+ true
9
+ end
10
+
11
+ action :create do
12
+ if !@current_exists
13
+ converge_by "create container #{new_resource.name} at #{rest.url}" do
14
+ rest.post("containers", normalize_for_post(new_json))
15
+ end
16
+ end
17
+ end
18
+
19
+ action :delete do
20
+ if @current_exists
21
+ converge_by "delete container #{new_resource.name} at #{rest.url}" do
22
+ rest.delete("containers/#{new_resource.name}")
23
+ end
24
+ end
25
+ end
26
+
27
+ def load_current_resource
28
+ begin
29
+ @current_exists = rest.get("containers/#{new_resource.name}")
30
+ rescue Net::HTTPServerException => e
31
+ if e.response.code == "404"
32
+ @current_exists = false
33
+ else
34
+ raise
35
+ end
36
+ end
37
+ end
38
+
39
+ def new_json
40
+ {}
41
+ end
42
+
43
+ def data_handler
44
+ Chef::ChefFS::DataHandler::ContainerDataHandler.new
45
+ end
46
+
47
+ def keys
48
+ { 'containername' => :name, 'containerpath' => :name }
49
+ end
50
+ end
@@ -0,0 +1,50 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_data_bag'
3
+
4
+ class Chef::Provider::ChefDataBag < Cheffish::ChefProviderBase
5
+
6
+ def whyrun_supported?
7
+ true
8
+ end
9
+
10
+ action :create do
11
+ if !current_resource_exists?
12
+ converge_by "create data bag #{new_resource.name} at #{rest.url}" do
13
+ rest.post("data", { 'name' => new_resource.name })
14
+ end
15
+ end
16
+ end
17
+
18
+ action :delete do
19
+ if current_resource_exists?
20
+ converge_by "delete data bag #{new_resource.name} at #{rest.url}" do
21
+ rest.delete("data/#{new_resource.name}")
22
+ end
23
+ end
24
+ end
25
+
26
+ def load_current_resource
27
+ begin
28
+ @current_resource = json_to_resource(rest.get("data/#{new_resource.name}"))
29
+ rescue Net::HTTPServerException => e
30
+ if e.response.code == "404"
31
+ @current_resource = not_found_resource
32
+ else
33
+ raise
34
+ end
35
+ end
36
+ end
37
+
38
+ #
39
+ # Helpers
40
+ #
41
+ # Gives us new_json, current_json, not_found_json, etc.
42
+
43
+ def resource_class
44
+ Chef::Resource::ChefDataBag
45
+ end
46
+
47
+ def json_to_resource(json)
48
+ Chef::Resource::ChefDataBag.new(json['name'], run_context)
49
+ end
50
+ end
@@ -0,0 +1,273 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_data_bag_item'
3
+ require 'chef/chef_fs/data_handler/data_bag_item_data_handler'
4
+ require 'chef/encrypted_data_bag_item'
5
+
6
+ class Chef::Provider::ChefDataBagItem < Cheffish::ChefProviderBase
7
+
8
+ def whyrun_supported?
9
+ true
10
+ end
11
+
12
+ action :create do
13
+ differences = calculate_differences
14
+
15
+ if current_resource_exists?
16
+ if differences.size > 0
17
+ description = [ "update data bag item #{new_resource.id} at #{rest.url}" ] + differences
18
+ converge_by description do
19
+ rest.put("data/#{new_resource.data_bag}/#{new_resource.id}", normalize_for_put(new_json))
20
+ end
21
+ end
22
+ else
23
+ description = [ "create data bag item #{new_resource.id} at #{rest.url}" ] + differences
24
+ converge_by description do
25
+ rest.post("data/#{new_resource.data_bag}", normalize_for_post(new_json))
26
+ end
27
+ end
28
+ end
29
+
30
+ action :delete do
31
+ if current_resource_exists?
32
+ converge_by "delete data bag item #{new_resource.id} at #{rest.url}" do
33
+ rest.delete("data/#{new_resource.data_bag}/#{new_resource.id}")
34
+ end
35
+ end
36
+ end
37
+
38
+ def load_current_resource
39
+ begin
40
+ json = rest.get("data/#{new_resource.data_bag}/#{new_resource.id}")
41
+ resource = Chef::Resource::ChefDataBagItem.new(new_resource.name, run_context)
42
+ resource.raw_data json
43
+ @current_resource = resource
44
+ rescue Net::HTTPServerException => e
45
+ if e.response.code == "404"
46
+ @current_resource = not_found_resource
47
+ else
48
+ raise
49
+ end
50
+ end
51
+
52
+ # Determine if data bag is encrypted and if so, what its version is
53
+ first_real_key, first_real_value = (current_resource.raw_data || {}).select { |key, value| key != 'id' && !value.nil? }.first
54
+ if first_real_value
55
+ if first_real_value.is_a?(Hash) &&
56
+ first_real_value['version'].is_a?(Integer) &&
57
+ first_real_value['version'] > 0 &&
58
+ first_real_value.has_key?('encrypted_data')
59
+
60
+ current_resource.encrypt true
61
+ current_resource.encryption_version first_real_value['version']
62
+
63
+ decrypt_error = nil
64
+
65
+ # Check if the desired secret is the one (which it generally should be)
66
+
67
+ if new_resource.secret || new_resource.secret_path
68
+ begin
69
+ Chef::EncryptedDataBagItem::Decryptor.for(first_real_value, new_secret).for_decrypted_item
70
+ current_resource.secret new_secret
71
+ rescue Chef::EncryptedDataBagItem::DecryptionFailure
72
+ decrypt_error = $!
73
+ end
74
+ end
75
+
76
+ # If the current secret doesn't work, look through the specified old secrets
77
+
78
+ if !current_resource.secret
79
+ old_secrets = []
80
+ if new_resource.old_secret
81
+ old_secrets += Array(new_resource.old_secret)
82
+ end
83
+ if new_resource.old_secret_path
84
+ old_secrets += Array(new_resource.old_secret_path).map do |secret_path|
85
+ Chef::EncryptedDataBagItem.load_secret(new_resource.old_secret_file)
86
+ end
87
+ end
88
+ old_secrets.each do |secret|
89
+ begin
90
+ Chef::EncryptedDataBagItem::Decryptor.for(first_real_value, secret).for_decrypted_item
91
+ current_resource.secret secret
92
+ rescue Chef::EncryptedDataBagItem::DecryptionFailure
93
+ decrypt_error = $!
94
+ end
95
+ end
96
+
97
+ # If we couldn't figure out the secret, emit a warning (this isn't a fatal flaw unless we
98
+ # need to reuse one of the values from the data bag)
99
+ if !current_resource.secret
100
+ if decrypt_error
101
+ Chef::Log.warn "Existing data bag is encrypted, but could not decrypt: #{decrypt_error.message}."
102
+ else
103
+ Chef::Log.warn "Existing data bag is encrypted, but no secret was specified."
104
+ end
105
+ end
106
+ end
107
+ end
108
+ else
109
+
110
+ # There are no encryptable values, so pretend encryption is the same as desired
111
+
112
+ current_resource.encrypt new_resource.encrypt
113
+ current_resource.encryption_version new_resource.encryption_version
114
+ if new_resource.secret || new_resource.secret_path
115
+ current_resource.secret new_secret
116
+ end
117
+ end
118
+ end
119
+
120
+ def new_json
121
+ @new_json ||= begin
122
+ if new_encrypt
123
+ # Encrypt new stuff
124
+ result = encrypt(new_decrypted, new_secret, new_resource.encryption_version)
125
+ else
126
+ result = new_decrypted
127
+ end
128
+ result
129
+ end
130
+ end
131
+
132
+ def new_encrypt
133
+ new_resource.encrypt.nil? ? current_resource.encrypt : new_resource.encrypt
134
+ end
135
+
136
+ def new_secret
137
+ @new_secret ||= begin
138
+ if new_resource.secret
139
+ new_resource.secret
140
+ elsif new_resource.secret_path
141
+ Chef::EncryptedDataBagItem.load_secret(new_resource.secret_path)
142
+ elsif new_resource.encrypt.nil?
143
+ current_resource.secret
144
+ else
145
+ raise "Data bag item #{new_resource.name} has encryption on but no secret or secret_path is specified"
146
+ end
147
+ end
148
+ end
149
+
150
+ def decrypt(json, secret)
151
+ Chef::EncryptedDataBagItem.new(json, secret).to_hash
152
+ end
153
+
154
+ def encrypt(json, secret, version)
155
+ old_version = run_context.config[:data_bag_encrypt_version]
156
+ run_context.config[:data_bag_encrypt_version] = version
157
+ begin
158
+ Chef::EncryptedDataBagItem.encrypt_data_bag_item(json, secret)
159
+ ensure
160
+ run_context.config[:data_bag_encrypt_version] = old_version
161
+ end
162
+ end
163
+
164
+ # Get the desired (new) json pre-encryption, for comparison purposes
165
+ def new_decrypted
166
+ @new_decrypted ||= begin
167
+ if new_resource.complete
168
+ result = new_resource.raw_data || {}
169
+ else
170
+ result = current_decrypted.merge(new_resource.raw_data || {})
171
+ end
172
+ result['id'] = new_resource.id
173
+ apply_modifiers(new_resource.raw_data_modifiers, result)
174
+ end
175
+ end
176
+
177
+ # Get the current json decrypted, for comparison purposes
178
+ def current_decrypted
179
+ @current_decrypted ||= begin
180
+ if current_resource.secret
181
+ decrypt(current_resource.raw_data || { 'id' => new_resource.id }, current_resource.secret)
182
+ elsif current_resource.encrypt
183
+ raise "Could not decrypt current data bag item #{current_resource.name}"
184
+ else
185
+ current_resource.raw_data || { 'id' => new_resource.id }
186
+ end
187
+ end
188
+ end
189
+
190
+ # Figure out the differences between new and current
191
+ def calculate_differences
192
+ if new_encrypt
193
+ if current_resource.encrypt
194
+ # Both are encrypted, check if the encryption type is the same
195
+ description = ''
196
+ if new_secret != current_resource.secret
197
+ description << ' with new secret'
198
+ end
199
+ if new_resource.encryption_version != current_resource.encryption_version
200
+ description << " from v#{current_resource.encryption_version} to v#{new_resource.encryption_version} encryption"
201
+ end
202
+
203
+ if description != ''
204
+ # Encryption is different, we're reencrypting
205
+ differences = [ "re-encrypt#{description}"]
206
+ else
207
+ # Encryption is the same, we're just updating
208
+ differences = []
209
+ end
210
+ else
211
+ # New stuff should be encrypted, old is not. Encrypting.
212
+ differences = [ "encrypt with v#{new_resource.encryption_version} encryption" ]
213
+ end
214
+
215
+ # Get differences in the actual json
216
+ if current_resource.secret
217
+ json_differences(current_decrypted, new_decrypted, false, '', differences)
218
+ elsif current_resource.encrypt
219
+ # Encryption is different and we can't read the old values. Only allow the change
220
+ # if we're overwriting the data bag item
221
+ if !new_resource.complete
222
+ raise "Cannot encrypt #{new_resource.name} due to failure to decrypt existing resource. Set 'complete true' to overwrite or add the old secret as old_secret / old_secret_path."
223
+ end
224
+ differences = [ "overwrite data bag item (cannot decrypt old data bag item)"]
225
+ differences = (new_resource.raw_data.keys & current_resource.raw_data.keys).map { |key| "overwrite #{key}"}
226
+ differences += (new_resource.raw_data.keys - current_resource.raw_data.keys).map { |key| "add #{key}"}
227
+ differences += (current_resource.raw_data.keys - new_resource.raw_data.keys).map { |key| "remove #{key}" }
228
+ else
229
+ json_differences(current_decrypted, new_decrypted, false, '', differences)
230
+ end
231
+ else
232
+ if current_resource.encrypt
233
+ # New stuff should not be encrypted, old is. Decrypting.
234
+ differences = [ "decrypt data bag item to plaintext" ]
235
+ else
236
+ differences = []
237
+ end
238
+ json_differences(current_decrypted, new_decrypted, true, '', differences)
239
+ end
240
+ differences
241
+ end
242
+
243
+ #
244
+ # Helpers
245
+ #
246
+
247
+ def resource_class
248
+ Chef::Resource::ChefDataBagItem
249
+ end
250
+
251
+ def data_handler
252
+ Chef::ChefFS::DataHandler::DataBagItemDataHandler.new
253
+ end
254
+
255
+ def keys
256
+ {
257
+ 'id' => :id,
258
+ 'data_bag' => :data_bag,
259
+ 'raw_data' => :raw_data
260
+ }
261
+ end
262
+
263
+ def not_found_resource
264
+ resource = super
265
+ resource.data_bag new_resource.data_bag
266
+ resource
267
+ end
268
+
269
+ def fake_entry
270
+ FakeEntry.new("#{new_resource.id}.json", FakeEntry.new(new_resource.data_bag))
271
+ end
272
+
273
+ end
@@ -0,0 +1,78 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_environment'
3
+ require 'chef/chef_fs/data_handler/environment_data_handler'
4
+
5
+ class Chef::Provider::ChefEnvironment < Cheffish::ChefProviderBase
6
+
7
+ def whyrun_supported?
8
+ true
9
+ end
10
+
11
+ action :create do
12
+ differences = json_differences(current_json, new_json)
13
+
14
+ if current_resource_exists?
15
+ if differences.size > 0
16
+ description = [ "update environment #{new_resource.name} at #{rest.url}" ] + differences
17
+ converge_by description do
18
+ rest.put("environments/#{new_resource.name}", normalize_for_put(new_json))
19
+ end
20
+ end
21
+ else
22
+ description = [ "create environment #{new_resource.name} at #{rest.url}" ] + differences
23
+ converge_by description do
24
+ rest.post("environments", normalize_for_post(new_json))
25
+ end
26
+ end
27
+ end
28
+
29
+ action :delete do
30
+ if current_resource_exists?
31
+ converge_by "delete environment #{new_resource.name} at #{rest.url}" do
32
+ rest.delete("environments/#{new_resource.name}")
33
+ end
34
+ end
35
+ end
36
+
37
+ def load_current_resource
38
+ begin
39
+ @current_resource = json_to_resource(rest.get("environments/#{new_resource.name}"))
40
+ rescue Net::HTTPServerException => e
41
+ if e.response.code == "404"
42
+ @current_resource = not_found_resource
43
+ else
44
+ raise
45
+ end
46
+ end
47
+ end
48
+
49
+ def augment_new_json(json)
50
+ # Apply modifiers
51
+ json['default_attributes'] = apply_modifiers(new_resource.default_attribute_modifiers, json['default_attributes'])
52
+ json['override_attributes'] = apply_modifiers(new_resource.override_attribute_modifiers, json['override_attributes'])
53
+ json
54
+ end
55
+
56
+ #
57
+ # Helpers
58
+ #
59
+
60
+ def resource_class
61
+ Chef::Resource::ChefEnvironment
62
+ end
63
+
64
+ def data_handler
65
+ Chef::ChefFS::DataHandler::EnvironmentDataHandler.new
66
+ end
67
+
68
+ def keys
69
+ {
70
+ 'name' => :name,
71
+ 'description' => :description,
72
+ 'cookbook_versions' => :cookbook_versions,
73
+ 'default_attributes' => :default_attributes,
74
+ 'override_attributes' => :override_attributes
75
+ }
76
+ end
77
+
78
+ end