Skip to content

Commit

Permalink
Merge pull request #106 from NoxOrg/feature/jobs-foreach
Browse files Browse the repository at this point in the history
- foreach working
  • Loading branch information
jan-schutte committed Sep 29, 2023
2 parents 30c0d57 + a9f53c4 commit b24b459
Show file tree
Hide file tree
Showing 14 changed files with 357 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public interface IJobConfiguration
string Id { get; set; }
string Name { get; set; }
string? If { get; set; }
string? ForEach { get; set; }
NoxJobDisplayMessage? Display { get; set; }
List<IActionConfiguration> Steps { get; set; }
}
1 change: 1 addition & 0 deletions src/Nox.Cli.Abstractions/INoxJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public interface INoxJob

string Name { get; set; }
string? If { get; set; }
object? ForEach { get; set; }
NoxJobDisplayMessage? Display { get; set; }
IDictionary<string, INoxAction> Steps { get; set; }

Expand Down
16 changes: 16 additions & 0 deletions src/Nox.Cli.Abstractions/NoxJobDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Collections.ObjectModel;

namespace Nox.Cli.Abstractions;

public class NoxJobDictionary: KeyedCollection<string, INoxJob>
{
public NoxJobDictionary(): base(StringComparer.OrdinalIgnoreCase)
{

}

protected override string GetKeyForItem(INoxJob item)
{
return item.Id;
}
}
2 changes: 1 addition & 1 deletion src/Nox.Cli.Caching/NoxCliCacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ internal INoxCliCacheManager Build()
}
}

var deserializer = BuildDeserializer();
BuildDeserializer();
ResolveManifest(yamlFiles);
ResolveWorkflows(yamlFiles);

Expand Down
3 changes: 2 additions & 1 deletion src/Nox.Cli.Configuration/JobConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public class JobConfiguration: IJobConfiguration
public string Name { get; set; } = string.Empty;

public string? If { get; set; }

public string? ForEach { get; set; }

public NoxJobDisplayMessage? Display { get; set; }
public List<IActionConfiguration> Steps { get; set; } = new();
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ public NoxActionMetaData Discover()
};
}

