clc-cheffish 0.8.clc

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +201 -0
  3. data/README.md +4 -0
  4. data/Rakefile +23 -0
  5. data/lib/chef/provider/chef_acl.rb +434 -0
  6. data/lib/chef/provider/chef_client.rb +48 -0
  7. data/lib/chef/provider/chef_container.rb +50 -0
  8. data/lib/chef/provider/chef_data_bag.rb +50 -0
  9. data/lib/chef/provider/chef_data_bag_item.rb +273 -0
  10. data/lib/chef/provider/chef_environment.rb +78 -0
  11. data/lib/chef/provider/chef_group.rb +78 -0
  12. data/lib/chef/provider/chef_mirror.rb +138 -0
  13. data/lib/chef/provider/chef_node.rb +82 -0
  14. data/lib/chef/provider/chef_organization.rb +150 -0
  15. data/lib/chef/provider/chef_resolved_cookbooks.rb +41 -0
  16. data/lib/chef/provider/chef_role.rb +79 -0
  17. data/lib/chef/provider/chef_user.rb +53 -0
  18. data/lib/chef/provider/private_key.rb +219 -0
  19. data/lib/chef/provider/public_key.rb +82 -0
  20. data/lib/chef/resource/chef_acl.rb +65 -0
  21. data/lib/chef/resource/chef_client.rb +44 -0
  22. data/lib/chef/resource/chef_container.rb +18 -0
  23. data/lib/chef/resource/chef_data_bag.rb +18 -0
  24. data/lib/chef/resource/chef_data_bag_item.rb +114 -0
  25. data/lib/chef/resource/chef_environment.rb +71 -0
  26. data/lib/chef/resource/chef_group.rb +49 -0
  27. data/lib/chef/resource/chef_mirror.rb +47 -0
  28. data/lib/chef/resource/chef_node.rb +18 -0
  29. data/lib/chef/resource/chef_organization.rb +64 -0
  30. data/lib/chef/resource/chef_resolved_cookbooks.rb +31 -0
  31. data/lib/chef/resource/chef_role.rb +104 -0
  32. data/lib/chef/resource/chef_user.rb +51 -0
  33. data/lib/chef/resource/private_key.rb +44 -0
  34. data/lib/chef/resource/public_key.rb +21 -0
  35. data/lib/cheffish.rb +222 -0
  36. data/lib/cheffish/actor_provider_base.rb +131 -0
  37. data/lib/cheffish/basic_chef_client.rb +115 -0
  38. data/lib/cheffish/chef_provider_base.rb +231 -0
  39. data/lib/cheffish/chef_run_data.rb +19 -0
  40. data/lib/cheffish/chef_run_listener.rb +28 -0
  41. data/lib/cheffish/key_formatter.rb +109 -0
  42. data/lib/cheffish/merged_config.rb +94 -0
  43. data/lib/cheffish/recipe_dsl.rb +147 -0
  44. data/lib/cheffish/server_api.rb +52 -0
  45. data/lib/cheffish/version.rb +3 -0
  46. data/lib/cheffish/with_pattern.rb +21 -0
  47. data/spec/functional/fingerprint_spec.rb +64 -0
  48. data/spec/functional/merged_config_spec.rb +20 -0
  49. data/spec/integration/chef_acl_spec.rb +914 -0
  50. data/spec/integration/chef_client_spec.rb +110 -0
  51. data/spec/integration/chef_container_spec.rb +34 -0
  52. data/spec/integration/chef_group_spec.rb +324 -0
  53. data/spec/integration/chef_mirror_spec.rb +244 -0
  54. data/spec/integration/chef_node_spec.rb +211 -0
  55. data/spec/integration/chef_organization_spec.rb +244 -0
  56. data/spec/integration/chef_user_spec.rb +90 -0
  57. data/spec/integration/private_key_spec.rb +446 -0
  58. data/spec/integration/recipe_dsl_spec.rb +29 -0
  59. data/spec/support/key_support.rb +29 -0
  60. data/spec/support/repository_support.rb +103 -0
  61. data/spec/support/spec_support.rb +176 -0
  62. data/spec/unit/get_private_key_spec.rb +93 -0
  63. metadata +162 -0
