cheffish 0.7.1 → 0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chef/provider/chef_acl.rb +434 -0
  3. data/lib/chef/provider/chef_client.rb +5 -1
  4. data/lib/chef/provider/chef_container.rb +50 -0
  5. data/lib/chef/provider/chef_group.rb +78 -0
  6. data/lib/chef/provider/chef_mirror.rb +138 -0
  7. data/lib/chef/provider/chef_organization.rb +150 -0
  8. data/lib/chef/provider/chef_user.rb +6 -1
  9. data/lib/chef/provider/public_key.rb +0 -1
  10. data/lib/chef/resource/chef_acl.rb +38 -44
  11. data/lib/chef/resource/chef_container.rb +18 -0
  12. data/lib/chef/resource/chef_group.rb +49 -0
  13. data/lib/chef/resource/chef_mirror.rb +47 -0
  14. data/lib/chef/resource/chef_organization.rb +64 -0
  15. data/lib/chef/resource/private_key.rb +6 -1
  16. data/lib/chef/resource/public_key.rb +5 -0
  17. data/lib/cheffish/actor_provider_base.rb +14 -9
  18. data/lib/cheffish/basic_chef_client.rb +18 -2
  19. data/lib/cheffish/chef_provider_base.rb +7 -0
  20. data/lib/cheffish/merged_config.rb +10 -2
  21. data/lib/cheffish/recipe_dsl.rb +34 -8
  22. data/lib/cheffish/server_api.rb +12 -2
  23. data/lib/cheffish/version.rb +1 -1
  24. data/lib/cheffish.rb +2 -2
  25. data/spec/functional/merged_config_spec.rb +20 -0
  26. data/spec/integration/chef_acl_spec.rb +914 -0
  27. data/spec/integration/chef_client_spec.rb +78 -44
  28. data/spec/integration/chef_container_spec.rb +34 -0
  29. data/spec/integration/chef_group_spec.rb +324 -0
  30. data/spec/integration/chef_mirror_spec.rb +244 -0
  31. data/spec/integration/chef_node_spec.rb +115 -93
  32. data/spec/integration/chef_organization_spec.rb +244 -0
  33. data/spec/integration/chef_user_spec.rb +51 -9
  34. data/spec/support/repository_support.rb +103 -0
  35. data/spec/support/spec_support.rb +55 -2
  36. metadata +23 -9
  37. data/lib/chef/resource/in_parallel.rb +0 -6
@@ -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,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
@@ -25,6 +25,10 @@ class Chef::Provider::ChefUser < Cheffish::ActorProviderBase
25
25
  'user'
26
26
  end
27
27
 
28
+ def actor_path
29
+ "#{rest.root_url}/users"
30
+ end
31
+
28
32
  def resource_class
29
33
  Chef::Resource::ChefUser
30
34
  end
@@ -36,6 +40,7 @@ class Chef::Provider::ChefUser < Cheffish::ActorProviderBase
36
40
  def keys
37
41
  {
38
42
  'name' => :name,
43
+ 'username' => :name,
39
44
  'admin' => :admin,
40
45
  'email' => :email,
41
46
  'password' => :password,
@@ -45,4 +50,4 @@ class Chef::Provider::ChefUser < Cheffish::ActorProviderBase
45
50
  }
46
51
  end
47
52
 
48
- end
53
+ end
@@ -3,7 +3,6 @@ require 'openssl'
3
3
  require 'cheffish/key_formatter'
4
4
 
5
5
  class Chef::Provider::PublicKey < Chef::Provider::LWRPBase
6
-
7
6
  action :create do
8
7
  if !new_source_key
9
8
  raise "No source key specified"
@@ -1,11 +1,10 @@
1
1
  require 'cheffish'
2
2
  require 'chef/resource/lwrp_base'
3
- require 'chef/environment'
4
3
 
5
4
  class Chef::Resource::ChefAcl < Chef::Resource::LWRPBase
6
5
  self.resource_name = 'chef_acl'
7
6
 
