cheffish 1.3.1 → 1.4.0

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 -117
  4. data/Rakefile +23 -23
  5. data/lib/chef/provider/chef_acl.rb +439 -434
  6. data/lib/chef/provider/chef_client.rb +53 -48
  7. data/lib/chef/provider/chef_container.rb +55 -50
  8. data/lib/chef/provider/chef_data_bag.rb +55 -50
  9. data/lib/chef/provider/chef_data_bag_item.rb +278 -273
  10. data/lib/chef/provider/chef_environment.rb +83 -78
  11. data/lib/chef/provider/chef_group.rb +83 -78
  12. data/lib/chef/provider/chef_mirror.rb +169 -164
  13. data/lib/chef/provider/chef_node.rb +87 -82
  14. data/lib/chef/provider/chef_organization.rb +155 -150
  15. data/lib/chef/provider/chef_resolved_cookbooks.rb +46 -41
  16. data/lib/chef/provider/chef_role.rb +84 -79
  17. data/lib/chef/provider/chef_user.rb +59 -54
  18. data/lib/chef/provider/private_key.rb +225 -220
  19. data/lib/chef/provider/public_key.rb +88 -82
  20. data/lib/chef/resource/chef_acl.rb +69 -65
  21. data/lib/chef/resource/chef_client.rb +48 -44
  22. data/lib/chef/resource/chef_container.rb +22 -18
  23. data/lib/chef/resource/chef_data_bag.rb +22 -18
  24. data/lib/chef/resource/chef_data_bag_item.rb +121 -114
  25. data/lib/chef/resource/chef_environment.rb +77 -71
  26. data/lib/chef/resource/chef_group.rb +53 -49
  27. data/lib/chef/resource/chef_mirror.rb +52 -48
  28. data/lib/chef/resource/chef_node.rb +22 -18
  29. data/lib/chef/resource/chef_organization.rb +69 -64
  30. data/lib/chef/resource/chef_resolved_cookbooks.rb +35 -31
  31. data/lib/chef/resource/chef_role.rb +110 -104
  32. data/lib/chef/resource/chef_user.rb +56 -52
  33. data/lib/chef/resource/private_key.rb +48 -44
  34. data/lib/chef/resource/public_key.rb +25 -21
  35. data/lib/cheffish.rb +235 -233
  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 -155
  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 -47
  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 -0
  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 -0
  76. metadata +8 -5
