cheffish 0.7.1 → 0.8

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