cheffish 1.6.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/cheffish.gemspec +1 -0
- data/lib/chef/resource/chef_acl.rb +440 -20
- data/lib/chef/resource/chef_client.rb +50 -25
- data/lib/chef/resource/chef_container.rb +44 -11
- data/lib/chef/resource/chef_data_bag.rb +43 -10
- data/lib/chef/resource/chef_data_bag_item.rb +292 -82
- data/lib/chef/resource/chef_environment.rb +79 -27
- data/lib/chef/resource/chef_group.rb +77 -40
- data/lib/chef/resource/chef_mirror.rb +170 -21
- data/lib/chef/resource/chef_node.rb +77 -11
- data/lib/chef/resource/chef_organization.rb +153 -43
- data/lib/chef/resource/chef_resolved_cookbooks.rb +40 -9
- data/lib/chef/resource/chef_role.rb +81 -29
- data/lib/chef/resource/chef_user.rb +64 -33
- data/lib/chef/resource/private_key.rb +230 -17
- data/lib/chef/resource/public_key.rb +88 -9
- data/lib/cheffish/array_property.rb +29 -0
- data/lib/cheffish/base_resource.rb +254 -0
- data/lib/cheffish/chef_actor_base.rb +135 -0
- data/lib/cheffish/node_properties.rb +107 -0
- data/lib/cheffish/recipe_dsl.rb +0 -14
- data/lib/cheffish/version.rb +1 -1
- data/lib/cheffish.rb +4 -108
- data/spec/integration/chef_acl_spec.rb +0 -2
- data/spec/integration/chef_client_spec.rb +0 -1
- data/spec/integration/chef_container_spec.rb +0 -2
- data/spec/integration/chef_group_spec.rb +0 -2
- data/spec/integration/chef_mirror_spec.rb +0 -2
- data/spec/integration/chef_node_spec.rb +0 -2
- data/spec/integration/chef_organization_spec.rb +1 -3
- data/spec/integration/chef_role_spec.rb +0 -2
- data/spec/integration/chef_user_spec.rb +0 -2
- data/spec/integration/private_key_spec.rb +0 -4
- data/spec/integration/recipe_dsl_spec.rb +0 -2
- data/spec/support/spec_support.rb +0 -1
- data/spec/unit/get_private_key_spec.rb +13 -0
- metadata +22 -20
- data/lib/chef/provider/chef_acl.rb +0 -446
- data/lib/chef/provider/chef_client.rb +0 -53
- data/lib/chef/provider/chef_container.rb +0 -55
- data/lib/chef/provider/chef_data_bag.rb +0 -55
- data/lib/chef/provider/chef_data_bag_item.rb +0 -278
- data/lib/chef/provider/chef_environment.rb +0 -83
- data/lib/chef/provider/chef_group.rb +0 -83
- data/lib/chef/provider/chef_mirror.rb +0 -169
- data/lib/chef/provider/chef_node.rb +0 -87
- data/lib/chef/provider/chef_organization.rb +0 -155
- data/lib/chef/provider/chef_resolved_cookbooks.rb +0 -46
- data/lib/chef/provider/chef_role.rb +0 -84
- data/lib/chef/provider/chef_user.rb +0 -59
- data/lib/chef/provider/private_key.rb +0 -225
- data/lib/chef/provider/public_key.rb +0 -88
- data/lib/cheffish/actor_provider_base.rb +0 -131
- data/lib/cheffish/chef_provider_base.rb +0 -246
@@ -1,53 +1,90 @@
|
|
1
1
|
require 'cheffish'
|
2
|
-
require '
|
2
|
+
require 'cheffish/base_resource'
|
3
3
|
require 'chef/run_list/run_list_item'
|
4
|
+
require 'chef/chef_fs/data_handler/group_data_handler'
|
4
5
|
|
5
6
|
class Chef
|
6
7
|
class Resource
|
7
|
-
class ChefGroup <
|
8
|
-
|
9
|
-
|
10
|
-
actions :create, :delete, :nothing
|
11
|
-
default_action :create
|
12
|
-
|
13
|
-
# Grab environment from with_environment
|
14
|
-
def initialize(*args)
|
15
|
-
super
|
16
|
-
chef_server run_context.cheffish.current_chef_server
|
17
|
-
@users = []
|
18
|
-
@clients = []
|
19
|
-
@groups = []
|
20
|
-
@remove_users = []
|
21
|
-
@remove_clients = []
|
22
|
-
@remove_groups = []
|
23
|
-
end
|
8
|
+
class ChefGroup < Cheffish::BaseResource
|
9
|
+
resource_name :chef_group
|
24
10
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
11
|
+
property :name, Cheffish::NAME_REGEX, name_property: true
|
12
|
+
property :users, ArrayType
|
13
|
+
property :clients, ArrayType
|
14
|
+
property :groups, ArrayType
|
15
|
+
property :remove_users, ArrayType
|
16
|
+
property :remove_clients, ArrayType
|
17
|
+
property :remove_groups, ArrayType
|
18
|
+
|
19
|
+
action :create do
|
20
|
+
differences = json_differences(current_json, new_json)
|
21
|
+
|
22
|
+
if current_resource_exists?
|
23
|
+
if differences.size > 0
|
24
|
+
description = [ "update group #{new_resource.name} at #{rest.url}" ] + differences
|
25
|
+
converge_by description do
|
26
|
+
rest.put("groups/#{new_resource.name}", normalize_for_put(new_json))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
else
|
30
|
+
description = [ "create group #{new_resource.name} at #{rest.url}" ] + differences
|
31
|
+
converge_by description do
|
32
|
+
rest.post("groups", normalize_for_post(new_json))
|
33
|
+
end
|
34
|
+
end
|
40
35
|
end
|
41
|
-
|
42
|
-
|
36
|
+
|
37
|
+
action :delete do
|
38
|
+
if current_resource_exists?
|
39
|
+
converge_by "delete group #{new_resource.name} at #{rest.url}" do
|
40
|
+
rest.delete("groups/#{new_resource.name}")
|
41
|
+
end
|
42
|
+
end
|
43
43
|
end
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
action_class.class_eval do
|
46
|
+
def load_current_resource
|
47
|
+
begin
|
48
|
+
@current_resource = json_to_resource(rest.get("groups/#{new_resource.name}"))
|
49
|
+
rescue Net::HTTPServerException => e
|
50
|
+
if e.response.code == "404"
|
51
|
+
@current_resource = not_found_resource
|
52
|
+
else
|
53
|
+
raise
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def augment_new_json(json)
|
59
|
+
# Apply modifiers
|
60
|
+
json['users'] |= new_resource.users
|
61
|
+
json['clients'] |= new_resource.clients
|
62
|
+
json['groups'] |= new_resource.groups
|
63
|
+
json['users'] -= new_resource.remove_users
|
64
|
+
json['clients'] -= new_resource.remove_clients
|
65
|
+
json['groups'] -= new_resource.remove_groups
|
66
|
+
json
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Helpers
|
71
|
+
#
|
48
72
|
|
49
|
-
|
50
|
-
|
73
|
+
def resource_class
|
74
|
+
Chef::Resource::ChefGroup
|
75
|
+
end
|
76
|
+
|
77
|
+
def data_handler
|
78
|
+
Chef::ChefFS::DataHandler::GroupDataHandler.new
|
79
|
+
end
|
80
|
+
|
81
|
+
def keys
|
82
|
+
{
|
83
|
+
'name' => :name,
|
84
|
+
'groupname' => :name
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
51
88
|
end
|
52
89
|
end
|
53
90
|
end
|
@@ -1,52 +1,201 @@
|
|
1
1
|
require 'cheffish'
|
2
|
-
require '
|
2
|
+
require 'cheffish/base_resource'
|
3
|
+
require 'chef/chef_fs/file_pattern'
|
4
|
+
require 'chef/chef_fs/file_system'
|
5
|
+
require 'chef/chef_fs/parallelizer'
|
6
|
+
require 'chef/chef_fs/file_system/chef_server_root_dir'
|
7
|
+
require 'chef/chef_fs/file_system/chef_repository_file_system_root_dir'
|
3
8
|
|
4
9
|
class Chef
|
5
10
|
class Resource
|
6
|
-
class ChefMirror <
|
7
|
-
|
8
|
-
|
9
|
-
actions :upload, :download, :nothing
|
10
|
-
default_action :nothing
|
11
|
-
|
12
|
-
def initialize(*args)
|
13
|
-
super
|
14
|
-
chef_server run_context.cheffish.current_chef_server
|
15
|
-
end
|
11
|
+
class ChefMirror < Cheffish::BaseResource
|
12
|
+
resource_name :chef_mirror
|
16
13
|
|
17
14
|
# Path of the data to mirror, e.g. nodes, nodes/*, nodes/mynode,
|
18
15
|
# */*, **, roles/base, data/secrets, cookbooks/apache2, etc.
|
19
|
-
|
16
|
+
property :path, String, name_property: true
|
20
17
|
|
21
18
|
# Local path. Can be a string (top level of repository) or hash
|
22
19
|
# (:chef_repo_path, :node_path, etc.)
|
23
20
|
# If neither chef_repo_path nor versioned_cookbooks are set, they default to their
|
24
21
|
# Chef::Config values. If chef_repo_path is set but versioned_cookbooks is not,
|
25
22
|
# versioned_cookbooks defaults to true.
|
26
|
-
|
23
|
+
property :chef_repo_path, [ String, Hash ]
|
27
24
|
|
28
25
|
# Whether the repo path should contain cookbooks with versioned names,
|
29
26
|
# i.e. cookbooks/mysql-1.0.0, cookbooks/mysql-1.2.0, etc.
|
30
27
|
# Defaults to true if chef_repo_path is specified, or to Chef::Config.versioned_cookbooks otherwise.
|
31
|
-
|
32
|
-
|
33
|
-
# Chef server
|
34
|
-
attribute :chef_server, :kind_of => Hash
|
28
|
+
property :versioned_cookbooks, Boolean
|
35
29
|
|
36
30
|
# Whether to purge deleted things: if we do not have cookbooks/x locally and we
|
37
31
|
# *do* have cookbooks/x remotely, then :upload with purge will delete it.
|
38
32
|
# Defaults to false.
|
39
|
-
|
33
|
+
property :purge, Boolean
|
40
34
|
|
41
35
|
# Whether to freeze cookbooks on upload
|
42
|
-
|
36
|
+
property :freeze, Boolean
|
43
37
|
|
44
38
|
# If this is true, only new files will be copied. File contents will not be
|
45
39
|
# diffed, so changed files will never be uploaded.
|
46
|
-
|
40
|
+
property :no_diff, Boolean
|
47
41
|
|
48
42
|
# Number of parallel threads to list/upload/download with. Defaults to 10.
|
49
|
-
|
43
|
+
property :concurrency, Integer, default: 10, desired_state: false
|
44
|
+
|
45
|
+
|
46
|
+
action :upload do
|
47
|
+
with_modified_config do
|
48
|
+
copy_to(local_fs, remote_fs)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
action :download do
|
53
|
+
with_modified_config do
|
54
|
+
copy_to(remote_fs, local_fs)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
action_class.class_eval do
|
59
|
+
|
60
|
+
def with_modified_config
|
61
|
+
# pre-Chef-12 ChefFS reads versioned_cookbooks out of Chef::Config instead of
|
62
|
+
# taking it as an input, so we need to modify it for the duration of copy_to
|
63
|
+
@old_versioned_cookbooks = Chef::Config.versioned_cookbooks
|
64
|
+
# If versioned_cookbooks is explicitly set, set it.
|
65
|
+
if !new_resource.versioned_cookbooks.nil?
|
66
|
+
Chef::Config.versioned_cookbooks = new_resource.versioned_cookbooks
|
67
|
+
|
68
|
+
# If new_resource.chef_repo_path is set, versioned_cookbooks defaults to true.
|
69
|
+
# Otherwise, it stays at its current Chef::Config value.
|
70
|
+
elsif new_resource.chef_repo_path
|
71
|
+
Chef::Config.versioned_cookbooks = true
|
72
|
+
end
|
73
|
+
|
74
|
+
begin
|
75
|
+
yield
|
76
|
+
ensure
|
77
|
+
Chef::Config.versioned_cookbooks = @old_versioned_cookbooks
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def copy_to(src_root, dest_root)
|
82
|
+
if new_resource.concurrency <= 0
|
83
|
+
raise "chef_mirror.concurrency must be above 0! Was set to #{new_resource.concurrency}"
|
84
|
+
end
|
85
|
+
# Honor concurrency
|
86
|
+
Chef::ChefFS::Parallelizer.threads = new_resource.concurrency - 1
|
87
|
+
|
88
|
+
# We don't let the user pass absolute paths; we want to reserve those for
|
89
|
+
# multi-org support (/organizations/foo).
|
90
|
+
if new_resource.path[0] == '/'
|
91
|
+
raise "Absolute paths in chef_mirror not yet supported."
|
92
|
+
end
|
93
|
+
# Copy!
|
94
|
+
path = Chef::ChefFS::FilePattern.new("/#{new_resource.path}")
|
95
|
+
ui = CopyListener.new(self)
|
96
|
+
error = Chef::ChefFS::FileSystem.copy_to(path, src_root, dest_root, nil, options, ui, proc { |p| p.path })
|
97
|
+
|
98
|
+
if error
|
99
|
+
raise "Errors while copying:#{ui.errors.map { |e| "#{e}\n" }.join('')}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def local_fs
|
104
|
+
# If chef_repo_path is set to a string, put it in the form it usually is in
|
105
|
+
# chef config (:chef_repo_path, :node_path, etc.)
|
106
|
+
path_config = new_resource.chef_repo_path
|
107
|
+
if path_config.is_a?(Hash)
|
108
|
+
chef_repo_path = path_config.delete(:chef_repo_path)
|
109
|
+
elsif path_config
|
110
|
+
chef_repo_path = path_config
|
111
|
+
path_config = {}
|
112
|
+
else
|
113
|
+
chef_repo_path = Chef::Config.chef_repo_path
|
114
|
+
path_config = Chef::Config
|
115
|
+
end
|
116
|
+
chef_repo_path = Array(chef_repo_path).flatten
|
117
|
+
|
118
|
+
# Go through the expected object paths and figure out the local paths for each.
|
119
|
+
case repo_mode
|
120
|
+
when 'hosted_everything'
|
121
|
+
object_names = %w(acls clients cookbooks containers data_bags environments groups nodes roles)
|
122
|
+
else
|
123
|
+
object_names = %w(clients cookbooks data_bags environments nodes roles users)
|
124
|
+
end
|
125
|
+
|
126
|
+
object_paths = {}
|
127
|
+
object_names.each do |object_name|
|
128
|
+
variable_name = "#{object_name[0..-2]}_path" # cookbooks -> cookbook_path
|
129
|
+
if path_config[variable_name.to_sym]
|
130
|
+
paths = Array(path_config[variable_name.to_sym]).flatten
|
131
|
+
else
|
132
|
+
paths = chef_repo_path.map { |path| ::File.join(path, object_name) }
|
133
|
+
end
|
134
|
+
object_paths[object_name] = paths.map { |path| ::File.expand_path(path) }
|
135
|
+
end
|
136
|
+
|
137
|
+
# Set up the root dir
|
138
|
+
Chef::ChefFS::FileSystem::ChefRepositoryFileSystemRootDir.new(object_paths)
|
139
|
+
end
|
140
|
+
|
141
|
+
def remote_fs
|
142
|
+
config = {
|
143
|
+
:chef_server_url => new_resource.chef_server[:chef_server_url],
|
144
|
+
:node_name => new_resource.chef_server[:options][:client_name],
|
145
|
+
:client_key => new_resource.chef_server[:options][:signing_key_filename],
|
146
|
+
:repo_mode => repo_mode,
|
147
|
+
:versioned_cookbooks => Chef::Config.versioned_cookbooks
|
148
|
+
}
|
149
|
+
Chef::ChefFS::FileSystem::ChefServerRootDir.new("remote", config)
|
150
|
+
end
|
151
|
+
|
152
|
+
def repo_mode
|
153
|
+
new_resource.chef_server[:chef_server_url] =~ /\/organizations\// ? 'hosted_everything' : 'everything'
|
154
|
+
end
|
155
|
+
|
156
|
+
def options
|
157
|
+
result = {
|
158
|
+
:purge => new_resource.purge,
|
159
|
+
:freeze => new_resource.freeze,
|
160
|
+
:diff => new_resource.no_diff,
|
161
|
+
:dry_run => whyrun_mode?
|
162
|
+
}
|
163
|
+
result[:diff] = !result[:diff]
|
164
|
+
result[:repo_mode] = repo_mode
|
165
|
+
result[:concurrency] = new_resource.concurrency if new_resource.concurrency
|
166
|
+
result
|
167
|
+
end
|
168
|
+
|
169
|
+
def load_current_resource
|
170
|
+
end
|
171
|
+
|
172
|
+
class CopyListener
|
173
|
+
def initialize(mirror)
|
174
|
+
@mirror = mirror
|
175
|
+
@errors = []
|
176
|
+
end
|
177
|
+
|
178
|
+
attr_reader :mirror
|
179
|
+
attr_reader :errors
|
180
|
+
|
181
|
+
# TODO output is not *always* indicative of a change. We may want to give
|
182
|
+
# ChefFS the ability to tell us that info. For now though, assuming any output
|
183
|
+
# means change is pretty damn close to the truth.
|
184
|
+
def output(str)
|
185
|
+
mirror.converge_by str do
|
186
|
+
end
|
187
|
+
end
|
188
|
+
def warn(str)
|
189
|
+
mirror.converge_by "WARNING: #{str}" do
|
190
|
+
end
|
191
|
+
end
|
192
|
+
def error(str)
|
193
|
+
mirror.converge_by "ERROR: #{str}" do
|
194
|
+
end
|
195
|
+
@errors << str
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
50
199
|
end
|
51
200
|
end
|
52
201
|
end
|
@@ -1,22 +1,88 @@
|
|
1
1
|
require 'cheffish'
|
2
|
-
require '
|
2
|
+
require 'cheffish/base_resource'
|
3
|
+
require 'chef/chef_fs/data_handler/node_data_handler'
|
4
|
+
require 'cheffish/node_properties'
|
3
5
|
|
4
6
|
class Chef
|
5
7
|
class Resource
|
6
|
-
class ChefNode <
|
7
|
-
|
8
|
+
class ChefNode < Cheffish::BaseResource
|
9
|
+
resource_name :chef_node
|
8
10
|
|
9
|
-
|
10
|
-
default_action :create
|
11
|
+
include Cheffish::NodeProperties
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
action :create do
|
14
|
+
differences = json_differences(current_json, new_json)
|
15
|
+
|
16
|
+
if current_resource_exists?
|
17
|
+
if differences.size > 0
|
18
|
+
description = [ "update node #{new_resource.name} at #{rest.url}" ] + differences
|
19
|
+
converge_by description do
|
20
|
+
rest.put("nodes/#{new_resource.name}", normalize_for_put(new_json))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
else
|
24
|
+
description = [ "create node #{new_resource.name} at #{rest.url}" ] + differences
|
25
|
+
converge_by description do
|
26
|
+
rest.post("nodes", normalize_for_post(new_json))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
action :delete do
|
32
|
+
if current_resource_exists?
|
33
|
+
converge_by "delete node #{new_resource.name} at #{rest.url}" do
|
34
|
+
rest.delete("nodes/#{new_resource.name}")
|
35
|
+
end
|
36
|
+
end
|
17
37
|
end
|
18
38
|
|
19
|
-
|
39
|
+
action_class.class_eval do
|
40
|
+
def load_current_resource
|
41
|
+
begin
|
42
|
+
@current_resource = json_to_resource(rest.get("nodes/#{new_resource.name}"))
|
43
|
+
rescue Net::HTTPServerException => e
|
44
|
+
if e.response.code == "404"
|
45
|
+
@current_resource = not_found_resource
|
46
|
+
else
|
47
|
+
raise
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def augment_new_json(json)
|
53
|
+
# Preserve tags even if "attributes" was overwritten directly
|
54
|
+
json['normal']['tags'] = current_json['normal']['tags'] unless json['normal']['tags']
|
55
|
+
# Apply modifiers
|
56
|
+
json['run_list'] = apply_run_list_modifiers(new_resource.run_list_modifiers, new_resource.run_list_removers, json['run_list'])
|
57
|
+
json['normal'] = apply_modifiers(new_resource.attribute_modifiers, json['normal'])
|
58
|
+
# Preserve default/override/automatic even when "complete true"
|
59
|
+
json['default'] = current_json['default']
|
60
|
+
json['override'] = current_json['override']
|
61
|
+
json['automatic'] = current_json['automatic']
|
62
|
+
json
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Helpers
|
67
|
+
#
|
68
|
+
|
69
|
+
def resource_class
|
70
|
+
Chef::Resource::ChefNode
|
71
|
+
end
|
72
|
+
|
73
|
+
def data_handler
|
74
|
+
Chef::ChefFS::DataHandler::NodeDataHandler.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def keys
|
78
|
+
{
|
79
|
+
'name' => :name,
|
80
|
+
'chef_environment' => :chef_environment,
|
81
|
+
'run_list' => :run_list,
|
82
|
+
'normal' => :attributes
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
20
86
|
end
|
21
87
|
end
|
22
88
|
end
|
@@ -1,69 +1,179 @@
|
|
1
1
|
require 'cheffish'
|
2
|
-
require '
|
2
|
+
require 'cheffish/base_resource'
|
3
3
|
require 'chef/run_list/run_list_item'
|
4
|
+
require 'chef/chef_fs/data_handler/data_handler_base'
|
4
5
|
|
5
6
|
class Chef
|
6
7
|
class Resource
|
7
|
-
class ChefOrganization <
|
8
|
-
|
9
|
-
|
10
|
-
actions :create, :delete, :nothing
|
11
|
-
default_action :create
|
12
|
-
|
13
|
-
# Grab environment from with_environment
|
14
|
-
def initialize(*args)
|
15
|
-
super
|
16
|
-
chef_server run_context.cheffish.current_chef_server
|
17
|
-
@invites = nil
|
18
|
-
@members = nil
|
19
|
-
@remove_members = []
|
20
|
-
end
|
8
|
+
class ChefOrganization < Cheffish::BaseResource
|
9
|
+
resource_name :chef_organization
|
21
10
|
|
22
|
-
|
23
|
-
|
11
|
+
property :name, Cheffish::NAME_REGEX, name_property: true
|
12
|
+
property :full_name, String
|
24
13
|
|
25
14
|
# A list of users who must at least be invited to the org (but may already be
|
26
15
|
# members). Invites will be sent to users who are not already invited/in the org.
|
27
|
-
|
28
|
-
if users.size == 0
|
29
|
-
@invites || []
|
30
|
-
else
|
31
|
-
@invites ||= []
|
32
|
-
@invites |= users.flatten
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
def invites_specified?
|
37
|
-
!!@invites
|
38
|
-
end
|
16
|
+
property :invites, ArrayType
|
39
17
|
|
40
18
|
# A list of users who must be members of the org. This will use the
|
41
19
|
# new Chef 12 POST /organizations/ORG/users endpoint to add them
|
42
20
|
# directly to the org. If you do not have permission to perform
|
43
21
|
# this operation, and the users are not a part of the org, the
|
44
22
|
# resource update will fail.
|
45
|
-
|
46
|
-
|
47
|
-
|
23
|
+
property :members, ArrayType
|
24
|
+
|
25
|
+
# A list of users who must not be members of the org. These users will be removed
|
26
|
+
# from the org and invites will be revoked (if any).
|
27
|
+
property :remove_members, ArrayType
|
28
|
+
|
29
|
+
|
30
|
+
action :create do
|
31
|
+
differences = json_differences(current_json, new_json)
|
32
|
+
|
33
|
+
if current_resource_exists?
|
34
|
+
if differences.size > 0
|
35
|
+
description = [ "update organization #{new_resource.name} at #{rest.url}" ] + differences
|
36
|
+
converge_by description do
|
37
|
+
rest.put("#{rest.root_url}/organizations/#{new_resource.name}", normalize_for_put(new_json))
|
38
|
+
end
|
39
|
+
end
|
48
40
|
else
|
49
|
-
|
50
|
-
|
41
|
+
description = [ "create organization #{new_resource.name} at #{rest.url}" ] + differences
|
42
|
+
converge_by description do
|
43
|
+
rest.post("#{rest.root_url}/organizations", normalize_for_post(new_json))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Revoke invites and memberships when asked
|
48
|
+
invites_to_remove.each do |user|
|
49
|
+
if outstanding_invites.has_key?(user)
|
50
|
+
converge_by "revoke #{user}'s invitation to organization #{new_resource.name}" do
|
51
|
+
rest.delete("#{rest.root_url}/organizations/#{new_resource.name}/association_requests/#{outstanding_invites[user]}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
members_to_remove.each do |user|
|
56
|
+
if existing_members.include?(user)
|
57
|
+
converge_by "remove #{user} from organization #{new_resource.name}" do
|
58
|
+
rest.delete("#{rest.root_url}/organizations/#{new_resource.name}/users/#{user}")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Invite and add members when asked
|
64
|
+
new_resource.invites.each do |user|
|
65
|
+
if !existing_members.include?(user) && !outstanding_invites.has_key?(user)
|
66
|
+
converge_by "invite #{user} to organization #{new_resource.name}" do
|
67
|
+
rest.post("#{rest.root_url}/organizations/#{new_resource.name}/association_requests", { 'user' => user })
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
new_resource.members.each do |user|
|
72
|
+
if !existing_members.include?(user)
|
73
|
+
converge_by "Add #{user} to organization #{new_resource.name}" do
|
74
|
+
rest.post("#{rest.root_url}/organizations/#{new_resource.name}/users/", { 'username' => user })
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
action_class.class_eval do
|
81
|
+
def existing_members
|
82
|
+
@existing_members ||= rest.get("#{rest.root_url}/organizations/#{new_resource.name}/users").map { |u| u['user']['username'] }
|
83
|
+
end
|
84
|
+
|
85
|
+
def outstanding_invites
|
86
|
+
@outstanding_invites ||= begin
|
87
|
+
invites = {}
|
88
|
+
rest.get("#{rest.root_url}/organizations/#{new_resource.name}/association_requests").each do |r|
|
89
|
+
invites[r['username']] = r['id']
|
90
|
+
end
|
91
|
+
invites
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def invites_to_remove
|
96
|
+
if new_resource.complete
|
97
|
+
if new_resource.property_is_set?(:invites) || new_resource.property_is_set?(:members)
|
98
|
+
result = outstanding_invites.keys
|
99
|
+
result -= new_resource.invites if new_resource.property_is_set?(:invites)
|
100
|
+
result -= new_resource.members if new_resource.property_is_set?(:members)
|
101
|
+
result
|
102
|
+
else
|
103
|
+
[]
|
104
|
+
end
|
105
|
+
else
|
106
|
+
new_resource.remove_members
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def members_to_remove
|
111
|
+
if new_resource.complete
|
112
|
+
if new_resource.property_is_set?(:members)
|
113
|
+
existing_members - (new_resource.invites | new_resource.members)
|
114
|
+
else
|
115
|
+
[]
|
116
|
+
end
|
117
|
+
else
|
118
|
+
new_resource.remove_members
|
119
|
+
end
|
51
120
|
end
|
52
121
|
end
|
53
122
|
|
54
|
-
|
55
|
-
|
123
|
+
action :delete do
|
124
|
+
if current_resource_exists?
|
125
|
+
converge_by "delete organization #{new_resource.name} at #{rest.url}" do
|
126
|
+
rest.delete("#{rest.root_url}/organizations/#{new_resource.name}")
|
127
|
+
end
|
128
|
+
end
|
56
129
|
end
|
57
130
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
131
|
+
action_class.class_eval do
|
132
|
+
def load_current_resource
|
133
|
+
begin
|
134
|
+
@current_resource = json_to_resource(rest.get("#{rest.root_url}/organizations/#{new_resource.name}"))
|
135
|
+
rescue Net::HTTPServerException => e
|
136
|
+
if e.response.code == "404"
|
137
|
+
@current_resource = not_found_resource
|
138
|
+
else
|
139
|
+
raise
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
# Helpers
|
146
|
+
#
|
147
|
+
|
148
|
+
def resource_class
|
149
|
+
Chef::Resource::ChefOrganization
|
150
|
+
end
|
151
|
+
|
152
|
+
def data_handler
|
153
|
+
OrganizationDataHandler.new
|
154
|
+
end
|
155
|
+
|
156
|
+
def keys
|
157
|
+
{
|
158
|
+
'name' => :name,
|
159
|
+
'full_name' => :full_name
|
160
|
+
}
|
161
|
+
end
|
162
|
+
|
163
|
+
class OrganizationDataHandler < Chef::ChefFS::DataHandler::DataHandlerBase
|
164
|
+
def normalize(organization, entry)
|
165
|
+
# Normalize the order of the keys for easier reading
|
166
|
+
normalize_hash(organization, {
|
167
|
+
'name' => remove_dot_json(entry.name),
|
168
|
+
'full_name' => remove_dot_json(entry.name),
|
169
|
+
'org_type' => 'Business',
|
170
|
+
'clientname' => "#{remove_dot_json(entry.name)}-validator",
|
171
|
+
'billing_plan' => 'platform-free'
|
172
|
+
})
|
173
|
+
end
|
174
|
+
end
|
62
175
|
end
|
63
176
|
|
64
|
-
attribute :complete, :kind_of => [ TrueClass, FalseClass ]
|
65
|
-
attribute :raw_json, :kind_of => Hash
|
66
|
-
attribute :chef_server, :kind_of => Hash
|
67
177
|
end
|
68
178
|
end
|
69
179
|
end
|