Skip to content

Commit

Permalink
Added support for split and concat functions during policy export A…
Browse files Browse the repository at this point in the history
  • Loading branch information
BernieWhite authored Jun 4, 2024
1 parent 8bd4055 commit 5e5a82e
Show file tree
Hide file tree
Showing 11 changed files with 448 additions and 218 deletions.
3 changes: 3 additions & 0 deletions docs/CHANGELOG-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
- Removed the `If` Premium SKU.
- Added check for Premium SKU.
- Bumped rule set to `2024_06`
- General improvements:
- Added support for `split` and `concat` functions during policy export by @BernieWhite.
[#2851](https://github.com/Azure/PSRule.Rules.Azure/issues/2851)
- Engineering:
- Bump xunit to v2.8.1.
[#2892](https://github.com/Azure/PSRule.Rules.Azure/pull/2892)
Expand Down
83 changes: 78 additions & 5 deletions src/PSRule.Rules.Azure/Data/Policy/PolicyAssignmentVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ internal abstract class PolicyAssignmentVisitor : ResourceManagerVisitor
private const string PROPERTY_HASVALUE = "hasValue";
private const string PROPERTY_EMPTY = "empty";
private const string PROPERTY_LENGTH = "length";
private const string PROPERTY_CONCAT = "concat";
private const string PROPERTY_SPLIT = "split";
private const string PROPERTY_STRING = "string";
private const string PROPERTY_INTEGER = "integer";
private const string PROPERTY_DELIMITER = "delimiter";

private const string EFFECT_DISABLED = "Disabled";
private const string EFFECT_AUDITIFNOTEXISTS = "AuditIfNotExists";
Expand Down Expand Up @@ -1291,7 +1296,7 @@ private static JObject VisitValueExpression(PolicyAssignmentContext context, JOb
}

// Handle [field('string')]
else if (tokens.HasFieldTokens())
else if (tokens.HasFieldTokens() && !tokens.HasPolicyRuntimeTokens())
{
condition = VisitFieldTokens(context, condition, tokens);
}
Expand Down Expand Up @@ -1331,14 +1336,14 @@ private static JObject VisitFieldTokens(PolicyAssignmentContext context, JObject
else if (tokens.ConsumeFunction(PROPERTY_IF) &&
tokens.TryTokenType(ExpressionTokenType.GroupStart, out _))
{
var orginal = condition;
var original = condition;

// Condition
var leftCondition = VisitFieldTokens(context, new JObject(), tokens);
var rightCondition = ReverseCondition(Clone(leftCondition));

var leftEvaluation = VisitFieldTokens(context, Clone(orginal), tokens);
var rightEvaluation = VisitFieldTokens(context, Clone(orginal), tokens);
var leftEvaluation = VisitFieldTokens(context, Clone(original), tokens);
var rightEvaluation = VisitFieldTokens(context, Clone(original), tokens);

var left = new JObject
{
Expand Down Expand Up @@ -1498,6 +1503,8 @@ private static JObject VisitFieldTokens(PolicyAssignmentContext context, JObject
private static JObject VisitRuntimeTokens(PolicyAssignmentContext context, TokenStream tokens)
{
var o = VisitRuntimeToken(context, tokens);
if (tokens.Count > 0) throw new NotImplementedException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.PolicyRuntimeTokenNotProcessed, tokens.AsString()));

return o == null ? null : new JObject
{
{ DOLLAR, o }
Expand All @@ -1524,7 +1531,7 @@ private static JObject VisitRuntimeToken(PolicyAssignmentContext context, TokenS
}
else if (tokens.ConsumeFunction(FUNCTION_CURRENT) && tokens.Skip(ExpressionTokenType.GroupStart))
{
var fieldTarget = "";
var fieldTarget = string.Empty;
if (tokens.TryTokenType(ExpressionTokenType.String, out var current))
{
fieldTarget = current.Content;
Expand All @@ -1538,6 +1545,72 @@ private static JObject VisitRuntimeToken(PolicyAssignmentContext context, TokenS
tokens.Skip(ExpressionTokenType.GroupEnd);
return o;
}
else if (tokens.ConsumeFunction(PROPERTY_CONCAT) &&
tokens.TryTokenType(ExpressionTokenType.GroupStart, out _))
{
var items = new JArray();

while (tokens.Current.Type == ExpressionTokenType.Element ||
tokens.Current.Type == ExpressionTokenType.String)
{
var child = VisitRuntimeToken(context, tokens);
items.Add(child);
}
var o = new JObject
{
[PROPERTY_CONCAT] = items
};
tokens.TryTokenType(ExpressionTokenType.GroupEnd, out _);
return o;
}
else if (tokens.ConsumeFunction(PROPERTY_SPLIT) &&
tokens.TryTokenType(ExpressionTokenType.GroupStart, out _))
{
var child = VisitRuntimeToken(context, tokens);

var delimiter = new JArray();
if (tokens.ConsumeString(out var d))
{
delimiter.Add(d);
}

var o = new JObject
{
{ PROPERTY_SPLIT, child },
{ PROPERTY_DELIMITER, delimiter }
};
tokens.TryTokenType(ExpressionTokenType.GroupEnd, out _);
return o;
}
else if (tokens.ConsumeFunction(PROPERTY_FIELD) &&
tokens.TryTokenType(ExpressionTokenType.GroupStart, out _) &&
tokens.ConsumeString(out var field))
{
field = context.TryPolicyAliasPath(field, out var aliasPath) ? TrimFieldName(context, aliasPath) : field;

var o = new JObject
{
{ PROPERTY_PATH, field }
};
tokens.TryTokenType(ExpressionTokenType.GroupEnd, out _);
return o;
}
else if (tokens.ConsumeString(out var s))
{
var o = new JObject
{
{ PROPERTY_STRING, s }
};
return o;
}
else if (tokens.ConsumeInteger(out var i))
{
var o = new JObject
{
{ PROPERTY_INTEGER, i }
};
return o;
}
return null;
}

Expand Down
30 changes: 30 additions & 0 deletions src/PSRule.Rules.Azure/Data/Template/ExpressionToken.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics;

namespace PSRule.Rules.Azure.Data.Template
{
/// <summary>
/// An individual expression token.
/// </summary>
[DebuggerDisplay("Type = {Type}, Content = {Content}")]
internal sealed class ExpressionToken
{
internal readonly ExpressionTokenType Type;
internal readonly long Value;
internal readonly string Content;

internal ExpressionToken(ExpressionTokenType type, string content)
{
Type = type;
Content = content;
}

internal ExpressionToken(long value)
{
Type = ExpressionTokenType.Numeric;
Value = value;
}
}
}
57 changes: 57 additions & 0 deletions src/PSRule.Rules.Azure/Data/Template/ExpressionTokenType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace PSRule.Rules.Azure.Data.Template
{
/// <summary>
/// The available token types used for Azure Resource Manager expressions.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "Represents standard type.")]
public enum ExpressionTokenType : byte
{
/// <summary>
/// Null token.
/// </summary>
None,

/// <summary>
/// A function name.
/// </summary>
Element,

/// <summary>
/// A property <c>.property_name</c>.
/// </summary>
Property,

/// <summary>
/// A string literal.
/// </summary>
String,

/// <summary>
/// A numeric literal.
/// </summary>
Numeric,

/// <summary>
/// Start a grouping <c>'('</c>.
/// </summary>
GroupStart,

/// <summary>
/// End a grouping <c>')'</c>.
/// </summary>
GroupEnd,

/// <summary>
/// Start an index <c>'['</c>.
/// </summary>
IndexStart,

/// <summary>
/// End an index <c>']'</c>.
/// </summary>
IndexEnd
}
}
Loading

0 comments on commit 5e5a82e

Please sign in to comment.