From 477ab6bed5fe9cd152d0037df84bcc98f2ad5a63 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 28 Oct 2019 21:38:22 +1000 Subject: [PATCH] Filter objects by target type #176 #315 #318 --- CHANGELOG.md | 7 + README.md | 1 + docs/commands/PSRule/en-US/Assert-PSRule.md | 37 ++++- docs/commands/PSRule/en-US/Invoke-PSRule.md | 35 ++++- .../commands/PSRule/en-US/New-PSRuleOption.md | 18 ++- .../commands/PSRule/en-US/Set-PSRuleOption.md | 24 +++- .../PSRule/en-US/Test-PSRuleTarget.md | 61 +++++++- .../PSRule/en-US/about_PSRule_Options.md | 45 +++++- schemas/PSRule-options.schema.json | 18 ++- src/PSRule/Configuration/BindingOption.cs | 2 +- src/PSRule/Configuration/ExecutionOption.cs | 2 +- src/PSRule/Configuration/InputOption.cs | 42 +++++- src/PSRule/Configuration/LoggingOption.cs | 2 +- src/PSRule/Configuration/OutputOption.cs | 2 +- src/PSRule/Configuration/PSRuleOption.cs | 29 ++-- src/PSRule/Host/Host.cs | 3 + src/PSRule/PSRule.psm1 | 39 +++++ src/PSRule/Pipeline/BaselineContext.cs | 27 +++- src/PSRule/Pipeline/InvokeResult.cs | 6 +- src/PSRule/Pipeline/InvokeRulePipeline.cs | 6 +- src/PSRule/Pipeline/PipelineBuilder.cs | 4 +- src/PSRule/Pipeline/PipelineContext.cs | 44 +++--- src/PSRule/Pipeline/TargetBinder.cs | 26 +++- src/PSRule/Pipeline/TestPipeline.cs | 21 ++- tests/PSRule.Tests/FromFileBaseline.Rule.ps1 | 5 +- tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 | 6 + tests/PSRule.Tests/PSRule.Common.Tests.ps1 | 133 ++++++++++++++++-- tests/PSRule.Tests/PSRule.Options.Tests.ps1 | 37 +++++ tests/PSRule.Tests/PSRule.Tests.yml | 2 + tests/PSRule.Tests/PSRule.Tests2.yml | 24 ++-- tests/PSRule.Tests/PSRule.Tests3.yml | 14 +- tests/PSRule.Tests/TargetNameBindingTests.cs | 2 +- 32 files changed, 611 insertions(+), 113 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d876ceee4..2cd3e8d438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +- Fixed missing `Markdown` input format in options schema. [#315](https://github.com/Microsoft/PSRule/issues/315) +- Added `-TargetType` parameter to filter input objects by target type. [#176](https://github.com/Microsoft/PSRule/issues/176) + - This parameter applies to `Invoke-PSRule`, `Assert-PSRule` and `Test-PSRuleTarget`. +- **Breaking change**: Unprocessed object results are not returned from `Test-PSRuleTarget` by default. [#318](https://github.com/Microsoft/PSRule/issues/318) + - Previously unprocessed objects returned `$True`, now unprocessed objects return no result. + - Use `-Outcome All` to return `$True` for unprocessed objects the same as <= v0.10.0. + ## v0.10.0 What's changed since v0.9.0: diff --git a/README.md b/README.md index 4c9b50767a..1164d8c5a6 100644 --- a/README.md +++ b/README.md @@ -245,6 +245,7 @@ The following conceptual topics exist in the `PSRule` module: - [Execution.NotProcessedWarning](docs/concepts/PSRule/en-US/about_PSRule_Options.md#executionnotprocessedwarning) - [Input.Format](docs/concepts/PSRule/en-US/about_PSRule_Options.md#inputformat) - [Input.ObjectPath](docs/concepts/PSRule/en-US/about_PSRule_Options.md#inputobjectpath) + - [Input.TargetType](docs/concepts/PSRule/en-US/about_PSRule_Options.md#inputtargettype) - [Logging.LimitDebug](docs/concepts/PSRule/en-US/about_PSRule_Options.md#logginglimitdebug) - [Logging.LimitVerbose](docs/concepts/PSRule/en-US/about_PSRule_Options.md#logginglimitverbose) - [Logging.RuleFail](docs/concepts/PSRule/en-US/about_PSRule_Options.md#loggingrulefail) diff --git a/docs/commands/PSRule/en-US/Assert-PSRule.md b/docs/commands/PSRule/en-US/Assert-PSRule.md index 84c8013166..fe6399ccf0 100644 --- a/docs/commands/PSRule/en-US/Assert-PSRule.md +++ b/docs/commands/PSRule/en-US/Assert-PSRule.md @@ -18,8 +18,8 @@ Evaluate objects against matching rules and assert any failures. ```text Assert-PSRule [-Module ] [-Format ] [-Baseline ] [-Style ] [[-Path] ] [-Name ] [-Tag ] [-OutputPath ] - [-OutputFormat ] [-Option ] [-ObjectPath ] [-Culture ] - -InputObject [-WhatIf] [-Confirm] [] + [-OutputFormat ] [-Option ] [-ObjectPath ] [-TargetType ] + [-Culture ] -InputObject [-WhatIf] [-Confirm] [] ``` ### InputPath @@ -27,8 +27,8 @@ Assert-PSRule [-Module ] [-Format ] [-Baseline [-Module ] [-Format ] [-Baseline ] [-Style ] [[-Path] ] [-Name ] [-Tag ] [-OutputPath ] - [-OutputFormat ] [-Option ] [-ObjectPath ] [-Culture ] [-WhatIf] - [-Confirm] [] + [-OutputFormat ] [-Option ] [-ObjectPath ] [-TargetType ] + [-Culture ] [-WhatIf] [-Confirm] [] ``` ## DESCRIPTION @@ -64,7 +64,7 @@ $items | Assert-PSRule -Path .\docs\scenarios\fruit\ ``` ```text - -> Fridge : System.Management.Automation.PSCustomObject +-> Fridge : System.Management.Automation.PSCustomObject [FAIL] isFruit @@ -247,6 +247,33 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -TargetType + +Filters input objects by TargetType. + +If specified, only objects with the specified TargetType are processed. +Objects that do not match TargetType are ignored. +If multiple values are specified, only one TargetType must match. This parameter is not case-sensitive. + +By default, all objects are processed. + +This parameter if set, overrides the `Input.TargetType` option. + +To change the field TargetType is bound to set the `Binding.TargetType` option. +For details see the about_PSRule_Options help topic. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Option Additional options that configure execution. diff --git a/docs/commands/PSRule/en-US/Invoke-PSRule.md b/docs/commands/PSRule/en-US/Invoke-PSRule.md index 2773dccacc..edacd2a47c 100644 --- a/docs/commands/PSRule/en-US/Invoke-PSRule.md +++ b/docs/commands/PSRule/en-US/Invoke-PSRule.md @@ -18,8 +18,8 @@ Evaluate objects against matching rules and output the results. ```text Invoke-PSRule [-Module ] [-Outcome ] [-As ] [-Format ] [-OutputPath ] [-OutputFormat ] [-Baseline ] [[-Path] ] - [-Name ] [-Tag ] [-Option ] [-ObjectPath ] [-Culture ] - -InputObject [-WhatIf] [-Confirm] [] + [-Name ] [-Tag ] [-Option ] [-ObjectPath ] [-TargetType ] + [-Culture ] -InputObject [-WhatIf] [-Confirm] [] ``` ### InputPath @@ -28,7 +28,7 @@ Invoke-PSRule [-Module ] [-Outcome ] [-As ] Invoke-PSRule -InputPath [-Module ] [-Outcome ] [-As ] [-Format ] [-OutputPath ] [-OutputFormat ] [-Baseline ] [[-Path] ] [-Name ] [-Tag ] [-Option ] [-ObjectPath ] - [-Culture ] [-WhatIf] [-Confirm] [] + [-TargetType ] [-Culture ] [-WhatIf] [-Confirm] [] ``` ## DESCRIPTION @@ -164,7 +164,7 @@ Accept wildcard characters: False ### -Outcome -Filter output to only show rules with a specific outcome. +Filter output to only show rule results with a specific outcome. ```yaml Type: RuleOutcome @@ -302,6 +302,33 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -TargetType + +Filters input objects by TargetType. + +If specified, only objects with the specified TargetType are processed. +Objects that do not match TargetType are ignored. +If multiple values are specified, only one TargetType must match. This parameter is not case-sensitive. + +By default, all objects are processed. + +This parameter if set, overrides the `Input.TargetType` option. + +To change the field TargetType is bound to set the `Binding.TargetType` option. +For details see the about_PSRule_Options help topic. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Module Search for rule definitions within a module. diff --git a/docs/commands/PSRule/en-US/New-PSRuleOption.md b/docs/commands/PSRule/en-US/New-PSRuleOption.md index b4e072c7ca..ffe502a069 100644 --- a/docs/commands/PSRule/en-US/New-PSRuleOption.md +++ b/docs/commands/PSRule/en-US/New-PSRuleOption.md @@ -18,7 +18,7 @@ New-PSRuleOption [[-Path] ] [[-Option] ] [-Configuration < [-SuppressTargetName ] [-BindTargetName ] [-BindTargetType ] [-BindingIgnoreCase ] [-TargetName ] [-TargetType ] [-InconclusiveWarning ] [-NotProcessedWarning ] - [-Format ] [-ObjectPath ] [-LoggingLimitDebug ] + [-Format ] [-ObjectPath ] [-InputTargetType ] [-LoggingLimitDebug ] [-LoggingLimitVerbose ] [-LoggingRuleFail ] [-LoggingRulePass ] [-OutputAs ] [-OutputEncoding ] [-OutputFormat ] [-OutputPath ] [-OutputStyle ] [] @@ -305,6 +305,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -InputTargetType + +Sets the `Input.TargetType` option to only process objects with the specified TargetType. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -LoggingLimitDebug Sets the `Logging.LimitDebug` option to limit debug messages to a list of named debug scopes. diff --git a/docs/commands/PSRule/en-US/Set-PSRuleOption.md b/docs/commands/PSRule/en-US/Set-PSRuleOption.md index ee1cbb9790..003a1016fd 100644 --- a/docs/commands/PSRule/en-US/Set-PSRuleOption.md +++ b/docs/commands/PSRule/en-US/Set-PSRuleOption.md @@ -17,10 +17,10 @@ Sets options that configure PSRule execution. Set-PSRuleOption [[-Path] ] [-Option ] [-PassThru] [-Force] [-AllowClobber] [-BindingIgnoreCase ] [-TargetName ] [-TargetType ] [-InconclusiveWarning ] [-NotProcessedWarning ] [-Format ] - [-ObjectPath ] [-LoggingLimitDebug ] [-LoggingLimitVerbose ] - [-LoggingRuleFail ] [-LoggingRulePass ] [-OutputAs ] - [-OutputEncoding ] [-OutputFormat ] [-OutputPath ] - [-OutputStyle ] [-WhatIf] [-Confirm] [] + [-ObjectPath ] [-InputTargetType ] [-LoggingLimitDebug ] + [-LoggingLimitVerbose ] [-LoggingRuleFail ] [-LoggingRulePass ] + [-OutputAs ] [-OutputEncoding ] [-OutputFormat ] + [-OutputPath ] [-OutputStyle ] [-WhatIf] [-Confirm] [] ``` ## DESCRIPTION @@ -253,6 +253,22 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -InputTargetType + +Sets the `Input.TargetType` option to only process objects with the specified TargetType. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -LoggingLimitDebug Sets the `Logging.LimitDebug` option to limit debug messages to a list of named debug scopes. diff --git a/docs/commands/PSRule/en-US/Test-PSRuleTarget.md b/docs/commands/PSRule/en-US/Test-PSRuleTarget.md index e11b00468c..6660d182ab 100644 --- a/docs/commands/PSRule/en-US/Test-PSRuleTarget.md +++ b/docs/commands/PSRule/en-US/Test-PSRuleTarget.md @@ -16,17 +16,17 @@ Pass or fail objects against matching rules. ### Input (Default) ```text -Test-PSRuleTarget [-Module ] [-Format ] [[-Path] ] [-Name ] - [-Tag ] -InputObject [-Option ] [-ObjectPath ] [-Culture ] - [] +Test-PSRuleTarget [-Module ] [-Outcome ] [-Format ] [[-Path] ] + [-Name ] [-Tag ] -InputObject [-Option ] [-ObjectPath ] + [-TargetType ] [-Culture ] [] ``` ### InputPath ```text -Test-PSRuleTarget -InputPath [-Module ] [-Format ] [[-Path] ] - [-Name ] [-Tag ] [-Option ] [-ObjectPath ] [-Culture ] - [] +Test-PSRuleTarget -InputPath [-Module ] [-Outcome ] [-Format ] + [[-Path] ] [-Name ] [-Tag ] [-Option ] [-ObjectPath ] + [-TargetType ] [-Culture ] [] ``` ## DESCRIPTION @@ -39,9 +39,12 @@ PSRule uses the following logic to determine overall pass or fail for an object: - Any rules fail or error. - Any rules are inconclusive. - The object passes if: - - No rules were found that match preconditions, name and tag filters. + - No matching rules were found. - All rules pass. +By default, objects that do match any rules are not returned in results. +To return `$True` for these objects, use `-Outcome All`. + ## EXAMPLES ### Example 1 @@ -91,6 +94,23 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Outcome + +Filter output to only show pipeline objects with a specific outcome. + +```yaml +Type: RuleOutcome +Parameter Sets: (All) +Aliases: +Accepted values: Pass, Fail, Error, None, Processed, All + +Required: False +Position: Named +Default value: Pass, Fail, Error +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Tag Only get rules with the specified tags set. @@ -192,6 +212,33 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -TargetType + +Filters input objects by TargetType. + +If specified, only objects with the specified TargetType are processed. +Objects that do not match TargetType are ignored. +If multiple values are specified, only one TargetType must match. This parameter is not case-sensitive. + +By default, all objects are processed. + +This parameter if set, overrides the `Input.TargetType` option. + +To change the field TargetType is bound to set the `Binding.TargetType` option. +For details see the about_PSRule_Options help topic. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Module Search for rule definitions within a module. diff --git a/docs/concepts/PSRule/en-US/about_PSRule_Options.md b/docs/concepts/PSRule/en-US/about_PSRule_Options.md index aa03ee1666..048a42b03b 100644 --- a/docs/concepts/PSRule/en-US/about_PSRule_Options.md +++ b/docs/concepts/PSRule/en-US/about_PSRule_Options.md @@ -18,6 +18,7 @@ The following workspace options are available for use: - [Execution.NotProcessedWarning](#executionnotprocessedwarning) - [Input.Format](#inputformat) - [Input.ObjectPath](#inputobjectpath) +- [Input.TargetType](#inputtargettype) - [Logging.LimitDebug](#logginglimitdebug) - [Logging.LimitVerbose](#logginglimitverbose) - [Logging.RuleFail](#loggingrulefail) @@ -448,7 +449,7 @@ If the property specified by `ObjectPath` is a collection/ array, then each item If the property specified by `ObjectPath` does not exist, PSRule skips the object. -When using `Invoke-PSRule` and `Test-PSRuleTarget` the `-ObjectPath` parameter will override any value set in configuration. +When using `Invoke-PSRule`, `Test-PSRuleTarget` and `Assert-PSRule` the `-ObjectPath` parameter will override any value set in configuration. This option can be specified using: @@ -473,6 +474,44 @@ input: objectPath: items ``` +### Input.TargetType + +Filters input objects by TargetType. + +If specified, only objects with the specified TargetType are processed. +Objects that do not match TargetType are ignored. +If multiple values are specified, only one TargetType must match. This option is not case-sensitive. + +By default, all objects are processed. + +To change the field TargetType is bound to set the `Binding.TargetType` option. + +When using `Invoke-PSRule`, `Test-PSRuleTarget` and `Assert-PSRule` the `-TargetType` parameter will override any value set in configuration. + +This option can be specified using: + +```powershell +# PowerShell: Using the InputTargetType parameter +$option = New-PSRuleOption -InputTargetType 'virtualMachine'; +``` + +```powershell +# PowerShell: Using the Input.TargetType hashtable key +$option = New-PSRuleOption -Option @{ 'Input.TargetType' = 'virtualMachine' }; +``` + +```powershell +# PowerShell: Using the InputTargetType parameter to set YAML +Set-PSRuleOption -InputTargetType 'virtualMachine'; +``` + +```yaml +# YAML: Using the input/targetType property +input: + targetType: + - virtualMachine +``` + ### Logging.LimitDebug Limits debug messages to a list of named debug scopes. @@ -962,6 +1001,9 @@ execution: input: format: Yaml objectPath: items + targetType: + - Microsoft.Compute/virtualMachines + - Microsoft.Network/virtualNetworks # Configures outcome logging options logging: @@ -1030,6 +1072,7 @@ execution: input: format: Detect objectPath: null + targetType: [ ] # Configures outcome logging options logging: diff --git a/schemas/PSRule-options.schema.json b/schemas/PSRule-options.schema.json index 65007bb3ef..1cac1a1b17 100644 --- a/schemas/PSRule-options.schema.json +++ b/schemas/PSRule-options.schema.json @@ -4,7 +4,7 @@ "description": "A schema for PSRule YAML options files.", "oneOf": [ { - "$ref": "#/definitions/options-v0.8.0" + "$ref": "#/definitions/options" } ], "definitions": { @@ -75,7 +75,7 @@ }, "additionalProperties": false }, - "input-v0.4.0": { + "input-option": { "type": "object", "title": "Input options", "description": "Options that affect how input types are processed.", @@ -88,6 +88,7 @@ "None", "Yaml", "Json", + "Markdown", "Detect" ], "default": "Detect" @@ -96,6 +97,15 @@ "type": "string", "title": "Object path", "description": "The object path to a property to use instead of the pipeline object." + }, + "targetType": { + "type": "array", + "title": "Target type", + "description": "Only process objects that match one of the included types.", + "items": { + "type": "string" + }, + "uniqueItems": true } }, "additionalProperties": false @@ -254,7 +264,7 @@ }, "additionalProperties": false }, - "options-v0.8.0": { + "options": { "properties": { "binding": { "type": "object", @@ -284,7 +294,7 @@ "type": "object", "oneOf": [ { - "$ref": "#/definitions/input-v0.4.0" + "$ref": "#/definitions/input-option" } ] }, diff --git a/src/PSRule/Configuration/BindingOption.cs b/src/PSRule/Configuration/BindingOption.cs index a3f822fd5f..91e0c76f4c 100644 --- a/src/PSRule/Configuration/BindingOption.cs +++ b/src/PSRule/Configuration/BindingOption.cs @@ -13,7 +13,7 @@ public sealed class BindingOption : IEquatable { private const bool DEFAULT_IGNORECASE = true; - public static readonly BindingOption Default = new BindingOption + internal static readonly BindingOption Default = new BindingOption { IgnoreCase = DEFAULT_IGNORECASE }; diff --git a/src/PSRule/Configuration/ExecutionOption.cs b/src/PSRule/Configuration/ExecutionOption.cs index 268b074517..3bf2edfbcc 100644 --- a/src/PSRule/Configuration/ExecutionOption.cs +++ b/src/PSRule/Configuration/ExecutionOption.cs @@ -11,7 +11,7 @@ public sealed class ExecutionOption private const bool DEFAULT_INCONCLUSIVEWARNING = true; private const bool DEFAULT_NOTPROCESSEDWARNING = true; - public static readonly ExecutionOption Default = new ExecutionOption + internal static readonly ExecutionOption Default = new ExecutionOption { LanguageMode = DEFAULT_LANGUAGEMODE, InconclusiveWarning = DEFAULT_INCONCLUSIVEWARNING, diff --git a/src/PSRule/Configuration/InputOption.cs b/src/PSRule/Configuration/InputOption.cs index 2ed0f2a8e4..e340362ce4 100644 --- a/src/PSRule/Configuration/InputOption.cs +++ b/src/PSRule/Configuration/InputOption.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.ComponentModel; namespace PSRule.Configuration @@ -8,27 +9,56 @@ namespace PSRule.Configuration /// /// Options that affect how input types are processed. /// - public sealed class InputOption + public sealed class InputOption : IEquatable { private const InputFormat DEFAULT_FORMAT = PSRule.Configuration.InputFormat.Detect; private const string DEFAULT_OBJECTPATH = null; + private const string[] DEFAULT_TARGETTYPE = null; - public static readonly InputOption Default = new InputOption + internal static readonly InputOption Default = new InputOption { Format = DEFAULT_FORMAT, - ObjectPath = DEFAULT_OBJECTPATH + ObjectPath = DEFAULT_OBJECTPATH, + TargetType = DEFAULT_TARGETTYPE, }; public InputOption() { Format = null; ObjectPath = null; + TargetType = null; } public InputOption(InputOption option) { Format = option.Format; ObjectPath = option.ObjectPath; + TargetType = option.TargetType; + } + + public override bool Equals(object obj) + { + return obj is InputOption option && Equals(option); + } + + public bool Equals(InputOption other) + { + return other != null && + Format == other.Format && + ObjectPath == other.ObjectPath && + TargetType == other.TargetType; + } + + public override int GetHashCode() + { + unchecked // Overflow is fine + { + int hash = 17; + hash = hash * 23 + (Format.HasValue ? Format.Value.GetHashCode() : 0); + hash = hash * 23 + (ObjectPath != null ? ObjectPath.GetHashCode() : 0); + hash = hash * 23 + (TargetType != null ? TargetType.GetHashCode() : 0); + return hash; + } } /// @@ -42,5 +72,11 @@ public InputOption(InputOption option) /// [DefaultValue(null)] public string ObjectPath { get; set; } + + /// + /// Only process objects that match one of the included types. + /// + [DefaultValue(null)] + public string[] TargetType { get; set; } } } diff --git a/src/PSRule/Configuration/LoggingOption.cs b/src/PSRule/Configuration/LoggingOption.cs index 9b7d07ea96..c34f07576e 100644 --- a/src/PSRule/Configuration/LoggingOption.cs +++ b/src/PSRule/Configuration/LoggingOption.cs @@ -13,7 +13,7 @@ public sealed class LoggingOption private const OutcomeLogStream DEFAULT_RULEFAIL = OutcomeLogStream.None; private const OutcomeLogStream DEFAULT_RULEPASS = OutcomeLogStream.None; - public static readonly LoggingOption Default = new LoggingOption + internal static readonly LoggingOption Default = new LoggingOption { RuleFail = DEFAULT_RULEFAIL, RulePass = DEFAULT_RULEPASS, diff --git a/src/PSRule/Configuration/OutputOption.cs b/src/PSRule/Configuration/OutputOption.cs index 68389d7398..7b10d88c19 100644 --- a/src/PSRule/Configuration/OutputOption.cs +++ b/src/PSRule/Configuration/OutputOption.cs @@ -16,7 +16,7 @@ public sealed class OutputOption : IEquatable private const OutputFormat DEFAULT_FORMAT = OutputFormat.None; private const OutputStyle DEFAULT_STYLE = OutputStyle.Client; - public static readonly OutputOption Default = new OutputOption + internal static readonly OutputOption Default = new OutputOption { As = DEFAULT_AS, Encoding = DEFAULT_ENCODING, diff --git a/src/PSRule/Configuration/PSRuleOption.cs b/src/PSRule/Configuration/PSRuleOption.cs index 6d6793134c..a87221d2dd 100644 --- a/src/PSRule/Configuration/PSRuleOption.cs +++ b/src/PSRule/Configuration/PSRuleOption.cs @@ -29,7 +29,7 @@ public sealed class PSRuleOption : IEquatable, IBaselineSpec private string SourcePath; - private static readonly PSRuleOption Default = new PSRuleOption + internal static readonly PSRuleOption Default = new PSRuleOption { Binding = BindingOption.Default, Execution = ExecutionOption.Default, @@ -262,27 +262,17 @@ public static implicit operator PSRuleOption(Hashtable hashtable) { option.Input.ObjectPath = (string)value; } + if (index.TryPopValue("input.targettype", out value)) + { + option.Input.TargetType = AsStringArray(value); + } if (index.TryGetValue("logging.limitdebug", out value)) { - if (value.GetType().IsArray) - { - option.Logging.LimitDebug = ((object[])value).OfType().ToArray(); - } - else - { - option.Logging.LimitDebug = new string[] { value.ToString() }; - } + option.Logging.LimitDebug = AsStringArray(value); } if (index.TryGetValue("logging.limitverbose", out value)) { - if (value.GetType().IsArray) - { - option.Logging.LimitVerbose = ((object[])value).OfType().ToArray(); - } - else - { - option.Logging.LimitVerbose = new string[] { value.ToString() }; - } + option.Logging.LimitVerbose = AsStringArray(value); } if (index.TryGetValue("logging.rulefail", out value)) { @@ -428,5 +418,10 @@ private string GetYaml() .Build(); return s.Serialize(this); } + + private static string[] AsStringArray(object value) + { + return value.GetType().IsArray ? ((object[])value).OfType().ToArray() : new string[] { value.ToString() }; + } } } diff --git a/src/PSRule/Host/Host.cs b/src/PSRule/Host/Host.cs index 57add66551..33bab19b6c 100644 --- a/src/PSRule/Host/Host.cs +++ b/src/PSRule/Host/Host.cs @@ -156,6 +156,9 @@ public static InitialSessionState CreateSessionState() state.Commands.Add(BuiltInCmdlets); state.Commands.Add(BuiltInAliases); + // Set thread options + state.ThreadOptions = PSThreadOptions.UseCurrentThread; + // Set execution policy SetExecutionPolicy(state: state, executionPolicy: Microsoft.PowerShell.ExecutionPolicy.RemoteSigned); diff --git a/src/PSRule/PSRule.psm1 b/src/PSRule/PSRule.psm1 index 711e0e7ed7..ddf0b095c6 100644 --- a/src/PSRule/PSRule.psm1 +++ b/src/PSRule/PSRule.psm1 @@ -77,6 +77,9 @@ function Invoke-PSRule { [Parameter(Mandatory = $False)] [String]$ObjectPath, + [Parameter(Mandatory = $False)] + [String[]]$TargetType, + [Parameter(Mandatory = $False)] [String[]]$Culture, @@ -131,6 +134,9 @@ function Invoke-PSRule { if ($PSBoundParameters.ContainsKey('ObjectPath')) { $Option.Input.ObjectPath = $ObjectPath; } + if ($PSBoundParameters.ContainsKey('TargetType')) { + $Option.Input.TargetType = $TargetType; + } if ($PSBoundParameters.ContainsKey('As')) { $Option.Output.As = $As; } @@ -206,6 +212,9 @@ function Test-PSRuleTarget { [Parameter(Mandatory = $False)] [String[]]$Module, + [Parameter(Mandatory = $False)] + [PSRule.Rules.RuleOutcome]$Outcome = [PSRule.Rules.RuleOutcome]::Processed, + [Parameter(Mandatory = $False)] [ValidateSet('None', 'Yaml', 'Json', 'Markdown', 'Detect')] [PSRule.Configuration.InputFormat]$Format, @@ -233,6 +242,9 @@ function Test-PSRuleTarget { [Parameter(Mandatory = $False)] [String]$ObjectPath, + [Parameter(Mandatory = $False)] + [String[]]$TargetType, + [Parameter(Mandatory = $False)] [String]$Culture ) @@ -283,6 +295,9 @@ function Test-PSRuleTarget { if ($PSBoundParameters.ContainsKey('ObjectPath')) { $Option.Input.ObjectPath = $ObjectPath; } + if ($PSBoundParameters.ContainsKey('TargetType')) { + $Option.Input.TargetType = $TargetType; + } if ($PSBoundParameters.ContainsKey('Culture')) { $Option.Output.Culture = $Culture; } @@ -293,6 +308,7 @@ function Test-PSRuleTarget { $builder = [PSRule.Pipeline.PipelineBuilder]::Test($sourceFiles, $Option); $builder.Name($Name); $builder.Tag($Tag); + $builder.Limit($Outcome); if ($PSBoundParameters.ContainsKey('InputPath')) { $inputPaths = GetFilePath -Path $InputPath -Verbose:$VerbosePreference; @@ -384,6 +400,9 @@ function Assert-PSRule { [Parameter(Mandatory = $False)] [String]$ObjectPath, + [Parameter(Mandatory = $False)] + [String[]]$TargetType, + [Parameter(Mandatory = $False)] [String[]]$Culture, @@ -437,6 +456,9 @@ function Assert-PSRule { if ($PSBoundParameters.ContainsKey('ObjectPath')) { $Option.Input.ObjectPath = $ObjectPath; } + if ($PSBoundParameters.ContainsKey('TargetType')) { + $Option.Input.TargetType = $TargetType; + } if ($PSBoundParameters.ContainsKey('Style')) { $Option.Output.Style = $Style; } @@ -893,6 +915,10 @@ function New-PSRuleOption { [Alias('InputObjectPath')] [String]$ObjectPath = '', + # Sets the Input.TargetType option + [Parameter(Mandatory = $False)] + [String[]]$InputTargetType, + # Sets the Logging.LimitDebug option [Parameter(Mandatory = $False)] [String[]]$LoggingLimitDebug = $Null, @@ -1059,6 +1085,10 @@ function Set-PSRuleOption { [Alias('InputObjectPath')] [String]$ObjectPath = '', + # Sets the Input.TargetType option + [Parameter(Mandatory = $False)] + [String[]]$InputTargetType, + # Sets the Logging.LimitDebug option [Parameter(Mandatory = $False)] [String[]]$LoggingLimitDebug = $Null, @@ -1620,6 +1650,10 @@ function SetOptions { [Alias('InputObjectPath')] [String]$ObjectPath = '', + # Sets the Input.TargetType option + [Parameter(Mandatory = $False)] + [String[]]$InputTargetType, + # Sets the Logging.LimitDebug option [Parameter(Mandatory = $False)] [String[]]$LoggingLimitDebug = $Null, @@ -1699,6 +1733,11 @@ function SetOptions { $Option.Input.ObjectPath = $ObjectPath; } + # Sets option Input.TargetType + if ($PSBoundParameters.ContainsKey('InputTargetType')) { + $Option.Input.TargetType = $InputTargetType; + } + # Sets option Logging.LimitDebug if ($PSBoundParameters.ContainsKey('LoggingLimitDebug')) { $Option.Logging.LimitDebug = $LoggingLimitDebug; diff --git a/src/PSRule/Pipeline/BaselineContext.cs b/src/PSRule/Pipeline/BaselineContext.cs index 01c51c6343..931abf893c 100644 --- a/src/PSRule/Pipeline/BaselineContext.cs +++ b/src/PSRule/Pipeline/BaselineContext.cs @@ -83,7 +83,7 @@ public BaselineContextScope(ScopeType type, string[] include, Hashtable tag) } } - private sealed class BindingOption : IBindingOption + private sealed class BindingOption : IBindingOption, IEquatable { public BindingOption(string[] targetName, string[] targetType, bool ignoreCase) { @@ -97,6 +97,31 @@ public BindingOption(string[] targetName, string[] targetType, bool ignoreCase) public string[] TargetName { get; } public string[] TargetType { get; } + + public override bool Equals(object obj) + { + return obj is BindingOption option && Equals(option); + } + + public bool Equals(BindingOption other) + { + return other != null && + IgnoreCase == other.IgnoreCase && + TargetName == other.TargetName && + TargetType == other.TargetType; + } + + public override int GetHashCode() + { + unchecked // Overflow is fine + { + int hash = 17; + hash = hash * 23 + (IgnoreCase ? IgnoreCase.GetHashCode() : 0); + hash = hash * 23 + (TargetName != null ? TargetName.GetHashCode() : 0); + hash = hash * 23 + (TargetType != null ? TargetType.GetHashCode() : 0); + return hash; + } + } } public void UseScope(string moduleName) diff --git a/src/PSRule/Pipeline/InvokeResult.cs b/src/PSRule/Pipeline/InvokeResult.cs index 16d6b4a102..260a5c611b 100644 --- a/src/PSRule/Pipeline/InvokeResult.cs +++ b/src/PSRule/Pipeline/InvokeResult.cs @@ -47,6 +47,11 @@ internal int Fail get { return _Fail; } } + internal RuleOutcome Outcome + { + get { return _Outcome; } + } + /// /// Get the individual records for the target object. /// @@ -105,7 +110,6 @@ private RuleOutcome GetWorstCase(RuleOutcome outcome) { return RuleOutcome.Pass; } - return outcome; } } diff --git a/src/PSRule/Pipeline/InvokeRulePipeline.cs b/src/PSRule/Pipeline/InvokeRulePipeline.cs index 567db39d4e..3a98f0442d 100644 --- a/src/PSRule/Pipeline/InvokeRulePipeline.cs +++ b/src/PSRule/Pipeline/InvokeRulePipeline.cs @@ -59,9 +59,6 @@ public override IPipelineBuilder Configure(PSRuleOption option) Option.Execution.InconclusiveWarning = option.Execution.InconclusiveWarning ?? ExecutionOption.Default.InconclusiveWarning; Option.Execution.NotProcessedWarning = option.Execution.NotProcessedWarning ?? ExecutionOption.Default.NotProcessedWarning; - Option.Input.Format = option.Input.Format ?? InputOption.Default.Format; - Option.Input.ObjectPath = option.Input.ObjectPath ?? InputOption.Default.ObjectPath; - Option.Logging.RuleFail = option.Logging.RuleFail ?? LoggingOption.Default.RuleFail; Option.Logging.RulePass = option.Logging.RulePass ?? LoggingOption.Default.RulePass; Option.Logging.LimitVerbose = option.Logging.LimitVerbose; @@ -256,6 +253,9 @@ private InvokeResult ProcessTargetObject(PSObject targetObject) try { + if (Context.ShouldFilter()) + continue; + // Check if dependency failed if (ruleBlockTarget.Skipped) { diff --git a/src/PSRule/Pipeline/PipelineBuilder.cs b/src/PSRule/Pipeline/PipelineBuilder.cs index 3bded300c2..9354ad2292 100644 --- a/src/PSRule/Pipeline/PipelineBuilder.cs +++ b/src/PSRule/Pipeline/PipelineBuilder.cs @@ -142,6 +142,8 @@ public virtual IPipelineBuilder Configure(PSRuleOption option) Option.Binding = new BindingOption(option.Binding); Option.Execution = new ExecutionOption(option.Execution); + Option.Input = new InputOption(option.Input); + Option.Input.Format = Option.Input.Format ?? InputOption.Default.Format; Option.Output = new OutputOption(option.Output); return this; } @@ -180,7 +182,7 @@ protected PipelineContext PrepareContext(BindTargetMethod bindTargetName, BindTa logger: Logger, option: Option, hostContext: HostContext, - binder: new TargetBinder(bindTargetName: bindTargetName, bindTargetType: bindTargetType), + binder: new TargetBinder(bindTargetName, bindTargetType, Option.Input.TargetType), baseline: GetBaselineContext(), unresolved: unresolved ); diff --git a/src/PSRule/Pipeline/PipelineContext.cs b/src/PSRule/Pipeline/PipelineContext.cs index 91316470ef..6622362705 100644 --- a/src/PSRule/Pipeline/PipelineContext.cs +++ b/src/PSRule/Pipeline/PipelineContext.cs @@ -21,6 +21,16 @@ namespace PSRule.Pipeline { internal sealed class PipelineContext : IDisposable, IBindingContext { + private const string SOURCE_OUTCOME_FAIL = "Rule.Outcome.Fail"; + private const string SOURCE_OUTCOME_PASS = "Rule.Outcome.Pass"; + + private const string ERRORID_INVALIDRULERESULT = "PSRule.Runtime.InvalidRuleResult"; + + private const string ErrorPreference = "ErrorActionPreference"; + private const string WarningPreference = "WarningPreference"; + private const string VerbosePreference = "VerbosePreference"; + private const string DebugPreference = "DebugPreference"; + [ThreadStatic] internal static PipelineContext CurrentThread; @@ -136,13 +146,9 @@ internal Runspace GetRunspace() if (_Runspace == null) { var state = HostState.CreateSessionState(); - - // Set PowerShell language mode state.LanguageMode = _LanguageMode == LanguageMode.FullLanguage ? PSLanguageMode.FullLanguage : PSLanguageMode.ConstrainedLanguage; _Runspace = RunspaceFactory.CreateRunspace(state); - _Runspace.ThreadOptions = PSThreadOptions.UseCurrentThread; - if (Runspace.DefaultRunspace == null) Runspace.DefaultRunspace = _Runspace; @@ -152,10 +158,10 @@ internal Runspace GetRunspace() _Runspace.SessionStateProxy.PSVariable.Set(new AssertVariable()); _Runspace.SessionStateProxy.PSVariable.Set(new TargetObjectVariable()); _Runspace.SessionStateProxy.PSVariable.Set(new ConfigurationVariable()); - _Runspace.SessionStateProxy.PSVariable.Set("ErrorActionPreference", ActionPreference.Continue); - _Runspace.SessionStateProxy.PSVariable.Set("WarningPreference", ActionPreference.Continue); - _Runspace.SessionStateProxy.PSVariable.Set("VerbosePreference", ActionPreference.Continue); - _Runspace.SessionStateProxy.PSVariable.Set("DebugPreference", ActionPreference.Continue); + _Runspace.SessionStateProxy.PSVariable.Set(ErrorPreference, ActionPreference.Continue); + _Runspace.SessionStateProxy.PSVariable.Set(WarningPreference, ActionPreference.Continue); + _Runspace.SessionStateProxy.PSVariable.Set(VerbosePreference, ActionPreference.Continue); + _Runspace.SessionStateProxy.PSVariable.Set(DebugPreference, ActionPreference.Continue); _Runspace.SessionStateProxy.Path.SetLocation(PSRuleOption.GetWorkingPath()); } return _Runspace; @@ -201,12 +207,12 @@ public void Pass() if (_PassStream == OutcomeLogStream.Error && _Logger.ShouldWriteError()) { - _Logger.WriteError(new ErrorRecord(new RuleRuntimeException(string.Format(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, _Binder.TargetName)), "Rule.Outcome.Pass", ErrorCategory.InvalidData, null)); + _Logger.WriteError(new ErrorRecord(new RuleRuntimeException(string.Format(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, _Binder.TargetName)), SOURCE_OUTCOME_PASS, ErrorCategory.InvalidData, null)); } if (_PassStream == OutcomeLogStream.Information && _Logger.ShouldWriteInformation()) { - _Logger.WriteInformation(new InformationRecord(messageData: string.Format(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, _Binder.TargetName), source: "Rule.Outcome.Pass")); + _Logger.WriteInformation(new InformationRecord(messageData: string.Format(PSRuleResources.OutcomeRulePass, RuleRecord.RuleName, _Binder.TargetName), source: SOURCE_OUTCOME_PASS)); } } @@ -222,12 +228,12 @@ public void Fail() if (_FailStream == OutcomeLogStream.Error && _Logger.ShouldWriteError()) { - _Logger.WriteError(new ErrorRecord(new RuleRuntimeException(string.Format(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, _Binder.TargetName)), "Rule.Outcome.Fail", ErrorCategory.InvalidData, null)); + _Logger.WriteError(new ErrorRecord(new RuleRuntimeException(string.Format(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, _Binder.TargetName)), SOURCE_OUTCOME_FAIL, ErrorCategory.InvalidData, null)); } if (_FailStream == OutcomeLogStream.Information && _Logger.ShouldWriteInformation()) { - _Logger.WriteInformation(new InformationRecord(messageData: string.Format(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, _Binder.TargetName), source: "Rule.Outcome.Fail")); + _Logger.WriteInformation(new InformationRecord(messageData: string.Format(PSRuleResources.OutcomeRuleFail, RuleRecord.RuleName, _Binder.TargetName), source: SOURCE_OUTCOME_FAIL)); } } @@ -262,7 +268,7 @@ public void ErrorInvaildRuleResult() _Logger.WriteError(errorRecord: new ErrorRecord( exception: new RuleRuntimeException(message: string.Format(PSRuleResources.InvalidRuleResult, RuleBlock.RuleId)), - errorId: "PSRule.Runtime.InvalidRuleResult", + errorId: ERRORID_INVALIDRULERESULT, errorCategory: ErrorCategory.InvalidResult, targetObject: null )); @@ -422,6 +428,8 @@ public void SetTargetObject(PSObject targetObject) /// public RuleRecord EnterRuleBlock(RuleBlock ruleBlock) { + _Binder.Bind(Baseline, TargetObject); + RuleBlock = ruleBlock; RuleRecord = new RuleRecord( ruleId: ruleBlock.RuleId, ruleName: ruleBlock.RuleName, @@ -432,16 +440,11 @@ public RuleRecord EnterRuleBlock(RuleBlock ruleBlock) info: ruleBlock.Info ); - RuleBlock = ruleBlock; - - _Binder.Bind(Baseline, TargetObject); - if (_Logger != null) _Logger.EnterScope(ruleBlock.RuleName); // Starts rule execution timer _RuleTimer.Restart(); - return RuleRecord; } @@ -466,6 +469,11 @@ public void ExitRuleBlock() _Reason.Clear(); } + public bool ShouldFilter() + { + return _Binder.ShouldFilter; + } + public void WriteReason(string text) { if (string.IsNullOrEmpty(text) || ExecutionScope != ExecutionScope.Condition) diff --git a/src/PSRule/Pipeline/TargetBinder.cs b/src/PSRule/Pipeline/TargetBinder.cs index 5c81e1d726..386fba0753 100644 --- a/src/PSRule/Pipeline/TargetBinder.cs +++ b/src/PSRule/Pipeline/TargetBinder.cs @@ -2,6 +2,8 @@ // Licensed under the MIT License. using PSRule.Configuration; +using System; +using System.Collections.Generic; using System.Management.Automation; namespace PSRule.Pipeline @@ -10,26 +12,40 @@ internal sealed class TargetBinder { private readonly BindTargetMethod _BindTargetName; private readonly BindTargetMethod _BindTargetType; + private readonly HashSet _TypeFilter; - public TargetBinder(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType) + internal TargetBinder(BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, string[] typeFilter) { _BindTargetName = bindTargetName; _BindTargetType = bindTargetType; + if (typeFilter != null && typeFilter.Length > 0) + _TypeFilter = new HashSet(typeFilter, StringComparer.OrdinalIgnoreCase); } + /// + /// The bound TargetName of the target object. + /// public string TargetName { get; private set; } + /// + /// The bound TargetType of the target object. + /// public string TargetType { get; private set; } + /// + /// Determines if the target object should be filtered. + /// + public bool ShouldFilter { get; private set; } + + /// + /// Bind target object based on the supplied baseline. + /// public void Bind(BaselineContext baseline, PSObject targetObject) { var binding = baseline.GetTargetBinding(); - - // Bind TargetName TargetName = _BindTargetName(binding.TargetName, !binding.IgnoreCase, targetObject); - - // Bind TargetType TargetType = _BindTargetType(binding.TargetType, !binding.IgnoreCase, targetObject); + ShouldFilter = !(_TypeFilter == null || _TypeFilter.Contains(TargetType)); } } } diff --git a/src/PSRule/Pipeline/TestPipeline.cs b/src/PSRule/Pipeline/TestPipeline.cs index f38bc4a344..ac5e03442d 100644 --- a/src/PSRule/Pipeline/TestPipeline.cs +++ b/src/PSRule/Pipeline/TestPipeline.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using PSRule.Rules; +using System.Runtime.InteropServices.ComTypes; namespace PSRule.Pipeline { @@ -12,21 +13,33 @@ internal TestPipelineBuilder(Source[] source) private sealed class BooleanWriter : PipelineWriter { - internal BooleanWriter(WriteOutput output) - : base(output) { } + private readonly RuleOutcome _Outcome; + + internal BooleanWriter(WriteOutput output, RuleOutcome outcome) + : base(output) + { + _Outcome = outcome; + } public override void Write(object o, bool enumerate) { - if (!(o is InvokeResult result)) + if (!(o is InvokeResult result) || !ShouldOutput(result.Outcome)) return; base.Write(result.IsSuccess(), false); } + + private bool ShouldOutput(RuleOutcome outcome) + { + return _Outcome == RuleOutcome.All || + (_Outcome == RuleOutcome.None && outcome == RuleOutcome.None) || + (_Outcome & outcome) > 0; + } } protected override PipelineWriter PrepareWriter() { - return new BooleanWriter(GetOutput()); + return new BooleanWriter(GetOutput(), Outcome); } } } diff --git a/tests/PSRule.Tests/FromFileBaseline.Rule.ps1 b/tests/PSRule.Tests/FromFileBaseline.Rule.ps1 index 1099fb71de..79fc9e4954 100644 --- a/tests/PSRule.Tests/FromFileBaseline.Rule.ps1 +++ b/tests/PSRule.Tests/FromFileBaseline.Rule.ps1 @@ -7,9 +7,8 @@ # Synopsis: Test for baseline Rule 'WithBaseline' { - # $Rule.TargetName -eq 'TestObject1' - # $Rule.TargetType -eq 'TestObjectType' - $True + $Rule.TargetName -eq 'TestObject1' + $Rule.TargetType -eq 'TestObjectType' } # Synopsis: Test for baseline diff --git a/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 b/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 index f876421a0d..fc1216aec6 100644 --- a/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Baseline.Tests.ps1 @@ -73,6 +73,8 @@ Describe 'Baseline' -Tag 'Baseline' { $result.Length | Should -Be 1; $result[0].RuleName | Should -Be 'WithBaseline'; $result[0].Outcome | Should -Be 'Pass'; + $result[0].TargetName | Should -Be 'TestObject1'; + $result[0].TargetType | Should -Be 'TestObjectType'; } It 'With -Module' { @@ -95,8 +97,12 @@ Describe 'Baseline' -Tag 'Baseline' { $result.Length | Should -Be 2; $result[0].RuleName | Should -Be 'M4.Rule1'; $result[0].Outcome | Should -Be 'Fail'; + $result[0].TargetName | Should -Be 'TestObject1'; + $result[0].TargetType | Should -Be 'TestObjectType'; $result[1].RuleName | Should -Be 'M4.Rule2'; $result[1].Outcome | Should -Be 'Pass'; + $result[1].TargetName | Should -Be 'TestObject1'; + $result[1].TargetType | Should -Be 'TestObjectType'; # Module + Workspace + Parameter $option = @{ diff --git a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 b/tests/PSRule.Tests/PSRule.Common.Tests.ps1 index 374e350549..6c231c1ec1 100644 --- a/tests/PSRule.Tests/PSRule.Common.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Common.Tests.ps1 @@ -362,17 +362,6 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } } - Context 'Using -ObjectPath' { - It 'Processes nested objects' { - $yaml = Get-Content -Path (Join-Path -Path $here -ChildPath 'ObjectFromNestedFile.yaml') -Raw; - $result = @(Invoke-PSRule -Path $ruleFilePath -Name 'WithFormat' -InputObject $yaml -Format Yaml -ObjectPath items); - $result | Should -Not -BeNullOrEmpty; - $result.Length | Should -Be 2; - $result | Should -BeOfType PSRule.Rules.RuleRecord; - $result.TargetName | Should -BeIn 'TestObject1', 'TestObject2'; - } - } - Context 'Using -OutputFormat' { $testObject = [PSCustomObject]@{ Name = 'TestObject1' @@ -549,6 +538,72 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' { } } + Context 'Using -ObjectPath' { + It 'Processes nested objects' { + $yaml = Get-Content -Path (Join-Path -Path $here -ChildPath 'ObjectFromNestedFile.yaml') -Raw; + $result = @(Invoke-PSRule -Path $ruleFilePath -Name 'WithFormat' -InputObject $yaml -Format Yaml -ObjectPath items); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 2; + $result | Should -BeOfType PSRule.Rules.RuleRecord; + $result.TargetName | Should -BeIn 'TestObject1', 'TestObject2'; + } + } + + Context 'Using -TargetType' { + It 'Filters target object' { + $testObject = [PSCustomObject]@{ + PSTypeName = 'TestType' + Name = 'TestObject1' + Value = 1 + } + $invokeParams = @{ + Path = $ruleFilePath + Name = 'FromFile1' + } + + # Include + $result = @(Invoke-PSRule @invokeParams -InputObject $testObject -TargetType 'TestType'); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 1; + + # Exclude + $result = @(Invoke-PSRule @invokeParams -InputObject $testObject -TargetType 'NotTestType'); + $result | Should -BeNullOrEmpty; + + $testObject = @( + [PSCustomObject]@{ + PSTypeName = 'TestType' + Name = 'TestObject1' + Value = 1 + } + [PSCustomObject]@{ + PSTypeName = 'NotTestType' + Name = 'TestObject2' + Value = 2 + } + ) + + # Multiple objects + $result = @($testObject | Invoke-PSRule @invokeParams -TargetType 'TestType' -Outcome All); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 1; + $result[0].TargetName | Should -Be 'TestObject1'; + $result[0].TargetType | Should -Be 'TestType'; + $result[0].Outcome | Should -Be 'Pass'; + + # Mutliple types + $result = @($testObject | Invoke-PSRule @invokeParams -TargetType 'TestType','NotTestType'); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 2; + $result[0].TargetName | Should -Be 'TestObject1'; + $result[0].TargetType | Should -Be 'TestType'; + $result[0].Outcome | Should -Be 'Pass'; + $result[1].TargetName | Should -Be 'TestObject2'; + $result[1].TargetType | Should -Be 'NotTestType'; + $result[1].Outcome | Should -Be 'Pass'; + } + } + Context 'With constrained language' { $testObject = [PSCustomObject]@{ Name = 'TestObject1' @@ -764,8 +819,13 @@ Describe 'Test-PSRuleTarget' -Tag 'Test-PSRuleTarget','Common' { } It 'Returns warning when not processed' { - # Check result with no rules matching precondition + # Default outcome $result = $testObject | Test-PSRuleTarget -Path $ruleFilePath -Name 'WithPreconditionFalse' -WarningVariable outWarnings -WarningAction SilentlyContinue; + $result | Should -BeNullOrEmpty; + $outWarnings | Should -Be 'Target object ''TestObject1'' has not been processed because no matching rules were found.'; + + # Outcome All + $result = $testObject | Test-PSRuleTarget -Path $ruleFilePath -Name 'WithPreconditionFalse' -WarningVariable outWarnings -WarningAction SilentlyContinue -Outcome All; $result | Should -Not -BeNullOrEmpty; $result | Should -BeOfType System.Boolean; $result | Should -Be $True; @@ -773,6 +833,55 @@ Describe 'Test-PSRuleTarget' -Tag 'Test-PSRuleTarget','Common' { } } + Context 'Using -TargetType' { + It 'Filters target object' { + $testObject = [PSCustomObject]@{ + PSTypeName = 'TestType' + Name = 'TestObject1' + Value = 1 + } + $testParams = @{ + Path = $ruleFilePath + Name = 'FromFile1' + } + + # Include + $result = @(Test-PSRuleTarget @testParams -InputObject $testObject -TargetType 'TestType'); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 1; + + # Exclude + $result = @(Test-PSRuleTarget @testParams -InputObject $testObject -TargetType 'NotTestType'); + $result | Should -BeNullOrEmpty; + + $testObject = @( + [PSCustomObject]@{ + PSTypeName = 'TestType' + Name = 'TestObject1' + Value = 1 + } + [PSCustomObject]@{ + PSTypeName = 'NotTestType' + Name = 'TestObject2' + Value = 2 + } + ) + + # Multiple objects + $result = @($testObject | Test-PSRuleTarget @testParams -TargetType 'TestType'); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 1; + $result[0] | Should -Be $True; + + # Mutliple types + $result = @($testObject | Test-PSRuleTarget @testParams -TargetType 'TestType','NotTestType'); + $result | Should -Not -BeNullOrEmpty; + $result.Length | Should -Be 2; + $result[0] | Should -Be $True; + $result[1] | Should -Be $True; + } + } + Context 'With constrained language' { $testObject = [PSCustomObject]@{ Name = 'TestObject1' diff --git a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 index 52afc50102..eb4a8674a2 100644 --- a/tests/PSRule.Tests/PSRule.Options.Tests.ps1 +++ b/tests/PSRule.Tests/PSRule.Options.Tests.ps1 @@ -360,6 +360,43 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' { } } + Context 'Read Input.TargetType' { + It 'from default' { + $option = New-PSRuleOption; + $option.Input.TargetType | Should -Be $Null; + } + + It 'from Hashtable' { + # With single item + $option = New-PSRuleOption -Option @{ 'Input.TargetType' = 'virtualMachine' }; + $option.Input.TargetType | Should -BeIn 'virtualMachine'; + + # With array + $option = New-PSRuleOption -Option @{ 'Input.TargetType' = 'virtualMachine', 'virtualNetwork' }; + $option.Input.TargetType.Length | Should -Be 2; + $option.Input.TargetType | Should -BeIn 'virtualMachine', 'virtualNetwork'; + } + + It 'from YAML' { + # With single item + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml'); + $option.Input.TargetType | Should -BeIn 'virtualMachine'; + + # With array + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests2.yml'); + $option.Input.TargetType | Should -BeIn 'virtualMachine', 'virtualNetwork'; + + # With flat single item + $option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests3.yml'); + $option.Input.TargetType | Should -BeIn 'virtualMachine'; + } + + It 'from parameter' { + $option = New-PSRuleOption -InputTargetType 'virtualMachine', 'virtualNetwork' -Path $emptyOptionsFilePath; + $option.Input.TargetType | Should -BeIn 'virtualMachine', 'virtualNetwork'; + } + } + Context 'Read Logging.LimitDebug' { It 'from default' { $option = New-PSRuleOption; diff --git a/tests/PSRule.Tests/PSRule.Tests.yml b/tests/PSRule.Tests/PSRule.Tests.yml index 7a72346a4f..75428039d8 100644 --- a/tests/PSRule.Tests/PSRule.Tests.yml +++ b/tests/PSRule.Tests/PSRule.Tests.yml @@ -33,6 +33,8 @@ execution: input: format: Yaml objectPath: items + targetType: + - virtualMachine # Configure logging options logging: diff --git a/tests/PSRule.Tests/PSRule.Tests2.yml b/tests/PSRule.Tests/PSRule.Tests2.yml index 4d12a4b835..7ad5dba61d 100644 --- a/tests/PSRule.Tests/PSRule.Tests2.yml +++ b/tests/PSRule.Tests/PSRule.Tests2.yml @@ -1,14 +1,5 @@ # These are options for unit tests -# Configure baseline -rule: - include: - - rule1 - - rule2 - exclude: - - rule3 - - rule4 - # Configure binding binding: targetName: @@ -17,3 +8,18 @@ binding: targetType: - ResourceType - kind + +# Configure input +input: + targetType: + - virtualMachine + - virtualNetwork + +# Configure baseline +rule: + include: + - rule1 + - rule2 + exclude: + - rule3 + - rule4 diff --git a/tests/PSRule.Tests/PSRule.Tests3.yml b/tests/PSRule.Tests/PSRule.Tests3.yml index 6e6fd882fe..ea2025c352 100644 --- a/tests/PSRule.Tests/PSRule.Tests3.yml +++ b/tests/PSRule.Tests/PSRule.Tests3.yml @@ -1,11 +1,15 @@ # These are options for unit tests -# Configure baseline -rule: - include: [ 'rule1' ] - exclude: [ 'rule3' ] - # Configure binding binding: targetName: [ 'ResourceName' ] targetType: [ 'ResourceType' ] + +# Configure input +input: + targetType: [ 'virtualMachine' ] + +# Configure baseline +rule: + include: [ 'rule1' ] + exclude: [ 'rule3' ] diff --git a/tests/PSRule.Tests/TargetNameBindingTests.cs b/tests/PSRule.Tests/TargetNameBindingTests.cs index 2f4e0c2a3a..0d7259c19f 100644 --- a/tests/PSRule.Tests/TargetNameBindingTests.cs +++ b/tests/PSRule.Tests/TargetNameBindingTests.cs @@ -28,7 +28,7 @@ public void UnboundObjectTargetName() var pso1 = PSObject.AsPSObject(testObject1); var pso2 = PSObject.AsPSObject(testObject2); - PipelineContext.CurrentThread = PipelineContext.New(logger: null, option: new PSRuleOption(), hostContext: null, binder: new TargetBinder(bindTargetName: null, bindTargetType: null), baseline: null, unresolved: null); + PipelineContext.CurrentThread = PipelineContext.New(logger: null, option: new PSRuleOption(), hostContext: null, binder: new TargetBinder(null, null, null), baseline: null, unresolved: null); var actual1 = PipelineHookActions.BindTargetName(null, false, pso1); var actual2 = PipelineHookActions.BindTargetName(null, false, pso2);