chef-dk 0.6.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -0
  3. data/lib/chef-dk/builtin_commands.rb +7 -0
  4. data/lib/chef-dk/command/env.rb +90 -0
  5. data/lib/chef-dk/command/export.rb +22 -1
  6. data/lib/chef-dk/command/generate.rb +1 -1
  7. data/lib/chef-dk/command/provision.rb +43 -0
  8. data/lib/chef-dk/command/push_archive.rb +126 -0
  9. data/lib/chef-dk/command/show_policy.rb +166 -0
  10. data/lib/chef-dk/command/verify.rb +58 -1
  11. data/lib/chef-dk/cookbook_omnifetch.rb +3 -2
  12. data/lib/chef-dk/exceptions.rb +27 -0
  13. data/lib/chef-dk/helpers.rb +29 -0
  14. data/lib/chef-dk/policyfile/chef_repo_cookbook_source.rb +8 -0
  15. data/lib/chef-dk/policyfile/chef_server_cookbook_source.rb +8 -0
  16. data/lib/chef-dk/policyfile/community_cookbook_source.rb +8 -0
  17. data/lib/chef-dk/policyfile/cookbook_locks.rb +76 -6
  18. data/lib/chef-dk/policyfile/dsl.rb +10 -5
  19. data/lib/chef-dk/policyfile/lister.rb +230 -0
  20. data/lib/chef-dk/policyfile/null_cookbook_source.rb +8 -0
  21. data/lib/chef-dk/policyfile_compiler.rb +35 -2
  22. data/lib/chef-dk/policyfile_lock.rb +43 -0
  23. data/lib/chef-dk/policyfile_services/clean_policies.rb +94 -0
  24. data/lib/chef-dk/policyfile_services/export_repo.rb +103 -16
  25. data/lib/chef-dk/policyfile_services/push_archive.rb +173 -0
  26. data/lib/chef-dk/policyfile_services/show_policy.rb +237 -0
  27. data/lib/chef-dk/service_exceptions.rb +21 -0
  28. data/lib/chef-dk/skeletons/code_generator/files/default/chefignore +1 -0
  29. data/lib/chef-dk/skeletons/code_generator/files/default/repo/README.md +2 -40
  30. data/lib/chef-dk/skeletons/code_generator/recipes/app.rb +0 -2
  31. data/lib/chef-dk/skeletons/code_generator/templates/default/kitchen.yml.erb +2 -2
  32. data/lib/chef-dk/skeletons/code_generator/templates/default/recipe_spec.rb.erb +1 -1
  33. data/lib/chef-dk/version.rb +1 -1
  34. data/spec/unit/command/env_spec.rb +52 -0
  35. data/spec/unit/command/exec_spec.rb +2 -2
  36. data/spec/unit/command/export_spec.rb +13 -0
  37. data/spec/unit/command/provision_spec.rb +56 -0
  38. data/spec/unit/command/push_archive_spec.rb +153 -0
  39. data/spec/unit/command/show_policy_spec.rb +235 -0
  40. data/spec/unit/command/verify_spec.rb +1 -0
  41. data/spec/unit/helpers_spec.rb +68 -0
  42. data/spec/unit/policyfile/cookbook_locks_spec.rb +107 -1
  43. data/spec/unit/policyfile/lister_spec.rb +256 -0
  44. data/spec/unit/policyfile_demands_spec.rb +202 -10
  45. data/spec/unit/policyfile_evaluation_spec.rb +30 -4
  46. data/spec/unit/policyfile_lock_serialization_spec.rb +45 -0
  47. data/spec/unit/policyfile_services/clean_policies_spec.rb +236 -0
  48. data/spec/unit/policyfile_services/export_repo_spec.rb +99 -6
  49. data/spec/unit/policyfile_services/push_archive_spec.rb +345 -0
  50. data/spec/unit/policyfile_services/show_policy_spec.rb +839 -0
  51. metadata +139 -8
