chef-dk 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +2 -2
- data/README.md +25 -0
- data/lib/chef-dk/builtin_commands.rb +4 -0
- data/lib/chef-dk/cli.rb +46 -0
- data/lib/chef-dk/command/base.rb +4 -0
- data/lib/chef-dk/command/generator_commands/template.rb +2 -1
- data/lib/chef-dk/command/install.rb +105 -0
- data/lib/chef-dk/command/push.rb +123 -0
- data/lib/chef-dk/cookbook_profiler/identifiers.rb +5 -0
- data/lib/chef-dk/exceptions.rb +38 -0
- data/lib/chef-dk/generator.rb +16 -1
- data/lib/chef-dk/helpers.rb +1 -1
- data/lib/chef-dk/policyfile/cookbook_location_specification.rb +4 -0
- data/lib/chef-dk/policyfile/cookbook_locks.rb +73 -0
- data/lib/chef-dk/policyfile/reports/install.rb +70 -0
- data/lib/chef-dk/policyfile/reports/table_printer.rb +58 -0
- data/lib/chef-dk/policyfile/reports/upload.rb +70 -0
- data/lib/chef-dk/policyfile/solution_dependencies.rb +102 -8
- data/lib/chef-dk/policyfile/uploader.rb +37 -6
- data/lib/chef-dk/policyfile_compiler.rb +19 -5
- data/lib/chef-dk/policyfile_lock.rb +122 -9
- data/lib/chef-dk/policyfile_services/install.rb +131 -0
- data/lib/chef-dk/policyfile_services/push.rb +121 -0
- data/lib/chef-dk/skeletons/code_generator/recipes/cookbook.rb +6 -4
- data/lib/chef-dk/ui.rb +50 -0
- data/lib/chef-dk/version.rb +1 -1
- data/spec/shared/a_file_generator.rb +4 -1
- data/spec/test_helpers.rb +21 -0
- data/spec/unit/cli_spec.rb +100 -1
- data/spec/unit/command/base_spec.rb +23 -0
- data/spec/unit/command/exec_spec.rb +2 -2
- data/spec/unit/command/install_spec.rb +159 -0
- data/spec/unit/command/push_spec.rb +203 -0
- data/spec/unit/command/shell_init_spec.rb +1 -1
- data/spec/unit/policyfile/cookbook_location_specification_spec.rb +7 -0
- data/spec/unit/policyfile/cookbook_locks_spec.rb +13 -2
- data/spec/unit/policyfile/reports/install_spec.rb +115 -0
- data/spec/unit/policyfile/reports/upload_spec.rb +96 -0
- data/spec/unit/policyfile/solution_dependencies_spec.rb +1 -1
- data/spec/unit/policyfile/uploader_spec.rb +9 -12
- data/spec/unit/policyfile_lock_serialization_spec.rb +292 -0
- data/spec/unit/policyfile_services/install_spec.rb +170 -0
- data/spec/unit/policyfile_services/push_spec.rb +202 -0
- metadata +48 -6
| @@ -20,7 +20,7 @@ require 'chef-dk/policyfile/solution_dependencies' | |
| 20 20 |  | 
| 21 21 | 
             
            describe ChefDK::Policyfile::SolutionDependencies do
         | 
| 22 22 |  | 
| 23 | 
            -
              let(:dependency_data) { {} }
         | 
| 23 | 
            +
              let(:dependency_data) { {"Policyfile" => [], "dependencies" => {}} }
         | 
| 24 24 |  | 
| 25 25 | 
             
              let(:solution_dependencies) do
         | 
| 26 26 | 
             
                s = described_class.new
         | 
| @@ -79,17 +79,6 @@ describe ChefDK::Policyfile::Uploader do | |
| 79 79 | 
             
                expect(uploader.http_client).to eq(http_client)
         | 
| 80 80 | 
             
              end
         | 
| 81 81 |  | 
| 82 | 
            -
              context "when created without an HTTP client" do
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                let(:http_client) { nil }
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                it "creates an HTTP client with default config" do
         | 
| 87 | 
            -
                  skip "TODO: determine correct behavior"
         | 
| 88 | 
            -
                end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
              end
         | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 82 | 
             
              describe "creating uploading documents in compat mode" do
         | 
| 94 83 |  | 
| 95 84 | 
             
                let(:cookbook_locks) { {} }
         | 
