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.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +4 -0
- data/Rakefile +23 -0
- data/lib/chef/provider/chef_acl.rb +434 -0
- data/lib/chef/provider/chef_client.rb +48 -0
- data/lib/chef/provider/chef_container.rb +50 -0
- data/lib/chef/provider/chef_data_bag.rb +50 -0
- data/lib/chef/provider/chef_data_bag_item.rb +273 -0
- data/lib/chef/provider/chef_environment.rb +78 -0
- data/lib/chef/provider/chef_group.rb +78 -0
- data/lib/chef/provider/chef_mirror.rb +138 -0
- data/lib/chef/provider/chef_node.rb +82 -0
- data/lib/chef/provider/chef_organization.rb +150 -0
- data/lib/chef/provider/chef_resolved_cookbooks.rb +41 -0
- data/lib/chef/provider/chef_role.rb +79 -0
- data/lib/chef/provider/chef_user.rb +53 -0
- data/lib/chef/provider/private_key.rb +219 -0
- data/lib/chef/provider/public_key.rb +82 -0
- data/lib/chef/resource/chef_acl.rb +65 -0
- data/lib/chef/resource/chef_client.rb +44 -0
- data/lib/chef/resource/chef_container.rb +18 -0
- data/lib/chef/resource/chef_data_bag.rb +18 -0
- data/lib/chef/resource/chef_data_bag_item.rb +114 -0
- data/lib/chef/resource/chef_environment.rb +71 -0
- data/lib/chef/resource/chef_group.rb +49 -0
- data/lib/chef/resource/chef_mirror.rb +47 -0
- data/lib/chef/resource/chef_node.rb +18 -0
- data/lib/chef/resource/chef_organization.rb +64 -0
- data/lib/chef/resource/chef_resolved_cookbooks.rb +31 -0
- data/lib/chef/resource/chef_role.rb +104 -0
- data/lib/chef/resource/chef_user.rb +51 -0
- data/lib/chef/resource/private_key.rb +44 -0
- data/lib/chef/resource/public_key.rb +21 -0
- data/lib/cheffish.rb +222 -0
- data/lib/cheffish/actor_provider_base.rb +131 -0
- data/lib/cheffish/basic_chef_client.rb +115 -0
- data/lib/cheffish/chef_provider_base.rb +231 -0
- data/lib/cheffish/chef_run_data.rb +19 -0
- data/lib/cheffish/chef_run_listener.rb +28 -0
- data/lib/cheffish/key_formatter.rb +109 -0
- data/lib/cheffish/merged_config.rb +94 -0
- data/lib/cheffish/recipe_dsl.rb +147 -0
- data/lib/cheffish/server_api.rb +52 -0
- data/lib/cheffish/version.rb +3 -0
- data/lib/cheffish/with_pattern.rb +21 -0
- data/spec/functional/fingerprint_spec.rb +64 -0
- data/spec/functional/merged_config_spec.rb +20 -0
- data/spec/integration/chef_acl_spec.rb +914 -0
- data/spec/integration/chef_client_spec.rb +110 -0
- data/spec/integration/chef_container_spec.rb +34 -0
- data/spec/integration/chef_group_spec.rb +324 -0
- data/spec/integration/chef_mirror_spec.rb +244 -0
- data/spec/integration/chef_node_spec.rb +211 -0
- data/spec/integration/chef_organization_spec.rb +244 -0
- data/spec/integration/chef_user_spec.rb +90 -0
- data/spec/integration/private_key_spec.rb +446 -0
- data/spec/integration/recipe_dsl_spec.rb +29 -0
- data/spec/support/key_support.rb +29 -0
- data/spec/support/repository_support.rb +103 -0
- data/spec/support/spec_support.rb +176 -0
- data/spec/unit/get_private_key_spec.rb +93 -0
- 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
|