8
- actions :create, :delete, :nothing
7
+ actions :create, :nothing
9
8
  default_action :create
10
9
 
11
10
  def initialize(*args)
@@ -13,59 +12,54 @@ class Chef::Resource::ChefAcl < Chef::Resource::LWRPBase
13
12
  chef_server run_context.cheffish.current_chef_server
14
13
  end
15
14
 
16
- attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
17
- attribute :description, :kind_of => String
18
- attribute :cookbook_versions, :kind_of => Hash, :callbacks => {
19
- "should have valid cookbook versions" => lambda { |value| Chef::Environment.validate_cookbook_versions(value) }
20
- }
21
- attribute :default_attributes, :kind_of => Hash
22
- attribute :override_attributes, :kind_of => Hash
15
+ # Path of the thing being secured, e.g. nodes, nodes/*, nodes/mynode,
16
+ # */*, **, roles/base, data/secrets, cookbooks/apache2, /users/*,
17
+ # /organizations/foo/nodes/x
18
+ attribute :path, :kind_of => String, :name_attribute => true
23
19
 
24
- # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
25
- # reset to their defaults)
20
+ # Whether to change things recursively. true means it will descend all children
21
+ # and make the same modifications to them. :on_change will only descend if
22
+ # the parent has changed. :on_change is the default.
23
+ attribute :recursive, :equal_to => [ true, false, :on_change ], :default => :on_change
24
+
25
+ # Specifies that this is a complete specification for the acl (i.e. rights
26
+ # you don't specify will be reset to their defaults)
26
27
  attribute :complete, :kind_of => [TrueClass, FalseClass]
27
28
 
28
29
  attribute :raw_json, :kind_of => Hash
29
30
  attribute :chef_server, :kind_of => Hash
30
31
 
31
- NOT_PASSED=Object.new
32
-
33
- # default 'ip_address', '127.0.0.1'
34
- # default [ 'pushy', 'port' ], '9000'
35
- # default 'ip_addresses' do |existing_value|
36
- # (existing_value || []) + [ '127.0.0.1' ]
37
- # end
38
- # default 'ip_address', :delete
39
- attr_reader :default_attribute_modifiers
40
- def default(attribute_path, value=NOT_PASSED, &block)
41
- @default_attribute_modifiers ||= []
42
- if value != NOT_PASSED
43
- @default_attribute_modifiers << [ attribute_path, value ]
44
- elsif block
45
- @default_attribute_modifiers << [ attribute_path, block ]
32
+ # rights :read, :users => 'jkeiser', :groups => [ 'admins', 'users' ]
33
+ # rights [ :create, :read ], :users => [ 'jkeiser', 'adam' ]
34
+ # rights :all, :users => 'jkeiser'
35
+ def rights(*values)
36
+ if values.size == 0
37
+ @rights
46
38
  else
47
- raise "default requires either a value or a block"
39
+ args = values.pop
40
+ args[:permissions] ||= []
41
+ values.each do |value|
42
+ args[:permissions] |= Array(value)
43
+ end
44
+ @rights ||= []
45
+ @rights << args
48
46
  end
49
47
  end
50
48
 
51
- # override 'ip_address', '127.0.0.1'
52
- # override [ 'pushy', 'port' ], '9000'
53
- # override 'ip_addresses' do |existing_value|
54
- # (existing_value || []) + [ '127.0.0.1' ]
55
- # end
56
- # override 'ip_address', :delete
57
- attr_reader :override_attribute_modifiers
58
- def override(attribute_path, value=NOT_PASSED, &block)
59
- @override_attribute_modifiers ||= []
60
- if value != NOT_PASSED
61
- @override_attribute_modifiers << [ attribute_path, value ]
62
- elsif block
63
- @override_attribute_modifiers << [ attribute_path, block ]
49
+ # remove_rights :read, :users => 'jkeiser', :groups => [ 'admins', 'users' ]
50
+ # remove_rights [ :create, :read ], :users => [ 'jkeiser', 'adam' ]
51
+ # remove_rights :all, :users => [ 'jkeiser', 'adam' ]
52
+ def remove_rights(*values)
53
+ if values.size == 0
54
+ @remove_rights
64
55
  else
