cheffish 1.6.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/cheffish.gemspec +1 -0
  3. data/lib/chef/resource/chef_acl.rb +440 -20
  4. data/lib/chef/resource/chef_client.rb +50 -25
  5. data/lib/chef/resource/chef_container.rb +44 -11
  6. data/lib/chef/resource/chef_data_bag.rb +43 -10
  7. data/lib/chef/resource/chef_data_bag_item.rb +292 -82
  8. data/lib/chef/resource/chef_environment.rb +79 -27
  9. data/lib/chef/resource/chef_group.rb +77 -40
  10. data/lib/chef/resource/chef_mirror.rb +170 -21
  11. data/lib/chef/resource/chef_node.rb +77 -11
  12. data/lib/chef/resource/chef_organization.rb +153 -43
  13. data/lib/chef/resource/chef_resolved_cookbooks.rb +40 -9
  14. data/lib/chef/resource/chef_role.rb +81 -29
  15. data/lib/chef/resource/chef_user.rb +64 -33
  16. data/lib/chef/resource/private_key.rb +230 -17
  17. data/lib/chef/resource/public_key.rb +88 -9
  18. data/lib/cheffish/array_property.rb +29 -0
  19. data/lib/cheffish/base_resource.rb +254 -0
  20. data/lib/cheffish/chef_actor_base.rb +135 -0
  21. data/lib/cheffish/node_properties.rb +107 -0
  22. data/lib/cheffish/recipe_dsl.rb +0 -14
  23. data/lib/cheffish/version.rb +1 -1
  24. data/lib/cheffish.rb +4 -108
  25. data/spec/integration/chef_acl_spec.rb +0 -2
  26. data/spec/integration/chef_client_spec.rb +0 -1
  27. data/spec/integration/chef_container_spec.rb +0 -2
  28. data/spec/integration/chef_group_spec.rb +0 -2
  29. data/spec/integration/chef_mirror_spec.rb +0 -2
  30. data/spec/integration/chef_node_spec.rb +0 -2
  31. data/spec/integration/chef_organization_spec.rb +1 -3
  32. data/spec/integration/chef_role_spec.rb +0 -2
  33. data/spec/integration/chef_user_spec.rb +0 -2
  34. data/spec/integration/private_key_spec.rb +0 -4
  35. data/spec/integration/recipe_dsl_spec.rb +0 -2
  36. data/spec/support/spec_support.rb +0 -1
  37. data/spec/unit/get_private_key_spec.rb +13 -0
  38. metadata +22 -20
  39. data/lib/chef/provider/chef_acl.rb +0 -446
  40. data/lib/chef/provider/chef_client.rb +0 -53
  41. data/lib/chef/provider/chef_container.rb +0 -55
  42. data/lib/chef/provider/chef_data_bag.rb +0 -55
  43. data/lib/chef/provider/chef_data_bag_item.rb +0 -278
  44. data/lib/chef/provider/chef_environment.rb +0 -83
  45. data/lib/chef/provider/chef_group.rb +0 -83
  46. data/lib/chef/provider/chef_mirror.rb +0 -169
  47. data/lib/chef/provider/chef_node.rb +0 -87
  48. data/lib/chef/provider/chef_organization.rb +0 -155
  49. data/lib/chef/provider/chef_resolved_cookbooks.rb +0 -46
  50. data/lib/chef/provider/chef_role.rb +0 -84
  51. data/lib/chef/provider/chef_user.rb +0 -59
  52. data/lib/chef/provider/private_key.rb +0 -225
  53. data/lib/chef/provider/public_key.rb +0 -88
  54. data/lib/cheffish/actor_provider_base.rb +0 -131
  55. data/lib/cheffish/chef_provider_base.rb +0 -246
@@ -1,25 +1,104 @@
1
1
  require 'openssl/cipher'
2
- require 'chef/resource/lwrp_base'
2
+ require 'cheffish/base_resource'
3
+ require 'openssl'
4
+ require 'cheffish/key_formatter'
3
5
 
4
6
  class Chef
5
7
  class Resource
