cheffish 0.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 (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