65
- raise "override requires either a value or a block"
56
+ args = values.pop
57
+ args[:permissions] ||= []
58
+ values.each do |value|
59
+ args[:permissions] |= Array(value)
60
+ end
61
+ @remove_rights ||= []
62
+ @remove_rights << args
66
63
  end
67
64
  end
68
-
69
- alias :attributes :default_attributes
70
- alias :attribute :default
71
65
  end
@@ -0,0 +1,18 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef::Resource::ChefContainer < Chef::Resource::LWRPBase
5
+ self.resource_name = 'chef_container'
6
+
7
+ actions :create, :delete, :nothing
8
+ default_action :create
9
+
10
+ # Grab environment from with_environment
11
+ def initialize(*args)
12
+ super
13
+ chef_server run_context.cheffish.current_chef_server
14
+ end
15
+
16
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
17
+ attribute :chef_server, :kind_of => Hash
18
+ end
@@ -0,0 +1,49 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+ require 'chef/run_list/run_list_item'
4
+
5
+ class Chef::Resource::ChefGroup < Chef::Resource::LWRPBase
6
+ self.resource_name = 'chef_group'
7
+
8
+ actions :create, :delete, :nothing
9
+ default_action :create
10
+
11
+ # Grab environment from with_environment
12
+ def initialize(*args)
13
+ super
14
+ chef_server run_context.cheffish.current_chef_server
15
+ @users = []
16
+ @clients = []
17
+ @groups = []
18
+ @remove_users = []
19
+ @remove_clients = []
20
+ @remove_groups = []
21
+ end
22
+
23
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
24
+ def users(*users)
25
+ users.size == 0 ? @users : (@users |= users.flatten)
26
+ end
27
+ def clients(*clients)
28
+ clients.size == 0 ? @clients : (@clients |= clients.flatten)
29
+ end
30
+ def groups(*groups)
31
+ groups.size == 0 ? @groups : (@groups |= groups.flatten)
32
+ end
33
+ def remove_users(*remove_users)
34
+ remove_users.size == 0 ? @remove_users : (@remove_users |= remove_users.flatten)
35
+ end
36
+ def remove_clients(*remove_clients)
37
+ remove_clients.size == 0 ? @remove_clients : (@remove_clients |= remove_clients.flatten)
38
+ end
39
+ def remove_groups(*remove_groups)
40
+ remove_groups.size == 0 ? @remove_groups : (@remove_groups |= remove_groups.flatten)
41
+ end
42
+
43
+ # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
44
+ # reset to their defaults)
45
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
46
+
47
+ attribute :raw_json, :kind_of => Hash
48
+ attribute :chef_server, :kind_of => Hash
49
+ end
@@ -0,0 +1,47 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef::Resource::ChefMirror < Chef::Resource::LWRPBase
5
+ self.resource_name = 'chef_mirror'
6
+
7
+ actions :upload, :download, :nothing
8
+ default_action :nothing
9
+
10
+ def initialize(*args)
11
+ super
12
+ chef_server run_context.cheffish.current_chef_server
13
+ end
14
+
15
+ # Path of the data to mirror, e.g. nodes, nodes/*, nodes/mynode,
16
+ # */*, **, roles/base, data/secrets, cookbooks/apache2, etc.
17
+ attribute :path, :kind_of => String, :name_attribute => true
18
+
19
+ # Local path. Can be a string (top level of repository) or hash
20
+ # (:chef_repo_path, :node_path, etc.)
21
+ # If neither chef_repo_path nor versioned_cookbooks are set, they default to their
22
+ # Chef::Config values. If chef_repo_path is set but versioned_cookbooks is not,
23
+ # versioned_cookbooks defaults to true.
24
+ attribute :chef_repo_path, :kind_of => [ String, Hash ]
25
+
26
+ # Whether the repo path contains / should contain cookbooks with versioned names,
27
+ # i.e. cookbooks/mysql-1.0.0, cookbooks/mysql-1.2.0, etc.
28
+ attribute :versioned_cookbooks, :kind_of => [ TrueClass, FalseClass ]
29
+
30
+ # Chef server
31
+ attribute :chef_server, :kind_of => Hash
32
+
33
+ # Whether to purge deleted things: if we do not have cookbooks/x locally and we
34
+ # *do* have cookbooks/x remotely, then :upload with purge will delete it.
35
+ # Defaults to false.
36
+ attribute :purge, :kind_of => [ TrueClass, FalseClass ]
37
+
38
+ # Whether to freeze cookbooks on upload
39
+ attribute :freeze, :kind_of => [ TrueClass, FalseClass ]
40
+
41
+ # If this is true, only new files will be copied. File contents will not be
42
+ # diffed, so changed files will never be uploaded.
43
+ attribute :no_diff, :kind_of => [ TrueClass, FalseClass ]
44
+
45
+ # Number of parallel threads to list/upload/download with. Defaults to 10.
46
+ attribute :concurrency, :kind_of => Integer
47
+ end
@@ -0,0 +1,64 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+ require 'chef/run_list/run_list_item'
4
+
5
+ class Chef::Resource::ChefOrganization < Chef::Resource::LWRPBase
6
+ self.resource_name = 'chef_organization'
7
+
8
+ actions :create, :delete, :nothing
9
+ default_action :create
10
+
11
+ # Grab environment from with_environment
12
+ def initialize(*args)
13
+ super
14
+ chef_server run_context.cheffish.current_chef_server
15
+ @invites = nil
16
+ @members = nil
17
+ @remove_members = []
18
+ end
19
+
20
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
21
+ attribute :full_name, :kind_of => String
22
+
23
+ # A list of users who must at least be invited to the org (but may already be
24
+ # members). Invites will be sent to users who are not already invited/in the org.
25
+ def invites(*users)
26
+ if users.size == 0
27
+ @invites || []
28
+ else
29
+ @invites ||= []
30
+ @invites |= users.flatten
31
+ end
32
+ end
33
+
34
+ def invites_specified?
35
+ !!@invites
36
+ end
37
+
38
+ # A list of users who must be members of the org. This will use the new Chef 12
39
+ # POST /organizations/ORG/users/NAME endpoint to add them directly to the org.
40
+ # If you do not have permission to perform this operation, and the users are not
41
+ # a part of the org, the resource update will fail.
42
+ def members(*users)
43
+ if users.size == 0
44
+ @members || []
45
+ else
46
+ @members ||= []
47
+ @members |= users.flatten
48
+ end
49
+ end
50
+
51
+ def members_specified?
52
+ !!@members
53
+ end
54
+
55
+ # A list of users who must not be members of the org. These users will be removed
56
+ # from the org and invites will be revoked (if any).
57
+ def remove_members(*users)
58
+ users.size == 0 ? @remove_members : (@remove_members |= users.flatten)
59
+ end
60
+
61
+ attribute :complete, :kind_of => [ TrueClass, FalseClass ]
62
+ attribute :raw_json, :kind_of => Hash
63
+ attribute :chef_server, :kind_of => Hash
64
+ end
@@ -36,4 +36,9 @@ class Chef::Resource::PrivateKey < Chef::Resource::LWRPBase
36
36
  def after(&block)
37
37
  block ? @after = block : @after
38
38
  end
39
- end
39
+
40
+ # We are not interested in Chef's cloning behavior here.
41
+ def load_prior_resource
42
+ Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
43
+ end
44
+ end
@@ -13,4 +13,9 @@ class Chef::Resource::PublicKey < Chef::Resource::LWRPBase
13
13
  attribute :source_key
14
14
  attribute :source_key_path, :kind_of => String
15
15
  attribute :source_key_pass_phrase
16
+
17
+ # We are not interested in Chef's cloning behavior here.
18
+ def load_prior_resource
19
+ Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
20
+ end
16
21
  end