| @@ -132,6 +121,8 @@ describe ChefDK::Policyfile::Uploader do | |
| 132 121 |  | 
| 133 122 | 
             
                  lock = instance_double("ChefDK::Policyfile::CookbookLock",
         | 
| 134 123 | 
             
                                         name: name,
         | 
| 124 | 
            +
                                         version: "1.0.0",
         | 
| 125 | 
            +
                                         identifier: "64b3e64306cff223206348e46af545b19032b170",
         | 
| 135 126 | 
             
                                         dotted_decimal_identifier: dotted_decimal_id,
         | 
| 136 127 | 
             
                                         cookbook_path: cache_path)
         | 
| 137 128 |  | 
| @@ -212,7 +203,13 @@ describe ChefDK::Policyfile::Uploader do | |
| 212 203 | 
             
                  it "lists the cookbooks in the lock as possibly needing to be uploaded" do
         | 
| 213 204 | 
             
                    expect(policyfile_lock).to receive(:validate_cookbooks!)
         | 
| 214 205 |  | 
| 215 | 
            -
                     | 
| 206 | 
            +
                    expected_versions_for_policy = cookbook_versions.keys.map do |cb_name|
         | 
| 207 | 
            +
                      cb = cookbook_versions[cb_name]
         | 
| 208 | 
            +
                      lock = cookbook_locks[cb_name]
         | 
| 209 | 
            +
                      ChefDK::Policyfile::Uploader::LockedCookbookForUpload.new(cb, lock)
         | 
| 210 | 
            +
                    end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                    expect(uploader.cookbook_versions_for_policy).to eq(expected_versions_for_policy)
         | 
| 216 213 | 
             
                  end
         | 
| 217 214 |  | 
| 218 215 | 
             
                  it "lists all cookbooks in the lock as needing to be uploaded" do
         | 
| @@ -0,0 +1,292 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Copyright:: Copyright (c) 2014 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_lock'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            describe ChefDK::PolicyfileLock, "when reading a Policyfile.lock" do
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              let(:valid_lock_data) do
         | 
| 24 | 
            +
                {
         | 
| 25 | 
            +
                  "name" => "example",
         | 
| 26 | 
            +
                  "run_list" => [ "recipe[cookbook::recipe_name]" ],
         | 
| 27 | 
            +
                  "cookbook_locks" => {
         | 
| 28 | 
            +
                    # TODO: add some valid locks
         | 
| 29 | 
            +
                  },
         | 
| 30 | 
            +
                  "solution_dependencies" => {
         | 
| 31 | 
            +
                    "Policyfile" => [],
         | 
| 32 | 
            +
                    "dependencies" => {}
         | 
| 33 | 
            +
                  }
         | 
| 34 | 
            +
                }
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              let(:storage_config) { ChefDK::Policyfile::StorageConfig.new }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              let(:lockfile) { ChefDK::PolicyfileLock.new(storage_config) }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
              describe "validating required fields" do
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                it "does not raise an error when all fields are valid" do
         | 
| 44 | 
            +
                  expect { lockfile.build_from_lock_data(valid_lock_data) }.to_not raise_error
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                it "requires the name to be present" do
         | 
| 48 | 
            +
                  missing_name = valid_lock_data.dup
         | 
