Skip to content

Commit

Permalink
Support for .bypassrules directive
Browse files Browse the repository at this point in the history
  • Loading branch information
sirtwist committed Feb 7, 2021
1 parent 9af9105 commit 59f338d
Show file tree
Hide file tree
Showing 11 changed files with 74 additions and 18 deletions.
10 changes: 8 additions & 2 deletions src/aggregator-cli/Rules/AggregatorRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ internal async Task<bool> AddAsync(InstanceName instance, string ruleName, strin
if (preprocessedRule.Impersonate)
{
_logger.WriteInfo($"Configure {ruleName} to execute impersonated.");
ok &= await ConfigureAsync(instance, ruleName, impersonate: true, cancellationToken: cancellationToken);
ok &= await ConfigureAsync(instance, ruleName, impersonate: true, bypassrules: true, cancellationToken: cancellationToken);
if (ok)
{
_logger.WriteInfo($"Updated {ruleName} configuration successfully.");
Expand Down Expand Up @@ -339,7 +339,7 @@ internal async Task<bool> RemoveAsync(InstanceName instance, string name, Cancel
return true;
}

internal async Task<bool> ConfigureAsync(InstanceName instance, string name, bool? disable = null, bool? impersonate = null, CancellationToken cancellationToken = default)
internal async Task<bool> ConfigureAsync(InstanceName instance, string name, bool? disable = null, bool? impersonate = null, bool? bypassrules = null, CancellationToken cancellationToken = default)
{
var webFunctionApp = await GetWebApp(instance, cancellationToken);
var configuration = await AggregatorConfiguration.ReadConfiguration(webFunctionApp);
Expand All @@ -355,6 +355,11 @@ internal async Task<bool> ConfigureAsync(InstanceName instance, string name, boo
ruleConfig.Impersonate = impersonate.Value;
}

if (bypassrules.HasValue)
{
ruleConfig.BypassRules = impersonate.Value;
}

ruleConfig.WriteConfiguration(webFunctionApp);

return true;
Expand Down Expand Up @@ -443,6 +448,7 @@ private async Task<bool> InvokeLocalAsyncImpl(string projectName, string @event,
{
ImpersonateExecution = impersonateExecution
};
_logger.WriteVerbose("Test 1");

var engineLogger = new EngineWrapperLogger(_logger);
var engine = new Engine.RuleEngine(engineLogger, saveMode, dryRun: dryRun);
Expand Down
2 changes: 1 addition & 1 deletion src/aggregator-cli/Rules/ConfigureRuleCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ internal override async Task<int> RunAsync(CancellationToken cancellationToken)
var disable = GetDisableStatus(Disable, Enable);
var impersonate = GetEnableStatus(DisableImpersonateExecution, EnableImpersonateExecution);

var ok = await rules.ConfigureAsync(instance, Name, disable, impersonate, cancellationToken);
var ok = await rules.ConfigureAsync(instance, Name, disable, impersonate, false, cancellationToken);
return ok ? ExitCodes.Success : ExitCodes.Failure;
}

Expand Down
7 changes: 7 additions & 0 deletions src/aggregator-ruleng/IRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ public interface IRule
/// <remarks>Setter public for CLI</remarks>
bool ImpersonateExecution { get; set; }

/// <summary>
/// WorkItem creation/updates will bypass rules
/// Assumes PAT or Account Permission is high enough
/// </summary>
/// <remarks>Setter public for CLI</remarks>
bool BypassRules { get; set; }

///<summary>
/// Configuration data picked by the directive parser that may influence a rule behaviour.
///</summary>
Expand Down
1 change: 1 addition & 0 deletions src/aggregator-ruleng/Language/IPreprocessedRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace aggregator.Engine.Language
{
public interface IPreprocessedRule
{
bool BypassRules { get; set; }
bool Impersonate { get; set; }
IRuleSettings Settings { get; }
RuleLanguage Language { get; }
Expand Down
3 changes: 3 additions & 0 deletions src/aggregator-ruleng/Language/PreprocessedRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ internal class PreprocessedRule : IPreprocessedRule
{
public PreprocessedRule()
{
BypassRules = false;
Impersonate = false;
Settings = new RuleSettings();
Language = RuleLanguage.Unknown;
Expand All @@ -20,6 +21,8 @@ public PreprocessedRule()

public bool Impersonate { get; set; }

public bool BypassRules { get; set; }

public IRuleSettings Settings { get; private set; }

public RuleLanguage Language { get; internal set; }
Expand Down
28 changes: 27 additions & 1 deletion src/aggregator-ruleng/Language/RuleFileParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,22 @@ void FailParsingWithMessage(string message)

(IPreprocessedRule preprocessedRule, bool parseSuccess) Parse(string[] ruleCode)
{
logger.WriteVerbose("start Parse");

var directiveLineIndex = 0;
var preprocessedRule = new PreprocessedRule()
{
Language = RuleLanguage.Csharp
};

logger.WriteVerbose("start Parse while");

while (directiveLineIndex < ruleCode.Length
&& ruleCode[directiveLineIndex].Length > 0
&& ruleCode[directiveLineIndex][0] == '.')
{
logger.WriteVerbose("inside Parse while");

string directive = ruleCode[directiveLineIndex][1..];
// stop at first '=' or ' '
int endVerb = directive.IndexOfAny(new char[] { '=', ' ' });
Expand All @@ -90,6 +95,7 @@ void FailParsingWithMessage(string message)
preprocessedRule.RuleCode.AddRange(ruleCode.Skip(preprocessedRule.FirstCodeLine));

var parseSuccessful = !parsingIssues;
logger.WriteVerbose("end Parse");
return (preprocessedRule, parseSuccessful);

}
Expand Down Expand Up @@ -123,7 +129,10 @@ void ParseDirective(PreprocessedRule preprocessedRule, string directive, string
case "impersonate":
ParseImpersonateDirective(preprocessedRule, directive, arguments);
break;

case "bypassrules":
logger.WriteVerbose("bypassrules");
ParseBypassRulesDirective(preprocessedRule, directive, arguments);
break;
case "check":
ParseCheckDirective(preprocessedRule, directive, arguments);
break;
Expand Down Expand Up @@ -214,6 +223,18 @@ private void ParseImpersonateDirective(PreprocessedRule preprocessedRule, string
}
}

private void ParseBypassRulesDirective(PreprocessedRule preprocessedRule, string directive, string arguments)
{
if (string.IsNullOrWhiteSpace(arguments))
{
FailParsingWithMessage($"Invalid bypassrules directive {directive}");
}
else
{
preprocessedRule.BypassRules = string.Equals("true", arguments.TrimEnd(), StringComparison.OrdinalIgnoreCase);
}
}

private void ParseCheckDirective(PreprocessedRule preprocessedRule, string directive, string arguments)
{
if (string.IsNullOrWhiteSpace(arguments))
Expand Down Expand Up @@ -280,6 +301,11 @@ public static string[] Write(IPreprocessedRule preprocessedRule)
content.Add($".impersonate=onBehalfOfInitiator");
}

if (preprocessedRule.BypassRules)
{
content.Add($".bypassrules=true");
}

content.AddRange(preprocessedRule.References.Select(reference => $".reference={reference}"));
content.AddRange(preprocessedRule.Imports.Select(import => $".import={import}"));
content.AddRange(preprocessedRule.Events.Select(evnt => $".event={evnt}"));
Expand Down
2 changes: 1 addition & 1 deletion src/aggregator-ruleng/RuleEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected override async Task<string> ExecuteRuleAsync(IRule rule, RuleExecution
var result = await rule.ApplyAsync(executionContext, cancellationToken);

var store = executionContext.store;
var (created, updated) = await store.SaveChanges(saveMode, !dryRun, rule.ImpersonateExecution, cancellationToken);
var (created, updated) = await store.SaveChanges(saveMode, !dryRun, rule.ImpersonateExecution, rule.BypassRules, cancellationToken);
if (created + updated > 0)
{
logger.WriteInfo($"Changes saved to Azure DevOps (mode {saveMode}): {created} created, {updated} updated.");
Expand Down
4 changes: 4 additions & 0 deletions src/aggregator-ruleng/ScriptedRuleWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class ScriptedRuleWrapper : IRule
internal IPreprocessedRule RuleDirectives { get; private set; }

public IRuleSettings Settings { get; private set; }
public bool BypassRules { get; set; }

private ScriptedRuleWrapper(string ruleName, IAggregatorLogger logger)
{
Expand Down Expand Up @@ -72,8 +73,11 @@ public ScriptedRuleWrapper(string ruleName, IPreprocessedRule preprocessedRule,

private void Initialize(IPreprocessedRule preprocessedRule)
{

RuleDirectives = preprocessedRule;
ImpersonateExecution = RuleDirectives.Impersonate;
BypassRules = RuleDirectives.BypassRules;

Settings = preprocessedRule.Settings;

var references = new HashSet<Assembly>(DefaultAssemblyReferences().Concat(RuleDirectives.LoadAssemblyReferences()));
Expand Down
25 changes: 13 additions & 12 deletions src/aggregator-ruleng/WorkItemStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private void ImpersonateChanges()
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Sonar Code Smell", "S907:\"goto\" statement should not be used")]
public async Task<(int created, int updated)> SaveChanges(SaveMode mode, bool commit, bool impersonate, CancellationToken cancellationToken)
public async Task<(int created, int updated)> SaveChanges(SaveMode mode, bool commit, bool impersonate, bool bypassrules, CancellationToken cancellationToken)
{
if (impersonate)
{
Expand All @@ -175,20 +175,20 @@ private void ImpersonateChanges()
_context.Logger.WriteVerbose($"No save mode specified, assuming {SaveMode.TwoPhases}.");
goto case SaveMode.TwoPhases;
case SaveMode.Item:
var resultItem = await SaveChanges_ByItem(commit, impersonate, cancellationToken);
var resultItem = await SaveChanges_ByItem(commit, impersonate, bypassrules, cancellationToken);
return resultItem;
case SaveMode.Batch:
var resultBatch = await SaveChanges_Batch(commit, impersonate, cancellationToken);
var resultBatch = await SaveChanges_Batch(commit, impersonate, bypassrules, cancellationToken);
return resultBatch;
case SaveMode.TwoPhases:
var resultTwoPhases = await SaveChanges_TwoPhases(commit, impersonate, cancellationToken);
var resultTwoPhases = await SaveChanges_TwoPhases(commit, impersonate, bypassrules, cancellationToken);
return resultTwoPhases;
default:
throw new InvalidOperationException($"Unsupported save mode: {mode}.");
}
}

private async Task<(int created, int updated)> SaveChanges_ByItem(bool commit, bool impersonate, CancellationToken cancellationToken)
private async Task<(int created, int updated)> SaveChanges_ByItem(bool commit, bool impersonate, bool bypassrules, CancellationToken cancellationToken)
{
int createdCounter = 0;
int updatedCounter = 0;
Expand All @@ -203,7 +203,7 @@ private void ImpersonateChanges()
item.Changes,
_context.ProjectName,
item.WorkItemType,
bypassRules: impersonate,
bypassRules: impersonate || bypassrules,
cancellationToken: cancellationToken
);
}
Expand Down Expand Up @@ -236,7 +236,7 @@ private void ImpersonateChanges()
_ = await _clients.WitClient.UpdateWorkItemAsync(
item.Changes,
item.Id,
bypassRules: impersonate,
bypassRules: impersonate || bypassrules,
cancellationToken: cancellationToken
);
}
Expand All @@ -251,7 +251,7 @@ private void ImpersonateChanges()
return (createdCounter, updatedCounter);
}

private async Task<(int created, int updated)> SaveChanges_Batch(bool commit, bool impersonate, CancellationToken cancellationToken)
private async Task<(int created, int updated)> SaveChanges_Batch(bool commit, bool impersonate, bool bypassrules, CancellationToken cancellationToken)
{
// see https://github.com/redarrowlabs/vsts-restapi-samplecode/blob/master/VSTSRestApiSamples/WorkItemTracking/Batch.cs
// and https://docs.microsoft.com/en-us/rest/api/vsts/wit/workitembatchupdate?view=vsts-rest-4.1
Expand Down Expand Up @@ -280,7 +280,7 @@ private void ImpersonateChanges()

var request = _clients.WitClient.CreateWorkItemBatchRequest(item.Id,
item.Changes,
bypassRules: impersonate,
bypassRules: impersonate || bypassrules,
suppressNotifications: false);
batchRequests.Add(request);
}
Expand Down Expand Up @@ -309,7 +309,7 @@ private static bool IsSuccessStatusCode(int statusCode)

//TODO no error handling here? SaveChanges_Batch has at least the DryRun support and error handling
//TODO Improve complex handling with ReplaceIdAndResetChanges and RemapIdReferences
private async Task<(int created, int updated)> SaveChanges_TwoPhases(bool commit, bool impersonate, CancellationToken cancellationToken)
private async Task<(int created, int updated)> SaveChanges_TwoPhases(bool commit, bool impersonate, bool bypassrules, CancellationToken cancellationToken)
{
// see https://github.com/redarrowlabs/vsts-restapi-samplecode/blob/master/VSTSRestApiSamples/WorkItemTracking/Batch.cs
// and https://docs.microsoft.com/en-us/rest/api/vsts/wit/workitembatchupdate?view=vsts-rest-4.1
Expand Down Expand Up @@ -337,7 +337,7 @@ private static bool IsSuccessStatusCode(int statusCode)
var request = _clients.WitClient.CreateWorkItemBatchRequest(_context.ProjectName,
item.WorkItemType,
document,
bypassRules: impersonate,
bypassRules: impersonate || bypassrules,
suppressNotifications: false);
batchRequests.Add(request);
}
Expand Down Expand Up @@ -366,10 +366,11 @@ private static bool IsSuccessStatusCode(int statusCode)
continue;
}
_context.Logger.WriteInfo($"Found a request to update workitem {item.Id} in {_context.ProjectName}");
_context.Logger.WriteVerbose(JsonConvert.SerializeObject(item.Changes));

var request = _clients.WitClient.CreateWorkItemBatchRequest(item.Id,
item.Changes,
bypassRules: impersonate,
bypassRules: impersonate || bypassrules,
suppressNotifications: false);
batchRequests.Add(request);
}
Expand Down
7 changes: 7 additions & 0 deletions src/aggregator-shared/AggregatorConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public interface IRuleConfiguration
string RuleName { get; }
bool IsDisabled { get; set; }
bool Impersonate { get; set; }
bool BypassRules { get; set; }
}


Expand Down Expand Up @@ -73,6 +74,11 @@ public static async Task<IAggregatorConfiguration> ReadConfiguration(Microsoft.A
{
ruleConfig.Impersonate = string.Equals("onBehalfOfInitiator", ruleSetting.value, StringComparison.OrdinalIgnoreCase);
}

if (string.Equals("BypassRules", key, StringComparison.OrdinalIgnoreCase))
{
ruleConfig.BypassRules = string.Equals("true", ruleSetting.value, StringComparison.OrdinalIgnoreCase);
}
}

Enum.TryParse(settings.GetValueOrDefault("Aggregator_VstsTokenType")?.Value, out DevOpsTokenType vtt);
Expand Down Expand Up @@ -156,6 +162,7 @@ private static void AddRuleSettings(this Dictionary<string, string> settings, IR
{
settings[$"{RULE_SETTINGS_PREFIX}{ruleSetting.RuleName}.Disabled"] = ruleSetting.IsDisabled.ToString();
settings[$"{RULE_SETTINGS_PREFIX}{ruleSetting.RuleName}.Impersonate"] = ruleSetting.Impersonate ? "onBehalfOfInitiator" : "false";
settings[$"{RULE_SETTINGS_PREFIX}{ruleSetting.RuleName}.BypassRules"] = ruleSetting.BypassRules ? "true" : "false";
}

private static void ApplyWithAppSettings(this Microsoft.Azure.Management.AppService.Fluent.IWebApp webApp, Dictionary<string, string> settings)
Expand Down
3 changes: 2 additions & 1 deletion src/aggregator-shared/Model/RuleConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public RuleConfiguration(string ruleName)
public string RuleName { get; }
public bool IsDisabled { get; set; }
public bool Impersonate { get; set; }
public bool BypassRules { get; set; }
}
}
}

0 comments on commit 59f338d

Please sign in to comment.