Skip to content

Commit

Permalink
Change TentacleClient.ExecuteScript to use new ExecuteScriptCommand t…
Browse files Browse the repository at this point in the history
…ype (#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
  • Loading branch information
APErebus authored Mar 25, 2024
1 parent 71b8444 commit b14ad17
Show file tree
Hide file tree
Showing 49 changed files with 662 additions and 302 deletions.
8 changes: 4 additions & 4 deletions source/Octopus.Tentacle.Client/ITentacleClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -18,14 +17,15 @@ public interface ITentacleClient : IDisposable
/// <summary>
/// Execute a script on Tentacle
/// </summary>
/// <param name="startScriptCommand">The start script command</param>
/// <param name="executeScriptCommand">The execute script command</param>
/// <param name="onScriptStatusResponseReceived"></param>
/// <param name="onScriptCompleted">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.</param>
/// <param name="logger">Used to output user orientated log messages</param>
/// <param name="scriptExecutionCancellationToken">When cancelled, will attempt to stop the execution of the script on Tentacle before returning.</param>
/// <returns></returns>
Task<ScriptExecutionResult> ExecuteScript(StartScriptCommandV3Alpha startScriptCommand,
Task<ScriptExecutionResult> ExecuteScript(
ExecuteScriptCommand executeScriptCommand,
OnScriptStatusResponseReceived onScriptStatusResponseReceived,
OnScriptCompleted onScriptCompleted,
ILog logger,
Expand Down
5 changes: 2 additions & 3 deletions source/Octopus.Tentacle.Client/Scripts/IScriptOrchestrator.cs
Original file line number Diff line number Diff line change
@@ -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<ScriptExecutionResult> ExecuteScript(StartScriptCommandV3Alpha startScriptCommand, CancellationToken scriptExecutionCancellationToken);
Task<ScriptExecutionResult> ExecuteScript(ExecuteScriptCommand command, CancellationToken scriptExecutionCancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -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
);
}
}
Original file line number Diff line number Diff line change
@@ -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<ScriptFile> Files { get; private set; } = new();
protected List<string> Arguments { get; private set; } = new();
protected Dictionary<ScriptType, string> 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<ScriptFile>? files)
{
Files.Clear();
if (files is not null)
{
Files.AddRange(files);
}

return this;
}

public ExecuteScriptCommandBuilder SetArguments(IEnumerable<string>? 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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<ScriptType, string>? additionalScripts,
ScriptFile[] additionalFiles,
KubernetesImageConfiguration? imageConfiguration)
: base(scriptTicket, taskId, scriptBody, arguments, isolationConfiguration, additionalScripts, additionalFiles)
{
ImageConfiguration = imageConfiguration;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<ScriptType, string>? additionalScripts = null,
ScriptFile[]? additionalFiles = null)
{
ScriptBody = scriptBody;
TaskId = taskId;
ScriptTicket = scriptTicket;
Arguments = arguments;
IsolationConfiguration = isolationConfiguration;

foreach (var additionalScript in additionalScripts ?? Enumerable.Empty<KeyValuePair<ScriptType, string>>())
{
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<ScriptType, string> Scripts { get; } = new();

public List<ScriptFile> Files { get; } = new();

}
}
Original file line number Diff line number Diff line change
@@ -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<ScriptType, string>? additionalScripts = null,
ScriptFile[]? additionalFiles = null,
TimeSpan? durationToWaitForScriptToFinish = null)
: base(scriptTicket, taskId, scriptBody, arguments, isolationConfiguration, additionalScripts, additionalFiles)
{
DurationToWaitForScriptToFinish = durationToWaitForScriptToFinish;
}

public TimeSpan? DurationToWaitForScriptToFinish { get; }
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading

0 comments on commit b14ad17

Please sign in to comment.