| 49 | 
            +
                  missing_name.delete("name")
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                  expect { lockfile.build_from_lock_data(missing_name) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  blank_name = valid_lock_data.dup
         | 
| 54 | 
            +
                  blank_name["name"] = ""
         | 
| 55 | 
            +
                  expect { lockfile.build_from_lock_data(blank_name) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  invalid_name = valid_lock_data.dup
         | 
| 58 | 
            +
                  invalid_name["name"] = {}
         | 
| 59 | 
            +
                  expect { lockfile.build_from_lock_data(invalid_name) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                it "requires the run_list to be present" do
         | 
| 63 | 
            +
                  no_run_list = valid_lock_data.dup
         | 
| 64 | 
            +
                  no_run_list.delete("run_list")
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  expect { lockfile.build_from_lock_data(no_run_list) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  bad_run_list = valid_lock_data.dup
         | 
| 69 | 
            +
                  bad_run_list["run_list"] = "bad data"
         | 
| 70 | 
            +
                  expect { lockfile.build_from_lock_data(bad_run_list) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                it "validates the format of run_list items" do
         | 
| 74 | 
            +
                  bad_run_list = valid_lock_data.dup
         | 
| 75 | 
            +
                  bad_run_list["run_list"] = [ "bad data" ]
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  expect { lockfile.build_from_lock_data(bad_run_list) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                it "requires the `cookbook_locks` section be present and its value is a Hash" do
         | 
| 81 | 
            +
                  missing_locks = valid_lock_data.dup
         | 
| 82 | 
            +
                  missing_locks.delete("cookbook_locks")
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  expect { lockfile.build_from_lock_data(missing_locks) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  invalid_locks = valid_lock_data.dup
         | 
| 87 | 
            +
                  invalid_locks["cookbook_locks"] = []
         | 
| 88 | 
            +
                  expect { lockfile.build_from_lock_data(invalid_locks) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 89 | 
            +
                end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                describe "validating solution_dependencies" do
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                  it "requires the `solution_dependencies' section be present" do
         | 
| 94 | 
            +
                    missing_soln_deps = valid_lock_data.dup
         | 
| 95 | 
            +
                    missing_soln_deps.delete("solution_dependencies")
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    expect { lockfile.build_from_lock_data(missing_soln_deps) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 98 | 
            +
                  end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  it "requires the solution_dependencies object be a Hash" do
         | 
| 101 | 
            +
                    invalid_soln_deps = valid_lock_data.dup
         | 
| 102 | 
            +
                    invalid_soln_deps["solution_dependencies"] = []
         | 
| 103 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_soln_deps) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  it "requires the solution_dependencies object have a 'Policyfile' and 'dependencies' key" do
         | 
| 107 | 
            +
                    missing_keys_soln_deps = valid_lock_data.dup
         | 
| 108 | 
            +
                    missing_keys_soln_deps["solution_dependencies"] = {}
         | 
| 109 | 
            +
                    expect { lockfile.build_from_lock_data(missing_keys_soln_deps) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    missing_policyfile_key = valid_lock_data.dup
         | 
| 112 | 
            +
                    missing_policyfile_key["solution_dependencies"] = {"dependencies" => {} }
         | 
| 113 | 
            +
                    expect { lockfile.build_from_lock_data(missing_policyfile_key) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                    missing_dependencies_key = valid_lock_data.dup
         | 
| 116 | 
            +
                    missing_dependencies_key["solution_dependencies"] = { "Policyfile" => [] }
         | 
| 117 | 
            +
                    expect { lockfile.build_from_lock_data(missing_dependencies_key) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                  it "requires the Policyfile dependencies be an Array" do
         | 
| 121 | 
            +
                    invalid_policyfile_deps = valid_lock_data.dup
         | 
| 122 | 
            +
                    invalid_policyfile_deps["solution_dependencies"] = {"Policyfile" => 42, "dependencies" => {} }
         | 
| 123 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_policyfile_deps) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 124 | 
            +
                  end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                  it %q(requires the Policyfile dependencies be formatted like [ "COOKBOOK_NAME", "CONSTRAINT" ]) do
         | 
| 127 | 
            +
                    invalid_policyfile_deps_content = valid_lock_data.dup
         | 
| 128 | 
            +
                    invalid_policyfile_deps_content["solution_dependencies"] = { "Policyfile" => [ "bad" ], "dependencies" => {} }
         | 
| 129 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_policyfile_deps_content) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    invalid_policyfile_deps_content2 = valid_lock_data.dup
         | 
| 132 | 
            +
                    invalid_policyfile_deps_content2["solution_dependencies"] = { "Policyfile" => [ [42, "~> 2.0"] ], "dependencies" => {} }
         | 
| 133 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_policyfile_deps_content2) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    invalid_policyfile_deps_content3 = valid_lock_data.dup
         | 
| 136 | 
            +
                    invalid_policyfile_deps_content3["solution_dependencies"] = { "Policyfile" => [ ["cookbook_name", "bad"] ], "dependencies" => {} }
         | 
| 137 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_policyfile_deps_content3) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 138 | 
            +
                  end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                  it "requires the cookbook dependencies be a Hash" do
         | 
| 141 | 
            +
                    invalid_cookbook_deps = valid_lock_data.dup
         | 
| 142 | 
            +
                    invalid_cookbook_deps["solution_dependencies"] = { "Policyfile" => [], "dependencies" => 42 }
         | 
| 143 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_cookbook_deps) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  it "requires the cookbook dependencies entries be in the correct format" do
         | 
| 147 | 
            +
                    invalid_cookbook_deps = valid_lock_data.dup
         | 
| 148 | 
            +
                    bad_deps = { 42 => 42 }
         | 
| 149 | 
            +
                    invalid_cookbook_deps["solution_dependencies"] = { "Policyfile" => [], "dependencies" => bad_deps }
         | 
| 150 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_cookbook_deps) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                    invalid_cookbook_deps2 = valid_lock_data.dup
         | 
| 153 | 
            +
                    bad_deps2 = { "bad-format" => [] }
         | 
| 154 | 
            +
                    invalid_cookbook_deps2["solution_dependencies"] = { "Policyfile" => [], "dependencies" => bad_deps2 }
         | 
| 155 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_cookbook_deps2) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    invalid_cookbook_deps3 = valid_lock_data.dup
         | 
| 158 | 
            +
                    bad_deps3 = { "cookbook (1.0.0)" => 42 }
         | 
| 159 | 
            +
                    invalid_cookbook_deps3["solution_dependencies"] = { "Policyfile" => [], "dependencies" => bad_deps3 }
         | 
| 160 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_cookbook_deps3) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                    invalid_cookbook_deps4 = valid_lock_data.dup
         | 
| 163 | 
            +
                    bad_deps4 = { "cookbook (1.0.0)" => [ 42 ] }
         | 
| 164 | 
            +
                    invalid_cookbook_deps4["solution_dependencies"] = { "Policyfile" => [], "dependencies" => bad_deps4 }
         | 
| 165 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_cookbook_deps4) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
                end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
                describe "validating cookbook_locks entries" do
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                  # TODO: also check non-cached cookbook
         | 
| 172 | 
            +
                  let(:valid_cookbook_lock) do
         | 
| 173 | 
            +
                    {
         | 
| 174 | 
            +
                      "version" => "1.0.0",
         | 
| 175 | 
            +
                      "identifier" => "68c13b136a49b4e66cfe9d8aa2b5a85167b5bf9b",
         | 
| 176 | 
            +
                      "dotted_decimal_identifier" => "111.222.333",
         | 
| 177 | 
            +
                      "cache_key" => "foo-1.0.0",
         | 
| 178 | 
            +
                      "source_options" => {}
         | 
| 179 | 
            +
                    }
         | 
| 180 | 
            +
                  end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                  it "requires that each cookbook lock be a Hash" do
         | 
| 183 | 
            +
                    invalid_cookbook_lock = valid_lock_data.dup
         | 
| 184 | 
            +
                    invalid_cookbook_lock["cookbook_locks"] = { "foo" => 42 }
         | 
| 185 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_cookbook_lock) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 186 | 
            +
                  end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                  it "requires that cookbook locks not be empty" do
         | 
| 189 | 
            +
                    invalid_cookbook_lock = valid_lock_data.dup
         | 
| 190 | 
            +
                    invalid_cookbook_lock["cookbook_locks"] = { "foo" => {} }
         | 
| 191 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_cookbook_lock) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 192 | 
            +
                  end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                  it "requires that each cookbook lock have a version" do
         | 
