clc-cheffish 0.8.clc

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