chef-dk 2.3.4 → 2.4.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +22 -18
  3. data/Gemfile.lock +184 -254
  4. data/README.md +1 -1
  5. data/Rakefile +1 -1
  6. data/acceptance/Gemfile.lock +27 -32
  7. data/lib/chef-dk/chef_server_api_multi.rb +73 -0
  8. data/lib/chef-dk/command/update.rb +5 -12
  9. data/lib/chef-dk/configurable.rb +19 -0
  10. data/lib/chef-dk/cookbook_omnifetch.rb +1 -0
  11. data/lib/chef-dk/exceptions.rb +11 -0
  12. data/lib/chef-dk/generator.rb +1 -1
  13. data/lib/chef-dk/policyfile/attribute_merge_checker.rb +110 -0
  14. data/lib/chef-dk/policyfile/chef_server_cookbook_source.rb +5 -4
  15. data/lib/chef-dk/policyfile/chef_server_lock_fetcher.rb +164 -0
  16. data/lib/chef-dk/policyfile/cookbook_location_specification.rb +3 -3
  17. data/lib/chef-dk/policyfile/dsl.rb +16 -0
  18. data/lib/chef-dk/policyfile/included_policies_cookbook_source.rb +156 -0
  19. data/lib/chef-dk/policyfile/local_lock_fetcher.rb +122 -0
  20. data/lib/chef-dk/policyfile/lock_applier.rb +80 -0
  21. data/lib/chef-dk/policyfile/null_cookbook_source.rb +4 -0
  22. data/lib/chef-dk/policyfile/policyfile_location_specification.rb +122 -0
  23. data/lib/chef-dk/policyfile_compiler.rb +129 -16
  24. data/lib/chef-dk/policyfile_lock.rb +30 -0
  25. data/lib/chef-dk/policyfile_services/install.rb +7 -1
  26. data/lib/chef-dk/policyfile_services/update_attributes.rb +10 -2
  27. data/lib/chef-dk/skeletons/code_generator/templates/default/recipe_spec.rb.erb +14 -1
  28. data/lib/chef-dk/version.rb +1 -1
  29. data/omnibus_overrides.rb +6 -6
  30. data/spec/unit/chef_server_api_multi_spec.rb +120 -0
  31. data/spec/unit/command/update_spec.rb +3 -3
  32. data/spec/unit/configurable_spec.rb +27 -0
  33. data/spec/unit/policyfile/attribute_merge_checker_spec.rb +80 -0
  34. data/spec/unit/policyfile/chef_server_lock_fetcher_spec.rb +161 -0
  35. data/spec/unit/policyfile/cookbook_location_specification_spec.rb +48 -0
  36. data/spec/unit/policyfile/included_policies_cookbook_source_spec.rb +242 -0
  37. data/spec/unit/policyfile/local_lock_fetcher_spec.rb +161 -0
  38. data/spec/unit/policyfile/lock_applier_spec.rb +100 -0
  39. data/spec/unit/policyfile_demands_spec.rb +1 -1
  40. data/spec/unit/policyfile_includes_dsl_spec.rb +159 -0
  41. data/spec/unit/policyfile_includes_spec.rb +720 -0
  42. data/spec/unit/policyfile_install_with_includes_spec.rb +232 -0
  43. data/spec/unit/policyfile_lock_build_spec.rb +11 -2
  44. data/spec/unit/policyfile_services/update_attributes_spec.rb +13 -0
  45. metadata +28 -3
@@ -278,6 +278,16 @@ describe ChefDK::Policyfile::CookbookLocationSpecification do
278
278
 
279
279
  let(:source_options) { { chef_server: "https://api.opscode.com/organizations/chef-oss-dev/cookbooks/my_cookbook/versions/2.0.0/download" } }
280
280
 
281
+ let(:http_client) { instance_double("ChefDK::ChefServerAPIMulti") }
282
+
283
+ before do
284
+ CookbookOmnifetch.integration.default_chef_server_http_client = http_client
285
+ end
286
+
287
+ after do
288
+ CookbookOmnifetch.integration.default_chef_server_http_client = nil
289
+ end
290
+
281
291
  it "has a chef_server installer" do