| 195 | 
            +
                    invalid_lockfile = valid_lock_data.dup
         | 
| 196 | 
            +
                    invalid_cookbook_lock = valid_cookbook_lock.dup
         | 
| 197 | 
            +
                    invalid_cookbook_lock.delete("version")
         | 
| 198 | 
            +
                    invalid_lockfile["cookbook_locks"] = { "foo" => invalid_cookbook_lock }
         | 
| 199 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lockfile) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                  it "requires that the version be a string" do
         | 
| 203 | 
            +
                    invalid_lockfile = valid_lock_data.dup
         | 
| 204 | 
            +
                    invalid_cookbook_lock = valid_cookbook_lock.dup
         | 
| 205 | 
            +
                    invalid_cookbook_lock["version"] = 42
         | 
| 206 | 
            +
                    invalid_lockfile["cookbook_locks"] = { "foo" => invalid_cookbook_lock }
         | 
| 207 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lockfile) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 208 | 
            +
                  end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  it "requires that each cookbook lock have an identifier" do
         | 
| 211 | 
            +
                    invalid_lockfile = valid_lock_data.dup
         | 
| 212 | 
            +
                    invalid_cookbook_lock = valid_cookbook_lock.dup
         | 
| 213 | 
            +
                    invalid_cookbook_lock.delete("identifier")
         | 