private Regex _defaultArrayRegex = new(@"\[(.*?)\]\.(.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));

private string? _schemaUrl = null!;

private string? _schema = null!;
Expand Down Expand Up @@ -562,14 +560,16 @@ private void AppendKey(string yamlSpacing, string key)

private void ProcessDefaults(string key, string yamlSpacing, string yamlSpacingPostfix)
{
Regex defaultArrayRegex = new(@"\[(.*?)\]\.(.*)", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));

_yaml.AppendLine($"{yamlSpacing}{key}:");
var arrayIndex = -1;
foreach (var defaultItem in _defaults!.Where(d => d.Key.StartsWith(key, StringComparison.CurrentCultureIgnoreCase)))
{
//check if this item is an array
var itemKey = defaultItem.Key;
var defaultSpacing = new string(' ', itemKey.Count(d => d == '.') * 2);
var match = _defaultArrayRegex.Match(itemKey);
var match = defaultArrayRegex.Match(itemKey);
if (match.Success) //array
{
if (int.TryParse(match.Groups[1].ToString(), out var itemIndex))
Expand Down
5 changes: 5 additions & 0 deletions src/Nox.Cli.Variables/ClientVariableProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ public void ResolveJobVariables(INoxJob job)
{
job.If = ReplaceVariable(job.If).ToString()!;
}

if (job.ForEach != null && !string.IsNullOrWhiteSpace(job.ForEach.ToString()))
{
job.ForEach = ReplaceVariable(job.ForEach.ToString()!);
}
}

private void Initialize(IWorkflowConfiguration workflow)
Expand Down
131 changes: 131 additions & 0 deletions src/Nox.Cli.Variables/ForEachVariableProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using System.Text.RegularExpressions;
using Nox.Cli.Abstractions;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

namespace Nox.Cli.Variables;

public class ForEachVariableProvider
{
private readonly Regex _variableRegex = new(@"\$\{\{\s*(?<variable>\b(foreach)\b[\w\.\-_:]+)\s*\}\}", RegexOptions.Compiled | RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));

private readonly Dictionary<string, object?> _variables;


public ForEachVariableProvider(INoxJob job)
{
_variables = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
Initialize(job);
}

public void ResolveAll(INoxJob job, object forEachObject)
{
_variables.ResolveForEachVariables(forEachObject);
foreach (var step in job.Steps)
{
foreach (var (_, input) in step.Value.Inputs)
{
if (input.Default is string inputValueString)
{
input.Default = ReplaceVariable(inputValueString);
}
else if (input.Default is List<object> inputValueList)
{
for (var i = 0; i < inputValueList.Count; i++)
{
if (inputValueList[i] is string)
{
var index = inputValueList.FindIndex(n => n.Equals(inputValueList[i]));
inputValueList[index] = ReplaceVariable((string)inputValueList[i]);
}
}
}
else if (input.Default is Dictionary<object, object> inputValueDictionary)
{
for (var i = 0; i < inputValueDictionary.Count; i++)
{
var item = inputValueDictionary.ElementAt(i);

if (item.Value is string itemValueString)
{
inputValueDictionary[item.Key] = ReplaceVariable(itemValueString);
}
}
}
}
}

job.Name = ReplaceVariable(job.Name).ToString()!;
if (!string.IsNullOrWhiteSpace(job.Display?.Success))
{
job.Display.Success = ReplaceVariable(job.Display.Success).ToString()!;
}
}

private void Initialize(INoxJob job)
{
var serializer = new SerializerBuilder()
.WithNamingConvention(CamelCaseNamingConvention.Instance)
.Build();

var serialized = serializer.Serialize(job);

var matches = _variableRegex.Matches(serialized);

var variablesTemp = matches.Select(m => m.Groups[2].Value)
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(e => e);

foreach (var v in variablesTemp)
{
_variables.Add(v, null);
}
}

private object ReplaceVariable(string value)
{
object result = value;

var match = _variableRegex.Match(result.ToString()!);

while (match.Success)
{
var fullPhrase = match.Groups[0].Value;

var variable = match.Groups["variable"].Value;

var resolvedValue = LookupValue(variable);

if (resolvedValue == null || resolvedValue.GetType() == typeof(object))
{
break;
}
else if (resolvedValue.GetType().IsSimpleType())
{
result = result.ToString()!.Replace(fullPhrase, resolvedValue.ToString());
}
else
{
result = resolvedValue;
break;
}

match = _variableRegex.Match(result.ToString()!);
}

return result;
}

private object? LookupValue(string variable)
{
if (_variables.ContainsKey(variable))
{
var lookupVar = _variables[variable];
if (lookupVar == null) return null;
return _variables[variable];
}
return null;
}


}
17 changes: 17 additions & 0 deletions src/Nox.Cli.Variables/ForEachVariableResolver.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Nox.Cli.Abstractions;

namespace Nox.Cli.Variables;

public static class ForEachVariableResolver
{
public static void ResolveForEachVariables(this IDictionary<string, object?> variables, object forEachObject)
{
var forEachKeys = variables
.Select(pk => pk.Key)
.Where(pk => pk.StartsWith("forEach.", StringComparison.OrdinalIgnoreCase))
.Select(pk => pk[8..])
.ToArray();

forEachObject.WalkProperties( (name, value) => { if (forEachKeys.Contains(name, StringComparer.OrdinalIgnoreCase)) { variables[$"forEach.{name}"] = value; } });
}
}
3 changes: 2 additions & 1 deletion src/Nox.Cli/Actions/NoxJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ public class NoxJob: INoxJob

public string Name { get; set; } = string.Empty;
public string? If { get; set; }

public object? ForEach { get; set; }

public NoxJobDisplayMessage? Display { get; set; } = new();
public IDictionary<string, INoxAction> Steps { get; set; } = new Dictionary<string, INoxAction>();

Expand Down
48 changes: 39 additions & 9 deletions src/Nox.Cli/Actions/NoxWorkflowContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;
using System.Text.RegularExpressions;
using Nox.Cli.Abstractions;
using Nox.Cli.Abstractions.Configuration;
using Nox.Cli.Abstractions.Helpers;
Expand All @@ -16,7 +17,7 @@ namespace Nox.Cli.Actions;
public class NoxWorkflowContext : INoxWorkflowContext
{
private readonly IWorkflowConfiguration _workflow;
private readonly IDictionary<string, INoxJob> _jobs;
private readonly NoxJobDictionary _jobs;
private IDictionary<string, INoxAction> _steps;
private readonly IClientVariableProvider _varProvider;
private readonly INoxCliCacheManager _cacheManager;
Expand Down Expand Up @@ -60,8 +61,9 @@ public NoxWorkflowContext(
public void NextJob()
{
_currentJobSequence++;
_currentJob = _jobs.Select(j => j.Value).FirstOrDefault(j => j.Sequence == _currentJobSequence);
_nextJob = _jobs.Select(j => j.Value).FirstOrDefault(j => j.Sequence == _currentJobSequence + 1);

_currentJob = _jobs.FirstOrDefault(j => j.Sequence == _currentJobSequence);
_nextJob = _jobs.FirstOrDefault(j => j.Sequence == _currentJobSequence + 1);

if (_currentJob != null)
{
Expand Down Expand Up @@ -164,17 +166,44 @@ public IDictionary<string, object> GetUnresolvedInputVariables(INoxAction action
return _varProvider.GetUnresolvedInputVariables(action);
}

private Dictionary<string, INoxJob> ParseWorkflow()
public int RemoveCurrentJob()
{
var currentIndex = _jobs.IndexOf(_currentJob!);
for (int i = currentIndex; i <= _jobs.Count - 1; i++)
{
_jobs[i].Sequence--;
}
_jobs.RemoveAt(currentIndex);
return currentIndex;
}

/// <summary>
/// Injects a job iteration of a recurring job at the current location
/// </summary>
public void InjectJobIteration(int index, INoxJob job, bool setAsCurrent)
{
job.Sequence = _jobs[index].Sequence;
_jobs.Insert(index, job);
//increment the sequence of the rest of the jobs
for (int i = index + 1; i <= _jobs.Count - 1; i++)
{
_jobs[i].Sequence++;
}

if (setAsCurrent) _currentJob = job;
}

private NoxJobDictionary ParseWorkflow()
{
var jobSequence = 1;
var stepSequence = 1;
var jobs = new Dictionary<string, INoxJob>(StringComparer.OrdinalIgnoreCase);
var jobs = new NoxJobDictionary();

foreach (var jobConfiguration in _workflow.Jobs)
{
var jobKey = jobConfiguration.Id;

if (jobs.ContainsKey(jobConfiguration.Id))
if (jobs.Contains(jobConfiguration.Id))
{
throw new NoxCliException($"Job Id {jobKey} exists more than once in your workflow configuration. Job Ids must be unique in a workflow configuration");
}
Expand All @@ -185,6 +214,7 @@ private Dictionary<string, INoxJob> ParseWorkflow()
Id = jobConfiguration.Id,
Name = jobConfiguration.Name,
If = jobConfiguration.If,
ForEach = jobConfiguration.ForEach,
Display = jobConfiguration.Display,
FirstStepSequence = stepSequence,
Steps = ParseSteps(jobConfiguration, ref stepSequence)
Expand All @@ -204,8 +234,8 @@ private Dictionary<string, INoxJob> ParseWorkflow()
newJob.Display.IfCondition = MaskSecretsInDisplayText(newJob.Display.IfCondition);
}
}

jobs[jobKey] = newJob;
jobs.Add(newJob);
}

return jobs;
Expand Down
Loading

0 comments on commit b24b459

Please sign in to comment.