cheffish 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +4 -0
- data/Rakefile +23 -0
- data/lib/chef/provider/chef_client.rb +44 -0
- data/lib/chef/provider/chef_data_bag.rb +50 -0
- data/lib/chef/provider/chef_data_bag_item.rb +273 -0
- data/lib/chef/provider/chef_environment.rb +78 -0
- data/lib/chef/provider/chef_node.rb +82 -0
- data/lib/chef/provider/chef_role.rb +79 -0
- data/lib/chef/provider/chef_user.rb +48 -0
- data/lib/chef/provider/private_key.rb +160 -0
- data/lib/chef/provider/public_key.rb +83 -0
- data/lib/chef/resource/chef_client.rb +44 -0
- data/lib/chef/resource/chef_data_bag.rb +18 -0
- data/lib/chef/resource/chef_data_bag_item.rb +114 -0
- data/lib/chef/resource/chef_environment.rb +71 -0
- data/lib/chef/resource/chef_node.rb +18 -0
- data/lib/chef/resource/chef_role.rb +104 -0
- data/lib/chef/resource/chef_user.rb +51 -0
- data/lib/chef/resource/in_parallel.rb +6 -0
- data/lib/chef/resource/private_key.rb +39 -0
- data/lib/chef/resource/public_key.rb +16 -0
- data/lib/cheffish.rb +245 -0
- data/lib/cheffish/actor_provider_base.rb +120 -0
- data/lib/cheffish/chef_provider_base.rb +222 -0
- data/lib/cheffish/cheffish_server_api.rb +21 -0
- data/lib/cheffish/inline_resource.rb +88 -0
- data/lib/cheffish/key_formatter.rb +93 -0
- data/lib/cheffish/recipe_dsl.rb +98 -0
- data/lib/cheffish/version.rb +4 -0
- data/spec/integration/chef_client_spec.rb +48 -0
- data/spec/integration/chef_node_spec.rb +75 -0
- data/spec/integration/chef_user_spec.rb +48 -0
- data/spec/integration/private_key_spec.rb +356 -0
- data/spec/integration/recipe_dsl_spec.rb +29 -0
- data/spec/support/key_support.rb +29 -0
- data/spec/support/spec_support.rb +148 -0
- metadata +124 -0
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'chef/provider/lwrp_base'
|
2
|
+
require 'openssl'
|
3
|
+
require 'cheffish/key_formatter'
|
4
|
+
|
5
|
+
class Chef::Provider::PublicKey < Chef::Provider::LWRPBase
|
6
|
+
|
7
|
+
action :create do
|
8
|
+
if !new_source_key
|
9
|
+
raise "No source key specified"
|
10
|
+
end
|
11
|
+
desired_output = encode_public_key(new_source_key)
|
12
|
+
if Array(current_resource.action) == [ :delete ] || desired_output != IO.read(new_resource.path)
|
13
|
+
converge_by "write #{new_resource.format} public key #{new_resource.path} from #{new_source_key_publicity} key #{new_resource.source_key_path}" do
|
14
|
+
IO.write(new_resource.path, desired_output)
|
15
|
+
# TODO permissions on file?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
action :delete do
|
21
|
+
if Array(current_resource.action) == [ :create ]
|
22
|
+
converge_by "delete public key #{new_resource.path}" do
|
23
|
+
::File.unlink(new_resource.path)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def whyrun_supported?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def encode_public_key(key)
|
33
|
+
key_format = {}
|
34
|
+
key_format[:format] = new_resource.format if new_resource.format
|
35
|
+
Cheffish::KeyFormatter.encode(key, key_format)
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :current_public_key
|
39
|
+
attr_reader :new_source_key_publicity
|
40
|
+
|
41
|
+
def new_source_key
|
42
|
+
@new_source_key ||= begin
|
43
|
+
if new_resource.source_key.is_a?(String)
|
44
|
+
source_key, source_key_format = Cheffish::KeyFormatter.decode(new_resource.source_key, new_resource.source_key_pass_phrase)
|
45
|
+
elsif new_resource.source_key
|
46
|
+
source_key = new_resource.source_key
|
47
|
+
elsif new_resource.source_key_path
|
48
|
+
source_key, source_key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.source_key_path), new_resource.source_key_pass_phrase, new_resource.source_key_path)
|
49
|
+
else
|
50
|
+
return nil
|
51
|
+
end
|
52
|
+
|
53
|
+
if source_key.private?
|
54
|
+
@new_source_key_publicity = 'private'
|
55
|
+
source_key.public_key
|
56
|
+
else
|
57
|
+
@new_source_key_publicity = 'public'
|
58
|
+
source_key
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def load_current_resource
|
64
|
+
if ::File.exist?(new_resource.path)
|
65
|
+
resource = Chef::Resource::PublicKey.new(new_resource.path)
|
66
|
+
begin
|
67
|
+
key, key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.path), nil, new_resource.path)
|
68
|
+
if key
|
69
|
+
@current_public_key = key
|
70
|
+
resource.format key_format[:format]
|
71
|
+
end
|
72
|
+
rescue
|
73
|
+
# If there is an error reading we assume format and such is broken
|
74
|
+
end
|
75
|
+
|
76
|
+
@current_resource = resource
|
77
|
+
else
|
78
|
+
not_found_resource = Chef::Resource::PublicKey.new(new_resource.path)
|
79
|
+
not_found_resource.action :delete
|
80
|
+
@current_resource = not_found_resource
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'cheffish'
|
2
|
+
require 'chef/resource/lwrp_base'
|
3
|
+
|
4
|
+
class Chef::Resource::ChefClient < Chef::Resource::LWRPBase
|
5
|
+
self.resource_name = 'chef_client'
|
6
|
+
|
7
|
+
actions :create, :delete, :regenerate_keys, :nothing
|
8
|
+
default_action :create
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super
|
12
|
+
chef_server Cheffish.enclosing_chef_server
|
13
|
+
end
|
14
|
+
|
15
|
+
# Client attributes
|
16
|
+
attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
|
17
|
+
attribute :admin, :kind_of => [TrueClass, FalseClass]
|
18
|
+
attribute :validator, :kind_of => [TrueClass, FalseClass]
|
19
|
+
|
20
|
+
# Input key
|
21
|
+
attribute :source_key # String or OpenSSL::PKey::*
|
22
|
+
attribute :source_key_path, :kind_of => String
|
23
|
+
attribute :source_key_pass_phrase
|
24
|
+
|
25
|
+
# Output public key (if so desired)
|
26
|
+
attribute :output_key_path, :kind_of => String
|
27
|
+
attribute :output_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
|
28
|
+
|
29
|
+
# If this is set, client is not patchy
|
30
|
+
attribute :complete, :kind_of => [TrueClass, FalseClass]
|
31
|
+
|
32
|
+
attribute :raw_json, :kind_of => Hash
|
33
|
+
attribute :chef_server, :kind_of => Hash
|
34
|
+
|
35
|
+
# Proc that runs just before the resource executes. Called with (resource)
|
36
|
+
def before(&block)
|
37
|
+
block ? @before = block : @before
|
38
|
+
end
|
39
|
+
|
40
|
+
# Proc that runs after the resource completes. Called with (resource, json, private_key, public_key)
|
41
|
+
def after(&block)
|
42
|
+
block ? @after = block : @after
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'cheffish'
|
2
|
+
require 'chef/resource/lwrp_base'
|
3
|
+
|
4
|
+
class Chef::Resource::ChefDataBag < Chef::Resource::LWRPBase
|
5
|
+
self.resource_name = 'chef_data_bag'
|
6
|
+
|
7
|
+
actions :create, :delete, :nothing
|
8
|
+
default_action :create
|
9
|
+
|
10
|
+
def initialize(*args)
|
11
|
+
super
|
12
|
+
chef_server Cheffish.enclosing_chef_server
|
13
|
+
end
|
14
|
+
|
15
|
+
attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
|
16
|
+
|
17
|
+
attribute :chef_server, :kind_of => Hash
|
18
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'cheffish'
|
2
|
+
require 'chef/config'
|
3
|
+
require 'chef/resource/lwrp_base'
|
4
|
+
|
5
|
+
class Chef::Resource::ChefDataBagItem < Chef::Resource::LWRPBase
|
6
|
+
self.resource_name = 'chef_data_bag_item'
|
7
|
+
|
8
|
+
actions :create, :delete, :nothing
|
9
|
+
default_action :create
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super
|
13
|
+
name @name
|
14
|
+
if !data_bag
|
15
|
+
data_bag Cheffish.enclosing_data_bag
|
16
|
+
end
|
17
|
+
if Cheffish.enclosing_data_bag_item_encryption
|
18
|
+
@encrypt = true if Cheffish.enclosing_data_bag_item_encryption[:encrypt_all]
|
19
|
+
@secret = Cheffish.enclosing_data_bag_item_encryption[:secret]
|
20
|
+
@secret_path = Cheffish.enclosing_data_bag_item_encryption[:secret_path] || Chef::Config[:encrypted_data_bag_secret]
|
21
|
+
@encryption_cipher = Cheffish.enclosing_data_bag_item_encryption[:encryption_cipher]
|
22
|
+
@encryption_version = Cheffish.enclosing_data_bag_item_encryption[:encryption_version]
|
23
|
+
@old_secret = Cheffish.enclosing_data_bag_item_encryption[:old_secret]
|
24
|
+
@old_secret_path = Cheffish.enclosing_data_bag_item_encryption[:old_secret_path]
|
25
|
+
end
|
26
|
+
chef_server Cheffish.enclosing_chef_server
|
27
|
+
end
|
28
|
+
|
29
|
+
def name(*args)
|
30
|
+
result = super(*args)
|
31
|
+
if args.size == 1
|
32
|
+
parts = name.split('/')
|
33
|
+
if parts.size == 1
|
34
|
+
@id = parts[0]
|
35
|
+
elsif parts.size == 2
|
36
|
+
@data_bag = parts[0]
|
37
|
+
@id = parts[1]
|
38
|
+
else
|
39
|
+
raise "Name #{args[0].inspect} must be a string with 1 or 2 parts, either 'id' or 'data_bag/id"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
result
|
43
|
+
end
|
44
|
+
|
45
|
+
NOT_PASSED = Object.new
|
46
|
+
def id(value = NOT_PASSED)
|
47
|
+
if value == NOT_PASSED
|
48
|
+
@id
|
49
|
+
else
|
50
|
+
@id = value
|
51
|
+
name data_bag ? "#{data_bag}/#{id}" : id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
def data_bag(value = NOT_PASSED)
|
55
|
+
if value == NOT_PASSED
|
56
|
+
@data_bag
|
57
|
+
else
|
58
|
+
@data_bag = value
|
59
|
+
name data_bag ? "#{data_bag}/#{id}" : id
|
60
|
+
end
|
61
|
+
end
|
62
|
+
attribute :raw_data, :kind_of => Hash
|
63
|
+
|
64
|
+
# If secret or secret_path are set, encrypt is assumed true. encrypt exists mainly for with_secret and with_secret_path
|
65
|
+
attribute :encrypt, :kind_of => [TrueClass, FalseClass]
|
66
|
+
#attribute :secret, :kind_of => String
|
67
|
+
def secret(new_secret = nil)
|
68
|
+
if !new_secret
|
69
|
+
@secret
|
70
|
+
else
|
71
|
+
@secret = new_secret
|
72
|
+
@encrypt = true if @encrypt.nil?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
#attribute :secret_path, :kind_of => String
|
76
|
+
def secret_path(new_secret_path = nil)
|
77
|
+
if !new_secret_path
|
78
|
+
@secret_path
|
79
|
+
else
|
80
|
+
@secret_path = new_secret_path
|
81
|
+
@encrypt = true if @encrypt.nil?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
attribute :encryption_version, :kind_of => Integer, :default => Chef::Config[:data_bag_encrypt_version]
|
85
|
+
|
86
|
+
# Old secret (or secrets) to read the old data bag when we are changing keys and re-encrypting data
|
87
|
+
attribute :old_secret, :kind_of => [String, Array]
|
88
|
+
attribute :old_secret_path, :kind_of => [String, Array]
|
89
|
+
|
90
|
+
# Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
|
91
|
+
# reset to their defaults)
|
92
|
+
attribute :complete, :kind_of => [TrueClass, FalseClass]
|
93
|
+
|
94
|
+
attribute :raw_json, :kind_of => Hash
|
95
|
+
attribute :chef_server, :kind_of => Hash
|
96
|
+
|
97
|
+
# value 'ip_address', '127.0.0.1'
|
98
|
+
# value [ 'pushy', 'port' ], '9000'
|
99
|
+
# value 'ip_addresses' do |existing_value|
|
100
|
+
# (existing_value || []) + [ '127.0.0.1' ]
|
101
|
+
# end
|
102
|
+
# value 'ip_address', :delete
|
103
|
+
attr_reader :raw_data_modifiers
|
104
|
+
def value(raw_data_path, value=NOT_PASSED, &block)
|
105
|
+
@raw_data_modifiers ||= []
|
106
|
+
if value != NOT_PASSED
|
107
|
+
@raw_data_modifiers << [ raw_data_path, value ]
|
108
|
+
elsif block
|
109
|
+
@raw_data_modifiers << [ raw_data_path, block ]
|
110
|
+
else
|
111
|
+
raise "value requires either a value or a block"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'cheffish'
|
2
|
+
require 'chef/resource/lwrp_base'
|
3
|
+
require 'chef/environment'
|
4
|
+
|
5
|
+
class Chef::Resource::ChefEnvironment < Chef::Resource::LWRPBase
|
6
|
+
self.resource_name = 'chef_environment'
|
7
|
+
|
8
|
+
actions :create, :delete, :nothing
|
9
|
+
default_action :create
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super
|
13
|
+
chef_server Cheffish.enclosing_chef_server
|
14
|
+
end
|
15
|
+
|
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
|
23
|
+
|
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)
|
26
|
+
attribute :complete, :kind_of => [TrueClass, FalseClass]
|
27
|
+
|
28
|
+
attribute :raw_json, :kind_of => Hash
|
29
|
+
attribute :chef_server, :kind_of => Hash
|
30
|
+
|
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 ]
|
46
|
+
else
|
47
|
+
raise "default requires either a value or a block"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
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 ]
|
64
|
+
else
|
65
|
+
raise "override requires either a value or a block"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
alias :attributes :default_attributes
|
70
|
+
alias :attribute :default
|
71
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'cheffish'
|
2
|
+
require 'chef/resource/lwrp_base'
|
3
|
+
|
4
|
+
class Chef::Resource::ChefNode < Chef::Resource::LWRPBase
|
5
|
+
self.resource_name = 'chef_node'
|
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_environment Cheffish.enclosing_environment
|
14
|
+
chef_server Cheffish.enclosing_chef_server
|
15
|
+
end
|
16
|
+
|
17
|
+
Cheffish.node_attributes(self)
|
18
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'cheffish'
|
2
|
+
require 'chef/resource/lwrp_base'
|
3
|
+
require 'chef/run_list/run_list_item'
|
4
|
+
|
5
|
+
class Chef::Resource::ChefRole < Chef::Resource::LWRPBase
|
6
|
+
self.resource_name = 'chef_role'
|
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 Cheffish.enclosing_chef_server
|
15
|
+
end
|
16
|
+
|
17
|
+
attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
|
18
|
+
attribute :description, :kind_of => String
|
19
|
+
attribute :run_list, :kind_of => Array # We should let them specify it as a series of parameters too
|
20
|
+
attribute :env_run_lists, :kind_of => Hash
|
21
|
+
attribute :default_attributes, :kind_of => Hash
|
22
|
+
attribute :override_attributes, :kind_of => Hash
|
23
|
+
|
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)
|
26
|
+
attribute :complete, :kind_of => [TrueClass, FalseClass]
|
27
|
+
|
28
|
+
attribute :raw_json, :kind_of => Hash
|
29
|
+
attribute :chef_server, :kind_of => Hash
|
30
|
+
|
31
|
+
NOT_PASSED=Object.new
|
32
|
+
|
33
|
+
# default_attribute 'ip_address', '127.0.0.1'
|
34
|
+
# default_attribute [ 'pushy', 'port' ], '9000'
|
35
|
+
# default_attribute 'ip_addresses' do |existing_value|
|
36
|
+
# (existing_value || []) + [ '127.0.0.1' ]
|
37
|
+
# end
|
38
|
+
# default_attribute 'ip_address', :delete
|
39
|
+
attr_reader :default_attribute_modifiers
|
40
|
+
def default_attribute(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 ]
|
46
|
+
else
|
47
|
+
raise "default_attribute requires either a value or a block"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# override_attribute 'ip_address', '127.0.0.1'
|
52
|
+
# override_attribute [ 'pushy', 'port' ], '9000'
|
53
|
+
# override_attribute 'ip_addresses' do |existing_value|
|
54
|
+
# (existing_value || []) + [ '127.0.0.1' ]
|
55
|
+
# end
|
56
|
+
# override_attribute 'ip_address', :delete
|
57
|
+
attr_reader :override_attribute_modifiers
|
58
|
+
def override_attribute(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 ]
|
64
|
+
else
|
65
|
+
raise "override_attribute requires either a value or a block"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Order matters--if two things here are in the wrong order, they will be flipped in the run list
|
70
|
+
# recipe 'apache', 'mysql'
|
71
|
+
# recipe 'recipe@version'
|
72
|
+
# recipe 'recipe'
|
73
|
+
# role ''
|
74
|
+
attr_reader :run_list_modifiers
|
75
|
+
attr_reader :run_list_removers
|
76
|
+
def recipe(*recipes)
|
77
|
+
if recipes.size == 0
|
78
|
+
raise ArgumentError, "At least one recipe must be specified"
|
79
|
+
end
|
80
|
+
@run_list_modifiers ||= []
|
81
|
+
@run_list_modifiers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
|
82
|
+
end
|
83
|
+
def role(*roles)
|
84
|
+
if roles.size == 0
|
85
|
+
raise ArgumentError, "At least one role must be specified"
|
86
|
+
end
|
87
|
+
@run_list_modifiers ||= []
|
88
|
+
@run_list_modifiers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
|
89
|
+
end
|
90
|
+
def remove_recipe(*recipes)
|
91
|
+
if recipes.size == 0
|
92
|
+
raise ArgumentError, "At least one recipe must be specified"
|
93
|
+
end
|
94
|
+
@run_list_removers ||= []
|
95
|
+
@run_list_removers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
|
96
|
+
end
|
97
|
+
def remove_role(*roles)
|
98
|
+
if roles.size == 0
|
99
|
+
raise ArgumentError, "At least one role must be specified"
|
100
|
+
end
|
101
|
+
@run_list_removers ||= []
|
102
|
+
@run_list_removers += roles.map { |recipe| Chef::RunList::RunListItem.new("role[#{role}]") }
|
103
|
+
end
|
104
|
+
end
|