chef 11.10.0.alpha.1-x86-mingw32 → 11.10.0.rc.0-x86-mingw32
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.
- data/README.md +57 -36
- data/distro/common/html/chef-client.8.html +4 -4
- data/distro/common/html/chef-expander.8.html +4 -4
- data/distro/common/html/chef-expanderctl.8.html +4 -4
- data/distro/common/html/chef-server-webui.8.html +4 -4
- data/distro/common/html/chef-server.8.html +4 -4
- data/distro/common/html/chef-shell.1.html +4 -4
- data/distro/common/html/chef-solo.8.html +4 -4
- data/distro/common/html/chef-solr.8.html +5 -5
- data/distro/common/html/knife-bootstrap.1.html +4 -4
- data/distro/common/html/knife-client.1.html +4 -4
- data/distro/common/html/knife-configure.1.html +4 -4
- data/distro/common/html/knife-cookbook-site.1.html +4 -4
- data/distro/common/html/knife-cookbook.1.html +4 -4
- data/distro/common/html/knife-data-bag.1.html +4 -4
- data/distro/common/html/knife-environment.1.html +4 -4
- data/distro/common/html/knife-exec.1.html +4 -4
- data/distro/common/html/knife-index.1.html +4 -4
- data/distro/common/html/knife-node.1.html +4 -4
- data/distro/common/html/knife-role.1.html +4 -4
- data/distro/common/html/knife-search.1.html +4 -4
- data/distro/common/html/knife-ssh.1.html +4 -4
- data/distro/common/html/knife-status.1.html +4 -4
- data/distro/common/html/knife-tag.1.html +4 -4
- data/distro/common/html/knife.1.html +4 -4
- data/distro/common/man/man1/knife-bootstrap.1 +58 -64
- data/distro/common/man/man1/knife-client.1 +19 -22
- data/distro/common/man/man1/knife-configure.1 +37 -46
- data/distro/common/man/man1/knife-cookbook-site.1 +14 -17
- data/distro/common/man/man1/knife-cookbook.1 +15 -18
- data/distro/common/man/man1/knife-data-bag.1 +14 -17
- data/distro/common/man/man1/knife-delete.1 +38 -47
- data/distro/common/man/man1/knife-deps.1 +39 -48
- data/distro/common/man/man1/knife-diff.1 +43 -52
- data/distro/common/man/man1/knife-download.1 +47 -53
- data/distro/common/man/man1/knife-edit.1 +32 -41
- data/distro/common/man/man1/knife-environment.1 +14 -17
- data/distro/common/man/man1/knife-exec.1 +52 -61
- data/distro/common/man/man1/knife-index-rebuild.1 +1 -61
- data/distro/common/man/man1/knife-list.1 +47 -59
- data/distro/common/man/man1/knife-node.1 +15 -18
- data/distro/common/man/man1/knife-raw.1 +28 -46
- data/distro/common/man/man1/knife-recipe-list.1 +1 -61
- data/distro/common/man/man1/knife-role.1 +19 -25
- data/distro/common/man/man1/knife-search.1 +53 -62
- data/distro/common/man/man1/knife-show.1 +36 -28
- data/distro/common/man/man1/knife-ssh.1 +55 -61
- data/distro/common/man/man1/knife-status.1 +34 -43
- data/distro/common/man/man1/knife-tag.1 +14 -17
- data/distro/common/man/man1/knife-upload.1 +47 -56
- data/distro/common/man/man1/knife-user.1 +17 -20
- data/distro/common/man/man1/knife-xargs.1 +60 -69
- data/lib/chef/application.rb +3 -1
- data/lib/chef/application/windows_service.rb +0 -1
- data/lib/chef/client.rb +41 -152
- data/lib/chef/config.rb +19 -23
- data/lib/chef/data_bag.rb +1 -1
- data/lib/chef/data_bag_item.rb +1 -1
- data/lib/chef/exceptions.rb +8 -0
- data/lib/chef/formatters/doc.rb +15 -0
- data/lib/chef/formatters/error_inspectors/api_error_formatting.rb +2 -1
- data/lib/chef/http.rb +18 -8
- data/lib/chef/http/authenticator.rb +4 -0
- data/lib/chef/http/cookie_manager.rb +3 -0
- data/lib/chef/http/decompressor.rb +4 -0
- data/lib/chef/http/json_input.rb +4 -0
- data/lib/chef/http/json_output.rb +4 -0
- data/lib/chef/http/validate_content_length.rb +94 -0
- data/lib/chef/knife.rb +0 -1
- data/lib/chef/knife/configure.rb +6 -6
- data/lib/chef/knife/cookbook_create.rb +2 -2
- data/lib/chef/knife/core/subcommand_loader.rb +49 -3
- data/lib/chef/knife/ssh.rb +34 -4
- data/lib/chef/mixin/path_sanity.rb +1 -0
- data/lib/chef/monologger.rb +1 -2
- data/lib/chef/node.rb +7 -0
- data/lib/chef/policy_builder.rb +49 -0
- data/lib/chef/policy_builder/expand_node_object.rb +230 -0
- data/lib/chef/policy_builder/policyfile.rb +338 -0
- data/lib/chef/provider/file.rb +15 -5
- data/lib/chef/provider/group.rb +6 -2
- data/lib/chef/provider/group/windows.rb +12 -2
- data/lib/chef/provider/http_request.rb +3 -2
- data/lib/chef/provider/package.rb +1 -0
- data/lib/chef/provider/package/aix.rb +1 -1
- data/lib/chef/provider/service/debian.rb +7 -2
- data/lib/chef/resource/file.rb +8 -1
- data/lib/chef/resource/package.rb +9 -0
- data/lib/chef/resource/service.rb +0 -1
- data/lib/chef/rest.rb +2 -0
- data/lib/chef/run_context.rb +1 -1
- data/lib/chef/util/file_edit.rb +1 -1
- data/lib/chef/util/windows/net_group.rb +7 -6
- data/lib/chef/version.rb +1 -1
- data/lib/chef/win32/version.rb +31 -18
- data/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed +1 -0
- data/spec/functional/resource/file_spec.rb +0 -1
- data/spec/functional/resource/group_spec.rb +96 -16
- data/spec/functional/resource/package_spec.rb +17 -0
- data/spec/functional/resource/user_spec.rb +2 -2
- data/spec/functional/win32/versions_spec.rb +39 -0
- data/spec/integration/client/client_spec.rb +27 -28
- data/spec/spec_helper.rb +2 -0
- data/spec/support/platform_helpers.rb +7 -1
- data/spec/support/shared/functional/file_resource.rb +83 -43
- data/spec/unit/application_spec.rb +7 -5
- data/spec/unit/client_spec.rb +10 -3
- data/spec/unit/config_spec.rb +0 -30
- data/spec/unit/cookbook_spec.rb +1 -0
- data/spec/unit/data_bag_item_spec.rb +8 -0
- data/spec/unit/data_bag_spec.rb +6 -0
- data/spec/unit/http_spec.rb +48 -0
- data/spec/unit/knife/core/subcommand_loader_spec.rb +77 -1
- data/spec/unit/knife/ssh_spec.rb +107 -0
- data/spec/unit/mixin/path_sanity_spec.rb +6 -0
- data/spec/unit/mixin/securable_spec.rb +77 -3
- data/spec/unit/monologger_spec.rb +45 -0
- data/spec/unit/node_spec.rb +16 -0
- data/spec/unit/policy_builder/expand_node_object_spec.rb +320 -0
- data/spec/unit/policy_builder/policyfile_spec.rb +399 -0
- data/spec/unit/policy_builder_spec.rb +26 -0
- data/spec/unit/provider/deploy_spec.rb +3 -0
- data/spec/unit/provider/group/windows_spec.rb +1 -0
- data/spec/unit/provider/http_request_spec.rb +23 -1
- data/spec/unit/provider/service/debian_service_spec.rb +50 -19
- data/spec/unit/recipe_spec.rb +4 -0
- data/spec/unit/resource/package_spec.rb +5 -0
- data/spec/unit/rest_spec.rb +375 -278
- data/spec/unit/run_context_spec.rb +4 -0
- metadata +120 -75
- checksums.yaml +0 -7
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# Copyright:: Copyright (c) 2014 Opscode, Inc.
|
3
|
+
# License:: Apache License, Version 2.0
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'chef/monologger'
|
19
|
+
require 'tempfile'
|
20
|
+
require 'spec_helper'
|
21
|
+
|
22
|
+
describe MonoLogger do
|
23
|
+
it "should disable buffering when passed an IO stream" do
|
24
|
+
STDOUT.sync = false
|
25
|
+
MonoLogger.new(STDOUT)
|
26
|
+
STDOUT.sync.should == true
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "when given an object that responds to write and close e.g. IO" do
|
30
|
+
it "should use the object directly" do
|
31
|
+
stream = StringIO.new
|
32
|
+
MonoLogger.new(stream).fatal("Houston, we've had a problem.")
|
33
|
+
stream.string.should =~ /Houston, we've had a problem./
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "when given an object that is stringable (to_str)" do
|
38
|
+
it "should open a File object with the given path" do
|
39
|
+
temp_file = Tempfile.new("rspec-monologger-log")
|
40
|
+
temp_file.close
|
41
|
+
MonoLogger.new(temp_file.path).fatal("Do, or do not. There is no try.")
|
42
|
+
File.read(temp_file.path).should =~ /Do, or do not. There is no try./
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/unit/node_spec.rb
CHANGED
@@ -461,6 +461,22 @@ describe Chef::Node do
|
|
461
461
|
end
|
462
462
|
end
|
463
463
|
|
464
|
+
describe "loaded_recipe" do
|
465
|
+
it "should not add a recipe that is already in the recipes list" do
|
466
|
+
node.automatic_attrs[:recipes] = [ "nginx::module" ]
|
467
|
+
node.loaded_recipe(:nginx, "module")
|
468
|
+
expect(node.automatic_attrs[:recipes].length).to eq(1)
|
469
|
+
end
|
470
|
+
|
471
|
+
it "should add a recipe that is not already in the recipes list" do
|
472
|
+
node.automatic_attrs[:recipes] = [ "nginx::other_module" ]
|
473
|
+
node.loaded_recipe(:nginx, "module")
|
474
|
+
expect(node.automatic_attrs[:recipes].length).to eq(2)
|
475
|
+
expect(node.recipe?("nginx::module")).to be_true
|
476
|
+
expect(node.recipe?("nginx::other_module")).to be_true
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
464
480
|
describe "when querying for recipes in the run list" do
|
465
481
|
context "when a recipe is in the top level run list" do
|
466
482
|
before do
|
@@ -0,0 +1,320 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Daniel DeLeo (<dan@getchef.com>)
|
3
|
+
# Copyright:: Copyright 2014 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'spec_helper'
|
20
|
+
require 'chef/policy_builder'
|
21
|
+
|
22
|
+
describe Chef::PolicyBuilder::ExpandNodeObject do
|
23
|
+
|
24
|
+
let(:node_name) { "joe_node" }
|
25
|
+
let(:ohai_data) { {"platform" => "ubuntu", "platform_version" => "13.04", "fqdn" => "joenode.example.com"} }
|
26
|
+
let(:json_attribs) { {"run_list" => []} }
|
27
|
+
let(:override_runlist) { "recipe[foo::default]" }
|
28
|
+
let(:events) { Chef::EventDispatch::Dispatcher.new }
|
29
|
+
let(:policy_builder) { Chef::PolicyBuilder::ExpandNodeObject.new(node_name, ohai_data, json_attribs, override_runlist, events) }
|
30
|
+
|
31
|
+
# All methods that Chef::Client calls on this class.
|
32
|
+
describe "Public API" do
|
33
|
+
it "implements a node method" do
|
34
|
+
expect(policy_builder).to respond_to(:node)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "implements a load_node method" do
|
38
|
+
expect(policy_builder).to respond_to(:load_node)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "implements a build_node method" do
|
42
|
+
expect(policy_builder).to respond_to(:build_node)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "implements a setup_run_context method that accepts a list of recipe files to run" do
|
46
|
+
expect(policy_builder).to respond_to(:setup_run_context)
|
47
|
+
expect(policy_builder.method(:setup_run_context).arity).to eq(-1) #optional argument
|
48
|
+
end
|
49
|
+
|
50
|
+
it "implements a run_context method" do
|
51
|
+
expect(policy_builder).to respond_to(:run_context)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "implements an expand_run_list method" do
|
55
|
+
expect(policy_builder).to respond_to(:expand_run_list)
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "loading the node" do
|
59
|
+
|
60
|
+
context "on chef-solo" do
|
61
|
+
|
62
|
+
before do
|
63
|
+
Chef::Config[:solo] = true
|
64
|
+
end
|
65
|
+
|
66
|
+
it "creates a new in-memory node object with the given name" do
|
67
|
+
policy_builder.load_node
|
68
|
+
policy_builder.node.name.should == node_name
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
context "on chef-client" do
|
74
|
+
|
75
|
+
let(:node) { Chef::Node.new.tap { |n| n.name(node_name) } }
|
76
|
+
|
77
|
+
it "loads or creates a node on the server" do
|
78
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
79
|
+
policy_builder.load_node
|
80
|
+
policy_builder.node.should == node
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "building the node" do
|
87
|
+
|
88
|
+
# XXX: Chef::Client just needs to be able to call this, it doesn't depend on the return value.
|
89
|
+
it "builds the node and returns the updated node object" do
|
90
|
+
pending
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
# Implementation specific tests
|
98
|
+
|
99
|
+
describe "when first created" do
|
100
|
+
|
101
|
+
it "has a node_name" do
|
102
|
+
expect(policy_builder.node_name).to eq(node_name)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "has ohai data" do
|
106
|
+
expect(policy_builder.ohai_data).to eq(ohai_data)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "has a set of attributes from command line option" do
|
110
|
+
expect(policy_builder.json_attribs).to eq(json_attribs)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "has an override_runlist" do
|
114
|
+
expect(policy_builder.override_runlist).to eq(override_runlist)
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
context "once the node has been loaded" do
|
120
|
+
let(:node) do
|
121
|
+
node = Chef::Node.new
|
122
|
+
node.name(node_name)
|
123
|
+
node.run_list(["recipe[a::default]", "recipe[b::server]"])
|
124
|
+
node
|
125
|
+
end
|
126
|
+
|
127
|
+
before do
|
128
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
129
|
+
policy_builder.load_node
|
130
|
+
end
|
131
|
+
|
132
|
+
it "expands the run_list" do
|
133
|
+
expect(policy_builder.expand_run_list).to be_a(Chef::RunList::RunListExpansion)
|
134
|
+
expect(policy_builder.run_list_expansion).to be_a(Chef::RunList::RunListExpansion)
|
135
|
+
expect(policy_builder.run_list_expansion.recipes).to eq(["a::default", "b::server"])
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
describe "building the node" do
|
141
|
+
|
142
|
+
let(:configured_environment) { nil }
|
143
|
+
let(:json_attribs) { nil }
|
144
|
+
|
145
|
+
let(:override_runlist) { nil }
|
146
|
+
let(:primary_runlist) { ["recipe[primary::default]"] }
|
147
|
+
|
148
|
+
let(:original_default_attrs) { {"default_key" => "default_value"} }
|
149
|
+
let(:original_override_attrs) { {"override_key" => "override_value"} }
|
150
|
+
|
151
|
+
let(:node) do
|
152
|
+
node = Chef::Node.new
|
153
|
+
node.name(node_name)
|
154
|
+
node.default_attrs = original_default_attrs
|
155
|
+
node.override_attrs = original_override_attrs
|
156
|
+
node.run_list(primary_runlist)
|
157
|
+
node
|
158
|
+
end
|
159
|
+
|
160
|
+
before do
|
161
|
+
Chef::Config[:environment] = configured_environment
|
162
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
163
|
+
policy_builder.load_node
|
164
|
+
policy_builder.build_node
|
165
|
+
end
|
166
|
+
|
167
|
+
it "sanity checks test setup" do
|
168
|
+
expect(node.run_list).to eq(primary_runlist)
|
169
|
+
end
|
170
|
+
|
171
|
+
it "clears existing default and override attributes from the node" do
|
172
|
+
expect(node["default_key"]).to be_nil
|
173
|
+
expect(node["override_key"]).to be_nil
|
174
|
+
end
|
175
|
+
|
176
|
+
it "applies ohai data to the node" do
|
177
|
+
expect(node["fqdn"]).to eq(ohai_data["fqdn"])
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "when the given run list is not in expanded form" do
|
181
|
+
|
182
|
+
# NOTE: for chef-client, the behavior is always to expand the run list,
|
183
|
+
# but this operation is a no-op when none of the run list items are
|
184
|
+
# roles. Because of the amount of mocking required to make this work in
|
185
|
+
# tests, this test is isolated from the others.
|
186
|
+
|
187
|
+
let(:primary_runlist) { ["role[some_role]"] }
|
188
|
+
let(:expansion) do
|
189
|
+
recipe_list = Chef::RunList::VersionedRecipeList.new
|
190
|
+
recipe_list.add_recipe("recipe[from_role::default", "1.0.2")
|
191
|
+
double("RunListExpansion", :recipes => recipe_list)
|
192
|
+
end
|
193
|
+
|
194
|
+
let(:node) do
|
195
|
+
node = Chef::Node.new
|
196
|
+
node.name(node_name)
|
197
|
+
node.default_attrs = original_default_attrs
|
198
|
+
node.override_attrs = original_override_attrs
|
199
|
+
node.run_list(primary_runlist)
|
200
|
+
|
201
|
+
node.should_receive(:expand!).with("server") do
|
202
|
+
node.run_list("recipe[from_role::default]")
|
203
|
+
expansion
|
204
|
+
end
|
205
|
+
|
206
|
+
node
|
207
|
+
end
|
208
|
+
|
209
|
+
it "expands run list items via the server API" do
|
210
|
+
expect(node.run_list).to eq(["recipe[from_role::default]"])
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
|
215
|
+
context "when JSON attributes are given on the command line" do
|
216
|
+
|
217
|
+
let(:json_attribs) { {"run_list" => ["recipe[json_attribs::default]"], "json_attribs_key" => "json_attribs_value" } }
|
218
|
+
|
219
|
+
it "sets the run list according to the given JSON" do
|
220
|
+
expect(node.run_list).to eq(["recipe[json_attribs::default]"])
|
221
|
+
end
|
222
|
+
|
223
|
+
it "sets node attributes according to the given JSON" do
|
224
|
+
expect(node["json_attribs_key"]).to eq("json_attribs_value")
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
|
229
|
+
context "when an override_runlist is given" do
|
230
|
+
|
231
|
+
let(:override_runlist) { "recipe[foo::default]" }
|
232
|
+
|
233
|
+
it "sets the override run_list on the node" do
|
234
|
+
expect(node.run_list).to eq([override_runlist])
|
235
|
+
expect(policy_builder.original_runlist).to eq(primary_runlist)
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
|
240
|
+
context "when no environment is specified" do
|
241
|
+
|
242
|
+
it "does not set the environment" do
|
243
|
+
expect(node.chef_environment).to eq("_default")
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
context "when a custom environment is configured" do
|
249
|
+
|
250
|
+
let(:configured_environment) { environment.name }
|
251
|
+
|
252
|
+
let(:environment) do
|
253
|
+
environment = Chef::Environment.new.tap {|e| e.name("prod") }
|
254
|
+
Chef::Environment.should_receive(:load).with("prod").and_return(environment)
|
255
|
+
environment
|
256
|
+
end
|
257
|
+
|
258
|
+
it "sets the environment as configured" do
|
259
|
+
expect(node.chef_environment).to eq(environment.name)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
describe "configuring the run_context" do
|
266
|
+
let(:json_attribs) { nil }
|
267
|
+
let(:override_runlist) { nil }
|
268
|
+
|
269
|
+
let(:node) do
|
270
|
+
node = Chef::Node.new
|
271
|
+
node.name(node_name)
|
272
|
+
node.run_list("recipe[first::default]", "recipe[second::default]")
|
273
|
+
node
|
274
|
+
end
|
275
|
+
|
276
|
+
let(:chef_http) { double("Chef::REST") }
|
277
|
+
|
278
|
+
let(:cookbook_resolve_url) { "environments/#{node.chef_environment}/cookbook_versions" }
|
279
|
+
let(:cookbook_resolve_post_data) { {:run_list=>["first::default", "second::default"]} }
|
280
|
+
|
281
|
+
# cookbook_hash is just a hash, but since we're passing it between mock
|
282
|
+
# objects, we get a little better test strictness by using a double (which
|
283
|
+
# will have object equality rather than semantic equality #== semantics).
|
284
|
+
let(:cookbook_hash) { double("cookbook hash", :each => nil) }
|
285
|
+
|
286
|
+
let(:cookbook_synchronizer) { double("CookbookSynchronizer") }
|
287
|
+
|
288
|
+
before do
|
289
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
290
|
+
|
291
|
+
policy_builder.stub(:api_service).and_return(chef_http)
|
292
|
+
|
293
|
+
policy_builder.load_node
|
294
|
+
policy_builder.build_node
|
295
|
+
|
296
|
+
run_list_expansion = policy_builder.run_list_expansion
|
297
|
+
|
298
|
+
chef_http.should_receive(:post).with(cookbook_resolve_url, cookbook_resolve_post_data).and_return(cookbook_hash)
|
299
|
+
Chef::CookbookSynchronizer.should_receive(:new).with(cookbook_hash, events).and_return(cookbook_synchronizer)
|
300
|
+
cookbook_synchronizer.should_receive(:sync_cookbooks)
|
301
|
+
|
302
|
+
Chef::RunContext.any_instance.should_receive(:load).with(run_list_expansion)
|
303
|
+
|
304
|
+
policy_builder.setup_run_context
|
305
|
+
end
|
306
|
+
|
307
|
+
it "configures FileVendor to fetch files remotely" do
|
308
|
+
manifest = double("cookbook manifest")
|
309
|
+
Chef::Cookbook::RemoteFileVendor.should_receive(:new).with(manifest, chef_http)
|
310
|
+
Chef::Cookbook::FileVendor.create_from_manifest(manifest)
|
311
|
+
end
|
312
|
+
|
313
|
+
it "triggers cookbook compilation in the run_context" do
|
314
|
+
# Test condition already covered by `Chef::RunContext.any_instance.should_receive(:load).with(run_list_expansion)`
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
@@ -0,0 +1,399 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Daniel DeLeo (<dan@getchef.com>)
|
3
|
+
# Copyright:: Copyright 2014 Chef Software, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'spec_helper'
|
20
|
+
require 'chef/policy_builder'
|
21
|
+
|
22
|
+
describe Chef::PolicyBuilder::Policyfile do
|
23
|
+
|
24
|
+
let(:node_name) { "joe_node" }
|
25
|
+
let(:ohai_data) { {"platform" => "ubuntu", "platform_version" => "13.04", "fqdn" => "joenode.example.com"} }
|
26
|
+
let(:json_attribs) { {"custom_attr" => "custom_attr_value"} }
|
27
|
+
let(:override_runlist) { nil }
|
28
|
+
let(:events) { Chef::EventDispatch::Dispatcher.new }
|
29
|
+
let(:policy_builder) { Chef::PolicyBuilder::Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events) }
|
30
|
+
|
31
|
+
# Convert a SHA1 (160 bit) hex string into an x.y.z version number where the
|
32
|
+
# maximum value is smaller than a postgres BIGINT (signed 64bit, so 63 usable
|
33
|
+
# bits). This requires enterprise Chef or open source server 11.1.0+ (currently not released)
|
34
|
+
#
|
35
|
+
# The SHA1 is devided as follows:
|
36
|
+
# * "major": first 14 chars (56 bits)
|
37
|
+
# * "minor": next 14 chars (56 bits)
|
38
|
+
# * "patch": last 12 chars (48 bits)
|
39
|
+
def id_to_dotted(sha1_id)
|
40
|
+
major = sha1_id[0...14]
|
41
|
+
minor = sha1_id[14...28]
|
42
|
+
patch = sha1_id[28..40]
|
43
|
+
decimal_integers =[major, minor, patch].map {|hex| hex.to_i(16) }
|
44
|
+
decimal_integers.join(".")
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
let(:example1_lock_data) do
|
49
|
+
# based on https://github.com/danielsdeleo/chef-workflow2-prototype/blob/master/skeletons/basic_policy/Policyfile.lock.json
|
50
|
+
{
|
51
|
+
"identifier" => "168d2102fb11c9617cd8a981166c8adc30a6e915",
|
52
|
+
"version" => "2.3.5",
|
53
|
+
# NOTE: for compatibility mode we include the dotted id in the policyfile to enhance discoverability.
|
54
|
+
"dotted_decimal_identifier" => id_to_dotted("168d2102fb11c9617cd8a981166c8adc30a6e915"),
|
55
|
+
"source" => { "path" => "./cookbooks/demo" },
|
56
|
+
"scm_identifier"=> {
|
57
|
+
"vcs"=> "git",
|
58
|
+
"rev_id"=> "9d5b09026470c322c3cb5ca8a4157c4d2f16cef3",
|
59
|
+
"remote"=> nil
|
60
|
+
}
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
let(:example2_lock_data) do
|
65
|
+
{
|
66
|
+
"identifier" => "feab40e1fca77c7360ccca1481bb8ba5f919ce3a",
|
67
|
+
"version" => "4.2.0",
|
68
|
+
# NOTE: for compatibility mode we include the dotted id in the policyfile to enhance discoverability.
|
69
|
+
"dotted_decimal_identifier" => id_to_dotted("feab40e1fca77c7360ccca1481bb8ba5f919ce3a"),
|
70
|
+
"source" => { "api" => "https://community.getchef.com/api/v1/cookbooks/example2" }
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
let(:policyfile_default_attributes) { {"policyfile_default_attr" => "policyfile_default_value"} }
|
75
|
+
let(:policyfile_override_attributes) { {"policyfile_override_attr" => "policyfile_override_value"} }
|
76
|
+
|
77
|
+
let(:policyfile_run_list) { ["recipe[example1::default]", "recipe[example2::server]"] }
|
78
|
+
|
79
|
+
let(:parsed_policyfile_json) do
|
80
|
+
{
|
81
|
+
"run_list" => policyfile_run_list,
|
82
|
+
|
83
|
+
"cookbook_locks" => {
|
84
|
+
"example1" => example1_lock_data,
|
85
|
+
"example2" => example2_lock_data
|
86
|
+
},
|
87
|
+
|
88
|
+
"default_attributes" => policyfile_default_attributes,
|
89
|
+
"override_attributes" => policyfile_override_attributes
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
let(:err_namespace) { Chef::PolicyBuilder::Policyfile }
|
94
|
+
|
95
|
+
it "configures a Chef HTTP API client" do
|
96
|
+
http = double("Chef::REST")
|
97
|
+
server_url = "https://api.opscode.com/organizations/example"
|
98
|
+
Chef::Config[:chef_server_url] = server_url
|
99
|
+
Chef::REST.should_receive(:new).with(server_url).and_return(http)
|
100
|
+
expect(policy_builder.http_api).to eq(http)
|
101
|
+
end
|
102
|
+
|
103
|
+
describe "reporting unsupported features" do
|
104
|
+
|
105
|
+
def initialize_pb
|
106
|
+
Chef::PolicyBuilder::Policyfile.new(node_name, ohai_data, json_attribs, override_runlist, events)
|
107
|
+
end
|
108
|
+
|
109
|
+
context "chef-solo" do
|
110
|
+
before { Chef::Config[:solo] = true }
|
111
|
+
|
112
|
+
it "errors on create" do
|
113
|
+
expect { initialize_pb }.to raise_error(err_namespace::UnsupportedFeature)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "when given an override run_list" do
|
118
|
+
let(:override_runlist) { "recipe[foo],recipe[bar]" }
|
119
|
+
|
120
|
+
it "errors on create" do
|
121
|
+
expect { initialize_pb }.to raise_error(err_namespace::UnsupportedFeature)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
context "when json_attribs contains a run_list" do
|
126
|
+
let(:json_attribs) { {"run_list" => []} }
|
127
|
+
|
128
|
+
it "errors on create" do
|
129
|
+
expect { initialize_pb }.to raise_error(err_namespace::UnsupportedFeature)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when an environment is configured" do
|
134
|
+
before { Chef::Config[:environment] = "blurch" }
|
135
|
+
|
136
|
+
it "errors when an environment is configured" do
|
137
|
+
expect { initialize_pb }.to raise_error(err_namespace::UnsupportedFeature)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "when using compatibility mode" do
|
144
|
+
|
145
|
+
let(:http_api) { double("Chef::REST") }
|
146
|
+
|
147
|
+
let(:configured_environment) { nil }
|
148
|
+
|
149
|
+
let(:override_runlist) { nil }
|
150
|
+
let(:primary_runlist) { nil }
|
151
|
+
|
152
|
+
let(:original_default_attrs) { {"default_key" => "default_value"} }
|
153
|
+
let(:original_override_attrs) { {"override_key" => "override_value"} }
|
154
|
+
|
155
|
+
let(:node) do
|
156
|
+
node = Chef::Node.new
|
157
|
+
node.name(node_name)
|
158
|
+
node.default_attrs = original_default_attrs
|
159
|
+
node.override_attrs = original_override_attrs
|
160
|
+
node.run_list(primary_runlist) if primary_runlist
|
161
|
+
node
|
162
|
+
end
|
163
|
+
|
164
|
+
before do
|
165
|
+
# TODO: agree on this name and logic.
|
166
|
+
Chef::Config[:deployment_group] = "example-policy-stage"
|
167
|
+
policy_builder.stub(:http_api).and_return(http_api)
|
168
|
+
end
|
169
|
+
|
170
|
+
context "when the deployment group cannot be loaded" do
|
171
|
+
let(:error404) { Net::HTTPServerException.new("404 message", :body) }
|
172
|
+
|
173
|
+
before do
|
174
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
175
|
+
http_api.should_receive(:get).
|
176
|
+
with("data/policyfiles/example-policy-stage").
|
177
|
+
and_raise(error404)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "raises an error" do
|
181
|
+
expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "sends error message to the event system" do
|
185
|
+
events.should_receive(:node_load_failed).with(node_name, an_instance_of(err_namespace::ConfigurationError), Chef::Config)
|
186
|
+
expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError)
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "when the deployment_group is not configured" do
|
192
|
+
before do
|
193
|
+
Chef::Config[:deployment_group] = nil
|
194
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "errors while loading the node" do
|
198
|
+
expect { policy_builder.load_node }.to raise_error(err_namespace::ConfigurationError)
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
it "passes error information to the event system" do
|
203
|
+
# TODO: also make sure something acceptable happens with the error formatters
|
204
|
+
err_class = err_namespace::ConfigurationError
|
205
|
+
events.should_receive(:node_load_failed).with(node_name, an_instance_of(err_class), Chef::Config)
|
206
|
+
expect { policy_builder.load_node }.to raise_error(err_class)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context "and a deployment_group is configured" do
|
211
|
+
before do
|
212
|
+
http_api.should_receive(:get).with("data/policyfiles/example-policy-stage").and_return(parsed_policyfile_json)
|
213
|
+
end
|
214
|
+
|
215
|
+
it "fetches the policy file from a data bag item" do
|
216
|
+
expect(policy_builder.policy).to eq(parsed_policyfile_json)
|
217
|
+
end
|
218
|
+
|
219
|
+
it "extracts the run_list from the policyfile" do
|
220
|
+
expect(policy_builder.run_list).to eq(policyfile_run_list)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "extracts the cookbooks and versions for display from the policyfile" do
|
224
|
+
expected = [
|
225
|
+
"example1::default@2.3.5 (168d210)",
|
226
|
+
"example2::server@4.2.0 (feab40e)"
|
227
|
+
]
|
228
|
+
|
229
|
+
expect(policy_builder.run_list_with_versions_for_display).to eq(expected)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "generates a RunListExpansion-alike object for feeding to the CookbookCompiler" do
|
233
|
+
expect(policy_builder.run_list_expansion_ish).to respond_to(:recipes)
|
234
|
+
expect(policy_builder.run_list_expansion_ish.recipes).to eq(["example1::default", "example2::server"])
|
235
|
+
end
|
236
|
+
|
237
|
+
it "implements #expand_run_list in a manner compatible with ExpandNodeObject" do
|
238
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
239
|
+
policy_builder.load_node
|
240
|
+
expect(policy_builder.expand_run_list).to respond_to(:recipes)
|
241
|
+
expect(policy_builder.expand_run_list.recipes).to eq(["example1::default", "example2::server"])
|
242
|
+
expect(policy_builder.expand_run_list.roles).to eq([])
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
describe "validating the Policyfile.lock" do
|
247
|
+
|
248
|
+
it "errors if the policyfile json contains any non-recipe items" do
|
249
|
+
parsed_policyfile_json["run_list"] = ["role[foo]"]
|
250
|
+
expect { policy_builder.validate_policyfile }.to raise_error(err_namespace::PolicyfileError)
|
251
|
+
end
|
252
|
+
|
253
|
+
it "errors if the policyfile json contains non-fully qualified recipe items" do
|
254
|
+
parsed_policyfile_json["run_list"] = ["recipe[foo]"]
|
255
|
+
expect { policy_builder.validate_policyfile }.to raise_error(err_namespace::PolicyfileError)
|
256
|
+
end
|
257
|
+
|
258
|
+
it "errors if the policyfile doesn't have a run_list key" do
|
259
|
+
parsed_policyfile_json.delete("run_list")
|
260
|
+
expect { policy_builder.validate_policyfile }.to raise_error(err_namespace::PolicyfileError)
|
261
|
+
end
|
262
|
+
|
263
|
+
it "error if the policyfile doesn't have a cookbook_locks key" do
|
264
|
+
parsed_policyfile_json.delete("cookbook_locks")
|
265
|
+
expect { policy_builder.validate_policyfile }.to raise_error(err_namespace::PolicyfileError)
|
266
|
+
end
|
267
|
+
|
268
|
+
it "accepts a valid policyfile" do
|
269
|
+
policy_builder.validate_policyfile
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
describe "building the node object" do
|
275
|
+
|
276
|
+
before do
|
277
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
278
|
+
|
279
|
+
policy_builder.load_node
|
280
|
+
policy_builder.build_node
|
281
|
+
end
|
282
|
+
|
283
|
+
it "resets default and override data" do
|
284
|
+
expect(node["default_key"]).to be_nil
|
285
|
+
expect(node["override_key"]).to be_nil
|
286
|
+
end
|
287
|
+
|
288
|
+
it "applies ohai data" do
|
289
|
+
expect(ohai_data).to_not be_empty # ensure test is testing something
|
290
|
+
ohai_data.each do |key, value|
|
291
|
+
expect(node.automatic_attrs[key]).to eq(value)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
it "applies attributes from json file" do
|
296
|
+
expect(node["custom_attr"]).to eq("custom_attr_value")
|
297
|
+
end
|
298
|
+
|
299
|
+
it "applies attributes from the policyfile" do
|
300
|
+
expect(node["policyfile_default_attr"]).to eq("policyfile_default_value")
|
301
|
+
expect(node["policyfile_override_attr"]).to eq("policyfile_override_value")
|
302
|
+
end
|
303
|
+
|
304
|
+
it "sets the policyfile's run_list on the node object" do
|
305
|
+
expect(node.run_list).to eq(policyfile_run_list)
|
306
|
+
end
|
307
|
+
|
308
|
+
it "creates node.automatic_attrs[:roles]" do
|
309
|
+
expect(node.automatic_attrs[:roles]).to eq([])
|
310
|
+
end
|
311
|
+
|
312
|
+
it "create node.automatic_attrs[:recipes]" do
|
313
|
+
expect(node.automatic_attrs[:recipes]).to eq(["example1::default", "example2::server"])
|
314
|
+
end
|
315
|
+
|
316
|
+
end
|
317
|
+
|
318
|
+
|
319
|
+
describe "fetching the desired cookbook set" do
|
320
|
+
|
321
|
+
let(:example1_cookbook_object) { double("Chef::CookbookVersion for example1 cookbook") }
|
322
|
+
let(:example2_cookbook_object) { double("Chef::CookbookVersion for example2 cookbook") }
|
323
|
+
|
324
|
+
let(:expected_cookbook_hash) do
|
325
|
+
{ "example1" => example1_cookbook_object, "example2" => example2_cookbook_object }
|
326
|
+
end
|
327
|
+
|
328
|
+
let(:example1_xyz_version) { example1_lock_data["dotted_decimal_identifier"] }
|
329
|
+
let(:example2_xyz_version) { example2_lock_data["dotted_decimal_identifier"] }
|
330
|
+
|
331
|
+
let(:cookbook_synchronizer) { double("Chef::CookbookSynchronizer") }
|
332
|
+
|
333
|
+
context "and a cookbook is missing" do
|
334
|
+
|
335
|
+
let(:error404) { Net::HTTPServerException.new("404 message", :body) }
|
336
|
+
|
337
|
+
before do
|
338
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
339
|
+
|
340
|
+
# Remove references to example2 cookbook because we're iterating
|
341
|
+
# over a Hash data structure and on ruby 1.8.7 iteration order will
|
342
|
+
# not be stable.
|
343
|
+
parsed_policyfile_json["cookbook_locks"].delete("example2")
|
344
|
+
parsed_policyfile_json["run_list"].delete("recipe[example2::server]")
|
345
|
+
|
346
|
+
policy_builder.load_node
|
347
|
+
policy_builder.build_node
|
348
|
+
|
349
|
+
http_api.should_receive(:get).with("cookbooks/example1/#{example1_xyz_version}").
|
350
|
+
and_raise(error404)
|
351
|
+
end
|
352
|
+
|
353
|
+
it "raises an error indicating which cookbook is missing" do
|
354
|
+
expect { policy_builder.cookbooks_to_sync }.to raise_error(Chef::Exceptions::CookbookNotFound)
|
355
|
+
end
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
context "and the cookbooks can be fetched" do
|
360
|
+
before do
|
361
|
+
Chef::Node.should_receive(:find_or_create).with(node_name).and_return(node)
|
362
|
+
|
363
|
+
policy_builder.load_node
|
364
|
+
policy_builder.build_node
|
365
|
+
|
366
|
+
http_api.should_receive(:get).with("cookbooks/example1/#{example1_xyz_version}").
|
367
|
+
and_return(example1_cookbook_object)
|
368
|
+
http_api.should_receive(:get).with("cookbooks/example2/#{example2_xyz_version}").
|
369
|
+
and_return(example2_cookbook_object)
|
370
|
+
|
371
|
+
Chef::CookbookSynchronizer.stub(:new).
|
372
|
+
with(expected_cookbook_hash, events).
|
373
|
+
and_return(cookbook_synchronizer)
|
374
|
+
end
|
375
|
+
|
376
|
+
it "builds a Hash of the form 'cookbook_name' => Chef::CookbookVersion" do
|
377
|
+
expect(policy_builder.cookbooks_to_sync).to eq(expected_cookbook_hash)
|
378
|
+
end
|
379
|
+
|
380
|
+
it "syncs the desired cookbooks via CookbookSynchronizer" do
|
381
|
+
cookbook_synchronizer.should_receive(:sync_cookbooks)
|
382
|
+
policy_builder.sync_cookbooks
|
383
|
+
end
|
384
|
+
|
385
|
+
it "builds a run context" do
|
386
|
+
cookbook_synchronizer.should_receive(:sync_cookbooks)
|
387
|
+
Chef::RunContext.any_instance.should_receive(:load).with(policy_builder.run_list_expansion_ish)
|
388
|
+
run_context = policy_builder.setup_run_context
|
389
|
+
expect(run_context.node).to eq(node)
|
390
|
+
expect(run_context.cookbook_collection.keys).to match_array(["example1", "example2"])
|
391
|
+
end
|
392
|
+
|
393
|
+
end
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
end
|
398
|
+
|
399
|
+
end
|