@@ -32,7 +32,7 @@ describe ChefDK::PolicyfileCompiler, "when expressing the Policyfile graph deman
32
32
  p.default_source(*default_source) if default_source
33
33
  p.run_list(*run_list)
34
34
 
35
- allow(p.default_source).to receive(:universe_graph).and_return(external_cookbook_universe)
35
+ allow(p.default_source.first).to receive(:universe_graph).and_return(external_cookbook_universe)
36
36
  end
37
37
 
38
38
  policyfile
@@ -71,6 +71,11 @@ describe ChefDK::PolicyfileCompiler, "when expressing the Policyfile graph deman
71
71
  "1.1.1" => [ ]
72
72
  },
73
73
 
74
+ "remote-cb-two" => {
75
+ "0.1.0" => [ ],
76
+ "1.1.1" => [ ]
77
+ },
78
+
74
79
  "local-cookbook-dep-one" => {
75
80
  "1.5.0" => [ ]
76
81
  },
@@ -207,6 +212,26 @@ describe ChefDK::PolicyfileCompiler, "when expressing the Policyfile graph deman
207
212
 
208
213
  let(:run_list) { ["remote-cb"] }
209
214
 
215
+ context "with no default source" do
216
+
217
+ it "fails to locate the cookbook" do
218
+ expect { policyfile.graph_solution }.to raise_error(Solve::Errors::NoSolutionError)
219
+ end
220
+
221
+ context "when the policyfile also has a `cookbook` entry for the run list item" do
222
+
223
+ before do
224
+ policyfile.dsl.cookbook "remote-cb"
225
+ end
226
+
227
+ it "fails to locate the cookbook" do
228
+ expect { policyfile.graph_solution }.to raise_error(Solve::Errors::NoSolutionError)
229
+ end
230
+
231
+ end
232
+
233
+ end
234
+
210
235
  context "And the default source is the community site" do
211
236
 
212
237
  include_context "community default source"
@@ -632,12 +657,6 @@ describe ChefDK::PolicyfileCompiler, "when expressing the Policyfile graph deman
632
657
  expect(policyfile.graph_solution).to eq({"local-cookbook" => "2.3.4", "remote-cb" => "0.1.0"})
633
658
  end
634
659
 
635
- it "builds a policyfile lock from the constraints" do
636
- skip
637
- expect(policyfile).to receive(:cache_path).and_return(Pathname.new("~/.nopenope/cache"))
638
- expect(policyfile.lock).to eq(:wat)
639
- end
640
-
641
660
  it "includes the policyfile constraint in the solution dependencies" do
642
661
  expected_solution_deps = {
643
662
  "Policyfile" => [ [ "remote-cb", "~> 0.1" ], [ "local-cookbook", ">= 0.0.0"] ],
@@ -675,10 +694,183 @@ describe ChefDK::PolicyfileCompiler, "when expressing the Policyfile graph deman
675
694
 
676
695
  end
677
696
 
678
- context "Given a run_list with roles" do
679
- it "expands the roles from the given role source" do
680
- skip
697
+ context "when using multiple default sources" do
698
+
699
+ include_context "community default source"
700
+
701
+ let(:run_list) { [ 'repo-cookbook-one', 'remote-cb', 'remote-cb-two' ] }
702
+
703
+ before do
704
+ policyfile.default_source(:chef_repo, "path/to/repo")
705
+ allow(policyfile.default_source.last).to receive(:universe_graph).and_return(repo_cookbook_universe)
681
706
  end
707
+
708
+ context "when the graphs don't conflict" do
709
+
710
+ before do
711
+ # This is on the community site
712
+ policyfile.dsl.cookbook("remote-cb")
713
+ end
714
+
715
+ let(:repo_cookbook_universe) do
716
+ {
717
+ "repo-cookbook-one" => {
718
+ "1.0.0" => [ ]
719
+ },
720
+
721
+ "repo-cookbook-two" => {
722
+ "9.9.9" => [ ["repo-cookbook-on-community-dep", "= 1.0.0"] ]
723
+ },
724
+
725
+ "private-cookbook" => {
726
+ "0.1.0" => [ ]
727
+ }
728
+ }
729
+ end
730
+
731
+ it "merges the graphs" do
732
+ merged = policyfile.remote_artifacts_graph
733
+ expected = external_cookbook_universe.merge(repo_cookbook_universe)
734
+
735
+ expect(merged).to eq(expected)
736
+ end
737
+
738
+ it "solves the graph demands using cookbooks from both sources" do
739
+ expected = {"repo-cookbook-one" => "1.0.0", "remote-cb" => "1.1.1", "remote-cb-two" => "1.1.1"}
740
+ expect(policyfile.graph_solution).to eq(expected)
741
+ end
742
+
743
+ it "finds the location of a cookbook declared via explicit `cookbook` with no source options" do
744
+ community_source = policyfile.default_source.first
745
+
746
+ expected_source_options = { artifactserver: "https://chef.example/url", version: "1.1.1" }
747
+
748
+ expect(community_source).to be_a(ChefDK::Policyfile::CommunityCookbookSource)
749
+ expect(community_source).to receive(:source_options_for).
750
+ with("remote-cb", "1.1.1").
751
+ and_return(expected_source_options)
752
+
753
+ location_spec = policyfile.create_spec_for_cookbook("remote-cb", "1.1.1")
754
+ expect(location_spec.source_options).to eq(expected_source_options)
755
+ end
756
+
757
+ it "sources cookbooks from the correct source when the cookbook doesn't have a `cookbook` entry" do
758
+ # these don't have `cookbook` entries in the Policyfile.rb, so they are nil
759
+ expect(policyfile.cookbook_location_spec_for("repo-cookbook-one")).to be_nil
760
+ expect(policyfile.cookbook_location_spec_for("remote-cb-two")).to be_nil
761
+
762
+ # We have to stub #source_options_for or else we'd need to stub the
763
+ # source options data inside the source object. That's getting a bit
764
+ # too deep into the source object's internals.
765
+
766
+ expected_repo_options = { path: "path/to/cookbook", version: "1.0.0" }
767
+ repo_source = policyfile.default_source.last
768
+ expect(repo_source).to be_a(ChefDK::Policyfile::ChefRepoCookbookSource)
769
+ expect(repo_source).to receive(:source_options_for).
770
+ with("repo-cookbook-one", "1.0.0").
771
+ and_return(expected_repo_options)
772
+
773
+
774
+ repo_cb_location = policyfile.create_spec_for_cookbook("repo-cookbook-one", "1.0.0")
775
+ expect(repo_cb_location.source_options).to eq(expected_repo_options)
776
+
777
+ expected_server_options = { artifactserver: "https://chef.example/url", version: "1.1.1" }
778
+ community_source = policyfile.default_source.first
779
+ expect(community_source).to be_a(ChefDK::Policyfile::CommunityCookbookSource)
780
+ expect(community_source).to receive(:source_options_for).
781
+ with("remote-cb-two", "1.1.1").
782
+ and_return(expected_server_options)
783
+
784
+ remote_cb_location = policyfile.create_spec_for_cookbook("remote-cb-two", "1.1.1")
785
+ expect(remote_cb_location.source_options).to eq(expected_server_options)
786
+ end
787
+
788
+ end
789
+
790
+ context "when the graphs conflict" do
791
+
792
+ let(:repo_cookbook_universe) do
793
+ {
794
+ "repo-cookbook-one" => {
795
+ "1.0.0" => [ ]
796
+ },
797
+
798
+ "repo-cookbook-two" => {
799
+ "9.9.9" => [ ["repo-cookbook-on-community-dep", "= 1.0.0"] ]
800
+ },
801
+
802
+ "private-cookbook" => {
803
+ "0.1.0" => [ ]
804
+ },
805
+
806
+ # NOTE: cookbooks are considered to conflict when both sources have
807
+ # cookbooks with the same name, regardless of whether any version
808
+ # numbers overlap.
809
+ #
810
+ # The before block does the equivalent to putting this in the
811
+ # Policyfile.rb:
812
+ #
813
+ # cookbook "remote-cb"
814
+ #
815
+ # This makes the compiler take a slightly different code path than if
816
+ # the cookbook was just in the dep graphs.
817
+ "remote-cb" => {
818
+ "99.99.99" => [ ]
819
+ },
820
+
821
+ # This also conflicts, but only via the graphs
822
+ "remote-cb-two" => {
823
+ "1.2.3" => [ ]
824
+ }
825
+
826
+ }
827
+ end
828
+
829
+ context "and no explicit source is given for the conflicting cookbook" do
830
+
831
+ before do
832
+ # This is on the community site
833
+ policyfile.dsl.cookbook("remote-cb")
834
+ end
835
+
836
+ it "raises an error describing the conflict" do
837
+ expected_err = <<-ERROR
838
+ Source supermarket(https://supermarket.chef.io) and chef_repo(path/to/repo) contain conflicting cookbooks:
839
+ - remote-cb
840
+ - remote-cb-two
841
+ ERROR
842
+
843
+ expect { policyfile.remote_artifacts_graph }.to raise_error do |error|
844
+ expect(error).to be_a(ChefDK::CookbookSourceConflict)
845
+ expect(error.message).to eq(expected_err)
846
+ end
847
+ end
848
+ end
849
+
850
+ context "and the conflicting cookbook has an explicit source in the Policyfile" do
851
+
852
+ before do
853
+ # This is on the community site
854
+ policyfile.dsl.cookbook("remote-cb", path: "path/to/remote-cb")
855
+ policyfile.dsl.cookbook("remote-cb-two", git: "git://git.example:user/remote-cb-two.git")
856
+ policyfile.error!
857
+ end
858
+
859
+ it "solves the graph" do
860
+ expect { policyfile.remote_artifacts_graph }.to_not raise_error
861
+ end
862
+
863
+ it "assigns the correct source options to the cookbook" do
864
+ remote_cb_source_opts = policyfile.cookbook_location_spec_for("remote-cb").source_options
865
+ expect(remote_cb_source_opts).to eq(path: "path/to/remote-cb")
866
+
867
+ remote_cb_two_source_opts = policyfile.cookbook_location_spec_for("remote-cb-two").source_options
868
+ expect(remote_cb_two_source_opts).to eq(git: "git://git.example:user/remote-cb-two.git")
869
+ end
870
+ end
871
+
872
+ end
873
+
682
874
  end
683
875
 
684
876
  end
@@ -171,7 +171,9 @@ E
171
171
  end
172
172
 
173
173
  it "has no default cookbook source" do
174
- expect(policyfile.default_source).to be_a(ChefDK::Policyfile::NullCookbookSource)
174
+ expect(policyfile.default_source).to be_an(Array)
175
+ expect(policyfile.default_source.size).to eq(1)
176
+ expect(policyfile.default_source.first).to be_a(ChefDK::Policyfile::NullCookbookSource)
175
177
  end
176
178
 
177
179
  context "with the default source set to the community site" do
@@ -185,7 +187,7 @@ E
185
187
 
186
188
  it "has a default source" do
187
189
  expect(policyfile.errors).to eq([])
188
- expected = ChefDK::Policyfile::CommunityCookbookSource.new("https://supermarket.chef.io")
190
+ expected = [ ChefDK::Policyfile::CommunityCookbookSource.new("https://supermarket.chef.io") ]
189
191
  expect(policyfile.default_source).to eq(expected)
190
192
  end
191
193
 
@@ -200,7 +202,7 @@ E
200
202
 
201
203
  it "has a default source" do
202
204
  expect(policyfile.errors).to eq([])
203
- expected = ChefDK::Policyfile::CommunityCookbookSource.new("https://cookbook-api.example.com")
205
+ expected = [ ChefDK::Policyfile::CommunityCookbookSource.new("https://cookbook-api.example.com") ]
204
206
  expect(policyfile.default_source).to eq(expected)
205
207
  end
206
208
 
@@ -256,7 +258,31 @@ E
256
258
 
257
259
  it "has a default source" do
258
260
  expect(policyfile.errors).to eq([])
259
- expected = ChefDK::Policyfile::ChefRepoCookbookSource.new(chef_repo)
261
+ expected = [ ChefDK::Policyfile::ChefRepoCookbookSource.new(chef_repo) ]
262
+ expect(policyfile.default_source).to eq(expected)
263
+ end
264
+
265
+ end
266
+
267
+ context "with multiple default sources" do
268
+ let(:chef_repo) { File.expand_path("spec/unit/fixtures/local_path_cookbooks", project_root) }
269
+
270
+ let(:policyfile_rb) do
271
+ <<-EOH
272
+ run_list "foo", "bar"
273
+
274
+ default_source :community
275
+ default_source :chef_repo, "#{chef_repo}"
276
+ EOH
277
+ end
278
+
279
+ it "has an array of sources" do
280
+ expect(policyfile.errors).to eq([])
281
+
282
+ community_source = ChefDK::Policyfile::CommunityCookbookSource.new("https://supermarket.chef.io")
283
+ repo_source = ChefDK::Policyfile::ChefRepoCookbookSource.new(chef_repo)
284
+ expected = [ community_source, repo_source ]
285
+
260
286
  expect(policyfile.default_source).to eq(expected)
261
287
  end
262
288
 
@@ -336,4 +336,49 @@ describe ChefDK::PolicyfileLock, "when reading a Policyfile.lock" do
336
336
  end
337
337
  end
338
338
 
339
+ describe "populating lock data from an archive" do
340
+
341
+ let(:valid_cookbook_lock) do
342
+ {
343
+ "version" => "1.0.0",
344
+ "identifier" => "68c13b136a49b4e66cfe9d8aa2b5a85167b5bf9b",
345
+ "dotted_decimal_identifier" => "111.222.333",
346
+ "cache_key" => nil,
347
+ "source" => "path/to/foo",
348
+ "source_options" => { path: "path/to/foo"},
349
+ "scm_info" => nil
350
+ }
351
+ end
352
+
353
+ let(:lock_data) do
354
+ valid_lock_with_cached_cookbook = valid_lock_data.dup
355
+ valid_cached_cookbook = valid_cookbook_lock.dup
356
+ valid_cached_cookbook["cache_key"] = nil
357
+ valid_cached_cookbook["source"] = "path/to/foo"
358
+ valid_lock_with_cached_cookbook["cookbook_locks"] = { "foo" => valid_cached_cookbook }
359
+ valid_lock_with_cached_cookbook
360
+ end
361
+
362
+ before do
363
+ lockfile.build_from_archive(lock_data)
364
+ end
365
+
366
+ it "creates cookbook locks as archived cookbooks" do
367
+ locks = lockfile.cookbook_locks
368
+
369
+ expect(locks).to have_key("foo")
370
+
371
+ cb_foo = locks["foo"]
372
+ expect(cb_foo).to be_a(ChefDK::Policyfile::ArchivedCookbook)
373
+
374
+ expected_path = File.join(storage_config.relative_paths_root, "cookbooks", "foo-111.222.333")
375
+
376
+ expect(cb_foo.cookbook_path).to eq(expected_path)
377
+ expect(cb_foo.dotted_decimal_identifier).to eq("111.222.333")
378
+ expect(locks["foo"].to_lock).to eq(valid_cookbook_lock)
379
+ end
380
+
381
+
382
+ end
383
+
339
384
  end
@@ -0,0 +1,236 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2015 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_services/clean_policies'
20
+
21
+ describe ChefDK::PolicyfileServices::CleanPolicies do
22
+
23
+ let(:chef_config) { double("Chef::Config") }
24
+
25
+ let(:policy_lister) do
26
+ clean_policies_service.policy_lister
27
+ end
28
+
29
+ let(:policies_by_name) { {} }
30
+ let(:policies_by_group) { {} }
31
+
32
+ let(:ui) { TestHelpers::TestUI.new }
33
+
34
+ subject(:clean_policies_service) do
35
+ described_class.new(config: chef_config, ui: ui)
36
+ end
37
+
38
+ describe "when there is an error listing data from the server" do
39
+
40
+ let(:http_client) { instance_double(ChefDK::AuthenticatedHTTP) }
41
+
42
+ let(:response) do
43
+ Net::HTTPResponse.send(:response_class, "500").new("1.0", "500", "Internal Server Error").tap do |r|
44
+ r.instance_variable_set(:@body, "oops")
45
+ end
46
+ end
47
+
48
+ let(:http_exception) do
49
+ begin
50
+ response.error!
51
+ rescue => e
52
+ e
53
+ end
54
+ end
55
+
56
+ before do
57
+ expect(policy_lister).to receive(:http_client).and_return(http_client)
58
+ expect(http_client).to receive(:get).and_raise(http_exception)
59
+ end
60
+
61
+ it "raises an error" do
62
+ expect { clean_policies_service.run }.to raise_error(ChefDK::PolicyfileCleanError)
63
+ end
64
+
65
+ end
66
+
67
+ context "when existing policies are listed successfully" do
68
+
69
+ let(:http_client) { instance_double(ChefDK::AuthenticatedHTTP) }
70
+
71
+ before do
72
+ policy_lister.set!(policies_by_name, policies_by_group)
73
+ end
74
+
75
+ describe "cleaning unused policy revisions" do
76
+
77
+ before do
78
+ allow(clean_policies_service).to receive(:http_client).and_return(http_client)
79
+ end
80
+
81
+ context "when there are no policies" do
82
+
83
+ before do
84
+ expect(http_client).to_not receive(:delete)
85
+ end
86
+
87
+ it "doesn't delete anything" do
88
+ clean_policies_service.run
89
+ expect(ui.output).to eq("No policy revisions deleted\n")
90
+ end
91
+
92
+ end
93
+
94
+ context "when there are policies but none are orphans" do
95
+
96
+ let(:policies_by_name) do
97
+ {
98
+ "appserver" => {
99
+ "1111111111111111111111111111111111111111111111111111111111111111" => {},
100
+ "2222222222222222222222222222222222222222222222222222222222222222" => {}
101
+ },
102
+ "load-balancer" => {
103
+ "5555555555555555555555555555555555555555555555555555555555555555" => {},
104
+ "6666666666666666666666666666666666666666666666666666666666666666" => {}
105
+ }
106
+ }
107
+ end
108
+
109
+ let(:policies_by_group) do
110
+ {
111
+ "dev" => {
112
+ "appserver" => "1111111111111111111111111111111111111111111111111111111111111111",
113
+ "load-balancer" => "5555555555555555555555555555555555555555555555555555555555555555"
114
+ },
115
+ "staging" => {
116
+ "appserver" => "2222222222222222222222222222222222222222222222222222222222222222",
117
+ "load-balancer" => "5555555555555555555555555555555555555555555555555555555555555555"
118
+ },
119
+ "prod" => {
120
+ "appserver" => "2222222222222222222222222222222222222222222222222222222222222222",
121
+ "load-balancer" => "6666666666666666666666666666666666666666666666666666666666666666"
122
+ }
123
+ }
124
+ end
125
+
126
+ before do
127
+ expect(http_client).to_not receive(:delete)
128
+ end
129
+
130
+ it "doesn't delete anything" do
131
+ clean_policies_service.run
132
+ expect(ui.output).to eq("No policy revisions deleted\n")
133
+ end
134
+
135
+ end
136
+
137
+ context "when there are policies and some are orphans" do
138
+
139
+ let(:policies_by_name) do
140
+ {
141
+ "appserver" => {
142
+ "1111111111111111111111111111111111111111111111111111111111111111" => {},
143
+ "2222222222222222222222222222222222222222222222222222222222222222" => {},
144
+ "4444444444444444444444444444444444444444444444444444444444444444" => {}
145
+ },
146
+ "load-balancer" => {
147
+ "5555555555555555555555555555555555555555555555555555555555555555" => {},
148
+ "6666666666666666666666666666666666666666666666666666666666666666" => {},
149
+ "7777777777777777777777777777777777777777777777777777777777777777" => {}
150
+ }
151
+ }
152
+ end
153
+
154
+ let(:policies_by_group) do
155
+ {
156
+ "dev" => {
157
+ "appserver" => "1111111111111111111111111111111111111111111111111111111111111111",
158
+ "load-balancer" => "5555555555555555555555555555555555555555555555555555555555555555"
159
+ },
160
+ "staging" => {
161
+ "appserver" => "2222222222222222222222222222222222222222222222222222222222222222",
162
+ "load-balancer" => "5555555555555555555555555555555555555555555555555555555555555555"
163
+ },
164
+ "prod" => {
165
+ "appserver" => "2222222222222222222222222222222222222222222222222222222222222222",
166
+ "load-balancer" => "6666666666666666666666666666666666666666666666666666666666666666"
167
+ }
168
+ }
169
+ end
170
+
171
+ describe "and all deletes are successful" do
172
+
173
+ before do
174
+ expect(http_client).to receive(:delete).with("/policies/appserver/revisions/4444444444444444444444444444444444444444444444444444444444444444")
175
+ expect(http_client).to receive(:delete).with("/policies/load-balancer/revisions/7777777777777777777777777777777777777777777777777777777777777777")
176
+ end
177
+
178
+ it "deletes the orphaned policies" do
179
+ clean_policies_service.run
180
+ expected_message = <<-MESSAGE
181
+ DELETE appserver 4444444444444444444444444444444444444444444444444444444444444444
182
+ DELETE load-balancer 7777777777777777777777777777777777777777777777777777777777777777
183
+ MESSAGE
184
+ expect(ui.output).to eq(expected_message)
185
+ end
186
+
187
+ end
188
+
189
+ # For example, a user doesn't have permission on all policy_names
190
+ describe "when some deletes fail" do
191
+
192
+ let(:response) do
193
+ Net::HTTPResponse.send(:response_class, "403").new("1.0", "403", "Unauthorized").tap do |r|
194
+ r.instance_variable_set(:@body, "I can't let you do that Dave")
195
+ end
196
+ end
197
+
198
+ let(:http_exception) do
199
+ begin
200
+ response.error!
201
+ rescue => e
202
+ e
203
+ end
204
+ end
205
+
206
+ before do
207
+ expect(http_client).to receive(:delete).
208
+ with("/policies/appserver/revisions/4444444444444444444444444444444444444444444444444444444444444444").
209
+ and_raise(http_exception)
210
+ expect(http_client).to receive(:delete).with("/policies/load-balancer/revisions/7777777777777777777777777777777777777777777777777777777777777777")
211
+ end
212
+
213
+ it "deletes what it can, then raises an error" do
214
+ expected_message = <<-ERROR
215
+ Failed to delete some policy revisions:
216
+ - appserver (4444444444444444444444444444444444444444444444444444444444444444): Net::HTTPServerException 403 \"Unauthorized\"
217
+ ERROR
218
+
219
+ expect { clean_policies_service.run }.to raise_error do |error|
220
+ expect(error.message).to eq(expected_message)
221
+ end
222
+ expected_message = <<-MESSAGE
223
+ DELETE appserver 4444444444444444444444444444444444444444444444444444444444444444
224
+ DELETE load-balancer 7777777777777777777777777777777777777777777777777777777777777777
225
+ MESSAGE
226
+ expect(ui.output).to eq(expected_message)
227
+ end
228
+
229
+ end
230
+
231
+ end
232
+ end
233
+
234
+ end
235
+
236
+ end