282
292
  expect(cookbook_location_spec.installer).to be_a_kind_of(CookbookOmnifetch::ChefServerLocation)
283
293
  end
@@ -296,4 +306,42 @@ describe ChefDK::Policyfile::CookbookLocationSpecification do
296
306
  end
297
307
 
298
308
  end
309
+
310
+ describe "when created with a chef_server_artifact source" do
311
+
312
+ let(:source_options) do
313
+ {
314
+ chef_server_artifact: "https://api.opscode.com/organizations/chef-oss-dev/",
315
+ identifier: "09d43fad354b3efcc5b5836fef5137131f60f974",
316
+ }
317
+ end
318
+
319
+ let(:http_client) { instance_double("ChefDK::ChefServerAPIMulti") }
320
+
321
+ before do
322
+ CookbookOmnifetch.integration.default_chef_server_http_client = http_client
323
+ end
324
+
325
+ after do
326
+ CookbookOmnifetch.integration.default_chef_server_http_client = nil
327
+ end
328
+
329
+ it "has a chef_server_artifact installer" do
330
+ expect(cookbook_location_spec.installer).to be_a_kind_of(CookbookOmnifetch::ChefServerArtifactLocation)
331
+ end
332
+
333
+ it "does has a fixed version" do
334
+ expect(cookbook_location_spec.version_fixed?).to be(true)
335
+ end
336
+
337
+ it "is a mirror of a canonical upstream" do
338
+ expect(cookbook_location_spec.mirrors_canonical_upstream?).to be(true)
339
+ end
340
+
341
+ it "is valid" do
342
+ expect(cookbook_location_spec.errors.size).to eq(0)
343
+ expect(cookbook_location_spec).to be_valid
344
+ end
345
+
346
+ end
299
347
  end