| 214 | 
            +
                    invalid_lockfile["cookbook_locks"] = { "foo" => invalid_cookbook_lock }
         | 
| 215 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lockfile) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 216 | 
            +
                  end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                  it "requires that the identifier be a string" do
         | 
| 219 | 
            +
                    invalid_lockfile = valid_lock_data.dup
         | 
| 220 | 
            +
                    invalid_cookbook_lock = valid_cookbook_lock.dup
         | 
| 221 | 
            +
                    invalid_cookbook_lock["identifier"] = 42
         | 
| 222 | 
            +
                    invalid_lockfile["cookbook_locks"] = { "foo" => invalid_cookbook_lock }
         | 
| 223 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lockfile) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 224 | 
            +
                  end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                  it "requires that a cookbook lock have a key named `cache_key'" do
         | 
| 227 | 
            +
                    invalid_lockfile = valid_lock_data.dup
         | 
| 228 | 
            +
                    invalid_cookbook_lock = valid_cookbook_lock.dup
         | 
| 229 | 
            +
                    invalid_cookbook_lock.delete("cache_key")
         | 
| 230 | 
            +
                    invalid_lockfile["cookbook_locks"] = { "foo" => invalid_cookbook_lock }
         | 
| 231 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lockfile) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                  it "requires that the cache_key be a string or null" do
         | 
| 235 | 
            +
                    invalid_lockfile = valid_lock_data.dup
         | 
| 236 | 
            +
                    invalid_cookbook_lock = valid_cookbook_lock.dup
         | 
| 237 | 
            +
                    invalid_cookbook_lock["cache_key"] = 42
         | 
| 238 | 
            +
                    invalid_lockfile["cookbook_locks"] = { "foo" => invalid_cookbook_lock }
         | 
| 239 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lockfile) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 240 | 
            +
                  end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                  it "requires that a cookbook lock have a source_options attribute" do
         | 
| 243 | 
            +
                    invalid_lockfile = valid_lock_data.dup
         | 
| 244 | 
            +
                    invalid_cookbook_lock = valid_cookbook_lock.dup
         | 
| 245 | 
            +
                    invalid_cookbook_lock.delete("source_options")
         | 
| 246 | 
            +
                    invalid_lockfile["cookbook_locks"] = { "foo" => invalid_cookbook_lock }
         | 
| 247 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lockfile) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 248 | 
            +
                  end
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                  it "requires that source options be a Hash" do
         | 
| 251 | 
            +
                    invalid_lockfile = valid_lock_data.dup
         | 
| 252 | 
            +
                    invalid_cookbook_lock = valid_cookbook_lock.dup
         | 
| 253 | 
            +
                    invalid_cookbook_lock["source_options"] = 42
         | 
| 254 | 
            +
                    invalid_lockfile["cookbook_locks"] = { "foo" => invalid_cookbook_lock }
         | 
| 255 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lockfile) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 256 | 
            +
                  end
         | 
| 257 | 
            +
             | 
| 258 | 
            +
                  it "requires that a cookbook lock be a valid local cookbook if `cache_key' is null/nil" do
         | 
| 259 | 
            +
                    valid_lock_with_local_cookbook = valid_lock_data.dup
         | 
| 260 | 
            +
                    valid_local_cookbook = valid_cookbook_lock.dup
         | 
| 261 | 
            +
                    valid_local_cookbook["cache_key"] = nil
         | 
| 262 | 
            +
                    valid_local_cookbook["source"] = "path/to/foo"
         | 
| 263 | 
            +
                    valid_lock_with_local_cookbook["cookbook_locks"] = { "foo" => valid_local_cookbook }
         | 
| 264 | 
            +
                    expect { lockfile.build_from_lock_data(valid_lock_with_local_cookbook) }.to_not raise_error
         | 
| 265 | 
            +
             | 
| 266 | 
            +
                    invalid_lock_with_local_cookbook = valid_lock_data.dup
         | 
| 267 | 
            +
                    invalid_local_cookbook = valid_cookbook_lock.dup
         | 
