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.
- checksums.yaml +4 -4
- data/cheffish.gemspec +1 -0
- data/lib/chef/resource/chef_acl.rb +440 -20
- data/lib/chef/resource/chef_client.rb +50 -25
- data/lib/chef/resource/chef_container.rb +44 -11
- data/lib/chef/resource/chef_data_bag.rb +43 -10
- data/lib/chef/resource/chef_data_bag_item.rb +292 -82
- data/lib/chef/resource/chef_environment.rb +79 -27
- data/lib/chef/resource/chef_group.rb +77 -40
- data/lib/chef/resource/chef_mirror.rb +170 -21
- data/lib/chef/resource/chef_node.rb +77 -11
- data/lib/chef/resource/chef_organization.rb +153 -43
- data/lib/chef/resource/chef_resolved_cookbooks.rb +40 -9
- data/lib/chef/resource/chef_role.rb +81 -29
- data/lib/chef/resource/chef_user.rb +64 -33
- data/lib/chef/resource/private_key.rb +230 -17
- data/lib/chef/resource/public_key.rb +88 -9
- data/lib/cheffish/array_property.rb +29 -0
- data/lib/cheffish/base_resource.rb +254 -0
- data/lib/cheffish/chef_actor_base.rb +135 -0
- data/lib/cheffish/node_properties.rb +107 -0
- data/lib/cheffish/recipe_dsl.rb +0 -14
- data/lib/cheffish/version.rb +1 -1
- data/lib/cheffish.rb +4 -108
- data/spec/integration/chef_acl_spec.rb +0 -2
- data/spec/integration/chef_client_spec.rb +0 -1
- data/spec/integration/chef_container_spec.rb +0 -2
- data/spec/integration/chef_group_spec.rb +0 -2
- data/spec/integration/chef_mirror_spec.rb +0 -2
- data/spec/integration/chef_node_spec.rb +0 -2
- data/spec/integration/chef_organization_spec.rb +1 -3
- data/spec/integration/chef_role_spec.rb +0 -2
- data/spec/integration/chef_user_spec.rb +0 -2
- data/spec/integration/private_key_spec.rb +0 -4
- data/spec/integration/recipe_dsl_spec.rb +0 -2
- data/spec/support/spec_support.rb +0 -1
- data/spec/unit/get_private_key_spec.rb +13 -0
- metadata +22 -20
- data/lib/chef/provider/chef_acl.rb +0 -446
- data/lib/chef/provider/chef_client.rb +0 -53
- data/lib/chef/provider/chef_container.rb +0 -55
- data/lib/chef/provider/chef_data_bag.rb +0 -55
- data/lib/chef/provider/chef_data_bag_item.rb +0 -278
- data/lib/chef/provider/chef_environment.rb +0 -83
- data/lib/chef/provider/chef_group.rb +0 -83
- data/lib/chef/provider/chef_mirror.rb +0 -169
- data/lib/chef/provider/chef_node.rb +0 -87
- data/lib/chef/provider/chef_organization.rb +0 -155
- data/lib/chef/provider/chef_resolved_cookbooks.rb +0 -46
- data/lib/chef/provider/chef_role.rb +0 -84
- data/lib/chef/provider/chef_user.rb +0 -59
- data/lib/chef/provider/private_key.rb +0 -225
- data/lib/chef/provider/public_key.rb +0 -88
- data/lib/cheffish/actor_provider_base.rb +0 -131
- data/lib/cheffish/chef_provider_base.rb +0 -246
@@ -1,25 +1,104 @@
|
|
1
1
|
require 'openssl/cipher'
|
2
|
-
require '
|
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 <
|
7
|
-
|
8
|
+
class PublicKey < Cheffish::BaseResource
|
9
|
+
resource_name :public_key
|
8
10
|
|
9
|
-
|
11
|
+
allowed_actions :create, :delete, :nothing
|
10
12
|
default_action :create
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
+
property :path, String, name_property: true
|
15
|
+
property :format, [ :pem, :der, :openssh ], default: :openssh
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
data/lib/cheffish/recipe_dsl.rb
CHANGED
@@ -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
|
data/lib/cheffish/version.rb
CHANGED