@@ -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
@@ -0,0 +1,31 @@
1
+ require 'chef/resource/lwrp_base'
2
+
3
+ class Chef::Resource::ChefResolvedCookbooks < Chef::Resource::LWRPBase
4
+ self.resource_name = 'chef_resolved_cookbooks'
5
+
6
+ actions :resolve, :nothing
7
+ default_action :resolve
8
+
9
+ def initialize(*args)
10
+ super
11
+ require 'berkshelf'
12
+ berksfile Berkshelf::Berksfile.new('/tmp/Berksfile')
13
+ chef_server run_context.cheffish.current_chef_server
14
+ @cookbooks_from = []
15
+ end
16
+
17
+ extend Forwardable
18
+
19
+ def_delegators :@berksfile, :cookbook, :extension, :group, :metadata, :source
20
+
21
+ def cookbooks_from(path = nil)
22
+ if path
23
+ @cookbooks_from << path
24
+ else
25
+ @cookbooks_from
26
+ end
27
+ end
28
+
29
+ attribute :berksfile
30
+ attribute :chef_server
31
+ 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 run_context.cheffish.current_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
@@ -0,0 +1,51 @@
1
+ require 'cheffish'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef::Resource::ChefUser < Chef::Resource::LWRPBase
5
+ self.resource_name = 'chef_user'
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
+ # Client attributes
17
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
18
+ attribute :admin, :kind_of => [TrueClass, FalseClass]
19
+ attribute :email, :kind_of => String
20
+ attribute :external_authentication_uid
21
+ attribute :recovery_authentication_enabled, :kind_of => [TrueClass, FalseClass]
22
+ attribute :password, :kind_of => String # Hmm. There is no way to idempotentize this.
23
+ #attribute :salt # TODO server doesn't support sending or receiving these, but it's the only way to backup / restore a user
24
+ #attribute :hashed_password
25
+ #attribute :hash_type
26
+
27
+ # Input key
28
+ attribute :source_key # String or OpenSSL::PKey::*
29
+ attribute :source_key_path, :kind_of => String
30
+ attribute :source_key_pass_phrase
31
+
32
+ # Output public key (if so desired)
33
+ attribute :output_key_path, :kind_of => String
34
+ attribute :output_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
35
+
36
+ # If this is set, client is not patchy
37
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
38
+
39
+ attribute :raw_json, :kind_of => Hash
40
+ attribute :chef_server, :kind_of => Hash
41
+
42
+ # Proc that runs just before the resource executes. Called with (resource)
43
+ def before(&block)
44
+ block ? @before = block : @before
45
+ end
46
+
47
+ # Proc that runs after the resource completes. Called with (resource, json, private_key, public_key)
48
+ def after(&block)
49
+ block ? @after = block : @after
50
+ end
51
+ end
@@ -0,0 +1,44 @@
1
+ require 'openssl/cipher'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef::Resource::PrivateKey < Chef::Resource::LWRPBase
5
+ self.resource_name = 'private_key'
6
+
7
+ actions :create, :delete, :regenerate, :nothing
8
+ default_action :create
9
+
10
+ # Path to private key. Set to :none to create the key in memory and not on disk.
11
+ attribute :path, :kind_of => [ String, Symbol ], :name_attribute => true
12
+ attribute :format, :kind_of => Symbol, :default => :pem, :equal_to => [ :pem, :der ]
13
+ attribute :type, :kind_of => Symbol, :default => :rsa, :equal_to => [ :rsa, :dsa ] # TODO support :ec
14
+ # These specify an optional public_key you can spit out if you want.
15
+ attribute :public_key_path, :kind_of => String
16
+ attribute :public_key_format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :openssh, :pem, :der ]
17
+ # Specify this if you want to copy another private key but give it a different format / password
18
+ attribute :source_key
19
+ attribute :source_key_path, :kind_of => String
20
+ attribute :source_key_pass_phrase
21
+
22
+ # RSA and DSA
23
+ attribute :size, :kind_of => Integer, :default => 2048
24
+
25
+ # RSA-only
26
+ attribute :exponent, :kind_of => Integer # For RSA
27
+
28
+ # PEM-only
29
+ attribute :pass_phrase, :kind_of => String
30
+ attribute :cipher, :kind_of => String, :default => 'DES-EDE3-CBC', :equal_to => OpenSSL::Cipher.ciphers
31
+
32
+ # Set this to regenerate the key if it does not have the desired characteristics (like size, type, etc.)
33
+ attribute :regenerate_if_different, :kind_of => [TrueClass, FalseClass]
34
+
35
+ # Proc that runs after the resource completes. Called with (resource, private_key)
36
+ def after(&block)
37
+ block ? @after = block : @after
38
+ 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
@@ -0,0 +1,21 @@
1
+ require 'openssl/cipher'
2
+ require 'chef/resource/lwrp_base'
3
+
4
+ class Chef::Resource::PublicKey < Chef::Resource::LWRPBase
5
+ self.resource_name = 'public_key'
6
+
7
+ actions :create, :delete, :nothing
8
+ default_action :create
9
+
10
+ attribute :path, :kind_of => String, :name_attribute => true
11
+ attribute :format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
12
+
13
+ attribute :source_key
14
+ attribute :source_key_path, :kind_of => String
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
21
+ end
data/lib/cheffish.rb ADDED
@@ -0,0 +1,222 @@
1
+ require 'chef/run_list/run_list_item'
2
+ require 'cheffish/basic_chef_client'
3
+ require 'cheffish/server_api'
4
+ require 'chef/knife'
5
+ require 'chef/config_fetcher'
6
+ require 'chef/log'
7
+ require 'chef/application'
8
+
9
+ module Cheffish
10
+ NAME_REGEX = /^[.\-[:alnum:]_]+$/
11
+
12
+ def self.inline_resource(provider, provider_action, *resources, &block)
13
+ BasicChefClient.inline_resource(provider, provider_action, *resources, &block)
14
+ end
15
+
16
+ def self.default_chef_server(config = profiled_config)
17
+ {
18
+ :chef_server_url => config[:chef_server_url],
19
+ :options => {
20
+ :client_name => config[:node_name],
21
+ :signing_key_filename => config[:client_key]
22
+ }
23
+ }
24
+ end
25
+
26
+ def self.chef_server_api(chef_server = default_chef_server)
27
+ Cheffish::ServerAPI.new(chef_server[:chef_server_url], chef_server[:options] || {})
28
+ end
29
+
30
+ def self.profiled_config(config = Chef::Config)
31
+ if config.profile && config.profiles && config.profiles[config.profile]
32
+ MergedConfig.new(config.profiles[config.profile], config)
33
+ else
34
+ config
35
+ end
36
+ end
37
+
38
+ def self.load_chef_config(chef_config = Chef::Config)
39
+ chef_config.config_file = Chef::Knife.locate_config_file
40
+ config_fetcher = Chef::ConfigFetcher.new(chef_config.config_file, chef_config.config_file_jail)
41
+ if chef_config.config_file.nil?
42
+ Chef::Log.warn("No config file found or specified on command line, using command line options.")
43
+ elsif config_fetcher.config_missing?
44
+ Chef::Log.warn("Did not find config file: #{chef_config.config_file}, using command line options.")
45
+ else
46
+ config_content = config_fetcher.read_config
47
+ config_file_path = chef_config.config_file
48
+ begin
49
+ chef_config.from_string(config_content, config_file_path)
50
+ rescue Exception => error
51
+ Chef::Log.fatal("Configuration error #{error.class}: #{error.message}")
52
+ filtered_trace = error.backtrace.grep(/#{Regexp.escape(config_file_path)}/)
53
+ filtered_trace.each {|line| Chef::Log.fatal(" " + line )}
54
+ Chef::Application.fatal!("Aborting due to error in '#{config_file_path}'", 2)
55
+ end
56
+ end
57
+ Cheffish.profiled_config(chef_config)
58
+ end
59
+
60
+ def self.honor_local_mode(local_mode_default = true)
61
+ if !Chef::Config.has_key?(:local_mode) && !local_mode_default.nil?
62
+ Chef::Config.local_mode = local_mode_default
63
+ end
64
+ if Chef::Config.local_mode && !Chef::Config.has_key?(:cookbook_path) && !Chef::Config.has_key?(:chef_repo_path)
65
+ Chef::Config.chef_repo_path = Chef::Config.find_chef_repo_path(Dir.pwd)
66
+ end
67
+ begin
68
+ require 'chef/local_mode'
69
+ Chef::LocalMode.with_server_connectivity(&block)
70
+
71
+ rescue LoadError
72
+ Chef::Application.setup_server_connectivity
73
+ if block_given?
74
+ begin
75
+ yield
76
+ ensure
77
+ Chef::Application.destroy_server_connectivity
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ def self.get_private_key(name, config = profiled_config)
84
+ key, key_path = get_private_key_with_path(name, config)
85
+ key
86
+ end
87
+
88
+ def self.get_private_key_with_path(name, config = profiled_config)
89
+ if config[:private_keys] && config[:private_keys][name]
90
+ if config[:private_keys][name].is_a?(String)
91
+ return [ IO.read(config[:private_keys][name]), config[:private_keys][name] ]
92
+ else
93
+ return [ config[:private_keys][name].to_pem, nil ]
94
+ end
95
+ elsif config[:private_key_paths]
96
+ config[:private_key_paths].each do |private_key_path|
97
+ next unless File.exist?(private_key_path)
98
+ Dir.entries(private_key_path).sort.each do |key|
99
+ ext = File.extname(key)
100
+ if ext == '' || ext == '.pem'
101
+ key_name = key[0..-(ext.length+1)]
102
+ if key_name == name
103
+ return [ IO.read("#{private_key_path}/#{key}"), "#{private_key_path}/#{key}" ]
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ nil
110
+ end
111
+
112
+ NOT_PASSED=Object.new
113
+
114
+ def self.node_attributes(klass)
115
+ klass.class_eval do
116
+ attribute :name, :kind_of => String, :regex => Cheffish::NAME_REGEX, :name_attribute => true
117
+ attribute :chef_environment, :kind_of => String, :regex => Cheffish::NAME_REGEX
118
+ attribute :run_list, :kind_of => Array # We should let them specify it as a series of parameters too
119
+ attribute :attributes, :kind_of => Hash
120
+
121
+ # Specifies that this is a complete specification for the environment (i.e. attributes you don't specify will be
122
+ # reset to their defaults)
123
+ attribute :complete, :kind_of => [TrueClass, FalseClass]
124
+
125
+ attribute :raw_json, :kind_of => Hash
126
+ attribute :chef_server, :kind_of => Hash
127
+
128
+ # attribute 'ip_address', '127.0.0.1'
129
+ # attribute [ 'pushy', 'port' ], '9000'
130
+ # attribute 'ip_addresses' do |existing_value|
131
+ # (existing_value || []) + [ '127.0.0.1' ]
132
+ # end
133
+ # attribute 'ip_address', :delete
134
+ attr_accessor :attribute_modifiers
135
+ def attribute(attribute_path, value=NOT_PASSED, &block)
136
+ @attribute_modifiers ||= []
137
+ if value != NOT_PASSED
138
+ @attribute_modifiers << [ attribute_path, value ]
139
+ elsif block
140
+ @attribute_modifiers << [ attribute_path, block ]
141
+ else
142
+ raise "attribute requires either a value or a block"
143
+ end
144
+ end
145
+
146
+ # Patchy tags
147
+ # tag 'webserver', 'apache', 'myenvironment'
148
+ def tag(*tags)
149
+ attribute 'tags' do |existing_tags|
150
+ existing_tags ||= []
151
+ tags.each do |tag|
152
+ if !existing_tags.include?(tag.to_s)
153
+ existing_tags << tag.to_s
154
+ end
155
+ end
156
+ existing_tags
157
+ end
158
+ end
159
+ def remove_tag(*tags)
160
+ attribute 'tags' do |existing_tags|
161
+ if existing_tags
162
+ tags.each do |tag|
163
+ existing_tags.delete(tag.to_s)
164
+ end
165
+ end
166
+ existing_tags
167
+ end
168
+ end
169
+
170
+ # NON-patchy tags
171
+ # tags :a, :b, :c # removes all other tags
172
+ def tags(*tags)
173
+ if tags.size == 0
174
+ attribute('tags')
175
+ else
176
+ tags = tags[0] if tags.size == 1 && tags[0].kind_of?(Array)
177
+ attribute 'tags', tags.map { |tag| tag.to_s }
178
+ end
179
+ end
180
+
181
+ # Order matters--if two things here are in the wrong order, they will be flipped in the run list
182
+ # recipe 'apache', 'mysql'
183
+ # recipe 'recipe@version'
184
+ # recipe 'recipe'
185
+ # role ''
186
+ attr_accessor :run_list_modifiers
187
+ attr_accessor :run_list_removers
188
+ def recipe(*recipes)
189
+ if recipes.size == 0
190
+ raise ArgumentError, "At least one recipe must be specified"
191
+ end
192
+ @run_list_modifiers ||= []
193
+ @run_list_modifiers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
194
+ end
195
+ def role(*roles)
196
+ if roles.size == 0
197
+ raise ArgumentError, "At least one role must be specified"
198
+ end
199
+ @run_list_modifiers ||= []
200
+ @run_list_modifiers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
201
+ end
202
+ def remove_recipe(*recipes)
203
+ if recipes.size == 0
204
+ raise ArgumentError, "At least one recipe must be specified"
205
+ end
206
+ @run_list_removers ||= []
207
+ @run_list_removers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
208
+ end
209
+ def remove_role(*roles)
210
+ if roles.size == 0
211
+ raise ArgumentError, "At least one role must be specified"
212
+ end
213
+ @run_list_removers ||= []
214
+ @run_list_removers += roles.map { |recipe| Chef::RunList::RunListItem.new("role[#{role}]") }
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ # Include all recipe objects so require 'cheffish' brings in the whole recipe DSL
221
+
222
+ require 'cheffish/recipe_dsl'