| 268 | 
            +
                    invalid_local_cookbook["cache_key"] = nil
         | 
| 269 | 
            +
                    invalid_local_cookbook["source"] = 42
         | 
| 270 | 
            +
                    invalid_lock_with_local_cookbook["cookbook_locks"] = { "foo" => invalid_local_cookbook }
         | 
| 271 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lock_with_local_cookbook) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 272 | 
            +
                  end
         | 
| 273 | 
            +
             | 
| 274 | 
            +
                  it "requires that a cookbook lock w/ a key named `cache_key' be a valid cached cookbook structure" do
         | 
| 275 | 
            +
                    valid_lock_with_cached_cookbook = valid_lock_data.dup
         | 
| 276 | 
            +
                    valid_cached_cookbook = valid_cookbook_lock.dup
         | 
| 277 | 
            +
                    valid_cached_cookbook["cache_key"] = nil
         | 
| 278 | 
            +
                    valid_cached_cookbook["source"] = "path/to/foo"
         | 
| 279 | 
            +
                    valid_lock_with_cached_cookbook["cookbook_locks"] = { "foo" => valid_cached_cookbook }
         | 
| 280 | 
            +
                    expect { lockfile.build_from_lock_data(valid_lock_with_cached_cookbook) }.to_not raise_error
         | 
| 281 | 
            +
             | 
| 282 | 
            +
                    invalid_lock_with_cached_cookbook = valid_lock_data.dup
         | 
| 283 | 
            +
                    invalid_cached_cookbook = valid_cookbook_lock.dup
         | 
| 284 | 
            +
                    invalid_cached_cookbook["cache_key"] = 42
         | 
| 285 | 
            +
                    invalid_lock_with_cached_cookbook["cookbook_locks"] = { "foo" => invalid_cached_cookbook }
         | 
| 286 | 
            +
                    expect { lockfile.build_from_lock_data(invalid_lock_with_cached_cookbook) }.to raise_error(ChefDK::InvalidLockfile)
         | 
| 287 | 
            +
                  end
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                end
         | 
| 290 | 
            +
              end
         | 
| 291 | 
            +
             | 
| 292 | 
            +
            end
         | 
| @@ -0,0 +1,170 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            # Copyright:: Copyright (c) 2014 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/install'
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            describe ChefDK::PolicyfileServices::Install do
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              let(:working_dir) do
         | 
| 24 | 
            +
                path = File.join(tempdir, "policyfile_services_test_working_dir")
         | 
| 25 | 
            +
                Dir.mkdir(path)
         | 
| 26 | 
            +
                path
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              let(:policyfile_rb_explicit_name) { nil }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              let(:policyfile_rb_name) { policyfile_rb_explicit_name || "Policyfile.rb" }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              let(:policyfile_lock_name) { "Policyfile.lock.json" }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              let(:policyfile_rb_path) { File.join(working_dir, policyfile_rb_name) }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              let(:policyfile_lock_path) { File.join(working_dir, policyfile_lock_name) }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
              let(:local_cookbooks_root) do
         | 
| 40 | 
            +
                File.join(fixtures_path, "local_path_cookbooks")
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
              let(:policyfile_content) do
         | 
| 44 | 
            +
                <<-E
         | 
| 45 | 
            +
            name 'install-example'
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            run_list 'local-cookbook'
         | 
| 48 | 
            +
             | 
| 49 | 
            +
            cookbook 'local-cookbook', path: '#{local_cookbooks_root}/local-cookbook'
         | 
| 50 | 
            +
            E
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              let(:ui) { TestHelpers::TestUI.new }
         | 
| 54 | 
            +
             | 
| 55 | 
            +
              let(:install_service) { described_class.new(policyfile: policyfile_rb_name, ui: ui, root_dir: working_dir) }
         | 
| 56 | 
            +
             | 
| 57 | 
            +
              let(:storage_config) do
         | 
| 58 | 
            +
                ChefDK::Policyfile::StorageConfig.new( cache_path: nil, relative_paths_root: local_cookbooks_root )
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              def result_policyfile_lock
         | 
| 62 | 
            +
                expect(File).to exist(policyfile_lock_path)
         | 
| 63 | 
            +
                content = IO.read(policyfile_lock_path)
         | 
| 64 | 
            +
                lock_data = FFI_Yajl::Parser.parse(content)
         | 