6
- class PublicKey < Chef::Resource::LWRPBase
7
- self.resource_name = 'public_key'
8
+ class PublicKey < Cheffish::BaseResource
9
+ resource_name :public_key
8
10
 
9
- actions :create, :delete, :nothing
11
+ allowed_actions :create, :delete, :nothing
10
12
  default_action :create
11
13
 
12
- attribute :path, :kind_of => String, :name_attribute => true
13
- attribute :format, :kind_of => Symbol, :default => :openssh, :equal_to => [ :pem, :der, :openssh ]
14
+ property :path, String, name_property: true
15
+ property :format, [ :pem, :der, :openssh ], default: :openssh
14
16
 
15
- attribute :source_key
16
- attribute :source_key_path, :kind_of => String
17
- attribute :source_key_pass_phrase
17
+ property :source_key
18
+ property :source_key_path, String
19
+ property :source_key_pass_phrase
18
20
 
19
21
  # We are not interested in Chef's cloning behavior here.
20
22
  def load_prior_resource(*args)
21
23
  Chef::Log.debug("Overloading #{resource_name}.load_prior_resource with NOOP")
22
24
  end
25
+
26
+
27
+ action :create do
28
+ if !new_source_key
29
+ raise "No source key specified"
30
+ end
31
+ desired_output = encode_public_key(new_source_key)
32
+ if Array(current_resource.action) == [ :delete ] || desired_output != IO.read(new_resource.path)
33
+ converge_by "write #{new_resource.format} public key #{new_resource.path} from #{new_source_key_publicity} key #{new_resource.source_key_path}" do
34
+ IO.write(new_resource.path, desired_output)
35
+ # TODO permissions on file?
36
+ end
37
+ end
38
+ end
39
+
40
+ action :delete do
41
+ if Array(current_resource.action) == [ :create ]
42
+ converge_by "delete public key #{new_resource.path}" do
43
+ ::File.unlink(new_resource.path)
44
+ end
45
+ end
46
+ end
47
+
48
+ action_class.class_eval do
49
+ def encode_public_key(key)
50
+ key_format = {}
51
+ key_format[:format] = new_resource.format if new_resource.format
52
+ Cheffish::KeyFormatter.encode(key, key_format)
53
+ end
54
+
55
+ attr_reader :current_public_key
56
+ attr_reader :new_source_key_publicity
57
+
58
+ def new_source_key
59
+ @new_source_key ||= begin
60
+ if new_resource.source_key.is_a?(String)
61
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(new_resource.source_key, new_resource.source_key_pass_phrase)
62
+ elsif new_resource.source_key
63
+ source_key = new_resource.source_key
64
+ elsif new_resource.source_key_path
65
+ 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)
66
+ else
67
+ return nil
68
+ end
69
+
70
+ if source_key.private?
71
+ @new_source_key_publicity = 'private'
72
+ source_key.public_key
73
+ else
74
+ @new_source_key_publicity = 'public'
75
+ source_key
76
+ end
77
+ end
78
+ end
79
+
80
+ def load_current_resource
81
+ if ::File.exist?(new_resource.path)
82
+ resource = Chef::Resource::PublicKey.new(new_resource.path, run_context)
83
+ begin
84
+ key, key_format = Cheffish::KeyFormatter.decode(IO.read(new_resource.path), nil, new_resource.path)
85
+ if key
86
+ @current_public_key = key
87
+ resource.format key_format[:format]
88
+ end
89
+ rescue
90
+ # If there is an error reading we assume format and such is broken
91
+ end
92
+
93
+ @current_resource = resource
94
+ else
95
+ not_found_resource = Chef::Resource::PublicKey.new(new_resource.path, run_context)
96
+ not_found_resource.action :delete
97
+ @current_resource = not_found_resource
98
+ end
99
+ end
100
+ end
101
+
23
102
  end
24
103
  end
25
104
  end
