require 'test_helper' class CodeDeployPluginInstallerTest < InstanceAgentTestCase include InstanceAgent::Plugins::CodeDeployPlugin context "The CodeDeploy Plugin's file installer" do setup do @app_spec = mock("app spec") @deployment_archive_dir = "deploy-archive-dir" @deployment_instructions_dir = "deploy-instructions-dir" @deployment_group_id = "ig1" @file_exists_behavior = "DISALLOW" @installer = Installer.new(:deployment_archive_dir => @deployment_archive_dir, :deployment_instructions_dir => @deployment_instructions_dir, :file_exists_behavior => @file_exists_behavior) end context "when initializing" do should "require a deployment archive directory" do assert_raise { Installer.new(:deployment_instructions_dir => "otherdir", :file_exists_behavior => @file_exists_behavior) } end should "require a deployment instructions directory" do assert_raise { Installer.new(:deployment_archive_dir => "somedir", :file_exists_behavior => @file_exists_behavior) } end should "require file exists behavior" do assert_raise {Installer.new(:deployment_instructions_dir => "otherdir", :deployment_archive_dir => "somedir")} end end context "deployment archive directory getter" do should "return the deployment archive directory from the initializer" do assert_equal(@deployment_archive_dir, @installer.deployment_archive_dir) end end context "deployment instructions directory getter" do should "return the deployment instructions directory from the initializer" do assert_equal(@deployment_instructions_dir, @installer.deployment_instructions_dir) end end context "file exists behavior getter" do should "return the file exisits behavior from the initializer" do assert_equal(@file_exists_behavior, @installer.file_exists_behavior) end end context "installing" do setup do @instruction_builder = mock("instruction builder") InstallInstruction .stubs(:generate_instructions) .yields(@instruction_builder) .returns(@instruction_builder) @instruction_builder.stubs(:cleanup).returns(nil) File.stubs(:exist?).returns(false) File.stubs(:exist?).with("deploy-instructions-dir/ig1-cleanup").returns(false) @app_spec.stubs(:permissions).returns([]) @app_spec.stubs(:files).returns([]) File.stubs(:open) end context "with an existing cleanup file" do setup do File.stubs(:exist?).with("deploy-instructions-dir/ig1-cleanup").returns(true) end should "parse the file, execute the commands and remove the file before generating new install instructions" do File.stubs(:read).with("deploy-instructions-dir/ig1-cleanup").returns("CLEANUP!") call_sequence = sequence("call sequence") cleanup_commands = [mock("first"), mock("second")] InstallInstruction .stubs(:parse_remove_commands) .with("CLEANUP!") .returns(cleanup_commands) cleanup_commands.each do |cmd| cmd.expects(:execute).in_sequence(call_sequence) end FileUtils .expects(:rm) .with("deploy-instructions-dir/ig1-cleanup") .in_sequence(call_sequence) InstallInstruction .expects(:generate_instructions) .returns(CommandBuilder.new) .in_sequence(call_sequence) @installer.install(@deployment_group_id, @app_spec) end end context "no files to install" do should "generate an empty install instructions file" do @app_spec.stubs(:files).returns([]) @instruction_builder.expects(:copy).never @instruction_builder.expects(:mkdir).never @installer.install(@deployment_group_id, @app_spec) end end context "files to install" do context "regular files" do setup do @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1"), stub(:source => "src2", :destination => "dst2")]) File.stubs(:directory?).returns(false) File.stubs(:exist?).returns(false) File.stubs(:exist?).with(any_of("dst1", "dst2")).returns(true) @instruction_builder.stubs(:copy) end should "generate an entry for each file" do @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1", "dst1/src1") @instruction_builder .expects(:copy) .with("deploy-archive-dir/src2", "dst2/src2") @installer.install(@deployment_group_id, @app_spec) end should "raise an error if the file already exists and @file_exists_behavior is set to 'DISALLOW'" do File.stubs(:exist?).with("dst2/src2").returns(true) assert_raised_with_message("The deployment failed because a specified file already exists at this location: dst2/src2") do @installer.install(@deployment_group_id, @app_spec) end end should "raise an error if the file already exists and appspec file_exists_behavior is set to 'DISALLOW'" do @installer.file_exists_behavior = "OVERWRITE" @app_spec .stubs(:file_exists_behavior) .returns("DISALLOW") File.stubs(:exist?).with("dst2/src2").returns(true) assert_raised_with_message("The deployment failed because a specified file already exists at this location: dst2/src2") do @installer.install(@deployment_group_id, @app_spec) end end should "generate a copy command if the file already exists and Installer @file_exists_behavior is set to 'OVERWRITE'" do @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1")]) File.stubs(:exist?).with("dst1/src1").returns(true) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1", "dst1/src1") @installer.file_exists_behavior = "OVERWRITE" @installer.install(@deployment_group_id, @app_spec) end should "generate a copy command if the file already exists and appspec file_exists_behavior is set to 'OVERWRITE'" do @app_spec .stubs(:file_exists_behavior) .returns("OVERWRITE") .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1")]) File.stubs(:exist?).with("dst1/src1").returns(true) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1", "dst1/src1") assert_equal(@installer.file_exists_behavior, "DISALLOW") @installer.install(@deployment_group_id, @app_spec) end should "neither generate a copy command nor raise an error if the file already exists and @file_exists_behavior is set to 'RETAIN'" do @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1")]) File.stubs(:exist?).with("dst1/src1").returns(true) @instruction_builder.expects(:copy).never @installer.file_exists_behavior = "RETAIN" @installer.install(@deployment_group_id, @app_spec) end should "neither generate a copy command nor raise an error if the file already exists and appspec file_exists_behavior is set to 'RETAIN'" do @app_spec .stubs(:file_exists_behavior) .returns("RETAIN") .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1")]) File.stubs(:exist?).with("dst1/src1").returns(true) @instruction_builder.expects(:copy).never assert_equal(@installer.file_exists_behavior, "DISALLOW") @installer.install(@deployment_group_id, @app_spec) end should "raise an error if the file already exists and @file_exists_behavior is set to some invalid value" do File.stubs(:exist?).with("dst2/src2").returns(true) @installer.file_exists_behavior = "SOMETHING_WEIRD" assert_raised_with_message("The deployment failed because an invalid option was specified for fileExistsBehavior: #{@installer.file_exists_behavior}. Valid options include OVERWRITE, RETAIN, and DISALLOW.") do @installer.install(@deployment_group_id, @app_spec) end end should "generate a mkdir command if the destination directory does not exist" do @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1")]) File.stubs(:exist?).with("dst1").returns(false) command_sequence = sequence("command sequence") @instruction_builder .expects(:mkdir) .with("dst1") .in_sequence(command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1", "dst1/src1") .in_sequence(command_sequence) @installer.install(@deployment_group_id, @app_spec) end should "generate a mkdir command if the destination directory does not exist (absolute path)" do @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "/dst1")]) command_sequence = sequence("command sequence") @instruction_builder .expects(:mkdir) .with("/dst1") .in_sequence(command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1", "/dst1/src1") .in_sequence(command_sequence) @installer.install(@deployment_group_id, @app_spec) end should "generate mkdir commands for multiple levels of missing directories" do @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1/foo/bar")]) File.stubs(:exist?).with("dst1").returns(true) command_sequence = sequence("command sequence") @instruction_builder .expects(:mkdir) .with("dst1/foo") .in_sequence(command_sequence) @instruction_builder .expects(:mkdir) .with("dst1/foo/bar") .in_sequence(command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1", "dst1/foo/bar/src1") .in_sequence(command_sequence) @installer.install(@deployment_group_id, @app_spec) end end # "regular files" context "directories" do setup do @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1")]) File.stubs(:directory?).returns(false) File.stubs(:directory?).with("deploy-archive-dir/src1").returns(true) Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", ".."]) @command_sequence = sequence("commands") end should "generate a mkdir if the destination doesn't exist" do @instruction_builder .expects(:mkdir) .with("dst1") @installer.install(@deployment_group_id, @app_spec) end should "generate mkdirs multiple levels of missing parent directories (absolute path)" do @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "/dst1/foo/bar")]) File.stubs(:exist?).with("/dst1").returns(true) command_sequence = sequence("command sequence") @instruction_builder .expects(:mkdir) .with("/dst1/foo") .in_sequence(command_sequence) @instruction_builder .expects(:mkdir) .with("/dst1/foo/bar") .in_sequence(command_sequence) @installer.install(@deployment_group_id, @app_spec) end should "not generate a mkdir if the destination exists" do File.stubs(:directory?).with("dst1").returns(true) @instruction_builder.expects(:mkdir).never @installer.install(@deployment_group_id, @app_spec) end should "generate a copy after the mkdir for each entry in the directory" do Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo", "bar"]) @instruction_builder.expects(:mkdir).with("dst1").in_sequence(@command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1/foo", "dst1/foo") .in_sequence(@command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1/bar", "dst1/bar") .in_sequence(@command_sequence) @installer.install(@deployment_group_id, @app_spec) end should "raise an error if an entry already exists and @file_exists_behavior is set to 'DISALLOW'" do Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo", "bar"]) File.stubs(:exist?).with("dst1/bar").returns(true) @instruction_builder.stubs(:mkdir) @instruction_builder.stubs(:copy) assert_raised_with_message("The deployment failed because a specified file already exists at this location: dst1/bar") do @installer.install(@deployment_group_id, @app_spec) end end should "generate a copy command if the file already exists and @file_exists_behavior is set to 'OVERWRITE'" do Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo", "bar"]) File.stubs(:directory?).with("dst1").returns(true) File.stubs(:exist?).with("dst1/bar").returns(true) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1/foo", "dst1/foo") .in_sequence(@command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1/bar", "dst1/bar") .in_sequence(@command_sequence) @installer.file_exists_behavior = "OVERWRITE" @installer.install(@deployment_group_id, @app_spec) end should "neither generate a copy command nor raise an error if the file already exists and @file_exists_behavior is set to 'RETAIN'" do Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo", "bar"]) File.stubs(:directory?).with("dst1").returns(true) File.stubs(:exist?).with("dst1/bar").returns(true) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1/foo", "dst1/foo") @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1/bar", "dst1/bar") .never @installer.file_exists_behavior = "RETAIN" @installer.install(@deployment_group_id, @app_spec) end context "with subdirectories" do setup do File.stubs(:directory?).with("deploy-archive-dir/src1/foo").returns(true) File.stubs(:directory?).with("dst1").returns(true) Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo"]) Dir.stubs(:entries) .with("deploy-archive-dir/src1/foo") .returns([".", ".."]) end should "generate a mkdir for the subdirectory if it doesn't exist" do @instruction_builder .expects(:mkdir) .with("dst1/foo") @installer.install(@deployment_group_id, @app_spec) end should "not generate a mkdir for the subdirectory if it exists" do File.stubs(:directory?).with("dst1/foo").returns(true) @instruction_builder .expects(:mkdir) .with("dst1/foo") .never @installer.install(@deployment_group_id, @app_spec) end should "generate a copy for each entry in the subdirectory" do Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo"]) Dir.stubs(:entries) .with("deploy-archive-dir/src1/foo") .returns([".", "..", "bar"]) @instruction_builder .expects(:mkdir) .with("dst1/foo") .in_sequence(@command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1/foo/bar", "dst1/foo/bar") .in_sequence(@command_sequence) @installer.install(@deployment_group_id, @app_spec) end should "raise an error if an entry already exists and @file_exists_behavior is set to 'DISALLOW'" do File.stubs(:exist?).with("dst1/foo/bar").returns(true) Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo"]) Dir.stubs(:entries) .with("deploy-archive-dir/src1/foo") .returns([".", "..", "bar"]) @instruction_builder.stubs(:mkdir) @instruction_builder.stubs(:copy) assert_raised_with_message("The deployment failed because a specified file already exists at this location: dst1/foo/bar") do @installer.install(@deployment_group_id, @app_spec) end end should "generate a copy command if the file already exists and @file_exists_behavior is set to 'OVERWRITE'" do File.stubs(:directory?).with("dst1/foo").returns(true) File.stubs(:exist?).with("dst1/foo/bar").returns(true) Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo"]) Dir.stubs(:entries) .with("deploy-archive-dir/src1/foo") .returns([".", "..", "bar"]) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1/foo/bar", "dst1/foo/bar") @installer.file_exists_behavior = "OVERWRITE" @installer.install(@deployment_group_id, @app_spec) end should "neither generate a copy command nor raise an error if the file already exists and @file_exists_behavior is set to 'RETAIN'" do File.stubs(:directory?).with("dst1/foo").returns(true) File.stubs(:exist?).with("dst1/foo/bar").returns(true) Dir.stubs(:entries) .with("deploy-archive-dir/src1") .returns([".", "..", "foo"]) Dir.stubs(:entries) .with("deploy-archive-dir/src1/foo") .returns([".", "..", "bar"]) @instruction_builder .expects(:copy) .never @installer.file_exists_behavior = "RETAIN" @installer.install(@deployment_group_id, @app_spec) end end # "with subdirectories" end # "directories" context "with permissions" do setup do @permissions = [InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst1/src1", {:type => ["file"]}), InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("/", {:type => ["directory"], :pattern => "dst*", :except => []}), InstanceAgent::Plugins::CodeDeployPlugin::ApplicationSpecification::LinuxPermissionInfo.new("dst3/", {:type => ["file","directory"], :pattern => "file*", :except => [*2]})] @app_spec .stubs(:files) .returns([stub(:source => "src1", :destination => "dst1"), stub(:source => "src2", :destination => "dst2"), stub(:source => "src3", :destination => "dst3")]) @app_spec .stubs(:permissions) .returns(@permissions) File.stubs(:directory?).returns(false) File.stubs(:directory?).with("deploy-archive-dir/src2").returns(true) File.stubs(:directory?).with("deploy-archive-dir/src3").returns(true) File.stubs(:directory?).with("deploy-archive-dir/src3/dir1").returns(true) File.stubs(:directory?).with("/").returns(true) Dir.stubs(:entries) .with("deploy-archive-dir/src2") .returns([".", ".."]) Dir.stubs(:entries) .with("deploy-archive-dir/src3") .returns(["file1", "file2", "dir1", ".", ".."]) Dir.stubs(:entries) .with("deploy-archive-dir/src3/dir1") .returns(["file1", ".", ".."]) File.stubs(:exist?).returns(false) File.stubs(:exist?).with(any_of("dst1","dst3")).returns(true) @instruction_builder.stubs(:copying_file?).returns(false) @instruction_builder.stubs(:copying_file?).with("dst1/src1").returns(true) @instruction_builder.stubs(:making_directory?).returns(false) @instruction_builder.stubs(:making_directory?).with("dst3/").returns(true) @instruction_builder.stubs(:find_matches).returns([]) @instruction_builder.stubs(:find_matches).with(@permissions[1]).returns(["dst2", "dst3"]) @instruction_builder.stubs(:find_matches).with(@permissions[2]).returns(["dst3/file1"]) end should "set the permissions" do command_sequence = sequence("command sequence") @instruction_builder .expects(:copy) .with("deploy-archive-dir/src1", "dst1/src1") .in_sequence(command_sequence) @instruction_builder .expects(:mkdir) .with("dst2") .in_sequence(command_sequence) @instruction_builder .expects(:mkdir) .with("dst3") .in_sequence(command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src3/file1", "dst3/file1") .in_sequence(command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src3/file2", "dst3/file2") .in_sequence(command_sequence) @instruction_builder .expects(:mkdir) .with("dst3/dir1") .in_sequence(command_sequence) @instruction_builder .expects(:copy) .with("deploy-archive-dir/src3/dir1/file1", "dst3/dir1/file1") .in_sequence(command_sequence) @instruction_builder .expects(:set_permissions) .with("dst1/src1", @permissions[0]) .in_sequence(command_sequence) @instruction_builder .expects(:set_permissions) .with("dst2", @permissions[1]) .in_sequence(command_sequence) @instruction_builder .expects(:set_permissions) .with("dst3", @permissions[1]) .in_sequence(command_sequence) @instruction_builder .expects(:set_permissions) .with("dst3/file1", @permissions[2]) .in_sequence(command_sequence) @installer.install(@deployment_group_id, @app_spec) end end end # "files to install" context "after generating instructions" do setup do @command_list = [stub("a command", :execute => nil), stub("a second command", :execute => nil)] commands = mock("command builder") commands.stubs(:command_array).returns(@command_list) commands.stubs(:cleanup) InstallInstruction.stubs(:generate_instructions).returns(commands) commands.stubs(:to_json).returns("INSTALL!") end should "write them to disk" do install_file = mock("install file") File .expects(:open) .with("deploy-instructions-dir/ig1-install.json", "w") .yields(install_file) install_file.expects(:write).with("INSTALL!") @installer.install(@deployment_group_id, @app_spec) end should "execute them after writing to disk" do call_sequence = sequence("call sequence") File .expects(:open) .with("deploy-instructions-dir/ig1-install.json", "w") .in_sequence(call_sequence) cleanup_file = stub("cleanup file") File .stubs(:open) .with("deploy-instructions-dir/ig1-cleanup", "w") .yields(cleanup_file) @command_list.each do |cmd| cmd .expects(:execute) .with(cleanup_file) .in_sequence(call_sequence) end @installer.install(@deployment_group_id, @app_spec) end end # "after generating instructions" end # "installing" end end