cheffish 1.6.0 → 2.0.0

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