cheffish 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) 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_client.rb +44 -0
  6. data/lib/chef/provider/chef_data_bag.rb +50 -0
  7. data/lib/chef/provider/chef_data_bag_item.rb +273 -0
  8. data/lib/chef/provider/chef_environment.rb +78 -0
  9. data/lib/chef/provider/chef_node.rb +82 -0
  10. data/lib/chef/provider/chef_role.rb +79 -0
  11. data/lib/chef/provider/chef_user.rb +48 -0
  12. data/lib/chef/provider/private_key.rb +160 -0
  13. data/lib/chef/provider/public_key.rb +83 -0
  14. data/lib/chef/resource/chef_client.rb +44 -0
  15. data/lib/chef/resource/chef_data_bag.rb +18 -0
  16. data/lib/chef/resource/chef_data_bag_item.rb +114 -0
  17. data/lib/chef/resource/chef_environment.rb +71 -0
  18. data/lib/chef/resource/chef_node.rb +18 -0
  19. data/lib/chef/resource/chef_role.rb +104 -0
  20. data/lib/chef/resource/chef_user.rb +51 -0
  21. data/lib/chef/resource/in_parallel.rb +6 -0
  22. data/lib/chef/resource/private_key.rb +39 -0
  23. data/lib/chef/resource/public_key.rb +16 -0
  24. data/lib/cheffish.rb +245 -0
  25. data/lib/cheffish/actor_provider_base.rb +120 -0
  26. data/lib/cheffish/chef_provider_base.rb +222 -0
  27. data/lib/cheffish/cheffish_server_api.rb +21 -0
  28. data/lib/cheffish/inline_resource.rb +88 -0
  29. data/lib/cheffish/key_formatter.rb +93 -0
  30. data/lib/cheffish/recipe_dsl.rb +98 -0
  31. data/lib/cheffish/version.rb +4 -0
  32. data/spec/integration/chef_client_spec.rb +48 -0
  33. data/spec/integration/chef_node_spec.rb +75 -0
  34. data/spec/integration/chef_user_spec.rb +48 -0
  35. data/spec/integration/private_key_spec.rb +356 -0
  36. data/spec/integration/recipe_dsl_spec.rb +29 -0
  37. data/spec/support/key_support.rb +29 -0
  38. data/spec/support/spec_support.rb +148 -0
  39. metadata +124 -0
