clc-cheffish 0.8.clc

Sign up to get free protection for your applications and to get access to all the features.
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