This commit is contained in:
Bernie White 2024-06-26 01:45:22 +10:00 коммит произвёл GitHub
Родитель fadde8c7bd
Коммит fe0ef097cc
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
14 изменённых файлов: 233 добавлений и 111 удалений

Просмотреть файл

@ -0,0 +1,10 @@
# Feature flagging
!!! Abstract
Feature flags are a way to enable or disable functionality.
Rule and module authors can use feature flags to toggle functionality on or off.
## Using feature flags in emitters
When an emitter is executed `IEmitterContext` is passed into each call.
This context includes a `Configuration` property that exposes `IConfiguration`.

Просмотреть файл

@ -29,7 +29,7 @@ public interface IEmitterContext
void Emit(ITargetObject value);
/// <summary>
///
/// Determine if a specified path should be queued for processing.
/// </summary>
bool ShouldQueue(string path);
}

Просмотреть файл

@ -35,8 +35,4 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Runtime\" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Runtime;
/// <summary>
/// Access configuration values at runtime.
/// </summary>
public interface IConfiguration
{
/// <summary>
/// Try to the configuration item if it exists.
/// </summary>
/// <param name="configurationKey">The name of the configuration item.</param>
/// <param name="defaultValue">The default value to use if the configuration item does not exist.</param>
/// <returns>Returns the configuration item or the specified default value.</returns>
object? GetValueOrDefault(string configurationKey, object? defaultValue = default);
/// <summary>
/// Get the specified configuration item as a string if it exists.
/// </summary>
/// <param name="configurationKey">The name of the configuration item.</param>
/// <param name="defaultValue">The default value to use if the configuration item does not exist.</param>
/// <returns>Returns the configuration item or the specified default value.</returns>
string? GetStringOrDefault(string configurationKey, string? defaultValue = default);
/// <summary>
/// Get the specified configuration item as a boolean if it exists.
/// </summary>
/// <param name="configurationKey">The name of the configuration item.</param>
/// <param name="defaultValue">The default value to use if the configuration item does not exist.</param>
/// <returns>Returns the configuration item or the specified default value.</returns>
bool? GetBoolOrDefault(string configurationKey, bool? defaultValue = default);
/// <summary>
/// Get the specified configuration item as an integer if it exists.
/// </summary>
/// <param name="configurationKey">The name of the configuration item.</param>
/// <param name="defaultValue">The default value to use if the configuration item does not exist.</param>
/// <returns>Returns the configuration item or the specified default value.</returns>
int? GetIntegerOrDefault(string configurationKey, int? defaultValue = default);
/// <summary>
/// Get the specified configuration item as a string array.
/// </summary>
/// <param name="configurationKey">The name of the configuration item.</param>
/// <returns>
/// Returns an array of strings.
/// If the configuration key does not exist and empty array is returned.
/// If the configuration key is a string, an array with a single element is returned.
/// </returns>
string[] GetStringValues(string configurationKey);
/// <summary>
/// Check if specified configuration item is enabled.
/// </summary>
/// <remarks>
/// Use this method to check if a feature is enabled.
/// </remarks>
/// <param name="configurationKey">The name of the configuration item.</param>
/// <returns>Returns <c>true</c> when the configuration item exists and it set to <c>true</c>. Otherwise <c>false</c> is returned.</returns>
bool IsEnabled(string configurationKey);
}

Просмотреть файл