@@ -0,0 +1,29 @@
1
+ require 'chef_compat/property'
2
+
3
+ module Cheffish
4
+ # A typical array property. Defaults to [], accepts multiple args to setter, accumulates values.
5
+ class ArrayProperty < ChefCompat::Property
6
+ def initialize(**options)
7
+ options[:is] ||= Array
8
+ options[:default] ||= []
9
+ options[:coerce] ||= proc { |v| v.is_a?(Array) ? v : [ v ] }
10
+ super
11
+ end
12
+
13
+ # Support my_property 'a', 'b', 'c'; my_property 'a'; and my_property ['a', 'b']
14
+ def emit_dsl
15
+ declared_in.class_eval(<<-EOM, __FILE__, __LINE__+1)
16
+ def #{name}(*values)
17
+ property = self.class.properties[#{name.inspect}]
18
+ if values.empty?
19
+ property.get(self)
20
+ elsif property.is_set?(self)
21
+ property.set(self, property.get(self) + values.flatten)
22
+ else
23
+ property.set(self, values.flatten)
24
+ end
25
+ end
26
+ EOM
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,254 @@
1
+ require 'chef_compat/resource'
2
+ require 'cheffish/array_property'
3
+
4
+ module Cheffish
5
+ class BaseResource < ChefCompat::Resource
6
+ def initialize(*args)
7
+ super
8
+ chef_server run_context.cheffish.current_chef_server
9
+ end
10
+
11
+ Boolean = property_type(is: [ true, false ])
12
+ ArrayType = ArrayProperty.new
13
+
14
+ property :chef_server, Hash
15
+ property :raw_json, Hash
16
+ property :complete, Boolean
17
+
18
+ declare_action_class.class_eval do
19
+ def rest
20
+ @rest ||= Cheffish.chef_server_api(new_resource.chef_server)
21
+ end
22
+
23
+ def current_resource_exists?
24
+ Array(current_resource.action) != [ :delete ]
25
+ end
26
+
27
+ def not_found_resource
28
+ resource = resource_class.new(new_resource.name, run_context)
29
+ resource.action :delete
30
+ resource
31
+ end
32
+
33
+ def normalize_for_put(json)
34
+ data_handler.normalize_for_put(json, fake_entry)
35
+ end
36
+
37
+ def normalize_for_post(json)
38
+ data_handler.normalize_for_post(json, fake_entry)
39
+ end
40
+
41
+ def new_json
42
+ @new_json ||= begin
43
+ if new_resource.complete
44
+ result = normalize(resource_to_json(new_resource))
45
+ else
46
+ # If the resource is incomplete, we use the current json to fill any holes
47
+ result = current_json.merge(resource_to_json(new_resource))
48
+ end
49
+ augment_new_json(result)
50
+ end
51
+ end
52
+
53
+ # Meant to be overridden
54
+ def augment_new_json(json)
55
+ json
56
+ end
57
+
58
+ def current_json
59
+ @current_json ||= begin
60
+ result = normalize(resource_to_json(current_resource))
61
+ result = augment_current_json(result)
62
+ result
63
+ end
64
+ end
65
+
66
+ # Meant to be overridden
67
+ def augment_current_json(json)
68
+ json
69
+ end
70
+
71
+ def resource_to_json(resource)
72
+ json = resource.raw_json || {}
73
+ keys.each do |json_key, resource_key|
74
+ value = resource.send(resource_key)
75
+ # This takes care of Chef ImmutableMash and ImmutableArray
76
+ value = value.to_hash if value.is_a?(Hash)
77
+ value = value.to_a if value.is_a?(Array)
78
+ json[json_key] = value if value
79
+ end
80
+ json
81
+ end
82
+
83
+ def json_to_resource(json)
84
+ resource = resource_class.new(new_resource.name, run_context)
85
+ keys.each do |json_key, resource_key|
86
+ resource.send(resource_key, json.delete(json_key))
87
+ end
88
+ # Set the leftover to raw_json
89
+ resource.raw_json json
90
+ resource
91
+ end
92
+
93
+ def normalize(json)
94
+ data_handler.normalize(json, fake_entry)
95
+ end
96
+
97
+ def json_differences(old_json, new_json, print_values=true, name = '', result = nil)
98
+ result ||= []
99
+ json_differences_internal(old_json, new_json, print_values, name, result)
100
+ result
101
+ end
102
+
103
+ def json_differences_internal(old_json, new_json, print_values, name, result)
104
+ if old_json.kind_of?(Hash) && new_json.kind_of?(Hash)
105
+ removed_keys = old_json.keys.inject({}) { |hash, key| hash[key] = true; hash }
106
+ new_json.each_pair do |new_key, new_value|
107
+ if old_json.has_key?(new_key)
108
+ removed_keys.delete(new_key)
109
+ if new_value != old_json[new_key]
110
+ json_differences_internal(old_json[new_key], new_value, print_values, name == '' ? new_key : "#{name}.#{new_key}", result)
111
+ end
112
+ else
113
+ if print_values
114
+ result << " add #{name == '' ? new_key : "#{name}.#{new_key}"} = #{new_value.inspect}"
115
+ else
116
+ result << " add #{name == '' ? new_key : "#{name}.#{new_key}"}"
117
+ end
118
+ end
119
+ end
120
+ removed_keys.keys.each do |removed_key|
121
+ result << " remove #{name == '' ? removed_key : "#{name}.#{removed_key}"}"
122
+ end
123
+ else
124
+ old_json = old_json.to_s if old_json.kind_of?(Symbol)
125
+ new_json = new_json.to_s if new_json.kind_of?(Symbol)
126
+ if old_json != new_json
127
+ if print_values
128
+ result << " update #{name} from #{old_json.inspect} to #{new_json.inspect}"
129
+ else
130
+ result << " update #{name}"
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ def apply_modifiers(modifiers, json)
137
+ return json if !modifiers || modifiers.size == 0
138
+
139
+ # If the attributes have nothing, set them to {} so we have something to add to
140
+ if json
141
+ json = Marshal.load(Marshal.dump(json)) # Deep copy
142
+ else
143
+ json = {}
144
+ end
145
+
146
+ modifiers.each do |path, value|
147
+ path = [path] if !path.kind_of?(Array)
148
+ path = path.map { |path_part| path_part.to_s }
149
+ parent = 0.upto(path.size-2).inject(json) do |hash, index|
150
+ if hash.nil?
151
+ nil
152
+ elsif !hash.is_a?(Hash)
153
+ raise "Attempt to set #{path} to #{value} when #{path[0..index-1]} is not a hash"
154
+ else
155
+ hash[path[index]]
156
+ end
157
+ end
158
+ if !parent.nil? && !parent.is_a?(Hash)
159
+ raise "Attempt to set #{path} to #{value} when #{path[0..-2]} is not a hash"
160
+ end
161
+ existing_value = parent ? parent[path[-1]] : nil
162
+
163
+ if value.is_a?(Proc)
164
+ value = value.call(existing_value)
165
+ end
166
+ if value == :delete
167
+ parent.delete(path[-1]) if parent
168
+ else
169
+ # Create parent if necessary, overwriting values
170
+ parent = path[0..-2].inject(json) do |hash, path_part|
171
+ hash[path_part] = {} if !hash[path_part]
172
+ hash[path_part]
173
+ end
174
+ if path.size > 0
175
+ parent[path[-1]] = value
176
+ else
177
+ json = value
178
+ end
179
+ end
180
+ end
181
+ json
182
+ end
183
+
184
+ def apply_run_list_modifiers(add_to_run_list, delete_from_run_list, run_list)
185
+ return run_list if (!add_to_run_list || add_to_run_list.size == 0) && (!delete_from_run_list || !delete_from_run_list.size)
186
+ delete_from_run_list ||= []
187
+ add_to_run_list ||= []
188
+
189
+ run_list = Chef::RunList.new(*run_list)
190
+
191
+ result = []
192
+ add_to_run_list_index = 0
193
+ run_list_index = 0
194
+ while run_list_index < run_list.run_list_items.size do
195
+ # See if the desired run list has this item
196
+ found_desired = add_to_run_list.index { |item| same_run_list_item(item, run_list[run_list_index]) }
197
+ if found_desired
198
+ # If so, copy all items up to that desired run list (to preserve order).
199
+ # If a run list item is out of order (run_list = X, B, Y, A, Z and desired = A, B)
200
+ # then this will give us X, A, B. When A is found later, nothing will be copied
201
+ # because found_desired will be less than add_to_run_list_index. The result will
202
+ # be X, A, B, Y, Z.
203
+ if found_desired >= add_to_run_list_index
204
+ result += add_to_run_list[add_to_run_list_index..found_desired].map { |item| item.to_s }
205
+ add_to_run_list_index = found_desired+1
206
+ end
207
+ else
208
+ # If not, just copy it in
209
+ unless delete_from_run_list.index { |item| same_run_list_item(item, run_list[run_list_index]) }
210
+ result << run_list[run_list_index].to_s
211
+ end
212
+ end
213
+ run_list_index += 1
214
+ end
215
+
216
+ # Copy any remaining desired items at the end
217
+ result += add_to_run_list[add_to_run_list_index..-1].map { |item| item.to_s }
218
+ result
219
+ end
220
+
221
+ def same_run_list_item(a, b)
222
+ a_name = a.name
223
+ b_name = b.name
224
+ # Handle "a::default" being the same as "a"
225
+ if a.type == :recipe && a_name =~ /(.+)::default$/
226
+ a_name = $1
227
+ elsif b.type == :recipe && b_name =~ /(.+)::default$/
228
+ b_name = $1
229
+ end
230
+
231
+ a_name == b_name && a.type == b.type # We want to replace things with same name and different version
232
+ end
233
+
234
+ private
235
+
236
+ # Needed to be able to use DataHandler classes
237
+ def fake_entry
238
+ FakeEntry.new("#{new_resource.send(keys.values.first)}.json")
239
+ end
240
+
241
+ class FakeEntry
242
+ def initialize(name, parent = nil)
243
+ @name = name
244
+ @parent = parent
245
+ @org = nil
246
+ end
247
+
248
+ attr_reader :name
249
+ attr_reader :parent
250
+ attr_reader :org
251
+ end
252
+ end
253
+ end
254
+ end
@@ -0,0 +1,135 @@
1
+ require 'cheffish/key_formatter'
2
+ require 'cheffish/base_resource'
3
+
4
+ module Cheffish
5
+ class ChefActorBase < Cheffish::BaseResource
6
+
7
+ action_class.class_eval do
8
+ def create_actor
9
+ if new_resource.before
10
+ new_resource.before.call(new_resource)
11
+ end
12
+
13
+ # Create or update the client/user
14
+ current_public_key = new_json['public_key']
15
+ differences = json_differences(current_json, new_json)
16
+ if current_resource_exists?
17
+ # Update the actor if it's different
18
+ if differences.size > 0
19
+ description = [ "update #{actor_type} #{new_resource.name} at #{actor_path}" ] + differences
20
+ converge_by description do
21
+ result = rest.put("#{actor_path}/#{new_resource.name}", normalize_for_put(new_json))
22
+ current_public_key, current_public_key_format = Cheffish::KeyFormatter.decode(result['public_key']) if result['public_key']
23
+ end
24
+ end
25
+ else
26
+ # Create the actor if it's missing
27
+ if !new_public_key
28
+ raise "You must specify a public key to create a #{actor_type}! Use the private_key resource to create a key, and pass it in with source_key_path."
29
+ end
30
+ description = [ "create #{actor_type} #{new_resource.name} at #{actor_path}" ] + differences
31
+ converge_by description do
32
+ result = rest.post("#{actor_path}", normalize_for_post(new_json))
33
+ current_public_key, current_public_key_format = Cheffish::KeyFormatter.decode(result['public_key']) if result['public_key']
34
+ end
35
+ end
36
+
37
+ # Write out the public key
38
+ if new_resource.output_key_path
39
+ # TODO use inline_resource
40
+ key_content = Cheffish::KeyFormatter.encode(current_public_key, { :format => new_resource.output_key_format })
41
+ if !current_resource.output_key_path
42
+ action = 'create'
43
+ elsif key_content != IO.read(current_resource.output_key_path)
44
+ action = 'overwrite'
45
+ else
46
+ action = nil
47
+ end
48
+ if action
49
+ converge_by "#{action} public key #{new_resource.output_key_path}" do
50
+ IO.write(new_resource.output_key_path, key_content)
51
+ end
52
+ end
53
+ # TODO permissions?
54
+ end
55
+
56
+ if new_resource.after
57
+ new_resource.after.call(self, new_json, server_private_key, server_public_key)
58
+ end
59
+ end
60
+
61
+ def delete_actor
62
+ if current_resource_exists?
63
+ converge_by "delete #{actor_type} #{new_resource.name} at #{actor_path}" do
64
+ rest.delete("#{actor_path}/#{new_resource.name}")
65
+ Chef::Log.info("#{new_resource} deleted #{actor_type} #{new_resource.name} at #{rest.url}")
66
+ end
67
+ end
68
+ if current_resource.output_key_path
69
+ converge_by "delete public key #{current_resource.output_key_path}" do
70
+ ::File.unlink(current_resource.output_key_path)
71
+ end
72
+ end
73
+ end
74
+
75
+ def new_public_key
76
+ @new_public_key ||= begin
77
+ if new_resource.source_key
78
+ if new_resource.source_key.is_a?(String)
79
+ key, key_format = Cheffish::KeyFormatter.decode(new_resource.source_key)
80
+
81
+ if key.private?
82
+ key.public_key
83
+ else
84
+ key
85
+ end
86
+ elsif new_resource.source_key.private?
87
+ new_resource.source_key.public_key
88
+ else
89
+ new_resource.source_key
90
+ end
91
+ elsif new_resource.source_key_path
92
+ source_key_path = new_resource.source_key_path
93
+ if Pathname.new(source_key_path).relative?
94
+ source_key_str, source_key_path = Cheffish.get_private_key_with_path(source_key_path, run_context.config)
95
+ else
96
+ source_key_str = IO.read(source_key_path)
97
+ end
98
+ source_key, source_key_format = Cheffish::KeyFormatter.decode(source_key_str, new_resource.source_key_pass_phrase, source_key_path)
99
+ if source_key.private?
100
+ source_key.public_key
101
+ else
102
+ source_key
103
+ end
104
+ else
105
+ nil
106
+ end
107
+ end
108
+ end
109
+
110
+ def augment_new_json(json)
111
+ if new_public_key
112
+ json['public_key'] = new_public_key.to_pem
113
+ end
114
+ json
115
+ end
116
+
117
+ def load_current_resource
118
+ begin
119
+ json = rest.get("#{actor_path}/#{new_resource.name}")
120
+ @current_resource = json_to_resource(json)
121
+ rescue Net::HTTPServerException => e
122
+ if e.response.code == "404"
123
+ @current_resource = not_found_resource
124
+ else
125
+ raise
126
+ end
127
+ end
128
+
129
+ if new_resource.output_key_path && ::File.exist?(new_resource.output_key_path)
130
+ current_resource.output_key_path = new_resource.output_key_path
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,107 @@
1
+ require 'chef_compat/mixin/properties'
2
+
3
+ module Cheffish
4
+ module NodeProperties
5
+ include ChefCompat::Mixin::Properties
6
+
7
+ # Grab environment from with_environment
8
+ def initialize(*args)
9
+ super
10
+ chef_environment run_context.cheffish.current_environment
11
+ end
12
+
13
+ property :name, Cheffish::NAME_REGEX, name_property: true
14
+ property :chef_environment, Cheffish::NAME_REGEX
15
+ property :run_list, Array # We should let them specify it as a series of parameters too
16
+ property :attributes, Hash
17
+
18
+ # attribute 'ip_address', '127.0.0.1'
19
+ # attribute [ 'pushy', 'port' ], '9000'
20
+ # attribute 'ip_addresses' do |existing_value|
21
+ # (existing_value || []) + [ '127.0.0.1' ]
22
+ # end
23
+ # attribute 'ip_address', :delete
24
+ attr_accessor :attribute_modifiers
25
+ def attribute(attribute_path, value=Chef::NOT_PASSED, &block)
26
+ @attribute_modifiers ||= []
27
+ if value != Chef::NOT_PASSED
28
+ @attribute_modifiers << [ attribute_path, value ]
29
+ elsif block
30
+ @attribute_modifiers << [ attribute_path, block ]
31
+ else
32
+ raise "attribute requires either a value or a block"
33
+ end
34
+ end
35
+
36
+ # Patchy tags
37
+ # tag 'webserver', 'apache', 'myenvironment'
38
+ def tag(*tags)
39
+ attribute 'tags' do |existing_tags|
40
+ existing_tags ||= []
41
+ tags.each do |tag|
42
+ if !existing_tags.include?(tag.to_s)
43
+ existing_tags << tag.to_s
44
+ end
45
+ end
46
+ existing_tags
47
+ end
48
+ end
49
+ def remove_tag(*tags)
50
+ attribute 'tags' do |existing_tags|
51
+ if existing_tags
52
+ tags.each do |tag|
53
+ existing_tags.delete(tag.to_s)
54
+ end
55
+ end
56
+ existing_tags
57
+ end
58
+ end
59
+
60
+ # NON-patchy tags
61
+ # tags :a, :b, :c # removes all other tags
62
+ def tags(*tags)
63
+ if tags.size == 0
64
+ attribute('tags')
65
+ else
66
+ tags = tags[0] if tags.size == 1 && tags[0].kind_of?(Array)
67
+ attribute 'tags', tags.map { |tag| tag.to_s }
68
+ end
69
+ end
70
+
71
+ # Order matters--if two things here are in the wrong order, they will be flipped in the run list
72
+ # recipe 'apache', 'mysql'
73
+ # recipe 'recipe@version'
74
+ # recipe 'recipe'
75
+ # role ''
76
+ attr_accessor :run_list_modifiers
77
+ attr_accessor :run_list_removers
78
+ def recipe(*recipes)
79
+ if recipes.size == 0
80
+ raise ArgumentError, "At least one recipe must be specified"
81
+ end
82
+ @run_list_modifiers ||= []
83
+ @run_list_modifiers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
84
+ end
85
+ def role(*roles)
86
+ if roles.size == 0
87
+ raise ArgumentError, "At least one role must be specified"
88
+ end
89
+ @run_list_modifiers ||= []
90
+ @run_list_modifiers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
91
+ end
92
+ def remove_recipe(*recipes)
93
+ if recipes.size == 0
94
+ raise ArgumentError, "At least one recipe must be specified"
95
+ end
96
+ @run_list_removers ||= []
97
+ @run_list_removers += recipes.map { |recipe| Chef::RunList::RunListItem.new("recipe[#{recipe}]") }
98
+ end
99
+ def remove_role(*roles)
100
+ if roles.size == 0
101
+ raise ArgumentError, "At least one role must be specified"
102
+ end
103
+ @run_list_removers ||= []
104
+ @run_list_removers += roles.map { |role| Chef::RunList::RunListItem.new("role[#{role}]") }
105
+ end
106
+ end
107
+ end
@@ -24,20 +24,6 @@ require 'chef/resource/chef_role'
24
24
  require 'chef/resource/chef_user'
25
25
  require 'chef/resource/private_key'
26
26
  require 'chef/resource/public_key'
27
- require 'chef/provider/chef_acl'
28
- require 'chef/provider/chef_client'
29
- require 'chef/provider/chef_container'
30
- require 'chef/provider/chef_data_bag'
31
- require 'chef/provider/chef_data_bag_item'
32
- require 'chef/provider/chef_environment'
33
- require 'chef/provider/chef_group'
34
- require 'chef/provider/chef_mirror'
35
- require 'chef/provider/chef_node'
36
- require 'chef/provider/chef_organization'
37
- require 'chef/provider/chef_role'
38
- require 'chef/provider/chef_user'
39
- require 'chef/provider/private_key'
40
- require 'chef/provider/public_key'
41
27
 
42
28
 
43
29
  class Chef
@@ -1,3 +1,3 @@
1
1
  module Cheffish
2
- VERSION = '1.6.0'
2
+ VERSION = '2.0.0'
3
3
  end