Skip to content

Commit

Permalink
Adding extra testing for both net48 and net6.0 installers (#582)
Browse files Browse the repository at this point in the history
Adding extra testing for both net48 and net6.0 installers
  • Loading branch information
sburmanoctopus authored Sep 6, 2023
1 parent 03b27a0 commit 85a652a
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 72 deletions.
4 changes: 2 additions & 2 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
"TestLinuxPackages",
"TestOsx",
"TestWindows",
"TestWindowsInstallerPermissions"
"TestWindowsInstallers"
]
}
},
Expand Down Expand Up @@ -166,7 +166,7 @@
"TestLinuxPackages",
"TestOsx",
"TestWindows",
"TestWindowsInstallerPermissions"
"TestWindowsInstallers"
]
}
},
Expand Down
197 changes: 128 additions & 69 deletions build/Build.Tests.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ReSharper disable RedundantUsingDirective
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
Expand Down Expand Up @@ -111,76 +112,10 @@ void RunLinuxPackageTestsFor(TestConfigurationOnLinuxDistribution testConfigurat

[PublicAPI]
//todo: move this out of the build script to a proper test project ("smoke tests"?)
Target TestWindowsInstallerPermissions => _ => _
Target TestWindowsInstallers => _ => _
.Executes(() =>
{
string GetTestName(AbsolutePath installerPath) => Path.GetFileName(installerPath).Replace(".msi", "");

void TestInstallerPermissions(AbsolutePath installerPath)
{
var destination = TestDirectory / "install" / GetTestName(installerPath);
FileSystemTasks.EnsureExistingDirectory(destination);

InstallMsi(installerPath, destination);

try
{
var builtInUsersHaveWriteAccess = DoesSidHaveRightsToDirectory(destination, WellKnownSidType.BuiltinUsersSid, FileSystemRights.AppendData, FileSystemRights.CreateFiles);
if (builtInUsersHaveWriteAccess)
{
throw new Exception($"The installation destination {destination} has write permissions for the user BUILTIN\\Users. Expected write permissions to be removed by the installer.");
}
}
finally
{
UninstallMsi(installerPath);
}

Log.Information($"BUILTIN\\Users do not have write access to {destination}. Hooray!");
}

void InstallMsi(AbsolutePath installerPath, AbsolutePath destination)
{
var installLogName = Path.Combine(TestDirectory, $"{GetTestName(installerPath)}.install.log");

Log.Information($"Installing {installerPath} to {destination}");

var arguments = $"/i {installerPath} /QN INSTALLLOCATION={destination} /L*V {installLogName}";
Log.Information($"Running msiexec {arguments}");
var installationProcess = ProcessTasks.StartProcess("msiexec", arguments);
installationProcess.WaitForExit();
FileSystemTasks.CopyFileToDirectory(installLogName, ArtifactsDirectory);
if (installationProcess.ExitCode != 0) {
throw new Exception($"The installation process exited with a non-zero exit code ({installationProcess.ExitCode}). Check the log {installLogName} for details.");
}
}

void UninstallMsi(AbsolutePath installerPath)
{
Log.Information($"Uninstalling {installerPath}");
var uninstallLogName = Path.Combine(TestDirectory, $"{GetTestName(installerPath)}.uninstall.log");

var arguments = $"/x {installerPath} /QN /L*V {uninstallLogName}";
Log.Information($"Running msiexec {arguments}");
var uninstallProcess = ProcessTasks.StartProcess("msiexec", arguments);
uninstallProcess.WaitForExit();
FileSystemTasks.CopyFileToDirectory(uninstallLogName, ArtifactsDirectory);
}

bool DoesSidHaveRightsToDirectory(string directory, WellKnownSidType sid, params FileSystemRights[] rights)
{
var destinationInfo = new DirectoryInfo(directory);
var acl = destinationInfo.GetAccessControl();
var identifier = new SecurityIdentifier(sid, null);
return acl
.GetAccessRules(true, true, typeof(SecurityIdentifier))
.Cast<FileSystemAccessRule>()
.Where(r => r.IdentityReference.Value == identifier.Value)
.Where(r => r.AccessControlType == AccessControlType.Allow)
.Any(r => rights.Any(right => r.FileSystemRights.HasFlag(right)));
}

Logging.InTest(nameof(TestWindowsInstallerPermissions), () =>
Logging.InTest(nameof(TestWindowsInstaller), () =>
{
FileSystemTasks.EnsureExistingDirectory(TestDirectory);
FileSystemTasks.EnsureCleanDirectory(TestDirectory);
Expand All @@ -194,10 +129,134 @@ bool DoesSidHaveRightsToDirectory(string directory, WellKnownSidType sid, params

foreach (var installer in installers)
{
TestInstallerPermissions(installer);
TestWindowsInstaller(installer);
}
});
});

string GetTestName(AbsolutePath installerPath) => Path.GetFileName(installerPath).Replace(".msi", "");

void TestWindowsInstaller(AbsolutePath installerPath)
{
Log.Information($"\n--------------------------------------\nTesting Installer {GetTestName(installerPath)}\n--------------------------------------");

var destination = TestDirectory / "install" / GetTestName(installerPath);
FileSystemTasks.EnsureExistingDirectory(destination);

InstallMsi(installerPath, destination);

try
{
ThenTentacleShouldHaveBeenInstalled(destination);
ThenTentacleShouldBeRunnable(destination);
ThenBuiltInUserShouldNotHaveWritePermissions(destination);
}
finally
{
UninstallMsi(installerPath);
}
}

void ThenTentacleShouldHaveBeenInstalled(string destination)
{
var expectedTentacleExe = Path.Combine(destination, "Tentacle.exe");

if (!File.Exists(expectedTentacleExe))
{
throw new Exception($"Tentacle was not installed at {expectedTentacleExe}");
}
Log.Information($"Tentacle was successfully installed at {expectedTentacleExe}");
}

void ThenTentacleShouldBeRunnable(string destination)
{
var tentacleExe = Path.Combine(destination, "Tentacle.exe");

Log.Information("Checking if Tentacle can run successfully...");

using var tentacleProcess = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = tentacleExe,
Arguments = "help",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true
}
};

tentacleProcess.Start();
var output = new List<string>();
while (!tentacleProcess.StandardOutput.EndOfStream)
{
output.Add(tentacleProcess.StandardOutput.ReadLine() ?? "");
}

if (tentacleProcess.ExitCode != 0)
{
throw new Exception($"Tentacle exited with exit code {tentacleProcess.ExitCode}");
}

if (!output.Any(o => o.StartsWith("Usage: Tentacle")))
{
throw new Exception($"Tentacle output does not look like '{tentacleProcess.StartInfo.Arguments}' argument was provided");
}

Log.Information($"Tentacle successfully ran using '{tentacleProcess.StartInfo.Arguments}' argument");
}

void ThenBuiltInUserShouldNotHaveWritePermissions(string destination)
{
var builtInUsersHaveWriteAccess = DoesSidHaveRightsToDirectory(destination, WellKnownSidType.BuiltinUsersSid, FileSystemRights.AppendData, FileSystemRights.CreateFiles);
if (builtInUsersHaveWriteAccess)
{
throw new Exception($"The installation destination {destination} has write permissions for the user BUILTIN\\Users. Expected write permissions to be removed by the installer.");
}
Log.Information($"BUILTIN\\Users do not have write access to {destination}. Hooray!");
}

void InstallMsi(AbsolutePath installerPath, AbsolutePath destination)
{
var installLogName = Path.Combine(TestDirectory, $"{GetTestName(installerPath)}.install.log");

Log.Information($"Installing {installerPath} to {destination}");

var arguments = $"/i {installerPath} /QN INSTALLLOCATION={destination} /L*V {installLogName}";
Log.Information($"Running msiexec {arguments}");
var installationProcess = ProcessTasks.StartProcess("msiexec", arguments);
installationProcess.WaitForExit();

FileSystemTasks.CopyFileToDirectory(installLogName, ArtifactsDirectory, FileExistsPolicy.Overwrite);
if (installationProcess.ExitCode != 0) {
throw new Exception($"The installation process exited with a non-zero exit code ({installationProcess.ExitCode}). Check the log {installLogName} for details.");
}
}

void UninstallMsi(AbsolutePath installerPath)
{
Log.Information($"Uninstalling {installerPath}");
var uninstallLogName = Path.Combine(TestDirectory, $"{GetTestName(installerPath)}.uninstall.log");

var arguments = $"/x {installerPath} /QN /L*V {uninstallLogName}";
Log.Information($"Running msiexec {arguments}");
var uninstallProcess = ProcessTasks.StartProcess("msiexec", arguments);
uninstallProcess.WaitForExit();
FileSystemTasks.CopyFileToDirectory(uninstallLogName, ArtifactsDirectory, FileExistsPolicy.Overwrite);
}

bool DoesSidHaveRightsToDirectory(string directory, WellKnownSidType sid, params FileSystemRights[] rights)
{
var destinationInfo = new DirectoryInfo(directory);
var acl = destinationInfo.GetAccessControl();
var identifier = new SecurityIdentifier(sid, null);
return acl
.GetAccessRules(true, true, typeof(SecurityIdentifier))
.Cast<FileSystemAccessRule>()
.Where(r => r.IdentityReference.Value == identifier.Value)
.Where(r => r.AccessControlType == AccessControlType.Allow)
.Any(r => rights.Any(right => r.FileSystemRights.HasFlag(right)));
}

void RunTests(string testFramework, string testRuntime)
{
Expand Down
2 changes: 1 addition & 1 deletion installer/Octopus.Tentacle.Installer/Product.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
- Product/Version : Change this every major build
- Product/UpgradeCode : Never change this
-->
<Product Id="*" Name="Octopus Deploy Tentacle" Language="1033" Version="7.0.178" Manufacturer="Octopus Deploy Pty. Ltd." UpgradeCode="1B32E04F-49C2-4907-8879-A556986F7F16">
<Product Id="*" Name="Octopus Deploy Tentacle" Language="1033" Version="7.0.201" Manufacturer="Octopus Deploy Pty. Ltd." UpgradeCode="1B32E04F-49C2-4907-8879-A556986F7F16">
<Package InstallerVersion="200" Compressed="yes" Description="Octopus Deploy Tentacle" Platform="$(var.Platform)" InstallScope="perMachine" />
<Media Id="1" Cabinet="Files.cab" EmbedCab="yes" />
<Property Id="MSIFASTINSTALL" Value="3" />
Expand Down

0 comments on commit 85a652a

Please sign in to comment.