| 65 | 
            +
                ChefDK::PolicyfileLock.new(storage_config).build_from_lock_data(lock_data)
         | 
| 66 | 
            +
              end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
              context "when no Policyfile is present or specified" do
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                it "errors out" do
         | 
| 71 | 
            +
                  expect { install_service.run }.to raise_error(ChefDK::PolicyfileNotFound, "Policyfile not found at path #{policyfile_rb_path}")
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
              end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              context "when a Policyfile exists" do
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                before do
         | 
| 79 | 
            +
                  File.open(policyfile_rb_path, "w+") { |f| f.print(policyfile_content) }
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                it "infers that the Policyfile.rb is located at $CWD/Policyfile.rb" do
         | 
| 83 | 
            +
                  expect(install_service.policyfile_path).to eq(policyfile_rb_path)
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                it "reads the policyfile from disk" do
         | 
| 87 | 
            +
                  expect(install_service.policyfile_content).to eq(policyfile_content)
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                context "and the policyfile has an error" do
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  let(:policyfile_content) { 'raise "borkbork"' }
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  it "errors out and creates no lockfile" do
         | 
| 95 | 
            +
                    expect { install_service.run }.to raise_error(ChefDK::PolicyfileInstallError)
         | 
| 96 | 
            +
                    expect(File).to_not exist(policyfile_lock_path)
         | 
| 97 | 
            +
                  end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                context "and no lockfile exists" do
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  it "solves the Policyfile demands, installs cookbooks, emits a lockfile" do
         | 
| 104 | 
            +
                    install_service.run
         | 
| 105 | 
            +
                    generated_lock = result_policyfile_lock
         | 
| 106 | 
            +
                    expect(generated_lock.name).to eq('install-example')
         | 
| 107 | 
            +
                    expect(generated_lock.cookbook_locks).to have_key("local-cookbook")
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                context "and a lockfile exists" do
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  before do
         | 
| 115 | 
            +
                    install_service.dup.run
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                  it "reads the policyfile lock from disk" do
         | 
| 119 | 
            +
                    lock = install_service.policyfile_lock
         | 
| 120 | 
            +
                    expect(lock).to be_an_instance_of(ChefDK::PolicyfileLock)
         | 
| 121 | 
            +
                    expect(lock.name).to eq('install-example')
         | 
| 122 | 
            +
                    expect(lock.cookbook_locks).to have_key("local-cookbook")
         | 
| 123 | 
            +
                  end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  it "ensures that cookbooks are installed" do
         | 
| 126 | 
            +
                    expect(install_service.policyfile_lock).to receive(:install_cookbooks).and_call_original
         | 
| 127 | 
            +
                    install_service.run
         | 
| 128 | 
            +
                  end
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  describe "when an error occurs during the install" do
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                    before do
         | 
| 133 | 
            +
                      expect(install_service.policyfile_lock).to receive(:install_cookbooks).and_raise("some error")
         | 
| 134 | 
            +
                    end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                    it "raises a PolicyfileInstallError" do
         | 
| 137 | 
            +
                      expect { install_service.run }.to raise_error(ChefDK::PolicyfileInstallError)
         | 
| 138 | 
            +
                    end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
             | 
| 141 | 
            +
                  end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                  context "and the Policyfile has updated dependendencies" do
         | 
| 144 | 
            +
             | 
| 145 | 
            +
                    # For very first iteration, we won't tackle this case if it's hard
         | 
| 146 | 
            +
                    it "Conservatively updates deps, recomputes lock, and installs"
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                  end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                context "and an explicit Policyfile name is given" do
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                  let(:policyfile_rb_explicit_name) { "MyPolicy.rb" }
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  let(:policyfile_lock_name) { "MyPolicy.lock.json" }
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                  it "infers that the Policyfile.rb is located at $CWD/$POLICYFILE_NAME" do
         | 
| 159 | 
            +
                    expect(install_service.policyfile_path).to eq(policyfile_rb_path)
         | 
| 160 | 
            +
                  end
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  it "reads the policyfile from disk" do
         | 
| 163 | 
            +
                    expect(install_service.policyfile_content).to eq(policyfile_content)
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                end
         | 
| 167 | 
            +
              end
         | 
| 168 | 
            +
             | 
| 169 | 
            +
            end
         | 
| 170 | 
            +
             |