From b14ad1751d39e7b8de99f83933027af32da45eac Mon Sep 17 00:00:00 2001 From: Alastair Pitts Date: Mon, 25 Mar 2024 14:52:32 +1100 Subject: [PATCH] Change TentacleClient.ExecuteScript to use new ExecuteScriptCommand type (#845) * Change to new ExecuteScriptCommand type * Clean up parameter orders * Change nullability * Change IsolationMutexName to be required and fix tests * Minor cleanup * Move builders into the test utils as they aren't needed in the contracts anymore * Create more distinct TentacleClient contract types & include builders * Fix tests --- .../ITentacleClient.cs | 8 +- .../Scripts/IScriptOrchestrator.cs | 5 +- .../ExecuteKubernetesScriptCommandBuilder.cs | 33 ++++++ .../Builders/ExecuteScriptCommandBuilder.cs | 102 ++++++++++++++++++ .../ExecuteShellScriptCommandBuilder.cs | 31 ++++++ .../Models/ExecuteKubernetesScriptCommand.cs | 25 +++++ .../Scripts/Models/ExecuteScriptCommand.cs | 45 ++++++++ .../Models/ExecuteShellScriptCommand.cs | 25 +++++ .../Models/KubernetesImageConfiguration.cs | 18 ++++ .../{ => Models}/ScriptExecutionResult.cs | 4 +- .../{ => Models}/ScriptExecutionStatus.cs | 5 +- .../Models/ScriptIsolationConfiguration.cs | 13 +++ .../Scripts/ObservingScriptOrchestrator.cs | 11 +- .../Scripts/OnScriptCompleted.cs | 8 ++ .../Scripts/OnScriptStatusResponseReceived.cs | 5 +- .../Scripts/ScriptOrchestratorFactory.cs | 1 - .../Scripts/ScriptServiceV1Orchestrator.cs | 28 +++-- .../Scripts/ScriptServiceV2Orchestrator.cs | 37 ++++--- .../ScriptServiceV3AlphaOrchestrator.cs | 58 +++++++--- .../Octopus.Tentacle.Client/TentacleClient.cs | 8 +- .../LatestStartScriptCommandBuilder.cs | 15 --- ...ptCommandV3AlphaBuilderExtensionMethods.cs | 19 ---- .../KubernetesAgentScriptExecutionContext.cs | 2 +- .../CapabilitiesServiceV2Test.cs | 4 +- .../ClientGathersRpcCallMetrics.cs | 8 +- .../ClientScriptExecutionAdditionalScripts.cs | 6 +- ...ionCanBeCancelledWhenRetriesAreDisabled.cs | 35 +++--- ...tionCanBeCancelledWhenRetriesAreEnabled.cs | 73 ++++++------- ...iptExecutionCanRecoverFromNetworkIssues.cs | 28 ++--- .../ClientScriptExecutionIsolationMutex.cs | 40 +++---- .../ClientScriptExecutionRetriesTimeout.cs | 38 +++---- ...lientScriptExecutionScriptArgumentsWork.cs | 8 +- ...ClientScriptExecutionScriptFilesAreSent.cs | 6 +- ...NonV1IsNotRetriedWhenRetriesAreDisabled.cs | 20 ++-- ...iptExecutionScriptServiceV1IsNotRetried.cs | 22 ++-- ...criptExecutionWorksWithMultipleVersions.cs | 4 +- ...orObservesScriptObserverBackoffStrategy.cs | 4 +- .../DisablingScriptServiceV3AlphaTests.cs | 4 +- .../ScriptServiceV2IntegrationTest.cs | 26 ++--- .../ScriptServiceV3AlphaIntegrationTest.cs | 22 ++-- .../ScriptExecutionResultExtensionMethods.cs | 2 +- .../TentacleClientObserver.cs | 8 +- .../TentacleStartupAndShutdownTests.cs | 10 +- .../TentacleClientExtensionMethods.cs | 6 +- ...uteScriptCommandBuilderExtensionMethods.cs | 35 ++++++ .../TestExecuteShellScriptCommandBuilder.cs | 15 +++ .../TentacleClientTestExtensionMethods.cs | 5 +- .../WorkspaceCleanerTests.cs | 4 +- .../ScriptServiceV3AlphaFixture.cs | 25 ++--- 49 files changed, 662 insertions(+), 302 deletions(-) create mode 100644 source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteKubernetesScriptCommandBuilder.cs create mode 100644 source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteScriptCommandBuilder.cs create mode 100644 source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteShellScriptCommandBuilder.cs create mode 100644 source/Octopus.Tentacle.Client/Scripts/Models/ExecuteKubernetesScriptCommand.cs create mode 100644 source/Octopus.Tentacle.Client/Scripts/Models/ExecuteScriptCommand.cs create mode 100644 source/Octopus.Tentacle.Client/Scripts/Models/ExecuteShellScriptCommand.cs create mode 100644 source/Octopus.Tentacle.Client/Scripts/Models/KubernetesImageConfiguration.cs rename source/Octopus.Tentacle.Client/Scripts/{ => Models}/ScriptExecutionResult.cs (80%) rename source/Octopus.Tentacle.Client/Scripts/{ => Models}/ScriptExecutionStatus.cs (71%) create mode 100644 source/Octopus.Tentacle.Client/Scripts/Models/ScriptIsolationConfiguration.cs create mode 100644 source/Octopus.Tentacle.Client/Scripts/OnScriptCompleted.cs delete mode 100644 source/Octopus.Tentacle.CommonTestUtils/Builders/LatestStartScriptCommandBuilder.cs create mode 100644 source/Octopus.Tentacle.Tests.Integration/Util/Builders/TestExecuteScriptCommandBuilderExtensionMethods.cs create mode 100644 source/Octopus.Tentacle.Tests.Integration/Util/Builders/TestExecuteShellScriptCommandBuilder.cs diff --git a/source/Octopus.Tentacle.Client/ITentacleClient.cs b/source/Octopus.Tentacle.Client/ITentacleClient.cs index 25f84b6ba..3e6275e52 100644 --- a/source/Octopus.Tentacle.Client/ITentacleClient.cs +++ b/source/Octopus.Tentacle.Client/ITentacleClient.cs @@ -4,9 +4,8 @@ using Halibut; using Octopus.Diagnostics; using Octopus.Tentacle.Client.Scripts; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ScriptServiceV2; -using Octopus.Tentacle.Contracts.ScriptServiceV3Alpha; namespace Octopus.Tentacle.Client { @@ -18,14 +17,15 @@ public interface ITentacleClient : IDisposable /// /// Execute a script on Tentacle /// - /// The start script command + /// The execute script command /// /// Called when the script has finished executing on Tentacle but before the working directory is cleaned up. /// This is called regardless of the outcome of the script. It will also be called if the script execution is cancelled. /// Used to output user orientated log messages /// When cancelled, will attempt to stop the execution of the script on Tentacle before returning. /// - Task ExecuteScript(StartScriptCommandV3Alpha startScriptCommand, + Task ExecuteScript( + ExecuteScriptCommand executeScriptCommand, OnScriptStatusResponseReceived onScriptStatusResponseReceived, OnScriptCompleted onScriptCompleted, ILog logger, diff --git a/source/Octopus.Tentacle.Client/Scripts/IScriptOrchestrator.cs b/source/Octopus.Tentacle.Client/Scripts/IScriptOrchestrator.cs index 11bb7e6c9..c0026d809 100644 --- a/source/Octopus.Tentacle.Client/Scripts/IScriptOrchestrator.cs +++ b/source/Octopus.Tentacle.Client/Scripts/IScriptOrchestrator.cs @@ -1,13 +1,12 @@ using System; using System.Threading; using System.Threading.Tasks; -using Octopus.Tentacle.Contracts.ScriptServiceV2; -using Octopus.Tentacle.Contracts.ScriptServiceV3Alpha; +using Octopus.Tentacle.Client.Scripts.Models; namespace Octopus.Tentacle.Client.Scripts { interface IScriptOrchestrator { - Task ExecuteScript(StartScriptCommandV3Alpha startScriptCommand, CancellationToken scriptExecutionCancellationToken); + Task ExecuteScript(ExecuteScriptCommand command, CancellationToken scriptExecutionCancellationToken); } } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteKubernetesScriptCommandBuilder.cs b/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteKubernetesScriptCommandBuilder.cs new file mode 100644 index 000000000..7228737e2 --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteKubernetesScriptCommandBuilder.cs @@ -0,0 +1,33 @@ +using System; +using Octopus.Tentacle.Contracts; + +namespace Octopus.Tentacle.Client.Scripts.Models.Builders +{ + public class ExecuteKubernetesScriptCommandBuilder : ExecuteScriptCommandBuilder + { + KubernetesImageConfiguration? configuration; + + public ExecuteKubernetesScriptCommandBuilder(string taskId) + : base(taskId, ScriptIsolationLevel.NoIsolation) //Kubernetes Agents don't need isolation since the scripts won't clash with each other (it won't clash more than Workers anyway) + { + } + + public ExecuteKubernetesScriptCommandBuilder SetKubernetesImageConfiguration(KubernetesImageConfiguration configuration) + { + this.configuration = configuration; + return this; + } + + public override ExecuteScriptCommand Build() + => new ExecuteKubernetesScriptCommand( + ScriptTicket, + TaskId, + ScriptBody.ToString(), + Arguments.ToArray(), + IsolationConfiguration, + AdditionalScripts, + Files.ToArray(), + configuration + ); + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteScriptCommandBuilder.cs b/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteScriptCommandBuilder.cs new file mode 100644 index 000000000..5c708c3b8 --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteScriptCommandBuilder.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Octopus.Tentacle.Contracts; +using Octopus.Tentacle.Contracts.Builders; + +namespace Octopus.Tentacle.Client.Scripts.Models.Builders +{ + public abstract class ExecuteScriptCommandBuilder + { + protected List Files { get; private set; } = new(); + protected List Arguments { get; private set; } = new(); + protected Dictionary AdditionalScripts { get; private set; } = new(); + protected StringBuilder ScriptBody { get; private set; } = new(string.Empty); + protected string TaskId { get; } + protected ScriptTicket ScriptTicket { get; } + protected ScriptIsolationConfiguration IsolationConfiguration { get; private set; } + + protected ExecuteScriptCommandBuilder(string taskId, ScriptIsolationLevel defaultIsolationLevel) + { + TaskId = taskId; + ScriptTicket = new UniqueScriptTicketBuilder().Build(); + + IsolationConfiguration = new ScriptIsolationConfiguration( + defaultIsolationLevel, + "RunningScript", + ScriptIsolationConfiguration.NoTimeout); + } + + public ExecuteScriptCommandBuilder SetScriptBody(string scriptBody) + { + ScriptBody = new StringBuilder(scriptBody); + return this; + } + + public ExecuteScriptCommandBuilder ReplaceInScriptBody(string oldValue, string newValue) + { + ScriptBody.Replace(oldValue, newValue); + return this; + } + + public ExecuteScriptCommandBuilder AddAdditionalScriptType(ScriptType scriptType, string scriptBody) + { + AdditionalScripts.Add(scriptType, scriptBody); + return this; + } + + public ExecuteScriptCommandBuilder SetIsolationLevel(ScriptIsolationLevel isolation) + { + IsolationConfiguration = IsolationConfiguration with { IsolationLevel= isolation }; + return this; + } + + public ExecuteScriptCommandBuilder SetNoIsolationLevel() => SetIsolationLevel(ScriptIsolationLevel.NoIsolation); + public ExecuteScriptCommandBuilder SetFullIsolationLevel() => SetIsolationLevel(ScriptIsolationLevel.FullIsolation); + + public ExecuteScriptCommandBuilder SetFiles(IEnumerable? files) + { + Files.Clear(); + if (files is not null) + { + Files.AddRange(files); + } + + return this; + } + + public ExecuteScriptCommandBuilder SetArguments(IEnumerable? arguments) + { + Arguments.Clear(); + if (arguments is not null) + { + Arguments.AddRange(arguments); + } + + return this; + } + + public ExecuteScriptCommandBuilder SetIsolationMutexTimeout(TimeSpan scriptIsolationMutexTimeout) + { + IsolationConfiguration = IsolationConfiguration with { MutexTimeout= scriptIsolationMutexTimeout }; + return this; + } + + public ExecuteScriptCommandBuilder SetNoIsolationMutexTimeout() => SetIsolationMutexTimeout(ScriptIsolationConfiguration.NoTimeout); + + public ExecuteScriptCommandBuilder SetIsolationMutexName(string name) + { + IsolationConfiguration = IsolationConfiguration with { MutexName= name }; + return this; + } + + public abstract ExecuteScriptCommand Build(); + + + public ExecuteScriptCommandBuilder AddFile(ScriptFile scriptFile) + { + Files.Add(scriptFile); + return this; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteShellScriptCommandBuilder.cs b/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteShellScriptCommandBuilder.cs new file mode 100644 index 000000000..ff950b661 --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/Models/Builders/ExecuteShellScriptCommandBuilder.cs @@ -0,0 +1,31 @@ +using System; +using Octopus.Tentacle.Contracts; + +namespace Octopus.Tentacle.Client.Scripts.Models.Builders +{ + public class ExecuteShellScriptCommandBuilder : ExecuteScriptCommandBuilder + { + TimeSpan? durationStartScriptCanWaitForScriptToFinish = TimeSpan.FromSeconds(5); // The UI refreshes every 5 seconds, so 5 seconds here might be reasonable. + + public ExecuteShellScriptCommandBuilder(string taskId, ScriptIsolationLevel defaultIsolationLevel) : base(taskId, defaultIsolationLevel) + { + } + + public ExecuteScriptCommandBuilder SetDurationStartScriptCanWaitForScriptToFinish(TimeSpan? duration) + { + durationStartScriptCanWaitForScriptToFinish = duration; + return this; + } + + public override ExecuteScriptCommand Build() + => new ExecuteShellScriptCommand( + ScriptTicket, + TaskId, + ScriptBody.ToString(), + Arguments.ToArray(), + IsolationConfiguration, + AdditionalScripts, + Files.ToArray(), + durationStartScriptCanWaitForScriptToFinish); + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteKubernetesScriptCommand.cs b/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteKubernetesScriptCommand.cs new file mode 100644 index 000000000..c28102cd0 --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteKubernetesScriptCommand.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Octopus.Tentacle.Contracts; + +namespace Octopus.Tentacle.Client.Scripts.Models +{ + public class ExecuteKubernetesScriptCommand : ExecuteScriptCommand + { + public KubernetesImageConfiguration? ImageConfiguration { get; } + + public ExecuteKubernetesScriptCommand( + ScriptTicket scriptTicket, + string taskId, + string scriptBody, + string[] arguments, + ScriptIsolationConfiguration isolationConfiguration, + Dictionary? additionalScripts, + ScriptFile[] additionalFiles, + KubernetesImageConfiguration? imageConfiguration) + : base(scriptTicket, taskId, scriptBody, arguments, isolationConfiguration, additionalScripts, additionalFiles) + { + ImageConfiguration = imageConfiguration; + } + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteScriptCommand.cs b/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteScriptCommand.cs new file mode 100644 index 000000000..7c2716d54 --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteScriptCommand.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Octopus.Tentacle.Contracts; + +namespace Octopus.Tentacle.Client.Scripts.Models +{ + public abstract class ExecuteScriptCommand + { + protected ExecuteScriptCommand( + ScriptTicket scriptTicket, + string taskId, + string scriptBody, + string[] arguments, + ScriptIsolationConfiguration isolationConfiguration, + Dictionary? additionalScripts = null, + ScriptFile[]? additionalFiles = null) + { + ScriptBody = scriptBody; + TaskId = taskId; + ScriptTicket = scriptTicket; + Arguments = arguments; + IsolationConfiguration = isolationConfiguration; + + foreach (var additionalScript in additionalScripts ?? Enumerable.Empty>()) + { + Scripts.Add(additionalScript.Key, additionalScript.Value); + } + + if (additionalFiles is not null) + Files.AddRange(additionalFiles); + } + + public string ScriptBody { get; } + public string[] Arguments { get; } + public string TaskId { get; } + public ScriptTicket ScriptTicket { get; } + public ScriptIsolationConfiguration IsolationConfiguration { get; } + + public Dictionary Scripts { get; } = new(); + + public List Files { get; } = new(); + + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteShellScriptCommand.cs b/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteShellScriptCommand.cs new file mode 100644 index 000000000..c4cb4b653 --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/Models/ExecuteShellScriptCommand.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Octopus.Tentacle.Contracts; + +namespace Octopus.Tentacle.Client.Scripts.Models +{ + public class ExecuteShellScriptCommand : ExecuteScriptCommand + { + public ExecuteShellScriptCommand( + ScriptTicket scriptTicket, + string taskId, + string scriptBody, + string[] arguments, + ScriptIsolationConfiguration isolationConfiguration, + Dictionary? additionalScripts = null, + ScriptFile[]? additionalFiles = null, + TimeSpan? durationToWaitForScriptToFinish = null) + : base(scriptTicket, taskId, scriptBody, arguments, isolationConfiguration, additionalScripts, additionalFiles) + { + DurationToWaitForScriptToFinish = durationToWaitForScriptToFinish; + } + + public TimeSpan? DurationToWaitForScriptToFinish { get; } + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/Models/KubernetesImageConfiguration.cs b/source/Octopus.Tentacle.Client/Scripts/Models/KubernetesImageConfiguration.cs new file mode 100644 index 000000000..9a1d2418f --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/Models/KubernetesImageConfiguration.cs @@ -0,0 +1,18 @@ +namespace Octopus.Tentacle.Client.Scripts.Models +{ + public class KubernetesImageConfiguration + { + public KubernetesImageConfiguration(string image, string? feedUrl, string? feedUsername, string? feedPassword) + { + Image = image; + FeedUrl = feedUrl; + FeedUsername = feedUsername; + FeedPassword = feedPassword; + } + + public string Image { get; } + public string? FeedUrl { get; } + public string? FeedUsername { get; } + public string? FeedPassword { get; } + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/ScriptExecutionResult.cs b/source/Octopus.Tentacle.Client/Scripts/Models/ScriptExecutionResult.cs similarity index 80% rename from source/Octopus.Tentacle.Client/Scripts/ScriptExecutionResult.cs rename to source/Octopus.Tentacle.Client/Scripts/Models/ScriptExecutionResult.cs index e1c07e2c3..b766f8ea5 100644 --- a/source/Octopus.Tentacle.Client/Scripts/ScriptExecutionResult.cs +++ b/source/Octopus.Tentacle.Client/Scripts/Models/ScriptExecutionResult.cs @@ -1,7 +1,7 @@ -using System.Collections.Generic; +using System; using Octopus.Tentacle.Contracts; -namespace Octopus.Tentacle.Client.Scripts +namespace Octopus.Tentacle.Client.Scripts.Models { public class ScriptExecutionResult { diff --git a/source/Octopus.Tentacle.Client/Scripts/ScriptExecutionStatus.cs b/source/Octopus.Tentacle.Client/Scripts/Models/ScriptExecutionStatus.cs similarity index 71% rename from source/Octopus.Tentacle.Client/Scripts/ScriptExecutionStatus.cs rename to source/Octopus.Tentacle.Client/Scripts/Models/ScriptExecutionStatus.cs index 1f35ef80b..b80a3b1d4 100644 --- a/source/Octopus.Tentacle.Client/Scripts/ScriptExecutionStatus.cs +++ b/source/Octopus.Tentacle.Client/Scripts/Models/ScriptExecutionStatus.cs @@ -1,7 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Octopus.Tentacle.Contracts; -namespace Octopus.Tentacle.Client.Scripts +namespace Octopus.Tentacle.Client.Scripts.Models { public class ScriptExecutionStatus { diff --git a/source/Octopus.Tentacle.Client/Scripts/Models/ScriptIsolationConfiguration.cs b/source/Octopus.Tentacle.Client/Scripts/Models/ScriptIsolationConfiguration.cs new file mode 100644 index 000000000..9f6c20a67 --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/Models/ScriptIsolationConfiguration.cs @@ -0,0 +1,13 @@ +using System; +using Octopus.Tentacle.Contracts; + +namespace Octopus.Tentacle.Client.Scripts.Models +{ + public record ScriptIsolationConfiguration( + ScriptIsolationLevel IsolationLevel, + string MutexName, + TimeSpan MutexTimeout) + { + public static readonly TimeSpan NoTimeout= TimeSpan.FromMilliseconds(int.MaxValue); + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/ObservingScriptOrchestrator.cs b/source/Octopus.Tentacle.Client/Scripts/ObservingScriptOrchestrator.cs index 5783f38d6..47d7fd64c 100644 --- a/source/Octopus.Tentacle.Client/Scripts/ObservingScriptOrchestrator.cs +++ b/source/Octopus.Tentacle.Client/Scripts/ObservingScriptOrchestrator.cs @@ -1,9 +1,8 @@ using System; using System.Threading; using System.Threading.Tasks; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ScriptServiceV2; -using Octopus.Tentacle.Contracts.ScriptServiceV3Alpha; namespace Octopus.Tentacle.Client.Scripts { @@ -27,9 +26,9 @@ protected ObservingScriptOrchestrator( this.onScriptCompleted = onScriptCompleted; } - public async Task ExecuteScript(StartScriptCommandV3Alpha startScriptCommand, CancellationToken scriptExecutionCancellationToken) + public async Task ExecuteScript(ExecuteScriptCommand command, CancellationToken scriptExecutionCancellationToken) { - var mappedStartCommand = Map(startScriptCommand); + var mappedStartCommand = Map(command); var scriptStatusResponse = await StartScript(mappedStartCommand, scriptExecutionCancellationToken).ConfigureAwait(false); @@ -83,7 +82,7 @@ async Task ObserveUntilComplete( } catch (Exception) { - if (scriptExecutionCancellationToken.IsCancellationRequested) + if (scriptExecutionCancellationToken.IsCancellationRequested) { continue; // Enter cancellation mode. } @@ -116,7 +115,7 @@ await Task.Delay(scriptObserverBackOffStrategy.GetBackoff(++iteration), scriptEx return lastStatusResponse; } - protected abstract TStartCommand Map(StartScriptCommandV3Alpha command); + protected abstract TStartCommand Map(ExecuteScriptCommand command); protected abstract ScriptExecutionStatus MapToStatus(TScriptStatusResponse response); diff --git a/source/Octopus.Tentacle.Client/Scripts/OnScriptCompleted.cs b/source/Octopus.Tentacle.Client/Scripts/OnScriptCompleted.cs new file mode 100644 index 000000000..6b65026a4 --- /dev/null +++ b/source/Octopus.Tentacle.Client/Scripts/OnScriptCompleted.cs @@ -0,0 +1,8 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Octopus.Tentacle.Client.Scripts +{ + public delegate Task OnScriptCompleted(CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/OnScriptStatusResponseReceived.cs b/source/Octopus.Tentacle.Client/Scripts/OnScriptStatusResponseReceived.cs index 5c15a8a59..24c1aadf8 100644 --- a/source/Octopus.Tentacle.Client/Scripts/OnScriptStatusResponseReceived.cs +++ b/source/Octopus.Tentacle.Client/Scripts/OnScriptStatusResponseReceived.cs @@ -1,7 +1,6 @@ -using System.Threading; -using System.Threading.Tasks; +using Octopus.Tentacle.Client.Scripts.Models; + namespace Octopus.Tentacle.Client.Scripts { public delegate void OnScriptStatusResponseReceived(ScriptExecutionStatus status); - public delegate Task OnScriptCompleted(CancellationToken cancellationToken); } \ No newline at end of file diff --git a/source/Octopus.Tentacle.Client/Scripts/ScriptOrchestratorFactory.cs b/source/Octopus.Tentacle.Client/Scripts/ScriptOrchestratorFactory.cs index 7ef92cf08..1152b4d61 100644 --- a/source/Octopus.Tentacle.Client/Scripts/ScriptOrchestratorFactory.cs +++ b/source/Octopus.Tentacle.Client/Scripts/ScriptOrchestratorFactory.cs @@ -1,7 +1,6 @@ using System; using System.Threading; using System.Threading.Tasks; -using Halibut; using Halibut.ServiceModel; using Octopus.Diagnostics; using Octopus.Tentacle.Client.Capabilities; diff --git a/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV1Orchestrator.cs b/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV1Orchestrator.cs index c8d1bd0bc..442af157d 100644 --- a/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV1Orchestrator.cs +++ b/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV1Orchestrator.cs @@ -1,13 +1,14 @@ +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Halibut.ServiceModel; using Octopus.Tentacle.Client.Execution; using Octopus.Tentacle.Client.Observability; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.Contracts; using Octopus.Tentacle.Contracts.ClientServices; using Octopus.Tentacle.Contracts.Observability; -using Octopus.Tentacle.Contracts.ScriptServiceV3Alpha; using ILog = Octopus.Diagnostics.ILog; namespace Octopus.Tentacle.Client.Scripts @@ -41,16 +42,21 @@ public ScriptServiceV1Orchestrator( this.logger = logger; } - protected override StartScriptCommand Map(StartScriptCommandV3Alpha command) - => new( - command.ScriptBody, - command.Isolation, - command.ScriptIsolationMutexTimeout, - command.IsolationMutexName!, - command.Arguments, - command.TaskId, - command.Scripts, - command.Files.ToArray()); + protected override StartScriptCommand Map(ExecuteScriptCommand command) + { + if (command is not ExecuteShellScriptCommand shellScriptCommand) + throw new InvalidOperationException($"{nameof(ScriptServiceV2Orchestrator)} only supports commands of type {nameof(ExecuteShellScriptCommand)}."); + + return new StartScriptCommand( + shellScriptCommand.ScriptBody, + shellScriptCommand.IsolationConfiguration.IsolationLevel, + shellScriptCommand.IsolationConfiguration.MutexTimeout, + shellScriptCommand.IsolationConfiguration.MutexName, + shellScriptCommand.Arguments, + shellScriptCommand.TaskId, + shellScriptCommand.Scripts, + shellScriptCommand.Files.ToArray()); + } protected override ScriptExecutionStatus MapToStatus(ScriptStatusResponse response) => new(response.Logs); diff --git a/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV2Orchestrator.cs b/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV2Orchestrator.cs index d21db7eac..2f81fd2ad 100644 --- a/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV2Orchestrator.cs +++ b/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV2Orchestrator.cs @@ -3,16 +3,14 @@ using System.Threading; using System.Threading.Tasks; using Halibut; -using Halibut.Exceptions; using Halibut.ServiceModel; -using Halibut.Transport; using Octopus.Tentacle.Client.Execution; using Octopus.Tentacle.Client.Observability; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.Contracts; using Octopus.Tentacle.Contracts.ClientServices; using Octopus.Tentacle.Contracts.Observability; using Octopus.Tentacle.Contracts.ScriptServiceV2; -using Octopus.Tentacle.Contracts.ScriptServiceV3Alpha; using ILog = Octopus.Diagnostics.ILog; namespace Octopus.Tentacle.Client.Scripts @@ -47,18 +45,23 @@ public ScriptServiceV2Orchestrator( this.logger = logger; } - protected override StartScriptCommandV2 Map(StartScriptCommandV3Alpha command) - => new( - command.ScriptBody, - command.Isolation, - command.ScriptIsolationMutexTimeout, - command.IsolationMutexName!, - command.Arguments, - command.TaskId, - command.ScriptTicket, - command.DurationToWaitForScriptToFinish, - command.Scripts, - command.Files.ToArray()); + protected override StartScriptCommandV2 Map(ExecuteScriptCommand command) + { + if (command is not ExecuteShellScriptCommand shellScriptCommand) + throw new InvalidOperationException($"{nameof(ScriptServiceV2Orchestrator)} only supports commands of type {nameof(ExecuteShellScriptCommand)}."); + + return new StartScriptCommandV2( + shellScriptCommand.ScriptBody, + shellScriptCommand.IsolationConfiguration.IsolationLevel, + shellScriptCommand.IsolationConfiguration.MutexTimeout, + shellScriptCommand.IsolationConfiguration.MutexName, + shellScriptCommand.Arguments, + shellScriptCommand.TaskId, + shellScriptCommand.ScriptTicket, + shellScriptCommand.DurationToWaitForScriptToFinish, + shellScriptCommand.Scripts, + shellScriptCommand.Files.ToArray()); + } protected override ScriptExecutionStatus MapToStatus(ScriptStatusResponseV2 response) => new(response.Logs); @@ -200,7 +203,7 @@ protected override async Task Finish(ScriptStatusRespons { // Finish performs a best effort cleanup of the Workspace on Tentacle // If we are cancelling script execution we abandon a call to complete script after a period of time - + using var completeScriptCancellationTokenSource = new CancellationTokenSource(); await using var _ = scriptExecutionCancellationToken.Register(() => completeScriptCancellationTokenSource.CancelAfter(onCancellationAbandonCompleteScriptAfter)); @@ -214,7 +217,7 @@ await rpcCallExecutor.ExecuteWithNoRetries( }, logger, clientOperationMetricsBuilder, - completeScriptCancellationTokenSource.Token); + completeScriptCancellationTokenSource.Token); } catch (Exception ex) when (ex is HalibutClientException or OperationCanceledException) { diff --git a/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV3AlphaOrchestrator.cs b/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV3AlphaOrchestrator.cs index 7e40a00a4..3f502a30b 100644 --- a/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV3AlphaOrchestrator.cs +++ b/source/Octopus.Tentacle.Client/Scripts/ScriptServiceV3AlphaOrchestrator.cs @@ -3,11 +3,10 @@ using System.Threading; using System.Threading.Tasks; using Halibut; -using Halibut.Exceptions; using Halibut.ServiceModel; -using Halibut.Transport; using Octopus.Tentacle.Client.Execution; using Octopus.Tentacle.Client.Observability; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.Contracts; using Octopus.Tentacle.Contracts.ClientServices; using Octopus.Tentacle.Contracts.Observability; @@ -46,7 +45,42 @@ public ScriptServiceV3AlphaOrchestrator( this.logger = logger; } - protected override StartScriptCommandV3Alpha Map(StartScriptCommandV3Alpha command) => command; + protected override StartScriptCommandV3Alpha Map(ExecuteScriptCommand command) + { + IScriptExecutionContext executionContext = command switch + { + ExecuteKubernetesScriptCommand { ImageConfiguration: not null } kubernetesScriptCommand => new KubernetesAgentScriptExecutionContext( + kubernetesScriptCommand.ImageConfiguration.Image, + kubernetesScriptCommand.ImageConfiguration.FeedUrl, + kubernetesScriptCommand.ImageConfiguration.FeedUsername, + kubernetesScriptCommand.ImageConfiguration.FeedPassword), + + ExecuteKubernetesScriptCommand => new KubernetesAgentScriptExecutionContext(), + + ExecuteShellScriptCommand => new LocalShellScriptExecutionContext(), + _ => throw new ArgumentOutOfRangeException(nameof(command), command, null) + }; + + var durationToWait = command switch + { + ExecuteKubernetesScriptCommand => null, + ExecuteShellScriptCommand executeShellScriptCommand => executeShellScriptCommand.DurationToWaitForScriptToFinish, + _ => throw new ArgumentOutOfRangeException(nameof(command), command, null) + }; + + return new StartScriptCommandV3Alpha( + command.ScriptBody, + command.IsolationConfiguration.IsolationLevel, + command.IsolationConfiguration.MutexTimeout, + command.IsolationConfiguration.MutexName, + command.Arguments, + command.TaskId, + command.ScriptTicket, + durationToWait, + executionContext, + command.Scripts, + command.Files.ToArray()); + } protected override ScriptExecutionStatus MapToStatus(ScriptStatusResponseV3Alpha response) => new(response.Logs); @@ -193,15 +227,15 @@ protected override async Task Finish(ScriptStatusRe await using var _ = scriptExecutionCancellationToken.Register(() => completeScriptCancellationTokenSource.CancelAfter(onCancellationAbandonCompleteScriptAfter)); await rpcCallExecutor.ExecuteWithNoRetries( - RpcCall.Create(nameof(IScriptServiceV3Alpha.CompleteScript)), - async ct => - { - var request = new CompleteScriptCommandV3Alpha(lastStatusResponse.ScriptTicket); - await clientScriptServiceV3Alpha.CompleteScriptAsync(request, new HalibutProxyRequestOptions(ct)); - }, - logger, - clientOperationMetricsBuilder, - completeScriptCancellationTokenSource.Token); + RpcCall.Create(nameof(IScriptServiceV3Alpha.CompleteScript)), + async ct => + { + var request = new CompleteScriptCommandV3Alpha(lastStatusResponse.ScriptTicket); + await clientScriptServiceV3Alpha.CompleteScriptAsync(request, new HalibutProxyRequestOptions(ct)); + }, + logger, + clientOperationMetricsBuilder, + completeScriptCancellationTokenSource.Token); } catch (Exception ex) when (ex is HalibutClientException or OperationCanceledException) { diff --git a/source/Octopus.Tentacle.Client/TentacleClient.cs b/source/Octopus.Tentacle.Client/TentacleClient.cs index bc40d7e67..15340c2f6 100644 --- a/source/Octopus.Tentacle.Client/TentacleClient.cs +++ b/source/Octopus.Tentacle.Client/TentacleClient.cs @@ -7,6 +7,7 @@ using Octopus.Tentacle.Client.Execution; using Octopus.Tentacle.Client.Observability; using Octopus.Tentacle.Client.Scripts; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.Contracts; using Octopus.Tentacle.Contracts.Capabilities; using Octopus.Tentacle.Contracts.ClientServices; @@ -81,7 +82,7 @@ internal TentacleClient( scriptServiceV3Alpha = halibutRuntime.CreateAsyncClient(serviceEndPoint); clientFileTransferServiceV1 = halibutRuntime.CreateAsyncClient(serviceEndPoint); capabilitiesServiceV2 = halibutRuntime.CreateAsyncClient(serviceEndPoint).WithBackwardsCompatability(); - + if (tentacleServicesDecoratorFactory != null) { scriptServiceV1 = tentacleServicesDecoratorFactory.Decorate(scriptServiceV1); @@ -166,8 +167,7 @@ async Task DownloadFileAction(CancellationToken ct) } } - public async Task ExecuteScript( - StartScriptCommandV3Alpha startScriptCommand, + public async Task ExecuteScript(ExecuteScriptCommand executeScriptCommand, OnScriptStatusResponseReceived onScriptStatusResponseReceived, OnScriptCompleted onScriptCompleted, ILog logger, @@ -193,7 +193,7 @@ public async Task ExecuteScript( var orchestrator = await factory.CreateOrchestrator(scriptExecutionCancellationToken); - var result = await orchestrator.ExecuteScript(startScriptCommand, scriptExecutionCancellationToken); + var result = await orchestrator.ExecuteScript(executeScriptCommand, scriptExecutionCancellationToken); return new ScriptExecutionResult(result.State, result.ExitCode); } diff --git a/source/Octopus.Tentacle.CommonTestUtils/Builders/LatestStartScriptCommandBuilder.cs b/source/Octopus.Tentacle.CommonTestUtils/Builders/LatestStartScriptCommandBuilder.cs deleted file mode 100644 index 1da36fe9d..000000000 --- a/source/Octopus.Tentacle.CommonTestUtils/Builders/LatestStartScriptCommandBuilder.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.Builders; - -namespace Octopus.Tentacle.CommonTestUtils.Builders -{ - public class LatestStartScriptCommandBuilder : StartScriptCommandV3AlphaBuilder - { - public LatestStartScriptCommandBuilder() - { - // Set defaults for the tests - WithIsolation(ScriptIsolationLevel.NoIsolation); - WithDurationStartScriptCanWaitForScriptToFinish(null); - } - } -} \ No newline at end of file diff --git a/source/Octopus.Tentacle.CommonTestUtils/Builders/StartScriptCommandV3AlphaBuilderExtensionMethods.cs b/source/Octopus.Tentacle.CommonTestUtils/Builders/StartScriptCommandV3AlphaBuilderExtensionMethods.cs index f4d7de6b9..e335e9a40 100644 --- a/source/Octopus.Tentacle.CommonTestUtils/Builders/StartScriptCommandV3AlphaBuilderExtensionMethods.cs +++ b/source/Octopus.Tentacle.CommonTestUtils/Builders/StartScriptCommandV3AlphaBuilderExtensionMethods.cs @@ -1,7 +1,5 @@ using System; using System.Text; -using Octopus.Tentacle.Contracts.Builders; -using Octopus.Tentacle.Tests.Integration.Util.Builders; namespace Octopus.Tentacle.CommonTestUtils.Builders { @@ -15,22 +13,5 @@ public static StartScriptCommandV3AlphaBuilder WithScriptBodyForCurrentOs(this S return builder; } - - public static StartScriptCommandV3AlphaBuilder WithScriptBody(this StartScriptCommandV3AlphaBuilder builder, ScriptBuilder scriptBuilder) - { - var scriptBody = new StringBuilder(scriptBuilder.BuildForCurrentOs()); - - builder.WithScriptBody(scriptBody.ToString()); - - return builder; - } - - public static StartScriptCommandV3AlphaBuilder WithScriptBody(this StartScriptCommandV3AlphaBuilder builder, Action builderFunc) - { - var scriptBuilder = new ScriptBuilder(); - builderFunc(scriptBuilder); - - return builder.WithScriptBody(scriptBuilder); - } } } diff --git a/source/Octopus.Tentacle.Contracts/ScriptServiceV3Alpha/KubernetesAgentScriptExecutionContext.cs b/source/Octopus.Tentacle.Contracts/ScriptServiceV3Alpha/KubernetesAgentScriptExecutionContext.cs index 3fc45ce39..b7e0afdbf 100644 --- a/source/Octopus.Tentacle.Contracts/ScriptServiceV3Alpha/KubernetesAgentScriptExecutionContext.cs +++ b/source/Octopus.Tentacle.Contracts/ScriptServiceV3Alpha/KubernetesAgentScriptExecutionContext.cs @@ -5,7 +5,7 @@ namespace Octopus.Tentacle.Contracts.ScriptServiceV3Alpha public class KubernetesAgentScriptExecutionContext : IScriptExecutionContext { [JsonConstructor] - public KubernetesAgentScriptExecutionContext(string image, string? feedUrl, string? feedUsername, string? feedPassword) + public KubernetesAgentScriptExecutionContext(string? image, string? feedUrl, string? feedUsername, string? feedPassword) { Image = image; FeedUrl = feedUrl; diff --git a/source/Octopus.Tentacle.Tests.Integration/CapabilitiesServiceV2Test.cs b/source/Octopus.Tentacle.Tests.Integration/CapabilitiesServiceV2Test.cs index 8235d55e2..37e75b61e 100644 --- a/source/Octopus.Tentacle.Tests.Integration/CapabilitiesServiceV2Test.cs +++ b/source/Octopus.Tentacle.Tests.Integration/CapabilitiesServiceV2Test.cs @@ -76,8 +76,8 @@ public async Task CapabilitiesResponseShouldBeCached(TentacleConfigurationTestCa .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("Running...")) .Build(); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientGathersRpcCallMetrics.cs b/source/Octopus.Tentacle.Tests.Integration/ClientGathersRpcCallMetrics.cs index eb5463c3e..cc91f1042 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientGathersRpcCallMetrics.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientGathersRpcCallMetrics.cs @@ -32,8 +32,8 @@ public async Task ExecuteScriptShouldGatherMetrics_WhenSucceeds(TentacleConfigur .WithTentacleClientObserver(tentacleClientObserver) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b.Print("Hello")) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b.Print("Hello")) .Build(); // Act @@ -84,8 +84,8 @@ public async Task ExecuteScriptShouldGatherMetrics_WhenFails(TentacleConfigurati .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b.Print("Hello")) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b.Print("Hello")) .Build(); // Act diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionAdditionalScripts.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionAdditionalScripts.cs index f0fb4d508..1e4d799d6 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionAdditionalScripts.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionAdditionalScripts.cs @@ -28,13 +28,13 @@ public async Task AdditionalScriptsWork(TentacleConfigurationTestCase tentacleCo .CreateFile(path) // How files are made are different in bash and powershell, doing this ensures the client and tentacle really are using the correct script. .Print("Hello"); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithAdditionalScriptTypes(ScriptType.Bash, scriptBuilder.BuildBashScript()) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .AddAdditionalScriptType(ScriptType.Bash, scriptBuilder.BuildBashScript()) // Additional Scripts don't actually work on tentacle for anything other than bash. // Below is what we would have expected to tentacle to work with. //.WithAdditionalScriptTypes(ScriptType.PowerShell, scriptBuilder.BuildPowershellScript()) // But instead we need to send the powershell in the scriptbody. - .WithScriptBody(scriptBuilder.BuildPowershellScript()) + .SetScriptBody(scriptBuilder.BuildPowershellScript()) .Build(); var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled.cs index 463e8ead5..31d2aa4e8 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreDisabled.cs @@ -7,6 +7,7 @@ using Halibut; using NUnit.Framework; using Octopus.Tentacle.Client.Scripts; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.CommonTestUtils.Builders; using Octopus.Tentacle.Contracts; using Octopus.Tentacle.Contracts.Capabilities; @@ -77,8 +78,8 @@ public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleCo .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("Should not run this script") .Sleep(TimeSpan.FromHours(1))) .Build(); @@ -89,9 +90,9 @@ public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleCo // ASSERT // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring capabilitiesMethodUsages.ForGetCapabilitiesAsync().LastException.Should().BeRequestCancelledException(rpcCallStage); - + var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - + actualException.ShouldMatchExceptionContract(expectedException); cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(15)); @@ -152,8 +153,8 @@ public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfig .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("The script") .Sleep(TimeSpan.FromHours(1))) .Build(); @@ -170,7 +171,7 @@ public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfig // Should have cancelled the RPC call and exited immediately var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - + actualException.ShouldMatchExceptionContract(expectedException); // We should have cancelled the RPC call quickly and existed @@ -184,7 +185,7 @@ public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfig else // Transferring { var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - + actualException.ShouldMatchExceptionContract(expectedException); // Assert the CancelScript and CompleteScript flow happened fairly quickly @@ -206,7 +207,7 @@ public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigur var restartedPortForwarderForCancel = false; var hasPausedOrStoppedPortForwarder = false; var ensureCancellationOccursDuringAnRpcCall = new SemaphoreSlim(0, 1); - + await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() .WithPendingRequestQueueFactory(new CancellationObservingPendingRequestQueueFactory()) // Partially works around disconnected polling tentacles take work from the queue .WithServiceEndpointModifier(point => point.TryAndConnectForALongTime()) @@ -253,8 +254,8 @@ public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigur .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("The script") .Sleep(TimeSpan.FromHours(1))) .Build(); @@ -267,7 +268,7 @@ public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigur actualException.Should().BeScriptExecutionCancelledException(); var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - + actualException.ShouldMatchExceptionContract(expectedException); // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring @@ -321,8 +322,8 @@ public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleCon clientAndTentacle.TentacleClient.OnCancellationAbandonCompleteScriptAfter = TimeSpan.FromSeconds(20); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("The script") .Sleep(TimeSpan.FromSeconds(5))) .Build(); @@ -383,7 +384,7 @@ void UnPauseOrRestartPortForwarder(TentacleType tentacleType, RpcCallStage rpcCa async Task<(ScriptExecutionResult response, Exception? actualException, TimeSpan cancellationDuration)> ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted( ClientAndTentacle clientAndTentacle, - StartScriptCommandV3Alpha startScriptCommand, + ExecuteScriptCommand executeScriptCommand, Reference rpcCallHasStarted, SemaphoreSlim whenTheRequestCanBeCancelled) { @@ -391,7 +392,7 @@ void UnPauseOrRestartPortForwarder(TentacleType tentacleType, RpcCallStage rpcCa var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( - startScriptCommand, + executeScriptCommand, cancelExecutionCancellationTokenSource.Token); Logger.Information("Create action"); @@ -399,7 +400,7 @@ void UnPauseOrRestartPortForwarder(TentacleType tentacleType, RpcCallStage rpcCa Logger.Information("Waiting for the RPC Call to start"); await Wait.For( - () => rpcCallHasStarted.Value, + () => rpcCallHasStarted.Value, TimeSpan.FromSeconds(30), () => throw new Exception("RPC call did not start") ,CancellationToken); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.cs index 2f42cd3fd..ac0f3fc67 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanBeCancelledWhenRetriesAreEnabled.cs @@ -8,6 +8,7 @@ using Halibut.Diagnostics; using NUnit.Framework; using Octopus.Tentacle.Client.Scripts; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.CommonTestUtils.Builders; using Octopus.Tentacle.Contracts; using Octopus.Tentacle.Contracts.Capabilities; @@ -90,8 +91,8 @@ public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleCo .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("Should not run this script") .Sleep(TimeSpan.FromHours(1))) .Build(); @@ -107,9 +108,9 @@ public async Task DuringGetCapabilities_ScriptExecutionCanBeCancelled(TentacleCo Assert.Inconclusive("This test is very fragile and often it will often cancel when the client is not in a wait trying to connect but instead gets error responses from the proxy. " + "This results in a halibut client exception being returned rather than a request cancelled error being returned and is not testing the intended scenario"); } - + var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - + actualException.ShouldMatchExceptionContract(expectedException); latestException.Should().BeRequestCancelledException(rpcCallStage); @@ -198,15 +199,15 @@ public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfig .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("The script") .Sleep(TimeSpan.FromHours(1))) .Build(); // ACT var (_, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, ensureCancellationOccursDuringAnRpcCall); - + // ASSERT // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring var latestException = recordedUsages.ForStartScriptAsync().LastException; @@ -220,7 +221,7 @@ public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfig if (rpcCall == RpcCall.FirstCall && rpcCallStage == RpcCallStage.Connecting) { var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - + actualException.ShouldMatchExceptionContract(expectedException); // We should have cancelled the RPC call quickly @@ -231,13 +232,13 @@ public async Task DuringStartScript_ScriptExecutionCanBeCancelled(TentacleConfig recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); recordedUsages.ForCompleteScriptAsync().Started.Should().Be(0); } - else if((rpcCall == RpcCall.RetryingCall && rpcCallStage == RpcCallStage.Connecting) || rpcCallStage == RpcCallStage.InFlight) + else if((rpcCall == RpcCall.RetryingCall && rpcCallStage == RpcCallStage.Connecting) || rpcCallStage == RpcCallStage.InFlight) { // Assert that script execution was cancelled actualException.Should().BeScriptExecutionCancelledException(); var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - + actualException.ShouldMatchExceptionContract(expectedException); @@ -269,21 +270,21 @@ public async Task DuringStartScript_ForPollingTentacle_ThatIsRetryingTheRpc_AndC var halibutTimeoutsAndLimits = HalibutTimeoutsAndLimits.RecommendedValues(); halibutTimeoutsAndLimits.PollingQueueWaitTimeout = TimeSpan.FromSeconds(4); halibutTimeoutsAndLimits.PollingRequestQueueTimeout = TimeSpan.FromSeconds(3); - + var started = false; var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - + await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() .WithRetryDuration(TimeSpan.FromHours(1)) .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder().RecordMethodUsages(tentacleConfigurationTestCase, out var scriptServiceRecordedUsages).Build()) .WithPendingRequestQueueFactory(new CancelWhenRequestQueuedPendingRequestQueueFactory( - cancelExecutionCancellationTokenSource, + cancelExecutionCancellationTokenSource, halibutTimeoutsAndLimits, // Cancel the execution when the StartScript RPC call is being retries shouldCancel: async () => { await Task.CompletedTask; - + if (started && scriptServiceRecordedUsages.ForStartScriptAsync().Started >= 2) { Logger.Information("Cancelling Execute Script"); @@ -294,11 +295,11 @@ public async Task DuringStartScript_ForPollingTentacle_ThatIsRetryingTheRpc_AndC })) .WithHalibutTimeoutsAndLimits(halibutTimeoutsAndLimits) .Build(CancellationToken); - + // Arrange Logger.Information("Execute a script so that GetCapabilities will be cached"); await clientAndTentacle.TentacleClient.ExecuteScript( - new LatestStartScriptCommandBuilder().WithScriptBody(b => b.Print("The script")).Build(), + new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("The script")).Build(), cancelExecutionCancellationTokenSource.Token); Logger.Information("Stop Tentacle so no more requests are picked up"); @@ -312,8 +313,8 @@ await clientAndTentacle.TentacleClient.ExecuteScript( started = true; // ACT - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b.Print("The script").Sleep(TimeSpan.FromHours(1))) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b.Print("The script").Sleep(TimeSpan.FromHours(1))) .Build(); Logger.Information("Start Executing the Script"); @@ -338,19 +339,19 @@ public async Task DuringStartScript_ForListeningTentacle_ThatIsRetryingTheRpc_An halibutTimeoutsAndLimits.RetryListeningSleepInterval = TimeSpan.Zero; halibutTimeoutsAndLimits.TcpClientConnectTimeout = TimeSpan.FromSeconds(4); halibutTimeoutsAndLimits.TcpClientPooledConnectionTimeout = TimeSpan.FromSeconds(5); - + var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); - + await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() .WithRetryDuration(TimeSpan.FromHours(1)) .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder().RecordMethodUsages(tentacleConfigurationTestCase, out var scriptServiceRecordedUsages).Build()) .WithHalibutTimeoutsAndLimits(halibutTimeoutsAndLimits) .Build(CancellationToken); - + // Arrange Logger.Information("Execute a script so that GetCapabilities will be cached"); await clientAndTentacle.TentacleClient.ExecuteScript( - new LatestStartScriptCommandBuilder().WithScriptBody(b => b.Print("The script")).Build(), + new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("The script")).Build(), cancelExecutionCancellationTokenSource.Token); Logger.Information("Stop Tentacle so no more requests are picked up"); @@ -363,8 +364,8 @@ await clientAndTentacle.TentacleClient.ExecuteScript( scriptServiceRecordedUsages.Reset(); // ACT - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b.Print("The script").Sleep(TimeSpan.FromHours(1))) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b.Print("The script").Sleep(TimeSpan.FromHours(1))) .Build(); Logger.Information("Start Executing the Script"); @@ -463,8 +464,8 @@ public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigur .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("The script") .Sleep(TimeSpan.FromHours(1))) .Build(); @@ -482,18 +483,18 @@ public async Task DuringGetStatus_ScriptExecutionCanBeCancelled(TentacleConfigur } latestException.Should().BeRequestCancelledException(rpcCallStage); - + // Assert that script execution was cancelled actualException.Should().BeScriptExecutionCancelledException(); var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientAndTentacle).Build(); - + actualException.ShouldMatchExceptionContract(expectedException); // Script Execution should cancel quickly cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); - + recordedUsages.ForStartScriptAsync().Started.Should().Be(1); if (rpcCall == RpcCall.FirstCall) { @@ -544,8 +545,8 @@ public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleCon clientAndTentacle.TentacleClient.OnCancellationAbandonCompleteScriptAfter = TimeSpan.FromSeconds(20); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("The script") .Sleep(TimeSpan.FromSeconds(5))) .Build(); @@ -554,7 +555,7 @@ public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleCon var (responseAndLogs, actualException, cancellationDuration) = await ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted(clientAndTentacle, startScriptCommand, rpcCallHasStarted, new SemaphoreSlim(int.MaxValue, int.MaxValue)); // ASSERT - + // Assert that the cancellation was performed at the correct state e.g. Connecting or Transferring var latestException = recordedUsages.ForCompleteScriptAsync().LastException; if (tentacleConfigurationTestCase.TentacleType == TentacleType.Listening && rpcCallStage == RpcCallStage.Connecting && latestException is HalibutClientException) @@ -574,7 +575,7 @@ public async Task DuringCompleteScript_ScriptExecutionCanBeCancelled(TentacleCon // Complete Script was cancelled quickly cancellationDuration.Should().BeLessOrEqualTo(TimeSpan.FromSeconds(30)); - + recordedUsages.ForStartScriptAsync().Started.Should().Be(1); recordedUsages.ForGetStatusAsync().Started.Should().BeGreaterThanOrEqualTo(1); recordedUsages.ForCancelScriptAsync().Started.Should().Be(0); @@ -623,18 +624,18 @@ void UnPauseOrRestartPortForwarder(TentacleType tentacleType, RpcCallStage rpcCa async Task<(ScriptExecutionResult response, Exception? actualException, TimeSpan cancellationDuration)> ExecuteScriptThenCancelExecutionWhenRpcCallHasStarted( ClientAndTentacle clientAndTentacle, - StartScriptCommandV3Alpha startScriptCommand, + ExecuteScriptCommand executeScriptCommand, Reference rpcCallHasStarted, SemaphoreSlim whenTheRequestCanBeCancelled) { var cancelExecutionCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(CancellationToken); var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript( - startScriptCommand, + executeScriptCommand, cancelExecutionCancellationTokenSource.Token); Logger.Information("Waiting for the RPC Call to start"); - await Wait.For(() => rpcCallHasStarted.Value, + await Wait.For(() => rpcCallHasStarted.Value, TimeSpan.FromSeconds(30), () => throw new Exception("RPC call did not start"), CancellationToken); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanRecoverFromNetworkIssues.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanRecoverFromNetworkIssues.cs index 6e7941194..6cbdc2011 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanRecoverFromNetworkIssues.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionCanRecoverFromNetworkIssues.cs @@ -36,13 +36,13 @@ public async Task WhenANetworkFailureOccurs_DuringStartScript_TheClientIsAbleToS var scriptHasStartFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "scripthasstarted"); var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .CreateFile(scriptHasStartFile) .WaitForFileToExist(waitForFile) .Print("hello")) // Configure the start script command to wait a long time, so we have plenty of time to kill the connection. - .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromHours(1)) + .SetDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromHours(1)) .Build(); var inMemoryLog = new InMemoryLog(); @@ -52,7 +52,7 @@ public async Task WhenANetworkFailureOccurs_DuringStartScript_TheClientIsAbleToS CancellationToken); // Wait for the script to start. - await Wait.For(() => File.Exists(scriptHasStartFile), + await Wait.For(() => File.Exists(scriptHasStartFile), TimeSpan.FromSeconds(30), () => throw new Exception("Script did not start"), CancellationToken); @@ -101,8 +101,8 @@ public async Task WhenANetworkFailureOccurs_DuringGetStatus_TheClientIsAbleToSuc var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .WaitForFileToExist(waitForFile) .Print("AllDone")) @@ -114,7 +114,7 @@ public async Task WhenANetworkFailureOccurs_DuringGetStatus_TheClientIsAbleToSuc async () => await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog), CancellationToken); - await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException != null, + await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.GetStatusAsync)).LastException != null, TimeSpan.FromSeconds(60), () => throw new Exception("GetStatus did not error"), CancellationToken); @@ -165,8 +165,8 @@ public async Task WhenANetworkFailureOccurs_DuringCompleteScript_TheClientIsAble .Build(CancellationToken); portForwarder = clientTentacle.PortForwarder; - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().Print("hello").Sleep(TimeSpan.FromSeconds(1))) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().Print("hello").Sleep(TimeSpan.FromSeconds(1))) .Build(); var inMemoryLog = new InMemoryLog(); @@ -218,8 +218,8 @@ await Wait.For(() => File.Exists(scriptIsRunningFlag), .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .CreateFile(scriptIsRunningFlag) .Sleep(TimeSpan.FromMinutes(2)) @@ -247,7 +247,7 @@ await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, } var expectedException = new ExceptionContractAssertionBuilder(FailureScenario.ScriptExecutionCancelled, tentacleConfigurationTestCase.TentacleType, clientTentacle).Build(); - + actualException!.ShouldMatchExceptionContract(expectedException); var allLogs = logs.JoinLogs(); @@ -285,8 +285,8 @@ public async Task WhenANetworkFailureOccurs_DuringGetCapabilities_TheClientIsAbl var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().Print("hello")) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().Print("hello")) .Build(); var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionIsolationMutex.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionIsolationMutex.cs index e96ca5808..b7de27dfb 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionIsolationMutex.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionIsolationMutex.cs @@ -32,25 +32,25 @@ public async Task ScriptIsolationMutexFull_EnsuresTwoDifferentScriptsDontRunAtTh var secondScriptStart = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "secondScriptStartFile"); - var firstStartScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var firstStartScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .CreateFile(firstScriptStartFile) .WaitForFileToExist(firstScriptWaitFile)) - .WithIsolation(ScriptIsolationLevel.FullIsolation) - .WithMutexName("mymutex") + .SetIsolationLevel(ScriptIsolationLevel.FullIsolation) + .SetIsolationMutexName("mymutex") .Build(); - var secondStartScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().CreateFile(secondScriptStart)) - .WithIsolation(levelOfSecondScript) - .WithMutexName("mymutex") + var secondStartScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().CreateFile(secondScriptStart)) + .SetIsolationLevel(levelOfSecondScript) + .SetIsolationMutexName("mymutex") .Build(); var tentacleClient = clientTentacle.TentacleClient; var firstScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(firstStartScriptCommand, CancellationToken)); // Wait for the first script to start running - await Wait.For(() => File.Exists(firstScriptStartFile), + await Wait.For(() => File.Exists(firstScriptStartFile), TimeSpan.FromSeconds(30), () => throw new Exception("Script did not start"), CancellationToken); @@ -59,7 +59,7 @@ await Wait.For(() => File.Exists(firstScriptStartFile), var secondScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(secondStartScriptCommand, CancellationToken)); // Wait for the second script start script RPC call to return. - await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Completed == 2, + await Wait.For(() => recordedUsages.For(nameof(IAsyncClientScriptServiceV2.StartScriptAsync)).Completed == 2, TimeSpan.FromSeconds(60), () => throw new Exception("Second execute script call did not complete"), CancellationToken); @@ -92,25 +92,25 @@ public async Task ScriptIsolationMutexFull_IsOnlyExclusiveWhenFullAndWhenTheMute var secondScriptStart = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "secondScriptStartFile"); - var firstStartScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var firstStartScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .CreateFile(firstScriptStartFile) .WaitForFileToExist(firstScriptWaitFile)) - .WithIsolation(scriptsInParallelTestCase.LevelOfFirstScript) - .WithMutexName(scriptsInParallelTestCase.MutexForFirstScript) + .SetIsolationLevel(scriptsInParallelTestCase.LevelOfFirstScript) + .SetIsolationMutexName(scriptsInParallelTestCase.MutexForFirstScript) .Build(); - var secondStartScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().CreateFile(secondScriptStart)) - .WithIsolation(scriptsInParallelTestCase.LevelOfSecondScript) - .WithMutexName(scriptsInParallelTestCase.MutexForSecondScript) + var secondStartScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().CreateFile(secondScriptStart)) + .SetIsolationLevel(scriptsInParallelTestCase.LevelOfSecondScript) + .SetIsolationMutexName(scriptsInParallelTestCase.MutexForSecondScript) .Build(); var tentacleClient = clientTentacle.TentacleClient; var firstScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(firstStartScriptCommand, CancellationToken)); // Wait for the first script to start running - await Wait.For(() => File.Exists(firstScriptStartFile), + await Wait.For(() => File.Exists(firstScriptStartFile), TimeSpan.FromSeconds(30), () => throw new Exception("Script did not start running"), CancellationToken); @@ -118,7 +118,7 @@ await Wait.For(() => File.Exists(firstScriptStartFile), var secondScriptExecution = Task.Run(async () => await tentacleClient.ExecuteScript(secondStartScriptCommand, CancellationToken)); // Wait for the second script to start - await Wait.For(() => File.Exists(secondScriptStart), + await Wait.For(() => File.Exists(secondScriptStart), TimeSpan.FromSeconds(30), () => throw new Exception("Second script did not start"), CancellationToken); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionRetriesTimeout.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionRetriesTimeout.cs index 93723db2b..41423ad2a 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionRetriesTimeout.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionRetriesTimeout.cs @@ -73,8 +73,8 @@ public async Task WhenRpcRetriesTimeOut_DuringGetCapabilities_TheRpcCallIsCancel var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("Should not run this script")) .Build(); @@ -128,7 +128,7 @@ public async Task WhenGetCapabilitiesFails_AndTakesLongerThanTheRetryDuration_Th var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() .Build(); var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); @@ -189,8 +189,8 @@ public async Task WhenRpcRetriesTimeOut_DuringStartScript_TheRpcCallIsCancelled( var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("Start Script") .Sleep(TimeSpan.FromHours(1)) .Print("End Script")) @@ -244,8 +244,8 @@ public async Task WhenStartScriptFails_AndTakesLongerThanTheRetryDuration_TheCal var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Sleep(TimeSpan.FromHours(1))) .Build(); @@ -309,13 +309,13 @@ public async Task WhenRpcRetriesTimeOut_DuringGetStatus_TheRpcCallIsCancelled(Te var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("Start Script") .Sleep(TimeSpan.FromHours(1)) .Print("End Script")) // Don't wait in start script as we want to tst get status - .WithDurationStartScriptCanWaitForScriptToFinish(null) + .SetDurationStartScriptCanWaitForScriptToFinish(null) .Build(); var duration = Stopwatch.StartNew(); @@ -367,11 +367,11 @@ public async Task WhenGetStatusFails_AndTakesLongerThanTheRetryDuration_TheCallI var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Sleep(TimeSpan.FromHours(1))) // Don't wait in start script as we want to test get status - .WithDurationStartScriptCanWaitForScriptToFinish(null) + .SetDurationStartScriptCanWaitForScriptToFinish(null) .Build(); var executeScriptTask = clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken, null, inMemoryLog); @@ -434,13 +434,13 @@ public async Task WhenRpcRetriesTimeOut_DuringCancelScript_TheRpcCallIsCancelled var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Print("Start Script") .Sleep(TimeSpan.FromHours(1)) .Print("End Script")) // Don't wait in start script as we want to tst get status - .WithDurationStartScriptCanWaitForScriptToFinish(null) + .SetDurationStartScriptCanWaitForScriptToFinish(null) .Build(); // Start the script which will wait for a file to exist @@ -506,11 +506,11 @@ public async Task WhenCancelScriptFails_AndTakesLongerThanTheRetryDuration_TheCa var inMemoryLog = new InMemoryLog(); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b .Sleep(TimeSpan.FromHours(1))) // Don't wait in start script as we want to test get status - .WithDurationStartScriptCanWaitForScriptToFinish(null) + .SetDurationStartScriptCanWaitForScriptToFinish(null) .Build(); // Start the script which will wait for a file to exist diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptArgumentsWork.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptArgumentsWork.cs index e170a1aac..19cdb2e04 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptArgumentsWork.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptArgumentsWork.cs @@ -20,9 +20,9 @@ public async Task ArgumentsArePassedToTheScript(TentacleConfigurationTestCase te { await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().PrintArguments()) - .WithArguments("First", "Second", "AndSpacesAreNotHandledWellInTentacle") + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().PrintArguments()) + .SetArguments(new[] { "First", "Second", "AndSpacesAreNotHandledWellInTentacle" }) .Build(); var tentacleServicesDecorator = new TentacleServiceDecoratorBuilder().Build(); @@ -37,4 +37,4 @@ public async Task ArgumentsArePassedToTheScript(TentacleConfigurationTestCase te allLogs.Should().MatchRegex(".*Argument: First\n.*Argument: Second\n.*Argument: AndSpacesAreNotHandledWellInTentacle\n"); } } -} +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptFilesAreSent.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptFilesAreSent.cs index 388d10a7b..40f0b3079 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptFilesAreSent.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptFilesAreSent.cs @@ -20,9 +20,9 @@ public async Task ScriptFilesAreSent(TentacleConfigurationTestCase tentacleConfi { await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().PrintFileContents("foo.txt")) - .WithFiles(new ScriptFile("foo.txt", DataStream.FromString("The File Contents"))) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().PrintFileContents("foo.txt")) + .AddFile(new ScriptFile("foo.txt", DataStream.FromString("The File Contents"))) .Build(); var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled.cs index 2406ad832..17ef16c86 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceNonV1IsNotRetriedWhenRetriesAreDisabled.cs @@ -50,8 +50,8 @@ public async Task WhenNetworkFailureOccurs_DuringGetCapabilities_TheCallIsNotRet .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().Print("hello")).Build(); + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().Print("hello")).Build(); var logs = new List(); var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); @@ -100,8 +100,8 @@ public async Task WhenANetworkFailureOccurs_DuringStartScript_TheCallIsNotRetrie .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .Print("AllDone")) .Build(); @@ -154,8 +154,8 @@ public async Task WhenANetworkFailureOccurs_DuringGetStatus_TheCallIsNotRetried( var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .WaitForFileToExist(waitForFile) .Print("AllDone")) @@ -218,8 +218,8 @@ public async Task WhenANetworkFailureOccurs_DuringCancelScript_TheCallIsNotRetri var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .WaitForFileToExist(waitForFile) .Print("AllDone")) @@ -271,8 +271,8 @@ public async Task WhenANetworkFailureOccurs_DuringCompleteScript_TheCallIsNotRet .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .Print("AllDone")) .Build(); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceV1IsNotRetried.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceV1IsNotRetried.cs index d60c60fe6..175150c86 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceV1IsNotRetried.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionScriptServiceV1IsNotRetried.cs @@ -45,13 +45,13 @@ public async Task WhenANetworkFailureOccurs_DuringStartScript_WithATentacleThatO var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithIsolation(ScriptIsolationLevel.FullIsolation) - .WithMutexName("bob") - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .WaitForFileToExist(waitForFile) .Print("AllDone")) + .SetIsolationLevel(ScriptIsolationLevel.FullIsolation) + .SetIsolationMutexName("bob") .Build(); var logs = new List(); @@ -105,8 +105,8 @@ public async Task WhenANetworkFailureOccurs_DuringGetStatus_WithATentacleThatOnl var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .WaitForFileToExist(waitForFile) .Print("AllDone")) @@ -124,7 +124,7 @@ public async Task WhenANetworkFailureOccurs_DuringGetStatus_WithATentacleThatOnl var legacyTentacleClient = clientTentacle.LegacyTentacleClientBuilder().Build(); await Wait.For( - async () => (await legacyTentacleClient.ScriptService.GetStatusAsync(scriptStatusRequest, new(CancellationToken))).State == ProcessState.Complete, + async () => (await legacyTentacleClient.ScriptService.GetStatusAsync(scriptStatusRequest, new(CancellationToken))).State == ProcessState.Complete, TimeSpan.FromSeconds(60), () => throw new Exception("Script Execution did not complete"), CancellationToken); @@ -175,8 +175,8 @@ public async Task WhenANetworkFailureOccurs_DuringCancelScript_WithATentacleThat var waitForFile = Path.Combine(clientTentacle.TemporaryDirectory.DirectoryPath, "waitforme"); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .WaitForFileToExist(waitForFile) .Print("AllDone")) @@ -202,7 +202,7 @@ public async Task WhenANetworkFailureOccurs_DuringCancelScript_WithATentacleThat var legacyTentacleClient = clientTentacle.LegacyTentacleClientBuilder().Build(); await Wait.For( - async () => (await legacyTentacleClient.ScriptService.GetStatusAsync(scriptStatusRequest, new(CancellationToken))).State == ProcessState.Complete, + async () => (await legacyTentacleClient.ScriptService.GetStatusAsync(scriptStatusRequest, new(CancellationToken))).State == ProcessState.Complete, TimeSpan.FromSeconds(60), () => throw new Exception("Script Execution did not complete"), CancellationToken); @@ -232,7 +232,7 @@ public async Task WhenANetworkFailureOccurs_DuringCompleteScript_WithATentacleTh .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder().WithScriptBody(new ScriptBuilder().Print("hello")).Build(); + var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(new ScriptBuilder().Print("hello")).Build(); var logs = new List(); var executeScriptTask = clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, logs, CancellationToken); diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionWorksWithMultipleVersions.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionWorksWithMultipleVersions.cs index 82d710160..a28ed3bde 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionWorksWithMultipleVersions.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutionWorksWithMultipleVersions.cs @@ -19,8 +19,8 @@ public async Task CanRunScript(TentacleConfigurationTestCase tentacleConfigurati { await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder().Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) .Print("AllDone")) diff --git a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutorObservesScriptObserverBackoffStrategy.cs b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutorObservesScriptObserverBackoffStrategy.cs index 5609bc5f8..faec66117 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutorObservesScriptObserverBackoffStrategy.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ClientScriptExecutorObservesScriptObserverBackoffStrategy.cs @@ -25,8 +25,8 @@ public async Task TheScriptObserverBackoffShouldBeRespected(TentacleConfiguratio .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1))) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1))) .Build(); var (_, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); diff --git a/source/Octopus.Tentacle.Tests.Integration/DisablingScriptServiceV3AlphaTests.cs b/source/Octopus.Tentacle.Tests.Integration/DisablingScriptServiceV3AlphaTests.cs index 728922948..9150b35e8 100644 --- a/source/Octopus.Tentacle.Tests.Integration/DisablingScriptServiceV3AlphaTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/DisablingScriptServiceV3AlphaTests.cs @@ -30,8 +30,8 @@ public async Task DisablingScriptServiceV3AlphaViaConfigUsesToScriptServiceV2(Te }) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("Lets do it") .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) .Print("All done")) diff --git a/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV2IntegrationTest.cs b/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV2IntegrationTest.cs index 3675d5542..2d688ea9f 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV2IntegrationTest.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV2IntegrationTest.cs @@ -28,8 +28,8 @@ public async Task CanRunScript(TentacleConfigurationTestCase tentacleConfigurati .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("Lets do it") .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) .Print("All done")) @@ -61,12 +61,12 @@ public async Task DelayInStartScriptSavesNetworkCalls(TentacleConfigurationTestC .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("Lets do it") .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) .Print("All done")) - .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromMinutes(1)) + .SetDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromMinutes(1)) .Build(); var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); @@ -95,8 +95,8 @@ public async Task WhenTentacleRestartsWhileRunningAScript_TheExitCodeShouldBe_Un .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .Sleep(TimeSpan.FromSeconds(1)) .Print("waitingtobestopped") @@ -143,8 +143,8 @@ public async Task WhenALongRunningScriptIsCancelled_TheScriptShouldStop(Tentacle .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .Sleep(TimeSpan.FromSeconds(1)) .Print("waitingtobestopped") @@ -180,13 +180,13 @@ await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, scriptCanc recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CompleteScriptAsync)).Started.Should().Be(1); recordedUsages.For(nameof(IAsyncClientScriptServiceV2.CancelScriptAsync)).Started.Should().BeGreaterThanOrEqualTo(1); } - + [Test] [TentacleConfigurations(scriptServiceToTest: ScriptServiceVersionToTest.Version2)] public async Task WhenOnCompleteTakesLongerThan_OnCancellationAbandonCompleteScriptAfter_AndTheExecutionIsNotCancelled_TheOrchestratorWaitsForCleanUpToComplete(TentacleConfigurationTestCase tentacleConfigurationTestCase) { bool calledWithNonCancelledCT = false; - + await using var clientTentacle = await tentacleConfigurationTestCase.CreateBuilder() .WithTentacleServiceDecorator(new TentacleServiceDecoratorBuilder() .RecordMethodUsages(tentacleConfigurationTestCase, out var recordedUsages) @@ -200,8 +200,8 @@ public async Task WhenOnCompleteTakesLongerThan_OnCancellationAbandonCompleteScr .Build()) .Build(CancellationToken); - var scriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b.Print("Hello")) + var scriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b.Print("Hello")) .Build(); var tentacleClient = clientTentacle.TentacleClient; diff --git a/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV3AlphaIntegrationTest.cs b/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV3AlphaIntegrationTest.cs index c96ca9e15..9a211d068 100644 --- a/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV3AlphaIntegrationTest.cs +++ b/source/Octopus.Tentacle.Tests.Integration/ScriptServiceV3AlphaIntegrationTest.cs @@ -27,8 +27,8 @@ public async Task CanRunScript(TentacleConfigurationTestCase tentacleConfigurati .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("Lets do it") .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) .Print("All done")) @@ -59,12 +59,12 @@ public async Task DelayInStartScriptSavesNetworkCalls(TentacleConfigurationTestC .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("Lets do it") .PrintNTimesWithDelay("another one", 10, TimeSpan.FromSeconds(1)) .Print("All done")) - .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromMinutes(1)) + .SetDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromMinutes(1)) .Build(); var (finalResponse, logs) = await clientTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); @@ -92,8 +92,8 @@ public async Task WhenTentacleRestartsWhileRunningAScript_TheExitCodeShouldBe_Un .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .Sleep(TimeSpan.FromSeconds(1)) .Print("waitingtobestopped") @@ -139,8 +139,8 @@ public async Task WhenALongRunningScriptIsCancelled_TheScriptShouldStop(Tentacle .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder() + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder() .Print("hello") .Sleep(TimeSpan.FromSeconds(1)) .Print("waitingtobestopped") @@ -196,8 +196,8 @@ public async Task WhenOnCompleteTakesLongerThan_OnCancellationAbandonCompleteScr .Build()) .Build(CancellationToken); - var scriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b.Print("Hello")) + var scriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b.Print("Hello")) .Build(); var tentacleClient = clientTentacle.TentacleClient; diff --git a/source/Octopus.Tentacle.Tests.Integration/Support/ExtensionMethods/ScriptExecutionResultExtensionMethods.cs b/source/Octopus.Tentacle.Tests.Integration/Support/ExtensionMethods/ScriptExecutionResultExtensionMethods.cs index 2b4e7d32f..fa585540a 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Support/ExtensionMethods/ScriptExecutionResultExtensionMethods.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Support/ExtensionMethods/ScriptExecutionResultExtensionMethods.cs @@ -8,7 +8,7 @@ namespace Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods { public static class ScriptExecutionResultExtensionMethods { - public static void LogExecuteScriptOutput(this (ScriptExecutionResult ScriptExecutionResult, List ProcessOutput) result, ILogger logger) + public static void LogExecuteScriptOutput(this (Client.Scripts.Models.ScriptExecutionResult ScriptExecutionResult, List ProcessOutput) result, ILogger logger) { var scriptOutput = new StringBuilder(); diff --git a/source/Octopus.Tentacle.Tests.Integration/TentacleClientObserver.cs b/source/Octopus.Tentacle.Tests.Integration/TentacleClientObserver.cs index 07894c0ca..956bfb3f0 100644 --- a/source/Octopus.Tentacle.Tests.Integration/TentacleClientObserver.cs +++ b/source/Octopus.Tentacle.Tests.Integration/TentacleClientObserver.cs @@ -31,8 +31,8 @@ public async Task AnErrorDuringTheCallbackTo_ExecuteScriptCompleted_ShouldNotThr .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b.Print("Hello")) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b.Print("Hello")) .Build(); // Act @@ -57,8 +57,8 @@ public async Task AnErrorDuringTheCallbackTo_RpcCallComplete_ShouldNotThrowOrSto .Build()) .Build(CancellationToken); - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(b => b.Print("Hello")) + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(b => b.Print("Hello")) .Build(); // Act diff --git a/source/Octopus.Tentacle.Tests.Integration/TentacleStartupAndShutdownTests.cs b/source/Octopus.Tentacle.Tests.Integration/TentacleStartupAndShutdownTests.cs index a8640f726..7cd833c4f 100644 --- a/source/Octopus.Tentacle.Tests.Integration/TentacleStartupAndShutdownTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/TentacleStartupAndShutdownTests.cs @@ -29,8 +29,8 @@ public async Task WhenRunningTentacleAsAServiceItShouldBeAbleToRestartItself(Ten .Build(CancellationToken)) { - var startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBodyForCurrentOs( + var startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBodyForCurrentOs( $@"cd ""{clientAndTentacle.RunningTentacle.TentacleExe.DirectoryName}"" .\Tentacle.exe service --instance {clientAndTentacle.RunningTentacle.InstanceName} --stop --start", $@"#!/bin/sh @@ -38,7 +38,7 @@ public async Task WhenRunningTentacleAsAServiceItShouldBeAbleToRestartItself(Ten ./Tentacle service --instance {clientAndTentacle.RunningTentacle.InstanceName} --stop --start") .Build(); - (ScriptExecutionResult ScriptExecutionResult, List ProcessOutput) result; + (Client.Scripts.Models.ScriptExecutionResult ScriptExecutionResult, List ProcessOutput) result; try { @@ -58,8 +58,8 @@ public async Task WhenRunningTentacleAsAServiceItShouldBeAbleToRestartItself(Ten result.ProcessOutput.Any(x => x.Text.Contains("Stopping service")).Should().BeTrue("Stopping service should be logged"); result.ScriptExecutionResult.State.Should().Be(ProcessState.Complete); - startScriptCommand = new LatestStartScriptCommandBuilder() - .WithScriptBody(new ScriptBuilder().Print("Running...")) + startScriptCommand = new TestExecuteShellScriptCommandBuilder() + .SetScriptBody(new ScriptBuilder().Print("Running...")) .Build(); result = await clientAndTentacle.TentacleClient.ExecuteScript(startScriptCommand, CancellationToken); diff --git a/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TentacleClientExtensionMethods.cs b/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TentacleClientExtensionMethods.cs index d08123d81..5993e9984 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TentacleClientExtensionMethods.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TentacleClientExtensionMethods.cs @@ -6,8 +6,8 @@ using Halibut; using Octopus.Tentacle.Client; using Octopus.Tentacle.Client.Scripts; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.Contracts; -using Octopus.Tentacle.Contracts.ScriptServiceV3Alpha; using Octopus.Tentacle.Diagnostics; using Octopus.Tentacle.Tests.Integration.Support; using Octopus.Tentacle.Tests.Integration.Support.ExtensionMethods; @@ -18,13 +18,13 @@ public static class TentacleClientExtensionMethods { public static async Task<(ScriptExecutionResult ScriptExecutionResult, List ProcessOutput)> ExecuteScript( this TentacleClient tentacleClient, - StartScriptCommandV3Alpha startScriptCommand, + ExecuteScriptCommand executeScriptCommand, CancellationToken token, OnScriptStatusResponseReceived? onScriptStatusResponseReceivedAction = null, Log? log = null) { var logs = new List(); - var finalResponse = await tentacleClient.ExecuteScript(startScriptCommand, + var finalResponse = await tentacleClient.ExecuteScript(executeScriptCommand, onScriptStatusResponseReceived => { if (onScriptStatusResponseReceivedAction != null) diff --git a/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TestExecuteScriptCommandBuilderExtensionMethods.cs b/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TestExecuteScriptCommandBuilderExtensionMethods.cs new file mode 100644 index 000000000..5670bd0a3 --- /dev/null +++ b/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TestExecuteScriptCommandBuilderExtensionMethods.cs @@ -0,0 +1,35 @@ +using System; +using System.Text; +using Octopus.Tentacle.CommonTestUtils; + +namespace Octopus.Tentacle.Tests.Integration.Util.Builders +{ + public static class ExecuteScriptCommandBuilderExtensionMethods + { + public static TestExecuteShellScriptCommandBuilder SetScriptBodyForCurrentOs(this TestExecuteShellScriptCommandBuilder builder, string windowsScript, string bashScript) + { + var scriptBody = new StringBuilder(PlatformDetection.IsRunningOnWindows ? windowsScript : bashScript); + + builder.SetScriptBody(scriptBody.ToString()); + + return builder; + } + + public static TestExecuteShellScriptCommandBuilder SetScriptBody(this TestExecuteShellScriptCommandBuilder builder, ScriptBuilder scriptBuilder) + { + var scriptBody = new StringBuilder(scriptBuilder.BuildForCurrentOs()); + + builder.SetScriptBody(scriptBody.ToString()); + + return builder; + } + + public static TestExecuteShellScriptCommandBuilder SetScriptBody(this TestExecuteShellScriptCommandBuilder builder, Action builderFunc) + { + var scriptBuilder = new ScriptBuilder(); + builderFunc(scriptBuilder); + + return builder.SetScriptBody(scriptBuilder); + } + } +} diff --git a/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TestExecuteShellScriptCommandBuilder.cs b/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TestExecuteShellScriptCommandBuilder.cs new file mode 100644 index 000000000..3c7ab47f2 --- /dev/null +++ b/source/Octopus.Tentacle.Tests.Integration/Util/Builders/TestExecuteShellScriptCommandBuilder.cs @@ -0,0 +1,15 @@ +using System; +using Octopus.Tentacle.Client.Scripts.Models.Builders; +using Octopus.Tentacle.Contracts; + +namespace Octopus.Tentacle.Tests.Integration.Util.Builders +{ + public class TestExecuteShellScriptCommandBuilder : ExecuteShellScriptCommandBuilder + { + public TestExecuteShellScriptCommandBuilder() + : base(Guid.NewGuid().ToString(), ScriptIsolationLevel.NoIsolation) + { + SetDurationStartScriptCanWaitForScriptToFinish(null); + } + } +} \ No newline at end of file diff --git a/source/Octopus.Tentacle.Tests.Integration/Util/TentacleClientTestExtensionMethods.cs b/source/Octopus.Tentacle.Tests.Integration/Util/TentacleClientTestExtensionMethods.cs index 57f08790d..4e403f2e1 100644 --- a/source/Octopus.Tentacle.Tests.Integration/Util/TentacleClientTestExtensionMethods.cs +++ b/source/Octopus.Tentacle.Tests.Integration/Util/TentacleClientTestExtensionMethods.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; using Octopus.Tentacle.Client; +using Octopus.Tentacle.Client.Scripts.Models; using Octopus.Tentacle.Contracts; using Octopus.Tentacle.Contracts.ScriptServiceV3Alpha; using Octopus.Tentacle.Tests.Integration.Support; @@ -12,11 +13,11 @@ static class TentacleClientTestExtensionMethods { public static async Task ExecuteScript( this TentacleClient tentacleClient, - StartScriptCommandV3Alpha startScriptCommand, + ExecuteScriptCommand executeScriptCommand, List logs, CancellationToken token) { - await tentacleClient.ExecuteScript(startScriptCommand, + await tentacleClient.ExecuteScript(executeScriptCommand, onScriptStatusResponseReceived => logs.AddRange(onScriptStatusResponseReceived.Logs), cts => Task.CompletedTask, new SerilogLoggerBuilder().Build().ForContext().ToILog(), diff --git a/source/Octopus.Tentacle.Tests.Integration/WorkspaceCleanerTests.cs b/source/Octopus.Tentacle.Tests.Integration/WorkspaceCleanerTests.cs index ba99eed5e..67765ff02 100644 --- a/source/Octopus.Tentacle.Tests.Integration/WorkspaceCleanerTests.cs +++ b/source/Octopus.Tentacle.Tests.Integration/WorkspaceCleanerTests.cs @@ -26,7 +26,7 @@ public async Task WhenScriptServiceIsRunning_ThenWorkspaceIsNotDeleted(TentacleC var existingHomeDirectory = new TemporaryDirectory(); var waitBeforeCompletingScriptFile = Path.Combine(existingHomeDirectory.DirectoryPath, "WaitForMeToExist.txt"); - var startScriptCommand = new LatestStartScriptCommandBuilder().WithScriptBody(b => b.WaitForFileToExist(waitBeforeCompletingScriptFile)).Build(); + var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.WaitForFileToExist(waitBeforeCompletingScriptFile)).Build(); var startScriptWorkspaceDirectory = GetWorkspaceDirectoryPath(existingHomeDirectory.DirectoryPath, startScriptCommand.ScriptTicket.TaskId); await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() @@ -65,7 +65,7 @@ public async Task WhenCompleteScriptIsNotCalled_ThenWorkspaceShouldGetDeletedWhe var cleanerDelay = TimeSpan.FromMilliseconds(500); var deleteWorkspacesOlderThan = TimeSpan.FromMilliseconds(500); - var startScriptCommand = new LatestStartScriptCommandBuilder().WithScriptBody(b => b.Print("Hello")).Build(); + var startScriptCommand = new TestExecuteShellScriptCommandBuilder().SetScriptBody(b => b.Print("Hello")).Build(); await using var clientAndTentacle = await tentacleConfigurationTestCase.CreateBuilder() .WithTentacle(b => diff --git a/source/Octopus.Tentacle.Tests/Integration/ScriptServiceV3AlphaFixture.cs b/source/Octopus.Tentacle.Tests/Integration/ScriptServiceV3AlphaFixture.cs index d780c7bb1..60c343974 100644 --- a/source/Octopus.Tentacle.Tests/Integration/ScriptServiceV3AlphaFixture.cs +++ b/source/Octopus.Tentacle.Tests/Integration/ScriptServiceV3AlphaFixture.cs @@ -12,6 +12,7 @@ using Octopus.Tentacle.CommonTestUtils.Builders; using Octopus.Tentacle.Configuration; using Octopus.Tentacle.Contracts; +using Octopus.Tentacle.Contracts.Builders; using Octopus.Tentacle.Contracts.ScriptServiceV3Alpha; using Octopus.Tentacle.Diagnostics; using Octopus.Tentacle.Scripts; @@ -53,7 +54,7 @@ public async Task ShouldExecuteAScriptSuccessfully() const string windowsScript = "& ping.exe localhost -n 1"; const string bashScript = "ping localhost -c 1"; - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBodyForCurrentOs(windowsScript, bashScript) .Build(); @@ -71,7 +72,7 @@ public async Task ShouldReturnANonZeroExitCodeForAFailingScript() const string windowsScript = "& ping.exe nope -n 1"; const string bashScript = "ping nope -c 1"; - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBodyForCurrentOs(windowsScript, bashScript) .Build(); @@ -90,7 +91,7 @@ public async Task ShouldExecuteMultipleScriptsConcurrently() const string windowsScript = "Start-Sleep -Seconds 10"; var scripts = Enumerable.Range(0, 5).Select(x => - new StartScriptCommandAndResponse(command: new LatestStartScriptCommandBuilder() + new StartScriptCommandAndResponse(command: new StartScriptCommandV3AlphaBuilder() .WithScriptBodyForCurrentOs(windowsScript, bashScript) .Build())).ToList(); @@ -126,7 +127,7 @@ public async Task ShouldStartExecuteAScriptQuickly() { var script = "echo \"finished\""; - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBody(script) .Build(); @@ -159,7 +160,7 @@ public async Task ShouldExecuteALongRunningScriptSuccessfully() var bashScript = "sleep 10"; var windowsScript = "Start-Sleep -Seconds 10"; - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBodyForCurrentOs(windowsScript, bashScript) .Build(); @@ -173,7 +174,7 @@ public async Task ShouldExecuteALongRunningScriptSuccessfully() [Test] public async Task StartScriptShouldWaitForAShortScriptToFinish() { - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBody("echo \"finished\"") .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromSeconds(5)) .Build(); @@ -193,7 +194,7 @@ public async Task StartScriptShouldNotWaitForALongScriptToFinish() var bashScript = "sleep 10"; var windowsScript = "Start-Sleep -Seconds 10"; - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBodyForCurrentOs(windowsScript, bashScript) .WithDurationStartScriptCanWaitForScriptToFinish(TimeSpan.FromSeconds(5)) .Build(); @@ -271,7 +272,7 @@ public async Task CancelScriptShouldCancelAnExecutingScript() var bashScript = "sleep 60"; var windowsScript = "Start-Sleep -Seconds 60"; - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBodyForCurrentOs(windowsScript, bashScript) .Build(); @@ -313,7 +314,7 @@ public async Task CompleteScriptShouldCleanupTheWorkspace() { var script = "echo \"finished\""; - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBody(script) .Build(); @@ -395,7 +396,7 @@ public async Task ShouldStoreTheStateOfTheScriptInTheScriptStateStore() var bashScript = "sleep 10"; var windowsScript = "Start-Sleep -Seconds 10"; - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBodyForCurrentOs(windowsScript, bashScript) .Build(); @@ -432,7 +433,7 @@ public async Task ShouldStoreTheStateOfTheScriptInTheScriptStateStore() public async Task ScriptTicketCasingShouldNotAffectCommands() { // Arrange - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBody("echo \"finished\"") .Build(); @@ -485,7 +486,7 @@ private async Task CleanupWorkspace(ScriptTicket ticket, CancellationToken cance windowsScript += $"{Environment.NewLine} Start-Sleep -Seconds {scriptDelayInSeconds}"; } - var startScriptCommand = new LatestStartScriptCommandBuilder() + var startScriptCommand = new StartScriptCommandV3AlphaBuilder() .WithScriptBodyForCurrentOs(windowsScript, bashScript) .WithScriptTicket(scriptTicket) .Build();