chef-dk 2.3.4 → 2.4.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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