@@ -0,0 +1,242 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 Chef Software 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 "spec_helper"
19
+ require "chef-dk/policyfile/included_policies_cookbook_source"
20
+ require "chef-dk/policyfile/policyfile_location_specification"
21
+
22
+ describe ChefDK::Policyfile::IncludedPoliciesCookbookSource do
23
+
24
+ let(:external_cookbook_universe) do
25
+ {
26
+ "cookbookA" => {
27
+ "1.0.0" => [ ],
28
+ "2.0.0" => [ ["cookbookB", "= 1.0.0" ] ],
29
+ },
30
+ "cookbookB" => {
31
+ "1.0.0" => [ ],
32
+ },
33
+ "cookbookC" => {
34
+ "1.0.0" => [ ],
35
+ "2.0.0" => [ ],
36
+ },
37
+ }
38
+ end
39
+
40
+ let(:policy1_cookbooks) do
41
+ [
42
+ {
43
+ name: "cookbookA",
44
+ version: "2.0.0",
45
+ },
46
+ # cookbookB because cookbookA depends on it
47
+ {
48
+ name: "cookbookB",
49
+ version: "1.0.0",
50
+ },
51
+ ]
52
+ end
53
+
54
+ let(:randomize_sources) { false }
55
+ let(:add_random_cookbook) { false }
56
+
57
+ def build_lockdata(cookbooks)
58
+ nonce = if randomize_sources
59
+ Random.rand
60
+ else
61
+ nil
62
+ end
63
+
64
+ cookbook_locks = cookbooks.inject({}) do |acc, cookbook_info|
65
+ acc[cookbook_info[:name]] = {
66
+ "version" => cookbook_info[:version],
67
+ "identifier" => "identifier",
68
+ "dotted_decimal_identifier" => "dotted_decimal_identifier",
69
+ "cache_key" => "#{cookbook_info[:name]}-#{cookbook_info[:version]}",
70
+ "origin" => "uri",
71
+ "source_options" => { "version" => cookbook_info[:version] }.tap do |so|
72
+ so["nonce"] = nonce if !nonce.nil?
73
+ end,
74
+ }
75
+ acc
76
+ end
77
+
78
+ solution_dependencies_lock = cookbooks.map do |cookbook_info|
79
+ [cookbook_info[:name], cookbook_info[:version]]
80
+ end
81
+
82
+ solution_dependencies_cookbooks = cookbooks.inject({}) do |acc, cookbook_info|
83
+ acc["#{cookbook_info[:name]} (#{cookbook_info[:version]})"] = external_cookbook_universe[cookbook_info[:name]][cookbook_info[:version]]
84
+ acc
85
+ end
86
+
87
+ {
88
+ "name" => "included_policyfile",
89
+ "revision_id" => "myrevisionid",
90
+ "run_list" => ["recipe[myrunlist::default]"],
91
+ "cookbook_locks" => cookbook_locks,
92
+ "default_attributes" => {},
93
+ "override_attributes" => {},
94
+ "solution_dependencies" => {
95
+ "Policyfile" => solution_dependencies_lock,
96
+ ## We dont use dependencies, no need to fill it out
97
+ "dependencies" => solution_dependencies_cookbooks,
98
+ },
99
+ }
100
+ end
101
+
102
+ let(:policy1_lockdata) do
103
+ build_lockdata(policy1_cookbooks)
104
+ end
105
+
106
+ let(:policy2_lockdata) do
107
+ build_lockdata(policy2_cookbooks)
108
+ end
109
+
110
+ let(:policy1_fetcher) do
111
+ instance_double("ChefDK::Policyfile::LocalLockFetcher").tap do |double|
112
+ allow(double).to receive(:lock_data).and_return(policy1_lockdata)
113
+ allow(double).to receive(:valid?).and_return(true)
114
+ allow(double).to receive(:errors).and_return([])
115
+ end
116
+ end
117
+
118
+ let(:policy2_fetcher) do
119
+ instance_double("ChefDK::Policyfile::LocalLockFetcher").tap do |double|
120
+ allow(double).to receive(:lock_data).and_return(policy2_lockdata)
121
+ allow(double).to receive(:valid?).and_return(true)
122
+ allow(double).to receive(:errors).and_return([])
123
+ end
124
+ end
125
+
126
+ let(:policy1_location_spec) do
127
+ ChefDK::Policyfile::PolicyfileLocationSpecification.new("policy1", { :path => "somelocation" }, nil).tap do |spec|
128
+ allow(spec).to receive(:valid?).and_return(true)
129
+ allow(spec).to receive(:fetcher).and_return(policy1_fetcher)
130
+ end
131
+ end
132
+
133
+ let(:policy2_location_spec) do
134
+ ChefDK::Policyfile::PolicyfileLocationSpecification.new("policy2", { :path => "somelocation" }, nil).tap do |spec|
135
+ allow(spec).to receive(:valid?).and_return(true)
136
+ allow(spec).to receive(:fetcher).and_return(policy2_fetcher)
137
+ end
138
+ end
139
+
140
+ let(:policies) { [] }
141
+
142
+ let(:cookbook_source) { ChefDK::Policyfile::IncludedPoliciesCookbookSource.new(policies) }
143
+
144
+ context "when no policies are included" do
145
+ it "returns false for preferred_source_for" do
146
+ expect(cookbook_source.preferred_source_for?("foo")).to eq(false)
147
+ end
148
+
149
+ it "has an empty universe" do
150
+ expect(cookbook_source.universe_graph).to eq({})
151
+ end
152
+ end
153
+
154
+ context "when a single policy is to be included" do
155
+ let(:policies) { [policy1_location_spec] }
156
+
157
+ it "does not have a preferred source for unlocked cookbooks" do
158
+ expect(cookbook_source.preferred_source_for?("cookbookC")).to eq(false)
159
+ end
160
+
161
+ it "has a preferred source for the included cookbooks" do
162
+ expect(cookbook_source.preferred_source_for?("cookbookA")).to eq(true)
163
+ expect(cookbook_source.preferred_source_for?("cookbookB")).to eq(true)
164
+ end
165
+
166
+ it "returns nil for the source options versions not included" do
167
+ expect(cookbook_source.source_options_for("cookbookA", "1.0.0")).to eq(nil)
168
+ end
169
+
170
+ it "returns the correct source options when the cookbook is included" do
171
+ expect(cookbook_source.source_options_for("cookbookA", "2.0.0")).to eq({ :version => "2.0.0" })
172
+ expect(cookbook_source.source_options_for("cookbookB", "1.0.0")).to eq({ :version => "1.0.0" })
173
+ end
174
+
175
+ it "has a universe with the used cookbooks" do
176
+ expect(cookbook_source.universe_graph).to eq({
177
+ "cookbookA" => {
178
+ "2.0.0" => external_cookbook_universe["cookbookA"]["2.0.0"],
179
+ },
180
+ "cookbookB" => {
181
+ "1.0.0" => external_cookbook_universe["cookbookB"]["1.0.0"],
182
+ },
183
+ })
184
+ end
185
+ end
186
+
187
+ context "when multiple policies are to be included" do
188
+ let(:policies) { [policy1_location_spec, policy2_location_spec] }
189
+
190
+ context "when the policies use the same cookbooks with the same versions and sources" do
191
+ let(:policy2_cookbooks) { policy1_cookbooks + [{ name: "cookbookC", version: "2.0.0" }] }
192
+
193
+ it "has a preferred source for the included cookbooks" do
194
+ expect(cookbook_source.preferred_source_for?("cookbookA")).to eq(true)
195
+ expect(cookbook_source.preferred_source_for?("cookbookB")).to eq(true)
196
+ expect(cookbook_source.preferred_source_for?("cookbookC")).to eq(true)
197
+ end
198
+
199
+ it "returns the correct source options when the cookbook is included" do
200
+ expect(cookbook_source.source_options_for("cookbookA", "2.0.0")).to eq({ :version => "2.0.0" })
201
+ expect(cookbook_source.source_options_for("cookbookB", "1.0.0")).to eq({ :version => "1.0.0" })
202
+ end
203
+
204
+ it "has a universe with the used cookbooks" do
205
+ expect(cookbook_source.universe_graph).to eq({
206
+ "cookbookA" => {
207
+ "2.0.0" => external_cookbook_universe["cookbookA"]["2.0.0"],
208
+ },
209
+ "cookbookB" => {
210
+ "1.0.0" => external_cookbook_universe["cookbookB"]["1.0.0"],
211
+ },
212
+ "cookbookC" => {
213
+ "2.0.0" => external_cookbook_universe["cookbookC"]["2.0.0"],
214
+ },
215
+ })
216
+ end
217
+ end
218
+
219
+ context "when the policies have the same versions with conflicting sources" do
220
+ let(:randomize_sources) { true }
221
+ let(:policy2_cookbooks) { policy1_cookbooks }
222
+
223
+ it "raises an error when check_for_conflicts! is called" do
224
+ expect { cookbook_source.check_for_conflicts! }.to raise_error(
225
+ ChefDK::Policyfile::IncludedPoliciesCookbookSource::ConflictingCookbookSources)
226
+ end
227
+ end
228
+
229
+ context "when the policies have conflicting versions" do
230
+ let(:policy2_cookbooks) { [{ name: "cookbookA", version: "1.0.0" }] }
231
+
232
+ it "raises an error when check_for_conflicts! is called" do
233
+ expect { cookbook_source.check_for_conflicts! }.to raise_error(
234
+ ChefDK::Policyfile::IncludedPoliciesCookbookSource::ConflictingCookbookVersions)
235
+ end
236
+ end
237
+
238
+ context "when the policies have conflicting dependencies" do
239
+ it "raises an error when check_for_conflicts! is called"
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,161 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2017 Chef Software 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 "spec_helper"
19
+ require "chef-dk/policyfile/local_lock_fetcher"
20
+
21
+ describe ChefDK::Policyfile::LocalLockFetcher do
22
+
23
+ let(:minimal_lockfile_json) do
24
+ <<-E
25
+ {
26
+ "revision_id": "6fe753184c8946052d3231bb4212116df28d89a3a5f7ae52832ad408419dd5eb",
27
+ "name": "install-example",
28
+ "run_list": [
29
+ "recipe[local-cookbook::default]"
30
+ ],
31
+ "cookbook_locks": {
32
+ "local-cookbook": {
33
+ "version": "2.3.4",
34
+ "identifier": "fab501cfaf747901bd82c1bc706beae7dc3a350c",
35
+ "dotted_decimal_identifier": "70567763561641081.489844270461035.258281553147148",
36
+ "source": "cookbooks/local-cookbook",
37
+ "cache_key": null,
38
+ "scm_info": null,
39
+ "source_options": {
40
+ "path": "../cookbooks/local-cookbook"
41
+ }
42
+ }
43
+ },
44
+ "default_attributes": {},
45
+ "override_attributes": {},
46
+ "solution_dependencies": {
47
+ "Policyfile": [
48
+ [
49
+ "local-cookbook",
50
+ ">= 0.0.0"
51
+ ]
52
+ ],
53
+ "dependencies": {
54
+ "local-cookbook (2.3.4)": [
55
+
56
+ ]
57
+ }
58
+ }
59
+ }
60
+ E
61
+ end
62
+
63
+ let(:lock_file_path) { "#{tempdir}/foo/bar/baz/foo.lock.json" }
64
+ let(:storage_config) { ChefDK::Policyfile::StorageConfig.new.use_policyfile("#{tempdir}/Policyfile.rb") }
65
+
66
+ before do
67
+ reset_tempdir
68
+ FileUtils.mkdir_p(Pathname.new(lock_file_path).dirname)
69
+ File.open(lock_file_path, "w") { |file| file.write(minimal_lockfile_json) }
70
+ end
71
+
72
+ after do
73
+ reset_tempdir
74
+ end
75
+
76
+ def minimal_lockfile
77
+ FFI_Yajl::Parser.parse(minimal_lockfile_json)
78
+ end
79
+
80
+ let(:minimal_lockfile_modified) do
81
+ minimal_lockfile.tap do |lockfile|
82
+ lockfile["cookbook_locks"]["local-cookbook"]["source_options"] = { "path" => "foo/bar/cookbooks/local-cookbook" }
83
+ end
84
+ end
85
+
86
+ subject(:fetcher) { described_class.new("foo", source_options, storage_config) }
87
+
88
+ context "when the path is a file" do
89
+ context "and the file exists" do
90
+ let(:source_options) do
91
+ {
92
+ path: lock_file_path,
93
+ }
94
+ end
95
+
96
+ let(:source_options_for_lock) { source_options }
97
+
98
+ it "loads the policy from disk" do
99
+ expect(fetcher.lock_data).to eq(minimal_lockfile_modified)
100
+ end
101
+
102
+ it "returns source_options_for_lock" do
103
+ expect(fetcher.source_options).to eq(source_options)
104
+ end
105
+
106
+ it "applies can apply source options from the lock" do
107
+ fetcher.apply_locked_source_options(source_options_for_lock)
108
+ expect(fetcher.lock_data).to eq(minimal_lockfile_modified)
109
+ end
110
+ end
111
+
112
+ context "and the file does not exist" do
113
+ let(:source_options) do
114
+ {
115
+ path: Pathname.new(lock_file_path).dirname.join("dne.json.lock").to_s,
116
+ }
117
+ end
118
+
119
+ it "raises an error" do
120
+ expect { fetcher.lock_data }.to raise_error(ChefDK::LocalPolicyfileLockNotFound)
121
+ end
122
+ end
123
+ end
124
+
125
+ context "when the path is a directory" do
126
+ context "and the file exists" do
127
+ let(:source_options) do
128
+ {
129
+ path: Pathname.new(lock_file_path).dirname.to_s,
130
+ }
131
+ end
132
+
133
+ let(:source_options_for_lock) { source_options }
134
+
135
+ it "loads the policy from disk" do
136
+ expect(fetcher.lock_data).to eq(minimal_lockfile_modified)
137
+ end
138
+
139
+ it "returns source_options_for_lock" do
140
+ expect(fetcher.source_options).to eq(source_options)
141
+ end
142
+
143
+ it "applies can apply source options from the lock" do
144
+ fetcher.apply_locked_source_options(source_options_for_lock)
145
+ expect(fetcher.lock_data).to eq(minimal_lockfile_modified)
146
+ end
147
+ end
148
+
149
+ context "and the file does not exist" do
150
+ let(:source_options) do
151
+ {
152
+ path: Pathname.new(lock_file_path).dirname.parent.to_s,
153
+ }
154
+ end
155
+
156
+ it "raises an error" do
157
+ expect { fetcher.lock_data }.to raise_error(ChefDK::LocalPolicyfileLockNotFound, /provide the file name as part of the path/)
158
+ end
159
+ end
160
+ end
161
+ end