@@ -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
@@ -0,0 +1,82 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_node'
3
+ require 'chef/chef_fs/data_handler/node_data_handler'
4
+
5
+ class Chef::Provider::ChefNode < 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 node #{new_resource.name} at #{rest.url}" ] + differences
17
+ converge_by description do
18
+ rest.put("nodes/#{new_resource.name}", normalize_for_put(new_json))
19
+ end
20
+ end
21
+ else
22
+ description = [ "create node #{new_resource.name} at #{rest.url}" ] + differences
23
+ converge_by description do
24
+ rest.post("nodes", 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 node #{new_resource.name} at #{rest.url}" do
32
+ rest.delete("nodes/#{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("nodes/#{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'] = apply_modifiers(new_resource.default_modifiers, json['default'])
53
+ json['normal'] = apply_modifiers(new_resource.normal_modifiers, json['normal'])
54
+ json['override'] = apply_modifiers(new_resource.override_modifiers, json['override'])
55
+ json['automatic'] = apply_modifiers(new_resource.automatic_modifiers, json['automatic'])
56
+ json
57
+ end
58
+
59
+ #
60
+ # Helpers
61
+ #
62
+
63
+ def resource_class
64
+ Chef::Resource::ChefNode
65
+ end
66
+
67
+ def data_handler
68
+ Chef::ChefFS::DataHandler::NodeDataHandler.new
69
+ end
70
+
71
+ def keys
72
+ {
73
+ 'name' => :name,
74
+ 'chef_environment' => :chef_environment,
75
+ 'run_list' => :run_list,
76
+ 'default' => :default_attributes,
77
+ 'normal' => :normal_attributes,
78
+ 'override' => :override_attributes,
79
+ 'automatic' => :automatic_attributes
80
+ }
81
+ end
82
+ end
@@ -0,0 +1,79 @@
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
@@ -0,0 +1,48 @@
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 resource_class
29
+ Chef::Resource::ChefUser
30
+ end
31
+
32
+ def data_handler
33
+ Chef::ChefFS::DataHandler::UserDataHandler.new
34
+ end
35
+
36
+ def keys
37
+ {
38
+ 'name' => :name,
39
+ 'admin' => :admin,
40
+ 'email' => :email,
41
+ 'password' => :password,
42
+ 'external_authentication_uid' => :external_authentication_uid,
43
+ 'recovery_authentication_enabled' => :recovery_authentication_enabled,
44
+ 'public_key' => :source_key
45
+ }
46
+ end
47
+
48
+ end
@@ -0,0 +1,160 @@
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)
9
+ end
10
+
11
+ action :regenerate do
12
+ create_key(true)
13
+ end
14
+
15
+ action :delete do
16
+ if Array(current_resource.action) == [ :create ]
17
+ converge_by "delete private key #{new_resource.path}" do
18
+ ::File.unlink(new_resource.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)
30
+ final_private_key = nil
31
+ if new_source_key
32
+ #
33
+ # Create private key from source
34
+ #
35
+ desired_output = encode_private_key(new_source_key)
36
+ if Array(current_resource.action) == [ :delete ] || desired_output != IO.read(new_resource.path)
37
+ converge_by "reformat key at #{new_resource.source_key_path} to #{new_resource.format} private key #{new_resource.path} (#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})" do
38
+ IO.write(new_resource.path, desired_output)
39
+ end
40
+ end
41
+
42
+ final_private_key = new_source_key
43
+
44
+ else
45
+ #
46
+ # Create private key file
47
+ #
48
+ if Array(current_resource.action) == [ :delete ] || regenerate ||
49
+ (new_resource.regenerate_if_different &&
50
+ (current_resource.size != new_resource.size ||
51
+ current_resource.type != new_resource.type))
52
+ action = (current_resource.path == :none || ::File.exists?(current_resource.path)) ? "overwrite" : "create"
53
+ converge_by "#{action} #{new_resource.type} private key #{new_resource.path} (#{new_resource.size} bits#{new_resource.pass_phrase ? ", #{new_resource.cipher} password" : ""})" do
54
+ case new_resource.type
55
+ when :rsa
56
+ if new_resource.exponent
57
+ final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size, new_resource.exponent)
58
+ else
59
+ final_private_key = OpenSSL::PKey::RSA.generate(new_resource.size)
60
+ end
61
+ when :dsa
62
+ final_private_key = OpenSSL::PKey::DSA.generate(new_resource.size)
63
+ end
64
+
65
+ if new_resource.path != :none
66
+ write_private_key(final_private_key)
67
+ end
68
+ end
69
+ else
70
+ # Warn if existing key has different characteristics than expected
71
+ if current_resource.size != new_resource.size
72
+ 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.")
73
+ elsif current_resource.type != new_resource.type
74
+ 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.")
75
+ end
76
+
77
+ final_private_key = current_private_key
78
+
79
+ if current_resource.format != new_resource.format
80
+ converge_by "change format of #{new_resource.type} private key #{new_resource.path} from #{current_resource.format} to #{new_resource.format}" do
81
+ write_private_key(current_private_key)
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ if new_resource.public_key_path
88
+ public_key_path = new_resource.public_key_path
89
+ public_key_format = new_resource.public_key_format
90
+ Cheffish.inline_resource(self) do
91
+ public_key public_key_path do
92
+ source_key final_private_key
93
+ format public_key_format
94
+ end
95
+ end
96
+ end
97
+
98
+ if new_resource.after
99
+ new_resource.after.call(new_resource, final_private_key)
100
+ end
101
+ end
102
+
103
+ def encode_private_key(key)
104
+ key_format = {}
105
+ key_format[:format] = new_resource.format if new_resource.format
106
+ key_format[:pass_phrase] = new_resource.pass_phrase if new_resource.pass_phrase
107
+ key_format[:cipher] = new_resource.cipher if new_resource.cipher
108
+ Cheffish::KeyFormatter.encode(key, key_format)
109
+ end
110
+
111
+ def write_private_key(key)
112
+ IO.write(new_resource.path, encode_private_key(key))
113
+ # TODO permissions on file?
114
+ end
115
+
116
+ def new_source_key
117
+ @new_source_key ||= begin
118
+ if new_resource.source_key.is_a?(String)
119
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(new_resource.source_key, new_resource.source_key_pass_phrase)
120
+ source_key
121
+ elsif new_resource.source_key
122
+ new_resource.source_key
123
+ elsif new_resource.source_key_path
124
+ 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)
125
+ source_key
126
+ else
127
+ nil
128
+ end
129
+ end
130
+ end
131
+
132
+ attr_reader :current_private_key
133
+
134
+ def load_current_resource
135
+ resource = Chef::Resource::PrivateKey.new(new_resource.name)
136
+
137
+ if new_resource.path != :none && ::File.exist?(new_resource.path)
138
+ resource.path new_resource.path
139
+
140
+ begin
141
+ key, key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.path), new_resource.pass_phrase, new_resource.path)
142
+ if key
143
+ @current_private_key = key
144
+ resource.format key_format[:format]
145
+ resource.type key_format[:type]
146
+ resource.size key_format[:size]
147
+ resource.exponent key_format[:exponent]
148
+ resource.pass_phrase key_format[:pass_phrase]
149
+ resource.cipher key_format[:cipher]
150
+ end
151
+ rescue
152
+ # If there's an error reading, we assume format and type are wrong and don't futz with them
153
+ end
154
+ else
155
+ resource.action :delete
156
+ end
157
+
158
+ @current_resource = resource
159
+ end
160
+ end