cheffish 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +201 -0
- data/README.md +4 -0
- data/Rakefile +23 -0
- data/lib/chef/provider/chef_client.rb +44 -0
- data/lib/chef/provider/chef_data_bag.rb +50 -0
- data/lib/chef/provider/chef_data_bag_item.rb +273 -0
- data/lib/chef/provider/chef_environment.rb +78 -0
- data/lib/chef/provider/chef_node.rb +82 -0
- data/lib/chef/provider/chef_role.rb +79 -0
- data/lib/chef/provider/chef_user.rb +48 -0
- data/lib/chef/provider/private_key.rb +160 -0
- data/lib/chef/provider/public_key.rb +83 -0
- data/lib/chef/resource/chef_client.rb +44 -0
- data/lib/chef/resource/chef_data_bag.rb +18 -0
- data/lib/chef/resource/chef_data_bag_item.rb +114 -0
- data/lib/chef/resource/chef_environment.rb +71 -0
- data/lib/chef/resource/chef_node.rb +18 -0
- data/lib/chef/resource/chef_role.rb +104 -0
- data/lib/chef/resource/chef_user.rb +51 -0
- data/lib/chef/resource/in_parallel.rb +6 -0
- data/lib/chef/resource/private_key.rb +39 -0
- data/lib/chef/resource/public_key.rb +16 -0
- data/lib/cheffish.rb +245 -0
- data/lib/cheffish/actor_provider_base.rb +120 -0
- data/lib/cheffish/chef_provider_base.rb +222 -0
- data/lib/cheffish/cheffish_server_api.rb +21 -0
- data/lib/cheffish/inline_resource.rb +88 -0
- data/lib/cheffish/key_formatter.rb +93 -0
- data/lib/cheffish/recipe_dsl.rb +98 -0
- data/lib/cheffish/version.rb +4 -0
- data/spec/integration/chef_client_spec.rb +48 -0
- data/spec/integration/chef_node_spec.rb +75 -0
- data/spec/integration/chef_user_spec.rb +48 -0
- data/spec/integration/private_key_spec.rb +356 -0
- data/spec/integration/recipe_dsl_spec.rb +29 -0
- data/spec/support/key_support.rb +29 -0
- data/spec/support/spec_support.rb +148 -0
- metadata +124 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'chef/config'
|
2
|
+
require 'chef/run_list'
|
3
|
+
require 'cheffish/cheffish_server_api'
|
4
|
+
|
5
|
+
module Cheffish
|
6
|
+
class ChefProviderBase < Chef::Provider::LWRPBase
|
7
|
+
def rest
|
8
|
+
@rest ||= CheffishServerAPI.new(new_resource.chef_server)
|
9
|
+
end
|
10
|
+
|
11
|
+
def current_resource_exists?
|
12
|
+
Array(current_resource.action) != [ :delete ]
|
13
|
+
end
|
14
|
+
|
15
|
+
def not_found_resource
|
16
|
+
resource = resource_class.new(new_resource.name)
|
17
|
+
resource.action :delete
|
18
|
+
resource
|
19
|
+
end
|
20
|
+
|
21
|
+
def normalize_for_put(json)
|
22
|
+
data_handler.normalize_for_put(json, fake_entry)
|
23
|
+
end
|
24
|
+
|
25
|
+
def normalize_for_post(json)
|
26
|
+
data_handler.normalize_for_post(json, fake_entry)
|
27
|
+
end
|
28
|
+
|
29
|
+
def new_json
|
30
|
+
@new_json ||= begin
|
31
|
+
if new_resource.complete
|
32
|
+
result = normalize(resource_to_json(new_resource))
|
33
|
+
else
|
34
|
+
# If resource is incomplete, use current json to fill any holes
|
35
|
+
result = current_json.merge(resource_to_json(new_resource))
|
36
|
+
end
|
37
|
+
augment_new_json(result)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Meant to be overridden
|
42
|
+
def augment_new_json(json)
|
43
|
+
json
|
44
|
+
end
|
45
|
+
|
46
|
+
def current_json
|
47
|
+
@current_json ||= begin
|
48
|
+
result = normalize(resource_to_json(current_resource))
|
49
|
+
result = augment_current_json(result)
|
50
|
+
result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Meant to be overridden
|
55
|
+
def augment_current_json(json)
|
56
|
+
json
|
57
|
+
end
|
58
|
+
|
59
|
+
def resource_to_json(resource)
|
60
|
+
json = resource.raw_json || {}
|
61
|
+
keys.each do |json_key, resource_key|
|
62
|
+
value = resource.send(resource_key)
|
63
|
+
json[json_key] = value if value
|
64
|
+
end
|
65
|
+
json
|
66
|
+
end
|
67
|
+
|
68
|
+
def json_to_resource(json)
|
69
|
+
resource = resource_class.new(new_resource.name)
|
70
|
+
keys.each do |json_key, resource_key|
|
71
|
+
resource.send(resource_key, json[json_key])
|
72
|
+
end
|
73
|
+
resource
|
74
|
+
end
|
75
|
+
|
76
|
+
def normalize(json)
|
77
|
+
data_handler.normalize(json, fake_entry)
|
78
|
+
end
|
79
|
+
|
80
|
+
def json_differences(old_json, new_json, print_values=true, name = '', result = nil)
|
81
|
+
result ||= []
|
82
|
+
json_differences_internal(old_json, new_json, print_values, name, result)
|
83
|
+
result
|
84
|
+
end
|
85
|
+
|
86
|
+
def json_differences_internal(old_json, new_json, print_values, name, result)
|
87
|
+
if old_json.kind_of?(Hash) && new_json.kind_of?(Hash)
|
88
|
+
removed_keys = old_json.keys.inject({}) { |hash, key| hash[key] = true; hash }
|
89
|
+
new_json.each_pair do |new_key, new_value|
|
90
|
+
if old_json.has_key?(new_key)
|
91
|
+
removed_keys.delete(new_key)
|
92
|
+
if new_value != old_json[new_key]
|
93
|
+
json_differences_internal(old_json[new_key], new_value, print_values, name == '' ? new_key : "#{name}.#{new_key}", result)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
if print_values
|
97
|
+
result << "add #{name == '' ? new_key : "#{name}.#{new_key}"} = #{new_value.inspect}"
|
98
|
+
else
|
99
|
+
result << "add #{name == '' ? new_key : "#{name}.#{new_key}"}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
removed_keys.keys.each do |removed_key|
|
104
|
+
result << "remove #{name == '' ? removed_key : "#{name}.#{removed_key}"}"
|
105
|
+
end
|
106
|
+
else
|
107
|
+
old_json = old_json.to_s if old_json.kind_of?(Symbol)
|
108
|
+
new_json = new_json.to_s if new_json.kind_of?(Symbol)
|
109
|
+
if old_json != new_json
|
110
|
+
if print_values
|
111
|
+
result << "update #{name} from #{old_json.inspect} to #{new_json.inspect}"
|
112
|
+
else
|
113
|
+
result << "update #{name}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def apply_modifiers(modifiers, json)
|
120
|
+
return json if !modifiers || modifiers.size == 0
|
121
|
+
|
122
|
+
# If the attributes have nothing, set them to {} so we have something to add to
|
123
|
+
if json
|
124
|
+
json = Marshal.load(Marshal.dump(json)) # Deep copy
|
125
|
+
else
|
126
|
+
json = {}
|
127
|
+
end
|
128
|
+
|
129
|
+
modifiers.each do |path, value|
|
130
|
+
path = [path] if !path.kind_of?(Array)
|
131
|
+
path = path.map { |path_part| path_part.to_s }
|
132
|
+
parent = path[0..-2].inject(json) { |hash, path_part| hash ? hash[path_part] : nil }
|
133
|
+
existing_value = parent ? parent[path[-1]] : nil
|
134
|
+
|
135
|
+
if value.is_a?(Proc)
|
136
|
+
value = value.call(existing_value)
|
137
|
+
end
|
138
|
+
if value == :delete
|
139
|
+
parent.delete(path[-1]) if parent
|
140
|
+
# TODO clean up parent chain if hash is completely emptied
|
141
|
+
else
|
142
|
+
if !parent
|
143
|
+
# Create parent if necessary
|
144
|
+
parent = path[0..-2].inject(json) do |hash, path_part|
|
145
|
+
hash[path_part] = {} if !hash[path_part]
|
146
|
+
hash[path_part]
|
147
|
+
end
|
148
|
+
end
|
149
|
+
parent[path[-1]] = value
|
150
|
+
end
|
151
|
+
end
|
152
|
+
json
|
153
|
+
end
|
154
|
+
|
155
|
+
def apply_run_list_modifiers(add_to_run_list, delete_from_run_list, run_list)
|
156
|
+
return run_list if (!add_to_run_list || add_to_run_list.size == 0) && (!delete_from_run_list || !delete_from_run_list.size)
|
157
|
+
delete_from_run_list ||= []
|
158
|
+
add_to_run_list ||= []
|
159
|
+
|
160
|
+
run_list = Chef::RunList.new(*run_list)
|
161
|
+
|
162
|
+
result = []
|
163
|
+
add_to_run_list_index = 0
|
164
|
+
run_list_index = 0
|
165
|
+
while run_list_index < run_list.run_list_items.size do
|
166
|
+
# See if the desired run list has this item
|
167
|
+
found_desired = add_to_run_list.index { |item| same_run_list_item(item, run_list[run_list_index]) }
|
168
|
+
if found_desired
|
169
|
+
# If so, copy all items up to that desired run list (to preserve order).
|
170
|
+
# If a run list item is out of order (run_list = X, B, Y, A, Z and desired = A, B)
|
171
|
+
# then this will give us X, A, B. When A is found later, nothing will be copied
|
172
|
+
# because found_desired will be less than add_to_run_list_index. The result will
|
173
|
+
# be X, A, B, Y, Z.
|
174
|
+
if found_desired >= add_to_run_list_index
|
175
|
+
result += add_to_run_list[add_to_run_list_index..found_desired].map { |item| item.to_s }
|
176
|
+
add_to_run_list_index = found_desired+1
|
177
|
+
end
|
178
|
+
else
|
179
|
+
# If not, just copy it in
|
180
|
+
unless delete_from_run_list.index { |item| same_run_list_item(item, run_list[run_list_index]) }
|
181
|
+
result << run_list[run_list_index].to_s
|
182
|
+
end
|
183
|
+
end
|
184
|
+
run_list_index += 1
|
185
|
+
end
|
186
|
+
|
187
|
+
# Copy any remaining desired items at the end
|
188
|
+
result += add_to_run_list[add_to_run_list_index..-1].map { |item| item.to_s }
|
189
|
+
result
|
190
|
+
end
|
191
|
+
|
192
|
+
def same_run_list_item(a, b)
|
193
|
+
a_name = a.name
|
194
|
+
b_name = b.name
|
195
|
+
# Handle "a::default" being the same as "a"
|
196
|
+
if a.type == :recipe && a_name =~ /(.+)::default$/
|
197
|
+
a_name = $1
|
198
|
+
elsif b.type == :recipe && b_name =~ /(.+)::default$/
|
199
|
+
b_name = $1
|
200
|
+
end
|
201
|
+
|
202
|
+
a_name == b_name && a.type == b.type # We want to replace things with same name and different version
|
203
|
+
end
|
204
|
+
|
205
|
+
private
|
206
|
+
|
207
|
+
# Needed to be able to use DataHandler classes
|
208
|
+
def fake_entry
|
209
|
+
FakeEntry.new("#{new_resource.send(keys.values.first)}.json")
|
210
|
+
end
|
211
|
+
|
212
|
+
class FakeEntry
|
213
|
+
def initialize(name, parent = nil)
|
214
|
+
@name = name
|
215
|
+
@parent = parent
|
216
|
+
end
|
217
|
+
|
218
|
+
attr_reader :name
|
219
|
+
attr_reader :parent
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'chef/http'
|
2
|
+
require 'chef/http/authenticator'
|
3
|
+
require 'chef/http/cookie_manager'
|
4
|
+
require 'chef/http/decompressor'
|
5
|
+
require 'chef/http/json_input'
|
6
|
+
require 'chef/http/json_output'
|
7
|
+
|
8
|
+
module Cheffish
|
9
|
+
# Just like ServerAPI, except it does not default the server URL or options
|
10
|
+
class CheffishServerAPI < Chef::HTTP
|
11
|
+
def initialize(chef_server)
|
12
|
+
super(chef_server[:chef_server_url], chef_server[:options] || {})
|
13
|
+
end
|
14
|
+
|
15
|
+
use Chef::HTTP::JSONInput
|
16
|
+
use Chef::HTTP::JSONOutput
|
17
|
+
use Chef::HTTP::CookieManager
|
18
|
+
use Chef::HTTP::Decompressor
|
19
|
+
use Chef::HTTP::Authenticator
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Cheffish
|
2
|
+
class InlineResource
|
3
|
+
def initialize(provider)
|
4
|
+
@provider = provider
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_reader :provider
|
8
|
+
|
9
|
+
def run_context
|
10
|
+
provider.run_context
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method_symbol, *args, &block)
|
14
|
+
# Stolen ruthlessly from Chef's chef/dsl/recipe.rb
|
15
|
+
|
16
|
+
# Checks the new platform => short_name => resource mapping initially
|
17
|
+
# then fall back to the older approach (Chef::Resource.const_get) for
|
18
|
+
# backward compatibility
|
19
|
+
resource_class = Chef::Resource.resource_for_node(method_symbol, provider.run_context.node)
|
20
|
+
|
21
|
+
super unless resource_class
|
22
|
+
raise ArgumentError, "You must supply a name when declaring a #{method_symbol} resource" unless args.size > 0
|
23
|
+
|
24
|
+
# If we have a resource like this one, we want to steal its state
|
25
|
+
args << run_context
|
26
|
+
resource = resource_class.new(*args)
|
27
|
+
resource.source_line = caller[0]
|
28
|
+
resource.load_prior_resource
|
29
|
+
resource.cookbook_name = provider.cookbook_name
|
30
|
+
resource.recipe_name = @recipe_name
|
31
|
+
resource.params = @params
|
32
|
+
# Determine whether this resource is being created in the context of an enclosing Provider
|
33
|
+
resource.enclosing_provider = provider.is_a?(Chef::Provider) ? provider : nil
|
34
|
+
# Evaluate resource attribute DSL
|
35
|
+
resource.instance_eval(&block) if block
|
36
|
+
|
37
|
+
# Run optional resource hook
|
38
|
+
resource.after_created
|
39
|
+
|
40
|
+
# Do NOT put this in the resource collection.
|
41
|
+
#run_context.resource_collection.insert(resource)
|
42
|
+
|
43
|
+
# Instead, run the action directly.
|
44
|
+
Array(resource.action).each do |action|
|
45
|
+
resource.updated_by_last_action(false)
|
46
|
+
run_provider_action(resource.provider_for_action(action))
|
47
|
+
provider.new_resource.updated_by_last_action(true) if resource.updated_by_last_action?
|
48
|
+
end
|
49
|
+
resource
|
50
|
+
end
|
51
|
+
|
52
|
+
# Do Chef::Provider.run_action, but without events
|
53
|
+
def run_provider_action(inline_provider)
|
54
|
+
if !inline_provider.whyrun_supported?
|
55
|
+
raise "#{inline_provider} is not why-run-safe. Only why-run-safe resources are supported in inline_resource."
|
56
|
+
end
|
57
|
+
|
58
|
+
# Blatantly ripped off from chef/provider run_action
|
59
|
+
|
60
|
+
# TODO: it would be preferable to get the action to be executed in the
|
61
|
+
# constructor...
|
62
|
+
|
63
|
+
# user-defined LWRPs may include unsafe load_current_resource methods that cannot be run in whyrun mode
|
64
|
+
inline_provider.load_current_resource
|
65
|
+
inline_provider.define_resource_requirements
|
66
|
+
inline_provider.process_resource_requirements
|
67
|
+
|
68
|
+
# user-defined providers including LWRPs may
|
69
|
+
# not include whyrun support - if they don't support it
|
70
|
+
# we can't execute any actions while we're running in
|
71
|
+
# whyrun mode. Instead we 'fake' whyrun by documenting that
|
72
|
+
# we can't execute the action.
|
73
|
+
# in non-whyrun mode, this will still cause the action to be
|
74
|
+
# executed normally.
|
75
|
+
if inline_provider.whyrun_supported? && !inline_provider.requirements.action_blocked?(@action)
|
76
|
+
inline_provider.send("action_#{inline_provider.action}")
|
77
|
+
elsif !inline_provider.whyrun_mode?
|
78
|
+
inline_provider.send("action_#{inline_provider.action}")
|
79
|
+
end
|
80
|
+
|
81
|
+
if inline_provider.resource_updated?
|
82
|
+
inline_provider.new_resource.updated_by_last_action(true)
|
83
|
+
end
|
84
|
+
|
85
|
+
inline_provider.cleanup_after_converge
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'net/ssh'
|
3
|
+
require 'etc'
|
4
|
+
require 'socket'
|
5
|
+
require 'digest/md5'
|
6
|
+
|
7
|
+
module Cheffish
|
8
|
+
class KeyFormatter
|
9
|
+
# Returns nil or key, format
|
10
|
+
def self.decode(str, pass_phrase=nil, filename='')
|
11
|
+
key_format = {}
|
12
|
+
key_format[:format] = format_of(str)
|
13
|
+
|
14
|
+
case key_format[:format]
|
15
|
+
when :openssh
|
16
|
+
key = decode_openssh_key(str, filename)
|
17
|
+
else
|
18
|
+
begin
|
19
|
+
key = OpenSSL::PKey.read(str) { pass_phrase }
|
20
|
+
rescue
|
21
|
+
return nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
key_format[:type] = type_of(key)
|
26
|
+
key_format[:size] = size_of(key)
|
27
|
+
key_format[:pass_phrase] = pass_phrase if pass_phrase
|
28
|
+
# TODO cipher, exponent
|
29
|
+
|
30
|
+
[key, key_format]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.encode(key, key_format)
|
34
|
+
format = key_format[:format] || :pem
|
35
|
+
case format
|
36
|
+
when :openssh
|
37
|
+
encode_openssh_key(key)
|
38
|
+
when :pem
|
39
|
+
if key_format[:pass_phrase]
|
40
|
+
cipher = key_format[:cipher] || 'DES-EDE3-CBC'
|
41
|
+
key.to_pem(OpenSSL::Cipher.new(cipher), key_format[:pass_phrase])
|
42
|
+
else
|
43
|
+
key.to_pem
|
44
|
+
end
|
45
|
+
when :der
|
46
|
+
key.to_der
|
47
|
+
when :fingerprint
|
48
|
+
hexes = Digest::MD5.hexdigest(key.to_der)
|
49
|
+
# Put : between every pair of hexes
|
50
|
+
hexes.scan(/../).join(':')
|
51
|
+
else
|
52
|
+
raise "Unrecognized key format #{format}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def self.encode_openssh_key(key)
|
59
|
+
# TODO there really isn't a method somewhere in net/ssh or openssl that does this??
|
60
|
+
type = key.ssh_type
|
61
|
+
data = [ key.to_blob ].pack('m0')
|
62
|
+
"#{type} #{data} #{Etc.getlogin}@#{Socket.gethostname}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.decode_openssh_key(str, filename='')
|
66
|
+
Net::SSH::KeyFactory.load_data_public_key(str, filename)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.format_of(key_contents)
|
70
|
+
if key_contents.start_with?('-----BEGIN ')
|
71
|
+
:pem
|
72
|
+
elsif key_contents.start_with?('ssh-rsa ') || key_contents.start_with?('ssh-dss ')
|
73
|
+
:openssh
|
74
|
+
else
|
75
|
+
:der
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.type_of(key)
|
80
|
+
case key.class
|
81
|
+
when OpenSSL::PKey::RSA
|
82
|
+
:rsa
|
83
|
+
when OpenSSL::PKey::DSA
|
84
|
+
:dsa
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.size_of(key)
|
89
|
+
# TODO DSA -- this is RSA only
|
90
|
+
key.n.num_bytes * 8
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require 'cheffish'
|
2
|
+
|
3
|
+
require 'chef_zero/server'
|
4
|
+
require 'chef/chef_fs/chef_fs_data_store'
|
5
|
+
require 'chef/chef_fs/config'
|
6
|
+
|
7
|
+
class Chef
|
8
|
+
class Recipe
|
9
|
+
def with_chef_data_bag(name)
|
10
|
+
old_enclosing_data_bag = Cheffish.enclosing_data_bag
|
11
|
+
Cheffish.enclosing_data_bag = name
|
12
|
+
if block_given?
|
13
|
+
begin
|
14
|
+
yield
|
15
|
+
ensure
|
16
|
+
Cheffish.enclosing_data_bag = old_enclosing_data_bag
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_chef_environment(name)
|
22
|
+
old_enclosing_environment = Cheffish.enclosing_environment
|
23
|
+
Cheffish.enclosing_environment = name
|
24
|
+
if block_given?
|
25
|
+
begin
|
26
|
+
yield
|
27
|
+
ensure
|
28
|
+
Cheffish.enclosing_environment = old_enclosing_environment
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def with_chef_data_bag_item_encryption(encryption_options)
|
34
|
+
old_enclosing_data_bag_item_encryption = Cheffish.enclosing_data_bag_item_encryption
|
35
|
+
Cheffish.enclosing_data_bag_item_encryption = encryption_options
|
36
|
+
if block_given?
|
37
|
+
begin
|
38
|
+
yield
|
39
|
+
ensure
|
40
|
+
Cheffish.enclosing_data_bag_item_encryption = old_enclosing_data_bag_item_encryption
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def with_chef_server(server_url, options = {})
|
46
|
+
old_enclosing_chef_server = Cheffish.enclosing_chef_server
|
47
|
+
Cheffish.enclosing_chef_server = { :chef_server_url => server_url, :options => options }
|
48
|
+
if block_given?
|
49
|
+
begin
|
50
|
+
yield
|
51
|
+
ensure
|
52
|
+
Cheffish.enclosing_chef_server = old_enclosing_chef_server
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def with_chef_local_server(options, &block)
|
58
|
+
options[:host] ||= '127.0.0.1'
|
59
|
+
options[:log_level] ||= Chef::Log.level
|
60
|
+
options[:port] ||= 8900
|
61
|
+
|
62
|
+
# Create the data store chef-zero will use
|
63
|
+
options[:data_store] ||= begin
|
64
|
+
if !options[:chef_repo_path]
|
65
|
+
raise "chef_repo_path must be specified to with_chef_local_server"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Ensure all paths are given
|
69
|
+
%w(acl client cookbook container data_bag environment group node role).each do |type|
|
70
|
+
options["#{type}_path".to_sym] ||= begin
|
71
|
+
if options[:chef_repo_path].kind_of?(String)
|
72
|
+
Chef::Config.path_join(options[:chef_repo_path], "#{type}s")
|
73
|
+
else
|
74
|
+
options[:chef_repo_path].map { |path| Chef::Config.path_join(path, "#{type}s")}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
# Work around issue in earlier versions of ChefFS where it expects strings for these
|
78
|
+
# instead of symbols
|
79
|
+
options["#{type}_path"] = options["#{type}_path".to_sym]
|
80
|
+
end
|
81
|
+
|
82
|
+
chef_fs = Chef::ChefFS::Config.new(options).local_fs
|
83
|
+
chef_fs.write_pretty_json = true
|
84
|
+
Chef::ChefFS::ChefFSDataStore.new(chef_fs)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Start the chef-zero server
|
88
|
+
Chef::Log.info("Starting chef-zero on port #{options[:port]} with repository at #{options[:data_store].chef_fs.fs_description}")
|
89
|
+
chef_zero_server = ChefZero::Server.new(options)
|
90
|
+
chef_zero_server.start_background
|
91
|
+
|
92
|
+
@@local_servers ||= []
|
93
|
+
@@local_servers << chef_zero_server
|
94
|
+
|
95
|
+
with_chef_server(chef_zero_server.url, &block)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|