diff --git a/lib/chef-dk/policyfile_services/export_repo.rb b/lib/chef-dk/policyfile_services/export_repo.rb index 35dd02e08..f2a46b476 100644 --- a/lib/chef-dk/policyfile_services/export_repo.rb +++ b/lib/chef-dk/policyfile_services/export_repo.rb @@ -16,6 +16,7 @@ # require 'fileutils' +require 'tmpdir' require 'chef-dk/service_exceptions' require 'chef-dk/policyfile_lock' @@ -50,6 +51,8 @@ def initialize(policyfile: nil, export_dir: nil, root_dir: nil, archive: false, policyfile_rel_path = policyfile || "Policyfile.rb" policyfile_full_path = File.expand_path(policyfile_rel_path, root_dir) @storage_config = Policyfile::StorageConfig.new.use_policyfile(policyfile_full_path) + + @staging_dir = nil end def archive? @@ -79,10 +82,19 @@ def policyfile_lock @policyfile_lock || validate_lockfile end + def archive_file_location + return nil unless archive? + filename = "#{policyfile_lock.name}-#{policyfile_lock.revision_id}.tgz" + File.join(export_dir, filename) + end + def export - create_repo_structure - copy_cookbooks - create_policyfile_data_item + with_staging_dir do + create_repo_structure + copy_cookbooks + create_policyfile_data_item + mv_staged_repo + end rescue => error msg = "Failed to export policy (in #{policyfile_filename}) to #{export_dir}" raise PolicyfileExportRepoError.new(msg, error) @@ -90,13 +102,27 @@ def export private - def create_repo_structure - FileUtils.rm_rf(cookbooks_dir) - FileUtils.rm_rf(policyfiles_data_bag_dir) + def with_staging_dir + p = Process.pid + t = Time.new.utc.strftime("%Y%m%d%H%M%S") + Dir.mktmpdir("chefdk-export-#{p}-#{t}") do |d| + begin + @staging_dir = d + yield + ensure + @staging_dir = nil + end + end + end + + def staging_dir + @staging_dir + end + def create_repo_structure FileUtils.mkdir_p(export_dir) - FileUtils.mkdir_p(cookbooks_dir) - FileUtils.mkdir_p(policyfiles_data_bag_dir) + FileUtils.mkdir_p(cookbooks_staging_dir) + FileUtils.mkdir_p(policyfiles_data_bag_staging_dir) end def copy_cookbooks @@ -107,7 +133,7 @@ def copy_cookbooks def copy_cookbook(lock) dirname = "#{lock.name}-#{lock.dotted_decimal_identifier}" - export_path = File.join(export_dir, "cookbooks", dirname) + export_path = File.join(staging_dir, "cookbooks", dirname) metadata_rb_path = File.join(export_path, "metadata.rb") FileUtils.cp_r(lock.cookbook_path, export_path) FileUtils.rm_f(metadata_rb_path) @@ -122,8 +148,6 @@ def copy_cookbook(lock) end def create_policyfile_data_item - policy_id = "#{policyfile_lock.name}-#{POLICY_GROUP}" - item_path = File.join(export_dir, "data_bags", "policyfiles", "#{policy_id}.json") lock_data = policyfile_lock.to_lock.dup @@ -144,6 +168,17 @@ def create_policyfile_data_item end end + def mv_staged_repo + # If we got here, either these dirs are empty/don't exist or force is + # set to true. + FileUtils.rm_rf(cookbooks_dir) + FileUtils.rm_rf(policyfiles_data_bag_dir) + + FileUtils.mv(cookbooks_staging_dir, export_dir) + FileUtils.mkdir_p(export_data_bag_dir) + FileUtils.mv(policyfiles_data_bag_staging_dir, export_data_bag_dir) + end + def validate_lockfile return @policyfile_lock if @policyfile_lock @policyfile_lock = ChefDK::PolicyfileLock.new(storage_config).build_from_lock_data(policy_data) @@ -188,8 +223,28 @@ def cookbooks_dir File.join(export_dir, "cookbooks") end + def export_data_bag_dir + File.join(export_dir, "data_bags") + end + def policyfiles_data_bag_dir - File.join(export_dir, "data_bags", "policyfiles") + File.join(export_data_bag_dir, "policyfiles") + end + + def policy_id + "#{policyfile_lock.name}-#{POLICY_GROUP}" + end + + def item_path + File.join(staging_dir, "data_bags", "policyfiles", "#{policy_id}.json") + end + + def cookbooks_staging_dir + File.join(staging_dir, "cookbooks") + end + + def policyfiles_data_bag_staging_dir + File.join(staging_dir, "data_bags", "policyfiles") end end diff --git a/spec/unit/policyfile_services/export_repo_spec.rb b/spec/unit/policyfile_services/export_repo_spec.rb index 0fad6ee4a..af6976c13 100644 --- a/spec/unit/policyfile_services/export_repo_spec.rb +++ b/spec/unit/policyfile_services/export_repo_spec.rb @@ -40,10 +40,13 @@ let(:force_export) { false } + let(:archive) { false } + subject(:export_service) do described_class.new(policyfile: policyfile_rb_explicit_name, root_dir: working_dir, export_dir: export_dir, + archive: archive, force: force_export) end @@ -154,6 +157,22 @@ expect(export_service.policy_name).to eq("install-example") end + context "when using archive mode" do + + let(:archive) { true } + + # TODO: also support a full file name + context "when the given 'export_dir' is a directory" do + + it "sets the archive file location to $policy_name-$revision.tgz" do + expected = File.join(export_dir, "install-example-60e5ad638dce219d8f87d589463ec4a9884007ba5e2adbb4c0a7021d67204f1a.tgz") + expect(export_service.archive_file_location).to eq(expected) + end + + end + + end + describe "writing updates to the policyfile lock" do let(:updated_lockfile_io) { StringIO.new } @@ -169,10 +188,11 @@ context "copying the cookbooks to the export dir" do - context "when the export dir is empty" do + shared_examples_for "successful_export" do before do allow(export_service.policyfile_lock).to receive(:validate_cookbooks!).and_return(true) export_service.run + pp :export_run end let(:cookbook_files) do @@ -222,6 +242,11 @@ end + context "when the export dir is empty" do + + include_examples "successful_export" + end + context "When an error occurs creating the export" do before do @@ -253,14 +278,15 @@ end it "ignores the non-conflicting content and exports" do - export_service.run - expect(File).to exist(file_in_export_dir) expect(File).to exist(extra_data_bag_item) expect(File).to be_directory(File.join(export_dir, "cookbooks")) expect(File).to be_directory(File.join(export_dir, "data_bags")) end + + include_examples "successful_export" + end context "When the export dir has conflicting content" do @@ -310,9 +336,26 @@ end + end # When the export dir has conflicting content + + context "when archive mode is enabled" do + + let(:archive) { true } + + # TODO: + it "exports the repo as a tgz archive" + + include_examples "successful_export" do + + before do + pp :archive_before_block + end + + end + end - end + end # copying the cookbooks to the export dir end end