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,78 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_group'
3
+ require 'chef/chef_fs/data_handler/group_data_handler'
4
+
5
+ class Chef::Provider::ChefGroup < 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 group #{new_resource.name} at #{rest.url}" ] + differences
17
+ converge_by description do
18
+ rest.put("groups/#{new_resource.name}", normalize_for_put(new_json))
19
+ end
20
+ end
21
+ else
22
+ description = [ "create group #{new_resource.name} at #{rest.url}" ] + differences
23
+ converge_by description do
24
+ rest.post("groups", 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 group #{new_resource.name} at #{rest.url}" do
32
+ rest.delete("groups/#{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("groups/#{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['users'] |= new_resource.users
52
+ json['clients'] |= new_resource.clients
53
+ json['groups'] |= new_resource.groups
54
+ json['users'] -= new_resource.remove_users
55
+ json['clients'] -= new_resource.remove_clients
56
+ json['groups'] -= new_resource.remove_groups
57
+ json
58
+ end
59
+
60
+ #
61
+ # Helpers
62
+ #
63
+
64
+ def resource_class
65
+ Chef::Resource::ChefGroup
66
+ end
67
+
68
+ def data_handler
69
+ Chef::ChefFS::DataHandler::GroupDataHandler.new
70
+ end
71
+
72
+ def keys
73
+ {
74
+ 'name' => :name,
75
+ 'groupname' => :name
76
+ }
77
+ end
78
+ end
@@ -0,0 +1,138 @@
1
+ require 'chef/provider/lwrp_base'
2
+ require 'chef/chef_fs/file_pattern'
3
+ require 'chef/chef_fs/file_system'
4
+ require 'chef/chef_fs/parallelizer'
5
+ require 'chef/chef_fs/file_system/chef_server_root_dir'
6
+ require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir'
7
+
8
+ class Chef::Provider::ChefMirror < Chef::Provider::LWRPBase
9
+
10
+ def whyrun_supported?
11
+ true
12
+ end
13
+
14
+ action :upload do
15
+ copy_to(local_fs, remote_fs)
16
+ end
17
+
18
+ action :download do
19
+ copy_to(remote_fs, local_fs)
20
+ end
21
+
22
+ def copy_to(src_root, dest_root)
23
+ if new_resource.concurrency && new_resource.concurrency <= 0
24
+ raise "chef_mirror.concurrency must be above 0! Was set to #{new_resource.concurrency}"
25
+ end
26
+ # Honor concurrency
27
+ Chef::ChefFS::Parallelizer.threads = (new_resource.concurrency || 10) - 1
28
+
29
+ # We don't let the user pass absolute paths; we want to reserve those for
30
+ # multi-org support (/organizations/foo).
31
+ if new_resource.path[0] == '/'
32
+ raise "Absolute paths in chef_mirror not yet supported."
33
+ end
34
+ # Copy!
35
+ path = Chef::ChefFS::FilePattern.new("/#{new_resource.path}")
36
+ ui = CopyListener.new(self)
37
+ error = Chef::ChefFS::FileSystem.copy_to(path, src_root, dest_root, nil, options, ui, proc { |p| p.path })
38
+
39
+ if error
40
+ raise "Errors while copying:#{ui.errors.map { |e| "#{e}\n" }.join('')}"
41
+ end
42
+ end
43
+
44
+ def local_fs
45
+ # If chef_repo_path is set to a string, put it in the form it usually is in
46
+ # chef config (:chef_repo_path, :node_path, etc.)
47
+ path_config = new_resource.chef_repo_path
48
+ if path_config.is_a?(Hash)
49
+ chef_repo_path = path_config.delete(:chef_repo_path)
50
+ elsif path_config
51
+ chef_repo_path = path_config
52
+ path_config = {}
53
+ else
54
+ chef_repo_path = Chef::Config.chef_repo_path
55
+ path_config = Chef::Config
56
+ end
57
+ chef_repo_path = Array(chef_repo_path).flatten
58
+
59
+ # Go through the expected object paths and figure out the local paths for each.
60
+ case repo_mode
61
+ when 'hosted_everything'
62
+ object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles)
63
+ else
64
+ object_names = %w(clients cookbooks data_bags environments nodes roles users)
65
+ end
66
+
67
+ object_paths = {}
68
+ object_names.each do |object_name|
69
+ variable_name = "#{object_name[0..-2]}_path" # cookbooks -> cookbook_path
70
+ if path_config[variable_name.to_sym]
71
+ paths = Array(path_config[variable_name.to_sym]).flatten
72
+ else
73
+ paths = chef_repo_path.map { |path| ::File.join(path, object_name) }
74
+ end
75
+ object_paths[object_name] = paths.map { |path| ::File.expand_path(path) }
76
+ end
77
+
78
+ # Set up the root dir
79
+ Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths)
80
+ end
81
+
82
+ def remote_fs
83
+ config = {
84
+ :chef_server_url => new_resource.chef_server[:chef_server_url],
85
+ :node_name => new_resource.chef_server[:options][:client_name],
86
+ :client_key => new_resource.chef_server[:options][:client_key],
87
+ :repo_mode => repo_mode
88
+ }
89
+ Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", config)
90
+ end
91
+
92
+ def repo_mode
93
+ new_resource.chef_server[:chef_server_url] =~ /\/organizations\// ? 'hosted_everything' : 'everything'
94
+ end
95
+
96
+ def options
97
+ result = {
98
+ :purge => new_resource.purge,
99
+ :freeze => new_resource.freeze,
100
+ :diff => new_resource.no_diff,
101
+ :dry_run => whyrun_mode?
102
+ }
103
+ result[:diff] = !result[:diff]
104
+ result[:repo_mode] = repo_mode
105
+ result[:concurrency] = new_resource.concurrency if new_resource.concurrency
106
+ result
107
+ end
108
+
109
+ def load_current_resource
110
+ end
111
+
112
+ class CopyListener
113
+ def initialize(mirror)
114
+ @mirror = mirror
115
+ @errors = []
116
+ end
117
+
118
+ attr_reader :mirror
119
+ attr_reader :errors
120
+
121
+ # TODO output is not *always* indicative of a change. We may want to give
122
+ # ChefFS the ability to tell us that info. For now though, assuming any output
123
+ # means change is pretty damn close to the truth.
124
+ def output(str)
125
+ mirror.converge_by str do
126
+ end
127
+ end
128
+ def warn(str)
129
+ mirror.converge_by "WARNING: #{str}" do
130
+ end
131
+ end
132
+ def error(str)
133
+ mirror.converge_by "ERROR: #{str}" do
134
+ end
135
+ @errors << str
136
+ end
137
+ end
138
+ 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
+ # Preserve tags even if "attributes" was overwritten directly
51
+ json['normal']['tags'] = current_json['normal']['tags'] unless json['normal']['tags']
52
+ # Apply modifiers
53
+ json['run_list'] = apply_run_list_modifiers(new_resource.run_list_modifiers, new_resource.run_list_removers, json['run_list'])
54
+ json['normal'] = apply_modifiers(new_resource.attribute_modifiers, json['normal'])
55
+ # Preserve default/override/automatic even when "complete true"
56
+ json['default'] = current_json['default']
57
+ json['override'] = current_json['override']
58
+ json['automatic'] = current_json['automatic']
59
+ json
60
+ end
61
+
62
+ #
63
+ # Helpers
64
+ #
65
+
66
+ def resource_class
67
+ Chef::Resource::ChefNode
68
+ end
69
+
70
+ def data_handler
71
+ Chef::ChefFS::DataHandler::NodeDataHandler.new
72
+ end
73
+
74
+ def keys
75
+ {
76
+ 'name' => :name,
77
+ 'chef_environment' => :chef_environment,
78
+ 'run_list' => :run_list,
79
+ 'normal' => :attributes
80
+ }
81
+ end
82
+ end
@@ -0,0 +1,150 @@
1
+ require 'cheffish/chef_provider_base'
2
+ require 'chef/resource/chef_organization'
3
+ require 'chef/chef_fs/data_handler/data_handler_base'
4
+
5
+ class Chef::Provider::ChefOrganization < 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 organization #{new_resource.name} at #{rest.url}" ] + differences
17
+ converge_by description do
18
+ rest.put("#{rest.root_url}/organizations/#{new_resource.name}", normalize_for_put(new_json))
19
+ end
20
+ end
21
+ else
22
+ description = [ "create organization #{new_resource.name} at #{rest.url}" ] + differences
23
+ converge_by description do
24
+ rest.post("#{rest.root_url}/organizations", normalize_for_post(new_json))
25
+ end
26
+ end
27
+
28
+ # Revoke invites and memberships when asked
29
+ invites_to_remove.each do |user|
30
+ if outstanding_invites.has_key?(user)
31
+ converge_by "revoke #{user}'s invitation to organization #{new_resource.name}" do
32
+ rest.delete("#{rest.root_url}/organizations/#{new_resource.name}/association_requests/#{outstanding_invites[user]}")
33
+ end
34
+ end
35
+ end
36
+ members_to_remove.each do |user|
37
+ if existing_members.include?(user)
38
+ converge_by "remove #{user} from organization #{new_resource.name}" do
39
+ rest.delete("#{rest.root_url}/organizations/#{new_resource.name}/users/#{user}")
40
+ end
41
+ end
42
+ end
43
+
44
+ # Invite and add members when asked
45
+ new_resource.invites.each do |user|
46
+ if !existing_members.include?(user) && !outstanding_invites.has_key?(user)
47
+ converge_by "invite #{user} to organization #{new_resource.name}" do
48
+ rest.post("#{rest.root_url}/organizations/#{new_resource.name}/association_requests", { 'user' => user })
49
+ end
50
+ end
51
+ end
52
+ new_resource.members.each do |user|
53
+ if !existing_members.include?(user)
54
+ converge_by "Add #{user} to organization #{new_resource.name}" do
55
+ rest.post("#{rest.root_url}/organizations/#{new_resource.name}/users/#{user}", {})
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ def existing_members
62
+ @existing_members ||= rest.get("#{rest.root_url}/organizations/#{new_resource.name}/users").map { |u| u['user']['username'] }
63
+ end
64
+
65
+ def outstanding_invites
66
+ @outstanding_invites ||= begin
67
+ invites = {}
68
+ rest.get("#{rest.root_url}/organizations/#{new_resource.name}/association_requests").each do |r|
69
+ invites[r['username']] = r['id']
70
+ end
71
+ invites
72
+ end
73
+ end
74
+
75
+ def invites_to_remove
76
+ if new_resource.complete
77
+ if new_resource.invites_specified? || new_resource.members_specified?
78
+ outstanding_invites.keys - (new_resource.invites | new_resource.members)
79
+ else
80
+ []
81
+ end
82
+ else
83
+ new_resource.remove_members
84
+ end
85
+ end
86
+
87
+ def members_to_remove
88
+ if new_resource.complete
89
+ if new_resource.members_specified?
90
+ existing_members - (new_resource.invites | new_resource.members)
91
+ else
92
+ []
93
+ end
94
+ else
95
+ new_resource.remove_members
96
+ end
97
+ end
98
+
99
+ action :delete do
100
+ if current_resource_exists?
101
+ converge_by "delete organization #{new_resource.name} at #{rest.url}" do
102
+ rest.delete("#{rest.root_url}/organizations/#{new_resource.name}")
103
+ end
104
+ end
105
+ end
106
+
107
+ def load_current_resource
108
+ begin
109
+ @current_resource = json_to_resource(rest.get("#{rest.root_url}/organizations/#{new_resource.name}"))
110
+ rescue Net::HTTPServerException => e
111
+ if e.response.code == "404"
112
+ @current_resource = not_found_resource
113
+ else
114
+ raise
115
+ end
116
+ end
117
+ end
118
+
119
+ #
120
+ # Helpers
121
+ #
122
+
123
+ def resource_class
124
+ Chef::Resource::ChefOrganization
125
+ end
126
+
127
+ def data_handler
128
+ OrganizationDataHandler.new
129
+ end
130
+
131
+ def keys
132
+ {
133
+ 'name' => :name,
134
+ 'full_name' => :full_name
135
+ }
136
+ end
137
+
138
+ class OrganizationDataHandler < Chef::ChefFS::DataHandler::DataHandlerBase
139
+ def normalize(organization, entry)
140
+ # Normalize the order of the keys for easier reading
141
+ normalize_hash(organization, {
142
+ 'name' => remove_dot_json(entry.name),
143
+ 'full_name' => remove_dot_json(entry.name),
144
+ 'org_type' => 'Business',
145
+ 'clientname' => "#{remove_dot_json(entry.name)}-validator",
146
+ 'billing_plan' => 'platform-free'
147
+ })
148
+ end
149
+ end
150
+ end