* Added rule ref and aliases #792 #881

* Minor fixes

Co-authored-by: ArmaanMcleod <armaan_mcleod@outlook.com>
This commit is contained in:
Bernie White 2022-01-14 17:57:03 +10:00 коммит произвёл GitHub
Родитель d9d8d32503
Коммит af6531aa20
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
65 изменённых файлов: 1877 добавлений и 747 удалений

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

@ -354,6 +354,7 @@ The following conceptual topics exist in the `PSRule` module:
- [Binding.UseQualifiedName](docs/concepts/PSRule/en-US/about_PSRule_Options.md#bindingusequalifiedname)
- [Configuration](docs/concepts/PSRule/en-US/about_PSRule_Options.md#configuration)
- [Convention.Include](docs/concepts/PSRule/en-US/about_PSRule_Options.md#conventioninclude)
- [Execution.AliasReferenceWarning](docs/concepts/PSRule/en-US/about_PSRule_Options.md#executionaliasreferencewarning)
- [Execution.LanguageMode](docs/concepts/PSRule/en-US/about_PSRule_Options.md#executionlanguagemode)
- [Execution.InconclusiveWarning](docs/concepts/PSRule/en-US/about_PSRule_Options.md#executioninconclusivewarning)
- [Execution.NotProcessedWarning](docs/concepts/PSRule/en-US/about_PSRule_Options.md#executionnotprocessedwarning)

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

@ -11,6 +11,21 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
## Unreleased
What's changed since pre-release v2.0.0-B2201054:
- General improvements:
- Added support for rule aliases. [#792](https://github.com/microsoft/PSRule/issues/792)
- Aliases allow rules to be references by an alternative name.
- When renaming rules, add a rule alias to avoid breaking references to the old rule name.
- To specify an alias use the `-Alias` parameter or `alias` metadata property in YAML or JSON.
- Added support for stable identifiers with rule refs. [#881](https://github.com/microsoft/PSRule/issues/881)
- A rule ref may be optionally be used to reference a rule.
- Rule refs should be:
stable, not changing between releases;
opaque, as opposed to being a human-readable string.
Stable and opaque refs ease web lookup and to help to avoid language difficulties.
- To specify a rule ref use the `-Ref` parameter or `ref` metadata property in YAML or JSON.
## v2.0.0-B2201054 (pre-release)
What's changed since v1.11.0:

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

@ -286,7 +286,7 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable
## OUTPUTS
### PSRule.Rules.Rule
### PSRule.Definitions.Rules.IRuleV1
## NOTES

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

@ -14,6 +14,7 @@ This topic describes what options are available, when to and how to use them.
The following workspace options are available for use:
- [Convention.Include](#conventioninclude)
- [Execution.AliasReferenceWarning](#executionaliasreferencewarning)
- [Execution.LanguageMode](#executionlanguagemode)
- [Execution.InconclusiveWarning](#executioninconclusivewarning)
- [Execution.NotProcessedWarning](#executionnotprocessedwarning)
@ -662,6 +663,58 @@ variables:
value: 'Convention1;Convention2'
```
### Execution.AliasReferenceWarning
Rules may define one or more aliases.
These aliases are alternative names to identify the rule.
An alias may be used to reference the rule anywhere a rule name is used.
The primary purpose of an alias is to provide a non-breaking method to change the rule name.
Alises can be removed at a later revision once the rule is no longer referenced by the alias.
A warning is logged by default to help identify when an alias is used.
We recommend taking action to update your usage of the alis to use the rule name or ref instead.
Alternatively, the alias reference warning can be disabled by using:
```powershell
# PowerShell: Using the AliasReferenceWarning parameter
$option = New-PSRuleOption -AliasReferenceWarning $False;
```
```powershell
# PowerShell: Using the Execution.AliasReferenceWarning hashtable key
$option = New-PSRuleOption -Option @{ 'Execution.AliasReferenceWarning' = $False };
```
```powershell
# PowerShell: Using the AliasReferenceWarning parameter to set YAML
Set-PSRuleOption -AliasReferenceWarning $False;
```
```yaml
# YAML: Using the execution/aliasReferenceWarning property
execution:
aliasReferenceWarning: false
```
```bash
# Bash: Using environment variable
export PSRULE_EXECUTION_ALIASREFERENCEWARNING=false
```
```yaml
# GitHub Actions: Using environment variable
env:
PSRULE_EXECUTION_ALIASREFERENCEWARNING: false
```
```yaml
# Azure Pipelines: Using environment variable
variables:
- name: PSRULE_EXECUTION_ALIASREFERENCEWARNING
value: false
```
### Execution.LanguageMode
Unless PowerShell has been constrained, full language features of PowerShell are available to use within rule definitions.
@ -2378,9 +2431,11 @@ convention:
# Configure execution options
execution:
aliasReferenceWarning: false
languageMode: ConstrainedLanguage
inconclusiveWarning: false
notProcessedWarning: false
suppressedRuleWarning: false
# Configure include options
include:
@ -2480,9 +2535,11 @@ convention:
# Configure execution options
execution:
aliasReferenceWarning: true
languageMode: FullLanguage
inconclusiveWarning: true
notProcessedWarning: true
suppressedRuleWarning: true
# Configure include options
include:

51
docs/deprecations.md Normal file
Просмотреть файл

@ -0,0 +1,51 @@
---
author: BernieWhite
---
# Deprecations
## Deprecations for v3
### Rule output object
Several properties of the rule object have been renamed to improve consistency with other objects.
Previously rules returned by `Get-PSRule` returned a rule object which included the following properties:
- `RuleId`
- `RuleName`
- `Description`
These have been replaced with the following properties:
- `Id` instead of `RuleId`.
- `Name` instead of `RuleName`.
- `Synopsis` instead of `Description`.
From _v3_ these properties will be removed.
These changes do not affect normal usage of PSRule.
Supporting scripts that directly use the old names may not work correctly until you update these names.
## Deprecations for v2
### Default baseline by module manifest
When packaging baselines in a module, you may want to specify a default baseline.
PSRule _v1.9.0_ added support for setting the default baseline in a module configuration.
Previously a default baseline could be set by specifying the baseline in the module manifest.
From _v1.9.0_ this is deprecated and will be removed from _v2_.
For details on how to migrate to the new default baseline option, continue reading the [upgrade notes][1].
[1]: upgrade-notes.md#setting-default-module-baseline
### Resources without an API version
When creating YAML and JSON resources you define a resource by specifying the `apiVersion` and `kind`.
To allow new schema versions for resources to be introduced in the future, an `apiVersion` was introduced.
For backwards compatibility, resources without an `apiVersion` deprecated but supported.
From _v2_ resources without an `apiVersion` will be ignored.
For details on how to add an `apiVersion` to a resource, continue reading the [upgrade notes][2].
[2]: upgrade-notes.md#setting-resource-api-version

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

@ -44,13 +44,15 @@ Conditions determine if the input object either _Pass_ or _Fail_ the rule.
Syntax:
```text
Rule [-Name] <string> [-Tag <hashtable>] [-When <string[]>] [-Type <string[]>] [-If <scriptBlock>] [-DependsOn <string[]>] [-Configure <hashtable>] [-ErrorAction <ActionPreference>] [-Body] {
Rule [-Name] <string> [-Ref <string>] [-Alias <string[]>] [-Tag <hashtable>] [-When <string[]>] [-Type <string[]>] [-If <scriptBlock>] [-DependsOn <string[]>] [-Configure <hashtable>] [-ErrorAction <ActionPreference>] [-Body] {
...
}
```
- `Name` - The name of the rule definition. Each rule name must be unique.
When packaging rules within a module, rule names must only be unique within the module.
- `Ref` - An optional stable and opaque identifier that can be used to reference the rule.
- `Alias` - A list of alternative names that can be used to reference the rule.
- `Tag` - A hashtable of key/ value metadata that can be used to filter and identify rules and rule results.
- `When` - A selector precondition that must evaluate true before the rule is executed.
- `Type` - A type precondition that must match the _TargetType_ of the pipeline object before the rule is executed.

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

@ -0,0 +1,29 @@
``` ini
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.404
[Host] : .NET Core 3.1.22 (CoreCLR 4.700.21.56803, CoreFX 4.700.21.57101), X64 RyuJIT
DefaultJob : .NET Core 3.1.22 (CoreCLR 4.700.21.56803, CoreFX 4.700.21.57101), X64 RyuJIT
```
| Method | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Allocated |
|------------------------- |-----------------:|----------------:|----------------:|-----------------:|-----------:|----------:|----------:|
| Invoke | 50,690,403.5 ns | 1,008,643.42 ns | 842,262.94 ns | 50,510,218.2 ns | 4000.0000 | 272.7273 | 17,758 KB |
| InvokeIf | 51,910,565.7 ns | 684,957.47 ns | 607,196.95 ns | 52,036,680.0 ns | 4500.0000 | 200.0000 | 20,008 KB |
| InvokeType | 50,323,385.8 ns | 419,714.32 ns | 327,685.33 ns | 50,448,415.0 ns | 4100.0000 | 400.0000 | 17,758 KB |
| InvokeSummary | 49,852,085.7 ns | 932,333.23 ns | 826,489.12 ns | 49,792,581.8 ns | 4000.0000 | 363.6364 | 17,758 KB |
| Assert | 51,774,439.2 ns | 534,962.40 ns | 446,717.83 ns | 51,741,530.0 ns | 4100.0000 | 300.0000 | 18,461 KB |
| Get | 6,153,688.9 ns | 110,055.91 ns | 102,946.36 ns | 6,122,121.1 ns | 85.9375 | - | 367 KB |
| GetHelp | 6,191,583.5 ns | 95,299.61 ns | 84,480.62 ns | 6,188,469.5 ns | 85.9375 | - | 367 KB |
| Within | 92,791,338.3 ns | 1,666,664.31 ns | 1,558,998.83 ns | 92,849,275.0 ns | 8250.0000 | 1000.0000 | 34,197 KB |
| WithinBulk | 136,104,105.9 ns | 2,647,952.81 ns | 4,275,950.21 ns | 135,769,100.0 ns | 14000.0000 | 2000.0000 | 61,224 KB |
| WithinLike | 117,091,166.7 ns | 1,955,451.58 ns | 1,733,456.89 ns | 116,485,916.7 ns | 11666.6667 | 1666.6667 | 48,352 KB |
| DefaultTargetNameBinding | 713,149.3 ns | 8,563.98 ns | 8,010.75 ns | 712,249.0 ns | 38.0859 | - | 156 KB |
| CustomTargetNameBinding | 948,598.4 ns | 17,400.93 ns | 21,369.90 ns | 938,536.5 ns | 85.9375 | - | 352 KB |
| NestedTargetNameBinding | 867,820.4 ns | 6,230.42 ns | 5,523.11 ns | 868,881.9 ns | 85.9375 | - | 352 KB |
| AssertHasFieldValue | 3,522,024.3 ns | 70,142.49 ns | 133,453.38 ns | 3,464,502.7 ns | 253.9063 | 7.8125 | 1,040 KB |
| PathTokenize | 863.4 ns | 17.13 ns | 27.66 ns | 852.0 ns | 0.2632 | - | 1 KB |
| PathExpressionBuild | 546.2 ns | 4.86 ns | 4.55 ns | 546.4 ns | 0.3500 | - | 1 KB |
| PathExpressionGet | 375,272.2 ns | 4,983.35 ns | 3,890.67 ns | 375,397.7 ns | 17.0898 | - | 70 KB |

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

@ -0,0 +1,29 @@
``` ini
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000
Intel Core i7-1065G7 CPU 1.30GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=5.0.404
[Host] : .NET Core 3.1.22 (CoreCLR 4.700.21.56803, CoreFX 4.700.21.57101), X64 RyuJIT
DefaultJob : .NET Core 3.1.22 (CoreCLR 4.700.21.56803, CoreFX 4.700.21.57101), X64 RyuJIT
```
| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Allocated |
|------------------------- |-----------------:|----------------:|----------------:|-----------:|----------:|----------:|
| Invoke | 53,201,973.3 ns | 965,400.91 ns | 903,036.61 ns | 4100.0000 | 400.0000 | 17,758 KB |
| InvokeIf | 54,266,264.0 ns | 516,824.36 ns | 483,437.83 ns | 4500.0000 | 200.0000 | 20,008 KB |
| InvokeType | 53,299,662.9 ns | 1,041,990.81 ns | 1,240,415.94 ns | 4000.0000 | 400.0000 | 17,758 KB |
| InvokeSummary | 52,364,196.7 ns | 741,213.07 ns | 693,331.17 ns | 4100.0000 | 400.0000 | 17,758 KB |
| Assert | 53,926,665.9 ns | 1,048,995.55 ns | 929,907.24 ns | 4111.1111 | 222.2222 | 18,461 KB |
| Get | 6,213,169.1 ns | 119,598.34 ns | 117,461.56 ns | 85.9375 | 7.8125 | 366 KB |
| GetHelp | 6,185,384.7 ns | 121,492.72 ns | 144,628.44 ns | 85.9375 | 7.8125 | 366 KB |
| Within | 91,044,076.7 ns | 1,250,486.23 ns | 1,169,705.59 ns | 8250.0000 | 1000.0000 | 34,198 KB |
| WithinBulk | 131,636,590.3 ns | 2,585,126.29 ns | 3,361,394.39 ns | 14666.6667 | 1666.6667 | 61,224 KB |
| WithinLike | 117,834,564.4 ns | 2,208,102.68 ns | 2,065,460.62 ns | 11666.6667 | 1666.6667 | 48,352 KB |
| DefaultTargetNameBinding | 696,752.1 ns | 9,049.16 ns | 8,021.84 ns | 38.0859 | - | 156 KB |
| CustomTargetNameBinding | 881,022.4 ns | 12,940.79 ns | 11,471.67 ns | 85.9375 | - | 352 KB |
| NestedTargetNameBinding | 876,321.2 ns | 7,650.01 ns | 6,781.54 ns | 85.9375 | - | 352 KB |
| AssertHasFieldValue | 3,076,694.5 ns | 30,768.35 ns | 25,692.97 ns | 253.9063 | 7.8125 | 1,040 KB |
| PathTokenize | 869.1 ns | 16.88 ns | 25.27 ns | 0.2632 | - | 1 KB |
| PathExpressionBuild | 529.8 ns | 8.39 ns | 7.44 ns | 0.3500 | - | 1 KB |
| PathExpressionGet | 358,678.4 ns | 4,814.17 ns | 4,020.05 ns | 17.0898 | - | 70 KB |

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

@ -59,7 +59,7 @@ nav:
- v1: 'CHANGELOG-v1.md'
- v0: 'CHANGELOG-v0.md'
- Upgrade notes: upgrade-notes.md
# - Deprecations: deprecations.md
- Deprecations: deprecations.md
- Support: support.md
# - Setup:
# - Configuring options: setup/configuring-options.md

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

@ -24,18 +24,44 @@
"type": "string",
"title": "Name",
"description": "The name of the resource. This must be unique.",
"minLength": 3
"$ref": "#/definitions/resourceName"
},
"annotations": {
"type": "object",
"title": "Annotations"
"title": "Annotations",
"description": "Additional annotations for the resource.",
"properties": {
"obsolete": {
"type": "boolean",
"title": "Obsolete",
"description": "A common annotation that flags the resource as obsolete.",
"default": false
}
},
"additionalProperties": true,
"defaultSnippets": [
{
"label": "Annotation key/ value",
"body": {
"${1:Key}": "${2:Value}"
}
}
]
},
"tags": {
"type": "object",
"title": "Tags",
"additionalProperties": {
"type": "string"
}
},
"defaultSnippets": [
{
"label": "Tag key/ value",
"body": {
"${1:Key}": "${2:Value}"
}
}
]
}
},
"required": [
@ -104,14 +130,22 @@
"title": "Include rules",
"description": "Rules to include by name in the baseline.",
"markdownDescription": "Rules to include by name in the baseline. [See help](https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html#ruleinclude)",
"$ref": "#/definitions/rule-names"
"items": {
"type": "string",
"$ref": "#/definitions/resourceName"
},
"uniqueItems": true
},
"exclude": {
"type": "array",
"title": "Exclude rules",
"description": "Rules to exclude by name from the baseline.",
"markdownDescription": "Rules to exclude by name from the baseline. [See help](https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html#ruleexclude)",
"$ref": "#/definitions/rule-names"
"items": {
"type": "string",
"$ref": "#/definitions/resourceName"
},
"uniqueItems": true
},
"tag": {
"type": "object",
@ -243,7 +277,7 @@
"title": "Baseline",
"description": "The name of a baseline to use.",
"markdownDescription": "The name of a baseline to use. [See help](https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html#rulebaseline)",
"$ref": "#/definitions/rule-names"
"$ref": "#/definitions/resourceName"
}
},
"additionalProperties": false
@ -251,15 +285,7 @@
},
"additionalProperties": false
},
"rule-names": {
"type": "array",
"items": {
"type": "string",
"$ref": "#/definitions/rule-name"
},
"uniqueItems": true
},
"rule-name": {
"resourceName": {
"type": "string",
"minLength": 3
},
@ -443,7 +469,7 @@
},
"metadata": {
"type": "object",
"$ref": "#/definitions/resource-metadata"
"$ref": "#/definitions/ruleMetadata"
},
"spec": {
"type": "object",
@ -490,6 +516,68 @@
],
"additionalProperties": false
},
"ruleMetadata": {
"type": "object",
"title": "Metadata",
"description": "Additional information to identify the resource.",
"properties": {
"name": {
"type": "string",
"title": "Name",
"description": "The name of the resource. This must be unique.",
"$ref": "#/definitions/resourceName"
},
"ref": {
"title": "Reference",
"description": "An optional stable opaque identifier of this resource for lookup. This must be unique if set.",
"$ref": "#/definitions/resourceName"
},
"annotations": {
"type": "object",
"title": "Annotations",
"description": "Additional annotations for the resource.",
"additionalProperties": true,
"defaultSnippets": [
{
"label": "Annotation key/ value",
"body": {
"${1:Key}": "${2:Value}"
}
}
]
},
"alias": {
"type": "array",
"title": "Aliases",
"description": "Alternative names this resource is known by.",
"items": {
"type": "string",
"title": "Alias",
"description": "An alternative name.",
"$ref": "#/definitions/resourceName"
},
"uniqueItems": true
},
"tags": {
"type": "object",
"title": "Tags",
"additionalProperties": {
"type": "string"
},
"defaultSnippets": [
{
"label": "Tag key/ value",
"body": {
"${1:Key}": "${2:Value}"
}
}
]
}
},
"required": [
"name"
]
},
"selectorExpression": {
"type": "object",
"oneOf": [

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

@ -142,6 +142,13 @@
"description": "Enable or disable warnings for suppressed rules. The default is `true`.",
"markdownDescription": "Enable or disable warnings for suppressed rules. The default is `true`. [See help](https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html#executionsuppressedrulewarning)",
"default": true
},
"aliasReferenceWarning": {
"type": "boolean",
"title": "Warn on resource aliases",
"description": "Enable or disable warnings when an alias to a resource is used. The default is true.",
"markdownDescription": "Enable or disable warnings when an alias to a resource is used. The default is `true`. [See help](https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html#executionaliasreferencewarning)",
"default": true
}
},
"additionalProperties": false
@ -537,11 +544,7 @@
},
"execution": {
"type": "object",
"oneOf": [
{
"$ref": "#/definitions/execution-option"
}
]
"$ref": "#/definitions/execution-option"
},
"include": {
"type": "object",
@ -549,11 +552,7 @@
},
"input": {
"type": "object",
"oneOf": [
{
"$ref": "#/definitions/input-option"
}
]
"$ref": "#/definitions/input-option"
},
"logging": {
"type": "object",
@ -621,7 +620,11 @@
"title": "Include rules",
"description": "Optionally filter to rules by name.",
"markdownDescription": "Optionally filter to rules by name. [See help](https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html#ruleinclude)",
"$ref": "#/definitions/rule-names"
"items": {
"type": "string",
"$ref": "#/definitions/resourceName"
},
"uniqueItems": true
},
"includeLocal": {
"type": "boolean",
@ -635,7 +638,11 @@
"title": "Exclude rules",
"description": "Specifies rules to exclude by name.",
"markdownDescription": "Specifies rules to exclude by name. [See help](https://microsoft.github.io/PSRule/concepts/PSRule/en-US/about_PSRule_Options.html#ruleexclude)",
"$ref": "#/definitions/rule-names"
"items": {
"type": "string",
"$ref": "#/definitions/resourceName"
},
"uniqueItems": true
},
"tag": {
"type": "object",
@ -662,15 +669,7 @@
},
"additionalProperties": false
},
"rule-names": {
"type": "array",
"items": {
"type": "string",
"$ref": "#/definitions/rule-name"
},
"uniqueItems": true
},
"rule-name": {
"resourceName": {
"type": "string",
"minLength": 3
}

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

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Management.Automation;
@ -18,6 +18,7 @@ namespace PSRule.Commands
[Parameter(Mandatory = true, Position = 0)]
[ValidateNotNullOrEmpty()]
[ValidateLength(3, 128)]
public string Name { get; set; }
/// <summary>
@ -83,7 +84,8 @@ namespace PSRule.Commands
begin: ConventionBlock(context, Begin, RunspaceScope.ConventionBegin),
process: ConventionBlock(context, Process, RunspaceScope.ConventionProcess),
end: ConventionBlock(context, End, RunspaceScope.ConventionEnd),
errorPreference: errorPreference
errorPreference: errorPreference,
flags: ResourceFlags.None
);
#pragma warning restore CA2000 // Dispose objects before losing scope, needs to be passed to pipeline
WriteObject(block);

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

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
@ -30,6 +30,7 @@ namespace PSRule.Commands
/// </summary>
[Parameter(Mandatory = true, Position = 0)]
[ValidateNotNullOrEmpty()]
[ValidateLength(3, 128)]
public string Name { get; set; }
/// <summary>
@ -75,6 +76,16 @@ namespace PSRule.Commands
[Parameter(Mandatory = false)]
public Hashtable Configure { get; set; }
/// <summary>
/// Any aliases for the rule.
/// </summary>
[Parameter(Mandatory = false)]
public string[] Alias { get; set; }
[Parameter(Mandatory = false)]
[ValidateLength(3, 128)]
public string Ref { get; set; }
protected override void ProcessRecord()
{
if (!IsSourceScope())
@ -89,28 +100,33 @@ namespace PSRule.Commands
file: source.Path,
startLineNumber: Body.Ast.Extent.StartLineNumber
);
var flags = ResourceFlags.None;
context.VerboseFoundResource(name: Name, moduleName: source.ModuleName, scriptName: MyInvocation.ScriptName);
CheckDependsOn();
var ps = GetCondition(context, errorPreference);
var helpInfo = PSRule.Host.HostHelper.GetHelpInfo(context, Name, metadata.Synopsis) ?? new RuleHelpInfo(
var info = PSRule.Host.HostHelper.GetHelpInfo(context, Name, metadata.Synopsis) ?? new RuleHelpInfo(
name: Name,
displayName: Name,
moduleName: source.ModuleName
);
var id = new ResourceId(source.ModuleName, Name, ResourceIdKind.Id);
#pragma warning disable CA2000 // Dispose objects before losing scope, needs to be passed to pipeline
var block = new RuleBlock(
source: source,
ruleName: Name,
info: helpInfo,
id: id,
@ref: ResourceHelper.GetIdNullable(source.ModuleName, Ref, ResourceIdKind.Ref),
info: info,
condition: ps,
tag: tag,
dependsOn: ResourceHelper.GetRuleIdStrings(source.ModuleName, DependsOn),
alias: ResourceHelper.GetRuleId(source.ModuleName, Alias, ResourceIdKind.Alias),
dependsOn: ResourceHelper.GetRuleId(source.ModuleName, DependsOn, ResourceIdKind.Unknown),
configuration: Configure,
extent: extent,
errorPreference: errorPreference
flags: flags
);
#pragma warning restore CA2000 // Dispose objects before losing scope, needs to be passed to pipeline
WriteObject(block);

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

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using PSRule.Definitions;
using PSRule.Definitions.Baselines;
@ -27,5 +28,15 @@ namespace PSRule
{
return string.IsNullOrEmpty(resource.Module);
}
internal static IEnumerable<ResourceId> GetIds(this IResource resource)
{
yield return resource.Id;
if (resource.Ref.HasValue)
yield return resource.Ref.Value;
for (var i = 0; resource.Alias != null && i < resource.Alias.Length; i++)
yield return resource.Alias[i];
}
}
}

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

@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using PSRule.Configuration;
using PSRule.Definitions;
using PSRule.Pipeline;
using PSRule.Resources;
using PSRule.Runtime;
namespace PSRule
{
internal static class RunspaceContextExtensions
{
private const string WARN_KEY_PROPERTY = "Property";
public static void WarnResourceObsolete(this RunspaceContext context, ResourceKind kind, string id)
{
if (context.Writer == null || !context.Writer.ShouldWriteWarning())
return;
context.Writer.WriteWarning(PSRuleResources.ResourceObsolete, Enum.GetName(typeof(ResourceKind), kind), id);
}
public static void WarnPropertyObsolete(this RunspaceContext context, string variableName, string propertyName)
{
context.DebugPropertyObsolete(variableName, propertyName);
if (context.Writer == null || !context.Writer.ShouldWriteWarning() || !context.ShouldWarnOnce(WARN_KEY_PROPERTY, variableName, propertyName))
return;
context.Writer.WriteWarning(PSRuleResources.PropertyObsolete, variableName, propertyName);
}
public static void WarnRuleNotFound(this RunspaceContext context)
{
if (context.Writer == null || !context.Writer.ShouldWriteWarning())
return;
context.Writer.WriteWarning(PSRuleResources.RuleNotFound);
}
public static void DebugPropertyObsolete(this RunspaceContext context, string variableName, string propertyName)
{
if (context.Writer == null || !context.Writer.ShouldWriteDebug())
return;
context.Writer.WriteDebug(PSRuleResources.DebugPropertyObsolete, context.RuleBlock.Name, variableName, propertyName);
}
public static void WarnAliasReference(this RunspaceContext context, ResourceKind kind, string resourceId, string targetId, string alias)
{
if (context.Writer == null || !context.Writer.ShouldWriteWarning() || !context.Pipeline.Option.Execution.AliasReferenceWarning.GetValueOrDefault(ExecutionOption.Default.AliasReferenceWarning.Value))
return;
context.Writer.WriteWarning(PSRuleResources.AliasReference, kind.ToString(), resourceId, targetId, alias);
}
public static void WarnAliasSuppression(this RunspaceContext context, string targetId, string alias)
{
if (context.Writer == null || !context.Writer.ShouldWriteWarning() || !context.Pipeline.Option.Execution.AliasReferenceWarning.GetValueOrDefault(ExecutionOption.Default.AliasReferenceWarning.Value))
return;
context.Writer.WriteWarning(PSRuleResources.AliasSuppression, targetId, alias);
}
}
}

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

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
@ -16,9 +16,11 @@ namespace PSRule.Configuration
private const bool DEFAULT_INCONCLUSIVEWARNING = true;
private const bool DEFAULT_NOTPROCESSEDWARNING = true;
private const bool DEFAULT_SUPPRESSEDRULEWARNING = true;
private const bool DEFAULT_ALIASREFERENCEWARNING = true;
internal static readonly ExecutionOption Default = new ExecutionOption
{
AliasReferenceWarning = DEFAULT_ALIASREFERENCEWARNING,
LanguageMode = DEFAULT_LANGUAGEMODE,
InconclusiveWarning = DEFAULT_INCONCLUSIVEWARNING,
NotProcessedWarning = DEFAULT_NOTPROCESSEDWARNING,
@ -27,6 +29,7 @@ namespace PSRule.Configuration
public ExecutionOption()
{
AliasReferenceWarning = null;
LanguageMode = null;
InconclusiveWarning = null;
NotProcessedWarning = null;
@ -38,6 +41,7 @@ namespace PSRule.Configuration
if (option == null)
return;
AliasReferenceWarning = option.AliasReferenceWarning;
LanguageMode = option.LanguageMode;
InconclusiveWarning = option.InconclusiveWarning;
NotProcessedWarning = option.NotProcessedWarning;
@ -52,6 +56,7 @@ namespace PSRule.Configuration
public bool Equals(ExecutionOption other)
{
return other != null &&
AliasReferenceWarning == other.AliasReferenceWarning &&
LanguageMode == other.LanguageMode &&
InconclusiveWarning == other.InconclusiveWarning &&
NotProcessedWarning == other.NotProcessedWarning &&
@ -63,6 +68,7 @@ namespace PSRule.Configuration
unchecked // Overflow is fine
{
var hash = 17;
hash = hash * 23 + (AliasReferenceWarning.HasValue ? AliasReferenceWarning.Value.GetHashCode() : 0);
hash = hash * 23 + (LanguageMode.HasValue ? LanguageMode.Value.GetHashCode() : 0);
hash = hash * 23 + (InconclusiveWarning.HasValue ? InconclusiveWarning.Value.GetHashCode() : 0);
hash = hash * 23 + (NotProcessedWarning.HasValue ? NotProcessedWarning.Value.GetHashCode() : 0);
@ -75,32 +81,54 @@ namespace PSRule.Configuration
{
var result = new ExecutionOption(o1)
{
AliasReferenceWarning = o1.AliasReferenceWarning ?? o2.AliasReferenceWarning,
LanguageMode = o1.LanguageMode ?? o2.LanguageMode,
InconclusiveWarning = o1.InconclusiveWarning ?? o2.InconclusiveWarning,
NotProcessedWarning = o1.NotProcessedWarning ?? o2.NotProcessedWarning,
SuppressedRuleWarning = o1.SuppressedRuleWarning ?? o2.SuppressedRuleWarning
SuppressedRuleWarning = o1.SuppressedRuleWarning ?? o2.SuppressedRuleWarning,
};
return result;
}
/// <summary>
/// Determines if a warning is raised when an alias to a resource is used.
/// </summary>
[DefaultValue(null)]
public bool? AliasReferenceWarning { get; set; }
/// <summary>
/// The langauge mode to execute PowerShell code with.
/// </summary>
[DefaultValue(null)]
public LanguageMode? LanguageMode { get; set; }
/// <summary>
/// Determines if a warning is raised when a rule does not return pass or fail.
/// </summary>
[DefaultValue(null)]
public bool? InconclusiveWarning { get; set; }
/// <summary>
/// Determines if a warning is raised when an object is not processed by any rule.
/// </summary>
[DefaultValue(null)]
public bool? NotProcessedWarning { get; set; }
/// <summary>
/// Determines if a warning is raised when a rule is suppressed.
/// </summary>
[DefaultValue(null)]
public bool? SuppressedRuleWarning { get; set; }
internal void Load(EnvironmentHelper env)
{
if (env.TryBool("PSRULE_EXECUTION_ALIASREFERENCEWARNING", out var bvalue))
AliasReferenceWarning = bvalue;
if (env.TryEnum("PSRULE_EXECUTION_LANGUAGEMODE", out LanguageMode languageMode))
LanguageMode = languageMode;
if (env.TryBool("PSRULE_EXECUTION_INCONCLUSIVEWARNING", out var bvalue))
if (env.TryBool("PSRULE_EXECUTION_INCONCLUSIVEWARNING", out bvalue))
InconclusiveWarning = bvalue;
if (env.TryBool("PSRULE_EXECUTION_NOTPROCESSEDWARNING", out bvalue))
@ -112,10 +140,13 @@ namespace PSRule.Configuration
internal void Load(Dictionary<string, object> index)
{
if (index.TryPopBool("Execution.AliasReferenceWarning", out var bvalue))
AliasReferenceWarning = bvalue;
if (index.TryPopEnum("Execution.LanguageMode", out LanguageMode languageMode))
LanguageMode = languageMode;
if (index.TryPopBool("Execution.InconclusiveWarning", out var bvalue))
if (index.TryPopBool("Execution.InconclusiveWarning", out bvalue))
InconclusiveWarning = bvalue;
if (index.TryPopBool("Execution.NotProcessedWarning", out bvalue))

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

@ -34,7 +34,11 @@ namespace PSRule.Definitions.Conventions
public bool Match(IResource resource)
{
return _Include != null && (_Include.Contains(resource.Name) || _Include.Contains(resource.Id) || MatchWildcard(resource.Name) || MatchWildcard(resource.Id));
return _Include != null &&
(_Include.Contains(resource.Name) ||
_Include.Contains(resource.Id.Value) ||
MatchWildcard(resource.Name) ||
MatchWildcard(resource.Id.Value));
}
private bool MatchWildcard(string name)
@ -50,13 +54,16 @@ namespace PSRule.Definitions.Conventions
{
Source = source;
Name = name;
Id = ResourceHelper.GetIdString(Source.ModuleName, name);
Id = new ResourceId(Source.ModuleName, name, ResourceIdKind.Id);
}
public SourceFile Source { get; }
public string Id { get; }
public ResourceId Id { get; }
/// <summary>
/// The name of the convetion.
/// </summary>
public string Name { get; }
public string SourcePath => Source.Path;

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

@ -18,7 +18,16 @@ namespace PSRule.Definitions.Conventions
private bool _Disposed;
internal ScriptBlockConvention(SourceFile source, ResourceMetadata metadata, ResourceHelpInfo info, LanguageScriptBlock begin, LanguageScriptBlock initialize, LanguageScriptBlock process, LanguageScriptBlock end, ActionPreference errorPreference)
internal ScriptBlockConvention(
SourceFile source,
ResourceMetadata metadata,
ResourceHelpInfo info,
LanguageScriptBlock begin,
LanguageScriptBlock initialize,
LanguageScriptBlock process,
LanguageScriptBlock end,
ActionPreference errorPreference,
ResourceFlags flags)
: base(source, metadata.Name)
{
Info = info;
@ -26,16 +35,24 @@ namespace PSRule.Definitions.Conventions
_Begin = begin;
_Process = process;
_End = end;
Flags = flags;
}
public ResourceHelpInfo Info { get; }
public ResourceFlags Flags { get; }
ResourceKind IResource.Kind => ResourceKind.Convention;
string IResource.ApiVersion => Specs.V1;
string IResource.Name => Name;
// Not supported with conventions.
ResourceId? IResource.Ref => null;
// Not supported with conventions.
ResourceId[] IResource.Alias => null;
// Not supported with conventions.
ResourceTags IResource.Tags => null;
public override void Initialize(RunspaceContext context, IEnumerable input)

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

@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
namespace PSRule.Host
namespace PSRule.Definitions
{
internal sealed class DependencyGraph<T> : IDisposable where T : IDependencyTarget
{
@ -86,7 +86,7 @@ namespace PSRule.Host
// Process each dependency
for (var d = 0; d < target.Value.DependsOn.Length; d++)
{
var dTarget = _Index[target.Value.DependsOn[d]];
var dTarget = _Index[target.Value.DependsOn[d].Value];
// Check if dependency was already completed
if (dTarget.Passed)
@ -105,12 +105,18 @@ namespace PSRule.Host
}
}
public IEnumerable<T> GetAll()
{
for (var i = 0; i < _Targets.Length; i++)
yield return _Targets[i].Value;
}
private void Prepare(T[] targets)
{
for (var i = 0; i < targets.Length; i++)
{
_Targets[i] = new DependencyTarget(this, targets[i]);
_Index.Add(targets[i].RuleId, _Targets[i]);
_Index.Add(targets[i].Id.Value, _Targets[i]);
}
}

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

@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using PSRule.Pipeline;
using PSRule.Resources;
using PSRule.Runtime;
namespace PSRule.Definitions
{
internal sealed class DependencyGraphBuilder<T> where T : IDependencyTarget
{
private readonly RunspaceContext _Context;
private readonly IEqualityComparer<ResourceId> _Comparer;
private readonly Dictionary<ResourceId, T> _Targets;
private readonly Stack<ResourceId> _Stack;
private readonly bool _IncludeDependencies;
private readonly bool _IncludeDisabled;
public DependencyGraphBuilder(RunspaceContext context, bool includeDependencies, bool includeDisabled)
{
_Context = context;
_Comparer = ResourceIdEqualityComparer.Default;
_Targets = new Dictionary<ResourceId, T>(_Comparer);
_Stack = new Stack<ResourceId>();
_IncludeDependencies = includeDependencies;
_IncludeDisabled = includeDisabled;
}
public void Include(DependencyTargetCollection<T> index, Func<T, bool> filter)
{
// Include any matching items
foreach (var item in index.GetAll())
{
if (item.Dependency)
continue;
if (filter == null || filter(item))
Include(index, item, parentId: null);
}
}
public DependencyGraph<T> Build()
{
return new DependencyGraph<T>(GetItems());
}
public T[] GetItems()
{
return _Targets.Values.ToArray();
}
private void Include(DependencyTargetCollection<T> index, T item, ResourceId? parentId)
{
// Check that the item is not already in the list of targets
if (_Targets.ContainsKey(item.Id))
return;
// Check for circular dependencies
if (_Stack.Contains(item.Id, _Comparer))
throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyCircularReference, parentId, item.Id));
try
{
_Stack.Push(item.Id);
// Check for dependencies
if (item.DependsOn != null && _IncludeDependencies)
{
foreach (var d in item.DependsOn)
{
if (!index.TryGet(d, out var dep, out var kind))
throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyNotFound, d, item.Id));
if (_Context != null && kind == ResourceIdKind.Alias)
_Context.WarnAliasReference(ResourceKind.Rule, item.Id.Value, dep.Id.Value, d.Value);
// Handle dependencies
if (!_Targets.ContainsKey(dep.Id))
Include(index, dep, parentId: item.Id);
}
}
_Targets.Add(key: item.Id, value: item);
}
finally
{
_Stack.Pop();
}
}
}
}

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

@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
namespace PSRule.Definitions
{
internal sealed class DependencyTargetCollection<T> where T : IDependencyTarget
{
private readonly Dictionary<ResourceId, TargetLink> _Index;
private readonly List<T> _Items;
public DependencyTargetCollection()
{
_Items = new List<T>();
_Index = new Dictionary<ResourceId, TargetLink>(ResourceIdEqualityComparer.Default);
}
private sealed class TargetLink
{
public readonly T Link;
public readonly ResourceIdKind Kind;
public TargetLink(T link, ResourceIdKind kind)
{
Link = link;
Kind = kind;
}
}
public bool Contains(ResourceId id)
{
return _Index.ContainsKey(id);
}
public bool TryGet(ResourceId id, out T value, out ResourceIdKind kind)
{
value = default;
kind = default;
if (!_Index.TryGetValue(id, out var link))
return false;
value = link.Link;
kind = link.Kind;
return true;
}
public bool TryAdd(T target)
{
if (_Index.ContainsKey(target.Id) || (target.Ref.HasValue && _Index.ContainsKey(target.Ref.Value)))
return false;
for (var i = 0; target.Alias != null && i < target.Alias.Length; i++)
if (_Index.ContainsKey(target.Alias[i]))
return false;
// Add Id, Ref, and aliases to the index.
_Index.Add(target.Id, new TargetLink(target, ResourceIdKind.Id));
if (target.Ref.HasValue)
_Index.Add(target.Ref.Value, new TargetLink(target, ResourceIdKind.Ref));
for (var i = 0; target.Alias != null && i < target.Alias.Length; i++)
_Index.Add(target.Alias[i], new TargetLink(target, ResourceIdKind.Alias));
_Items.Add(target);
return true;
}
public IEnumerable<T> GetAll()
{
return _Items;
}
}
}

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

@ -1,7 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Management.Automation;
namespace PSRule.Definitions
{
@ -17,5 +18,7 @@ namespace PSRule.Definitions
public interface ICondition : IDisposable
{
IConditionResult If();
ActionPreference ErrorAction { get; }
}
}

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

@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System.Collections;
using PSRule.Host;
using PSRule.Runtime;
namespace PSRule.Definitions

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

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions
{
public interface IDependencyTarget
{
ResourceId Id { get; }
ResourceId? Ref { get; }
ResourceId[] Alias { get; }
/// <summary>
/// Resources this target depends on.
/// </summary>
ResourceId[] DependsOn { get; }
/// <summary>
/// Determines if the source was imported as a dependency.
/// </summary>
bool Dependency { get; }
}
}

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

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Definitions
{
public interface ILanguageBlock
{
ResourceId Id { get; }
string SourcePath { get; }
string Module { get; }
}
}

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

@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using PSRule.Host;
using PSRule.Pipeline;
using PSRule.Runtime;
using YamlDotNet.Core;
@ -48,15 +47,50 @@ namespace PSRule.Definitions
Convention = 5
}
internal interface IResource : ILanguageBlock
[Flags]
public enum ResourceFlags
{
None = 0,
Obsolete = 1
}
public interface IResource : ILanguageBlock
{
/// <summary>
/// The type of resource.
/// </summary>
ResourceKind Kind { get; }
/// <summary>
/// The API version of the resource.
/// </summary>
string ApiVersion { get; }
/// <summary>
/// The name of the resource.
/// </summary>
string Name { get; }
/// <summary>
/// An optional reference identifer for the resource.
/// </summary>
ResourceId? Ref { get; }
/// <summary>
/// Any additional aliases for the resource.
/// </summary>
ResourceId[] Alias { get; }
/// <summary>
/// Any resource tags.
/// </summary>
ResourceTags Tags { get; }
/// <summary>
/// Flags for the resource.
/// </summary>
ResourceFlags Flags { get; }
}
internal abstract class ResourceRef
@ -253,11 +287,24 @@ namespace PSRule.Definitions
Tags = new ResourceTags();
}
/// <summary>
/// The name of the resource.
/// </summary>
public string Name { get; set; }
public string Ref { get; set; }
public string[] Alias { get; set; }
/// <summary>
/// Any resource annotations.
/// </summary>
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public ResourceAnnotations Annotations { get; set; }
/// <summary>
/// Any resource tags.
/// </summary>
[YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitEmptyCollections)]
public ResourceTags Tags { get; set; }
}
@ -289,14 +336,17 @@ namespace PSRule.Definitions
Info = info;
Source = source;
Spec = spec;
Id = ResourceHelper.GetIdString(source.ModuleName, metadata.Name);
Metadata = metadata;
Name = metadata.Name;
Id = new ResourceId(source.ModuleName, Name, ResourceIdKind.Id);
}
[YamlIgnore()]
public readonly string Id;
public ResourceId Id { get; }
/// <summary>
/// The name of the resource.
/// </summary>
[YamlIgnore()]
public string Name { get; }
@ -309,10 +359,16 @@ namespace PSRule.Definitions
[YamlIgnore()]
public readonly ResourceHelpInfo Info;
/// <summary>
/// Resource metadata details.
/// </summary>
public ResourceMetadata Metadata { get; }
public ResourceKind Kind { get; }
/// <summary>
/// The API version of the resource.
/// </summary>
public string ApiVersion { get; }
public TSpec Spec { get; }
@ -327,12 +383,14 @@ namespace PSRule.Definitions
{
_Annotations = new Dictionary<Type, ResourceAnnotation>();
Obsolete = ResourceHelper.IsObsolete(metadata);
Flags |= ResourceHelper.IsObsolete(metadata) ? ResourceFlags.Obsolete : ResourceFlags.None;
}
[YamlIgnore()]
internal readonly bool Obsolete;
string ILanguageBlock.Id => Id;
[YamlIgnore()]
internal ResourceFlags Flags { get; }
string ILanguageBlock.SourcePath => Source.Path;
@ -344,8 +402,16 @@ namespace PSRule.Definitions
string IResource.Name => Name;
// Not supported with base resources.
ResourceId? IResource.Ref => null;
// Not supported with base resources.
ResourceId[] IResource.Alias => null;
ResourceTags IResource.Tags => Metadata.Tags;
ResourceFlags IResource.Flags => Flags;
TAnnotation IAnnotated<ResourceAnnotation>.GetAnnotation<TAnnotation>()
{
return _Annotations.TryGetValue(typeof(TAnnotation), out var annotation) ? (TAnnotation)annotation : null;
@ -361,14 +427,17 @@ namespace PSRule.Definitions
{
private const string ANNOTATION_OBSOLETE = "obsolete";
private const char ID_SEPARATOR = '\\';
private const char SCOPE_SEPARATOR = '\\';
internal static string GetIdString(string scope, string name)
{
if (name.IndexOf(ID_SEPARATOR) >= 0)
return name;
return string.Concat(string.IsNullOrEmpty(scope) ? LanguageScope.STANDALONE_SCOPENAME : scope, ID_SEPARATOR, name);
return name.IndexOf(SCOPE_SEPARATOR) >= 0
? name
: string.Concat(
string.IsNullOrEmpty(scope) ? LanguageScope.STANDALONE_SCOPENAME : scope,
SCOPE_SEPARATOR,
name
);
}
internal static void ParseIdString(string defaultScope, string id, out string scope, out string name)
@ -388,9 +457,9 @@ namespace PSRule.Definitions
if (string.IsNullOrEmpty(id))
return;
var index = id.IndexOf(ID_SEPARATOR);
scope = index >= 0 ? id.Substring(0, index) : null;
name = id.Substring(index + 1);
var scopeSeparator = id.IndexOf(SCOPE_SEPARATOR);
scope = scopeSeparator >= 0 ? id.Substring(0, scopeSeparator) : null;
name = id.Substring(scopeSeparator + 1);
}
/// <summary>
@ -398,29 +467,21 @@ namespace PSRule.Definitions
/// </summary>
/// <param name="name">An array of names. Qualified names (RuleIds) supplied are left intact.</param>
/// <returns>An array of RuleIds.</returns>
internal static string[] GetRuleIdStrings(string scope, string[] name)
internal static ResourceId[] GetRuleId(string defaultScope, string[] name, ResourceIdKind kind)
{
if (name == null)
return null;
var result = new string[name.Length];
name.CopyTo(result, 0);
var result = new ResourceId[name.Length];
for (var i = 0; i < name.Length; i++)
{
if (name[i] == null)
continue;
result[i] = name[i].IndexOf(SCOPE_SEPARATOR) > 0 ? ResourceId.Parse(name[i], kind) : new ResourceId(defaultScope, name[i], kind);
// The name is not already qualified
if (name[i].IndexOf(ID_SEPARATOR) == -1)
result[i] = GetRuleIdString(scope, name[i]);
}
return (result.Length == 0) ? null : result;
}
internal static string GetRuleIdString(string scope, string name)
internal static ResourceId? GetIdNullable(string scope, string name, ResourceIdKind kind)
{
return (scope == null) ? name : string.Concat(scope, ID_SEPARATOR, name);
return string.IsNullOrEmpty(name) ? null : (ResourceId?)new ResourceId(scope, name, kind);
}
internal static bool IsObsolete(ResourceMetadata metadata)
@ -431,37 +492,4 @@ namespace PSRule.Definitions
&& obsolete.GetValueOrDefault(false);
}
}
internal sealed class ResourceIdEqualityComparer : EqualityComparer<string>
{
public override bool Equals(string x, string y)
{
return IdEquals(x, y);
}
public override int GetHashCode(string obj)
{
ResourceHelper.ParseIdString(obj, out var scope, out var name);
unchecked // Overflow is fine
{
var hash = 17;
hash = hash * 23 + (scope != null ? scope.GetHashCode() : 0);
hash = hash * 23 + (name != null ? name.GetHashCode() : 0);
return hash;
}
}
public static bool IdEquals(string x, string y)
{
ResourceHelper.ParseIdString(x, out var scope_x, out var name_x);
ResourceHelper.ParseIdString(y, out var scope_y, out var name_y);
return EqualOrNull(scope_x, scope_y) &&
EqualOrNull(name_x, name_y);
}
private static bool EqualOrNull(string x, string y)
{
return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y);
}
}
}

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

@ -0,0 +1,222 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using PSRule.Runtime;
namespace PSRule.Definitions
{
/// <summary>
/// Additional information about the type of identifier if available.
/// </summary>
internal enum ResourceIdKind
{
None = 0,
Unknown = 1,
Id = 2,
Ref = 3,
Alias = 4,
}
/// <summary>
/// A unique identifier for a resource.
/// </summary>
[DebuggerDisplay("{Value}")]
public struct ResourceId
{
private const char SCOPE_SEPARATOR = '\\';
private readonly int _HashCode;
private ResourceId(string id, string scope, string name, ResourceIdKind kind)
{
Value = id;
Scope = scope;
Name = name;
Kind = kind;
_HashCode = GetHashCode(id);
}
internal ResourceId(string scope, string name, ResourceIdKind kind)
: this(GetIdString(scope, name), NormalScope(scope), name, kind) { }
public string Value { get; }
public string Scope { get; }
public string Name { get; }
internal ResourceIdKind Kind { get; }
public override string ToString()
{
return Value;
}
[DebuggerStepThrough]
public override int GetHashCode()
{
return _HashCode;
}
public override bool Equals(object obj)
{
return obj is ResourceId id && Equals(id);
}
public bool Equals(ResourceId id)
{
return _HashCode == id._HashCode &&
EqualOrNull(Scope, id.Scope) &&
EqualOrNull(Name, id.Name);
}
public bool Equals(string id)
{
return TryParse(id, out var scope, out var name) &&
EqualOrNull(Scope, scope) &&
EqualOrNull(Name, name);
}
public static bool operator ==(ResourceId left, ResourceId right)
{
return left.Equals(right);
}
public static bool operator !=(ResourceId left, ResourceId right)
{
return !left.Equals(right);
}
private static bool EqualOrNull(string x, string y)
{
return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y);
}
private static int GetHashCode(string id)
{
return id.ToLowerInvariant().GetHashCode();
}
private static string GetIdString(string scope, string name)
{
return string.Concat(
NormalScope(scope),
SCOPE_SEPARATOR,
name
);
}
internal static ResourceId Parse(string id, ResourceIdKind kind = ResourceIdKind.Unknown)
{
return TryParse(id, kind, out var value) ? value.Value : default;
}
private static bool TryParse(string id, ResourceIdKind kind, out ResourceId? value)
{
value = null;
if (string.IsNullOrEmpty(id) || !TryParse(id, out var scope, out var name))
return false;
value = new ResourceId(id, scope, name, kind);
return true;
}
private static bool TryParse(string id, out string scope, out string name)
{
scope = null;
name = null;
if (string.IsNullOrEmpty(id))
return false;
var scopeSeparatorIndex = id.IndexOf(SCOPE_SEPARATOR);
scope = scopeSeparatorIndex >= 0 ? id.Substring(0, scopeSeparatorIndex) : null;
name = id.Substring(scopeSeparatorIndex + 1);
return true;
}
private static string NormalScope(string scope)
{
return string.IsNullOrEmpty(scope) ? LanguageScope.STANDALONE_SCOPENAME : scope;
}
}
/// <summary>
/// Compares to resource identifiers to determine if they are equal.
/// </summary>
internal sealed class ResourceIdEqualityComparer : IEqualityComparer<ResourceId>, IEqualityComparer<string>
{
public static ResourceIdEqualityComparer Default = new ResourceIdEqualityComparer();
public static bool IdEquals(ResourceId x, ResourceId y)
{
return EqualOrNull(x.Scope, y.Scope) &&
EqualOrNull(x.Name, y.Name);
}
public static bool IdEquals(ResourceId x, string y)
{
return x.Equals(y);
}
public static bool IdEquals(string x, string y)
{
ResourceHelper.ParseIdString(x, out var scope_x, out var name_x);
ResourceHelper.ParseIdString(y, out var scope_y, out var name_y);
return EqualOrNull(scope_x, scope_y) &&
EqualOrNull(name_x, name_y);
}
#region IEqualityComparer<ResourceId>
public bool Equals(ResourceId x, ResourceId y)
{
return IdEquals(x, y);
}
public int GetHashCode(ResourceId obj)
{
unchecked // Overflow is fine
{
var hash = 17;
hash = hash * 23 + (obj.Scope != null ? obj.Scope.GetHashCode() : 0);
hash = hash * 23 + (obj.Name != null ? obj.Name.GetHashCode() : 0);
return hash;
}
}
#endregion IEqualityComparer<ResourceId>
#region IEqualityComparer<string>
public bool Equals(string x, string y)
{
return IdEquals(x, y);
}
public int GetHashCode(string obj)
{
ResourceHelper.ParseIdString(obj, out var scope, out var name);
unchecked // Overflow is fine
{
var hash = 17;
hash = hash * 23 + (scope != null ? scope.GetHashCode() : 0);
hash = hash * 23 + (name != null ? name.GetHashCode() : 0);
return hash;
}
}
#endregion IEqualityComparer<string>
private static bool EqualOrNull(string x, string y)
{
return x == null || y == null || StringComparer.OrdinalIgnoreCase.Equals(x, y);
}
}
}

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

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
namespace PSRule.Definitions
{
internal sealed class ResourceIndex
{
private readonly IndexEntry[] _Items;
public ResourceIndex(IEnumerable<IResource> items)
{
_Items = Load(items);
}
private sealed class IndexEntry
{
public readonly ResourceId Id;
public readonly ResourceId Target;
public IndexEntry(ResourceId id, ResourceId target)
{
Id = id;
Target = target;
}
}
public bool TryFind(string id, out ResourceId value, out ResourceIdKind kind)
{
value = default;
kind = default;
for (var i = 0; i < _Items.Length; i++)
{
if (_Items[i].Id.Equals(id))
{
value = _Items[i].Target;
kind = _Items[i].Id.Kind;
return true;
}
}
return false;
}
private static IndexEntry[] Load(IEnumerable<IResource> items)
{
var results = new List<IndexEntry>();
foreach (var item in items)
{
var ids = item.GetIds();
foreach (var id in ids)
results.Add(new IndexEntry(id, item.Id));
}
return results.ToArray();
}
}
}

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

@ -1,16 +1,27 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Newtonsoft.Json;
using PSRule.Definitions.Expressions;
using PSRule.Host;
using PSRule.Pipeline;
using YamlDotNet.Serialization;
namespace PSRule.Definitions.Rules
{
public interface IRule
public interface IRuleV1 : IResource, IDependencyTarget
{
[Obsolete("Use Name instead.")]
string RuleName { get; }
string Synopsis { get; }
[Obsolete("Use Synopsis instead.")]
string Description { get; }
ResourceTags Tag { get; }
SourceFile Source { get; }
}
internal interface IRuleSpec
@ -23,10 +34,22 @@ namespace PSRule.Definitions.Rules
}
[Spec(Specs.V1, Specs.Rule)]
internal sealed class RuleV1 : InternalResource<RuleV1Spec>, IResource
internal sealed class RuleV1 : InternalResource<RuleV1Spec>, IResource, IRuleV1
{
public RuleV1(string apiVersion, SourceFile source, ResourceMetadata metadata, ResourceHelpInfo info, RuleV1Spec spec)
: base(ResourceKind.Rule, apiVersion, source, metadata, info, spec) { }
: base(ResourceKind.Rule, apiVersion, source, metadata, info, spec)
{
Ref = ResourceHelper.GetIdNullable(source.ModuleName, metadata.Ref, ResourceIdKind.Ref);
Alias = ResourceHelper.GetRuleId(source.ModuleName, metadata.Alias, ResourceIdKind.Alias);
}
[JsonIgnore]
[YamlIgnore]
public ResourceId? Ref { get; }
[JsonIgnore]
[YamlIgnore]
public ResourceId[] Alias { get; }
/// <summary>
/// A human readable block of text, used to identify the purpose of the rule.
@ -35,7 +58,26 @@ namespace PSRule.Definitions.Rules
[YamlIgnore]
public string Synopsis => Info.Synopsis;
string ILanguageBlock.Id => Name;
ResourceId? IDependencyTarget.Ref => Ref;
ResourceId[] IDependencyTarget.Alias => Alias;
// Not supported with resource rules.
ResourceId[] IDependencyTarget.DependsOn => Array.Empty<ResourceId>();
bool IDependencyTarget.Dependency => Source.IsDependency();
ResourceId? IResource.Ref => Ref;
ResourceId[] IResource.Alias => Alias;
string IRuleV1.RuleName => Name;
ResourceTags IRuleV1.Tag => Metadata.Tags;
SourceFile IRuleV1.Source => Source;
string IRuleV1.Description => Info.Synopsis;
}
internal sealed class RuleV1Spec : Spec, IRuleSpec

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

@ -3,12 +3,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management.Automation;
using PSRule.Configuration;
using PSRule.Definitions;
using PSRule.Resources;
namespace PSRule.Rules
namespace PSRule.Definitions.Rules
{
/// <summary>
/// A filter to include or exclude rules from being processed by id or tag.
@ -48,7 +48,8 @@ namespace PSRule.Rules
internal bool Match(string name, ResourceTags tag)
{
return !IsExcluded(name) && IsIncluded(name, tag);
return !IsExcluded(new ResourceId[] { ResourceId.Parse(name) }) &&
IsIncluded(new ResourceId[] { ResourceId.Parse(name) }, tag);
}
/// <summary>
@ -57,42 +58,56 @@ namespace PSRule.Rules
/// <returns>Return true if rule is matched, otherwise false.</returns>
public bool Match(IResource resource)
{
return !IsExcluded(resource.Name) &&
(_IncludeLocal && resource.IsLocalScope() ||
IsIncluded(resource.Name, resource.Tags));
var ids = resource.GetIds();
return !IsExcluded(ids) && (_IncludeLocal && resource.IsLocalScope() || IsIncluded(ids, resource.Tags));
}
private bool IsExcluded(string name)
private bool IsExcluded(IEnumerable<ResourceId> ids)
{
return _Excluded != null && Contains(name, _Excluded);
}
if (_Excluded == null)
return false;
private bool IsIncluded(string name, ResourceTags tag)
{
if (_Include == null || Contains(name, _Include) || MatchWildcard(name))
foreach (var id in ids)
{
if (_Tag == null)
if (Contains(id, _Excluded))
return true;
if (tag == null || _Tag.Count > tag.Count)
return false;
foreach (DictionaryEntry entry in _Tag)
{
if (!tag.Contains(entry.Key, entry.Value))
return false;
}
return true;
}
return false;
}
private bool Contains(string name, string[] set)
private bool IsIncluded(IEnumerable<ResourceId> ids, ResourceTags tag)
{
foreach (var id in ids)
{
if (_Include == null || Contains(id, _Include) || MatchWildcard(id.Name))
return TagEquals(tag);
}
return false;
}
private bool TagEquals(ResourceTags tag)
{
if (_Tag == null)
return true;
if (tag == null || _Tag.Count > tag.Count)
return false;
foreach (DictionaryEntry entry in _Tag)
{
if (!tag.Contains(entry.Key, entry.Value))
return false;
}
return true;
}
private static bool Contains(ResourceId id, string[] set)
{
for (var i = 0; set != null && i < set.Length; i++)
if (ResourceIdEqualityComparer.IdEquals(name, set[i]))
{
if (ResourceIdEqualityComparer.IdEquals(id, set[i]))
return true;
}
return false;
}

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

@ -1,8 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Diagnostics;
using System.Management.Automation;
using PSRule.Definitions.Expressions;
using PSRule.Resources;
using PSRule.Runtime;
@ -16,6 +17,7 @@ namespace PSRule.Definitions.Rules
public RuleVisitor(string module, string id, IRuleSpec spec)
{
ErrorAction = ActionPreference.Stop;
Module = module;
Id = id;
InstanceId = Guid.NewGuid();
@ -32,6 +34,8 @@ namespace PSRule.Definitions.Rules
public string Id { get; }
public ActionPreference ErrorAction { get; }
public void Dispose()
{
// Do nothing

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

@ -1,98 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using PSRule.Pipeline;
using PSRule.Resources;
namespace PSRule.Host
{
internal sealed class DependencyGraphBuilder<T> where T : IDependencyTarget
{
private readonly StringComparer _Comparer;
private readonly Dictionary<string, T> _Targets;
private readonly Stack<string> _Stack;
private readonly bool _IncludeDependencies;
public DependencyGraphBuilder(bool includeDependencies)
{
_Comparer = StringComparer.OrdinalIgnoreCase;
_Targets = new Dictionary<string, T>(_Comparer);
_Stack = new Stack<string>();
_IncludeDependencies = includeDependencies;
}
public void Include(IEnumerable<T> items, Func<T, bool> filter)
{
var index = new Dictionary<string, T>(_Comparer);
// Load items into index
foreach (var item in items)
{
if (index.ContainsKey(item.RuleId))
throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DuplicateRuleId, item.RuleId));
index.Add(item.RuleId, item);
}
// Include any matching items
foreach (var item in index.Values)
{
if (item.Dependency)
continue;
if (filter == null || filter(item))
Include(ruleId: item.RuleId, parentId: null, index: index);
}
}
public DependencyGraph<T> Build()
{
return new DependencyGraph<T>(_Targets.Values.ToArray());
}
public T[] GetItems()
{
return _Targets.Values.ToArray();
}
private void Include(string ruleId, string parentId, IDictionary<string, T> index)
{
// Check that the item is not already in the list of targets
if (_Targets.ContainsKey(ruleId))
return;
// Check for circular dependencies
if (_Stack.Contains(value: ruleId, comparer: _Comparer))
throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyCircularReference, parentId, ruleId));
try
{
_Stack.Push(item: ruleId);
var item = index[ruleId];
// Check for dependencies
if (item.DependsOn != null && _IncludeDependencies)
{
foreach (var d in item.DependsOn)
{
if (!index.ContainsKey(d))
throw new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DependencyNotFound, d, ruleId));
// Handle dependencies
if (!_Targets.ContainsKey(d))
Include(ruleId: d, parentId: ruleId, index: index);
}
}
_Targets.Add(key: ruleId, value: item);
}
finally
{
_Stack.Pop();
}
}
}
}

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

@ -7,7 +7,7 @@ using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Threading;
using Newtonsoft.Json;
using PSRule.Annotations;
using PSRule.Definitions;
@ -18,6 +18,7 @@ using PSRule.Definitions.Rules;
using PSRule.Definitions.Selectors;
using PSRule.Parser;
using PSRule.Pipeline;
using PSRule.Resources;
using PSRule.Rules;
using PSRule.Runtime;
using YamlDotNet.Core;
@ -33,17 +34,17 @@ namespace PSRule.Host
{
private const string Markdown_Extension = ".md";
internal static Rule[] GetRule(Source[] source, RunspaceContext context, bool includeDependencies)
internal static IRuleV1[] GetRule(Source[] source, RunspaceContext context, bool includeDependencies)
{
var rules = ToRuleV1(GetLanguageBlock(context, source), context);
var builder = new DependencyGraphBuilder<Rule>(includeDependencies);
var builder = new DependencyGraphBuilder<IRuleV1>(context, includeDependencies, includeDisabled: true);
builder.Include(rules, filter: (b) => Match(context, b));
return builder.GetItems();
}
internal static RuleHelpInfo[] GetRuleHelp(Source[] source, RunspaceContext context)
{
return ToRuleHelp(ToRuleBlockV1(GetLanguageBlock(context, source), context), context);
return ToRuleHelp(ToRuleBlockV1(GetLanguageBlock(context, source), context).GetAll(), context);
}
internal static DependencyGraph<RuleBlock> GetRuleBlockGraph(Source[] source, RunspaceContext context)
@ -51,22 +52,14 @@ namespace PSRule.Host
var blocks = GetLanguageBlock(context, source);
var rules = ToRuleBlockV1(blocks, context);
Import(GetConventions(blocks, context), context);
var builder = new DependencyGraphBuilder<RuleBlock>(includeDependencies: true);
var builder = new DependencyGraphBuilder<RuleBlock>(context, includeDependencies: true, includeDisabled: false);
builder.Include(rules, filter: (b) => Match(context, b));
return builder.Build();
}
/// <summary>
/// Read YAML objects and return rules.
/// </summary>
internal static IEnumerable<Rule> GetRuleYaml(Source[] source, RunspaceContext context)
{
return ToRuleV1(ReadYamlObjects(source, context), context);
}
internal static IEnumerable<RuleBlock> GetRuleYamlBlocks(Source[] source, RunspaceContext context)
{
return ToRuleBlockV1(ReadYamlObjects(source, context), context);
return ToRuleBlockV1(GetYamlLanguageBlocks(source, context), context).GetAll();
}
/// <summary>
@ -75,10 +68,8 @@ namespace PSRule.Host
internal static IEnumerable<Baseline> GetBaseline(Source[] source, RunspaceContext context)
{
var results = new List<ILanguageBlock>();
results.AddRange(ReadYamlObjects(source, context));
results.AddRange(ReadJsonObjects(source, context));
results.AddRange(GetYamlLanguageBlocks(source, context));
results.AddRange(GetJsonLanguageBlocks(source, context));
return ToBaselineV1(results, context);
}
@ -87,7 +78,7 @@ namespace PSRule.Host
/// </summary>
internal static IEnumerable<ModuleConfigV1> GetModuleConfigYaml(Source[] source, RunspaceContext context)
{
return ToModuleConfigV1(ReadYamlObjects(source, context), context);
return ToModuleConfigV1(GetYamlLanguageBlocks(source, context), context);
}
/// <summary>
@ -95,7 +86,7 @@ namespace PSRule.Host
/// </summary>
internal static IEnumerable<SelectorV1> GetSelectorYaml(Source[] source, RunspaceContext context)
{
return ToSelectorV1(ReadYamlObjects(source, context), context);
return ToSelectorV1(GetYamlLanguageBlocks(source, context), context);
}
internal static void ImportResource(Source[] source, RunspaceContext context)
@ -104,10 +95,8 @@ namespace PSRule.Host
return;
var results = new List<ILanguageBlock>();
results.AddRange(ReadYamlObjects(source, context));
results.AddRange(ReadJsonObjects(source, context));
results.AddRange(GetYamlLanguageBlocks(source, context));
results.AddRange(GetJsonLanguageBlocks(source, context));
Import(results.ToArray(), context);
}
@ -150,20 +139,18 @@ namespace PSRule.Host
private static ILanguageBlock[] GetLanguageBlock(RunspaceContext context, Source[] sources)
{
var results = new List<ILanguageBlock>();
results.AddRange(GetPowerShellRule(context, sources));
results.AddRange(ReadYamlObjects(sources, context));
results.AddRange(ReadJsonObjects(sources, context));
results.AddRange(GetPSLanguageBlocks(context, sources));
results.AddRange(GetYamlLanguageBlocks(sources, context));
results.AddRange(GetJsonLanguageBlocks(sources, context));
return results.ToArray();
}
/// <summary>
/// Execute one or more PowerShell script files to get language blocks.
/// Execute PowerShell script files to get language blocks.
/// </summary>
/// <param name="sources"></param>
/// <returns></returns>
private static ILanguageBlock[] GetPowerShellRule(RunspaceContext context, Source[] sources)
private static ILanguageBlock[] GetPSLanguageBlocks(RunspaceContext context, Source[] sources)
{
var results = new Collection<ILanguageBlock>();
var results = new List<ILanguageBlock>();
var ps = context.GetPowerShell();
try
@ -229,7 +216,10 @@ namespace PSRule.Host
return results.ToArray();
}
private static ILanguageBlock[] ReadYamlObjects(Source[] sources, RunspaceContext context)
/// <summary>
/// Get language blocks from YAML source files.
/// </summary>
private static ILanguageBlock[] GetYamlLanguageBlocks(Source[] sources, RunspaceContext context)
{
var result = new Collection<ILanguageBlock>();
var d = new DeserializerBuilder()
@ -256,12 +246,13 @@ namespace PSRule.Host
context.VerboseRuleDiscovery(path: file.Path);
context.EnterSourceScope(source: file);
using var reader = new StreamReader(file.Path);
var parser = new YamlDotNet.Core.Parser(reader);
parser.TryConsume<StreamStart>(out _);
while (parser.Current is DocumentStart)
{
var item = d.Deserialize<ResourceObject>(parser: parser);
var item = d.Deserialize<ResourceObject>(parser);
if (item == null || item.Block == null)
continue;
@ -279,10 +270,12 @@ namespace PSRule.Host
return result.Count == 0 ? Array.Empty<ILanguageBlock>() : result.ToArray();
}
private static ILanguageBlock[] ReadJsonObjects(Source[] sources, RunspaceContext context)
/// <summary>
/// Get language blocks from JSON source files.
/// </summary>
private static ILanguageBlock[] GetJsonLanguageBlocks(Source[] sources, RunspaceContext context)
{
var result = new Collection<ILanguageBlock>();
var deserializer = new JsonSerializer();
deserializer.Converters.Add(new ResourceObjectJsonConverter());
@ -307,26 +300,19 @@ namespace PSRule.Host
// Consume lines until start of array is found
while (reader.TokenType != JsonToken.StartArray)
{
reader.Read();
}
if (reader.TokenType == JsonToken.StartArray && reader.Read())
{
while (reader.TokenType != JsonToken.EndArray)
{
var value = deserializer.Deserialize<ResourceObject>(reader);
if (value?.Block != null)
{
result.Add(value.Block);
}
// Consume all end objects at the end of each resource
while (reader.TokenType == JsonToken.EndObject)
{
reader.Read();
}
}
}
}
@ -339,7 +325,6 @@ namespace PSRule.Host
context.PopScope(RunspaceScope.Resource);
context.ExitSourceScope();
}
return result.Count == 0 ? Array.Empty<ILanguageBlock>() : result.ToArray();
}
@ -385,95 +370,104 @@ namespace PSRule.Host
}
}
private static RuleException ThrowDuplicateRuleId(IDependencyTarget block)
{
return new RuleException(string.Format(Thread.CurrentThread.CurrentCulture, PSRuleResources.DuplicateRuleId, block.Id));
}
/// <summary>
/// Convert matching langauge blocks to rules.
/// </summary>
private static Rule[] ToRuleV1(IEnumerable<ILanguageBlock> blocks, RunspaceContext context)
private static DependencyTargetCollection<IRuleV1> ToRuleV1(ILanguageBlock[] blocks, RunspaceContext context)
{
// Index rules by RuleId
var results = new Dictionary<string, Rule>(StringComparer.OrdinalIgnoreCase);
var results = new DependencyTargetCollection<IRuleV1>();
try
{
foreach (var block in blocks.OfType<RuleBlock>())
{
if (!results.ContainsKey(block.RuleId))
results.TryAdd(new Rule
{
results[block.RuleId] = new Rule
{
RuleId = block.RuleId,
RuleName = block.RuleName,
Source = block.Source,
Tag = block.Tag,
Info = block.Info,
DependsOn = block.DependsOn
};
}
Id = block.Id,
Ref = block.Ref,
Alias = block.Alias,
Source = block.Source,
Tag = block.Tag,
Info = block.Info,
DependsOn = block.DependsOn,
Flags = block.Flags,
});
//throw ThrowDuplicateRuleId(block);
}
foreach (var block in blocks.OfType<RuleV1>())
{
if (!results.ContainsKey(block.Id))
context.EnterSourceScope(block.Source);
var info = GetHelpInfo(context, block.Name, block.Synopsis);
results.TryAdd(new Rule
{
context.EnterSourceScope(block.Source);
var info = GetHelpInfo(context, block.Name, block.Synopsis);
results[block.Id] = new Rule
{
RuleId = block.Id,
RuleName = block.Name,
Source = block.Source,
Tag = block.Metadata.Tags,
Info = info,
DependsOn = null // TODO: No support for DependsOn yet
};
}
Id = block.Id,
Ref = block.Ref,
Alias = block.Alias,
Source = block.Source,
Tag = block.Metadata.Tags,
Info = info,
DependsOn = null, // TODO: No support for DependsOn yet
Flags = block.Flags,
});
//throw ThrowDuplicateRuleId(block);
}
}
finally
{
context.ExitSourceScope();
}
return results.Values.ToArray();
return results;
}
private static RuleBlock[] ToRuleBlockV1(IEnumerable<ILanguageBlock> blocks, RunspaceContext context)
private static DependencyTargetCollection<RuleBlock> ToRuleBlockV1(ILanguageBlock[] blocks, RunspaceContext context)
{
// Index rules by RuleId
var results = new Dictionary<string, RuleBlock>(StringComparer.OrdinalIgnoreCase);
//var results = new Dictionary<string, RuleBlock>(StringComparer.OrdinalIgnoreCase);
var results = new DependencyTargetCollection<RuleBlock>();
try
{
foreach (var block in blocks)
foreach (var block in blocks.OfType<RuleBlock>())
{
if (block is RuleBlock script && !results.ContainsKey(script.RuleId))
results.TryAdd(block);
//throw ThrowDuplicateRuleId(block);
}
foreach (var yaml in blocks.OfType<RuleV1>())
{
context.EnterSourceScope(yaml.Source);
var info = GetHelpInfo(context, yaml.Name, yaml.Synopsis) ?? new RuleHelpInfo(yaml.Name, yaml.Name, yaml.Source.ModuleName)
{
results[script.RuleId] = script;
}
else if (block is RuleV1 yaml && !results.ContainsKey(yaml.Id))
{
context.EnterSourceScope(yaml.Source);
var info = GetHelpInfo(context, yaml.Name, yaml.Synopsis) ?? new RuleHelpInfo(yaml.Name, yaml.Name, yaml.Source.ModuleName)
{
Synopsis = yaml.Synopsis
};
results[yaml.Id] = new RuleBlock
(
source: yaml.Source,
ruleName: yaml.Name,
info: info,
condition: new RuleVisitor(yaml.Source.ModuleName, yaml.Id, yaml.Spec),
tag: yaml.Metadata.Tags,
dependsOn: null, // TODO: No support for DependsOn yet
configuration: null, // TODO: No support for rule configuration use module or workspace config
extent: null,
errorPreference: ActionPreference.Stop
);
}
Synopsis = yaml.Synopsis
};
var block = new RuleBlock
(
source: yaml.Source,
id: yaml.Id,
@ref: yaml.Ref,
info: info,
condition: new RuleVisitor(yaml.Source.ModuleName, yaml.Id.Value, yaml.Spec),
alias: yaml.Alias,
tag: yaml.Metadata.Tags,
dependsOn: null, // TODO: No support for DependsOn yet
configuration: null, // TODO: No support for rule configuration use module or workspace config
extent: null,
flags: yaml.Flags
);
results.TryAdd(block);
//throw ThrowDuplicateRuleId(block);
}
}
finally
{
context.ExitSourceScope();
}
return results.Values.ToArray();
return results;
}
private static RuleHelpInfo[] ToRuleHelp(IEnumerable<ILanguageBlock> blocks, RunspaceContext context)
@ -488,8 +482,8 @@ namespace PSRule.Host
if (!Match(context, block))
continue;
if (!results.ContainsKey(block.RuleId))
results[block.RuleId] = block.Info;
if (!results.ContainsKey(block.Id.Value))
results[block.Id.Value] = block.Info;
}
}
finally
@ -563,7 +557,7 @@ namespace PSRule.Host
if (!Match(context, block))
continue;
if (!index.Contains(block.Id))
if (!index.Contains(block.Id.Value))
results.Add(block);
}
}
@ -589,8 +583,8 @@ namespace PSRule.Host
if (!Match(context, block))
continue;
if (!results.ContainsKey(block.Id))
results[block.Id] = block;
if (!results.ContainsKey(block.Id.Value))
results[block.Id.Value] = block;
}
}
finally
@ -626,7 +620,7 @@ namespace PSRule.Host
return filter == null || filter.Match(resource);
}
private static bool Match(RunspaceContext context, Rule resource)
private static bool Match(RunspaceContext context, IRuleV1 resource)
{
context.EnterSourceScope(resource.Source);
var filter = context.LanguageScope.GetFilter(ResourceKind.Rule);

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

@ -1,14 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Host
{
internal interface IDependencyTarget
{
string RuleId { get; }
string[] DependsOn { get; }
bool Dependency { get; }
}
}

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

@ -1,23 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
namespace PSRule.Host
{
public enum LanguageBlockKind
{
Unknown = 0,
Rule = 1,
Baseline = 2
}
public interface ILanguageBlock
{
string Id { get; }
string SourcePath { get; }
string Module { get; }
}
}

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

@ -639,7 +639,7 @@ function Assert-PSRule {
# .ExternalHelp PSRule-Help.xml
function Get-PSRule {
[CmdletBinding()]
[OutputType([PSRule.Rules.Rule])]
[OutputType([PSRule.Definitions.Rules.IRuleV1])]
param (
[Parameter(Mandatory = $False)]
[Alias('m')]
@ -1190,6 +1190,11 @@ function New-PSRuleOption {
[Alias('ExecutionSuppressedRuleWarning')]
[System.Boolean]$SuppressedRuleWarning = $True,
# Sets the Execution.AliasReferenceWarning option
[Parameter(Mandatory = $False)]
[Alias('ExecutionAliasReferenceWarning')]
[System.Boolean]$AliasReferenceWarning = $True,
# Sets the Include.Module option
[Parameter(Mandatory = $False)]
[String[]]$IncludeModule,
@ -1442,6 +1447,11 @@ function Set-PSRuleOption {
[Alias('ExecutionSuppressedRuleWarning')]
[System.Boolean]$SuppressedRuleWarning = $True,
# Sets the Execution.AliasReferenceWarning option
[Parameter(Mandatory = $False)]
[Alias('ExecutionAliasReferenceWarning')]
[System.Boolean]$AliasReferenceWarning = $True,
# Sets the Include.Module option
[Parameter(Mandatory = $False)]
[String[]]$IncludeModule,
@ -1649,8 +1659,18 @@ function Rule {
param (
# The name of the rule
[Parameter(Position = 0, Mandatory = $True)]
[ValidateLength(3, 128)]
[ValidateNotNullOrEmpty()]
[String]$Name,
# An optional stable opaque identifier of this resource for lookup.
[Parameter(Mandatory = $False)]
[String]$Ref,
# Any aliases for the rule.
[Parameter(Mandatory = $False)]
[String[]]$Alias,
# The body of the rule
[Parameter(Position = 1, Mandatory = $True)]
[ScriptBlock]$Body,
@ -2126,6 +2146,11 @@ function SetOptions {
[Alias('ExecutionSuppressedRuleWarning')]
[System.Boolean]$SuppressedRuleWarning = $True,
# Sets the Execution.AliasReferenceWarning option
[Parameter(Mandatory = $False)]
[Alias('ExecutionAliasReferenceWarning')]
[System.Boolean]$AliasReferenceWarning = $True,
# Sets the Include.Module option
[Parameter(Mandatory = $False)]
[String[]]$IncludeModule,
@ -2289,6 +2314,11 @@ function SetOptions {
$Option.Execution.SuppressedRuleWarning = $SuppressedRuleWarning;
}
# Sets option Execution.AliasReferenceWarning
if ($PSBoundParameters.ContainsKey('AliasReferenceWarning')) {
$Option.Execution.AliasReferenceWarning = $AliasReferenceWarning;
}
# Sets option Include.Module
if ($PSBoundParameters.ContainsKey('IncludeModule')) {
$Option.Include.Module = $IncludeModule;

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

@ -375,6 +375,46 @@ namespace PSRule.Pipeline
LineBreak();
}
protected static string GetErrorMessage(RuleRecord record)
{
return string.IsNullOrEmpty(record.Ref) ?
string.Format(
Thread.CurrentThread.CurrentCulture,
FormatterStrings.Result_ErrorDetail,
record.TargetName,
record.RuleName,
record.Error.Message
) :
string.Format(
Thread.CurrentThread.CurrentCulture,
FormatterStrings.Result_ErrorDetailWithRef,
record.TargetName,
record.RuleName,
record.Error.Message,
record.Ref
);
}
protected static string GetFailMessage(RuleRecord record)
{
return string.IsNullOrEmpty(record.Ref) ?
string.Format(
Thread.CurrentThread.CurrentCulture,
FormatterStrings.Result_FailDetail,
record.TargetName,
record.RuleName,
record.Info.Synopsis
) :
string.Format(
Thread.CurrentThread.CurrentCulture,
FormatterStrings.Result_FailDetailWithRef,
record.TargetName,
record.RuleName,
record.Info.Synopsis,
record.Ref
);
}
protected override void DoWriteError(ErrorRecord errorRecord)
{
Error(errorRecord);
@ -431,7 +471,7 @@ namespace PSRule.Pipeline
WriteLineFormat(FormatterStrings.FooterRunInfo, PipelineContext.CurrentThread.RunId, elapsed.ToString("c", Thread.CurrentThread.CurrentCulture));
}
protected void WriteStatus(string status, string statusIndent, ConsoleColor? statusForeground, ConsoleColor? statusBackground, ConsoleColor? messageForeground, ConsoleColor? messageBackground, string message)
protected void WriteStatus(string status, string statusIndent, ConsoleColor? statusForeground, ConsoleColor? statusBackground, ConsoleColor? messageForeground, ConsoleColor? messageBackground, string message, string suffix = null)
{
var output = message;
if (statusForeground != null || statusBackground != null)
@ -445,10 +485,11 @@ namespace PSRule.Pipeline
NoNewLine = true
});
Writer.WriteHost(new HostInformationMessage { Message = " ", NoNewLine = true });
output = string.IsNullOrEmpty(suffix) ? output : string.Concat(output, " (", suffix, ")");
}
else
{
output = string.Concat(status, output);
output = string.IsNullOrEmpty(suffix) ? string.Concat(status, output) : string.Concat(status, output, " (", suffix, ")"); ;
}
Writer.WriteHost(new HostInformationMessage
{
@ -657,7 +698,8 @@ namespace PSRule.Pipeline
statusBackground: GetTerminalSupport().WarningStatusBackgroundColor,
messageForeground: GetTerminalSupport().WarningForegroundColor,
messageBackground: GetTerminalSupport().WarningBackgroundColor,
message: message);
message: message
);
UnbrokenInfo();
}
@ -672,7 +714,9 @@ namespace PSRule.Pipeline
statusBackground: GetTerminalSupport().PassStatusBackgroundColor,
messageForeground: GetTerminalSupport().PassForegroundColor,
messageBackground: GetTerminalSupport().PassBackgroundColor,
message: record.RuleName);
message: record.RuleName,
suffix: record.Ref
);
UnbrokenContent();
}
@ -687,7 +731,9 @@ namespace PSRule.Pipeline
statusBackground: GetTerminalSupport().FailStatusBackgroundColor,
messageForeground: GetTerminalSupport().FailForegroundColor,
messageBackground: GetTerminalSupport().FailBackgroundColor,
message: record.RuleName);
message: record.RuleName,
suffix: record.Ref
);
FailDetail(record);
}
@ -702,7 +748,9 @@ namespace PSRule.Pipeline
statusBackground: GetTerminalSupport().ErrorStatusBackgroundColor,
messageForeground: GetTerminalSupport().ErrorForegroundColor,
messageBackground: GetTerminalSupport().ErrorBackgroundColor,
message: record.RuleName);
message: record.RuleName,
suffix: record.Ref
);
ErrorDetail(record);
UnbrokenContent();
}
@ -820,12 +868,7 @@ namespace PSRule.Pipeline
return;
LineBreak();
Error(string.Format(
Thread.CurrentThread.CurrentCulture,
FormatterStrings.Result_ErrorDetail,
record.TargetName,
record.RuleName,
record.Error.Message));
Error(GetErrorMessage(record));
LineBreak();
WriteLine(record.Error.PositionMessage);
LineBreak();
@ -835,12 +878,7 @@ namespace PSRule.Pipeline
protected override void FailDetail(RuleRecord record)
{
base.FailDetail(record);
Error(string.Format(
Thread.CurrentThread.CurrentCulture,
FormatterStrings.Result_FailDetail,
record.TargetName,
record.RuleName,
record.Info.Synopsis));
Error(GetFailMessage(record));
LineBreak();
}
}
@ -877,12 +915,7 @@ namespace PSRule.Pipeline
return;
LineBreak();
Error(string.Format(
Thread.CurrentThread.CurrentCulture,
FormatterStrings.Result_ErrorDetail,
record.TargetName,
record.RuleName,
record.Error.Message));
Error(GetErrorMessage(record));
LineBreak();
WriteLine(record.Error.PositionMessage);
LineBreak();
@ -892,12 +925,7 @@ namespace PSRule.Pipeline
protected override void FailDetail(RuleRecord record)
{
base.FailDetail(record);
Error(string.Format(
Thread.CurrentThread.CurrentCulture,
FormatterStrings.Result_FailDetail,
record.TargetName,
record.RuleName,
record.Info.Synopsis));
Error(GetFailMessage(record));
LineBreak();
}
}

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

@ -7,6 +7,7 @@ using System.Linq;
using System.Management.Automation;
using PSRule.Configuration;
using PSRule.Data;
using PSRule.Definitions;
using PSRule.Host;
using PSRule.Rules;
@ -170,7 +171,7 @@ namespace PSRule.Pipeline
private readonly Dictionary<string, RuleSummaryRecord> _Summary;
private readonly ResultFormat _ResultFormat;
private readonly RuleSuppressionFilter _SuppressionFilter;
private readonly SuppressionFilter _SuppressionFilter;
private readonly List<InvokeResult> _Completed;
// Track whether Dispose has been called.
@ -187,7 +188,7 @@ namespace PSRule.Pipeline
_Outcome = outcome;
_Summary = new Dictionary<string, RuleSummaryRecord>();
_ResultFormat = context.Option.Output.As.Value;
_SuppressionFilter = new RuleSuppressionFilter(context.Option.Suppression);
_SuppressionFilter = new SuppressionFilter(Context, context.Option.Suppression, _RuleGraph.GetAll());
_Completed = new List<InvokeResult>();
}
@ -254,7 +255,7 @@ namespace PSRule.Pipeline
ruleRecord.OutcomeReason = RuleOutcomeReason.DependencyFail;
}
// Check for suppression
else if (_SuppressionFilter.Match(ruleName: ruleRecord.RuleName, targetName: ruleRecord.TargetName))
else if (_SuppressionFilter.Match(id: ruleBlockTarget.Value.Id, targetName: ruleRecord.TargetName))
{
ruleRecord.OutcomeReason = RuleOutcomeReason.Suppressed;
suppressedRuleCounter++;
@ -314,15 +315,15 @@ namespace PSRule.Pipeline
/// </summary>
private void AddToSummary(RuleBlock ruleBlock, RuleOutcome outcome)
{
if (!_Summary.TryGetValue(ruleBlock.RuleId, out var s))
if (!_Summary.TryGetValue(ruleBlock.Id.Value, out var s))
{
s = new RuleSummaryRecord(
ruleId: ruleBlock.RuleId,
ruleName: ruleBlock.RuleName,
ruleId: ruleBlock.Id.Value,
ruleName: ruleBlock.Name,
tag: ruleBlock.Tag,
info: ruleBlock.Info
);
_Summary.Add(ruleBlock.RuleId, s);
_Summary.Add(ruleBlock.Id.Value, s);
}
if (outcome == RuleOutcome.Pass)

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

@ -9,7 +9,7 @@ using PSRule.Definitions;
using PSRule.Definitions.Baselines;
using PSRule.Definitions.Conventions;
using PSRule.Definitions.ModuleConfigs;
using PSRule.Rules;
using PSRule.Definitions.Rules;
using PSRule.Runtime;
namespace PSRule.Pipeline
@ -332,10 +332,10 @@ namespace PSRule.Pipeline
foreach (var baseline in _ModuleBaselineScope.Values)
{
if (baseline.Obsolete)
context.WarnBaselineObsolete(baseline.Id);
context.WarnResourceObsolete(ResourceKind.Baseline, baseline.Id);
}
if (_Explicit != null && _Explicit.Obsolete)
context.WarnBaselineObsolete(_Explicit.Id);
context.WarnResourceObsolete(ResourceKind.Baseline, _Explicit.Id);
}
internal void Add(BaselineScope scope)
@ -378,7 +378,7 @@ namespace PSRule.Pipeline
internal int GetConventionOrder(IConvention convention)
{
var index = _ConventionOrder.IndexOf(convention.Id);
var index = _ConventionOrder.IndexOf(convention.Id.Value);
if (index == -1)
index = _ConventionOrder.IndexOf(convention.Name);

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

@ -1,9 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System.Management.Automation;
using PSRule.Configuration;
using PSRule.Definitions.Rules;
using PSRule.Rules;
namespace PSRule.Pipeline.Output
@ -17,7 +18,7 @@ namespace PSRule.Pipeline.Output
{
if (sendToPipeline is InvokeResult result)
WriteWideObject(result.AsRecord());
else if (sendToPipeline is IEnumerable<Rule> rules)
else if (sendToPipeline is IEnumerable<IRuleV1> rules)
WriteWideObject(rules);
else
base.WriteObject(sendToPipeline, enumerateCollection);

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

@ -167,7 +167,7 @@ namespace PSRule.Pipeline
Baseline.Add(new OptionContext.BaselineScope(baselineRef.Type, baseline.BaselineId, resource.Module, baseline.Spec, baseline.Obsolete));
}
else if (resource.Kind == ResourceKind.Selector && resource is SelectorV1 selector)
Selector[selector.Id] = new SelectorVisitor(resource.Module, selector.Id, selector.Spec.If);
Selector[selector.Id.Value] = new SelectorVisitor(resource.Module, selector.Id.Value, selector.Spec.If);
else if (TryModuleConfig(resource, out var moduleConfig))
{
if (!string.IsNullOrEmpty(moduleConfig?.Spec?.Rule?.Baseline))
@ -186,10 +186,10 @@ namespace PSRule.Pipeline
// _TrackedIssues.Add(new ResourceIssue(resource.Kind, resource.Id, ResourceIssueType.MissingApiVersion));
}
private bool TryBaselineRef(string resourceId, out BaselineRef baselineRef)
private bool TryBaselineRef(ResourceId resourceId, out BaselineRef baselineRef)
{
baselineRef = null;
var r = _Unresolved.FirstOrDefault(i => ResourceIdEqualityComparer.IdEquals(i.Id, resourceId));
var r = _Unresolved.FirstOrDefault(i => ResourceIdEqualityComparer.IdEquals(i.Id, resourceId.Value));
if (r == null || !(r is BaselineRef br))
return false;

315
src/PSRule/Resources/FormatterStrings.Designer.cs сгенерированный
Просмотреть файл

@ -8,11 +8,10 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace PSRule.Resources
{
namespace PSRule.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
@ -23,400 +22,346 @@ namespace PSRule.Resources
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class FormatterStrings
{
internal class FormatterStrings {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal FormatterStrings()
{
internal FormatterStrings() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager
{
get
{
if (object.ReferenceEquals(resourceMan, null))
{
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PSRule.Resources.FormatterStrings", typeof(FormatterStrings).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture
{
get
{
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set
{
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to ____ _____ ____ __\n / __ \/ ___// __ \__ __/ /__\n / /_/ /\__ \/ /_/ / / / / / _ \\n / ____/___/ / _, _/ /_/ / / __/\n/_/ /____/_/ |_|\__,_/_/\___/.
/// </summary>
internal static string Banner
{
get
{
internal static string Banner {
get {
return ResourceManager.GetString("Banner", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Rules processed: {0}, failed: {1}, errored: {2}.
/// </summary>
internal static string FooterRuleCount
{
get
{
internal static string FooterRuleCount {
get {
return ResourceManager.GetString("FooterRuleCount", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Run {0} completed in {1}.
/// </summary>
internal static string FooterRunInfo
{
get
{
internal static string FooterRunInfo {
get {
return ResourceManager.GetString("FooterRunInfo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to | HELP:.
/// </summary>
internal static string Help
{
get
{
internal static string Help {
get {
return ResourceManager.GetString("Help", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Contribute and find source: https://github.com/microsoft/PSRule.
/// </summary>
internal static string HelpContribute
{
get
{
internal static string HelpContribute {
get {
return ResourceManager.GetString("HelpContribute", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Explore documentation: https://microsoft.github.io/PSRule/.
/// </summary>
internal static string HelpDocs
{
get
{
internal static string HelpDocs {
get {
return ResourceManager.GetString("HelpDocs", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Report issues: https://github.com/microsoft/PSRule/issues.
/// </summary>
internal static string HelpIssues
{
get
{
internal static string HelpIssues {
get {
return ResourceManager.GetString("HelpIssues", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0}: {1}.
/// </summary>
internal static string HelpModule
{
get
{
internal static string HelpModule {
get {
return ResourceManager.GetString("HelpModule", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to | MESSAGE:.
/// </summary>
internal static string Message
{
get
{
internal static string Message {
get {
return ResourceManager.GetString("Message", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Using {0} v{1}.
/// </summary>
internal static string ModuleVersion
{
get
{
internal static string ModuleVersion {
get {
return ResourceManager.GetString("ModuleVersion", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to | POSITION:.
/// </summary>
internal static string Position
{
get
{
internal static string Position {
get {
return ResourceManager.GetString("Position", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Using PSRule v{0}.
/// </summary>
internal static string PSRuleVersion
{
get
{
internal static string PSRuleVersion {
get {
return ResourceManager.GetString("PSRuleVersion", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to | REASON:.
/// </summary>
internal static string Reason
{
get
{
internal static string Reason {
get {
return ResourceManager.GetString("Reason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to | RECOMMEND:.
/// </summary>
internal static string Recommend
{
get
{
internal static string Recommend {
get {
return ResourceManager.GetString("Recommend", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to [ERROR] .
/// </summary>
internal static string Result_Error
{
get
{
internal static string Result_Error {
get {
return ResourceManager.GetString("Result_Error", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} failed {1} with the error &apos;{2}&apos;..
/// </summary>
internal static string Result_ErrorDetail
{
get
{
internal static string Result_ErrorDetail {
get {
return ResourceManager.GetString("Result_ErrorDetail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {3}: {0} failed {1} with the error &apos;{2}&apos;..
/// </summary>
internal static string Result_ErrorDetailWithRef {
get {
return ResourceManager.GetString("Result_ErrorDetailWithRef", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to [FAIL] .
/// </summary>
internal static string Result_Fail
{
get
{
internal static string Result_Fail {
get {
return ResourceManager.GetString("Result_Fail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} failed {1}. {2}.
/// </summary>
internal static string Result_FailDetail
{
get
{
internal static string Result_FailDetail {
get {
return ResourceManager.GetString("Result_FailDetail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {3}: {0} failed {1}. {2}.
/// </summary>
internal static string Result_FailDetailWithRef {
get {
return ResourceManager.GetString("Result_FailDetailWithRef", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to [PASS] .
/// </summary>
internal static string Result_Pass
{
get
{
internal static string Result_Pass {
get {
return ResourceManager.GetString("Result_Pass", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to [WARN] .
/// </summary>
internal static string Result_Warning
{
get
{
internal static string Result_Warning {
get {
return ResourceManager.GetString("Result_Warning", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to At.
/// </summary>
internal static string SourceAt
{
get
{
internal static string SourceAt {
get {
return ResourceManager.GetString("SourceAt", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to | STACK TRACE:.
/// </summary>
internal static string StackTrace
{
get
{
internal static string StackTrace {
get {
return ResourceManager.GetString("StackTrace", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to -&gt; .
/// </summary>
internal static string StartObjectPrefix
{
get
{
internal static string StartObjectPrefix {
get {
return ResourceManager.GetString("StartObjectPrefix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to | SYNOPSIS: .
/// </summary>
internal static string SynopsisPrefix
{
get
{
internal static string SynopsisPrefix {
get {
return ResourceManager.GetString("SynopsisPrefix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to ERROR .
/// </summary>
internal static string VSCode_Error
{
get
{
internal static string VSCode_Error {
get {
return ResourceManager.GetString("VSCode_Error", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to FAIL .
/// </summary>
internal static string VSCode_Fail
{
get
{
internal static string VSCode_Fail {
get {
return ResourceManager.GetString("VSCode_Fail", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Help:.
/// </summary>
internal static string VSCode_Help
{
get
{
internal static string VSCode_Help {
get {
return ResourceManager.GetString("VSCode_Help", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to PASS .
/// </summary>
internal static string VSCode_Pass
{
get
{
internal static string VSCode_Pass {
get {
return ResourceManager.GetString("VSCode_Pass", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Reason:.
/// </summary>
internal static string VSCode_Reason
{
get
{
internal static string VSCode_Reason {
get {
return ResourceManager.GetString("VSCode_Reason", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Recommend:.
/// </summary>
internal static string VSCode_Recommend
{
get
{
internal static string VSCode_Recommend {
get {
return ResourceManager.GetString("VSCode_Recommend", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &gt; .
/// </summary>
internal static string VSCode_StartObjectPrefix
{
get
{
internal static string VSCode_StartObjectPrefix {
get {
return ResourceManager.GetString("VSCode_StartObjectPrefix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to WARN .
/// </summary>
internal static string VSCode_Warning
{
get
{
internal static string VSCode_Warning {
get {
return ResourceManager.GetString("VSCode_Warning", resourceCulture);
}
}

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

@ -167,12 +167,18 @@
<data name="Result_ErrorDetail" xml:space="preserve">
<value>{0} failed {1} with the error '{2}'.</value>
</data>
<data name="Result_ErrorDetailWithRef" xml:space="preserve">
<value>{3}: {0} failed {1} with the error '{2}'.</value>
</data>
<data name="Result_Fail" xml:space="preserve">
<value> [FAIL] </value>
</data>
<data name="Result_FailDetail" xml:space="preserve">
<value>{0} failed {1}. {2}</value>
</data>
<data name="Result_FailDetailWithRef" xml:space="preserve">
<value>{3}: {0} failed {1}. {2}</value>
</data>
<data name="Result_Pass" xml:space="preserve">
<value> [PASS] </value>
</data>

24
src/PSRule/Resources/PSRuleResources.Designer.cs сгенерированный
Просмотреть файл

@ -61,11 +61,20 @@ namespace PSRule.Resources {
}
/// <summary>
/// Looks up a localized string similar to The baseline &apos;{0}&apos; is obsolete. Consider switching to an alternative baseline..
/// Looks up a localized string similar to The {0} resource &apos;{1}&apos; is currently referencing &apos;{2}&apos; using the alias &apos;{3}&apos;. Consider updating the reference to use name or id directly..
/// </summary>
internal static string BaselineObsolete {
internal static string AliasReference {
get {
return ResourceManager.GetString("BaselineObsolete", resourceCulture);
return ResourceManager.GetString("AliasReference", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Suppression for the rule &apos;{0}&apos; was configured using the alias &apos;{1}&apos;. Consider updating the suppression to use the name or id directly..
/// </summary>
internal static string AliasSuppression {
get {
return ResourceManager.GetString("AliasSuppression", resourceCulture);
}
}
@ -393,6 +402,15 @@ namespace PSRule.Resources {
}
}
/// <summary>
/// Looks up a localized string similar to The {0} &apos;{1}&apos; is obsolete. Consider switching to an alternative {0}..
/// </summary>
internal static string ResourceObsolete {
get {
return ResourceManager.GetString("ResourceObsolete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} rule/s were suppressed for &apos;{1}&apos;..
/// </summary>

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

@ -117,9 +117,9 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="BaselineObsolete" xml:space="preserve">
<value>The baseline '{0}' is obsolete. Consider switching to an alternative baseline.</value>
<comment>Occurs when a baseline is used that has been flagged as obsolete.</comment>
<data name="ResourceObsolete" xml:space="preserve">
<value>The {0} '{1}' is obsolete. Consider switching to an alternative {0}.</value>
<comment>Occurs when a resource is used that has been flagged as obsolete.</comment>
</data>
<data name="ConstrainedTargetBinding" xml:space="preserve">
<value>Binding functions are not supported in this language mode.</value>
@ -308,4 +308,10 @@
<data name="WithinTrue" xml:space="preserve">
<value>Within: {0}</value>
</data>
<data name="AliasReference" xml:space="preserve">
<value>The {0} resource '{1}' is currently referencing '{2}' using the alias '{3}'. Consider updating the reference to use name or id directly.</value>
</data>
<data name="AliasSuppression" xml:space="preserve">
<value>Suppression for the rule '{0}' was configured using the alias '{1}'. Consider updating the suppression to use the name or id directly.</value>
</data>
</root>

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

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.ObjectModel;
@ -12,16 +12,17 @@ namespace PSRule.Rules
private const string ERROR_ACTION_PREFERENCE = "ErrorActionPreference";
private readonly PowerShell _Condition;
private readonly ActionPreference _ErrorAction;
private bool _Disposed;
internal PowerShellCondition(PowerShell condition, ActionPreference errorAction)
{
_Condition = condition;
_ErrorAction = errorAction;
ErrorAction = errorAction;
}
public ActionPreference ErrorAction { get; }
private void Dispose(bool disposing)
{
if (!_Disposed)
@ -43,7 +44,7 @@ namespace PSRule.Rules
public IConditionResult If()
{
_Condition.Streams.ClearStreams();
_Condition.Runspace.SessionStateProxy.SetVariable(ERROR_ACTION_PREFERENCE, _ErrorAction);
_Condition.Runspace.SessionStateProxy.SetVariable(ERROR_ACTION_PREFERENCE, ErrorAction);
return GetResult(_Condition.Invoke<Runtime.RuleConditionResult>());
}

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

@ -1,11 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.ComponentModel;
using Newtonsoft.Json;
using PSRule.Data;
using PSRule.Definitions;
using PSRule.Host;
using PSRule.Definitions.Rules;
using PSRule.Pipeline;
using YamlDotNet.Serialization;
@ -15,19 +16,33 @@ namespace PSRule.Rules
/// Define a single rule.
/// </summary>
[JsonObject]
public sealed class Rule : IDependencyTarget, ITargetInfo, IResource
public sealed class Rule : IDependencyTarget, ITargetInfo, IResource, IRuleV1
{
/// <summary>
/// A unique identifier for the rule.
/// </summary>
[JsonProperty(PropertyName = "ruleId", Required = Required.Always)]
public string RuleId { get; set; }
[JsonProperty(PropertyName = "id", Required = Required.Always)]
public ResourceId Id { get; set; }
/// <summary>
/// The name of the rule.
/// </summary>
[JsonProperty(PropertyName = "name", Required = Required.Always)]
public string Name => Id.Name;
/// <summary>
/// Legacy. A unique identifier for the rule.
/// </summary>
[JsonProperty(PropertyName = "ruleId", Required = Required.Always)]
[Obsolete("Use Id instead")]
public string RuleId => Id.Value;
/// <summary>
/// Legacy. The name of the rule.
/// </summary>
[JsonProperty(PropertyName = "ruleName", Required = Required.Always)]
public string RuleName { get; set; }
[Obsolete("Use Name instead")]
public string RuleName => Name;
/// <summary>
/// The script file path where the rule is defined.
@ -50,9 +65,12 @@ namespace PSRule.Rules
[YamlIgnore]
public string Synopsis => Info.Synopsis;
// Alias to synopsis
/// <summary>
/// Legacy. Alias to synopsis
/// </summary>
[JsonIgnore]
[YamlIgnore]
[Obsolete("Use Synopsis instead.")]
public string Description => Info.Synopsis;
/// <summary>
@ -74,9 +92,13 @@ namespace PSRule.Rules
/// Other rules that must completed successfully before calling this rule.
/// </summary>
[JsonProperty(PropertyName = "dependsOn")]
public string[] DependsOn { get; set; }
public ResourceId[] DependsOn { get; set; }
string ITargetInfo.TargetName => RuleName;
[JsonIgnore]
[YamlIgnore]
public ResourceFlags Flags { get; set; }
string ITargetInfo.TargetName => Name;
string ITargetInfo.TargetType => typeof(Rule).FullName;
@ -86,14 +108,20 @@ namespace PSRule.Rules
string IResource.ApiVersion => Specs.V1;
string IResource.Name => RuleName;
string IResource.Name => Name;
ResourceTags IResource.Tags => Tag;
string ILanguageBlock.Id => RuleId;
string ILanguageBlock.SourcePath => Source.Path;
string ILanguageBlock.Module => Source.ModuleName;
[JsonIgnore]
[YamlIgnore]
public ResourceId? Ref { get; set; }
[JsonIgnore]
[YamlIgnore]
public ResourceId[] Alias { get; set; }
}
}

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

@ -1,14 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections;
using System.Diagnostics;
using System.Management.Automation;
using Newtonsoft.Json;
using PSRule.Definitions;
using PSRule.Host;
using PSRule.Definitions.Rules;
using PSRule.Pipeline;
using PSRule.Runtime;
using YamlDotNet.Serialization;
namespace PSRule.Rules
{
@ -19,16 +20,18 @@ namespace PSRule.Rules
/// <summary>
/// Define an instance of a rule block. Each rule block has a unique id.
/// </summary>
[DebuggerDisplay("{RuleId} @{Source.Path}")]
public sealed class RuleBlock : ILanguageBlock, IDependencyTarget, IDisposable, IResource
[DebuggerDisplay("{Id} @{Source.Path}")]
internal sealed class RuleBlock : ILanguageBlock, IDependencyTarget, IDisposable, IResource, IRuleV1
{
internal RuleBlock(SourceFile source, string ruleName, RuleHelpInfo info, ICondition condition, ResourceTags tag, string[] dependsOn, Hashtable configuration, RuleExtent extent, ActionPreference errorPreference)
internal RuleBlock(SourceFile source, ResourceId id, ResourceId? @ref, RuleHelpInfo info, ICondition condition, ResourceTags tag, ResourceId[] alias, ResourceId[] dependsOn, Hashtable configuration, RuleExtent extent, ResourceFlags flags)
{
Source = source;
RuleName = ruleName;
Name = id.Name;
// Get fully qualified Id, either RuleName or Module\RuleName
RuleId = ResourceHelper.GetRuleIdString(Source.ModuleName, ruleName);
Id = id;
Ref = @ref;
Alias = alias;
Info = info;
Condition = condition;
@ -36,28 +39,34 @@ namespace PSRule.Rules
DependsOn = dependsOn;
Configuration = configuration;
Extent = extent;
ErrorPreference = errorPreference;
Flags = flags;
}
/// <summary>
/// A unique identifier for the rule.
/// </summary>
public readonly string RuleId;
public ResourceId Id { get; }
public ResourceId? Ref { get; }
public ResourceId[] Alias { get; }
/// <summary>
/// The name of the rule.
/// </summary>
public readonly string RuleName;
public readonly string Name;
/// <summary>
/// The body of the rule definition where conditions are provided that either pass or fail the rule.
/// </summary>
[JsonIgnore]
[YamlIgnore]
public readonly ICondition Condition;
/// <summary>
/// Other rules that must completed successfully before calling this rule.
/// </summary>
public readonly string[] DependsOn;
public readonly ResourceId[] DependsOn;
/// <summary>
/// Tags assigned to block. Tags are additional metadata used to select rules to execute and identify results.
@ -78,17 +87,15 @@ namespace PSRule.Rules
internal readonly RuleExtent Extent;
internal readonly ActionPreference ErrorPreference;
string ILanguageBlock.Id => RuleId;
[JsonIgnore]
[YamlIgnore]
public ResourceFlags Flags { get; }
string ILanguageBlock.SourcePath => Source.Path;
string ILanguageBlock.Module => Source.ModuleName;
string IDependencyTarget.RuleId => RuleId;
string[] IDependencyTarget.DependsOn => DependsOn;
ResourceId[] IDependencyTarget.DependsOn => DependsOn;
bool IDependencyTarget.Dependency => Source.IsDependency();
@ -96,10 +103,20 @@ namespace PSRule.Rules
string IResource.ApiVersion => Specs.V1;
string IResource.Name => RuleName;
string IResource.Name => Name;
ResourceTags IResource.Tags => Tag;
string IRuleV1.RuleName => Name;
ResourceTags IRuleV1.Tag => Tag;
string IRuleV1.Synopsis => Info.Synopsis;
string IRuleV1.Description => Info.Synopsis;
SourceFile IRuleV1.Source => Source;
#region IDisposable
public void Dispose()

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

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
@ -20,11 +20,12 @@ namespace PSRule.Rules
[JsonObject]
public sealed class RuleRecord
{
internal RuleRecord(string runId, string ruleId, string ruleName, PSObject targetObject, string targetName, string targetType, ResourceTags tag, RuleHelpInfo info, Hashtable field, Hashtable data, TargetSourceInfo[] source, RuleOutcome outcome = RuleOutcome.None, RuleOutcomeReason reason = RuleOutcomeReason.None)
internal RuleRecord(string runId, string ruleId, string ruleName, string @ref, PSObject targetObject, string targetName, string targetType, ResourceTags tag, RuleHelpInfo info, Hashtable field, Hashtable data, TargetSourceInfo[] source, RuleOutcome outcome = RuleOutcome.None, RuleOutcomeReason reason = RuleOutcomeReason.None)
{
RunId = runId;
RuleId = ruleId;
RuleName = ruleName;
Ref = @ref;
TargetObject = targetObject;
TargetName = targetName;
TargetType = targetType;
@ -61,6 +62,8 @@ namespace PSRule.Rules
[JsonProperty(PropertyName = "ruleName")]
public readonly string RuleName;
public string Ref { get; }
/// <summary>
/// The outcome after the rule processes an object.
/// </summary>

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

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
@ -6,25 +6,27 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using PSRule.Configuration;
using PSRule.Definitions;
using PSRule.Runtime;
namespace PSRule.Rules
{
[DebuggerDisplay("{_Index.Count}")]
internal sealed class RuleSuppressionFilter
internal sealed class SuppressionFilter
{
private readonly HashSet<SuppressionKey> _Index;
private readonly bool _IsEmpty;
public RuleSuppressionFilter(SuppressionOption option)
public SuppressionFilter(RunspaceContext context, SuppressionOption option, IEnumerable<IResource> rules)
{
if (option == null || option.Count == 0)
if (option == null || option.Count == 0 || rules == null)
{
_IsEmpty = true;
}
else
{
_Index = new HashSet<SuppressionKey>();
Index(option);
_Index = Index(context, option, rules);
_IsEmpty = _Index.Count == 0;
}
}
@ -77,26 +79,36 @@ namespace PSRule.Rules
}
}
public bool Match(string ruleName, string targetName)
public bool Match(ResourceId id, string targetName)
{
return !_IsEmpty &&
!string.IsNullOrEmpty(ruleName) &&
!string.IsNullOrEmpty(targetName) &&
_Index.Contains(new SuppressionKey(ruleName, targetName));
_Index.Contains(new SuppressionKey(id.Value, targetName));
}
private void Index(SuppressionOption option)
private static HashSet<SuppressionKey> Index(RunspaceContext context, SuppressionOption option, IEnumerable<IResource> rules)
{
var resolver = new ResourceIndex(rules);
var index = new HashSet<SuppressionKey>();
// Read suppress rules into index combined key (RuleName + TargetName)
foreach (var rule in option)
{
// Only add suppresion entries for rules that are loaded
if (!resolver.TryFind(rule.Key, out var blockId, out var kind))
continue;
if (kind == ResourceIdKind.Alias)
context.WarnAliasSuppression(blockId.Value, rule.Key);
foreach (var targetName in rule.Value.TargetName)
{
var key = new SuppressionKey(rule.Key, targetName);
if (!_Index.Contains(key))
_Index.Add(key);
var key = new SuppressionKey(blockId.Value, targetName);
if (!index.Contains(key))
index.Add(key);
}
}
return index;
}
}
}

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

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
@ -9,7 +9,8 @@ namespace PSRule.Runtime
internal sealed class LanguageScriptBlock : IDisposable
{
private readonly PowerShell _Block;
private bool disposedValue;
private bool _Disposed;
public LanguageScriptBlock(PowerShell block)
{
@ -25,13 +26,13 @@ namespace PSRule.Runtime
private void Dispose(bool disposing)
{
if (!disposedValue)
if (!_Disposed)
{
if (disposing)
{
_Block.Dispose();
}
disposedValue = true;
_Disposed = true;
}
}

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

@ -60,7 +60,6 @@ namespace PSRule.Runtime
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 WARN_KEY_PROPERTY = "Property";
private const string WARN_KEY_SEPARATOR = "_";
[ThreadStatic]
@ -262,39 +261,6 @@ namespace PSRule.Runtime
Writer.WriteWarning(PSRuleResources.RuleCountSuppressed, ruleCount, Binding.TargetName);
}
public void WarnRuleNotFound()
{
if (Writer == null || !Writer.ShouldWriteWarning())
return;
Writer.WriteWarning(PSRuleResources.RuleNotFound);
}
public void WarnBaselineObsolete(string baselineId)
{
if (Writer == null || !Writer.ShouldWriteWarning())
return;
Writer.WriteWarning(PSRuleResources.BaselineObsolete, baselineId);
}
public void WarnPropertyObsolete(string variableName, string propertyName)
{
DebugPropertyObsolete(variableName, propertyName);
if (Writer == null || !Writer.ShouldWriteWarning() || !ShouldWarnOnce(WARN_KEY_PROPERTY, variableName, propertyName))
return;
Writer.WriteWarning(PSRuleResources.PropertyObsolete, variableName, propertyName);
}
private void DebugPropertyObsolete(string variableName, string propertyName)
{
if (Writer == null || !Writer.ShouldWriteDebug())
return;
Writer.WriteDebug(PSRuleResources.DebugPropertyObsolete, RuleBlock.RuleName, variableName, propertyName);
}
public void ErrorInvaildRuleResult()
{
if (Writer == null || !Writer.ShouldWriteError())
@ -304,7 +270,8 @@ namespace PSRule.Runtime
exception: new RuleException(message: string.Format(
Thread.CurrentThread.CurrentCulture,
PSRuleResources.InvalidRuleResult,
RuleBlock.RuleId)),
RuleBlock.Id
)),
errorId: ERRORID_INVALIDRULERESULT,
errorCategory: ErrorCategory.InvalidResult,
targetObject: null
@ -528,7 +495,7 @@ namespace PSRule.Runtime
string.Format(
Thread.CurrentThread.CurrentCulture,
PSRuleResources.RuleStackTrace,
RuleBlock.RuleName,
RuleBlock.Name,
RuleBlock.Extent.File,
RuleBlock.Extent.StartLineNumber)
);
@ -541,7 +508,7 @@ namespace PSRule.Runtime
: string.Concat(
record.FullyQualifiedErrorId,
",",
RuleBlock.RuleName
RuleBlock.Name
);
}
@ -658,8 +625,9 @@ namespace PSRule.Runtime
RuleBlock = ruleBlock;
RuleRecord = new RuleRecord(
runId: Pipeline.RunId,
ruleId: ruleBlock.RuleId,
ruleName: ruleBlock.RuleName,
ruleId: ruleBlock.Id.Value,
ruleName: ruleBlock.Name,
@ref: ruleBlock.Ref.GetValueOrDefault().Name,
targetObject: TargetObject.Value,
targetName: Binding.TargetName,
targetType: Binding.TargetType,
@ -671,7 +639,7 @@ namespace PSRule.Runtime
);
if (Writer != null)
Writer.EnterScope(ruleBlock.RuleName);
Writer.EnterScope(ruleBlock.Name);
// Starts rule execution timer
_RuleTimer.Restart();
@ -831,7 +799,7 @@ namespace PSRule.Runtime
return null;
}
private bool ShouldWarnOnce(params string[] key)
internal bool ShouldWarnOnce(params string[] key)
{
var combinedKey = string.Join(WARN_KEY_SEPARATOR, key);
if (_WarnOnce.Contains(combinedKey))

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

@ -0,0 +1,20 @@
[
{
// Synopsis: A rule with an alias.
"apiVersion": "github.com/microsoft/PSRule/v1",
"kind": "Rule",
"metadata": {
"name": "JSON.RuleWithAlias1",
"alias": [
"JSON.AlternativeName"
],
"ref": "PSRZZ.0003"
},
"spec": {
"condition": {
"field": "name",
"exists": true
}
}
}
]

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

@ -0,0 +1,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# Synopsis: A rule with an alias.
Rule 'PS.RuleWithAlias1' -Ref 'PSRZZ.0001' -Alias 'PS.AlternativeName' {
$Assert.HasField($TargetObject, 'name');
}

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

@ -0,0 +1,16 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
---
# Synopsis: A rule with an alias.
apiVersion: github.com/microsoft/PSRule/v1
kind: Rule
metadata:
name: 'YAML.RuleWithAlias1'
alias:
- 'YAML.AlternativeName'
ref: PSRZZ.0002
spec:
condition:
field: name
exists: true

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

@ -708,7 +708,7 @@ Describe 'Baseline' -Tag 'Baseline' {
$Null = @($testObject | Invoke-PSRule -Path $ruleFilePath,$baselineFilePath -Baseline 'TestBaseline5' -WarningVariable outWarn -WarningAction SilentlyContinue);
$warnings = @($outWarn);
$warnings.Length | Should -Be 1;
$warnings[0] | Should -BeExactly "The baseline 'TestBaseline5' is obsolete. Consider switching to an alternative baseline.";
$warnings[0] | Should -BeExactly "The Baseline 'TestBaseline5' is obsolete. Consider switching to an alternative Baseline.";
}
It 'With -Module' {

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

@ -281,6 +281,16 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' {
$result | Should -BeOfType PSRule.Rules.RuleRecord;
($result | Where-Object { $_.TargetName -eq 'TestObject1' }).OutcomeReason | Should -BeIn 'Suppressed';
($result | Where-Object { $_.TargetName -eq 'TestObject2' }).OutcomeReason | Should -BeIn 'Processed';
# With aliases
$aliasRuleFilePath = @(
(Join-Path -Path $here -ChildPath 'FromFileAlias.Rule.jsonc')
(Join-Path -Path $here -ChildPath 'FromFileAlias.Rule.ps1')
)
$option = New-PSRuleOption -SuppressTargetName @{ 'JSON.AlternativeName' = 'TestObject1'; 'PSRZZ.0003' = 'testobject2'; 'PS.AlternativeName' = 'TestObject1' };
$result = $testObject | Invoke-PSRule -Path $aliasRuleFilePath -Option $option -Name 'JSON.AlternativeName','PS.RuleWithAlias1' -Outcome All;
($result | Where-Object { $_.TargetName -eq 'TestObject1' }).OutcomeReason | Should -BeIn 'Suppressed';
$result.Count | Should -Be 4;
}
It 'Processes configuration' {
@ -1149,9 +1159,9 @@ Describe 'Invoke-PSRule' -Tag 'Invoke-PSRule','Common' {
$warningMessages.Length | Should -Be 2;
$warningMessages[0] | Should -BeOfType [System.Management.Automation.WarningRecord];
$warningMessages[0].Message | Should -BeExactly "Rule 'FromFile1' was suppressed for 'TestObject1'.";
$warningMessages[0].Message | Should -BeExactly "Rule '.\FromFile1' was suppressed for 'TestObject1'.";
$warningMessages[1] | Should -BeOfType [System.Management.Automation.WarningRecord];
$warningMessages[1].Message | Should -BeExactly "Rule 'FromFile2' was suppressed for 'TestObject1'.";
$warningMessages[1].Message | Should -BeExactly "Rule '.\FromFile2' was suppressed for 'TestObject1'.";
}
It 'No warnings' {
@ -1632,7 +1642,7 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' {
$result | Should -Not -BeNullOrEmpty;
$result.Length | Should -Be 3;
$result.RuleName | Should -BeIn 'M1.Rule1', 'M1.Rule2', 'M1.YamlTestName';
($result | Get-Member).TypeName | Should -BeIn 'PSRule.Rules.Rule';
$result | Should -BeOfType 'PSRule.Definitions.Rules.IRuleV1';
}
finally {
Pop-Location;
@ -1972,7 +1982,7 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' {
$result = @(Get-PSRule -Path $ruleFilePath -Name 'FromFile4');
$result | Should -Not -BeNullOrEmpty;
$result.Length | Should -Be 1;
$result[0].DependsOn | Should -BeIn 'FromFile3';
$result[0].DependsOn | Should -BeIn '.\FromFile3';
# Get a list of rules with dependencies
$result = @(Get-PSRule -Path $ruleFilePath -Name 'FromFile4' -IncludeDependencies);
@ -1980,7 +1990,7 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' {
$result.Length | Should -Be 2;
$result[0].RuleName | Should -Be 'FromFile3';
$result[1].RuleName | Should -Be 'FromFile4';
$result[1].DependsOn | Should -BeIn 'FromFile3';
$result[1].DependsOn | Should -BeIn '.\FromFile3';
}
}
@ -1988,7 +1998,7 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' {
It 'Wide' {
$result = Get-PSRule -Path $ruleFilePath -Name 'FromFile1' -OutputFormat Wide;
$result | Should -Not -BeNullOrEmpty;
($result | Get-Member).TypeName | Should -BeIn 'PSRule.Rules.Rule+Wide';
($result | Get-Member).TypeName | Should -BeIn 'PSRule.Definitions.Rules.IRuleV1+Wide';
}
It 'Yaml' {
@ -2035,31 +2045,31 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' {
Title = '0 space indentation'
OptionHashtable = @{'Output.JsonIndent' = 0}
YamlPath = (Join-Path -Path $here -ChildPath 'PSRule.Tests9.yml')
ExpectedJson = '"ruleId":"FromFile1","ruleName":"FromFile1"'
ExpectedJson = '"ruleId":"\.\\\\FromFile1","ruleName":"FromFile1"'
}
@{
Title = '1 space indentation'
OptionHashtable = @{'Output.JsonIndent' = 1}
YamlPath = (Join-Path -Path $here -ChildPath 'PSRule.Tests10.yml')
ExpectedJson = "`"ruleId`": `"FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`""
ExpectedJson = "`"ruleId`": `"\.\\\\FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`""
}
@{
Title = '2 space indentation'
OptionHashtable = @{'Output.JsonIndent' = 2}
YamlPath = (Join-Path -Pat $here -ChildPath 'PSRule.Tests11.yml')
ExpectedJson = "`"ruleId`": `"FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`""
ExpectedJson = "`"ruleId`": `"\.\\\\FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`""
}
@{
Title = '3 space indentation'
OptionHashtable = @{'Output.JsonIndent' = 3}
YamlPath = (Join-Path -Pat $here -ChildPath 'PSRule.Tests12.yml')
ExpectedJson = "`"ruleId`": `"FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`""
ExpectedJson = "`"ruleId`": `"\.\\\\FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`""
}
@{
Title = '4 space indentation'
OptionHashtable = @{'Output.JsonIndent' = 4}
YamlPath = (Join-Path -Pat $here -ChildPath 'PSRule.Tests13.yml')
ExpectedJson = "`"ruleId`": `"FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`""
ExpectedJson = "`"ruleId`": `"\.\\\\FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`""
}
)
}
@ -2116,12 +2126,12 @@ Describe 'Get-PSRule' -Tag 'Get-PSRule','Common' {
Context 'Normalizie range' {
It 'Normalize to 0 when indentation is less than 0' {
$result = Get-PSRule -Path $ruleFilePath -Name 'FromFile1' -OutputFormat 'Json' -Option @{'Output.JsonIndent' = -1};
$result | Should -MatchExactly '"ruleId":"FromFile1","ruleName":"FromFile1"';
$result | Should -MatchExactly '"ruleId":"\.\\\\FromFile1","ruleName":"FromFile1"';
}
It 'Normalize to 4 when indentation is more than 4' {
$result = Get-PSRule -Path $ruleFilePath -Name 'FromFile1' -OutputFormat 'Json' -Option @{'Output.JsonIndent' = 5};
$result | Should -MatchExactly "`"ruleId`": `"FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`"";
$result | Should -MatchExactly "`"ruleId`": `"\.\\\\FromFile1`",$([Environment]::Newline) `"ruleName`": `"FromFile1`"";
}
}
}

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

@ -123,7 +123,7 @@ Describe 'Scenarios -- fruit' -Tag 'EndToEnd','fruit' {
It 'Returns rules' {
$result = @(Get-PSRule -Path (Join-Path -Path $rootPath -ChildPath docs/scenarios/fruit));
$result.Count | Should -Be 1;
$result | Should -BeOfType PSRule.Rules.Rule;
$result | Should -BeOfType PSRule.Definitions.Rules.IRuleV1;
}
}
}

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

@ -741,6 +741,45 @@ Describe 'New-PSRuleOption' -Tag 'Option','New-PSRuleOption' {
}
}
Context 'Read Execution.AliasReferenceWarning' {
It 'from default' {
$option = New-PSRuleOption -Default;
$option.Execution.AliasReferenceWarning | Should -Be $True;
}
It 'from Hashtable' {
$option = New-PSRuleOption -Option @{ 'Execution.AliasReferenceWarning' = $False };
$option.Execution.AliasReferenceWarning | Should -Be $False;
}
It 'from YAML' {
$option = New-PSRuleOption -Option (Join-Path -Path $here -ChildPath 'PSRule.Tests.yml');
$option.Execution.AliasReferenceWarning | Should -Be $False;
}
It 'from Environment' {
try {
# With bool
$Env:PSRULE_EXECUTION_ALIASREFERENCEWARNING = 'false';
$option = New-PSRuleOption;
$option.Execution.AliasReferenceWarning | Should -Be $False;
# With int
$Env:PSRULE_EXECUTION_ALIASREFERENCEWARNING = '0';
$option = New-PSRuleOption;
$option.Execution.AliasReferenceWarning | Should -Be $False;
}
finally {
Remove-Item 'Env:PSRULE_EXECUTION_ALIASREFERENCEWARNING' -Force;
}
}
It 'from parameter' {
$option = New-PSRuleOption -AliasReferenceWarning $False -Path $emptyOptionsFilePath;
$option.Execution.AliasReferenceWarning | Should -Be $False;
}
}
Context 'Read Include.Path' {
It 'from default' {
$option = New-PSRuleOption -Default;
@ -1707,6 +1746,20 @@ Describe 'Set-PSRuleOption' -Tag 'Option','Set-PSRuleOption' {
}
}
Context 'Read Execution.SuppressedRuleWarning' {
It 'from parameter' {
$option = Set-PSRuleOption -SuppressedRuleWarning $False @optionParams;
$option.Execution.SuppressedRuleWarning | Should -Be $False;
}
}
Context 'Read Execution.AliasReferenceWarning' {
It 'from parameter' {
$option = Set-PSRuleOption -AliasReferenceWarning $False @optionParams;
$option.Execution.AliasReferenceWarning | Should -Be $False;
}
}
Context 'Read Input.Format' {
It 'from parameter' {
$option = Set-PSRuleOption -Format 'Yaml' @optionParams;

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

@ -40,6 +40,15 @@
<None Update="FromFile.Rule.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="FromFileAlias.Rule.jsonc">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="FromFileAlias.Rule.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="FromFileAlias.Rule.yaml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="FromFileConventions.Rule.ps1">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
@ -73,6 +82,9 @@
<None Update="PSRule.Tests.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="PSRule.Tests14.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="PSRule.Tests6.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

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

@ -45,6 +45,7 @@ binding:
# Configure execution options
execution:
aliasReferenceWarning: false
languageMode: ConstrainedLanguage
inconclusiveWarning: false
notProcessedWarning: false

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

@ -0,0 +1,15 @@
# These are options for unit tests
suppression:
YAML.RuleWithAlias1:
- 'TestObject1'
PSRZZ.0001:
- 'TestObject1'
JSON.AlternativeName:
- 'TestObject2'
YAML.AlternativeName:
- 'TestObject2'
.\PSRZZ.0003:
- 'TestObject3'
'.\PS.AlternativeName':
- 'TestObject3'

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

@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
using PSRule.Definitions;
using PSRule.Rules;
using PSRule.Definitions.Rules;
using Xunit;
namespace PSRule

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

@ -24,9 +24,9 @@ namespace PSRule
var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, null, null, null, new OptionContext(), null), new TestWriter(GetOption()));
context.Init(GetSource());
context.Begin();
var rule = HostHelper.GetRuleYaml(GetSource(), context).ToArray();
var rule = HostHelper.GetRule(GetSource(), context, includeDependencies: false);
Assert.NotNull(rule);
Assert.Equal("BasicRule", rule[0].RuleName);
Assert.Equal("BasicRule", rule[0].Name);
var hashtable = rule[0].Tag.ToHashtable();
Assert.Equal("tag", hashtable["feature"]);
@ -120,6 +120,8 @@ namespace PSRule
Assert.True(yamlObjectPath.Condition.If().AllOf());
}
#region Helper methods
private static PSRuleOption GetOption()
{
return new PSRuleOption();
@ -149,7 +151,7 @@ namespace PSRule
private static RuleBlock GetRuleVisitor(RunspaceContext context, string name)
{
var block = HostHelper.GetRuleYamlBlocks(GetSource(), context);
return block.FirstOrDefault(s => s.RuleName == name);
return block.FirstOrDefault(s => s.Name == name);
}
private static void ImportSelectors(RunspaceContext context)
@ -163,5 +165,7 @@ namespace PSRule
{
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
}
#endregion Helper methods
}
}

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

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.IO;
using PSRule.Configuration;
using PSRule.Definitions;
using PSRule.Host;
using PSRule.Pipeline;
using PSRule.Rules;
using PSRule.Runtime;
using Xunit;
using Assert = Xunit.Assert;
namespace PSRule
{
public sealed class SuppressionFilterTests
{
[Fact]
public void Match()
{
var option = GetOption();
var context = new RunspaceContext(PipelineContext.New(option, null, null, null, null, null, new OptionContext(), null), new TestWriter(option));
context.Init(GetSource());
context.Begin();
var rules = HostHelper.GetRule(GetSource(), context, includeDependencies: false);
var filter = new SuppressionFilter(context, option.Suppression, rules);
Assert.True(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1"));
Assert.False(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1"));
Assert.True(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject1"));
Assert.True(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2"));
Assert.True(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2"));
Assert.False(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject2"));
Assert.False(filter.Match(new ResourceId(".", "YAML.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3"));
Assert.True(filter.Match(new ResourceId(".", "JSON.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3"));
Assert.True(filter.Match(new ResourceId(".", "PS.RuleWithAlias1", ResourceIdKind.Unknown), "TestObject3"));
}
#region Helper methods
private Source[] GetSource()
{
var builder = new SourcePipelineBuilder(null, null);
builder.Directory(GetSourcePath("FromFileAlias.Rule.yaml"));
builder.Directory(GetSourcePath("FromFileAlias.Rule.jsonc"));
builder.Directory(GetSourcePath("FromFileAlias.Rule.ps1"));
return builder.Build();
}
private static PSRuleOption GetOption(string path = "PSRule.Tests14.yml")
{
return PSRuleOption.FromFileOrEmpty(path);
}
private static string GetSourcePath(string fileName)
{
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
}
#endregion Helper methods
}
}