# # Copyright:: Copyright (c) 2014 Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "spec_helper" require "chef-dk/policyfile/differ" describe ChefDK::Policyfile::Differ do let(:old_lock_json) do <<-E { "revision_id": "cf4b8a020bdc1ba6914093a8a07a5514cce8a3a2979a967b1f32ea704a61785b", "name": "jenkins", "run_list": [ "recipe[java::default]", "recipe[jenkins::master]", "recipe[policyfile_demo::whatever]", "recipe[policyfile_demo::default]" ], "named_run_lists": { "update_jenkins": [ "recipe[jenkins::master]", "recipe[policyfile_demo::default]" ] }, "cookbook_locks": { "policyfile_demo": { "version": "0.1.0", "identifier": "ea96c99da079db9ff3cb22601638fabd5df49599", "dotted_decimal_identifier": "66030937227426267.45022575077627448.275691232073113", "source": "cookbooks/policyfile_demo", "cache_key": null, "scm_info": { "scm": "git", "remote": "git@github.com:danielsdeleo/policyfile-jenkins-demo.git", "revision": "6f92fe8f24fd953a1c40ebb1d7cdb2a4fbbf4d4d", "working_tree_clean": false, "published": true, "synchronized_remote_branches": [ "mine/master" ] }, "source_options": { "path": "cookbooks/policyfile_demo" } }, "apt": { "version": "2.7.0", "identifier": "16c57abbd056543f7d5a15dabbb03261024a9c5e", "dotted_decimal_identifier": "6409580415309396.17870749399956400.55392231660638", "cache_key": "apt-2.7.0-supermarket.chef.io", "origin": "https://supermarket.chef.io/api/v1/cookbooks/apt/versions/2.7.0/download", "source_options": { "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/apt/versions/2.7.0/download", "version": "2.7.0" } }, "java": { "version": "1.31.0", "identifier": "9178a38ad3e3baa55b49c1b8d9f4bf6a43dbc358", "dotted_decimal_identifier": "40946515427189690.46543743498115572.210463125914456", "cache_key": "java-1.31.0-supermarket.chef.io", "origin": "https://supermarket.chef.io/api/v1/cookbooks/java/versions/1.31.0/download", "source_options": { "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/java/versions/1.31.0/download", "version": "1.31.0" } }, "jenkins": { "version": "2.2.2", "identifier": "0be380429add00d189b4431059ac967a60052323", "dotted_decimal_identifier": "3346364756581632.58979677444790700.165452341125923", "cache_key": "jenkins-2.2.2-supermarket.chef.io", "origin": "https://supermarket.chef.io/api/v1/cookbooks/jenkins/versions/2.2.2/download", "source_options": { "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/jenkins/versions/2.2.2/download", "version": "2.2.2" } }, "runit": { "version": "1.5.18", "identifier": "1a0aeb2c167a24e0c5120ca7b06ba8c4cff4610c", "dotted_decimal_identifier": "7330354567739940.63267076095586411.185563255955724", "cache_key": "runit-1.5.18-supermarket.chef.io", "origin": "https://supermarket.chef.io/api/v1/cookbooks/runit/versions/1.5.18/download", "source_options": { "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/runit/versions/1.5.18/download", "version": "1.5.18" } }, "build-essential": { "version": "2.2.2", "identifier": "d8ce58401d154378599b0fead81d2c390615602b", "dotted_decimal_identifier": "61025473397593411.33875519727130653.48623426822187", "cache_key": "build-essential-2.2.2-supermarket.chef.io", "origin": "https://supermarket.chef.io/api/v1/cookbooks/build-essential/versions/2.2.2/download", "source_options": { "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/build-essential/versions/2.2.2/download", "version": "2.2.2" } }, "yum": { "version": "3.5.4", "identifier": "f9c778c3cd3908071e0c55722682f96e653b5642", "dotted_decimal_identifier": "70306590695962888.2003363158959746.274252540106306", "cache_key": "yum-3.5.4-supermarket.chef.io", "origin": "https://supermarket.chef.io/api/v1/cookbooks/yum/versions/3.5.4/download", "source_options": { "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/yum/versions/3.5.4/download", "version": "3.5.4" } }, "yum-epel": { "version": "0.6.0", "identifier": "cd74f541ba0341abcc168c74471c349ca68f77b7", "dotted_decimal_identifier": "57830966944203585.48356618235299612.57847413962679", "cache_key": "yum-epel-0.6.0-supermarket.chef.io", "origin": "https://supermarket.chef.io/api/v1/cookbooks/yum-epel/versions/0.6.0/download", "source_options": { "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/yum-epel/versions/0.6.0/download", "version": "0.6.0" } } }, "default_attributes": { "greeting": "Attributes, f*** yeah" }, "override_attributes": { "attr_only_updating": "use -a" }, "solution_dependencies": { "Policyfile": [ [ "policyfile_demo", ">= 0.0.0" ], [ "apt", "= 2.7.0" ], [ "java", "= 1.31.0" ], [ "jenkins", "= 2.2.2" ], [ "runit", "= 1.5.18" ], [ "build-essential", "= 2.2.2" ], [ "yum", "= 3.5.4" ], [ "yum-epel", "= 0.6.0" ] ], "dependencies": { "apt (2.7.0)": [ ], "java (1.31.0)": [ ], "jenkins (2.2.2)": [ [ "apt", "~> 2.0" ], [ "runit", "~> 1.5" ], [ "yum", "~> 3.0" ] ], "runit (1.5.18)": [ [ "build-essential", ">= 0.0.0" ], [ "yum", "~> 3.0" ], [ "yum-epel", ">= 0.0.0" ] ], "build-essential (2.2.2)": [ ], "yum (3.5.4)": [ ], "yum-epel (0.6.0)": [ [ "yum", "~> 3.0" ] ], "policyfile_demo (0.1.0)": [ ] } } } E end let(:old_lock) { FFI_Yajl::Parser.parse(old_lock_json) } let(:new_lock) { old_lock } let(:new_rev_id) { "304566f86a620aae85797a3c491a51fb8c6ecf996407e77b8063aa3ee59672c5" } let(:ui) { TestHelpers::TestUI.new } def output # ANSI codes make the tests harder to read Paint.unpaint(ui.output) end subject(:differ) do described_class.new(old_name: "git: HEAD", old_lock: old_lock, new_name: "local disk", new_lock: new_lock, ui: ui) end it "has a UI object" do expect(differ.ui).to eq(ui) end it "has the old lock data" do expect(differ.old_lock).to eq(old_lock) end it "has the old lock `name'" do expect(differ.old_name).to eq("git: HEAD") end it "has the new lock data" do expect(differ.new_lock).to eq(new_lock) end it "has the new lock `name'" do expect(differ.new_name).to eq("local disk") end context "when old and new lock data are the same" do let(:new_lock) { old_lock } it "has no updates" do expect(differ.different?).to be(false) end it "has no updated sections" do expect(differ.updated_sections).to be_empty end it "reports that there are no updates" do expected_message = <<-E No changes for policy lock 'jenkins' between 'git: HEAD' and 'local disk' E differ.run_report expect(output).to include(expected_message) end end context "when the run list is updated" do let(:new_lock) do n = old_lock.dup n["revision_id"] = new_rev_id n["run_list"] = old_lock["run_list"].dup n["run_list"].delete_at(2) n["run_list"] += %w{ recipe[one::one] recipe[two::two] recipe[three::three]} n end it "has updates" do expect(differ.different?).to be(true) end it "has an updated revision_id and run_list" do expect(differ.updated_sections).to match_array(%w{revision_id run_list}) end it "reports the updated rev_id and run_list" do expected_message = <<-E Policy lock 'jenkins' differs between 'git: HEAD' and 'local disk': REVISION ID CHANGED =================== @@ -1,2 +1,2 @@ -cf4b8a020bdc1ba6914093a8a07a5514cce8a3a2979a967b1f32ea704a61785b +304566f86a620aae85797a3c491a51fb8c6ecf996407e77b8063aa3ee59672c5 RUN LIST CHANGED ================ @@ -1,5 +1,7 @@ recipe[java::default] recipe[jenkins::master] -recipe[policyfile_demo::whatever] recipe[policyfile_demo::default] +recipe[one::one] +recipe[two::two] +recipe[three::three] E differ.run_report expect(output).to eq(expected_message) end # primary goal here is to make sure the differ behaves correctly when diff # "hunks" don't overlap context "when the run_list has non-contiguous changes" do let(:old_lock) do FFI_Yajl::Parser.parse(old_lock_json).tap do |l| l["run_list"] = %w{ a b c d e f g h i j k l m n }.map do |letter| "recipe[#{letter}::default]" end end end let(:new_lock) do old_lock.dup.tap do |n| n["revision_id"] = new_rev_id n["run_list"] = n["run_list"].dup n["run_list"].delete_at(1) n["run_list"] += %w{ o p q r }.map do |letter| "recipe[#{letter}::new]" end end end it "prints the correct changes with context for the run list" do expected_message = <<-E Policy lock 'jenkins' differs between 'git: HEAD' and 'local disk': REVISION ID CHANGED =================== @@ -1,2 +1,2 @@ -cf4b8a020bdc1ba6914093a8a07a5514cce8a3a2979a967b1f32ea704a61785b +304566f86a620aae85797a3c491a51fb8c6ecf996407e77b8063aa3ee59672c5 RUN LIST CHANGED ================ @@ -1,5 +1,4 @@ recipe[a::default] -recipe[b::default] recipe[c::default] recipe[d::default] recipe[e::default] @@ -12,4 +11,8 @@ recipe[l::default] recipe[m::default] recipe[n::default] +recipe[o::new] +recipe[p::new] +recipe[q::new] +recipe[r::new] E differ.run_report expect(output).to eq(expected_message) end end end context "with a removed cookbook" do let(:new_lock) do old_lock.dup.tap do |n| n["revision_id"] = new_rev_id n["cookbook_locks"] = n["cookbook_locks"].dup n["cookbook_locks"].delete("apt") end end it "has updates" do expect(differ.different?).to be(true) end it "has an updated revision_id and cookbook_locks" do expect(differ.updated_sections).to match_array(%w{revision_id cookbook_locks}) end it "has removed the 'apt' cookbook" do expect(differ.removed_cookbooks).to eq(%w{apt}) end it "reports the updated revision_id and removed cookbooks" do expected_message = <<-E Policy lock 'jenkins' differs between 'git: HEAD' and 'local disk': REVISION ID CHANGED =================== @@ -1,2 +1,2 @@ -cf4b8a020bdc1ba6914093a8a07a5514cce8a3a2979a967b1f32ea704a61785b +304566f86a620aae85797a3c491a51fb8c6ecf996407e77b8063aa3ee59672c5 REMOVED COOKBOOKS ================= apt --- @@ -1,12 +1 @@ -{ - "version": "2.7.0", - "identifier": "16c57abbd056543f7d5a15dabbb03261024a9c5e", - "dotted_decimal_identifier": "6409580415309396.17870749399956400.55392231660638", - "cache_key": "apt-2.7.0-supermarket.chef.io", - "origin": "https://supermarket.chef.io/api/v1/cookbooks/apt/versions/2.7.0/download", - "source_options": { - "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/apt/versions/2.7.0/download", - "version": "2.7.0" - } -} E differ.run_report expect(output).to eq(expected_message) end end context "with an added cookbook" do let(:new_cookbook) do { "version" => "2.3.2", "identifier" => "9c6990944d9a347dec8bd375e707ba0aecdc17cd", "dotted_decimal_identifier" => "69437059924760478.24393100994078142.115593340606828", "cache_key" => "bluepill-2.3.2-supermarket.chef.io", "origin" => "https://supermarket.chef.io/api/v1/cookbooks/bluepill/versions/2.3.2/download", "source_options" => { "artifactserver" => "https://supermarket.chef.io/api/v1/cookbooks/bluepill/versions/2.3.2/download", "version" => "2.3.2", }, } end let(:new_lock) do old_lock.dup.tap do |n| n["revision_id"] = new_rev_id n["cookbook_locks"] = n["cookbook_locks"].dup n["cookbook_locks"]["bluepill"] = new_cookbook end end it "has updates" do expect(differ.different?).to be(true) end it "has an updated revision_id and cookbook_locks" do expect(differ.updated_sections).to match_array(%w{revision_id cookbook_locks}) end it "has added the bluepill cookbook" do expect(differ.added_cookbooks).to eq(%w{ bluepill }) end it "reports the updated revision_id and added cookbook" do expected_message = <<-E Policy lock 'jenkins' differs between 'git: HEAD' and 'local disk': REVISION ID CHANGED =================== @@ -1,2 +1,2 @@ -cf4b8a020bdc1ba6914093a8a07a5514cce8a3a2979a967b1f32ea704a61785b +304566f86a620aae85797a3c491a51fb8c6ecf996407e77b8063aa3ee59672c5 ADDED COOKBOOKS =============== bluepill -------- @@ -1 +1,12 @@ +{ + "version": "2.3.2", + "identifier": "9c6990944d9a347dec8bd375e707ba0aecdc17cd", + "dotted_decimal_identifier": "69437059924760478.24393100994078142.115593340606828", + "cache_key": "bluepill-2.3.2-supermarket.chef.io", + "origin": "https://supermarket.chef.io/api/v1/cookbooks/bluepill/versions/2.3.2/download", + "source_options": { + "artifactserver": "https://supermarket.chef.io/api/v1/cookbooks/bluepill/versions/2.3.2/download", + "version": "2.3.2" + } +} E differ.run_report expect(output).to eq(expected_message) end end context "with a modified cookbook" do let(:new_lock) do old_lock.dup.tap do |n| n["revision_id"] = new_rev_id n["cookbook_locks"] = n["cookbook_locks"].dup policyfile_demo = n["cookbook_locks"]["policyfile_demo"].dup policyfile_demo["identifier"] = "f04cc40faf628253fe7d9566d66a1733fb1afbe9" policyfile_demo["dotted_decimal_identifier"] = "67638399371010690.23642238397896298.25512023620585" n["cookbook_locks"]["policyfile_demo"] = policyfile_demo end end it "has updates" do expect(differ.different?).to be(true) end it "has an updated revision_id and cookbook_locks" do expect(differ.updated_sections).to match_array(%w{revision_id cookbook_locks}) end it "has modified the 'policyfile_demo' cookbook" do expect(differ.modified_cookbooks).to eq(%w{policyfile_demo}) end it "reports the updated revision_id and modified policyfile_demo cookbook" do expected_message = <<-E Policy lock 'jenkins' differs between 'git: HEAD' and 'local disk': REVISION ID CHANGED =================== @@ -1,2 +1,2 @@ -cf4b8a020bdc1ba6914093a8a07a5514cce8a3a2979a967b1f32ea704a61785b +304566f86a620aae85797a3c491a51fb8c6ecf996407e77b8063aa3ee59672c5 MODIFIED COOKBOOKS ================== policyfile_demo --------------- @@ -1,7 +1,7 @@ { "version": "0.1.0", - "identifier": "ea96c99da079db9ff3cb22601638fabd5df49599", - "dotted_decimal_identifier": "66030937227426267.45022575077627448.275691232073113", + "identifier": "f04cc40faf628253fe7d9566d66a1733fb1afbe9", + "dotted_decimal_identifier": "67638399371010690.23642238397896298.25512023620585", "source": "cookbooks/policyfile_demo", "cache_key": null, "scm_info": { E differ.run_report expect(output).to eq(expected_message) end end context "with updated default attributes" do let(:new_lock) do old_lock.dup.tap do |n| n["revision_id"] = new_rev_id n["default_attributes"] = n["default_attributes"].dup n["default_attributes"]["new_attr"] = "hello" end end it "has updates" do expect(differ.different?).to be(true) end it "has an updated revision_id and default_attributes" do expect(differ.updated_sections).to match_array(%w{revision_id default_attributes}) end it "reports the updated revision_id and modified attributes" do expected_output = <<-E Policy lock 'jenkins' differs between 'git: HEAD' and 'local disk': REVISION ID CHANGED =================== @@ -1,2 +1,2 @@ -cf4b8a020bdc1ba6914093a8a07a5514cce8a3a2979a967b1f32ea704a61785b +304566f86a620aae85797a3c491a51fb8c6ecf996407e77b8063aa3ee59672c5 DEFAULT ATTRIBUTES CHANGED ========================== @@ -1,4 +1,5 @@ { - "greeting": "Attributes, f*** yeah" + "greeting": "Attributes, f*** yeah", + "new_attr": "hello" } E differ.run_report expect(output).to eq(expected_output) end end context "with updated override_attributes" do let(:new_lock) do old_lock.dup.tap do |n| n["revision_id"] = new_rev_id n["override_attributes"] = n["override_attributes"].dup n["override_attributes"]["new_attr"] = "ALL THE DIFF" end end it "has updates" do expect(differ.different?).to be(true) end it "has an updated revision_id and override_attributes" do expect(differ.updated_sections).to match_array(%w{revision_id override_attributes}) end it "reports the updated revision_id and override_attributes" do expected_output = <<-E Policy lock 'jenkins' differs between 'git: HEAD' and 'local disk': REVISION ID CHANGED =================== @@ -1,2 +1,2 @@ -cf4b8a020bdc1ba6914093a8a07a5514cce8a3a2979a967b1f32ea704a61785b +304566f86a620aae85797a3c491a51fb8c6ecf996407e77b8063aa3ee59672c5 OVERRIDE ATTRIBUTES CHANGED =========================== @@ -1,4 +1,5 @@ { - "attr_only_updating": "use -a" + "attr_only_updating": "use -a", + "new_attr": "ALL THE DIFF" } E differ.run_report expect(output).to eq(expected_output) end end end