@ -2,6 +2,7 @@
// Licensed under the MIT License.
using System.Collections.Concurrent;
using PSRule.Configuration;
using PSRule.Data;
using PSRule.Emitters;
using PSRule.Options;
@ -19,8 +20,8 @@ internal sealed class EmitterContext : BaseEmitterContext
/// <summary>
/// Create an instance containing context for an <see cref="IEmitter"/>.
/// </summary>
internal EmitterContext(ConcurrentQueue<ITargetObject> queue, PathFilter inputFilter, InputFormat? inputFormat, string objectPath, bool? shouldEmitFile)
: base(inputFormat ?? InputFormat.None, objectPath, shouldEmitFile ?? false)
internal EmitterContext(ConcurrentQueue<ITargetObject> queue, PathFilter inputFilter, PSRuleOption option)
: base(option?.Input?.Format ?? InputFormat.None, option?.Input?.ObjectPath, option?.Input?.FileObjects ?? false)
{
_Queue = queue;
_InputFilter = inputFilter;

Просмотреть файл

@ -99,13 +99,6 @@ internal sealed class GetTargetPipelineBuilder : PipelineBuilderBase, IGetTarget
return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next);
});
}
//else if (Option.Input.Format == InputFormat.Detect && _InputPath != null)
//{
// AddVisitTargetObjectAction((sourceObject, next) =>
// {
// return PipelineReceiverActions.DetectInputFormat(sourceObject, next);
// });
//}
return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter(), Option.Input.Format, Option.Input.ObjectPath, Option.Input.FileObjects);
return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter(), Option);
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a pipeline for executing rules and conventions within a PSRule sandbox.
/// </summary>
public interface IInvokePipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Configures paths that will be scanned for input.
/// </summary>
/// <param name="path">An array of relative or absolute path specs to be scanned. Directories will be recursively scanned for all files not excluded matching the file path spec.</param>
void InputPath(string[] path);
/// <summary>
/// Configures a variable that will receive all results in addition to the host context.
/// </summary>
/// <param name="variableName">The name of the variable to set.</param>
void ResultVariable(string variableName);
/// <summary>
/// Unblocks PowerShell sources from trusted publishers that originate from an Internet zone.
/// </summary>
/// <param name="publisher">The trusted publisher to unblock.</param>
void UnblockPublisher(string publisher);
}

