clc-cheffish 0.8.clc

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 (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