using System.ComponentModel.DataAnnotations;
using System.Text;
using FileFlows.Plugin;
using FileFlows.Plugin.Attributes;
namespace FileFlows.Docker.FlowElements;
///
/// Flow element that executes a Docker command
///
public class DockerExecute: Node
{
///
public override string Group => "Docker";
///
public override int Inputs => 1;
///
public override int Outputs => AdditionalOutputs?.Any() == true ? (1 + AdditionalOutputs.Count) : 1;
///
public override FlowElementType Type => FlowElementType.Process;
///
public override string Icon => "fab fa-docker";
///
public override string HelpUrl => "https://fileflows.com/docs/plugins/basic-nodes/docker-execute";
///
/// Gets or sets the name of the docker image
///
[Required]
[TextVariable(1)]
public string Image { get; set; } = string.Empty;
///
/// Gets or sets volumes
///
[KeyValue(2, null)]
public List> Volumes { get; set; } = [];
///
/// Gets or sets additional outputs
///
[StringArray(3)]
public List AdditionalOutputs { get; set; } = [];
///
/// Gets or sets the docker command
///
[Required]
[TextArea(3, true)]
public string Command { get; set; } = string.Empty;
///
public override int Execute(NodeParameters args)
{
var command = args.ReplaceVariables(Command, stripMissing: true);
command = command.Replace(args.TempPath, "/temp");
string image = args.ReplaceVariables(Image, stripMissing: true);
args.Logger?.ILog("Image: " + image);
args.Logger?.ILog("Command: " + command);
List arguments =
[
"run",
"--rm"
];
foreach (var vol in Volumes ?? [])
{
arguments.AddRange(["-v", vol.Key.Trim() + ":" + vol.Value.Trim() ]);
}
arguments.AddRange(["-v", $"{args.TempPathHost}:/temp"]);
arguments.Add(image);
// Split the command string into individual arguments, considering quotes
var commandArguments = SplitCommand(command)?.ToArray() ?? [];
if (commandArguments.Length > 0)
{
arguments.AddRange(commandArguments);
foreach (var arg in commandArguments)
{
args.Logger?.ILog("Arg: " + arg);
}
}
var result = args.Execute(new()
{
Command = "docker",
ArgumentList = arguments.ToArray(),
Silent = false
});
if (AdditionalOutputs?.Any() == true)
{
for (int i = 0; i < AdditionalOutputs.Count;i++)
{
var text = args.ReplaceVariables(AdditionalOutputs[i], stripMissing: true);
if (args.StringHelper.Matches(text, result.StandardOutput)
|| args.StringHelper.Matches(text, result.StandardError))
{
// + 2 since outputs are 1 based, and these are additional so above the standard 1 output
args.Logger?.ILog($"Additional output[{i + 2}] detected: {text}");
return i + 2;
}
}
}
args.Logger?.ILog("Exit Code: " + result.ExitCode);
if (result.ExitCode != 0)
{
args.FailureReason = "Invalid exit code received: " + result.ExitCode;
args.Logger?.ELog(args.FailureReason);
return -1;
}
args.Logger?.ILog("Success exit code received");
return 1;
}
///
/// Splits a command string into a list of arguments, handling quoted segments correctly.
///
private static IEnumerable SplitCommand(string command)
{
var args = new List();
var currentArg = new StringBuilder();
bool inQuotes = false;
foreach (char c in command)
{
if (c == '\"')
{
inQuotes = !inQuotes;
}
else if (c == ' ' && !inQuotes)
{
if (currentArg.Length > 0)
{
args.Add(currentArg.ToString());
currentArg.Clear();
}
}
else
{
currentArg.Append(c);
}
}
if (currentArg.Length > 0)
{
args.Add(currentArg.ToString());
}
return args;
}
}