Просмотреть файл

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Configuration;
@ -7,30 +7,6 @@ using PSRule.Options;
namespace PSRule.Pipeline;
/// <summary>
/// A helper to build a pipeline for executing rules and conventions within a PSRule sandbox.
/// </summary>
public interface IInvokePipelineBuilder : IPipelineBuilder
{
/// <summary>
/// Configures paths that will be scanned for input.
/// </summary>
/// <param name="path">An array of relative or absolute path specs to be scanned. Directories will be recursively scanned for all files not excluded matching the file path spec.</param>
void InputPath(string[] path);
/// <summary>
/// Configures a variable that will receive all results in addition to the host context.
/// </summary>
/// <param name="variableName">The name of the variable to set.</param>
void ResultVariable(string variableName);
/// <summary>
/// Unblocks PowerShell sources from trusted publishers that originate from an Internet zone.
/// </summary>
/// <param name="publisher">The trusted publisher to unblock.</param>
void UnblockPublisher(string publisher);
}
internal abstract class InvokePipelineBuilderBase : PipelineBuilderBase, IInvokePipelineBuilder
{
protected InputPathBuilder _InputPath;
@ -190,22 +166,6 @@ internal abstract class InvokePipelineBuilderBase : PipelineBuilderBase, IInvoke
return PipelineReceiverActions.ConvertFromPowerShellData(sourceObject, next);
});
}
//else if (Option.Input.Format == InputFormat.Detect && _InputPath != null)
//{
// AddVisitTargetObjectAction((sourceObject, next) =>
// {
// return PipelineReceiverActions.DetectInputFormat(sourceObject, next);
// });
//}
return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter(), Option.Input.Format, Option.Input.ObjectPath, Option.Input.FileObjects);
return new PipelineInputStream(VisitTargetObject, _InputPath, GetInputObjectSourceFilter(), Option);
}
}
/// <summary>
/// A helper to construct the pipeline for Invoke-PSRule.
/// </summary>
internal sealed class InvokeRulePipelineBuilder : InvokePipelineBuilderBase
{
internal InvokeRulePipelineBuilder(Source[] source, IHostContext hostContext)
: base(source, hostContext) { }
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Pipeline;
/// <summary>
/// A helper to construct the pipeline for Invoke-PSRule.
/// </summary>
internal sealed class InvokeRulePipelineBuilder : InvokePipelineBuilderBase
{
internal InvokeRulePipelineBuilder(Source[] source, IHostContext hostContext)
: base(source, hostContext) { }
}

Просмотреть файл

@ -387,7 +387,7 @@ internal abstract class PipelineBuilderBase : IPipelineBuilder
protected virtual PipelineInputStream PrepareReader()
{
return new PipelineInputStream(null, null, GetInputObjectSourceFilter(), Option.Input.Format, Option.Input.ObjectPath, Option.Input.FileObjects);
return new PipelineInputStream(null, null, GetInputObjectSourceFilter(), Option);
}
protected virtual PipelineWriter PrepareWriter()

Просмотреть файл

@ -3,8 +3,8 @@
using System.Collections.Concurrent;
using System.Management.Automation;
using PSRule.Configuration;
using PSRule.Data;
using PSRule.Options;
using PSRule.Pipeline.Emitters;
namespace PSRule.Pipeline;
@ -22,13 +22,13 @@ internal sealed class PipelineInputStream
private readonly ConcurrentQueue<ITargetObject> _Queue;
private readonly EmitterCollection _EmitterCollection;
public PipelineInputStream(VisitTargetObject input, InputPathBuilder inputPath, PathFilter inputFilter, InputFormat? inputFormat, string objectPath, bool? shouldEmitFile)
public PipelineInputStream(VisitTargetObject input, InputPathBuilder inputPath, PathFilter inputFilter, PSRuleOption option)
{
_Input = input;
_InputPath = inputPath;
_InputFilter = inputFilter;
_Queue = new ConcurrentQueue<ITargetObject>();
_EmitterCollection = new EmitterBuilder().Build(new EmitterContext(_Queue, inputFilter, inputFormat, objectPath, shouldEmitFile));
_EmitterCollection = new EmitterBuilder().Build(new EmitterContext(_Queue, inputFilter, option));
}
public int Count => _Queue.Count;

Просмотреть файл

@ -6,10 +6,12 @@ using System.Dynamic;
namespace PSRule.Runtime;
#nullable enable
/// <summary>
/// A set of rule configuration values that are exposed at runtime and automatically failback to defaults when not set in configuration.
/// A set of rule configuration values that are exposed at runtime and automatically fallback to defaults when not set in configuration.
/// </summary>
public sealed class Configuration : DynamicObject
public sealed class Configuration : DynamicObject, IConfiguration
{
private readonly RunspaceContext _Context;
@ -19,7 +21,7 @@ public sealed class Configuration : DynamicObject
}
/// <inheritdoc/>
public override bool TryGetMember(GetMemberBinder binder, out object result)
public override bool TryGetMember(GetMemberBinder binder, out object? result)
{
result = null;
if (binder == null || string.IsNullOrEmpty(binder.Name))
@ -29,18 +31,47 @@ public sealed class Configuration : DynamicObject
return TryGetValue(binder.Name, out result);
}
/// <summary>
/// Get the specified configuration key as a string array.
/// </summary>
/// <param name="configurationKey">A key for the configuration value.</param>
/// <returns>Returns an array of strings. If the configuration key does not exist and empty array is returned.</returns>
/// <inheritdoc/>
public object? GetValueOrDefault(string configurationKey, object? defaultValue = default)
{
return TryGetValue(configurationKey, out var value) && value != null ? value : defaultValue;
}
/// <inheritdoc/>
public string? GetStringOrDefault(string configurationKey, string? defaultValue = default)
{
return TryGetValue(configurationKey, out var value) &&
value != null &&
TryString(value, out var result) &&
result != null ? result : defaultValue;
}
/// <inheritdoc/>
public bool? GetBoolOrDefault(string configurationKey, bool? defaultValue = default)
{
return TryGetValue(configurationKey, out var value) &&
value != null &&
TryBool(value, out var result) &&
result != null ? result : defaultValue;
}
/// <inheritdoc/>
public int? GetIntegerOrDefault(string configurationKey, int? defaultValue = default)
{
return TryGetValue(configurationKey, out var value) &&
value != null &&
TryInt(value, out var result) &&
result != null ? result : defaultValue;
}
/// <inheritdoc/>
public string[] GetStringValues(string configurationKey)
{
if (!TryGetValue(configurationKey, out var value) || value == null)
return Array.Empty<string>();
return [];
if (value is string valueT)
return new string[] { valueT };
return [valueT];
if (value is string[] result)
return result;
@ -49,56 +80,34 @@ public sealed class Configuration : DynamicObject
{
var cList = new List<string>();
foreach (var v in c)
{
cList.Add(v.ToString());
}
return cList.ToArray();
return [.. cList];
}
return new string[] { value.ToString() };
return [value.ToString()];
}
/// <summary>
/// Try to the configuration key or use the specified default value if the key does not exist.
/// </summary>
/// <param name="configurationKey">A key for the configuration value.</param>
/// <param name="defaultValue">The default value to use if the configuration key does not exist.</param>
/// <returns>Returns the configured value or the default.</returns>
public object GetValueOrDefault(string configurationKey, object defaultValue)
/// <inheritdoc/>
public bool IsEnabled(string configurationKey)
{
return !TryGetValue(configurationKey, out var value) || value == null ? defaultValue : value;
return TryGetValue(configurationKey, out var value) &&
value != null &&
TryBool(value, out var result) &&
result == true;
}
/// <summary>
/// Try to get the configuration key as a <seealso cref="bool"/>.
/// </summary>
/// <param name="configurationKey">A key for the configuration value.</param>
/// <param name="defaultValue">The default value to use if the configuration key does not exist.</param>
/// <returns>Returns the configured value or the default.</returns>
public bool GetBoolOrDefault(string configurationKey, bool defaultValue)
private bool TryGetValue(string name, out object? value)
{
return !TryGetValue(configurationKey, out var value) || !TryBool(value, out var result) ? defaultValue : result;
}
/// <summary>
/// Try to get the configuration key as an <seealso cref="int"/>.
/// </summary>
/// <param name="configurationKey">A key for the configuration value.</param>
/// <param name="defaultValue">The default value to use if the configuration key does not exist.</param>
/// <returns>Returns the configured value or the default.</returns>
public int GetIntegerOrDefault(string configurationKey, int defaultValue)
{
return !TryGetValue(configurationKey, out var value) || !TryInt(value, out var result) ? defaultValue : result;
}
private bool TryGetValue(string name, out object value)
{
value = null;
value = default;
return _Context != null && _Context.TryGetConfigurationValue(name, out value);
}
private static bool TryBool(object o, out bool value)
private static bool TryBool(object o, out bool? value)
{
value = default;
if (o is bool result || (o is string svalue && bool.TryParse(svalue, out result)))
if (o is bool result || (o is string s && bool.TryParse(s, out result)))
{
value = result;
return true;
@ -106,10 +115,21 @@ public sealed class Configuration : DynamicObject
return false;
}
private static bool TryInt(object o, out int value)
private static bool TryInt(object o, out int? value)
{
value = default;
if (o is int result || (o is string svalue && int.TryParse(svalue, out result)))
if (o is int result || (o is string s && int.TryParse(s, out result)))
{
value = result;
return true;
}
return false;
}
private static bool TryString(object o, out string? value)
{
value = default;
if (o is string result)
{
value = result;
return true;
@ -117,3 +137,5 @@ public sealed class Configuration : DynamicObject
return false;
}
}
#nullable restore

Просмотреть файл

@ -9,8 +9,14 @@ namespace PSRule.Runtime;
[Flags]
public enum RunspaceScope
{
/// <summary>
/// Unknown scope.
/// </summary>
None = 0,
/// <summary>
/// During source discovery.
/// </summary>
Source = 1,
/// <summary>
@ -28,13 +34,43 @@ public enum RunspaceScope
/// </summary>
Resource = 8,
/// <summary>
/// When a convention is executing the begin block.
/// </summary>
ConventionBegin = 16,
/// <summary>
/// When a convention is executing the process block.
/// </summary>
ConventionProcess = 32,
/// <summary>
/// When a convention is executing the end block.
/// </summary>
ConventionEnd = 64,
/// <summary>
/// When a convention is executing the initialize block.
/// </summary>
ConventionInitialize = 128,
/// <summary>
/// When any convention block is executing.
/// </summary>
Convention = ConventionInitialize | ConventionBegin | ConventionProcess | ConventionEnd,
/// <summary>
/// When a runtime block is executing and the target is available.
/// </summary>
Target = Rule | Precondition | ConventionBegin | ConventionProcess,
/// <summary>
/// When any runtime block is executing within a rule or convention.
/// </summary>
Runtime = Rule | Precondition | Convention,
/// <summary>
/// All scopes.
/// </summary>
All = Source | Rule | Precondition | Resource | Convention,
}

Просмотреть файл

@ -180,7 +180,7 @@ public sealed class PipelineTests
Environment.UseCurrentCulture(CultureInfo.InvariantCulture);
var context = PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContextBuilder(), null);
var writer = new TestWriter(GetOption());
var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null, null, null, null), writer, false);
var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null, null), writer, false);
try
{
pipeline.Begin();
@ -202,7 +202,7 @@ public sealed class PipelineTests
option.Execution.InvariantCulture = ExecutionActionPreference.Ignore;
var context = PipelineContext.New(option, null, null, null, null, null, new OptionContextBuilder(), null);
var writer = new TestWriter(option);
var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null, null, null, null), writer, false);
var pipeline = new GetRulePipeline(context, GetSource(), new PipelineInputStream(null, null, null, null), writer, false);
try
{
pipeline.Begin();