cheffish 1.6.0 → 2.0.0
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 +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
|