@@ -1,41 +1,46 @@
1
- require 'chef/provider/lwrp_base'
2
- require 'chef_zero'
3
-
4
- class Chef::Provider::ChefResolvedCookbooks < Chef::Provider::LWRPBase
5
-
6
- action :resolve do
7
- new_resource.cookbooks_from.each do |path|
8
- ::Dir.entries(path).each do |name|
9
- if ::File.directory?(::File.join(path, name)) && name != '.' && name != '..'
10
- new_resource.berksfile.cookbook name, :path => ::File.join(path, name)
11
- end
12
- end
13
- end
14
-
15
- new_resource.berksfile.install
16
-
17
- # Ridley really really wants a key :/
18
- if new_resource.chef_server[:options][:signing_key_filename]
19
- new_resource.berksfile.upload(
20
- :server_url => new_resource.chef_server[:chef_server_url],
21
- :client_name => new_resource.chef_server[:options][:client_name],
22
- :client_key => new_resource.chef_server[:options][:signing_key_filename])
23
- else
24
- file = Tempfile.new('privatekey')
25
- begin
26
- file.write(ChefZero::PRIVATE_KEY)
27
- file.close
28
-
29
- new_resource.berksfile.upload(
30
- :server_url => new_resource.chef_server[:chef_server_url],
31
- :client_name => new_resource.chef_server[:options][:client_name] || 'me',
32
- :client_key => file.path)
33
-
34
- ensure
35
- file.close
36
- file.unlink
37
- end
38
- end
39
- end
40
-
41
- end
1
+ require 'chef/provider/lwrp_base'
2
+ require 'chef_zero'
3
+
4
+ class Chef
5
+ class Provider
6
+ class ChefResolvedCookbooks < Chef::Provider::LWRPBase
7
+ provides :chef_resolved_cookbooks
8
+
9
+ action :resolve do
10
+ new_resource.cookbooks_from.each do |path|
11
+ ::Dir.entries(path).each do |name|
12
+ if ::File.directory?(::File.join(path, name)) && name != '.' && name != '..'
13
+ new_resource.berksfile.cookbook name, :path => ::File.join(path, name)
14
+ end
15
+ end
16
+ end
17
+
18
+ new_resource.berksfile.install
19
+
20
+ # Ridley really really wants a key :/
21
+ if new_resource.chef_server[:options][:signing_key_filename]
22
+ new_resource.berksfile.upload(
23
+ :server_url => new_resource.chef_server[:chef_server_url],
24
+ :client_name => new_resource.chef_server[:options][:client_name],
25
+ :client_key => new_resource.chef_server[:options][:signing_key_filename])
26
+ else
27
+ file = Tempfile.new('privatekey')
28
+ begin
29
+ file.write(ChefZero::PRIVATE_KEY)
30
+ file.close
31
+
32
+ new_resource.berksfile.upload(
33
+ :server_url => new_resource.chef_server[:chef_server_url],
34
+ :client_name => new_resource.chef_server[:options][:client_name] || 'me',
35
+ :client_key => file.path)
36
+
37
+ ensure
38
+ file.close
39
+ file.unlink
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -1,79 +1,84 @@
1
- require 'cheffish/chef_provider_base'
2
- require 'chef/resource/chef_role'
3
- require 'chef/chef_fs/data_handler/role_data_handler'
4
-
5
- class Chef::Provider::ChefRole < 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 role #{new_resource.name} at #{rest.url}" ] + differences
17
- converge_by description do
18
- rest.put("roles/#{new_resource.name}", normalize_for_put(new_json))
19
- end
20
- end
21
- else
22
- description = [ "create role #{new_resource.name} at #{rest.url}" ] + differences
23
- converge_by description do
24
- rest.post("roles", 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 role #{new_resource.name} at #{rest.url}" do
32
- rest.delete("roles/#{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("roles/#{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['run_list'] = apply_run_list_modifiers(new_resource.run_list_modifiers, new_resource.run_list_removers, json['run_list'])
52
- json['default_attributes'] = apply_modifiers(new_resource.default_attribute_modifiers, json['default_attributes'])
53
- json['override_attributes'] = apply_modifiers(new_resource.override_attribute_modifiers, json['override_attributes'])
54
- json
55
- end
56
-
57
- #
58
- # Helpers
59
- #
60
-
61
- def resource_class
62
- Chef::Resource::ChefRole
63
- end
64
-
65
- def data_handler
66
- Chef::ChefFS::DataHandler::RoleDataHandler.new
67
- end
68
-
69
- def keys
70
- {
71
- 'name' => :name,
72
- 'description' => :description,
73
- 'run_list' => :run_list,
74
- 'env_run_lists' => :env_run_lists,
75
- 'default_attributes' => :default_attributes,
76
- 'override_attributes' => :override_attributes
77
- }
78
- end
79
- end
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_role'
3
+ require 'chef/chef_fs/data_handler/role_data_handler'
4
+
5
+ class Chef
6
+ class Provider
7
+ class ChefRole < Cheffish::ChefProviderBase
8
+ provides :chef_role
9
+
10
+ def whyrun_supported?
11
+ true
12
+ end
13
+
14
+ action :create do
15
+ differences = json_differences(current_json, new_json)
16
+
17
+ if current_resource_exists?
18
+ if differences.size > 0
19
+ description = [ "update role #{new_resource.name} at #{rest.url}" ] + differences
20
+ converge_by description do
21
+ rest.put("roles/#{new_resource.name}", normalize_for_put(new_json))
22
+ end
23
+ end
24
+ else
25
+ description = [ "create role #{new_resource.name} at #{rest.url}" ] + differences
26
+ converge_by description do
27
+ rest.post("roles", normalize_for_post(new_json))
28
+ end
29
+ end
30
+ end
31
+
32
+ action :delete do
33
+ if current_resource_exists?
34
+ converge_by "delete role #{new_resource.name} at #{rest.url}" do
35
+ rest.delete("roles/#{new_resource.name}")
36
+ end
37
+ end
38
+ end
39
+
40
+ def load_current_resource
41
+ begin
42
+ @current_resource = json_to_resource(rest.get("roles/#{new_resource.name}"))
43
+ rescue Net::HTTPServerException => e
44
+ if e.response.code == "404"
45
+ @current_resource = not_found_resource
46
+ else
47
+ raise
48
+ end
49
+ end
50
+ end
51
+
52
+ def augment_new_json(json)
53
+ # Apply modifiers
54
+ json['run_list'] = apply_run_list_modifiers(new_resource.run_list_modifiers, new_resource.run_list_removers, json['run_list'])
55
+ json['default_attributes'] = apply_modifiers(new_resource.default_attribute_modifiers, json['default_attributes'])
56
+ json['override_attributes'] = apply_modifiers(new_resource.override_attribute_modifiers, json['override_attributes'])
57
+ json
58
+ end
59
+
60
+ #
61
+ # Helpers
62
+ #
63
+
64
+ def resource_class
65
+ Chef::Resource::ChefRole
66
+ end
67
+
68
+ def data_handler
69
+ Chef::ChefFS::DataHandler::RoleDataHandler.new
70
+ end
71
+
72
+ def keys
73
+ {
74
+ 'name' => :name,
75
+ 'description' => :description,
76
+ 'run_list' => :run_list,
77
+ 'env_run_lists' => :env_run_lists,
78
+ 'default_attributes' => :default_attributes,
79
+ 'override_attributes' => :override_attributes
80
+ }
81
+ end
82
+ end
83
+ end
84
+ end
@@ -1,54 +1,59 @@
1
- require 'cheffish/actor_provider_base'
2
- require 'chef/resource/chef_user'
3
- require 'chef/chef_fs/data_handler/user_data_handler'
4
-
5
- class Chef::Provider::ChefUser < Cheffish::ActorProviderBase
6
-
7
- def whyrun_supported?
8
- true
9
- end
10
-
11
- action :create do
12
- create_actor
13
- end
14
-
15
- action :delete do
16
- delete_actor
17
- end
18
-
19
- #
20
- # Helpers
21
- #
22
- # Gives us new_json, current_json, not_found_json, etc.
23
-
24
- def actor_type
25
- 'user'
26
- end
27
-
28
- def actor_path
29
- "#{rest.root_url}/users"
30
- end
31
-
32
- def resource_class
33
- Chef::Resource::ChefUser
34
- end
35
-
36
- def data_handler
37
- Chef::ChefFS::DataHandler::UserDataHandler.new
38
- end
39
-
40
- def keys
41
- {
42
- 'name' => :name,
43
- 'username' => :name,
44
- 'display_name' => :display_name,
45
- 'admin' => :admin,
46
- 'email' => :email,
47
- 'password' => :password,
48
- 'external_authentication_uid' => :external_authentication_uid,
49
- 'recovery_authentication_enabled' => :recovery_authentication_enabled,
50
- 'public_key' => :source_key
51
- }
52
- end
53
-
54
- end
1
+ require 'cheffish/actor_provider_base'
2
+ require 'chef/resource/chef_user'
3
+ require 'chef/chef_fs/data_handler/user_data_handler'
4
+
5
+ class Chef
6
+ class Provider
7
+ class ChefUser < Cheffish::ActorProviderBase
8
+ provides :chef_user
9
+
10
+ def whyrun_supported?
11
+ true
12
+ end
13
+
14
+ action :create do
15
+ create_actor
16
+ end
17
+
18
+ action :delete do
19
+ delete_actor
20
+ end
21
+
22
+ #
23
+ # Helpers
24
+ #
25
+ # Gives us new_json, current_json, not_found_json, etc.
26
+
27
+ def actor_type
28
+ 'user'
29
+ end
30
+
31
+ def actor_path
32
+ "#{rest.root_url}/users"
33
+ end
34
+
35
+ def resource_class
36
+ Chef::Resource::ChefUser
37
+ end
38
+
39
+ def data_handler
40
+ Chef::ChefFS::DataHandler::UserDataHandler.new
41
+ end
42
+
43
+ def keys
44
+ {
45
+ 'name' => :name,
46
+ 'username' => :name,
47
+ 'display_name' => :display_name,
48
+ 'admin' => :admin,
49
+ 'email' => :email,
50
+ 'password' => :password,
51
+ 'external_authentication_uid' => :external_authentication_uid,
52
+ 'recovery_authentication_enabled' => :recovery_authentication_enabled,
53
+ 'public_key' => :source_key
54
+ }
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -1,220 +1,225 @@
1
- require 'chef/provider/lwrp_base'
2
- require 'openssl'
3
- require 'cheffish/key_formatter'
4
-
5
- class Chef::Provider::PrivateKey < Chef::Provider::LWRPBase
6
-
7
- action :create do
8
- create_key(false, :create)
9
- end
10
-
11
- action :regenerate do
12
- create_key(true, :regenerate)
13
- end
14
-
15
- action :delete do
16
- if current_resource.path
17
- converge_by "delete private key #{new_path}" do
18
- ::File.unlink(new_path)
19
- end
20
- end
21
- end
22
-
23
- use_inline_resources
24
-
25
- def whyrun_supported?
26
- true
27
- end
28
-
29
- def create_key(regenerate, action)
30
- if @should_create_directory
31
- Cheffish.inline_resource(self, action) do
32
- directory run_context.config[:private_key_write_path]
33
- end
34
- end
35
-
36
- final_private_key = nil
37
- if new_source_key
38
- #
39
- # Create private key from source
40
- #
41
- desired_output = encode_private_key(new_source_key)
42
- if current_resource.path == :none || desired_output != IO.read(new_path)
43
- converge_by "reformat key at #{new_resource.source_key_path} to #{new_resource.format} private key #{new_path} (#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})" do
44
- IO.write(new_path, desired_output)
45
- end
46
- end
47
-
48
- final_private_key = new_source_key
49
-
50
- else
51
- #
52
- # Generate a new key
53
- #
54
- if current_resource.action == [ :delete ] || regenerate ||
55
- (new_resource.regenerate_if_different &&
56
- (!current_private_key ||
57
- current_resource.size != new_resource.size ||
58
- current_resource.type != new_resource.type))
59
-
60
- case new_resource.type
61
- when :rsa
62
- if new_resource.exponent
63
- final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size, new_resource.exponent)
64
- else
65
- final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size)
66
- end
67
- when :dsa
68
- final_private_key = OpenSSL::PKey::DSA.generate(new_resource.size)
69
- end
70
-
71
- generated_key = true
72
- elsif !current_private_key
73
- raise "Could not read private key from #{current_resource.path}: missing pass phrase?"
74
- else
75
- final_private_key = current_private_key
76
- generated_key = false
77
- end
78
-
79
- if generated_key
80
- generated_description = " (#{new_resource.size} bits#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})"
81
-
82
- if new_path != :none
83
- action = current_resource.path == :none ? 'create' : 'overwrite'
84
- converge_by "#{action} #{new_resource.type} private key #{new_path}#{generated_description}" do
85
- write_private_key(final_private_key)
86
- end
87
- else
88
- converge_by "generate private key#{generated_description}" do
89
- end
90
- end
91
- else
92
- # Warn if existing key has different characteristics than expected
93
- if current_resource.size != new_resource.size
94
- Chef::Log.warn("Mismatched key size! #{current_resource.path} is #{current_resource.size} bytes, desired is #{new_resource.size} bytes. Use action :regenerate to force key regeneration.")
95
- elsif current_resource.type != new_resource.type
96
- Chef::Log.warn("Mismatched key type! #{current_resource.path} is #{current_resource.type}, desired is #{new_resource.type} bytes. Use action :regenerate to force key regeneration.")
97
- end
98
-
99
- if current_resource.format != new_resource.format
100
- converge_by "change format of #{new_resource.type} private key #{new_path} from #{current_resource.format} to #{new_resource.format}" do
101
- write_private_key(current_private_key)
102
- end
103
- elsif (@current_file_mode & 0077) != 0
104
- new_mode = @current_file_mode & 07700
105
- converge_by "change mode of private key #{new_path} to #{new_mode.to_s(8)}" do
106
- ::File.chmod(new_mode, new_path)
107
- end
108
- end
109
- end
110
- end
111
-
112
- if new_resource.public_key_path
113
- public_key_path = new_resource.public_key_path
114
- public_key_format = new_resource.public_key_format
115
- Cheffish.inline_resource(self, action) do
116
- public_key public_key_path do
117
- source_key final_private_key
118
- format public_key_format
119
- end
120
- end
121
- end
122
-
123
- if new_resource.after
124
- new_resource.after.call(new_resource, final_private_key)
125
- end
126
- end
127
-
128
- def encode_private_key(key)
129
- key_format = {}
130
- key_format[:format] = new_resource.format if new_resource.format
131
- key_format[:pass_phrase] = new_resource.pass_phrase if new_resource.pass_phrase
132
- key_format[:cipher] = new_resource.cipher if new_resource.cipher
133
- Cheffish::KeyFormatter.encode(key, key_format)
134
- end
135
-
136
- def write_private_key(key)
137
- ::File.open(new_path, 'w') do |file|
138
- file.chmod(0600)
139
- file.write(encode_private_key(key))
140
- end
141
- end
142
-
143
- def new_source_key
144
- @new_source_key ||= begin
145
- if new_resource.source_key.is_a?(String)
146
- source_key, source_key_format = Cheffish::KeyFormatter.decode(new_resource.source_key, new_resource.source_key_pass_phrase)
147
- source_key
148
- elsif new_resource.source_key
149
- new_resource.source_key
150
- elsif new_resource.source_key_path
151
- source_key, source_key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.source_key_path), new_resource.source_key_pass_phrase, new_resource.source_key_path)
152
- source_key
153
- else
154
- nil
155
- end
156
- end
157
- end
158
-
159
- attr_reader :current_private_key
160
-
161
- def new_path
162
- new_key_with_path[1]
163
- end
164
-
165
- def new_key_with_path
166
- path = new_resource.path
167
- if path.is_a?(Symbol)
168
- return [ nil, path ]
169
- elsif Pathname.new(path).relative?
170
- private_key, private_key_path = Cheffish.get_private_key_with_path(path, run_context.config)
171
- if private_key
172
- return [ private_key, (private_key_path || :none) ]
173
- elsif run_context.config[:private_key_write_path]
174
- @should_create_directory = true
175
- path = ::File.join(run_context.config[:private_key_write_path], path)
176
- return [ nil, path ]
177
- else
178
- raise "Could not find key #{path} and Chef::Config.private_key_write_path is not set."
179
- end
180
- elsif ::File.exist?(path)
181
- return [ IO.read(path), path ]
182
- else
183
- return [ nil, path ]
184
- end
185
- end
186
-
187
- def load_current_resource
188
- resource = Chef::Resource::PrivateKey.new(new_resource.name, run_context)
189
-
190
- new_key, new_path = new_key_with_path
191
- if new_path != :none && ::File.exist?(new_path)
192
- resource.path new_path
193
- @current_file_mode = ::File.stat(new_path).mode
194
- else
195
- resource.path :none
196
- end
197
-
198
- if new_key
199
- begin
200
- key, key_format = Cheffish::KeyFormatter.decode(new_key, new_resource.pass_phrase, new_path)
201
- if key
202
- @current_private_key = key
203
- resource.format key_format[:format]
204
- resource.type key_format[:type]
205
- resource.size key_format[:size]
206
- resource.exponent key_format[:exponent]
207
- resource.pass_phrase key_format[:pass_phrase]
208
- resource.cipher key_format[:cipher]
209
- end
210
- rescue
211
- # If there's an error reading, we assume format and type are wrong and don't futz with them
212
- Chef::Log.warn("Error reading #{new_path}: #{$!}")
213
- end
214
- else
215
- resource.action :delete
216
- end
217
-
218
- @current_resource = resource
219
- end
220
- end
1
+ require 'chef/provider/lwrp_base'
2
+ require 'openssl'
3
+ require 'cheffish/key_formatter'
4
+
5
+ class Chef
6
+ class Provider
7
+ class PrivateKey < Chef::Provider::LWRPBase
8
+ provides :private_key
9
+
10
+ action :create do
11
+ create_key(false, :create)
12
+ end
13
+
14
+ action :regenerate do
15
+ create_key(true, :regenerate)
16
+ end
17
+
18
+ action :delete do
19
+ if current_resource.path
20
+ converge_by "delete private key #{new_path}" do
21
+ ::File.unlink(new_path)
22
+ end
23
+ end
24
+ end
25
+
26
+ use_inline_resources
27
+
28
+ def whyrun_supported?
29
+ true
30
+ end
31
+
32
+ def create_key(regenerate, action)
33
+ if @should_create_directory
34
+ Cheffish.inline_resource(self, action) do
35
+ directory run_context.config[:private_key_write_path]
36
+ end
37
+ end
38
+
39
+ final_private_key = nil
40
+ if new_source_key
41
+ #
42
+ # Create private key from source
43
+ #
44
+ desired_output = encode_private_key(new_source_key)
45
+ if current_resource.path == :none || desired_output != IO.read(new_path)
46
+ converge_by "reformat key at #{new_resource.source_key_path} to #{new_resource.format} private key #{new_path} (#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})" do
47
+ IO.write(new_path, desired_output)
48
+ end
49
+ end
50
+
51
+ final_private_key = new_source_key
52
+
53
+ else
54
+ #
55
+ # Generate a new key
56
+ #
57
+ if current_resource.action == [ :delete ] || regenerate ||
58
+ (new_resource.regenerate_if_different &&
59
+ (!current_private_key ||
60
+ current_resource.size != new_resource.size ||
61
+ current_resource.type != new_resource.type))
62
+
63
+ case new_resource.type
64
+ when :rsa
65
+ if new_resource.exponent
66
+ final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size, new_resource.exponent)
67
+ else
68
+ final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size)
69
+ end
70
+ when :dsa
71
+ final_private_key = OpenSSL::PKey::DSA.generate(new_resource.size)
72
+ end
73
+
74
+ generated_key = true
75
+ elsif !current_private_key
76
+ raise "Could not read private key from #{current_resource.path}: missing pass phrase?"
77
+ else
78
+ final_private_key = current_private_key
79
+ generated_key = false
80
+ end
81
+
82
+ if generated_key
83
+ generated_description = " (#{new_resource.size} bits#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})"
84
+
85
+ if new_path != :none
86
+ action = current_resource.path == :none ? 'create' : 'overwrite'
87
+ converge_by "#{action} #{new_resource.type} private key #{new_path}#{generated_description}" do
88
+ write_private_key(final_private_key)
89
+ end
90
+ else
91
+ converge_by "generate private key#{generated_description}" do
92
+ end
93
+ end
94
+ else
95
+ # Warn if existing key has different characteristics than expected
96
+ if current_resource.size != new_resource.size
97
+ Chef::Log.warn("Mismatched key size! #{current_resource.path} is #{current_resource.size} bytes, desired is #{new_resource.size} bytes. Use action :regenerate to force key regeneration.")
98
+ elsif current_resource.type != new_resource.type
99
+ Chef::Log.warn("Mismatched key type! #{current_resource.path} is #{current_resource.type}, desired is #{new_resource.type} bytes. Use action :regenerate to force key regeneration.")
100
+ end
101
+
102
+ if current_resource.format != new_resource.format
103
+ converge_by "change format of #{new_resource.type} private key #{new_path} from #{current_resource.format} to #{new_resource.format}" do
104
+ write_private_key(current_private_key)
105
+ end
106
+ elsif (@current_file_mode & 0077) != 0
107
+ new_mode = @current_file_mode & 07700
108
+ converge_by "change mode of private key #{new_path} to #{new_mode.to_s(8)}" do
109
+ ::File.chmod(new_mode, new_path)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ if new_resource.public_key_path
116
+ public_key_path = new_resource.public_key_path
117
+ public_key_format = new_resource.public_key_format
118
+ Cheffish.inline_resource(self, action) do
119
+ public_key public_key_path do
120
+ source_key final_private_key
121
+ format public_key_format
122
+ end
123
+ end
124
+ end
125
+
126
+ if new_resource.after
127
+ new_resource.after.call(new_resource, final_private_key)
128
+ end
129
+ end
130
+
131
+ def encode_private_key(key)
132
+ key_format = {}
133
+ key_format[:format] = new_resource.format if new_resource.format
134
+ key_format[:pass_phrase] = new_resource.pass_phrase if new_resource.pass_phrase
135
+ key_format[:cipher] = new_resource.cipher if new_resource.cipher
136
+ Cheffish::KeyFormatter.encode(key, key_format)
137
+ end
138
+
139
+ def write_private_key(key)
140
+ ::File.open(new_path, 'w') do |file|
141
+ file.chmod(0600)
142
+ file.write(encode_private_key(key))
143
+ end
144
+ end
145
+
146
+ def new_source_key
147
+ @new_source_key ||= begin
148
+ if new_resource.source_key.is_a?(String)
149
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(new_resource.source_key, new_resource.source_key_pass_phrase)
150
+ source_key
151
+ elsif new_resource.source_key
152
+ new_resource.source_key
153
+ elsif new_resource.source_key_path
154
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.source_key_path), new_resource.source_key_pass_phrase, new_resource.source_key_path)
155
+ source_key
156
+ else
157
+ nil
158
+ end
159
+ end
160
+ end
161
+
162
+ attr_reader :current_private_key
163
+
164
+ def new_path
165
+ new_key_with_path[1]
166
+ end
167
+
168
+ def new_key_with_path
169
+ path = new_resource.path
170
+ if path.is_a?(Symbol)
171
+ return [ nil, path ]
172
+ elsif Pathname.new(path).relative?
173
+ private_key, private_key_path = Cheffish.get_private_key_with_path(path, run_context.config)
174
+ if private_key
175
+ return [ private_key, (private_key_path || :none) ]
176
+ elsif run_context.config[:private_key_write_path]
177
+ @should_create_directory = true
178
+ path = ::File.join(run_context.config[:private_key_write_path], path)
179
+ return [ nil, path ]
180
+ else
181
+ raise "Could not find key #{path} and Chef::Config.private_key_write_path is not set."
182
+ end
183
+ elsif ::File.exist?(path)
184
+ return [ IO.read(path), path ]
185
+ else
186
+ return [ nil, path ]
187
+ end
188
+ end
189
+
190
+ def load_current_resource
191
+ resource = Chef::Resource::PrivateKey.new(new_resource.name, run_context)
192
+
193
+ new_key, new_path = new_key_with_path
194
+ if new_path != :none && ::File.exist?(new_path)
195
+ resource.path new_path
196
+ @current_file_mode = ::File.stat(new_path).mode
197
+ else
198
+ resource.path :none
199
+ end
200
+
201
+ if new_key
202
+ begin
203
+ key, key_format = Cheffish::KeyFormatter.decode(new_key, new_resource.pass_phrase, new_path)
204
+ if key
205
+ @current_private_key = key
206
+ resource.format key_format[:format]
207
+ resource.type key_format[:type]
208
+ resource.size key_format[:size]
209
+ resource.exponent key_format[:exponent]
210
+ resource.pass_phrase key_format[:pass_phrase]
211
+ resource.cipher key_format[:cipher]
212
+ end
213
+ rescue
214
+ # If there's an error reading, we assume format and type are wrong and don't futz with them
215
+ Chef::Log.warn("Error reading #{new_path}: #{$!}")
216
+ end
217
+ else
218
+ resource.action :delete
219
+ end
220
+
221
+ @current_resource = resource
222
+ end
223
+ end
224
+ end
225
+ end