Split out context interfaces for discovery (#2546)

This commit is contained in:
Bernie White 2024-09-20 02:41:21 +10:00 коммит произвёл GitHub
Родитель 272b8744bf
Коммит 4650fcc68a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
12 изменённых файлов: 391 добавлений и 71 удалений

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

@ -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);
}
}