chef-dk 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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