зеркало из https://github.com/microsoft/PSRule.git
Split out context interfaces for discovery (#2546)
This commit is contained in:
Родитель
272b8744bf
Коммит
4650fcc68a
|
@ -0,0 +1,38 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using PSRule.Pipeline;
|
||||||
|
using PSRule.Runtime;
|
||||||
|
|
||||||
|
namespace PSRule.Definitions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A context that is used for discovery of resources.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IResourceDiscoveryContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A writer to log messages.
|
||||||
|
/// </summary>
|
||||||
|
IPipelineWriter Writer { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enter a language scope.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The source file to enter.</param>
|
||||||
|
void EnterLanguageScope(ISourceFile file);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exit a language scope.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="file">The source file to exit.</param>
|
||||||
|
void ExitLanguageScope(ISourceFile file);
|
||||||
|
|
||||||
|
void PushScope(RunspaceScope scope);
|
||||||
|
|
||||||
|
void PopScope(RunspaceScope scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
#nullable restore
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System.Management.Automation;
|
||||||
|
using PSRule.Options;
|
||||||
|
|
||||||
|
namespace PSRule.Definitions;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A context that is used for discovery of resources defined as script blocks.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IScriptResourceDiscoveryContext : IResourceDiscoveryContext
|
||||||
|
{
|
||||||
|
PowerShell GetPowerShell();
|
||||||
|
|
||||||
|
ExecutionOption GetExecutionOption();
|
||||||
|
}
|
|
@ -17,7 +17,7 @@ public abstract class InternalResource<TSpec> : Resource<TSpec>, IResource, IAnn
|
||||||
private protected InternalResource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec)
|
private protected InternalResource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec)
|
||||||
: base(kind, apiVersion, source, metadata, info, extent, spec)
|
: base(kind, apiVersion, source, metadata, info, extent, spec)
|
||||||
{
|
{
|
||||||
_Annotations = new Dictionary<Type, ResourceAnnotation>();
|
_Annotations = [];
|
||||||
Obsolete = ResourceHelper.IsObsolete(metadata);
|
Obsolete = ResourceHelper.IsObsolete(metadata);
|
||||||
Flags |= ResourceHelper.IsObsolete(metadata) ? ResourceFlags.Obsolete : ResourceFlags.None;
|
Flags |= ResourceHelper.IsObsolete(metadata) ? ResourceFlags.Obsolete : ResourceFlags.None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using PSRule.Pipeline;
|
using PSRule.Pipeline;
|
||||||
using YamlDotNet.Serialization;
|
using YamlDotNet.Serialization;
|
||||||
|
|
||||||
|
@ -22,12 +23,12 @@ public abstract class Resource<TSpec> where TSpec : Spec, new()
|
||||||
Kind = kind;
|
Kind = kind;
|
||||||
ApiVersion = apiVersion;
|
ApiVersion = apiVersion;
|
||||||
Info = info;
|
Info = info;
|
||||||
Source = source;
|
Source = source ?? throw new ArgumentNullException(nameof(source));
|
||||||
Extent = extent;
|
Extent = extent;
|
||||||
Spec = spec;
|
Spec = spec ?? throw new ArgumentNullException(nameof(spec));
|
||||||
Metadata = metadata;
|
Metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
|
||||||
Name = metadata.Name;
|
Name = metadata.Name;
|
||||||
Id = new ResourceId(source.Module, Name, ResourceIdKind.Id);
|
Id = new ResourceId(Source.Module, Name, ResourceIdKind.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -57,11 +57,13 @@ internal static class HostHelper
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get meta resources which are resource defined in YAML or JSON.
|
/// Get meta resources which are resource defined in YAML or JSON.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static IEnumerable<ILanguageBlock> GetYamlJsonLanguageBlocks(Source[] source, RunspaceContext context)
|
internal static IEnumerable<T> GetMetaResources<T>(Source[] source, IResourceDiscoveryContext context) where T : ILanguageBlock
|
||||||
{
|
{
|
||||||
var results = new List<ILanguageBlock>();
|
if (source == null || source.Length == 0) return [];
|
||||||
results.AddRange(GetYamlLanguageBlocks(source, context));
|
|
||||||
results.AddRange(GetJsonLanguageBlocks(source, context));
|
var results = new List<T>();
|
||||||
|
results.AddRange(GetYamlLanguageBlocks(source, context).OfType<T>());
|
||||||
|
results.AddRange(GetJsonLanguageBlocks(source, context).OfType<T>());
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +72,7 @@ internal static class HostHelper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static IEnumerable<Baseline> GetBaseline(Source[] source, RunspaceContext context)
|
internal static IEnumerable<Baseline> GetBaseline(Source[] source, RunspaceContext context)
|
||||||
{
|
{
|
||||||
return ToBaselineV1(GetYamlJsonLanguageBlocks(source, context), context);
|
return ToBaselineV1(GetMetaResources<ILanguageBlock>(source, context), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -78,7 +80,7 @@ internal static class HostHelper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static IEnumerable<ModuleConfigV1> GetModuleConfigForTests(Source[] source, RunspaceContext context)
|
internal static IEnumerable<ModuleConfigV1> GetModuleConfigForTests(Source[] source, RunspaceContext context)
|
||||||
{
|
{
|
||||||
return ToModuleConfigV1(GetYamlJsonLanguageBlocks(source, context), context);
|
return ToModuleConfigV1(GetMetaResources<ILanguageBlock>(source, context), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -86,7 +88,7 @@ internal static class HostHelper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static IEnumerable<SelectorV1> GetSelectorForTests(Source[] source, RunspaceContext context)
|
internal static IEnumerable<SelectorV1> GetSelectorForTests(Source[] source, RunspaceContext context)
|
||||||
{
|
{
|
||||||
return ToSelectorV1(GetYamlJsonLanguageBlocks(source, context), context);
|
return ToSelectorV1(GetMetaResources<ILanguageBlock>(source, context), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -94,15 +96,7 @@ internal static class HostHelper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static IEnumerable<SuppressionGroupV1> GetSuppressionGroupForTests(Source[] source, RunspaceContext context)
|
internal static IEnumerable<SuppressionGroupV1> GetSuppressionGroupForTests(Source[] source, RunspaceContext context)
|
||||||
{
|
{
|
||||||
return ToSuppressionGroupV1(GetYamlJsonLanguageBlocks(source, context), context);
|
return ToSuppressionGroupV1(GetMetaResources<ILanguageBlock>(source, context), context);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Import meta resources which are resource defined in YAML or JSON.
|
|
||||||
/// </summary>
|
|
||||||
internal static IEnumerable<ILanguageBlock> ImportResource(Source[] source, RunspaceContext context)
|
|
||||||
{
|
|
||||||
return source == null || source.Length == 0 ? Array.Empty<ILanguageBlock>() : GetYamlJsonLanguageBlocks(source, context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -167,16 +161,16 @@ internal static class HostHelper
|
||||||
{
|
{
|
||||||
var results = new List<ILanguageBlock>();
|
var results = new List<ILanguageBlock>();
|
||||||
results.AddRange(GetPSLanguageBlocks(context, sources));
|
results.AddRange(GetPSLanguageBlocks(context, sources));
|
||||||
results.AddRange(GetYamlJsonLanguageBlocks(sources, context));
|
results.AddRange(GetMetaResources<ILanguageBlock>(sources, context));
|
||||||
return [.. results];
|
return [.. results];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Execute PowerShell script files to get language blocks.
|
/// Execute PowerShell script files to get language blocks.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static ILanguageBlock[] GetPSLanguageBlocks(RunspaceContext context, Source[] sources)
|
private static ILanguageBlock[] GetPSLanguageBlocks(IScriptResourceDiscoveryContext context, Source[] sources)
|
||||||
{
|
{
|
||||||
if (context.Pipeline.Option.Execution.RestrictScriptSource == Options.RestrictScriptSource.DisablePowerShell)
|
if (context.GetExecutionOption().RestrictScriptSource == Options.RestrictScriptSource.DisablePowerShell)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
var results = new List<ILanguageBlock>();
|
var results = new List<ILanguageBlock>();
|
||||||
|
@ -196,7 +190,7 @@ internal static class HostHelper
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ps.Commands.Clear();
|
ps.Commands.Clear();
|
||||||
context.VerboseRuleDiscovery(path: file.Path);
|
context.Writer?.VerboseRuleDiscovery(path: file.Path);
|
||||||
context.EnterLanguageScope(file);
|
context.EnterLanguageScope(file);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -207,14 +201,14 @@ internal static class HostHelper
|
||||||
if (visitor.Errors != null && visitor.Errors.Count > 0)
|
if (visitor.Errors != null && visitor.Errors.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var record in visitor.Errors)
|
foreach (var record in visitor.Errors)
|
||||||
context.WriteError(record);
|
context.Writer?.WriteError(record);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (errors != null && errors.Length > 0)
|
if (errors != null && errors.Length > 0)
|
||||||
{
|
{
|
||||||
foreach (var error in errors)
|
foreach (var error in errors)
|
||||||
context.WriteError(error);
|
context.Writer?.WriteError(error);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -242,7 +236,7 @@ internal static class HostHelper
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
context.Writer.ExitScope();
|
context.Writer?.ExitScope();
|
||||||
context.PopScope(RunspaceScope.Source);
|
context.PopScope(RunspaceScope.Source);
|
||||||
ps.Runspace = null;
|
ps.Runspace = null;
|
||||||
ps.Dispose();
|
ps.Dispose();
|
||||||
|
@ -253,7 +247,7 @@ internal static class HostHelper
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get language blocks from YAML source files.
|
/// Get language blocks from YAML source files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static ILanguageBlock[] GetYamlLanguageBlocks(Source[] sources, RunspaceContext context)
|
private static ILanguageBlock[] GetYamlLanguageBlocks(Source[] sources, IResourceDiscoveryContext context)
|
||||||
{
|
{
|
||||||
var result = new Collection<ILanguageBlock>();
|
var result = new Collection<ILanguageBlock>();
|
||||||
var visitor = new ResourceValidator(context.Writer);
|
var visitor = new ResourceValidator(context.Writer);
|
||||||
|
@ -284,7 +278,7 @@ internal static class HostHelper
|
||||||
if (file.Type != SourceType.Yaml)
|
if (file.Type != SourceType.Yaml)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
context.VerboseRuleDiscovery(path: file.Path);
|
context.Writer?.VerboseRuleDiscovery(path: file.Path);
|
||||||
context.EnterLanguageScope(file);
|
context.EnterLanguageScope(file);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -319,7 +313,7 @@ internal static class HostHelper
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get language blocks from JSON source files.
|
/// Get language blocks from JSON source files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static ILanguageBlock[] GetJsonLanguageBlocks(Source[] sources, RunspaceContext context)
|
private static ILanguageBlock[] GetJsonLanguageBlocks(Source[] sources, IResourceDiscoveryContext context)
|
||||||
{
|
{
|
||||||
var result = new Collection<ILanguageBlock>();
|
var result = new Collection<ILanguageBlock>();
|
||||||
var visitor = new ResourceValidator(context.Writer);
|
var visitor = new ResourceValidator(context.Writer);
|
||||||
|
@ -344,7 +338,7 @@ internal static class HostHelper
|
||||||
if (file.Type != SourceType.Json)
|
if (file.Type != SourceType.Json)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
context.VerboseRuleDiscovery(file.Path);
|
context.Writer?.VerboseRuleDiscovery(file.Path);
|
||||||
context.EnterLanguageScope(file);
|
context.EnterLanguageScope(file);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -636,7 +630,7 @@ internal static class HostHelper
|
||||||
private static Baseline[] ToBaselineV1(IEnumerable<ILanguageBlock> blocks, RunspaceContext context)
|
private static Baseline[] ToBaselineV1(IEnumerable<ILanguageBlock> blocks, RunspaceContext context)
|
||||||
{
|
{
|
||||||
if (blocks == null)
|
if (blocks == null)
|
||||||
return Array.Empty<Baseline>();
|
return [];
|
||||||
|
|
||||||
// Index baselines by BaselineId
|
// Index baselines by BaselineId
|
||||||
var results = new Dictionary<string, Baseline>(StringComparer.OrdinalIgnoreCase);
|
var results = new Dictionary<string, Baseline>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using PSRule.Definitions;
|
||||||
|
|
||||||
|
namespace PSRule.Pipeline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A cache that stores resources.
|
||||||
|
/// </summary>
|
||||||
|
internal interface IResourceCache
|
||||||
|
{
|
||||||
|
bool Import(IResource resource);
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
// Licensed under the MIT License.
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
using System.Management.Automation;
|
using System.Management.Automation;
|
||||||
|
using System.Management.Automation.Language;
|
||||||
using PSRule.Resources;
|
using PSRule.Resources;
|
||||||
|
|
||||||
namespace PSRule.Pipeline;
|
namespace PSRule.Pipeline;
|
||||||
|
@ -98,6 +99,21 @@ public static class PipelineWriterExtensions
|
||||||
writer.WriteError(new ErrorRecord(exception, errorId, errorCategory, null));
|
writer.WriteError(new ErrorRecord(exception, errorId, errorCategory, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static void WriteError(this IPipelineWriter writer, ParseError error)
|
||||||
|
{
|
||||||
|
if (writer == null || !writer.ShouldWriteError())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var record = new ErrorRecord
|
||||||
|
(
|
||||||
|
exception: new Pipeline.ParseException(message: error.Message, errorId: error.ErrorId),
|
||||||
|
errorId: error.ErrorId,
|
||||||
|
errorCategory: ErrorCategory.InvalidOperation,
|
||||||
|
targetObject: null
|
||||||
|
);
|
||||||
|
writer.WriteError(errorRecord: record);
|
||||||
|
}
|
||||||
|
|
||||||
internal static void WriteDebug(this IPipelineWriter writer, string message, params object[] args)
|
internal static void WriteDebug(this IPipelineWriter writer, string message, params object[] args)
|
||||||
{
|
{
|
||||||
if (writer == null || !writer.ShouldWriteDebug() || string.IsNullOrEmpty(message))
|
if (writer == null || !writer.ShouldWriteDebug() || string.IsNullOrEmpty(message))
|
||||||
|
|
|
@ -0,0 +1,169 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using PSRule.Definitions;
|
||||||
|
using PSRule.Definitions.Baselines;
|
||||||
|
using PSRule.Definitions.ModuleConfigs;
|
||||||
|
using PSRule.Definitions.Selectors;
|
||||||
|
using PSRule.Definitions.SuppressionGroups;
|
||||||
|
|
||||||
|
namespace PSRule.Pipeline;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Define a cache for resources.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ResourceCache : IResourceCache
|
||||||
|
{
|
||||||
|
private readonly List<ResourceIssue> _TrackedIssues;
|
||||||
|
private readonly IList<ResourceRef> _Unresolved;
|
||||||
|
|
||||||
|
internal readonly Dictionary<string, ModuleConfigV1> ModuleConfigs;
|
||||||
|
internal readonly Dictionary<string, (Baseline baseline, BaselineRef baselineRef)> Baselines;
|
||||||
|
internal readonly List<SelectorV1> Selectors;
|
||||||
|
internal readonly List<SuppressionGroupV1> SuppressionGroups;
|
||||||
|
|
||||||
|
public ResourceCache(IList<ResourceRef> unresolved)
|
||||||
|
{
|
||||||
|
_TrackedIssues = [];
|
||||||
|
Selectors = [];
|
||||||
|
SuppressionGroups = [];
|
||||||
|
ModuleConfigs = new Dictionary<string, ModuleConfigV1>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
Baselines = new Dictionary<string, (Baseline baseline, BaselineRef baselineRef)>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
_Unresolved = unresolved ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<ResourceIssue> Issues => _TrackedIssues;
|
||||||
|
|
||||||
|
public IEnumerable<ResourceRef> Unresolved => _Unresolved;
|
||||||
|
|
||||||
|
public bool Import(IResource resource)
|
||||||
|
{
|
||||||
|
if (resource == null) throw new ArgumentNullException(nameof(resource));
|
||||||
|
|
||||||
|
if (TrackIssue(resource))
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (TryBaseline(resource, out var baseline) && TryBaselineRef(resource.Id, out var baselineRef))
|
||||||
|
{
|
||||||
|
RemoveBaselineRef(resource.Id);
|
||||||
|
//_OptionBuilder.Baseline(baselineRef.Type, baseline.BaselineId, resource.Source.Module, baseline.Spec, baseline.Obsolete);
|
||||||
|
Baselines.Add(resource.Id.Value, (baseline!, baselineRef!));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (TrySelector(resource, out var selector))
|
||||||
|
{
|
||||||
|
Selectors.Add(selector!);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (TryModuleConfig(resource, out var moduleConfig))
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(moduleConfig!.Spec?.Rule?.Baseline))
|
||||||
|
{
|
||||||
|
var baselineId = ResourceHelper.GetIdString(moduleConfig.Source.Module, moduleConfig.Spec!.Rule.Baseline);
|
||||||
|
if (!Baselines.ContainsKey(baselineId))
|
||||||
|
_Unresolved.Add(new BaselineRef(baselineId, ScopeType.Baseline));
|
||||||
|
}
|
||||||
|
// _OptionBuilder.ModuleConfig(resource.Source.Module, moduleConfig?.Spec);
|
||||||
|
ModuleConfigs.Add(resource.Source.Module, moduleConfig);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (TrySuppressionGroup(resource, out var suppressionGroup))
|
||||||
|
{
|
||||||
|
if (!suppressionGroup!.Spec.ExpiresOn.HasValue || suppressionGroup.Spec.ExpiresOn.Value > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
SuppressionGroups.Add(suppressionGroup);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check for and track resource issues.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>If the resource should be ignored then return <c>true</c>, otherwise <c>false</c> is returned.</returns>
|
||||||
|
private bool TrackIssue(IResource resource)
|
||||||
|
{
|
||||||
|
if (TrySuppressionGroup(resource, out var suppressionGroup))
|
||||||
|
{
|
||||||
|
if (suppressionGroup!.Spec.ExpiresOn.HasValue && suppressionGroup.Spec.ExpiresOn.Value <= DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
_TrackedIssues.Add(new ResourceIssue(resource.Kind, resource.Id, ResourceIssueType.SuppressionGroupExpired));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryBaselineRef(ResourceId resourceId, out BaselineRef? baselineRef)
|
||||||
|
{
|
||||||
|
baselineRef = null;
|
||||||
|
var r = _Unresolved.FirstOrDefault(i => ResourceIdEqualityComparer.IdEquals(i.Id, resourceId.Value));
|
||||||
|
if (r is not BaselineRef br)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
baselineRef = br;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveBaselineRef(ResourceId resourceId)
|
||||||
|
{
|
||||||
|
foreach (var r in _Unresolved.ToArray())
|
||||||
|
{
|
||||||
|
if (ResourceIdEqualityComparer.IdEquals(r.Id, resourceId.Value))
|
||||||
|
_Unresolved.Remove(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryBaseline(IResource resource, out Baseline? baseline)
|
||||||
|
{
|
||||||
|
baseline = null;
|
||||||
|
if (resource.Kind == ResourceKind.Baseline && resource is Baseline result)
|
||||||
|
{
|
||||||
|
baseline = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryModuleConfig(IResource resource, out ModuleConfigV1? moduleConfig)
|
||||||
|
{
|
||||||
|
moduleConfig = null;
|
||||||
|
if (resource.Kind == ResourceKind.ModuleConfig &&
|
||||||
|
!string.IsNullOrEmpty(resource.Source.Module) &&
|
||||||
|
StringComparer.OrdinalIgnoreCase.Equals(resource.Source.Module, resource.Name) &&
|
||||||
|
resource is ModuleConfigV1 result)
|
||||||
|
{
|
||||||
|
moduleConfig = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TrySelector(IResource resource, out SelectorV1? selector)
|
||||||
|
{
|
||||||
|
selector = null;
|
||||||
|
if (resource.Kind == ResourceKind.Selector && resource is SelectorV1 result)
|
||||||
|
{
|
||||||
|
selector = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TrySuppressionGroup(IResource resource, out SuppressionGroupV1? suppressionGroup)
|
||||||
|
{
|
||||||
|
suppressionGroup = null;
|
||||||
|
if (resource.Kind == ResourceKind.SuppressionGroup && resource is SuppressionGroupV1 result)
|
||||||
|
{
|
||||||
|
suppressionGroup = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#nullable restore
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using PSRule.Definitions;
|
||||||
|
using PSRule.Host;
|
||||||
|
|
||||||
|
namespace PSRule.Pipeline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines a builder to create a resource cache.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ResourceCacheBuilder(IPipelineWriter writer)
|
||||||
|
{
|
||||||
|
private IEnumerable<IResource> _Resources;
|
||||||
|
private readonly IPipelineWriter _Writer = writer;
|
||||||
|
|
||||||
|
public void Import(Source[] sources)
|
||||||
|
{
|
||||||
|
_Resources = HostHelper.GetMetaResources<IResource>(sources, new ResourceCacheDiscoveryContext(_Writer));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceCache Build(List<ResourceRef> unresolved)
|
||||||
|
{
|
||||||
|
var cache = new ResourceCache(unresolved);
|
||||||
|
|
||||||
|
foreach (var resource in _Resources)
|
||||||
|
cache.Import(resource);
|
||||||
|
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using PSRule.Definitions;
|
||||||
|
using PSRule.Runtime;
|
||||||
|
|
||||||
|
namespace PSRule.Pipeline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Define a context used for early stage resource discovery.
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class ResourceCacheDiscoveryContext(IPipelineWriter writer) : IResourceDiscoveryContext
|
||||||
|
{
|
||||||
|
public IPipelineWriter Writer { get; } = writer;
|
||||||
|
|
||||||
|
public void EnterLanguageScope(ISourceFile file)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExitLanguageScope(ISourceFile file)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopScope(RunspaceScope scope)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PushScope(RunspaceScope scope)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ namespace PSRule.Runtime;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A context applicable to rule execution.
|
/// A context applicable to rule execution.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class RunspaceContext : IDisposable, ILogger
|
internal sealed class RunspaceContext : IDisposable, ILogger, IScriptResourceDiscoveryContext
|
||||||
{
|
{
|
||||||
private const string SOURCE_OUTCOME_FAIL = "Rule.Outcome.Fail";
|
private const string SOURCE_OUTCOME_FAIL = "Rule.Outcome.Fail";
|
||||||
private const string SOURCE_OUTCOME_PASS = "Rule.Outcome.Pass";
|
private const string SOURCE_OUTCOME_PASS = "Rule.Outcome.Pass";
|
||||||
|
@ -31,7 +31,6 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
||||||
internal static RunspaceContext? CurrentThread;
|
internal static RunspaceContext? CurrentThread;
|
||||||
|
|
||||||
internal readonly PipelineContext Pipeline;
|
internal readonly PipelineContext Pipeline;
|
||||||
internal readonly IPipelineWriter Writer;
|
|
||||||
|
|
||||||
// Fields exposed to engine
|
// Fields exposed to engine
|
||||||
internal RuleRecord? RuleRecord;
|
internal RuleRecord? RuleRecord;
|
||||||
|
@ -99,12 +98,12 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
||||||
|
|
||||||
internal bool HadErrors => _RuleErrors > 0;
|
internal bool HadErrors => _RuleErrors > 0;
|
||||||
|
|
||||||
|
public IPipelineWriter Writer { get; }
|
||||||
|
|
||||||
internal IEnumerable<InvokeResult>? Output { get; private set; }
|
internal IEnumerable<InvokeResult>? Output { get; private set; }
|
||||||
|
|
||||||
internal TargetObject? TargetObject { get; private set; }
|
internal TargetObject? TargetObject { get; private set; }
|
||||||
|
|
||||||
internal ITargetBinder? TargetBinder { get; private set; }
|
|
||||||
|
|
||||||
internal SourceScope? Source { get; private set; }
|
internal SourceScope? Source { get; private set; }
|
||||||
|
|
||||||
internal ILanguageScope LanguageScope
|
internal ILanguageScope LanguageScope
|
||||||
|
@ -128,12 +127,12 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
||||||
return scope.HasFlag(current);
|
return scope.HasFlag(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void PushScope(RunspaceScope scope)
|
public void PushScope(RunspaceScope scope)
|
||||||
{
|
{
|
||||||
_Scope.Push(scope);
|
_Scope.Push(scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void PopScope(RunspaceScope scope)
|
public void PopScope(RunspaceScope scope)
|
||||||
{
|
{
|
||||||
var current = _Scope.Peek();
|
var current = _Scope.Peek();
|
||||||
if (current != scope)
|
if (current != scope)
|
||||||
|
@ -259,14 +258,6 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void VerboseRuleDiscovery(string path)
|
|
||||||
{
|
|
||||||
if (Writer == null || !Writer.ShouldWriteVerbose() || string.IsNullOrEmpty(path))
|
|
||||||
return;
|
|
||||||
|
|
||||||
Writer.WriteVerbose($"[PSRule][D] -- Discovering rules in: {path}");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void VerboseFoundResource(string name, string moduleName, string scriptName)
|
public void VerboseFoundResource(string name, string moduleName, string scriptName)
|
||||||
{
|
{
|
||||||
if (Writer == null || !Writer.ShouldWriteVerbose())
|
if (Writer == null || !Writer.ShouldWriteVerbose())
|
||||||
|
@ -321,30 +312,12 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
||||||
Writer.WriteVerbose(string.Concat(GetLogPrefix(), " -- [", pass, "/", count, "] [", outcome, "]"));
|
Writer.WriteVerbose(string.Concat(GetLogPrefix(), " -- [", pass, "/", count, "] [", outcome, "]"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteError(ErrorRecord record)
|
public ExecutionOption GetExecutionOption()
|
||||||
{
|
{
|
||||||
if (Writer == null || !Writer.ShouldWriteError())
|
return Pipeline.Option.Execution;
|
||||||
return;
|
|
||||||
|
|
||||||
Writer.WriteError(errorRecord: record);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteError(ParseError error)
|
public PowerShell GetPowerShell()
|
||||||
{
|
|
||||||
if (Writer == null || !Writer.ShouldWriteError())
|
|
||||||
return;
|
|
||||||
|
|
||||||
var record = new ErrorRecord
|
|
||||||
(
|
|
||||||
exception: new Pipeline.ParseException(message: error.Message, errorId: error.ErrorId),
|
|
||||||
errorId: error.ErrorId,
|
|
||||||
errorCategory: ErrorCategory.InvalidOperation,
|
|
||||||
targetObject: null
|
|
||||||
);
|
|
||||||
Writer.WriteError(errorRecord: record);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal PowerShell GetPowerShell()
|
|
||||||
{
|
{
|
||||||
var result = PowerShell.Create();
|
var result = PowerShell.Create();
|
||||||
result.Runspace = Pipeline.GetRunspace();
|
result.Runspace = Pipeline.GetRunspace();
|
||||||
|
@ -545,7 +518,7 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
||||||
return _LogPrefix ?? string.Empty;
|
return _LogPrefix ?? string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void EnterLanguageScope(ISourceFile file)
|
public void EnterLanguageScope(ISourceFile file)
|
||||||
{
|
{
|
||||||
// TODO: Look at scope caching, and a scope stack.
|
// TODO: Look at scope caching, and a scope stack.
|
||||||
|
|
||||||
|
@ -560,7 +533,7 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
||||||
Source = new SourceScope(file);
|
Source = new SourceScope(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ExitLanguageScope(ISourceFile file)
|
public void ExitLanguageScope(ISourceFile file)
|
||||||
{
|
{
|
||||||
// Look at scope popping and validation.
|
// Look at scope popping and validation.
|
||||||
|
|
||||||
|
@ -744,7 +717,7 @@ internal sealed class RunspaceContext : IDisposable, ILogger
|
||||||
public void Init(Source[] source)
|
public void Init(Source[] source)
|
||||||
{
|
{
|
||||||
InitLanguageScopes(source);
|
InitLanguageScopes(source);
|
||||||
var resources = Host.HostHelper.ImportResource(source, this).OfType<IResource>();
|
var resources = Host.HostHelper.GetMetaResources<IResource>(source, this);
|
||||||
|
|
||||||
// Process module configurations first
|
// Process module configurations first
|
||||||
foreach (var resource in resources.Where(r => r.Kind == ResourceKind.ModuleConfig).ToArray())
|
foreach (var resource in resources.Where(r => r.Kind == ResourceKind.ModuleConfig).ToArray())
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT License.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using PSRule.Definitions;
|
||||||
|
using PSRule.Definitions.Selectors;
|
||||||
|
|
||||||
|
namespace PSRule.Pipeline;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Units tests for <see cref="ResourceCache"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ResourceCacheTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Import_WhenNullResource_ShouldReturnException()
|
||||||
|
{
|
||||||
|
var cache = new ResourceCache([]);
|
||||||
|
|
||||||
|
Assert.Throws<ArgumentNullException>(() => cache.Import(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Import_WhenValidSelector_ShouldReturnTrue()
|
||||||
|
{
|
||||||
|
var cache = new ResourceCache([]);
|
||||||
|
var selector = new SelectorV1("", new SourceFile("", default, SourceType.Yaml, ""), new ResourceMetadata { Name = "test" }, default, default, new SelectorV1Spec());
|
||||||
|
|
||||||
|
Assert.True(cache.Import(selector));
|
||||||
|
Assert.Single(cache.Selectors);
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче