cheffish 1.4.0 → 1.4.1

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