diff --git a/build/_build.csproj b/build/_build.csproj index 8862ec4eb..6819c0d13 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -19,19 +19,19 @@ - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive - - + + - + diff --git a/source/Calamari.Common/Features/Processes/Semaphores/FileBasedSempahoreManager.cs b/source/Calamari.Common/Features/Processes/Semaphores/FileBasedSempahoreManager.cs deleted file mode 100644 index 295d9c6f9..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/FileBasedSempahoreManager.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using Calamari.Common.Plumbing.Logging; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - //Originally based on https://github.com/markedup-mobi/file-lock (MIT license) - public class FileBasedSempahoreManager : ISemaphoreFactory - { - readonly ILog log; - readonly ICreateSemaphores semaphoreCreator; - readonly int initialWaitBeforeShowingLogMessage; - - public FileBasedSempahoreManager() - { - log = ConsoleLog.Instance; - initialWaitBeforeShowingLogMessage = (int)TimeSpan.FromSeconds(3).TotalMilliseconds; - semaphoreCreator = new LockFileBasedSemaphoreCreator(log); - } - - public FileBasedSempahoreManager(ILog log, TimeSpan initialWaitBeforeShowingLogMessage, ICreateSemaphores semaphoreCreator) - { - this.log = log; - this.semaphoreCreator = semaphoreCreator; - this.initialWaitBeforeShowingLogMessage = (int)initialWaitBeforeShowingLogMessage.TotalMilliseconds; - } - - public IDisposable Acquire(string name, string waitMessage) - { - var semaphore = semaphoreCreator.Create(name, TimeSpan.FromMinutes(2)); - - if (!semaphore.WaitOne(initialWaitBeforeShowingLogMessage)) - { - log.Verbose(waitMessage); - semaphore.WaitOne(); - } - - return new LockFileBasedSemaphoreReleaser(semaphore); - } - - class LockFileBasedSemaphoreReleaser : IDisposable - { - readonly ISemaphore semaphore; - - public LockFileBasedSemaphoreReleaser(ISemaphore semaphore) - { - this.semaphore = semaphore; - } - - public void Dispose() - { - semaphore.ReleaseLock(); - } - } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/FileLock.cs b/source/Calamari.Common/Features/Processes/Semaphores/FileLock.cs deleted file mode 100644 index 814e0f1c7..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/FileLock.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Diagnostics; -using System.Runtime.Serialization; -using System.Threading; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public interface IFileLock - {} - - [DataContract] - public class FileLock : IFileLock - { - public FileLock(long processId, string processName, int threadId, long timestamp) - { - ProcessId = processId; - ProcessName = processName; - ThreadId = threadId; - Timestamp = timestamp; - } - - [DataMember] - public long ProcessId { get; } - - [DataMember] - public long Timestamp { get; internal set; } - - [DataMember] - public string ProcessName { get; } - - [DataMember] - public int ThreadId { get; } - - protected bool Equals(FileLock other) - { - return ProcessId == other.ProcessId && - string.Equals(ProcessName, other.ProcessName) && - ThreadId == other.ThreadId; - } - - public override bool Equals(object? obj) - { - if (ReferenceEquals(null, obj)) - return false; - if (ReferenceEquals(this, obj)) - return true; - if (obj.GetType() != GetType()) - return false; - return Equals((FileLock)obj); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = ProcessId.GetHashCode(); - hashCode = (hashCode * 397) ^ (ProcessName?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ ThreadId; - return hashCode; - } - } - - public bool BelongsToCurrentProcessAndThread() - { - return ProcessId == Process.GetCurrentProcess().Id && ThreadId == Thread.CurrentThread.ManagedThreadId; - } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/ICreateSemaphores.cs b/source/Calamari.Common/Features/Processes/Semaphores/ICreateSemaphores.cs deleted file mode 100644 index 213e6b469..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/ICreateSemaphores.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public interface ICreateSemaphores - { - ISemaphore Create(string name, TimeSpan lockTimeout); - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/ILockIo.cs b/source/Calamari.Common/Features/Processes/Semaphores/ILockIo.cs deleted file mode 100644 index f9c022744..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/ILockIo.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public interface ILockIo - { - string GetFilePath(string lockName); - bool LockExists(string lockFilePath); - IFileLock ReadLock(string lockFilePath); - bool WriteLock(string lockFilePath, FileLock fileLock); - void DeleteLock(string lockFilePath); - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/IProcessFinder.cs b/source/Calamari.Common/Features/Processes/Semaphores/IProcessFinder.cs deleted file mode 100644 index 5e2efbe52..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/IProcessFinder.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public interface IProcessFinder - { - bool ProcessIsRunning(int processId, string processName); - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/LockFileBasedSemaphore.cs b/source/Calamari.Common/Features/Processes/Semaphores/LockFileBasedSemaphore.cs deleted file mode 100644 index c9ee6b48f..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/LockFileBasedSemaphore.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using Calamari.Common.Plumbing.FileSystem; -using Calamari.Common.Plumbing.Logging; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public class LockFileBasedSemaphore : ISemaphore - { - public enum AcquireLockAction - { - DontAcquireLock, - AcquireLock, - ForciblyAcquireLock - } - - readonly ILockIo lockIo; - readonly IProcessFinder processFinder; - readonly ILog log; - - public LockFileBasedSemaphore(string name, TimeSpan lockTimeout, ILog log) - : this(name, - lockTimeout, - new LockIo(CalamariPhysicalFileSystem.GetPhysicalFileSystem()), - new ProcessFinder(), - log) - { - } - - public LockFileBasedSemaphore(string name, - TimeSpan lockTimeout, - ILockIo lockIo, - IProcessFinder processFinder, - ILog log) - { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException(nameof(name), "name cannot be null or emtpy."); - - Name = name; - LockTimeout = lockTimeout; - LockFilePath = lockIo.GetFilePath(name); - this.lockIo = lockIo; - this.processFinder = processFinder; - this.log = log; - } - - TimeSpan LockTimeout { get; } - - public string Name { get; } - - string LockFilePath { get; } - - public AcquireLockAction ShouldAcquireLock(IFileLock fileLock) - { - if (lockIo.LockExists(LockFilePath)) - { - //Someone else owns the lock - if (fileLock is OtherProcessHasExclusiveLockOnFileLock) - //we couldn't read the file as some other process has it open exlusively - return AcquireLockAction.DontAcquireLock; - - if (fileLock is UnableToDeserialiseLockFile nonDeserialisedLockFile) - { - if ((DateTime.Now - nonDeserialisedLockFile.CreationTime).TotalSeconds > LockTimeout.TotalSeconds) - { - log.Warn("Lock file existed but was not readable, and has existed for longer than lock timeout. Taking lock."); - return AcquireLockAction.AcquireLock; - } - - return AcquireLockAction.DontAcquireLock; - } - - //the file no longer exists - if (fileLock is MissingFileLock) - return AcquireLockAction.AcquireLock; - - var concreteFileLock = fileLock as FileLock; - if (concreteFileLock == null) - return AcquireLockAction.AcquireLock; - - //This lock belongs to this process - we can reacquire the lock - if (concreteFileLock.BelongsToCurrentProcessAndThread()) - return AcquireLockAction.AcquireLock; - - if (!processFinder.ProcessIsRunning((int)concreteFileLock.ProcessId, concreteFileLock.ProcessName ?? "")) - { - log.Warn($"Process {concreteFileLock.ProcessId}, thread {concreteFileLock.ThreadId} had lock, but appears to have crashed. Taking lock."); - - return AcquireLockAction.AcquireLock; - } - - var lockWriteTime = new DateTime(concreteFileLock.Timestamp); - //The lock has not timed out - we can't acquire it - if (!(Math.Abs((DateTime.Now - lockWriteTime).TotalSeconds) > LockTimeout.TotalSeconds)) - return AcquireLockAction.DontAcquireLock; - - log.Warn($"Forcibly taking lock from process {concreteFileLock.ProcessId}, thread {concreteFileLock.ThreadId} as lock has timed out. If this happens regularly, please contact Octopus Support."); - - return AcquireLockAction.ForciblyAcquireLock; - } - - return AcquireLockAction.AcquireLock; - } - - public bool TryAcquireLock() - { - var lockContent = lockIo.ReadLock(LockFilePath); - var response = ShouldAcquireLock(lockContent); - if (response == AcquireLockAction.ForciblyAcquireLock) - lockIo.DeleteLock(LockFilePath); - - if (response == AcquireLockAction.AcquireLock || response == AcquireLockAction.ForciblyAcquireLock) - return lockIo.WriteLock(LockFilePath, CreateLockContent()); - - return false; - } - - public void ReleaseLock() - { - //Need to own the lock in order to release it (and we can reacquire the lock inside the current process) - if (lockIo.LockExists(LockFilePath) && TryAcquireLock()) - lockIo.DeleteLock(LockFilePath); - } - - static FileLock CreateLockContent() - { - var process = Process.GetCurrentProcess(); - return new FileLock(process.Id, - process.ProcessName, - Thread.CurrentThread.ManagedThreadId, - DateTime.Now.Ticks); - } - - public bool WaitOne(int millisecondsToWait) - { - var stopwatch = new Stopwatch(); - stopwatch.Start(); - while (true) - { - if (TryAcquireLock()) - return true; - if (stopwatch.ElapsedMilliseconds > millisecondsToWait) - return false; - Thread.Sleep(100); - } - } - - public bool WaitOne() - { - while (true) - { - if (TryAcquireLock()) - return true; - Thread.Sleep(100); - } - } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/LockFileBasedSemaphoreCreator.cs b/source/Calamari.Common/Features/Processes/Semaphores/LockFileBasedSemaphoreCreator.cs deleted file mode 100644 index 083750b49..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/LockFileBasedSemaphoreCreator.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Calamari.Common.Plumbing.Logging; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public class LockFileBasedSemaphoreCreator : ICreateSemaphores - { - readonly ILog log; - - public LockFileBasedSemaphoreCreator(ILog log) - { - this.log = log; - } - - public ISemaphore Create(string name, TimeSpan lockTimeout) - { - return new LockFileBasedSemaphore(name, lockTimeout, log); - } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/LockIo.cs b/source/Calamari.Common/Features/Processes/Semaphores/LockIo.cs deleted file mode 100644 index 03a4b3b50..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/LockIo.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using System.IO; -using Calamari.Common.Plumbing.FileSystem; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public class LockIo : ILockIo - { - readonly ICalamariFileSystem fileSystem; - - public LockIo(ICalamariFileSystem fileSystem) - { - this.fileSystem = fileSystem; - } - - public string GetFilePath(string lockName) - { - return Path.Combine(Path.GetTempPath(), lockName + ".lck"); - } - - public bool LockExists(string lockFilePath) - { - return fileSystem.FileExists(lockFilePath); - } - - public IFileLock ReadLock(string lockFilePath) - { - try - { - using (var stream = fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read)) - { - using (var streamReader = new StreamReader(stream)) - { - var obj = JObject.Load(new JsonTextReader(streamReader)); - var lockContent = new FileLock(obj["ProcessId"].ToObject(), - obj["ProcessName"].ToString(), - obj["ThreadId"].ToObject(), - obj["Timestamp"].ToObject()); - if (lockContent.BelongsToCurrentProcessAndThread()) - return lockContent; - return new OtherProcessOwnsFileLock(lockContent); - } - } - } - catch (FileNotFoundException) - { - return new MissingFileLock(); - } - catch (IOException) - { - return new OtherProcessHasExclusiveLockOnFileLock(); - } - catch (JsonReaderException) - { - return new UnableToDeserialiseLockFile(fileSystem.GetCreationTime(lockFilePath)); - } - catch (Exception) //We have no idea what went wrong - reacquire this lock - { - return new OtherProcessHasExclusiveLockOnFileLock(); - } - } - - public bool WriteLock(string lockFilePath, FileLock fileLock) - { - try - { - var fileMode = FileMode.CreateNew; - if (LockExists(lockFilePath)) - { - var currentContent = ReadLock(lockFilePath); - if (Equals(currentContent, fileLock)) - { - if ((currentContent as FileLock)?.Timestamp == fileLock.Timestamp) - return true; - fileMode = FileMode.Create; - } - else if (currentContent.GetType() == typeof(UnableToDeserialiseLockFile)) - { - DeleteLock(lockFilePath); - } - } - - var obj = new JObject - { - ["ProcessId"] = fileLock.ProcessId, - ["ThreadId"] = fileLock.ThreadId, - ["ProcessName"] = fileLock.ProcessName, - ["Timestamp"] = fileLock.Timestamp - }; - using (var stream = fileSystem.OpenFileExclusively(lockFilePath, fileMode, FileAccess.Write)) - { - using (var streamWriter = new StreamWriter(stream)) - { - obj.WriteTo(new JsonTextWriter(streamWriter)); - } - } - - var writtenContent = ReadLock(lockFilePath); - return Equals(writtenContent, fileLock); - } - catch (Exception) - { - return false; - } - } - - public void DeleteLock(string lockFilePath) - { - try - { - fileSystem.DeleteFile(lockFilePath); - } - catch (Exception) - { - // ignored - handled in create - } - } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/MissingFileLock.cs b/source/Calamari.Common/Features/Processes/Semaphores/MissingFileLock.cs deleted file mode 100644 index 5218911bb..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/MissingFileLock.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public class MissingFileLock : IFileLock - { - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/OtherProcessHasExclusiveLockOnFileLock.cs b/source/Calamari.Common/Features/Processes/Semaphores/OtherProcessHasExclusiveLockOnFileLock.cs deleted file mode 100644 index e54a6598d..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/OtherProcessHasExclusiveLockOnFileLock.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public class OtherProcessHasExclusiveLockOnFileLock : IFileLock - { - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/OtherProcessOwnsFileLock.cs b/source/Calamari.Common/Features/Processes/Semaphores/OtherProcessOwnsFileLock.cs deleted file mode 100644 index af9592e07..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/OtherProcessOwnsFileLock.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public class OtherProcessOwnsFileLock : FileLock - { - public OtherProcessOwnsFileLock(FileLock fileLock) : base(fileLock.ProcessId, fileLock.ProcessName, fileLock.ThreadId, fileLock.Timestamp) - { - } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/ProcessFinder.cs b/source/Calamari.Common/Features/Processes/Semaphores/ProcessFinder.cs deleted file mode 100644 index 897c43158..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/ProcessFinder.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using Calamari.Common.Plumbing; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public class ProcessFinder : IProcessFinder - { - public bool ProcessIsRunning(int processId, string processName) - { - // borrowed from https://github.com/sillsdev/libpalaso/blob/7c7e5eed0a3d9c8a961b01887cbdebbf1b63b899/SIL.Core/IO/FileLock/SimpleFileLock.cs (Apache 2.0 license) - Process process; - try - { - // First, look for a process with this processId - process = Process.GetProcesses().FirstOrDefault(x => x.Id == processId); - } - catch (NotSupportedException) - { - //FreeBSD does not support EnumProcesses - //assume that the process is running - return true; - } - - // If there is no process with this processId, it is not running. - if (process == null) - return false; - - // Next, check for a match on processName. - var isRunning = process.ProcessName == processName; - - // If a match was found or this is running on Windows, this is as far as we need to go. - if (isRunning || CalamariEnvironment.IsRunningOnWindows) - return isRunning; - - // We need to look a little deeper on Linux. - - // If the name of the process is not "mono" or does not start with "mono-", this is not - // a mono application, and therefore this is not the process we are looking for. - if (process.ProcessName.ToLower() != "mono" && !process.ProcessName.ToLower().StartsWith("mono-")) - return false; - - // The mono application will have a module with the same name as the process, with ".exe" added. - var moduleName = processName.ToLower() + ".exe"; - return process.Modules.Cast().Any(mod => mod.ModuleName.ToLower() == moduleName); - } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/SemaphoreFactory.cs b/source/Calamari.Common/Features/Processes/Semaphores/SemaphoreFactory.cs deleted file mode 100644 index 76bcf18de..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/SemaphoreFactory.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using Calamari.Common.Plumbing; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public static class SemaphoreFactory - { - public static ISemaphoreFactory Get() - { - if (CalamariEnvironment.IsRunningOnMono) - return new FileBasedSempahoreManager(); - return new SystemSemaphoreManager(); - } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Processes/Semaphores/UnableToDeserialiseLockFile.cs b/source/Calamari.Common/Features/Processes/Semaphores/UnableToDeserialiseLockFile.cs deleted file mode 100644 index a3d59df7a..000000000 --- a/source/Calamari.Common/Features/Processes/Semaphores/UnableToDeserialiseLockFile.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace Calamari.Common.Features.Processes.Semaphores -{ - public class UnableToDeserialiseLockFile : IFileLock - { - public UnableToDeserialiseLockFile(DateTime creationTime) - { - CreationTime = creationTime; - } - - public DateTime CreationTime { get; } - } -} \ No newline at end of file diff --git a/source/Calamari.Common/Features/Scripting/ScriptExecutor.cs b/source/Calamari.Common/Features/Scripting/ScriptExecutor.cs index afbfac317..4d6268741 100644 --- a/source/Calamari.Common/Features/Scripting/ScriptExecutor.cs +++ b/source/Calamari.Common/Features/Scripting/ScriptExecutor.cs @@ -36,7 +36,7 @@ public CommandResult Execute(Script script, try { if (execution.CommandLineInvocation.Isolate) - using (SemaphoreFactory.Get() + using (new SystemSemaphoreManager() .Acquire("CalamariSynchronizeProcess", "Waiting for other process to finish executing script")) { diff --git a/source/Calamari.Common/Plumbing/CalamariEnvironment.cs b/source/Calamari.Common/Plumbing/CalamariEnvironment.cs index 5b801ae6d..9ea1cf9b9 100644 --- a/source/Calamari.Common/Plumbing/CalamariEnvironment.cs +++ b/source/Calamari.Common/Plumbing/CalamariEnvironment.cs @@ -5,13 +5,6 @@ namespace Calamari.Common.Plumbing { public static class CalamariEnvironment { - /// - /// If/When we try executing this on another runtime we can 'tweak' this logic. - /// The reccomended way to determine at runtime if executing within the mono framework - /// http://www.mono-project.com/docs/gui/winforms/porting-winforms-applications/#runtime-conditionals - /// - public static readonly bool IsRunningOnMono = Type.GetType("Mono.Runtime") != null; - public static bool IsRunningOnKubernetes => !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(KubernetesEnvironmentVariables.KubernetesAgentNamespace)); diff --git a/source/Calamari.Common/Plumbing/Deployment/Journal/DeploymentJournalWriter.cs b/source/Calamari.Common/Plumbing/Deployment/Journal/DeploymentJournalWriter.cs index 3f248e01a..414dc4b35 100644 --- a/source/Calamari.Common/Plumbing/Deployment/Journal/DeploymentJournalWriter.cs +++ b/source/Calamari.Common/Plumbing/Deployment/Journal/DeploymentJournalWriter.cs @@ -28,7 +28,7 @@ public void AddJournalEntry(RunningDeployment deployment, bool wasSuccessful, st { if (deployment.SkipJournal) return; - var semaphore = SemaphoreFactory.Get(); + var semaphore = new SystemSemaphoreManager(); var journal = new DeploymentJournal(fileSystem, semaphore, deployment.Variables); var hasPackages = !string.IsNullOrWhiteSpace(packageFile) || deployment.Variables.GetIndexes(PackageVariables.PackageCollection).Any(); diff --git a/source/Calamari.Common/Plumbing/Extensions/ApplicationDirectory.cs b/source/Calamari.Common/Plumbing/Extensions/ApplicationDirectory.cs index 863d69a6e..3cd256b65 100644 --- a/source/Calamari.Common/Plumbing/Extensions/ApplicationDirectory.cs +++ b/source/Calamari.Common/Plumbing/Extensions/ApplicationDirectory.cs @@ -9,7 +9,7 @@ namespace Calamari.Common.Plumbing.Extensions { public class ApplicationDirectory { - static readonly ISemaphoreFactory Semaphore = SemaphoreFactory.Get(); + static readonly ISemaphoreFactory Semaphore = new SystemSemaphoreManager(); /// /// Returns the directory where the package will be installed. diff --git a/source/Calamari.Common/Plumbing/Extensions/EnvironmentHelper.cs b/source/Calamari.Common/Plumbing/Extensions/EnvironmentHelper.cs index e4ddfec90..9bfb271ce 100644 --- a/source/Calamari.Common/Plumbing/Extensions/EnvironmentHelper.cs +++ b/source/Calamari.Common/Plumbing/Extensions/EnvironmentHelper.cs @@ -42,8 +42,6 @@ static IEnumerable GetEnvironmentVars() yield return SafelyGet(() => $"OperatingSystem: {Environment.OSVersion}"); yield return SafelyGet(() => $"OsBitVersion: {(Environment.Is64BitOperatingSystem ? "x64" : "x86")}"); yield return SafelyGet(() => $"Is64BitProcess: {Environment.Is64BitProcess}"); - if (CalamariEnvironment.IsRunningOnNix || CalamariEnvironment.IsRunningOnMac) - yield return SafelyGet(() => $"Running on Mono: {CalamariEnvironment.IsRunningOnMono}"); if (CalamariEnvironment.IsRunningOnWindows) yield return SafelyGet(() => $"CurrentUser: {WindowsIdentity.GetCurrent().Name}"); else diff --git a/source/Calamari.Common/Plumbing/FileSystem/FreeSpaceChecker.cs b/source/Calamari.Common/Plumbing/FileSystem/FreeSpaceChecker.cs index 2e98dcc14..e34d01911 100644 --- a/source/Calamari.Common/Plumbing/FileSystem/FreeSpaceChecker.cs +++ b/source/Calamari.Common/Plumbing/FileSystem/FreeSpaceChecker.cs @@ -42,12 +42,6 @@ ulong GetRequiredSpaceInBytes(out bool spaceOverrideSpecified) public ulong GetSpaceRequiredToBeFreed(string directoryPath) { - if (CalamariEnvironment.IsRunningOnMono && CalamariEnvironment.IsRunningOnMac) - { - Log.Verbose("Unable to determine disk free space under Mono on macOS. Assuming there's enough."); - return 0; - } - var requiredSpaceInBytes = GetRequiredSpaceInBytes(out var spaceOverrideSpecified); if (spaceOverrideSpecified) @@ -70,15 +64,6 @@ public ulong GetSpaceRequiredToBeFreed(string directoryPath) public void EnsureDiskHasEnoughFreeSpace(string directoryPath) { - if (CalamariEnvironment.IsRunningOnMono && CalamariEnvironment.IsRunningOnMac) - { - //After upgrading to macOS 10.15.2, and mono 5.14.0, drive.TotalFreeSpace and drive.AvailableFreeSpace both started returning 0. - //see https://github.com/mono/mono/issues/17151, which was fixed in mono 6.4.xx - //Rock and a hard place. - Log.Verbose("Unable to determine disk free space under Mono on macOS. Assuming there's enough."); - return; - } - if (variables.GetFlag(SkipFreeDiskSpaceCheckVariable)) { Log.Verbose($"{SkipFreeDiskSpaceCheckVariable} is enabled. The check to ensure that the drive containing the directory '{directoryPath}' on machine '{Environment.MachineName}' has enough free space will be skipped."); diff --git a/source/Calamari.Common/Plumbing/Variables/DeploymentJournalVariableContributor.cs b/source/Calamari.Common/Plumbing/Variables/DeploymentJournalVariableContributor.cs index 5cf5e67ba..35a16ffdf 100644 --- a/source/Calamari.Common/Plumbing/Variables/DeploymentJournalVariableContributor.cs +++ b/source/Calamari.Common/Plumbing/Variables/DeploymentJournalVariableContributor.cs @@ -14,7 +14,7 @@ public static void Contribute(ICalamariFileSystem fileSystem, IVariables variabl if (string.IsNullOrWhiteSpace(policySet)) return; - var journal = new DeploymentJournal(fileSystem, SemaphoreFactory.Get(), variables); + var journal = new DeploymentJournal(fileSystem, new SystemSemaphoreManager(), variables); Previous(variables, journal, policySet); PreviousSuccessful(variables, journal, policySet); } diff --git a/source/Calamari.FullFrameworkTools.Tests/Command/CommandRequestInvokerTests.cs b/source/Calamari.FullFrameworkTools.Tests/Command/CommandRequestInvokerTests.cs index 80775e5f7..4a4ef4811 100644 --- a/source/Calamari.FullFrameworkTools.Tests/Command/CommandRequestInvokerTests.cs +++ b/source/Calamari.FullFrameworkTools.Tests/Command/CommandRequestInvokerTests.cs @@ -87,133 +87,4 @@ public MockRequest(int numberOne, int numberTwo) public int NumberOne { get; } public int NumberTwo { get; } } - /*public class CommandRequestInvokerTests - { - readonly ICommandLocator commandLocator; - - public CommandRequestInvokerTests() - { - commandLocator = Substitute.For(); - } - - [Test] - public void SimpleHandlerReturnsSerializedResult() - { - var handler = new TestAdditionCommandHandler(); - commandLocator.GetCommand(Arg.Any()).Returns(handler); - var requestObject = new TestAdditionRequest(4, 5); - var jsonRequestObj = JsonSerializer.Serialize(requestObject, new JsonSerializerOptions()); - - var invoker = new CommandRequestInvoker(commandLocator); - var response = invoker.Run("TestCommand", jsonRequestObj); - - response.Should().BeEquivalentTo(new TestAdditionResponse(9)); - } - - [Test] - public void SimpleHandlerEncrypted() - { - var handler = new TestAdditionCommandHandler(); - commandLocator.GetCommand(Arg.Any()).Returns(handler); - var requestObject = new TestAdditionRequest(4, 5); - var jsonRequestObj = JsonSerializer.Serialize(requestObject, new JsonSerializerOptions()); - - var password = "pass23HJka"; - var enc = new AesEncryption(password); - var encRequestObj = enc.Encrypt(jsonRequestObj); - - using (var temp = new TempFile(encRequestObj)) - { - var invoker = new CommandRequestInvoker(commandLocator); - var response = invoker.Run("TestCommand", password, temp.FilePath); - - response.Should().BeEquivalentTo(new TestAdditionResponse(9)); - } - } - - public class TempFile : IDisposable - { - public TempFile(byte[] content) - { - FilePath = Path.GetTempFileName(); - File.WriteAllBytes(FilePath, content); - } - - public string FilePath { get; } - - public void Dispose() - { - try - { - File.Delete(FilePath); - } - catch - { - // ignored - } - } - } - - public class TestLog : ILog - { - public List Errors { get; } = new List(); - public List Infos { get; } = new List(); - - public List Fatals { get; } = new List(); - - public void Verbose(string message) - { - throw new NotImplementedException(); - } - - public void Error(string value) - { - Errors.Add(value); - } - - public void Info(string value) - { - Infos.Add(value); - } - - public void Fatal(Exception exception) - { - Fatals.Add(exception); - } - - public void Result(object response) - { - } - } - - class TestAdditionCommandHandler : FullFrameworkToolCommandHandler - { - protected override TestAdditionResponse Handle(TestAdditionRequest additionRequest) - { - return new TestAdditionResponse(additionRequest.NumberOne + additionRequest.NumberTwo); - } - - } - - class TestAdditionRequest: IFullFrameworkToolRequest - { - public int NumberOne { get; } - public int NumberTwo { get; } - - public TestAdditionRequest(int numberOne, int numberTwo) - { - NumberOne = numberOne; - NumberTwo = numberTwo; - } - } - class TestAdditionResponse: IFullFrameworkToolResponse - { - public int Result { get; } - - public TestAdditionResponse(int result) - d { - Result = result; - } - } - }*/ } \ No newline at end of file diff --git a/source/Calamari.FullFrameworkTools/Command/CommandLocator.cs b/source/Calamari.FullFrameworkTools/Command/CommandLocator.cs deleted file mode 100644 index f9d7af427..000000000 --- a/source/Calamari.FullFrameworkTools/Command/CommandLocator.cs +++ /dev/null @@ -1,47 +0,0 @@ -/*using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Calamari.FullFrameworkTools.Iis; - -namespace Calamari.FullFrameworkTools.Command -{ - public interface ICommandLocator - { - IFullFrameworkToolCommandHandler GetCommand(string name); - } - - public class CommandLocator: ICommandLocator - { - public IList Commands = new List() - { - new ImportCertificateToStoreHandler(), - new OverwriteHomeDirectoryHandler() - }; - - public IFullFrameworkToolCommandHandler GetCommand(string name) - { - return Commands.FirstOrDefault(t => t.GetType().Name.Equals($"{name}Handler")); - } - - - public IFullFrameworkToolCommandHandler GetCommand() - { - return Commands.FirstOrDefault(t => GetAllTypes(t.GetType()).Contains(typeof(THandler))); - } - - public IEnumerable GetAllTypes(Type type) - { - yield return type; - - if (type.BaseType == null) - yield break; - - yield return type.BaseType; - foreach (var b in GetAllTypes(type.BaseType)) - { - yield return b; - } - } - } -}*/ \ No newline at end of file diff --git a/source/Calamari.FullFrameworkTools/Iis/OverwriteHomeDirectoryHandler.cs b/source/Calamari.FullFrameworkTools/Iis/OverwriteHomeDirectoryHandler.cs deleted file mode 100644 index 2da1a56e7..000000000 --- a/source/Calamari.FullFrameworkTools/Iis/OverwriteHomeDirectoryHandler.cs +++ /dev/null @@ -1,34 +0,0 @@ -/*using System; -using Calamari.FullFrameworkTools.Command; - -namespace Calamari.FullFrameworkTools.Iis -{ - public class OverwriteHomeDirectoryHandler : FullFrameworkToolCommandHandler - { - public string Name { get; } - - protected override OverwriteHomeDirectoryResponse Handle(OverwriteHomeDirectoryRequest request) - { - var iis = new InternetInformationServer(); - var result = iis.OverwriteHomeDirectory(request.IisWebSiteName, request.Path, request.LegacySupport); - return new OverwriteHomeDirectoryResponse(result); - } - } - - public class OverwriteHomeDirectoryRequest : IFullFrameworkToolRequest - { - public string IisWebSiteName { get; set; } - public string Path { get; set; } - public bool LegacySupport { get; set; } - } - - public class OverwriteHomeDirectoryResponse : IFullFrameworkToolResponse - { - public OverwriteHomeDirectoryResponse(bool result) - { - Result = result; - } - - public bool Result { get; set; } - } -}*/ \ No newline at end of file diff --git a/source/Calamari.FullFrameworkTools/WindowsCertStore/PrivateKeyAccessRule.cs b/source/Calamari.FullFrameworkTools/WindowsCertStore/PrivateKeyAccessRule.cs index d37d1c8a7..8ba42d8bf 100644 --- a/source/Calamari.FullFrameworkTools/WindowsCertStore/PrivateKeyAccessRule.cs +++ b/source/Calamari.FullFrameworkTools/WindowsCertStore/PrivateKeyAccessRule.cs @@ -9,6 +9,7 @@ namespace Calamari.FullFrameworkTools.WindowsCertStore; public class PrivateKeyAccessRule { + [JsonConstructor] public PrivateKeyAccessRule(string identity, PrivateKeyAccess access) :this(new NTAccount(identity), access) { diff --git a/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs b/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs index 0bacce135..fde07eb9a 100644 --- a/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs +++ b/source/Calamari.Shared/Integration/Certificates/WindowsX509CertificateStore.cs @@ -20,7 +20,7 @@ namespace Calamari.Integration.Certificates { public class WindowsX509CertificateStore: IWindowsX509CertificateStore { - public static readonly ISemaphoreFactory Semaphores = SemaphoreFactory.Get(); + public static readonly ISemaphoreFactory Semaphores = new SystemSemaphoreManager(); public static readonly string SemaphoreName = nameof(WindowsX509CertificateStore); const string IntermediateAuthorityStoreName = "CA"; diff --git a/source/Calamari.Tests/Fixtures/Deployment/CleanFixture.cs b/source/Calamari.Tests/Fixtures/Deployment/CleanFixture.cs index 21540c0e5..2f7bdaac5 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/CleanFixture.cs +++ b/source/Calamari.Tests/Fixtures/Deployment/CleanFixture.cs @@ -43,7 +43,7 @@ public void SetUp() variables = new VariablesFactory(fileSystem).Create(new CommonOptions("test")); - deploymentJournal = new DeploymentJournal(fileSystem, SemaphoreFactory.Get(), variables); + deploymentJournal = new DeploymentJournal(fileSystem, new SystemSemaphoreManager(), variables); packagesDirectory = Path.Combine(Path.GetTempPath(), "CalamariTestPackages"); fileSystem.EnsureDirectoryExists(packagesDirectory); diff --git a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/FileBasedSempahoreManagerFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/FileBasedSempahoreManagerFixture.cs deleted file mode 100644 index fd3dd44c8..000000000 --- a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/FileBasedSempahoreManagerFixture.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using Calamari.Common.Features.Processes.Semaphores; -using Calamari.Common.Plumbing.Logging; -using Calamari.Testing.Helpers; -using Calamari.Tests.Helpers; -using FluentAssertions; -using NSubstitute; -using NUnit.Framework; - -namespace Calamari.Tests.Fixtures.Integration.Process.Semaphores -{ - [TestFixture] - public class FileBasedSempahoreManagerFixture : SemaphoreFixtureBase - { - [Test] - public void FileBasedSempahoreWaitsUntilFirstSemaphoreIsReleased() - { - SecondSemaphoreWaitsUntilFirstSemaphoreIsReleased(new FileBasedSempahoreManager()); - } - - [Test] - public void FileBasedSempahoreShouldIsolate() - { - ShouldIsolate(new FileBasedSempahoreManager()); - } - - [Test] - public void WritesMessageToLogIfCannotGetSemaphore() - { - var log = new InMemoryLog(); - var semaphoreCreator = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = Substitute.For(); - semaphore.WaitOne(200).Returns(false); - semaphoreCreator.Create(name, TimeSpan.FromSeconds(60)).Returns(semaphore); - var sempahoreManager = new FileBasedSempahoreManager(log, TimeSpan.FromMilliseconds(200), semaphoreCreator); - using (sempahoreManager.Acquire(name, "wait message")) { } - - log.Messages.Should().Contain(m => m.Level == InMemoryLog.Level.Verbose && m.FormattedMessage == "wait message"); - } - - [Test] - public void ReleasesLockOnDispose() - { - var semaphoreCreator = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = Substitute.For(); - semaphoreCreator.Create(name, TimeSpan.FromMinutes(2)).Returns(semaphore); - var sempahoreManager = new FileBasedSempahoreManager(Substitute.For(), TimeSpan.FromMilliseconds(200), semaphoreCreator); - using (sempahoreManager.Acquire(name, "wait message")) { } - semaphore.Received().ReleaseLock(); - } - - [Test] - public void AttemptsToGetLockWithTimeoutThenWaitsIndefinitely() - { - var semaphoreCreator = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = Substitute.For(); - semaphore.WaitOne(200).Returns(false); - semaphoreCreator.Create(name, TimeSpan.FromMinutes(2)).Returns(semaphore); - var sempahoreManager = new FileBasedSempahoreManager(Substitute.For(), TimeSpan.FromMilliseconds(200), semaphoreCreator); - using (sempahoreManager.Acquire(name, "wait message")) { } - semaphore.Received().WaitOne(200); - semaphore.Received().WaitOne(); - } - } -} \ No newline at end of file diff --git a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/FileLockFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/FileLockFixture.cs deleted file mode 100644 index bcdc22535..000000000 --- a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/FileLockFixture.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Threading; -using Calamari.Common.Features.Processes.Semaphores; -using NUnit.Framework; - -namespace Calamari.Tests.Fixtures.Integration.Process.Semaphores -{ - [TestFixture] - public class FileLockFixture - { - [Test] - [TestCase(123, 456, "ProcessName", 123, 456, "ProcessName", true)] - [TestCase(123, 456, "ProcessName", 789, 456, "ProcessName", false)] - [TestCase(123, 456, "ProcessName", 123, 789, "ProcessName", false)] - [TestCase(123, 456, "ProcessName", 123, 456, "DiffProcessName", false)] - [TestCase(123, 456, "ProcessName", 789, 246, "ProcessName", false)] - [TestCase(123, 456, "ProcessName", 123, 246, "DiffProcessName", false)] - [TestCase(123, 456, "ProcessName", 789, 456, "ProcessName", false)] - [TestCase(123, 456, "ProcessName", 789, 246, "DiffProcessName", false)] - public void EqualsComparesCorrectly(int processIdA, int threadIdA, string processNameA, int processIdB, int threadIdB, string processNameB, bool expectedResult) - { - var objectA = new FileLock(processIdA, processNameA, threadIdA, DateTime.Now.Ticks); - var objectB = new FileLock(processIdB, processNameB, threadIdB, DateTime.Now.Ticks); - Assert.That(Equals(objectA, objectB), Is.EqualTo(expectedResult)); - } - - [Test] - public void EqualsIgnoresTimestamp() - { - var objectA = new FileLock(123, "ProcessName", 456, DateTime.Now.Ticks); - var objectB = new FileLock(123, "ProcessName", 456, DateTime.Now.Ticks + 5); - Assert.That(Equals(objectA, objectB), Is.True); - } - - [Test] - public void EqualsReturnsFalseIfOtherObjectIsNull() - { - var fileLock = new FileLock(123, "ProcessName", 456, DateTime.Now.Ticks); - Assert.That(fileLock.Equals(null), Is.False); - } - - [Test] - public void EqualsReturnsFalseIfOtherObjectIsDifferentType() - { - var fileLock = new FileLock(123, "ProcessName", 456, DateTime.Now.Ticks); - Assert.That(fileLock.Equals(new object()), Is.False); - } - - [Test] - public void EqualsReturnsTrueIfSameObject() - { - var fileLock = new FileLock(123, "ProcessName", 456, DateTime.Now.Ticks); - // ReSharper disable once EqualExpressionComparison - Assert.That(fileLock.Equals(fileLock), Is.True); - } - - [Test] - public void BelongsToCurrentProcessAndThreadMatchesOnCurrentProcessAndThread() - { - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var fileLock = new FileLock( - currentProcess.Id, - currentProcess.ProcessName, - Thread.CurrentThread.ManagedThreadId, - DateTime.Now.Ticks - ); - Assert.That(fileLock.BelongsToCurrentProcessAndThread(), Is.True); - } - - [Test] - public void BelongsToCurrentProcessAndThreadReturnsFalseIfIncorrectProcessId() - { - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var fileLockWithIncorrectProcessId = new FileLock( - -100, - currentProcess.ProcessName, - Thread.CurrentThread.ManagedThreadId, - DateTime.Now.Ticks - ); - Assert.That(fileLockWithIncorrectProcessId.BelongsToCurrentProcessAndThread(), Is.False); - } - - [Test] - public void BelongsToCurrentProcessAndThreadReturnsFalseIfIncorrectThreadId() - { - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var fileLockWithIncorrectThreadId = new FileLock( - currentProcess.Id, - currentProcess.ProcessName, - -200, - DateTime.Now.Ticks - ); - Assert.That(fileLockWithIncorrectThreadId.BelongsToCurrentProcessAndThread(), Is.False); - } - - } -} diff --git a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockFileBasedSemaphoreCreatorFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockFileBasedSemaphoreCreatorFixture.cs deleted file mode 100644 index cd2e2f2b4..000000000 --- a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockFileBasedSemaphoreCreatorFixture.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Calamari.Common.Features.Processes.Semaphores; -using Calamari.Testing.Helpers; -using Calamari.Tests.Helpers; -using NUnit.Framework; - -namespace Calamari.Tests.Fixtures.Integration.Process.Semaphores -{ - [TestFixture] - public class LockFileBasedSemaphoreCreatorFixture - { - [Test] - public void ReturnsInstanceOfLockFileBasedSemaphore() - { - var creator = new LockFileBasedSemaphoreCreator(new InMemoryLog()); - var result = creator.Create("name", TimeSpan.FromSeconds(1)); - Assert.That(result, Is.InstanceOf()); - } - } -} diff --git a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockFileBasedSemaphoreFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockFileBasedSemaphoreFixture.cs deleted file mode 100644 index e07fd6fdb..000000000 --- a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockFileBasedSemaphoreFixture.cs +++ /dev/null @@ -1,282 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using Calamari.Common.Features.Processes.Semaphores; -using Calamari.Testing.Helpers; -using Calamari.Tests.Helpers; -using FluentAssertions; -using NSubstitute; -using NUnit.Framework; - -namespace Calamari.Tests.Fixtures.Integration.Process.Semaphores -{ - [TestFixture] - public class LockFileBasedSemaphoreFixture - { - [Test] - public void AttemptsToAcquireLockIfLockFileDoesNotExist() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.LockExists(Arg.Any()).Returns(false); - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.AcquireLock)); - } - - [Test] - public void DoesNotAttemptToAcquireLockIfLockFileIsLockedByOtherProcess() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new OtherProcessHasExclusiveLockOnFileLock(); - lockIo.LockExists(Arg.Any()).Returns(true); - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.DontAcquireLock)); - } - - [Test] - public void DoesNotAttemptToAcquireLockIfWeCantDeserialise() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new UnableToDeserialiseLockFile(DateTime.Now); - lockIo.LockExists(Arg.Any()).Returns(true); - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.DontAcquireLock)); - } - - [Test] - public void AttemptsToAcquireLockIfWeCantDeserialiseButFileIsOlderThanLockTimeout() - { - var log = new InMemoryLog(); - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), log); - var fileLock = new UnableToDeserialiseLockFile(DateTime.Now.Subtract(TimeSpan.FromMinutes(5))); - lockIo.LockExists(Arg.Any()).Returns(true); - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.AcquireLock)); - log.Messages.Should().Contain(m => m.Level == InMemoryLog.Level.Warn && m.FormattedMessage == "Lock file existed but was not readable, and has existed for longer than lock timeout. Taking lock."); - } - - [Test] - public void AttemptsToAcquireLockIfWeCantFindLockFile() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new MissingFileLock(); - //when we check for the lock file, it exists - lockIo.LockExists(Arg.Any()).Returns(true); - //but when we read it, its been released - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.AcquireLock)); - } - - [Test] - public void AttemptsToAcquireLockIfItBelongsToUs() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.LockExists(Arg.Any()).Returns(true); - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.AcquireLock)); - } - - [Test] - public void AttemptsToAcquireLockIfOtherProcessIsNoLongerRunning() - { - var log = new InMemoryLog(); - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var processFinder = Substitute.For(); - - processFinder.ProcessIsRunning(Arg.Any(), Arg.Any()).Returns(false); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, processFinder, log); - var fileLock = new FileLock(-1, Guid.NewGuid().ToString(), -2, DateTime.Now.Ticks); - lockIo.LockExists(Arg.Any()).Returns(true); - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.AcquireLock)); - log.Messages.Should().Contain(m => m.Level == InMemoryLog.Level.Warn && m.FormattedMessage == "Process -1, thread -2 had lock, but appears to have crashed. Taking lock."); - } - - [Test] - public void DoesNotAttemptToAcquireLockIfOwnedBySomeoneElseAndLockHasNotTimedOut() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var processFinder = Substitute.For(); - processFinder.ProcessIsRunning(Arg.Any(), Arg.Any()).Returns(true); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, processFinder, new InMemoryLog()); - var fileLock = new FileLock(0, Guid.NewGuid().ToString(), 0, DateTime.Now.Ticks); - lockIo.LockExists(Arg.Any()).Returns(true); - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.DontAcquireLock)); - } - - [Test] - public void AttemptsToForciblyAcquireLockIfOwnedBySomeoneElseAndLockHasTimedOut() - { - var log = new InMemoryLog(); - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var processFinder = Substitute.For(); - processFinder.ProcessIsRunning(Arg.Any(), Arg.Any()).Returns(true); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, processFinder, log); - var fileLock = new FileLock(0, Guid.NewGuid().ToString(), 0, DateTime.Now.Subtract(TimeSpan.FromMinutes(5)).Ticks); - lockIo.LockExists(Arg.Any()).Returns(true); - var result = semaphore.ShouldAcquireLock(fileLock); - Assert.That(result, Is.EqualTo(LockFileBasedSemaphore.AcquireLockAction.ForciblyAcquireLock)); - log.Messages.Should().Contain(m => m.Level == InMemoryLog.Level.Warn && m.FormattedMessage == "Forcibly taking lock from process 0, thread 0 as lock has timed out. If this happens regularly, please contact Octopus Support."); - } - - [Test] - public void DeletesLockIfOwnedBySomeoneElseAndLockHasTimedOut() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var processFinder = Substitute.For(); - processFinder.ProcessIsRunning(Arg.Any(), Arg.Any()).Returns(true); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, processFinder, new InMemoryLog()); - //not setting processid/threadid, therefore its someone elses - var fileLock = new FileLock(0, Guid.NewGuid().ToString(), 0, DateTime.Now.Subtract(TimeSpan.FromMinutes(5)).Ticks); - lockIo.ReadLock(Arg.Any()).Returns(fileLock); - lockIo.LockExists(Arg.Any()).Returns(true); - lockIo.WriteLock(Arg.Any(), Arg.Any()).Returns(true); - var result = semaphore.TryAcquireLock(); - Assert.That(result, Is.True); - lockIo.Received().DeleteLock(Arg.Any()); - lockIo.Received().WriteLock(Arg.Any(), Arg.Any()); - } - - [Test] - public void WritesLockFile() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var processFinder = Substitute.For(); - processFinder.ProcessIsRunning(Arg.Any(), Arg.Any()).Returns(true); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, processFinder, new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.ReadLock(Arg.Any()).Returns(fileLock); - lockIo.LockExists(Arg.Any()).Returns(true); - lockIo.WriteLock(Arg.Any(), Arg.Any()).Returns(true); - var result = semaphore.TryAcquireLock(); - Assert.That(result, Is.True); - lockIo.DidNotReceive().DeleteLock(Arg.Any()); - lockIo.Received().WriteLock(Arg.Any(), Arg.Any()); - } - - [Test] - public void CanReleaseLockIfWeCanAcquireIt() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.ReadLock(Arg.Any()).Returns(fileLock); - lockIo.LockExists(Arg.Any()).Returns(true); - lockIo.WriteLock(Arg.Any(), Arg.Any()).Returns(true); - semaphore.ReleaseLock(); - lockIo.Received().DeleteLock(Arg.Any()); - } - - [Test] - public void CannotReleaseLockIfWeCannotAcquireIt() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.ReadLock(Arg.Any()).Returns(fileLock); - lockIo.LockExists(Arg.Any()).Returns(true); - //failed to write the lock file (ie, someone else got in first) - lockIo.WriteLock(Arg.Any(), Arg.Any()).Returns(false); - semaphore.ReleaseLock(); - lockIo.DidNotReceive().DeleteLock(Arg.Any()); - } - - [Test] - public void WaitOneWithTimeoutTimesOut() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.ReadLock(Arg.Any()).Returns(fileLock); - lockIo.LockExists(Arg.Any()).Returns(true); - //failed to write the lock file (ie, someone else got in first) - lockIo.WriteLock(Arg.Any(), Arg.Any()).Returns(false); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var result = semaphore.WaitOne(300); - Assert.That(result, Is.False); - Assert.That(stopwatch.ElapsedMilliseconds, Is.GreaterThan(300)); - } - - [Test] - public void WaitOneWithTimeoutDoesNotWaitIfCanAcquireLock() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.ReadLock(Arg.Any()).Returns(fileLock); - lockIo.LockExists(Arg.Any()).Returns(true); - lockIo.WriteLock(Arg.Any(), Arg.Any()).Returns(true); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var result = semaphore.WaitOne(300); - Assert.That(result, Is.True); - Assert.That(stopwatch.ElapsedMilliseconds, Is.LessThan(100)); - } - - [Test] - public void WaitOneWithTimeoutReturnsAfterAquiringLock() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.ReadLock(Arg.Any()).Returns(fileLock); - lockIo.LockExists(Arg.Any()).Returns(true); - lockIo.WriteLock(Arg.Any(), Arg.Any()).Returns(false, false, false, true); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var result = semaphore.WaitOne(500); - Assert.That(result, Is.True); - Assert.That(stopwatch.ElapsedMilliseconds, Is.GreaterThan(200)); - } - - [Test] - public void WaitOneReturnsAfterAquiringLock() - { - var lockIo = Substitute.For(); - var name = Guid.NewGuid().ToString(); - var semaphore = new LockFileBasedSemaphore(name, TimeSpan.FromSeconds(30), lockIo, Substitute.For(), new InMemoryLog()); - var fileLock = new FileLock(System.Diagnostics.Process.GetCurrentProcess().Id, Guid.NewGuid().ToString(), Thread.CurrentThread.ManagedThreadId, DateTime.Now.Ticks); - lockIo.ReadLock(Arg.Any()).Returns(fileLock); - lockIo.LockExists(Arg.Any()).Returns(true); - lockIo.WriteLock(Arg.Any(), Arg.Any()).Returns(false, false, true); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var result = semaphore.WaitOne(); - Assert.That(result, Is.True); - Assert.That(stopwatch.ElapsedMilliseconds, Is.GreaterThan(175)); - } - } -} \ No newline at end of file diff --git a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockIoTests.cs b/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockIoTests.cs deleted file mode 100644 index 3a8913b60..000000000 --- a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/LockIoTests.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System; -using System.IO; -using System.Text; -using System.Threading; -using Calamari.Common.Features.Processes.Semaphores; -using Calamari.Common.Plumbing.FileSystem; -using Newtonsoft.Json; -using NSubstitute; -using NUnit.Framework; - -namespace Calamari.Tests.Fixtures.Integration.Process.Semaphores -{ - [TestFixture] - public class LockIoTests - { - [Test] - public void ReadLockReturnsMissingFileLockIfFileNotFound() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => { throw new FileNotFoundException(); } ); - var lockIo = new LockIo(fileSystem); - var result = lockIo.ReadLock(lockFilePath); - Assert.That(result, Is.InstanceOf()); - } - - [Test] - public void ReadLockReturnsOtherProcessHasExclusiveLockIfIoException() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => { throw new IOException("Sharing violation"); }); - var lockIo = new LockIo(fileSystem); - var result = lockIo.ReadLock(lockFilePath); - Assert.That(result, Is.InstanceOf()); - } - - [Test] - public void ReadLockReturnsUnableToDeserialiseWhenDeserialisationFails() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var fileCreationTime = DateTime.Now; - fileSystem.GetCreationTime(lockFilePath).Returns(fileCreationTime); - - var lockIo = new LockIo(fileSystem); - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => { throw new JsonReaderException(); }); - var result = lockIo.ReadLock(lockFilePath); - Assert.That(result, Is.InstanceOf()); - Assert.That(((UnableToDeserialiseLockFile)result).CreationTime, Is.EqualTo(fileCreationTime)); - } - - [Test] - public void ReadLockReturnsOtherProcessHasExclusiveLockIfUnknownException() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => { throw new ApplicationException(); }); - var result = lockIo.ReadLock(lockFilePath); - Assert.That(result, Is.InstanceOf()); - } - - [Test] - public void ReadLockReturnsLockDetailsIfLockBelongsToUs() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var fileContent = $"{{\"__type\":\"FileLock:#Calamari.Integration.Processes.Semaphores\",\"ProcessId\":{currentProcess.Id},\"ProcessName\":\"{currentProcess.ProcessName}\",\"ThreadId\":{Thread.CurrentThread.ManagedThreadId},\"Timestamp\":636114372739676700}}"; - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => new MemoryStream(Encoding.UTF8.GetBytes(fileContent))); - var result = lockIo.ReadLock(lockFilePath) as FileLock; - Assert.That(result, Is.InstanceOf()); - Assert.That(result.ProcessId, Is.EqualTo(currentProcess.Id)); - Assert.That(result.ProcessName, Is.EqualTo(currentProcess.ProcessName)); - Assert.That(result.ThreadId, Is.EqualTo(Thread.CurrentThread.ManagedThreadId)); - Assert.That(result.Timestamp, Is.EqualTo(636114372739676700)); - } - - [Test] - public void ReadLockReturnsOtherProcessOwnsFileLockIfLockBelongsToSomeoneElse() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var fileContent = $"{{\"__type\":\"FileLock:#Calamari.Integration.Processes.Semaphores\",\"ProcessId\":{currentProcess.Id + 1},\"ProcessName\":\"{currentProcess.ProcessName}\",\"ThreadId\":{Thread.CurrentThread.ManagedThreadId},\"Timestamp\":636114372739676700}}"; - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => new MemoryStream(Encoding.UTF8.GetBytes(fileContent))); - var result = lockIo.ReadLock(lockFilePath) as OtherProcessOwnsFileLock; - Assert.That(result, Is.InstanceOf()); - Assert.That(result.ProcessId, Is.EqualTo(currentProcess.Id + 1)); - Assert.That(result.ProcessName, Is.EqualTo(currentProcess.ProcessName)); - Assert.That(result.ThreadId, Is.EqualTo(Thread.CurrentThread.ManagedThreadId)); - Assert.That(result.Timestamp, Is.EqualTo(636114372739676700)); - } - - [Test] - public void DeleteLockSwallowsExceptions() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - fileSystem.When(x => x.DeleteFile(lockFilePath)).Do(x => { throw new Exception("failed to delete file"); }); - Assert.DoesNotThrow(() => lockIo.DeleteLock(lockFilePath)); - } - - [Test] - public void WriteLockDoesNotOverwriteLockFileIfItsIdenticalToWhatWeAreWantingToWrite() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - fileSystem.FileExists(lockFilePath).Returns(true); - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var fileContent = $"{{\"__type\":\"FileLock:#Calamari.Integration.Processes.Semaphores\",\"ProcessId\":{currentProcess.Id},\"ProcessName\":\"{currentProcess.ProcessName}\",\"ThreadId\":{Thread.CurrentThread.ManagedThreadId},\"Timestamp\":636114372739676700}}"; - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => new MemoryStream(Encoding.UTF8.GetBytes(fileContent))); - var lockFile = (FileLock)lockIo.ReadLock(lockFilePath); - lockIo.WriteLock(lockFilePath, lockFile); - } - - [Test] - public void WriteLockOverwritesLockFileIfTimestampIsDifferent() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - fileSystem.FileExists(lockFilePath).Returns(true); - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var fileContent = $"{{\"__type\":\"FileLock:#Calamari.Integration.Processes.Semaphores\",\"ProcessId\":{currentProcess.Id},\"ProcessName\":\"{currentProcess.ProcessName}\",\"ThreadId\":{Thread.CurrentThread.ManagedThreadId},\"Timestamp\":636114372739676700}}"; - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => new MemoryStream(Encoding.UTF8.GetBytes(fileContent))); - var lockFile = (FileLock)lockIo.ReadLock(lockFilePath); - lockFile.Timestamp = lockFile.Timestamp + 1; - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Create, FileAccess.Write) - .Returns(x => new MemoryStream()); - var result = lockIo.WriteLock(lockFilePath, lockFile); - Assert.That(result, Is.True); - } - - [Test] - public void WriteLockDeletesLockIfUnableToDeserialise() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - fileSystem.FileExists(lockFilePath).Returns(true); - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var expectedFileContent = $"{{\"__type\":\"FileLock:#Calamari.Integration.Processes.Semaphores\",\"ProcessId\":{currentProcess.Id},\"ProcessName\":\"{currentProcess.ProcessName}\",\"ThreadId\":{Thread.CurrentThread.ManagedThreadId},\"Timestamp\":636114372739676700}}"; - - fileSystem.OpenFileExclusively(lockFilePath, FileMode.Open, FileAccess.Read) - .Returns(x => new MemoryStream(Encoding.UTF8.GetBytes("non deserialisable content")), x => new MemoryStream(Encoding.UTF8.GetBytes(expectedFileContent))); - fileSystem.OpenFileExclusively(lockFilePath, FileMode.CreateNew, FileAccess.Write) - .Returns(x => new MemoryStream()); - var result = lockIo.WriteLock(lockFilePath, new FileLock(currentProcess.Id, currentProcess.ProcessName, Thread.CurrentThread.ManagedThreadId, 636114372739676700)); - fileSystem.Received().DeleteFile(lockFilePath); - Assert.That(result, Is.True); - } - - [Test] - public void WriteLockReturnsFalseIfIoException() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - fileSystem.FileExists(lockFilePath).Returns(true); - fileSystem.OpenFileExclusively(lockFilePath, Arg.Any(), Arg.Any()) - .Returns(x => { throw new IOException("Sharing exception"); }); - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var result = lockIo.WriteLock(lockFilePath, new FileLock(currentProcess.Id, currentProcess.ProcessName, Thread.CurrentThread.ManagedThreadId, 636114372739676700)); - Assert.That(result, Is.False); - } - - [Test] - public void WriteLockReturnsFalseIfUnknownException() - { - var fileSystem = Substitute.For(); - var lockFilePath = "fake path"; - var lockIo = new LockIo(fileSystem); - fileSystem.FileExists(lockFilePath).Returns(true); - fileSystem.OpenFileExclusively(lockFilePath, Arg.Any(), Arg.Any()) - .Returns(x => { throw new Exception("Unknown exception"); }); - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var result = lockIo.WriteLock(lockFilePath, new FileLock(currentProcess.Id, currentProcess.ProcessName, Thread.CurrentThread.ManagedThreadId, 636114372739676700)); - Assert.That(result, Is.False); - } - } -} diff --git a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/ProcessFinderFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/ProcessFinderFixture.cs deleted file mode 100644 index f3db0ce85..000000000 --- a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/ProcessFinderFixture.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Calamari.Common.Features.Processes.Semaphores; -using NUnit.Framework; - -namespace Calamari.Tests.Fixtures.Integration.Process.Semaphores -{ - [TestFixture] - public class ProcessFinderFixture - { - [Test] - public void ProcessIsRunningReturnsTrueForCurrentProcess() - { - var processFinder = new ProcessFinder(); - var currentProcess = System.Diagnostics.Process.GetCurrentProcess(); - var result = processFinder.ProcessIsRunning(currentProcess.Id, currentProcess.ProcessName); - Assert.That(result, Is.True); - } - - [Test] - public void ProcessIsRunningReturnsFalseForNonExistantProcess() - { - var processFinder = new ProcessFinder(); - var result = processFinder.ProcessIsRunning(-1, Guid.NewGuid().ToString()); - Assert.That(result, Is.EqualTo(GetExpectedResult())); - } - - private bool GetExpectedResult() - { - try - { - var processes = System.Diagnostics.Process.GetProcesses(); - return false; - } - catch (NotSupportedException) - { - //not supported on FreeBSD. Probably a nicer way to do this. - return true; - } - } - } -} diff --git a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/SemaphoreFactoryFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/SemaphoreFactoryFixture.cs deleted file mode 100644 index 6aa8a0023..000000000 --- a/source/Calamari.Tests/Fixtures/Integration/Process/Semaphores/SemaphoreFactoryFixture.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Calamari.Common.Features.Processes.Semaphores; -using Calamari.Common.Plumbing; -using Calamari.Testing.Helpers; -using Calamari.Testing.Requirements; -using Calamari.Tests.Helpers; -using NUnit.Framework; - -namespace Calamari.Tests.Fixtures.Integration.Process.Semaphores -{ - [TestFixture] - public class SemaphoreFactoryFixture - { - [Test] - public void ReturnsFileBasedSemaphoreManagerForMono() - { - if (!CalamariEnvironment.IsRunningOnMono) - Assert.Ignore("This test is designed to run on mono"); - var result = SemaphoreFactory.Get(); - Assert.That(result, Is.InstanceOf()); - } - -#if NETFX - [Test] - [Category(TestCategory.CompatibleOS.OnlyWindows)] - public void ReturnsSystemSemaphoreManagerForWindows() - { - if (!CalamariEnvironment.IsRunningOnWindows) - Assert.Ignore("This test is designed to run on windows"); - var result = SemaphoreFactory.Get(); - Assert.That(result, Is.InstanceOf()); - } -#else - [Test] - public void ReturnsSystemSemaphoreManagerForAllPlatformsUnderNetCore() - { - var result = SemaphoreFactory.Get(); - Assert.That(result, Is.InstanceOf()); - } -#endif - } -} \ No newline at end of file diff --git a/source/Calamari.Tests/Fixtures/PackageDownload/PackageDownloadFixture.cs b/source/Calamari.Tests/Fixtures/PackageDownload/PackageDownloadFixture.cs index cdea751c2..9bb7a85c0 100644 --- a/source/Calamari.Tests/Fixtures/PackageDownload/PackageDownloadFixture.cs +++ b/source/Calamari.Tests/Fixtures/PackageDownload/PackageDownloadFixture.cs @@ -99,9 +99,6 @@ public void ShouldDownloadPackage() [RequiresNonFreeBSDPlatform] public void ShouldDownloadMavenPackage() { - if (CalamariEnvironment.IsRunningOnMac && TestEnvironment.IsCI && !CalamariEnvironment.IsRunningOnMono) - Assert.Inconclusive("As of November 2018, this test is failing under dotnet core on the cloudmac under teamcity - we were getting an error 'SSL connect error' when trying to download from 'https://repo.maven.apache.org/maven2/'. Marking as inconclusive so we can re-enable the build - it had been disabled for months :("); - var result = DownloadPackage( MavenPublicFeed.PackageId, MavenPublicFeed.Version.ToString(), @@ -127,9 +124,6 @@ public void ShouldDownloadMavenPackage() [RequiresNonFreeBSDPlatform] public void ShouldDownloadMavenSnapshotPackage() { - if (CalamariEnvironment.IsRunningOnMac && TestEnvironment.IsCI && !CalamariEnvironment.IsRunningOnMono) - Assert.Inconclusive("As of November 2018, this test is failing under dotnet core on the cloudmac under teamcity - we were getting an error 'SSL connect error' when trying to download from 'https://repo.maven.apache.org/maven2/'. Marking as inconclusive so we can re-enable the build - it had been disabled for months :("); - var result = DownloadPackage( MavenPublicFeed.PackageId, MavenPublicFeed.Version.ToString(), @@ -193,9 +187,6 @@ public void ShouldUsePackageFromCache() [RequiresNonFreeBSDPlatform] public void ShouldUseMavenPackageFromCache() { - if (CalamariEnvironment.IsRunningOnMac && TestEnvironment.IsCI && !CalamariEnvironment.IsRunningOnMono) - Assert.Inconclusive("As of November 2018, this test is failing under dotnet core on the cloudmac under teamcity - we were getting an error 'SSL connect error' when trying to download from 'https://repo.maven.apache.org/maven2/'. Marking as inconclusive so we can re-enable the build - it had been disabled for months :("); - DownloadPackage(MavenPublicFeed.PackageId, MavenPublicFeed.Version.ToString(), MavenPublicFeed.Id, @@ -224,9 +215,6 @@ public void ShouldUseMavenPackageFromCache() [RequiresNonFreeBSDPlatform] public void ShouldUseMavenSnapshotPackageFromCache() { - if (CalamariEnvironment.IsRunningOnMac && TestEnvironment.IsCI && !CalamariEnvironment.IsRunningOnMono) - Assert.Inconclusive("As of November 2018, this test is failing under dotnet core on the cloudmac under teamcity - we were getting an error 'SSL connect error' when trying to download from 'https://repo.maven.apache.org/maven2/'. Marking as inconclusive so we can re-enable the build - it had been disabled for months :("); - DownloadPackage(MavenPublicFeed.PackageId, MavenPublicFeed.Version.ToString(), MavenPublicFeed.Id, @@ -276,9 +264,6 @@ public void ShouldByPassCacheAndDownloadPackage() [RequiresNonFreeBSDPlatform] public void ShouldByPassCacheAndDownloadMavenPackage() { - if (CalamariEnvironment.IsRunningOnMac && TestEnvironment.IsCI && !CalamariEnvironment.IsRunningOnMono) - Assert.Inconclusive("As of November 2018, this test is failing under dotnet core on the cloudmac under teamcity - we were getting an error 'SSL connect error' when trying to download from 'https://repo.maven.apache.org/maven2/'. Marking as inconclusive so we can re-enable the build - it had been disabled for months :("); - var firstDownload = DownloadPackage( MavenPublicFeed.PackageId, MavenPublicFeed.Version.ToString(), diff --git a/source/Calamari.Tests/KubernetesFixtures/Approved/KubernetesContextScriptWrapperLiveFixtureEks.DeployRawYaml_WithMultipleYamlFilesGlobPatterns_YamlFilesAppliedInCorrectBatches.ApplyingBatches.approved.txt b/source/Calamari.Tests/KubernetesFixtures/Approved/KubernetesContextScriptWrapperLiveFixtureEks.DeployRawYaml_WithMultipleYamlFilesGlobPatterns_YamlFilesAppliedInCorrectBatches.ApplyingBatches.approved.txt index 68433ed41..ba716b1d1 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Approved/KubernetesContextScriptWrapperLiveFixtureEks.DeployRawYaml_WithMultipleYamlFilesGlobPatterns_YamlFilesAppliedInCorrectBatches.ApplyingBatches.approved.txt +++ b/source/Calamari.Tests/KubernetesFixtures/Approved/KubernetesContextScriptWrapperLiveFixtureEks.DeployRawYaml_WithMultipleYamlFilesGlobPatterns_YamlFilesAppliedInCorrectBatches.ApplyingBatches.approved.txt @@ -11,7 +11,7 @@ Applying Batch #2 for YAML matching 'services/myapp-service.yml' Matched file: services/myapp-service.yml Created Resources: - Service/nginx-service in namespace calamari-testing -Resource Status Check: reported 8 updates, 0 removals +Resource Status Check: reported 9 updates, 0 removals Resource Status Check: 1 new resources have been added: - Service/nginx-service in namespace calamari-testing Applying Batch #3 for YAML matching 'configmaps/*.yml' @@ -20,7 +20,7 @@ Matched file: configmaps/myapp-configmap1.yml Created Resources: - ConfigMap/game-demo in namespace calamari-testing - ConfigMap/game-demo2 in namespace calamari-testing -Resource Status Check: reported 10 updates, 0 removals +Resource Status Check: reported 11 updates, 0 removals Resource Status Check: 2 new resources have been added: - ConfigMap/game-demo in namespace calamari-testing - ConfigMap/game-demo2 in namespace calamari-testing \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Commands/Executors/GatherAndApplyRawYamlExecutorTests.cs b/source/Calamari.Tests/KubernetesFixtures/Commands/Executors/GatherAndApplyRawYamlExecutorTests.cs index 9dd568053..d1f32a8c3 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Commands/Executors/GatherAndApplyRawYamlExecutorTests.cs +++ b/source/Calamari.Tests/KubernetesFixtures/Commands/Executors/GatherAndApplyRawYamlExecutorTests.cs @@ -6,7 +6,6 @@ using Calamari.Common.Commands; using Calamari.Common.Features.Processes; using Calamari.Common.Plumbing.FileSystem; -using Calamari.Common.Plumbing.ServiceMessages; using Calamari.Common.Plumbing.Variables; using Calamari.Kubernetes; using Calamari.Kubernetes.Commands; @@ -27,6 +26,7 @@ public class GatherAndApplyRawYamlExecutorTests { readonly ICalamariFileSystem fileSystem = TestCalamariPhysicalFileSystem.GetPhysicalFileSystem(); readonly ICommandLineRunner commandLineRunner = Substitute.For(); + readonly IManifestReporter manifestReporter = Substitute.For(); InMemoryLog log; List receivedCallbacks; @@ -337,7 +337,8 @@ void SetupCommandLineRunnerMocks() IRawYamlKubernetesApplyExecutor CreateExecutor(IVariables variables, ICalamariFileSystem fs) { var kubectl = new Kubectl(variables, log, commandLineRunner); - return new GatherAndApplyRawYamlExecutor(log, fs, kubectl); + + return new GatherAndApplyRawYamlExecutor(log, fs, manifestReporter, kubectl); } Task RecordingCallback(ResourceIdentifier[] identifiers) diff --git a/source/Calamari.Tests/KubernetesFixtures/InstallTools.cs b/source/Calamari.Tests/KubernetesFixtures/InstallTools.cs index d699ef83d..c286a6e13 100644 --- a/source/Calamari.Tests/KubernetesFixtures/InstallTools.cs +++ b/source/Calamari.Tests/KubernetesFixtures/InstallTools.cs @@ -164,7 +164,7 @@ await Download(Path.Combine(destinationDirectoryName, GetAWSCliFileName()), $"/a {awsInstaller} /qn TARGETDIR={destinationDirectoryName}\\extract", destinationDirectoryName); } - else if (CalamariEnvironment.IsRunningOnNix && !CalamariEnvironment.IsRunningOnMono) + else if (CalamariEnvironment.IsRunningOnNix) { ExecuteCommandAndReturnResult("sudo", "apt-get install unzip", diff --git a/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixture.cs b/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixture.cs index 8b5cb8cd2..8ce52c24d 100644 --- a/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixture.cs +++ b/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixture.cs @@ -141,7 +141,7 @@ async Task RunTerraformInternal(string terraformWorkingFolder, Dictionar TestContext.Error.WriteLine(e); }); - result.ExitCode.Should().Be(0, because: $"`terraform {args[0]}` should run without error and exit cleanly during infrastructure setup"); + result.ExitCode.Should().Be(0, because: $"`terraform {args[0]}` should run without error and exit cleanly during infrastructure setup. Error output: \\r\\n{{result.ErrorOutput}}\");"); return stdOut.ToString().Trim(Environment.NewLine.ToCharArray()); } diff --git a/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureBase.cs b/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureBase.cs index f86852bd5..588e34cc4 100644 --- a/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureBase.cs +++ b/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureBase.cs @@ -27,6 +27,7 @@ namespace Calamari.Tests.KubernetesFixtures public abstract class KubernetesContextScriptWrapperLiveFixtureBase : CalamariFixture { protected const string TestNamespace = "calamari-testing"; + protected const string StaticTestResourcePrefix = "calamari-testing-static"; protected IVariables variables; protected string testFolder; diff --git a/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureEks.cs b/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureEks.cs index fec8b04d8..ad1b5fbcc 100644 --- a/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureEks.cs +++ b/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureEks.cs @@ -65,6 +65,10 @@ public class KubernetesContextScriptWrapperLiveFixtureEks: KubernetesContextScri private const string SimpleConfigMap2ResourceName = "game-demo2"; private const string SimpleConfigMap2 = "apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: game-demo2\ndata:\n player_initial_lives: '1'\n ui_properties_file_name: 'user-interface.properties'\n game.properties: |\n enemy.types=blobs,foxes\n player.maximum-lives=10\n user-interface.properties: |\n color.good=orange\n color.bad=pink\n allow.textmode=false"; + + string awsAccessKey; + string awsSecretKey; + string eksClientID; string eksSecretKey; string eksClusterEndpoint; @@ -81,7 +85,7 @@ public class KubernetesContextScriptWrapperLiveFixtureEks: KubernetesContextScri protected override async Task PreInitialise() { - region = RegionRandomiser.GetARegion(); + region = "ap-southeast-1"; await TestContext.Progress.WriteLineAsync($"Aws Region chosen: {region}"); } @@ -112,12 +116,16 @@ protected override void ExtractVariablesFromTerraformOutput(JObject jsonOutput) protected override async Task> GetEnvironmentVars(CancellationToken cancellationToken) { + awsAccessKey = await ExternalVariables.Get(ExternalVariable.AwsCloudFormationAndS3AccessKey, cancellationToken); + awsSecretKey = await ExternalVariables.Get(ExternalVariable.AwsCloudFormationAndS3SecretKey, cancellationToken); + return new Dictionary { - { "AWS_ACCESS_KEY_ID", await ExternalVariables.Get(ExternalVariable.AwsCloudFormationAndS3AccessKey, cancellationToken) }, - { "AWS_SECRET_ACCESS_KEY", await ExternalVariables.Get(ExternalVariable.AwsCloudFormationAndS3SecretKey, cancellationToken) }, + { "AWS_ACCESS_KEY_ID", awsAccessKey }, + { "AWS_SECRET_ACCESS_KEY", awsSecretKey }, { "AWS_DEFAULT_REGION", region }, - { "TF_VAR_tests_source_dir", testFolder } + { "TF_VAR_tests_source_dir", testFolder }, + { "TF_VAR_static_resource_prefix", StaticTestResourcePrefix } }; } @@ -134,7 +142,7 @@ public void DeployRawYaml_WithRawYamlDeploymentScriptOrCommand_OutputShouldIndic var objectStatusUpdates = Log.Messages.GetServiceMessagesOfType("k8s-status"); - objectStatusUpdates.Where(m => m.Properties["status"] == "Successful").Should().HaveCount(5); + objectStatusUpdates.Where(m => m.Properties["status"] == "Successful").Should().HaveCount(6); rawLogs.Should().ContainSingle(m => m.Contains("Resource status check completed successfully because all resources are deployed successfully")); @@ -383,8 +391,8 @@ public void DiscoverKubernetesClusterWithEnvironmentVariableCredentialsAndNoIamR try { - Environment.SetEnvironmentVariable(accessKeyEnvVar, eksClientID); - Environment.SetEnvironmentVariable(secretKeyEnvVar, eksSecretKey); + Environment.SetEnvironmentVariable(accessKeyEnvVar, awsAccessKey); + Environment.SetEnvironmentVariable(secretKeyEnvVar, awsSecretKey); var authenticationDetails = new AwsAuthenticationDetails { @@ -428,8 +436,8 @@ public void DiscoverKubernetesClusterWithAwsAccountCredentialsAndNoIamRole() { Account = new AwsAccessKeyCredentials { - AccessKey = eksClientID, - SecretKey = eksSecretKey + AccessKey = awsAccessKey, + SecretKey = awsSecretKey }, AccountId = "Accounts-1", Type = "account" @@ -608,15 +616,15 @@ private void SetVariablesToAuthoriseWithAmazonAccount() variables.Set(KubernetesSpecialVariables.EksClusterName, eksClusterName); variables.Set("Octopus.Action.Aws.Region", region); variables.Set("Octopus.Action.AwsAccount.Variable", account); - variables.Set($"{account}.AccessKey", eksClientID); - variables.Set($"{account}.SecretKey", eksSecretKey); + variables.Set($"{account}.AccessKey", awsAccessKey); + variables.Set($"{account}.SecretKey", awsSecretKey); variables.Set("Octopus.Action.Kubernetes.CertificateAuthority", certificateAuthority); variables.Set($"{certificateAuthority}.CertificatePem", eksClusterCaCertificate); } private void SetVariablesForRawYamlCommand(string globPaths) { - variables.Set("Octopus.Action.KubernetesContainers.Namespace", "nginx"); + variables.Set("Octopus.Action.KubernetesContainers.Namespace", "nginx-2"); variables.Set(KnownVariables.Package.JsonConfigurationVariablesTargets, "**/*.{yml,yaml}"); variables.Set(KubernetesSpecialVariables.CustomResourceYamlFileName, globPaths); } diff --git a/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureGke.cs b/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureGke.cs index 240cdda1f..990ec36a6 100644 --- a/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureGke.cs +++ b/source/Calamari.Tests/KubernetesFixtures/KubernetesContextScriptWrapperLiveFixtureGke.cs @@ -53,7 +53,8 @@ protected override async Task> GetEnvironmentVars(Can return new Dictionary { { "GOOGLE_CLOUD_KEYFILE_JSON", await ExternalVariables.Get(ExternalVariable.GoogleCloudJsonKeyfile, cancellationToken) }, - { "USE_GKE_GCLOUD_AUTH_PLUGIN", "True" } + { "USE_GKE_GCLOUD_AUTH_PLUGIN", "True" }, + { "TF_VAR_static_resource_prefix", StaticTestResourcePrefix } }; } diff --git a/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs b/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs new file mode 100644 index 000000000..b5b7d674d --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/ManifestReporterTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Calamari.Common.Plumbing.FileSystem; +using Calamari.Common.Plumbing.ServiceMessages; +using Calamari.Common.Plumbing.Variables; +using Calamari.Kubernetes; +using Calamari.Testing.Helpers; +using FluentAssertions; +using NUnit.Framework; + +namespace Calamari.Tests.KubernetesFixtures +{ + [TestFixture] + public class ManifestReporterTests + { + [Test] + public void GivenValidYaml_ShouldPostSingleServiceMessage() + { + var memoryLog = new InMemoryLog(); + var variables = new CalamariVariables(); + + var yaml = @"foo: bar"; + var expectedJson = "{\"foo\": \"bar\"}"; + using (CreateFile(yaml, out var filePath)) + { + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + + mr.ReportManifestApplied(filePath); + + var expected = ServiceMessage.Create(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, ("ns", "default"), ("manifest", expectedJson)); + memoryLog.ServiceMessages.Should().BeEquivalentTo(new List { expected }); + } + } + + [Test] + public void GivenInValidManifest_ShouldNotPostServiceMessage() + { + var memoryLog = new InMemoryLog(); + var variables = new CalamariVariables(); + + var yaml = @"text - Bar"; + using (CreateFile(yaml, out var filePath)) + { + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + + mr.ReportManifestApplied(filePath); + + memoryLog.ServiceMessages.Should().BeEmpty(); + } + } + + [Test] + public void GivenNamespaceInManifest_ShouldReportManifestNamespace() + { + var memoryLog = new InMemoryLog(); + var variables = new CalamariVariables(); + var yaml = @"metadata: + name: game-demo + namespace: XXX"; + using (CreateFile(yaml, out var filePath)) + { + var variableNs = Some.String(); + variables.Set(SpecialVariables.Namespace, variableNs); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + + mr.ReportManifestApplied(filePath); + + memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "XXX")); + } + } + + [Test] + public void GivenNamespaceNotInManifest_ShouldReportVariableNamespace() + { + var memoryLog = new InMemoryLog(); + var variables = new CalamariVariables(); + var yaml = @"foo: bar"; + using (CreateFile(yaml, out var filePath)) + { + var variableNs = Some.String(); + variables.Set(SpecialVariables.Namespace, variableNs); + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + + mr.ReportManifestApplied(filePath); + + memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", variableNs)); + } + } + + [Test] + public void GiveNoNamespaces_ShouldDefaultNamespace() + { + var memoryLog = new InMemoryLog(); + var variables = new CalamariVariables(); + var yaml = @"foo: bar"; + using (CreateFile(yaml, out var filePath)) + { + var mr = new ManifestReporter(variables, CalamariPhysicalFileSystem.GetPhysicalFileSystem(), memoryLog); + + mr.ReportManifestApplied(filePath); + + memoryLog.ServiceMessages.First().Properties.Should().Contain(new KeyValuePair("ns", "default")); + } + } + + IDisposable CreateFile(string yaml, out string filePath) + { + var tempDir = TemporaryDirectory.Create(); + filePath = Path.Combine(tempDir.DirectoryPath, $"{Guid.NewGuid():d}.tmp"); + File.WriteAllText(filePath, yaml); + return tempDir; + } + } +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/ResourceStatus/ResourceUpdateReporterTests.cs b/source/Calamari.Tests/KubernetesFixtures/ResourceStatus/ResourceUpdateReporterTests.cs index 9c1b461bb..7337eaa16 100644 --- a/source/Calamari.Tests/KubernetesFixtures/ResourceStatus/ResourceUpdateReporterTests.cs +++ b/source/Calamari.Tests/KubernetesFixtures/ResourceStatus/ResourceUpdateReporterTests.cs @@ -29,7 +29,7 @@ public void ReportsCreatedResourcesCorrectly() reporter.ReportUpdatedResources(originalStatuses, newStatuses, 1); var serviceMessages = log.ServiceMessages - .Where(message => message.Name == SpecialVariables.KubernetesResourceStatusServiceMessageName) + .Where(message => message.Name == SpecialVariables.ServiceMessageNames.ResourceStatus.Name) .ToList(); serviceMessages.Select(message => message.Properties["name"]) @@ -72,7 +72,7 @@ public void ReportsUpdatedResourcesCorrectly() reporter.ReportUpdatedResources(originalStatuses, newStatuses, 1); var serviceMessages = log.ServiceMessages - .Where(message => message.Name == SpecialVariables.KubernetesResourceStatusServiceMessageName) + .Where(message => message.Name == SpecialVariables.ServiceMessageNames.ResourceStatus.Name) .ToList(); serviceMessages.Should().ContainSingle().Which.Properties @@ -102,7 +102,7 @@ public void ReportsRemovedResourcesCorrectly() reporter.ReportUpdatedResources(originalStatuses, newStatuses, 1); var serviceMessages = log.ServiceMessages - .Where(message => message.Name == SpecialVariables.KubernetesResourceStatusServiceMessageName) + .Where(message => message.Name == SpecialVariables.ServiceMessageNames.ResourceStatus.Name) .ToList(); serviceMessages.Should().ContainSingle().Which.Properties @@ -129,7 +129,7 @@ public void ClusterScopedResourcesAreIgnored() reporter.ReportUpdatedResources(new Dictionary(), newStatuses, 1); var serviceMessages = log.ServiceMessages - .Where(message => message.Name == SpecialVariables.KubernetesResourceStatusServiceMessageName) + .Where(message => message.Name == SpecialVariables.ServiceMessageNames.ResourceStatus.Name) .ToList(); serviceMessages.Should().ContainSingle().Which.Properties diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/eks.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/eks.tf index 14e615081..fd9e78242 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/eks.tf +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/eks.tf @@ -1,228 +1,41 @@ -resource "aws_security_group" "cluster" { - ingress = [{ - cidr_blocks = ["0.0.0.0/0"] - description = "" - from_port = 0 - ipv6_cidr_blocks = ["::/0"] - prefix_list_ids = [] - protocol = "-1" - security_groups = [] - self = false - to_port = 0 - }] - egress = [{ - cidr_blocks = [ - "0.0.0.0/0", - ] - description = "" - from_port = 0 - ipv6_cidr_blocks = [ - "::/0", - ] - prefix_list_ids = [] - protocol = "-1" - security_groups = [] - self = false - to_port = 0 - }] - vpc_id = aws_vpc.default.id -} - -resource "aws_iam_role" "nodes" { - name = "${random_pet.prefix.id}-nodes" - - assume_role_policy = jsonencode({ - Statement = [{ - Action = "sts:AssumeRole" - Effect = "Allow" - Principal = { - Service = "ec2.amazonaws.com" - } - }] - Version = "2012-10-17" - }) -} - -resource "aws_iam_role_policy_attachment" "example-AmazonEKSWorkerNodePolicy" { - policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" - role = aws_iam_role.nodes.name -} - -resource "aws_iam_role_policy_attachment" "example-AmazonEKS_CNI_Policy" { - policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" - role = aws_iam_role.nodes.name -} - -resource "aws_iam_role_policy_attachment" "example-AmazonEC2ContainerRegistryReadOnly" { - policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" - role = aws_iam_role.nodes.name -} - -resource "aws_eks_node_group" "default" { - cluster_name = aws_eks_cluster.default.name - node_group_name = "${random_pet.prefix.id}-nodes" - node_role_arn = aws_iam_role.nodes.arn - subnet_ids = aws_subnet.default.*.id - - scaling_config { - desired_size = 1 - max_size = 1 - min_size = 1 - } -} - -resource "aws_eks_cluster" "default" { - name = "${random_pet.prefix.id}-eks" - role_arn = aws_iam_role.cluster.arn - version = "1.28" - - tags = { - octopus-environment = "Staging" - octopus-role = "discovery-role" - source = "calamari-e2e-tests" - } - - vpc_config { - endpoint_private_access = true - public_access_cidrs = ["0.0.0.0/0"] - subnet_ids = aws_subnet.default.*.id - security_group_ids = [aws_security_group.cluster.id] - } -} - -data "aws_iam_policy_document" "cluster" { - statement { - actions = [ - "sts:AssumeRole" - ] - principals { - type = "Service" - identifiers = ["eks.amazonaws.com"] - } - } -} - -resource "aws_iam_role" "cluster" { - path = "/test/" - assume_role_policy = data.aws_iam_policy_document.cluster.json - managed_policy_arns = [ - "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy", - "arn:aws:iam::aws:policy/AmazonEKSServicePolicy", - "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy", - ] -} - -data "aws_iam_policy_document" "userRole" { - statement { - actions = [ - "sts:AssumeRole" - ] - principals { - type = "AWS" - identifiers = [aws_iam_user.default.arn] - } - principals { - type = "AWS" - identifiers = [aws_iam_role.ec2.arn] - } - } +data "aws_iam_role" "iam_role_with_cluster_access" { + name = "${var.static_resource_prefix}-iam-role-with-cluster-access" } -data "aws_iam_policy_document" "ec2Role" { - statement { - actions = [ - "sts:AssumeRole", - ] - principals { - type = "Service" - identifiers = ["ec2.amazonaws.com"] - } - } +data "aws_eks_cluster" "default" { + name = "${var.static_resource_prefix}-eks" } -resource "aws_iam_instance_profile" "profile" { - role = aws_iam_role.ec2.name +data "aws_iam_instance_profile" "profile" { + name = "${var.static_resource_prefix}-instance-profile" } -resource "aws_iam_role" "ec2" { - assume_role_policy = data.aws_iam_policy_document.ec2Role.json - managed_policy_arns = [aws_iam_policy.default.arn] +resource "aws_iam_user" "default" { + name = "${var.static_resource_prefix}-${random_pet.name.id}" + path = "/test/" } -resource "aws_iam_role" "user" { - assume_role_policy = data.aws_iam_policy_document.userRole.json - managed_policy_arns = [ - aws_iam_policy.default.arn - ] +resource "aws_iam_access_key" "default" { + user = aws_iam_user.default.name } data "aws_iam_policy_document" "user" { statement { + effect = "Allow" actions = [ - "sts:AssumeRole", - "eks:ListClusters", - "eks:ListTagsForResource", - "eks:AccessKubernetesApi", - "eks:DescribeCluster", + "sts:AssumeRole" ] - effect = "Allow" - resources = ["*"] + + resources = [data.aws_iam_role.iam_role_with_cluster_access.arn] } } -resource "aws_iam_policy" "default" { +resource "aws_iam_policy" "user" { path = "/test/" policy = data.aws_iam_policy_document.user.json } resource "aws_iam_user_policy_attachment" "default" { user = aws_iam_user.default.name - policy_arn = aws_iam_policy.default.arn -} - -data "aws_availability_zones" "available" { -} - -resource "aws_iam_access_key" "default" { - user = aws_iam_user.default.name -} - -resource "aws_iam_user" "default" { - name = "${random_pet.prefix.id}-test" - path = "/test/" -} - -resource "aws_subnet" "default" { - count = 2 - - availability_zone = data.aws_availability_zones.available.names[count.index] - cidr_block = "10.0.${count.index}.0/24" - vpc_id = aws_vpc.default.id - map_public_ip_on_launch = true -} - -resource "aws_vpc" "default" { - cidr_block = "10.0.0.0/16" - enable_dns_support = true - enable_dns_hostnames = true -} - -resource "aws_internet_gateway" "default" { - vpc_id = aws_vpc.default.id -} - -resource "aws_route_table" "default" { - vpc_id = aws_vpc.default.id - - route { - cidr_block = "0.0.0.0/0" - gateway_id = aws_internet_gateway.default.id - } -} - -resource "aws_route_table_association" "default" { - count = 2 - - subnet_id = aws_subnet.default[count.index].id - route_table_id = aws_route_table.default.id + policy_arn = aws_iam_policy.user.arn } diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/main.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/main.tf index 97e63d59a..53a348157 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/main.tf +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/main.tf @@ -1,5 +1,5 @@ -variable "tests_source_dir" { +variable "static_resource_prefix" { type = string } -resource "random_pet" "prefix" {} \ No newline at end of file +resource "random_pet" "name" {} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/outputs.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/outputs.tf index eb7c0b545..9205834de 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/outputs.tf +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/outputs.tf @@ -8,37 +8,37 @@ output "eks_secret_key" { } output "eks_iam_role_arn" { - value = aws_iam_role.user.arn + value = data.aws_iam_role.iam_role_with_cluster_access.arn } output "eks_cluster_endpoint" { description = "Endpoint for EKS control plane." - value = aws_eks_cluster.default.endpoint + value = data.aws_eks_cluster.default.endpoint } output "eks_cluster_ca_certificate" { - value = base64decode(aws_eks_cluster.default.certificate_authority[0].data) + value = base64decode(data.aws_eks_cluster.default.certificate_authority[0].data) sensitive = true } output "eks_cluster_name" { description = "EKS name." - value = aws_eks_cluster.default.name + value = data.aws_eks_cluster.default.name } output "eks_cluster_arn" { description = "EKS ARN" - value = aws_eks_cluster.default.arn + value = data.aws_eks_cluster.default.arn } output "aws_vpc_id" { - value = aws_vpc.default.id + value = one(data.aws_eks_cluster.default.vpc_config[*].vpc_id) } output "aws_subnet_id" { - value = aws_subnet.default[0].id + value = tolist(data.aws_eks_cluster.default.vpc_config[0].subnet_ids)[0] } output "aws_iam_instance_profile_name" { - value = aws_iam_instance_profile.profile.name + value = data.aws_iam_instance_profile.profile.name } \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/providers.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/providers.tf index 028ed3122..b25d159e9 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/providers.tf +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/providers.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.45.0" + version = "5.64.0" } kubernetes = { source = "hashicorp/kubernetes" diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/gke.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/gke.tf index 44b424e2b..85f073e3c 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/gke.tf +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/gke.tf @@ -1,58 +1,8 @@ -# Get latest version -data "google_container_engine_versions" "main" { - location = local.region - project = "octopus-api-tester" - - # Since this is just a string match, it's recommended that you append a . after minor versions - # to ensure that prefixes such as 1.1 don't match versions like 1.12.5-gke.10 accidentally. - version_prefix = "1.28." -} - -# VPC -resource "google_compute_network" "vpc" { - name = "${random_pet.prefix.id}-vpc" - auto_create_subnetworks = "false" - project = "octopus-api-tester" -} - -# Subnet -resource "google_compute_subnetwork" "subnet" { - name = "${random_pet.prefix.id}-subnet" - region = local.region - network = google_compute_network.vpc.name - ip_cidr_range = "10.10.0.0/24" - project = "octopus-api-tester" -} - -locals { - # we will pick the latest k8s version - master_version = data.google_container_engine_versions.main.valid_master_versions[0] -} - -resource "google_container_cluster" "default" { - name = "${random_pet.prefix.id}-gke" - project = "octopus-api-tester" - min_master_version = local.master_version - initial_node_count = 1 - node_config { - preemptible = true - machine_type = "e2-medium" - } - master_auth { - client_certificate_config { - issue_client_certificate = true - } - } - - # to prevent automatic updates to cluster - release_channel { - channel = "UNSPECIFIED" - } - - network = google_compute_network.vpc.name - subnetwork = google_compute_subnetwork.subnet.name +data "google_container_cluster" "default" { + name = "${var.static_resource_prefix}-gke" + project = "octopus-api-tester" } data "google_client_config" "default" { - depends_on = [google_container_cluster.default] + depends_on = [data.google_container_cluster.default] } \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/main.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/main.tf index d5bff7a84..1a53e9c95 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/main.tf +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/main.tf @@ -1 +1,3 @@ -resource "random_pet" "prefix" {} \ No newline at end of file +variable "static_resource_prefix" { + type = string +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/outputs.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/outputs.tf index 273a900e1..8afda02fc 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/outputs.tf +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/GKE/outputs.tf @@ -1,10 +1,10 @@ output "gke_cluster_endpoint" { description = "Endpoint for GKE control plane." - value = google_container_cluster.default.endpoint + value = data.google_container_cluster.default.endpoint } output "gke_cluster_ca_certificate" { - value = base64decode(google_container_cluster.default.master_auth.0.cluster_ca_certificate) + value = base64decode(data.google_container_cluster.default.master_auth.0.cluster_ca_certificate) sensitive = true } @@ -15,15 +15,15 @@ output "gke_token" { output "gke_cluster_name" { description = "GKE name." - value = google_container_cluster.default.name + value = data.google_container_cluster.default.name } output "gke_cluster_project" { description = "GKE clusters project." - value = google_container_cluster.default.project + value = data.google_container_cluster.default.project } output "gke_cluster_location" { description = "GKE clusters location." - value = google_container_cluster.default.location + value = data.google_container_cluster.default.location } \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/eks.kubernetes.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/eks.kubernetes.tf similarity index 89% rename from source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/eks.kubernetes.tf rename to source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/eks.kubernetes.tf index 9e07e7a3b..f27b4b9a3 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/eks.kubernetes.tf +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/eks.kubernetes.tf @@ -1,51 +1,50 @@ -data "aws_eks_cluster" "default" { - name = aws_eks_cluster.default.name -} - -data "aws_eks_cluster_auth" "default" { - name = aws_eks_cluster.default.name -} - -provider "kubernetes" { - alias = "aws" - host = data.aws_eks_cluster.default.endpoint - cluster_ca_certificate = base64decode(data.aws_eks_cluster.default.certificate_authority[0].data) - token = data.aws_eks_cluster_auth.default.token -} - -resource "local_file" "kubeconfig" { - sensitive_content = templatefile("${path.module}/kubeconfig.tpl", { - cluster_name = aws_eks_cluster.default.name, - cluster_ca = data.aws_eks_cluster.default.certificate_authority[0].data, - endpoint = data.aws_eks_cluster.default.endpoint, - }) - filename = "./kubeconfig-${aws_eks_cluster.default.name}" -} - -resource "kubernetes_config_map" "aws_auth" { - provider = kubernetes.aws - metadata { - name = "aws-auth" - namespace = "kube-system" - } - data = { - mapRoles = <<-EOT - - rolearn: ${aws_iam_role.user.arn} - username: system:node:{{EC2PrivateDNSName}} - groups: - - system:bootstrappers - - system:nodes - - rolearn: ${aws_iam_role.ec2.arn} - username: system:node:{{EC2PrivateDNSName}} - groups: - - system:bootstrappers - - system:nodes -EOT - mapUsers = <<-EOT - - userarn: ${aws_iam_user.default.arn} - username: ${aws_iam_user.default.name} - groups: - - system:masters -EOT - } -} +data "aws_eks_cluster" "default" { + name = aws_eks_cluster.default.name +} + +data "aws_eks_cluster_auth" "default" { + name = aws_eks_cluster.default.name +} + +provider "kubernetes" { + alias = "aws" + host = data.aws_eks_cluster.default.endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.default.certificate_authority[0].data) + token = data.aws_eks_cluster_auth.default.token +} + +resource "local_file" "kubeconfig" { + sensitive_content = templatefile("${path.module}/kubeconfig.tpl", { + cluster_name = aws_eks_cluster.default.name, + cluster_ca = data.aws_eks_cluster.default.certificate_authority[0].data, + endpoint = data.aws_eks_cluster.default.endpoint, + }) + filename = "./kubeconfig-${aws_eks_cluster.default.name}" +} + +resource "kubernetes_config_map" "aws_auth" { + provider = kubernetes.aws + metadata { + name = "aws-auth" + namespace = "kube-system" + } + data = { + mapRoles = <<-EOT + - rolearn: ${aws_iam_role.user.arn} + username: ${aws_iam_role.user.name} + groups: + - system:masters + - rolearn: ${aws_iam_role.ec2.arn} + username: system:node:{{EC2PrivateDNSName}} + groups: + - system:bootstrappers + - system:nodes +EOT + mapUsers = <<-EOT + - userarn: ${aws_iam_user.default.arn} + username: ${aws_iam_user.default.name} + groups: + - system:masters +EOT + } +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/eks.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/eks.tf new file mode 100644 index 000000000..a8455a93c --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/eks.tf @@ -0,0 +1,234 @@ +resource "aws_security_group" "cluster" { + ingress = [{ + cidr_blocks = ["0.0.0.0/0"] + description = "" + from_port = 0 + ipv6_cidr_blocks = ["::/0"] + prefix_list_ids = [] + protocol = "-1" + security_groups = [] + self = false + to_port = 0 + }] + egress = [{ + cidr_blocks = [ + "0.0.0.0/0", + ] + description = "" + from_port = 0 + ipv6_cidr_blocks = [ + "::/0", + ] + prefix_list_ids = [] + protocol = "-1" + security_groups = [] + self = false + to_port = 0 + }] + vpc_id = aws_vpc.default.id +} + +resource "aws_iam_role" "nodes" { + name = "${var.static_resource_prefix}-nodes" + + assume_role_policy = jsonencode({ + Statement = [{ + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + }] + Version = "2012-10-17" + }) +} + +resource "aws_iam_role_policy_attachment" "example-AmazonEKSWorkerNodePolicy" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" + role = aws_iam_role.nodes.name +} + +resource "aws_iam_role_policy_attachment" "example-AmazonEKS_CNI_Policy" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" + role = aws_iam_role.nodes.name +} + +resource "aws_iam_role_policy_attachment" "example-AmazonEC2ContainerRegistryReadOnly" { + policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + role = aws_iam_role.nodes.name +} + +resource "aws_eks_node_group" "default" { + cluster_name = aws_eks_cluster.default.name + node_group_name = "${var.static_resource_prefix}-nodes" + node_role_arn = aws_iam_role.nodes.arn + subnet_ids = aws_subnet.default.*.id + + scaling_config { + desired_size = 1 + max_size = 1 + min_size = 1 + } +} + +resource "aws_eks_cluster" "default" { + name = "${var.static_resource_prefix}-eks" + role_arn = aws_iam_role.cluster.arn + version = "1.28" + + tags = { + octopus-environment = "Staging" + octopus-role = "discovery-role" + source = "calamari-e2e-tests" + } + + vpc_config { + endpoint_private_access = true + public_access_cidrs = ["0.0.0.0/0"] + subnet_ids = aws_subnet.default.*.id + security_group_ids = [aws_security_group.cluster.id] + } +} + +data "aws_iam_policy_document" "cluster" { + statement { + actions = [ + "sts:AssumeRole" + ] + principals { + type = "Service" + identifiers = ["eks.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "cluster" { + path = "/test/" + assume_role_policy = data.aws_iam_policy_document.cluster.json + managed_policy_arns = [ + "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy", + "arn:aws:iam::aws:policy/AmazonEKSServicePolicy", + "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy", + ] +} + +data "aws_iam_policy_document" "userRole" { + statement { + actions = [ + "sts:AssumeRole" + ] + principals { + type = "AWS" + identifiers = ["017645897735"] # TODO: replace with var (account id) + } + principals { + type = "AWS" + identifiers = [aws_iam_user.default.arn] + } + principals { + type = "AWS" + identifiers = [aws_iam_role.ec2.arn] + } + } +} + +data "aws_iam_policy_document" "ec2Role" { + statement { + actions = [ + "sts:AssumeRole", + ] + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +resource "aws_iam_instance_profile" "profile" { + name = "${var.static_resource_prefix}-instance-profile" + role = aws_iam_role.ec2.name +} + +resource "aws_iam_role" "ec2" { + assume_role_policy = data.aws_iam_policy_document.ec2Role.json + managed_policy_arns = [aws_iam_policy.default.arn] +} + +resource "aws_iam_role" "user" { + name = "${var.static_resource_prefix}-iam-role-with-cluster-access" + assume_role_policy = data.aws_iam_policy_document.userRole.json + managed_policy_arns = [ + aws_iam_policy.default.arn + ] +} + +data "aws_iam_policy_document" "user" { + statement { + actions = [ + "sts:AssumeRole", + "eks:ListClusters", + "eks:ListTagsForResource", + "eks:AccessKubernetesApi", + "eks:DescribeCluster", + ] + effect = "Allow" + resources = ["*"] + } +} + +resource "aws_iam_policy" "default" { + path = "/test/" + policy = data.aws_iam_policy_document.user.json +} + +resource "aws_iam_user_policy_attachment" "default" { + user = aws_iam_user.default.name + policy_arn = aws_iam_policy.default.arn +} + +data "aws_availability_zones" "available" { +} + +resource "aws_iam_access_key" "default" { + user = aws_iam_user.default.name +} + +resource "aws_iam_user" "default" { + name = "${var.static_resource_prefix}-user" + path = "/test/" +} + +resource "aws_subnet" "default" { + count = 2 + + availability_zone = data.aws_availability_zones.available.names[count.index] + cidr_block = "10.0.${count.index}.0/24" + vpc_id = aws_vpc.default.id + map_public_ip_on_launch = true +} + +resource "aws_vpc" "default" { + cidr_block = "10.0.0.0/16" + enable_dns_support = true + enable_dns_hostnames = true +} + +resource "aws_internet_gateway" "default" { + vpc_id = aws_vpc.default.id +} + +resource "aws_route_table" "default" { + vpc_id = aws_vpc.default.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.default.id + } +} + +resource "aws_route_table_association" "default" { + count = 2 + + subnet_id = aws_subnet.default[count.index].id + route_table_id = aws_route_table.default.id +} diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/kubeconfig.tpl b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/kubeconfig.tpl similarity index 94% rename from source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/kubeconfig.tpl rename to source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/kubeconfig.tpl index 52de89ba0..e5cdc262b 100644 --- a/source/Calamari.Tests/KubernetesFixtures/Terraform/Clusters/EKS/kubeconfig.tpl +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/kubeconfig.tpl @@ -1,29 +1,29 @@ -apiVersion: v1 -preferences: {} -kind: Config - -clusters: -- cluster: - server: ${endpoint} - certificate-authority-data: ${cluster_ca} - name: ${cluster_name} - -contexts: -- context: - cluster: ${cluster_name} - user: ${cluster_name} - name: ${cluster_name} - -current-context: ${cluster_name} - -users: -- name: ${cluster_name} - user: - exec: - apiVersion: client.authentication.k8s.io/v1beta1 - command: aws - args: - - eks - - get-token - - --cluster-name=${cluster_name} +apiVersion: v1 +preferences: {} +kind: Config + +clusters: +- cluster: + server: ${endpoint} + certificate-authority-data: ${cluster_ca} + name: ${cluster_name} + +contexts: +- context: + cluster: ${cluster_name} + user: ${cluster_name} + name: ${cluster_name} + +current-context: ${cluster_name} + +users: +- name: ${cluster_name} + user: + exec: + apiVersion: client.authentication.k8s.io/v1beta1 + command: aws + args: + - eks + - get-token + - --cluster-name=${cluster_name} - --region=region \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/main.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/main.tf new file mode 100644 index 000000000..1a53e9c95 --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/main.tf @@ -0,0 +1,3 @@ +variable "static_resource_prefix" { + type = string +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/outputs.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/outputs.tf new file mode 100644 index 000000000..8321de39e --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/outputs.tf @@ -0,0 +1,44 @@ +output "eks_client_id" { + value = aws_iam_access_key.default.id +} + +output "eks_secret_key" { + value = aws_iam_access_key.default.secret + sensitive = true +} + +output "eks_iam_role_arn" { + value = aws_iam_role.user.arn +} + +output "eks_cluster_endpoint" { + description = "Endpoint for EKS control plane." + value = aws_eks_cluster.default.endpoint +} + +output "eks_cluster_ca_certificate" { + value = base64decode(aws_eks_cluster.default.certificate_authority[0].data) + sensitive = true +} + +output "eks_cluster_name" { + description = "EKS name." + value = aws_eks_cluster.default.name +} + +output "eks_cluster_arn" { + description = "EKS ARN" + value = aws_eks_cluster.default.arn +} + +output "aws_vpc_id" { + value = aws_vpc.default.id +} + +output "aws_subnet_id" { + value = aws_subnet.default[0].id +} + +output "aws_iam_instance_profile_name" { + value = aws_iam_instance_profile.profile.name +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/providers.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/providers.tf new file mode 100644 index 000000000..a64967e36 --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/EKS/providers.tf @@ -0,0 +1,16 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.64.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.3.2" + } + } + required_version = ">= 0.15" +} + +provider "aws" { +} diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/gke.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/gke.tf new file mode 100644 index 000000000..5e16cbed3 --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/gke.tf @@ -0,0 +1,58 @@ +# Get latest version +data "google_container_engine_versions" "main" { + location = local.region + project = "octopus-api-tester" + + # Since this is just a string match, it's recommended that you append a . after minor versions + # to ensure that prefixes such as 1.1 don't match versions like 1.12.5-gke.10 accidentally. + version_prefix = "1.28." +} + +# VPC +resource "google_compute_network" "vpc" { + name = "${var.static_resource_prefix}-vpc" + auto_create_subnetworks = "false" + project = "octopus-api-tester" +} + +# Subnet +resource "google_compute_subnetwork" "subnet" { + name = "${var.static_resource_prefix}-subnet" + region = local.region + network = google_compute_network.vpc.name + ip_cidr_range = "10.10.0.0/24" + project = "octopus-api-tester" +} + +locals { + # we will pick the latest k8s version + master_version = data.google_container_engine_versions.main.valid_master_versions[0] +} + +resource "google_container_cluster" "default" { + name = "${var.static_resource_prefix}-gke" + project = "octopus-api-tester" + min_master_version = local.master_version + initial_node_count = 1 + node_config { + preemptible = true + machine_type = "e2-medium" + } + master_auth { + client_certificate_config { + issue_client_certificate = true + } + } + + # to prevent automatic updates to cluster + release_channel { + channel = "UNSPECIFIED" + } + + network = google_compute_network.vpc.name + subnetwork = google_compute_subnetwork.subnet.name +} + +data "google_client_config" "default" { + depends_on = [google_container_cluster.default] +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/main.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/main.tf new file mode 100644 index 000000000..1a53e9c95 --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/main.tf @@ -0,0 +1,3 @@ +variable "static_resource_prefix" { + type = string +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/providers.tf b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/providers.tf new file mode 100644 index 000000000..38ccb214d --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/GKE/providers.tf @@ -0,0 +1,22 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "3.71.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.3.2" + } + } + required_version = ">= 0.15" +} + +locals { + region = "australia-southeast1" +} + +provider "google" { + region = local.region + zone = "australia-southeast1-c" +} \ No newline at end of file diff --git a/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/readme.md b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/readme.md new file mode 100644 index 000000000..db5c76841 --- /dev/null +++ b/source/Calamari.Tests/KubernetesFixtures/Terraform/StaticClusters/readme.md @@ -0,0 +1,10 @@ +# Static test infrastructure + +Static clusters for cloud provider specific authentication tests are provisioned using Terraform Cloud. + +- [EKS configuration](https://app.terraform.io/app/octopus-deploy/workspaces/calamari-testing-kubernetes-static-infrastructure-eks) +- [AKS configuration (In progress)](https://app.terraform.io/app/octopus-deploy/workspaces/calamari-testing-kubernetes-static-infrastructure-sks) +- [GKE configuration](https://app.terraform.io/app/octopus-deploy/workspaces/calamari-testing-kubernetes-static-infrastructure-gke) + +Ensure all the tests that are written against these clusters do not interact with each other. +Tests that do anything more than test authentication/cloud provider specific features should be written to target a local kind instance. \ No newline at end of file diff --git a/source/Calamari.Tests/Some.cs b/source/Calamari.Tests/Some.cs new file mode 100644 index 000000000..dd5a47147 --- /dev/null +++ b/source/Calamari.Tests/Some.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading; + +namespace Calamari.Tests +{ + public static class Some + { + static int next; + + public static string String() => "S__" + Int(); + + public static int Int() => Interlocked.Increment(ref next); + } +} \ No newline at end of file diff --git a/source/Calamari/Commands/CleanCommand.cs b/source/Calamari/Commands/CleanCommand.cs index 06fa9816e..51ad580b2 100644 --- a/source/Calamari/Commands/CleanCommand.cs +++ b/source/Calamari/Commands/CleanCommand.cs @@ -39,7 +39,7 @@ public override int Execute(string[] commandLineArguments) if (days <=0 && releases <= 0) throw new CommandException("A value must be provided for either --days or --releases"); - var deploymentJournal = new DeploymentJournal(fileSystem, SemaphoreFactory.Get(), variables); + var deploymentJournal = new DeploymentJournal(fileSystem, new SystemSemaphoreManager(), variables); var clock = new SystemClock(); var retentionPolicy = new RetentionPolicy(fileSystem, deploymentJournal, clock); diff --git a/source/Calamari/Commands/DeployPackageCommand.cs b/source/Calamari/Commands/DeployPackageCommand.cs index 898418060..daa0228a9 100644 --- a/source/Calamari/Commands/DeployPackageCommand.cs +++ b/source/Calamari/Commands/DeployPackageCommand.cs @@ -100,7 +100,7 @@ public override int Execute(string[] commandLineArguments) featureClasses.Add(new NginxFeature(NginxServer.AutoDetect(), fileSystem)); } - var semaphore = SemaphoreFactory.Get(); + var semaphore = new SystemSemaphoreManager(); var journal = new DeploymentJournal(fileSystem, semaphore, variables); var conventions = new List diff --git a/source/Calamari/Commands/Java/DeployJavaArchiveCommand.cs b/source/Calamari/Commands/Java/DeployJavaArchiveCommand.cs index cdd334ddc..5cd15232d 100644 --- a/source/Calamari/Commands/Java/DeployJavaArchiveCommand.cs +++ b/source/Calamari/Commands/Java/DeployJavaArchiveCommand.cs @@ -78,7 +78,7 @@ public override int Execute(string[] commandLineArguments) Log.Info("Deploying: " + archiveFile); - var semaphore = SemaphoreFactory.Get(); + var semaphore = new SystemSemaphoreManager(); var journal = new DeploymentJournal(fileSystem, semaphore, variables); var jarTools = new JarTool(commandLineRunner, log, fileSystem, variables); var packageExtractor = new JarPackageExtractor(jarTools).WithExtractionLimits(log, variables); diff --git a/source/Calamari/Commands/TransferPackageCommand.cs b/source/Calamari/Commands/TransferPackageCommand.cs index 06a4234a4..cf9fc36cc 100644 --- a/source/Calamari/Commands/TransferPackageCommand.cs +++ b/source/Calamari/Commands/TransferPackageCommand.cs @@ -37,7 +37,7 @@ public override int Execute(string[] commandLineArguments) if (packageFile == null) // required: true in the above call means it will throw rather than return null, but there's no way to tell the compiler that. And ! doesn't work in older frameworks throw new CommandException("Package File path could not be determined"); - var journal = new DeploymentJournal(fileSystem, SemaphoreFactory.Get(), variables); + var journal = new DeploymentJournal(fileSystem, new SystemSemaphoreManager(), variables); var conventions = new List { diff --git a/source/Calamari/Kubernetes/Commands/Executors/GatherAndApplyRawYamlExecutor.cs b/source/Calamari/Kubernetes/Commands/Executors/GatherAndApplyRawYamlExecutor.cs index cc1384c5d..f831dcaf9 100644 --- a/source/Calamari/Kubernetes/Commands/Executors/GatherAndApplyRawYamlExecutor.cs +++ b/source/Calamari/Kubernetes/Commands/Executors/GatherAndApplyRawYamlExecutor.cs @@ -6,7 +6,6 @@ using Calamari.Common.Commands; using Calamari.Common.Plumbing.FileSystem; using Calamari.Common.Plumbing.Logging; -using Calamari.Common.Plumbing.Variables; using Calamari.Kubernetes.Integration; using Calamari.Kubernetes.ResourceStatus.Resources; using Octopus.CoreUtilities.Extensions; @@ -21,15 +20,18 @@ class GatherAndApplyRawYamlExecutor : BaseKubernetesApplyExecutor, IRawYamlKuber { readonly ILog log; readonly ICalamariFileSystem fileSystem; + readonly IManifestReporter manifestReporter; readonly Kubectl kubectl; public GatherAndApplyRawYamlExecutor( ILog log, ICalamariFileSystem fileSystem, + IManifestReporter manifestReporter, Kubectl kubectl) : base(log) { this.log = log; this.fileSystem = fileSystem; + this.manifestReporter = manifestReporter; this.kubectl = kubectl; } @@ -37,7 +39,7 @@ protected override async Task> ApplyAndGetResour { var variables = deployment.Variables; var globs = variables.GetPaths(SpecialVariables.CustomResourceYamlFileName); - + if (globs.IsNullOrEmpty()) return Enumerable.Empty(); @@ -65,38 +67,31 @@ protected override async Task> ApplyAndGetResour IEnumerable ApplyBatchAndReturnResourceIdentifiers(RunningDeployment deployment, GlobDirectory globDirectory) { - if (!LogFoundFiles(globDirectory)) + var files = fileSystem.EnumerateFilesRecursively(globDirectory.Directory).ToArray(); + if (!files.Any()) + { + log.Warn($"No files found matching '{globDirectory.Glob}'"); return Array.Empty(); + } + + ReportEachManifestBeingApplied(globDirectory, files); string[] executeArgs = {"apply", "-f", $@"""{globDirectory.Directory}""", "--recursive", "-o", "json"}; executeArgs = executeArgs.AddOptionsForServerSideApply(deployment.Variables, log); - var result = kubectl.ExecuteCommandAndReturnOutput(executeArgs); return ProcessKubectlCommandOutput(deployment, result, globDirectory.Directory); } - /// - /// Logs files that are found at the relevant glob locations. - /// - /// - /// True if files are found, False if no files exist at this location - bool LogFoundFiles(GlobDirectory globDirectory) + void ReportEachManifestBeingApplied(GlobDirectory globDirectory, string[] files) { var directoryWithTrailingSlash = globDirectory.Directory + Path.DirectorySeparatorChar; - var files = fileSystem.EnumerateFilesRecursively(globDirectory.Directory).ToArray(); - if (!files.Any()) - { - log.Warn($"No files found matching '{globDirectory.Glob}'"); - return false; - } - foreach (var file in files) { - log.Verbose($"Matched file: {fileSystem.GetRelativePath(directoryWithTrailingSlash, file)}"); + var fullFilePath = fileSystem.GetRelativePath(directoryWithTrailingSlash, file); + log.Verbose($"Matched file: {fullFilePath}"); + manifestReporter.ReportManifestApplied(file); } - - return true; } } } \ No newline at end of file diff --git a/source/Calamari/Kubernetes/ManifestReporter.cs b/source/Calamari/Kubernetes/ManifestReporter.cs new file mode 100644 index 000000000..b5c54ef6c --- /dev/null +++ b/source/Calamari/Kubernetes/ManifestReporter.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Calamari.Common.Plumbing.FileSystem; +using Calamari.Common.Plumbing.Logging; +using Calamari.Common.Plumbing.ServiceMessages; +using Calamari.Common.Plumbing.Variables; +using YamlDotNet.Core; +using YamlDotNet.RepresentationModel; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Calamari.Kubernetes +{ + public interface IManifestReporter + { + void ReportManifestApplied(string filePath); + } + + public class ManifestReporter : IManifestReporter + { + readonly IVariables variables; + readonly ICalamariFileSystem fileSystem; + readonly ILog log; + + static readonly IDeserializer YamlDeserializer = new Deserializer(); + + static readonly ISerializer YamlSerializer = new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .JsonCompatible() + .Build(); + + public ManifestReporter(IVariables variables, ICalamariFileSystem fileSystem, ILog log) + { + this.variables = variables; + this.fileSystem = fileSystem; + this.log = log; + } + + string GetNamespace(YamlMappingNode yamlRoot) + { + var implicitNamespace = variables.Get(SpecialVariables.Namespace) ?? "default"; + + if (yamlRoot.Children.TryGetValue("metadata", out var metadataNode) && metadataNode is YamlMappingNode metadataMappingNode && + metadataMappingNode.Children.TryGetValue("namespace", out var namespaceNode) && namespaceNode is YamlScalarNode namespaceScalarNode && + !string.IsNullOrWhiteSpace(namespaceScalarNode.Value)) + { + implicitNamespace = namespaceScalarNode.Value; + } + + return implicitNamespace; + } + + public void ReportManifestApplied(string filePath) + { + using (var yamlFile = fileSystem.OpenFile(filePath, FileAccess.ReadWrite)) + { + try + { + var yamlStream = new YamlStream(); + yamlStream.Load(new StreamReader(yamlFile)); + + foreach (var document in yamlStream.Documents) + { + if (!(document.RootNode is YamlMappingNode rootNode)) + { + log.Warn("Could not parse manifest, resources will not be added to live object status"); + continue; + } + + var updatedDocument = YamlNodeToJson(rootNode); + + var ns = GetNamespace(rootNode); + log.WriteServiceMessage(new ServiceMessage(SpecialVariables.ServiceMessageNames.ManifestApplied.Name, + new Dictionary + { + { SpecialVariables.ServiceMessageNames.ManifestApplied.ManifestAttribute, updatedDocument }, + { SpecialVariables.ServiceMessageNames.ManifestApplied.NamespaceAttribute, ns } + })); + } + } + catch (SemanticErrorException) + { + log.Warn("Invalid YAML syntax found, resources will not be added to live object status"); + } + } + } + + static string YamlNodeToJson(YamlNode node) + { + var stream = new YamlStream { new YamlDocument(node) }; + using (var writer = new StringWriter()) + { + stream.Save(writer); + + using (var reader = new StringReader(writer.ToString())) + { + var yamlObject = YamlDeserializer.Deserialize(reader); + return yamlObject is null ? string.Empty : YamlSerializer.Serialize(yamlObject).Trim(); + } + } + } + } +} \ No newline at end of file diff --git a/source/Calamari/Kubernetes/ResourceStatus/ResourceUpdateReporter.cs b/source/Calamari/Kubernetes/ResourceStatus/ResourceUpdateReporter.cs index f596fc9d7..3c7096074 100644 --- a/source/Calamari/Kubernetes/ResourceStatus/ResourceUpdateReporter.cs +++ b/source/Calamari/Kubernetes/ResourceStatus/ResourceUpdateReporter.cs @@ -99,7 +99,7 @@ private void SendServiceMessage(Resource resource, bool removed, int checkCount) {"checkCount", checkCount.ToString()} }; - var message = new ServiceMessage(SpecialVariables.KubernetesResourceStatusServiceMessageName, parameters); + var message = new ServiceMessage(SpecialVariables.ServiceMessageNames.ResourceStatus.Name, parameters); log.WriteServiceMessage(message); } } diff --git a/source/Calamari/Kubernetes/SpecialVariables.cs b/source/Calamari/Kubernetes/SpecialVariables.cs index 1c3b6be11..aa79808c7 100644 --- a/source/Calamari/Kubernetes/SpecialVariables.cs +++ b/source/Calamari/Kubernetes/SpecialVariables.cs @@ -36,8 +36,6 @@ public static class SpecialVariables public const string KubeConfig = "Octopus.KubeConfig.Path"; public const string KustomizeManifest = "Octopus.Kustomize.Manifest.Path"; - public const string KubernetesResourceStatusServiceMessageName = "k8s-status"; - public const string ServerSideApplyEnabled = "Octopus.Action.Kubernetes.ServerSideApply.Enabled"; public const string ServerSideApplyForceConflicts = "Octopus.Action.Kubernetes.ServerSideApply.ForceConflicts"; @@ -66,5 +64,20 @@ public static string ValuesFilePath(string key) } } } + + public class ServiceMessageNames + { + public static class ResourceStatus + { + public const string Name = "k8s-status"; + } + + public static class ManifestApplied + { + public const string Name = "k8s-manifest-applied"; + public const string ManifestAttribute = "manifest"; + public const string NamespaceAttribute = "ns"; + } + } } } diff --git a/source/Calamari/Program.cs b/source/Calamari/Program.cs index 837b19177..8a245b7e1 100644 --- a/source/Calamari/Program.cs +++ b/source/Calamari/Program.cs @@ -28,6 +28,7 @@ using Calamari.Azure.Kubernetes.Discovery; using Calamari.Integration.FullFramework; using Calamari.Integration.Iis; +using Calamari.Kubernetes; using Calamari.Kubernetes.Commands.Executors; namespace Calamari @@ -73,8 +74,7 @@ protected override void ConfigureContainer(ContainerBuilder builder, CommonOptio builder.RegisterType().As().SingleInstance(); builder.RegisterType().AsSelf(); builder.RegisterType().As().SingleInstance(); - - + //TODO: Once this runs on both netcore and full framework, this can be converted to a runtime conditional check #if WINDOWS_CERTIFICATE_STORE_SUPPORT @@ -85,7 +85,7 @@ protected override void ConfigureContainer(ContainerBuilder builder, CommonOptio #else builder.RegisterType().As().SingleInstance(); #endif - + builder.RegisterType().As().SingleInstance(); builder.RegisterType().As(); builder.RegisterType().As(); builder.RegisterType().As(); @@ -98,7 +98,7 @@ protected override void ConfigureContainer(ContainerBuilder builder, CommonOptio .As() .SingleInstance(); - builder.RegisterInstance(SemaphoreFactory.Get()).As(); + builder.RegisterInstance(new SystemSemaphoreManager()).As(); builder.RegisterModule();