зеркало из https://github.com/microsoft/PSRule.git
Родитель
6d59f18be8
Коммит
85fa989a41
|
@ -20,7 +20,7 @@
|
|||
"no-reversed-links": true,
|
||||
"no-multiple-blanks": true,
|
||||
"line-length": {
|
||||
"line_length": 100,
|
||||
"line_length": 120,
|
||||
"code_blocks": false,
|
||||
"tables": false,
|
||||
"headers": true
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
"./schemas/PSRule-language.schema.json": [
|
||||
"/tests/PSRule.Tests/**.Rule.yaml",
|
||||
"/tests/PSRule.Tests/**/**.Rule.yaml",
|
||||
"/docs/scenarios/*/*.Rule.yaml"
|
||||
"/docs/scenarios/*/*.Rule.yaml",
|
||||
"/docs/expressions/**/*.Rule.yaml"
|
||||
]
|
||||
},
|
||||
"json.schemas": [
|
||||
|
@ -30,6 +31,7 @@
|
|||
"fileMatch": [
|
||||
"/tests/PSRule.Tests/**.Rule.jsonc",
|
||||
"/tests/PSRule.Tests/**/**.Rule.jsonc",
|
||||
"/docs/expressions/**/*.Rule.jsonc"
|
||||
],
|
||||
"url": "./schemas/PSRule-resources.schema.json"
|
||||
}
|
||||
|
|
|
@ -13,10 +13,26 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
|
|||
|
||||
**Experimental features**:
|
||||
|
||||
- Functions within YAML expressions can be used to perform manipulation prior to testing a condition.
|
||||
- Functions within YAML and JSON expressions can be used to perform manipulation prior to testing a condition.
|
||||
See [functions][3] for more information.
|
||||
- Sub-selectors within YAML and JSON expressions can be used to filter rules and list properties.
|
||||
See [sub-selectors][4] for more information.
|
||||
|
||||
[3]: expressions/functions.md
|
||||
[4]: expressions/sub-selectors.md
|
||||
|
||||
## Unreleased
|
||||
|
||||
What's changed since pre-release v2.4.0-B0039:
|
||||
|
||||
- New features:
|
||||
- **Experimental**: Added support for sub-selectors YAML and JSON expressions by @BernieWhite.
|
||||
[#1024](https://github.com/microsoft/PSRule/issues/1024)
|
||||
[#1045](https://github.com/microsoft/PSRule/issues/1045)
|
||||
- Sub-selector pre-conditions add an additional expression to determine if a rule is executed.
|
||||
- Sub-selector object filters provide an way to filter items from list properties.
|
||||
- See [sub-selectors][4] for more information.
|
||||
|
||||
## v2.4.0-B0039 (pre-release)
|
||||
|
||||
What's changed since pre-release v2.4.0-B0022:
|
||||
|
@ -27,6 +43,7 @@ What's changed since pre-release v2.4.0-B0022:
|
|||
- Added conversion functions `boolean`, `string`, and `integer`.
|
||||
- Added lookup functions `configuration`, and `path`.
|
||||
- Added string functions `concat`, `substring`.
|
||||
- See [functions][3] for more information.
|
||||
- Bug fixes:
|
||||
- Fixed reporting of duplicate identifiers which were not generating an error for all cases by @BernieWhite.
|
||||
[#1229](https://github.com/microsoft/PSRule/issues/1229)
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
[
|
||||
{
|
||||
// Synopsis: A rule with a sub-selector precondition.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "Json.Subselector.Precondition"
|
||||
},
|
||||
"spec": {
|
||||
"where": {
|
||||
"field": "kind",
|
||||
"equals": "api"
|
||||
},
|
||||
"condition": {
|
||||
"field": "resources",
|
||||
"count": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// Synopsis: A rule with a sub-selector filter.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "Json.Subselector.Filter"
|
||||
},
|
||||
"spec": {
|
||||
"condition": {
|
||||
"field": "resources",
|
||||
"where": {
|
||||
"type": ".",
|
||||
"equals": "Microsoft.Web/sites/config"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"field": "properties.detailedErrorLoggingEnabled",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// Synopsis: A rule with a sub-selector filter.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "Json.Subselector.FilterOr"
|
||||
},
|
||||
"spec": {
|
||||
"condition": {
|
||||
"anyOf": [
|
||||
{
|
||||
"field": "resources",
|
||||
"where": {
|
||||
"type": ".",
|
||||
"equals": "Microsoft.Web/sites/config"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"field": "properties.detailedErrorLoggingEnabled",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"field": "resources",
|
||||
"exists": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,57 @@
|
|||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
#
|
||||
# YAML-based rules for documentation
|
||||
#
|
||||
|
||||
---
|
||||
# Synopsis: A rule with a sub-selector precondition.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: Yaml.Subselector.Precondition
|
||||
spec:
|
||||
where:
|
||||
field: 'kind'
|
||||
equals: 'api'
|
||||
condition:
|
||||
field: resources
|
||||
count: 10
|
||||
|
||||
---
|
||||
# Synopsis: A rule with a sub-selector filter.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: Yaml.Subselector.Filter
|
||||
spec:
|
||||
condition:
|
||||
field: resources
|
||||
where:
|
||||
type: '.'
|
||||
equals: 'Microsoft.Web/sites/config'
|
||||
allOf:
|
||||
- field: properties.detailedErrorLoggingEnabled
|
||||
equals: true
|
||||
|
||||
---
|
||||
# Synopsis: A rule with a sub-selector filter.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: Yaml.Subselector.FilterOr
|
||||
spec:
|
||||
condition:
|
||||
anyOf:
|
||||
|
||||
- field: resources
|
||||
where:
|
||||
type: '.'
|
||||
equals: 'Microsoft.Web/sites/config'
|
||||
allOf:
|
||||
- field: properties.detailedErrorLoggingEnabled
|
||||
equals: true
|
||||
|
||||
- field: resources
|
||||
exists: false
|
|
@ -1,16 +1,22 @@
|
|||
# Expression functions
|
||||
# Functions
|
||||
|
||||
!!! Abstract
|
||||
Functions are an advanced lanaguage feature specific to YAML and JSON resources.
|
||||
_Functions_ are an advanced lanaguage feature specific to YAML and JSON expressions.
|
||||
That extend the language to allow for more complex use cases with expressions.
|
||||
Functions don't apply to script expressions because PowerShell already has rich support for complex manipulation.
|
||||
|
||||
!!! Experimental
|
||||
Functions are a work in progress and subject to change.
|
||||
_Functions_ are a work in progress and subject to change.
|
||||
We hope to add more functions, broader support, and more detailed documentation in the future.
|
||||
[Join or start a disucssion][1] to let us know how we can improve this feature going forward.
|
||||
|
||||
[1]: https://github.com/microsoft/PSRule/discussions
|
||||
|
||||
Functions cover two (2) main scenarios:
|
||||
|
||||
- **Transformation** — you need to perform minor transformation before a condition.
|
||||
- **Configuration** — you want to configure an input into a condition.
|
||||
|
||||
## Using functions
|
||||
|
||||
It may be necessary to perform minor transformation before evaluating a condition.
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
# Sub-selectors
|
||||
|
||||
!!! Abstract
|
||||
This topic covers _sub-selectors_ which are a PSRule language feature specific to YAML and JSON expressions.
|
||||
They are useful for filtering out objects that you do not want to evaluate.
|
||||
Sub-selectors don't apply to script expressions because PowerShell already has rich support for filtering.
|
||||
|
||||
!!! Experimental
|
||||
_Sub-selectors_ are a work in progress and subject to change.
|
||||
We hope to add broader support, and more detailed documentation in the future.
|
||||
[Join or start a disucssion][1] to let us know how we can improve this feature going forward.
|
||||
|
||||
[1]: https://github.com/microsoft/PSRule/discussions
|
||||
|
||||
Sub-selectors cover two (2) main scenarios:
|
||||
|
||||
- **Pre-conditions** — you want to filtering out objects before a rule is run.
|
||||
- **Object filtering** — you want to limit a condition to specific elements in a list of items.
|
||||
|
||||
## Pre-conditions
|
||||
|
||||
PSRule can process many different types of objects.
|
||||
Rules however, are normally written to test a specific property or type of object.
|
||||
So it is important that rules only run on objects that you want to evaluate.
|
||||
Pre-condition sub-selectors are one way you can determine if a rule should be run.
|
||||
|
||||
To use a sub-selector as a pre-condition, use the `where` property, directly under the `spec`.
|
||||
The expressions in the sub-selector follow the same form that you can use in rules.
|
||||
|
||||
For example:
|
||||
|
||||
=== "YAML"
|
||||
|
||||
```yaml hl_lines="8-10"
|
||||
---
|
||||
# Synopsis: A rule with a sub-selector precondition.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: Yaml.Subselector.Precondition
|
||||
spec:
|
||||
where:
|
||||
field: 'kind'
|
||||
equals: 'api'
|
||||
condition:
|
||||
field: resources
|
||||
count: 10
|
||||
```
|
||||
|
||||
=== "JSON"
|
||||
|
||||
```json hl_lines="9-12"
|
||||
{
|
||||
// Synopsis: A rule with a sub-selector precondition.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "Json.Subselector.Precondition"
|
||||
},
|
||||
"spec": {
|
||||
"where": {
|
||||
"field": "kind",
|
||||
"equals": "api"
|
||||
},
|
||||
"condition": {
|
||||
"field": "resources",
|
||||
"count": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the example:
|
||||
|
||||
1. The `where` property is the start of a sub-selector.
|
||||
2. The sub-selector checks if the `kind` property equals `api`.
|
||||
|
||||
The rule does not run if the:
|
||||
|
||||
- The object does not have a `kind` property. **OR**
|
||||
- The value of the `kind` property is not `api`.
|
||||
|
||||
!!! Tip
|
||||
Other types of pre-conditions also exist that allow you to filter based on type or by a shared selector.
|
||||
|
||||
## Object filter
|
||||
|
||||
When you are evaluating an object, you can use sub-selectors to limit the condition.
|
||||
This is helpful when dealing with properties that are a list of items.
|
||||
Properties that contain a list of items may contain a sub-set of items that you want to evaluate.
|
||||
|
||||
For example, the object may look like this:
|
||||
|
||||
=== "YAML"
|
||||
|
||||
```yaml
|
||||
name: app1
|
||||
type: Microsoft.Web/sites
|
||||
resources:
|
||||
- name: web
|
||||
type: Microsoft.Web/sites/config
|
||||
properties:
|
||||
detailedErrorLoggingEnabled: true
|
||||
```
|
||||
|
||||
=== "JSON"
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "app1",
|
||||
"type": "Microsoft.Web/sites",
|
||||
"resources": [
|
||||
{
|
||||
"name": "web",
|
||||
"type": "Microsoft.Web/sites/config",
|
||||
"properties": {
|
||||
"detailedErrorLoggingEnabled": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
A rule to test if any sub-resources with the `detailedErrorLoggingEnabled` set to `true` exist might look like this:
|
||||
|
||||
=== "YAML"
|
||||
|
||||
```yaml hl_lines="10-12"
|
||||
---
|
||||
# Synopsis: A rule with a sub-selector filter.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: Yaml.Subselector.Filter
|
||||
spec:
|
||||
condition:
|
||||
field: resources
|
||||
where:
|
||||
type: '.'
|
||||
equals: 'Microsoft.Web/sites/config'
|
||||
allOf:
|
||||
- field: properties.detailedErrorLoggingEnabled
|
||||
equals: true
|
||||
```
|
||||
|
||||
=== "JSON"
|
||||
|
||||
```json
|
||||
{
|
||||
// Synopsis: A rule with a sub-selector filter.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "Json.Subselector.Filter"
|
||||
},
|
||||
"spec": {
|
||||
"condition": {
|
||||
"field": "resources",
|
||||
"where": {
|
||||
"type": ".",
|
||||
"equals": "Microsoft.Web/sites/config"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"field": "properties.detailedErrorLoggingEnabled",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the example:
|
||||
|
||||
- If the array property `resources` exists, any items with a type of `Microsoft.Web/sites/config` are evaluated.
|
||||
- Each item must have the `properties.detailedErrorLoggingEnabled` property set to `true` to pass.
|
||||
- Items without the `properties.detailedErrorLoggingEnabled` property fail.
|
||||
- Items with the `properties.detailedErrorLoggingEnabled` property set to a value other then `true` fail.
|
||||
- If the `resources` property does not exist, the rule fails.
|
||||
- If the `resources` property exists but has 0 items of type `Microsoft.Web/sites/config`, the rule fails.
|
||||
- If the `resources` property exists and has any items of type `Microsoft.Web/sites/config` but any fail, the rule fails.
|
||||
- If the `resources` property exists and has any items of type `Microsoft.Web/sites/config` and all pass, the rule passes.
|
||||
|
||||
### When there are no results
|
||||
|
||||
Given the example, is important to understand what happens if:
|
||||
|
||||
- The `resources` property doesn't exist.
|
||||
- The `resources` property doesn't contain any items that match the sub-selector condition.
|
||||
|
||||
In either of these two cases, the sub-selector will return `false` and fail the rule.
|
||||
The rule fails because there is no secondary conditions that could be used instead.
|
||||
|
||||
If this was not the desired behavior, you could:
|
||||
|
||||
- Use a pre-condition to avoid running the rule.
|
||||
- Group the sub-selector into a `anyOf`, and provide a secondary condition.
|
||||
|
||||
For example:
|
||||
|
||||
=== "YAML"
|
||||
|
||||
```yaml hl_lines="9 11-14 19-20"
|
||||
---
|
||||
# Synopsis: A rule with a sub-selector filter.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: Yaml.Subselector.FilterOr
|
||||
spec:
|
||||
condition:
|
||||
anyOf:
|
||||
|
||||
- field: resources
|
||||
where:
|
||||
type: '.'
|
||||
equals: 'Microsoft.Web/sites/config'
|
||||
allOf:
|
||||
- field: properties.detailedErrorLoggingEnabled
|
||||
equals: true
|
||||
|
||||
- field: resources
|
||||
exists: false
|
||||
```
|
||||
|
||||
=== "JSON"
|
||||
|
||||
```json hl_lines="10 12-16 25-26"
|
||||
{
|
||||
// Synopsis: A rule with a sub-selector filter.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "Json.Subselector.FilterOr"
|
||||
},
|
||||
"spec": {
|
||||
"condition": {
|
||||
"anyOf": [
|
||||
{
|
||||
"field": "resources",
|
||||
"where": {
|
||||
"type": ".",
|
||||
"equals": "Microsoft.Web/sites/config"
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"field": "properties.detailedErrorLoggingEnabled",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"field": "resources",
|
||||
"exists": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the example:
|
||||
|
||||
- If the array property `resources` exists, any items with a type of `Microsoft.Web/sites/config` are evaluated.
|
||||
- Each item must have the `properties.detailedErrorLoggingEnabled` property set to `true` to pass.
|
||||
- Items without the `properties.detailedErrorLoggingEnabled` property fail.
|
||||
- Items with the `properties.detailedErrorLoggingEnabled` property set to a value other then `true` fail.
|
||||
- If the `resources` property does not exist, the rule passes.
|
||||
- If the `resources` property exists but has 0 items of type `Microsoft.Web/sites/config`, the rule fails.
|
||||
- If the `resources` property exists and has any items of type `Microsoft.Web/sites/config` but any fail, the rule fails.
|
||||
- If the `resources` property exists and has any items of type `Microsoft.Web/sites/config` and all pass, the rule passes.
|
|
@ -0,0 +1,37 @@
|
|||
# Changes and versioning
|
||||
|
||||
PSRule uses [semantic versioning][1] to declare breaking changes.
|
||||
The latest module version can be installed from the PowerShell Gallery.
|
||||
For a list of module changes please see the [change log][2].
|
||||
|
||||
[1]: https://semver.org/
|
||||
[2]: https://aka.ms/ps-rule/changelog
|
||||
|
||||
## Pre-releases
|
||||
|
||||
Pre-release module versions are created on major commits and can be installed from the PowerShell Gallery.
|
||||
Module versions and change log details for pre-releases will be removed as stable releases are made available.
|
||||
|
||||
!!! Important
|
||||
Pre-release versions should be considered work in progress.
|
||||
These releases should not be used in production.
|
||||
We may introduce breaking changes between a pre-release as we work towards a stable version release.
|
||||
|
||||
## Experimental features
|
||||
|
||||
From time to time we may ship experimential features.
|
||||
These features are generally marked experimential in the change log as these features ship.
|
||||
Experimental features may ship in stable releases, however to use them you may need to:
|
||||
|
||||
- Enabled or explictly reference them.
|
||||
|
||||
!!! Important
|
||||
Experimental features should be considered work in progress.
|
||||
These features may be incomplete and should not be used in production.
|
||||
We may introduce breaking changes for experimental features as we work towards a general release for the feature.
|
||||
|
||||
## Reporting bugs
|
||||
|
||||
If you experience an issue with an pre-release or experimental feature please let us know by logging an issue as a [bug][3].
|
||||
|
||||
[3]: https://github.com/microsoft/PSRule/issues
|
|
@ -29,6 +29,7 @@ theme:
|
|||
level: 1
|
||||
- navigation.tabs
|
||||
- content.code.annotate
|
||||
- content.tabs.link
|
||||
|
||||
nav:
|
||||
- Home: index.md
|
||||
|
@ -55,8 +56,9 @@ nav:
|
|||
- Azure resource tagging example: scenarios/azure-tags/azure-tags.md
|
||||
- Kubernetes resource validation example: scenarios/kubernetes-resources/kubernetes-resources.md
|
||||
- Concepts:
|
||||
- Expressions:
|
||||
- Functions: expressions/functions.md
|
||||
- Functions: expressions/functions.md
|
||||
- Sub-selectors: expressions/sub-selectors.md
|
||||
- Scenarios:
|
||||
- Using within continuous integration: scenarios/validation-pipeline/validation-pipeline.md
|
||||
# - Troubleshooting: troubleshooting.md
|
||||
- License and contributing: license-contributing.md
|
||||
|
@ -68,6 +70,7 @@ nav:
|
|||
- v0: 'CHANGELOG-v0.md'
|
||||
- Upgrade notes: upgrade-notes.md
|
||||
- Deprecations: deprecations.md
|
||||
- Changes and versioning: versioning.md
|
||||
- Support: support.md
|
||||
# - Setup:
|
||||
# - Configuring options: setup/configuring-options.md
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using PSRule.Definitions;
|
||||
|
@ -9,12 +9,17 @@ namespace PSRule
|
|||
{
|
||||
public static bool AllOf(this IConditionResult result)
|
||||
{
|
||||
return result.Count > 0 && result.Pass == result.Count;
|
||||
return result != null && result.Count > 0 && result.Pass == result.Count;
|
||||
}
|
||||
|
||||
public static bool AnyOf(this IConditionResult result)
|
||||
{
|
||||
return result.Pass > 0;
|
||||
return result != null && result.Pass > 0;
|
||||
}
|
||||
|
||||
public static bool Skipped(this IConditionResult result)
|
||||
{
|
||||
return result == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -645,7 +645,7 @@ namespace PSRule
|
|||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
var expression = MapOperator(OPERATOR_IF, reader);
|
||||
var expression = MapOperator(OPERATOR_IF, null, null, reader);
|
||||
return new LanguageIf(expression);
|
||||
}
|
||||
|
||||
|
@ -657,40 +657,44 @@ namespace PSRule
|
|||
/// <summary>
|
||||
/// Map an operator.
|
||||
/// </summary>
|
||||
private LanguageExpression MapOperator(string type, JsonReader reader)
|
||||
private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, JsonReader reader)
|
||||
{
|
||||
if (TryExpression(type, out LanguageOperator result))
|
||||
if (TryExpression(type, properties, out LanguageOperator result))
|
||||
{
|
||||
// If and Not
|
||||
if (reader.TokenType == JsonToken.StartObject)
|
||||
if (reader.TryConsume(JsonToken.StartObject))
|
||||
{
|
||||
result.Add(MapExpression(reader));
|
||||
reader.Read();
|
||||
//reader.Consume(JsonToken.EndObject);
|
||||
}
|
||||
// AllOf and AnyOf
|
||||
else if (reader.TokenType == JsonToken.StartArray && reader.Read())
|
||||
else if (reader.TryConsume(JsonToken.StartArray))
|
||||
{
|
||||
while (reader.TokenType != JsonToken.EndArray)
|
||||
{
|
||||
if (reader.SkipComments(out var hasComments) && hasComments)
|
||||
continue;
|
||||
|
||||
result.Add(MapExpression(reader));
|
||||
reader.Read();
|
||||
if (reader.TryConsume(JsonToken.StartObject))
|
||||
{
|
||||
result.Add(MapExpression(reader));
|
||||
reader.Consume(JsonToken.EndObject);
|
||||
}
|
||||
}
|
||||
reader.Read();
|
||||
reader.Consume(JsonToken.EndArray);
|
||||
}
|
||||
result.Subselector = subselector;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private LanguageExpression MapCondition(string type, LanguageExpression.PropertyBag properties, JsonReader reader)
|
||||
{
|
||||
if (TryExpression(type, out LanguageCondition result))
|
||||
if (TryExpression(type, null, out LanguageCondition result))
|
||||
{
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
{
|
||||
MapProperty(properties, reader, out _);
|
||||
MapProperty(properties, reader, out _, out _);
|
||||
}
|
||||
result.Add(properties);
|
||||
}
|
||||
|
@ -701,7 +705,7 @@ namespace PSRule
|
|||
{
|
||||
LanguageExpression result = null;
|
||||
var properties = new LanguageExpression.PropertyBag();
|
||||
MapProperty(properties, reader, out var key);
|
||||
MapProperty(properties, reader, out var key, out var subselector);
|
||||
if (key != null && TryCondition(key))
|
||||
{
|
||||
result = MapCondition(key, properties, reader);
|
||||
|
@ -709,7 +713,12 @@ namespace PSRule
|
|||
else if ((reader.TokenType == JsonToken.StartObject || reader.TokenType == JsonToken.StartArray) &&
|
||||
TryOperator(key))
|
||||
{
|
||||
result = MapOperator(key, reader);
|
||||
var op = MapOperator(key, properties, subselector, reader);
|
||||
MapProperty(properties, reader, out _, out subselector);
|
||||
if (subselector != null)
|
||||
op.Subselector = subselector;
|
||||
|
||||
result = op;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -766,13 +775,14 @@ namespace PSRule
|
|||
return result.ToArray();
|
||||
}
|
||||
|
||||
private void MapProperty(LanguageExpression.PropertyBag properties, JsonReader reader, out string name)
|
||||
private void MapProperty(LanguageExpression.PropertyBag properties, JsonReader reader, out string name, out LanguageExpression subselector)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject || !reader.Read())
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
//if (reader.TokenType != JsonToken.StartObject || !reader.Read())
|
||||
// throw new PipelineSerializationException(PSRuleResources.ReadJsonFailedExpectedToken, Enum.GetName(typeof(JsonToken), reader.TokenType));
|
||||
|
||||
name = null;
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
subselector = null;
|
||||
while (reader.TokenType == JsonToken.PropertyName)
|
||||
{
|
||||
var key = reader.Value.ToString();
|
||||
if (TryCondition(key) || TryOperator(key))
|
||||
|
@ -780,18 +790,29 @@ namespace PSRule
|
|||
|
||||
if (reader.Read())
|
||||
{
|
||||
// value:
|
||||
if (TryValue(key, reader, out var value))
|
||||
{
|
||||
properties[key] = value;
|
||||
reader.Read();
|
||||
}
|
||||
else if (TryCondition(key) && reader.TryConsume(JsonToken.StartObject))
|
||||
{
|
||||
if (TryFunction(reader, key, out var fn))
|
||||
properties.Add(key, fn);
|
||||
|
||||
reader.Consume(JsonToken.EndObject);
|
||||
}
|
||||
// where:
|
||||
else if (TrySubSelector(key) && reader.TryConsume(JsonToken.StartObject))
|
||||
{
|
||||
subselector = MapExpression(reader);
|
||||
reader.Consume(JsonToken.EndObject);
|
||||
}
|
||||
else if (reader.TokenType == JsonToken.StartObject)
|
||||
{
|
||||
break;
|
||||
|
||||
}
|
||||
else if (reader.TokenType == JsonToken.StartArray)
|
||||
{
|
||||
if (!TryCondition(key))
|
||||
|
@ -808,17 +829,22 @@ namespace PSRule
|
|||
objects.Add(item);
|
||||
}
|
||||
properties.Add(key, objects.ToArray());
|
||||
reader.Consume(JsonToken.EndArray);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
properties.Add(key, reader.Value);
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySubSelector(string key)
|
||||
{
|
||||
return _Factory.IsSubselector(key);
|
||||
}
|
||||
|
||||
private bool TryOperator(string key)
|
||||
{
|
||||
return _Factory.IsOperator(key);
|
||||
|
@ -867,7 +893,7 @@ namespace PSRule
|
|||
s == "$";
|
||||
}
|
||||
|
||||
private bool TryExpression<T>(string type, out T expression) where T : LanguageExpression
|
||||
private bool TryExpression<T>(string type, LanguageExpression.PropertyBag properties, out T expression) where T : LanguageExpression
|
||||
{
|
||||
expression = null;
|
||||
|
||||
|
@ -875,7 +901,7 @@ namespace PSRule
|
|||
{
|
||||
expression = (T)descriptor.CreateInstance(
|
||||
source: RunspaceContext.CurrentThread.Source.File,
|
||||
properties: null
|
||||
properties: properties
|
||||
);
|
||||
|
||||
return expression != null;
|
||||
|
|
|
@ -34,11 +34,6 @@ namespace PSRule
|
|||
: PSObject.AsPSObject(o.Properties[propertyName].Value);
|
||||
}
|
||||
|
||||
public static string ValueAsString(this PSObject o, string propertyName, bool caseSensitive)
|
||||
{
|
||||
return ObjectHelper.GetPath(o, propertyName, caseSensitive, out var value) && value != null ? value.ToString() : null;
|
||||
}
|
||||
|
||||
public static bool HasProperty(this PSObject o, string propertyName)
|
||||
{
|
||||
return o.Properties[propertyName] != null;
|
||||
|
|
|
@ -620,7 +620,7 @@ namespace PSRule
|
|||
{
|
||||
if (typeof(LanguageExpression).IsAssignableFrom(expectedType))
|
||||
{
|
||||
var resource = MapOperator(OPERATOR_IF, reader, nestedObjectDeserializer);
|
||||
var resource = MapOperator(OPERATOR_IF, null, null, reader, nestedObjectDeserializer);
|
||||
value = new LanguageIf(resource);
|
||||
return true;
|
||||
}
|
||||
|
@ -633,9 +633,9 @@ namespace PSRule
|
|||
/// <summary>
|
||||
/// Map an operator.
|
||||
/// </summary>
|
||||
private LanguageExpression MapOperator(string type, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer)
|
||||
private LanguageOperator MapOperator(string type, LanguageExpression.PropertyBag properties, LanguageExpression subselector, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer)
|
||||
{
|
||||
if (TryExpression(reader, type, nestedObjectDeserializer, out LanguageOperator result))
|
||||
if (TryExpression(reader, type, properties, nestedObjectDeserializer, out LanguageOperator result))
|
||||
{
|
||||
// If and Not
|
||||
if (reader.TryConsume<MappingStart>(out _))
|
||||
|
@ -656,17 +656,18 @@ namespace PSRule
|
|||
reader.Require<SequenceEnd>();
|
||||
reader.MoveNext();
|
||||
}
|
||||
result.Subselector = subselector;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private LanguageExpression MapCondition(string type, LanguageExpression.PropertyBag properties, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer)
|
||||
{
|
||||
if (TryExpression(reader, type, nestedObjectDeserializer, out LanguageCondition result))
|
||||
if (TryExpression(reader, type, null, nestedObjectDeserializer, out LanguageCondition result))
|
||||
{
|
||||
while (!reader.Accept(out MappingEnd end))
|
||||
{
|
||||
MapProperty(properties, reader, nestedObjectDeserializer, out _);
|
||||
MapProperty(properties, reader, nestedObjectDeserializer, out _, out _);
|
||||
}
|
||||
result.Add(properties);
|
||||
}
|
||||
|
@ -677,18 +678,25 @@ namespace PSRule
|
|||
{
|
||||
LanguageExpression result = null;
|
||||
var properties = new LanguageExpression.PropertyBag();
|
||||
MapProperty(properties, reader, nestedObjectDeserializer, out var key);
|
||||
MapProperty(properties, reader, nestedObjectDeserializer, out var key, out var subselector);
|
||||
if (key != null && TryCondition(key))
|
||||
{
|
||||
result = MapCondition(key, properties, reader, nestedObjectDeserializer);
|
||||
}
|
||||
else if (TryOperator(key) && reader.Accept<MappingStart>(out _))
|
||||
{
|
||||
result = MapOperator(key, reader, nestedObjectDeserializer);
|
||||
var op = MapOperator(key, properties, subselector, reader, nestedObjectDeserializer);
|
||||
MapProperty(properties, reader, nestedObjectDeserializer, out _, out _);
|
||||
result = op;
|
||||
}
|
||||
else if (TryOperator(key) && reader.Accept<SequenceStart>(out _))
|
||||
{
|
||||
result = MapOperator(key, reader, nestedObjectDeserializer);
|
||||
var op = MapOperator(key, properties, subselector, reader, nestedObjectDeserializer);
|
||||
MapProperty(properties, reader, nestedObjectDeserializer, out _, out subselector);
|
||||
if (subselector != null)
|
||||
op.Subselector = subselector;
|
||||
|
||||
result = op;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -754,9 +762,10 @@ namespace PSRule
|
|||
return result.ToArray();
|
||||
}
|
||||
|
||||
private void MapProperty(LanguageExpression.PropertyBag properties, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer, out string name)
|
||||
private void MapProperty(LanguageExpression.PropertyBag properties, IParser reader, Func<IParser, Type, object> nestedObjectDeserializer, out string name, out LanguageExpression subselector)
|
||||
{
|
||||
name = null;
|
||||
subselector = null;
|
||||
while (reader.TryConsume(out Scalar scalar))
|
||||
{
|
||||
var key = scalar.Value;
|
||||
|
@ -767,6 +776,7 @@ namespace PSRule
|
|||
{
|
||||
properties[key] = scalar.Value;
|
||||
}
|
||||
// value:
|
||||
else if (TryValue(key, reader, nestedObjectDeserializer, out var value))
|
||||
{
|
||||
properties[key] = value;
|
||||
|
@ -788,9 +798,20 @@ namespace PSRule
|
|||
}
|
||||
properties[key] = objects.ToArray();
|
||||
}
|
||||
// where:
|
||||
else if (TrySubSelector(key) && reader.TryConsume<MappingStart>(out _))
|
||||
{
|
||||
subselector = MapExpression(reader, nestedObjectDeserializer);
|
||||
reader.Consume<MappingEnd>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySubSelector(string key)
|
||||
{
|
||||
return _Factory.IsSubselector(key);
|
||||
}
|
||||
|
||||
private bool TryOperator(string key)
|
||||
{
|
||||
return _Factory.IsOperator(key);
|
||||
|
@ -835,12 +856,12 @@ namespace PSRule
|
|||
return reader.Accept<Scalar>(out var scalar) || scalar.Value == "$";
|
||||
}
|
||||
|
||||
private bool TryExpression<T>(IParser reader, string type, Func<IParser, Type, object> nestedObjectDeserializer, out T expression) where T : LanguageExpression
|
||||
private bool TryExpression<T>(IParser reader, string type, LanguageExpression.PropertyBag properties, Func<IParser, Type, object> nestedObjectDeserializer, out T expression) where T : LanguageExpression
|
||||
{
|
||||
expression = null;
|
||||
if (_Factory.TryDescriptor(type, out var descriptor))
|
||||
{
|
||||
expression = (T)descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, null);
|
||||
expression = (T)descriptor.CreateInstance(RunspaceContext.CurrentThread.Source.File, properties);
|
||||
return expression != null;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace PSRule.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Used by custom binding functions.
|
||||
/// </summary>
|
||||
public delegate string BindTargetName(PSObject targetObject);
|
||||
public delegate string BindTargetName(object targetObject);
|
||||
|
||||
internal delegate string BindTargetMethod(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path);
|
||||
internal delegate string BindTargetFunc(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, BindTargetMethod next, out string path);
|
||||
internal delegate string BindTargetMethod(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path);
|
||||
internal delegate string BindTargetFunc(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path);
|
||||
|
||||
/// <summary>
|
||||
/// Hooks that provide customize pipeline execution.
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace PSRule.Definitions.Expressions
|
|||
|
||||
object Current { get; }
|
||||
|
||||
RunspaceContext GetContext();
|
||||
RunspaceContext Context { get; }
|
||||
}
|
||||
|
||||
internal sealed class ExpressionContext : IExpressionContext, IBindingContext
|
||||
|
@ -27,8 +27,9 @@ namespace PSRule.Definitions.Expressions
|
|||
|
||||
private List<ResultReason> _Reason;
|
||||
|
||||
internal ExpressionContext(SourceFile source, ResourceKind kind, object current)
|
||||
internal ExpressionContext(RunspaceContext context, SourceFile source, ResourceKind kind, object current)
|
||||
{
|
||||
Context = context;
|
||||
Source = source;
|
||||
LanguageScope = source.Module;
|
||||
Kind = kind;
|
||||
|
@ -44,6 +45,8 @@ namespace PSRule.Definitions.Expressions
|
|||
|
||||
public object Current { get; }
|
||||
|
||||
public RunspaceContext Context { get; }
|
||||
|
||||
[DebuggerStepThrough]
|
||||
void IBindingContext.CachePathExpression(string path, PathExpression expression)
|
||||
{
|
||||
|
@ -81,7 +84,7 @@ namespace PSRule.Definitions.Expressions
|
|||
return;
|
||||
|
||||
_Reason ??= new List<ResultReason>();
|
||||
_Reason.Add(new ResultReason(RunspaceContext.CurrentThread?.TargetObject?.Path, operand, text, args));
|
||||
_Reason.Add(new ResultReason(Context.TargetObject?.Path, operand, text, args));
|
||||
}
|
||||
|
||||
public void Reason(string text, params object[] args)
|
||||
|
@ -90,17 +93,12 @@ namespace PSRule.Definitions.Expressions
|
|||
return;
|
||||
|
||||
_Reason ??= new List<ResultReason>();
|
||||
_Reason.Add(new ResultReason(RunspaceContext.CurrentThread?.TargetObject?.Path, null, text, args));
|
||||
_Reason.Add(new ResultReason(Context.TargetObject?.Path, null, text, args));
|
||||
}
|
||||
|
||||
internal ResultReason[] GetReasons()
|
||||
{
|
||||
return _Reason == null || _Reason.Count == 0 ? Array.Empty<ResultReason>() : _Reason.ToArray();
|
||||
}
|
||||
|
||||
public RunspaceContext GetContext()
|
||||
{
|
||||
return RunspaceContext.CurrentThread;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ namespace PSRule.Definitions.Expressions
|
|||
// Lookup a configuration value.
|
||||
return (context) =>
|
||||
{
|
||||
return context.GetContext().TryGetConfigurationValue(name, out var value) ? value : null;
|
||||
return context.Context.TryGetConfigurationValue(name, out var value) ? value : null;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -54,6 +54,11 @@ namespace PSRule.Definitions.Expressions
|
|||
_Descriptors.TryGetValue(name, out descriptor);
|
||||
}
|
||||
|
||||
public bool IsSubselector(string name)
|
||||
{
|
||||
return name == "where";
|
||||
}
|
||||
|
||||
public bool IsOperator(string name)
|
||||
{
|
||||
return TryDescriptor(name, out var d) && d != null && d.Type == LanguageExpressionType.Operator;
|
||||
|
@ -80,12 +85,14 @@ namespace PSRule.Definitions.Expressions
|
|||
{
|
||||
private const char Dot = '.';
|
||||
private const char OpenBracket = '[';
|
||||
private const char CloseBracket = '[';
|
||||
private const char CloseBracket = ']';
|
||||
private const string Where = ".where";
|
||||
|
||||
private readonly bool _Debugger;
|
||||
|
||||
private string[] _With;
|
||||
private string[] _Type;
|
||||
private LanguageExpression _When;
|
||||
private string[] _Rule;
|
||||
|
||||
public LanguageExpressionBuilder(bool debugger = true)
|
||||
|
@ -111,6 +118,15 @@ namespace PSRule.Definitions.Expressions
|
|||
return this;
|
||||
}
|
||||
|
||||
public LanguageExpressionBuilder WithSubselector(LanguageIf subselector)
|
||||
{
|
||||
if (subselector == null || subselector.Expression == null)
|
||||
return this;
|
||||
|
||||
_When = subselector.Expression;
|
||||
return this;
|
||||
}
|
||||
|
||||
public LanguageExpressionBuilder WithRule(string[] rule)
|
||||
{
|
||||
if (rule == null || rule.Length == 0)
|
||||
|
@ -120,12 +136,12 @@ namespace PSRule.Definitions.Expressions
|
|||
return this;
|
||||
}
|
||||
|
||||
public LanguageExpressionOuterFn Build(LanguageIf selectorIf)
|
||||
public LanguageExpressionOuterFn Build(LanguageIf condition)
|
||||
{
|
||||
return Precondition(Expression(string.Empty, selectorIf.Expression), _With, _Type, _Rule);
|
||||
return Precondition(Expression(string.Empty, condition.Expression), _With, _Type, Expression(string.Empty, _When), _Rule);
|
||||
}
|
||||
|
||||
private static LanguageExpressionOuterFn Precondition(LanguageExpressionOuterFn expression, string[] with, string[] type, string[] rule)
|
||||
private static LanguageExpressionOuterFn Precondition(LanguageExpressionOuterFn expression, string[] with, string[] type, LanguageExpressionOuterFn when, string[] rule)
|
||||
{
|
||||
var fn = expression;
|
||||
if (type != null)
|
||||
|
@ -134,6 +150,9 @@ namespace PSRule.Definitions.Expressions
|
|||
if (with != null)
|
||||
fn = PreconditionSelector(with, fn);
|
||||
|
||||
if (when != null)
|
||||
fn = PreconditionSubselector(when, fn);
|
||||
|
||||
if (rule != null)
|
||||
fn = PreconditionRule(rule, fn);
|
||||
|
||||
|
@ -182,8 +201,25 @@ namespace PSRule.Definitions.Expressions
|
|||
};
|
||||
}
|
||||
|
||||
private static LanguageExpressionOuterFn PreconditionSubselector(LanguageExpressionOuterFn subselector, LanguageExpressionOuterFn fn)
|
||||
{
|
||||
return (context, o) =>
|
||||
{
|
||||
// Evalute sub-selector pre-condition
|
||||
if (!AcceptsSubselector(context, subselector, o))
|
||||
{
|
||||
context.Debug(PSRuleResources.DebugTargetSubselectorMismatch);
|
||||
return null;
|
||||
}
|
||||
return fn(context, o);
|
||||
};
|
||||
}
|
||||
|
||||
private LanguageExpressionOuterFn Expression(string path, LanguageExpression expression)
|
||||
{
|
||||
if (expression == null)
|
||||
return null;
|
||||
|
||||
path = Path(path, expression);
|
||||
if (expression is LanguageOperator selectorOperator)
|
||||
return Scope(Debugger(Operator(path, selectorOperator), path));
|
||||
|
@ -226,7 +262,38 @@ namespace PSRule.Definitions.Expressions
|
|||
}
|
||||
var innerA = inner.ToArray();
|
||||
var info = new ExpressionInfo(path);
|
||||
return (context, o) => expression.Descriptor.Fn(context, info, innerA, o);
|
||||
|
||||
// Check for sub-selectors
|
||||
if (expression.Property == null || expression.Property.Count == 0)
|
||||
{
|
||||
return (context, o) => expression.Descriptor.Fn(context, info, innerA, o);
|
||||
}
|
||||
else
|
||||
{
|
||||
var subselector = expression.Subselector != null ? Expression(string.Concat(path, Where), expression.Subselector) : null;
|
||||
return (context, o) =>
|
||||
{
|
||||
if (!ObjectHelper.GetPath(context, o, Value<string>(context, expression.Property["field"]), caseSensitive: false, out object[] items) ||
|
||||
items == null || items.Length == 0)
|
||||
return false;
|
||||
|
||||
// If any fail, all fail
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
if (subselector == null || subselector(context, items[i]).GetValueOrDefault(true))
|
||||
{
|
||||
if (!expression.Descriptor.Fn(context, info, innerA, items[i]))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string Value<T>(ExpressionContext context, object v)
|
||||
{
|
||||
return v as string;
|
||||
}
|
||||
|
||||
private LanguageExpressionOuterFn Debugger(LanguageExpressionOuterFn expression, string path)
|
||||
|
@ -273,6 +340,11 @@ namespace PSRule.Definitions.Expressions
|
|||
return false;
|
||||
}
|
||||
|
||||
private static bool AcceptsSubselector(ExpressionContext context, LanguageExpressionOuterFn subselector, object o)
|
||||
{
|
||||
return subselector == null || subselector.Invoke(context, o).GetValueOrDefault(false);
|
||||
}
|
||||
|
||||
private static bool AcceptsRule(string[] rule)
|
||||
{
|
||||
if (rule == null || rule.Length == 0)
|
||||
|
@ -1520,38 +1592,36 @@ namespace PSRule.Definitions.Expressions
|
|||
return operand != null || NotHasField(context, field);
|
||||
}
|
||||
|
||||
private static bool TryName(IExpressionContext context, LanguageExpression.PropertyBag properties, out IOperand operand)
|
||||
private static bool TryName(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand)
|
||||
{
|
||||
operand = null;
|
||||
if (properties.TryGetString(NAME, out var svalue))
|
||||
{
|
||||
if (svalue != ".")
|
||||
if (svalue != "." || context?.Context?.LanguageScope == null)
|
||||
return Invalid(context, svalue);
|
||||
|
||||
var binding = context.GetContext()?.TargetBinder?.Using(context.LanguageScope);
|
||||
var name = binding?.TargetName;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
if (!context.Context.LanguageScope.TryGetName(o, out var name, out var path) ||
|
||||
string.IsNullOrEmpty(name))
|
||||
return Invalid(context, svalue);
|
||||
|
||||
operand = Operand.FromName(name, binding.TargetNamePath);
|
||||
operand = Operand.FromName(name, path);
|
||||
}
|
||||
return operand != null;
|
||||
}
|
||||
|
||||
private static bool TryType(IExpressionContext context, LanguageExpression.PropertyBag properties, out IOperand operand)
|
||||
private static bool TryType(IExpressionContext context, LanguageExpression.PropertyBag properties, object o, out IOperand operand)
|
||||
{
|
||||
operand = null;
|
||||
if (properties.TryGetString(TYPE, out var svalue))
|
||||
{
|
||||
if (svalue != ".")
|
||||
if (svalue != "." || context?.Context?.LanguageScope == null)
|
||||
return Invalid(context, svalue);
|
||||
|
||||
var binding = context.GetContext()?.TargetBinder?.Using(context.LanguageScope);
|
||||
var type = binding?.TargetType;
|
||||
if (string.IsNullOrEmpty(type))
|
||||
if (!context.Context.LanguageScope.TryGetType(o, out var type, out var path) ||
|
||||
string.IsNullOrEmpty(type))
|
||||
return Invalid(context, svalue);
|
||||
|
||||
operand = Operand.FromType(type, binding.TargetTypePath);
|
||||
operand = Operand.FromType(type, path);
|
||||
}
|
||||
return operand != null;
|
||||
}
|
||||
|
@ -1561,7 +1631,7 @@ namespace PSRule.Definitions.Expressions
|
|||
operand = null;
|
||||
if (properties.TryGetString(SOURCE, out var sourceValue))
|
||||
{
|
||||
var source = context?.GetContext()?.TargetObject?.Source[sourceValue];
|
||||
var source = context?.Context?.TargetObject?.Source[sourceValue];
|
||||
if (source == null)
|
||||
return Invalid(context, sourceValue);
|
||||
|
||||
|
@ -1627,8 +1697,8 @@ namespace PSRule.Definitions.Expressions
|
|||
private static bool TryOperand(ExpressionContext context, string name, object o, LanguageExpression.PropertyBag properties, out IOperand operand)
|
||||
{
|
||||
return TryField(context, properties, o, out operand) ||
|
||||
TryType(context, properties, out operand) ||
|
||||
TryName(context, properties, out operand) ||
|
||||
TryType(context, properties, o, out operand) ||
|
||||
TryName(context, properties, o, out operand) ||
|
||||
TrySource(context, properties, out operand) ||
|
||||
TryValue(context, properties, out operand) ||
|
||||
Invalid(context, name);
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace PSRule.Definitions.Expressions
|
|||
public LanguageExpression CreateInstance(SourceFile source, LanguageExpression.PropertyBag properties)
|
||||
{
|
||||
if (Type == LanguageExpressionType.Operator)
|
||||
return new LanguageOperator(this);
|
||||
return new LanguageOperator(this, properties);
|
||||
|
||||
if (Type == LanguageExpressionType.Condition)
|
||||
return new LanguageCondition(this, properties);
|
||||
|
@ -74,12 +74,17 @@ namespace PSRule.Definitions.Expressions
|
|||
[DebuggerDisplay("Selector {Descriptor.Name}")]
|
||||
internal sealed class LanguageOperator : LanguageExpression
|
||||
{
|
||||
internal LanguageOperator(LanguageExpresssionDescriptor descriptor)
|
||||
internal LanguageOperator(LanguageExpresssionDescriptor descriptor, PropertyBag properties)
|
||||
: base(descriptor)
|
||||
{
|
||||
Property = properties ?? new PropertyBag();
|
||||
Children = new List<LanguageExpression>();
|
||||
}
|
||||
|
||||
public LanguageExpression Subselector { get; set; }
|
||||
|
||||
public PropertyBag Property { get; }
|
||||
|
||||
public List<LanguageExpression> Children { get; }
|
||||
|
||||
public void Add(LanguageExpression item)
|
||||
|
@ -109,9 +114,6 @@ namespace PSRule.Definitions.Expressions
|
|||
internal sealed class LanguageFunction : LanguageExpression
|
||||
{
|
||||
internal LanguageFunction(LanguageExpresssionDescriptor descriptor)
|
||||
: base(descriptor)
|
||||
{
|
||||
|
||||
}
|
||||
: base(descriptor) { }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,33 @@ using PSRule.Pipeline;
|
|||
|
||||
namespace PSRule.Definitions
|
||||
{
|
||||
/// <summary>
|
||||
/// A language block.
|
||||
/// </summary>
|
||||
public interface ILanguageBlock
|
||||
{
|
||||
/// <summary>
|
||||
/// The unique identifier for the block.
|
||||
/// </summary>
|
||||
ResourceId Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete. The source file path.
|
||||
/// Replaced by <see cref="Source"/>.
|
||||
/// </summary>
|
||||
[Obsolete("Use Source property instead.")]
|
||||
string SourcePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete. The source module.
|
||||
/// Replaced by <see cref="Source"/>.
|
||||
/// </summary>
|
||||
[Obsolete("Use Source property instead.")]
|
||||
string Module { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The source location for the block.
|
||||
/// </summary>
|
||||
SourceFile Source { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,8 +18,14 @@ using YamlDotNet.Serialization.NodeDeserializers;
|
|||
|
||||
namespace PSRule.Definitions
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of resource.
|
||||
/// </summary>
|
||||
public enum ResourceKind
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown or empty.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
|
@ -53,14 +59,26 @@ namespace PSRule.Definitions
|
|||
SuppressionGroup = 6
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Additional flags that indicate the status of the resource.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ResourceFlags
|
||||
{
|
||||
/// <summary>
|
||||
/// No flags are set.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// The resource is obsolete.
|
||||
/// </summary>
|
||||
Obsolete = 1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A resource langange block.
|
||||
/// </summary>
|
||||
public interface IResource : ILanguageBlock
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -142,6 +160,9 @@ namespace PSRule.Definitions
|
|||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A resource object.
|
||||
/// </summary>
|
||||
public sealed class ResourceObject
|
||||
{
|
||||
internal ResourceObject(IResource block)
|
||||
|
@ -196,15 +217,24 @@ namespace PSRule.Definitions
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Additional resource annotations.
|
||||
/// </summary>
|
||||
public sealed class ResourceAnnotations : Dictionary<string, object>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Additional resource tags.
|
||||
/// </summary>
|
||||
public sealed class ResourceTags : Dictionary<string, string>
|
||||
{
|
||||
private Hashtable _Hashtable;
|
||||
|
||||
/// <summary>
|
||||
/// Create an empty set of resource tags.
|
||||
/// </summary>
|
||||
public ResourceTags() : base(StringComparer.OrdinalIgnoreCase) { }
|
||||
|
||||
/// <summary>
|
||||
|
@ -287,6 +317,10 @@ namespace PSRule.Definitions
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the resourecs tags to a display string for PowerShell views.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string ToViewString()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
@ -308,8 +342,14 @@ namespace PSRule.Definitions
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Additional resource metadata.
|
||||
/// </summary>
|
||||
public sealed class ResourceMetadata
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an empty set of metadata.
|
||||
/// </summary>
|
||||
public ResourceMetadata()
|
||||
{
|
||||
Annotations = new ResourceAnnotations();
|
||||
|
@ -321,8 +361,14 @@ namespace PSRule.Definitions
|
|||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A opaque reference for the resource.
|
||||
/// </summary>
|
||||
public string Ref { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional aliases for the resource.
|
||||
/// </summary>
|
||||
public string[] Alias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -338,16 +384,32 @@ namespace PSRule.Definitions
|
|||
public ResourceTags Tags { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The source location of the resource.
|
||||
/// </summary>
|
||||
public sealed class ResourceExtent
|
||||
{
|
||||
/// <summary>
|
||||
/// The file where the resource is located.
|
||||
/// </summary>
|
||||
public string File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the module if the resource is contained within a module.
|
||||
/// </summary>
|
||||
public string Module { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A base class for resources.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSpec">The type for the resource specification.</typeparam>
|
||||
[DebuggerDisplay("Kind = {Kind}, Id = {Id}")]
|
||||
public abstract class Resource<TSpec> where TSpec : Spec, new()
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a resource.
|
||||
/// </summary>
|
||||
internal protected Resource(ResourceKind kind, string apiVersion, SourceFile source, ResourceMetadata metadata, IResourceHelpInfo info, ISourceExtent extent, TSpec spec)
|
||||
{
|
||||
Kind = kind;
|
||||
|
@ -361,6 +423,9 @@ namespace PSRule.Definitions
|
|||
Id = new ResourceId(source.Module, Name, ResourceIdKind.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The resource identifier for the resource.
|
||||
/// </summary>
|
||||
[YamlIgnore()]
|
||||
public ResourceId Id { get; }
|
||||
|
||||
|
@ -376,6 +441,9 @@ namespace PSRule.Definitions
|
|||
[YamlIgnore()]
|
||||
public SourceFile Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Information about the resource.
|
||||
/// </summary>
|
||||
[YamlIgnore()]
|
||||
public IResourceHelpInfo Info { get; }
|
||||
|
||||
|
|
|
@ -14,17 +14,36 @@ namespace PSRule.Definitions.Rules
|
|||
/// </summary>
|
||||
public enum SeverityLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// Severity is unset.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// A failure generates an error.
|
||||
/// </summary>
|
||||
Error = 1,
|
||||
|
||||
/// <summary>
|
||||
/// A fiailure generates a warning.
|
||||
/// </summary>
|
||||
Warning = 2,
|
||||
|
||||
/// <summary>
|
||||
/// A failure generate an informational message.
|
||||
/// </summary>
|
||||
Information = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A rule resource V1.
|
||||
/// </summary>
|
||||
public interface IRuleV1 : IResource, IDependencyTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Obsolete. The name of the rule.
|
||||
/// Replaced by <see cref="IResource.Name"/>.
|
||||
/// </summary>
|
||||
[Obsolete("Use Name instead.")]
|
||||
string RuleName { get; }
|
||||
|
||||
|
@ -33,16 +52,32 @@ namespace PSRule.Definitions.Rules
|
|||
/// </summary>
|
||||
SeverityLevel Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A short description of the rule.
|
||||
/// </summary>
|
||||
string Synopsis { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Obsolete. A short description of the rule.
|
||||
/// Replaced by <see cref="Synopsis"/>.
|
||||
/// </summary>
|
||||
[Obsolete("Use Synopsis instead.")]
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Any additional tags assigned to the rule.
|
||||
/// </summary>
|
||||
ResourceTags Tag { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A specification for a rule resource.
|
||||
/// </summary>
|
||||
internal interface IRuleSpec
|
||||
{
|
||||
/// <summary>
|
||||
/// The of the rule condition that will be evaluated.
|
||||
/// </summary>
|
||||
LanguageIf Condition { get; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -50,9 +85,20 @@ namespace PSRule.Definitions.Rules
|
|||
/// </summary>
|
||||
SeverityLevel? Level { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional type pre-condition before the rule is evaluated.
|
||||
/// </summary>
|
||||
string[] Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional selector pre-condition before the rule is evaluated.
|
||||
/// </summary>
|
||||
string[] With { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional sub-selector pre-condition before the rule is evaluated.
|
||||
/// </summary>
|
||||
LanguageIf Where { get; }
|
||||
}
|
||||
|
||||
[Spec(Specs.V1, Specs.Rule)]
|
||||
|
@ -68,10 +114,12 @@ namespace PSRule.Definitions.Rules
|
|||
Level = ResourceHelper.GetLevel(spec.Level);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
[YamlIgnore]
|
||||
public ResourceId? Ref { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
[YamlIgnore]
|
||||
public ResourceId[] Alias { get; }
|
||||
|
@ -90,43 +138,52 @@ namespace PSRule.Definitions.Rules
|
|||
[YamlIgnore]
|
||||
public string Synopsis => Info.Synopsis.Text;
|
||||
|
||||
/// <inheritdoc/>
|
||||
ResourceId? IDependencyTarget.Ref => Ref;
|
||||
|
||||
/// <inheritdoc/>
|
||||
ResourceId[] IDependencyTarget.Alias => Alias;
|
||||
|
||||
// Not supported with resource rules.
|
||||
ResourceId[] IDependencyTarget.DependsOn => Array.Empty<ResourceId>();
|
||||
|
||||
/// <inheritdoc/>
|
||||
bool IDependencyTarget.Dependency => Source.IsDependency();
|
||||
|
||||
/// <inheritdoc/>
|
||||
ResourceId? IResource.Ref => Ref;
|
||||
|
||||
/// <inheritdoc/>
|
||||
ResourceId[] IResource.Alias => Alias;
|
||||
|
||||
/// <inheritdoc/>
|
||||
string IRuleV1.RuleName => Name;
|
||||
|
||||
/// <inheritdoc/>
|
||||
ResourceTags IRuleV1.Tag => Metadata.Tags;
|
||||
|
||||
/// <inheritdoc/>
|
||||
string IRuleV1.Description => Info.Synopsis.Text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A specification for a V1 rule resource.
|
||||
/// </summary>
|
||||
internal sealed class RuleV1Spec : Spec, IRuleSpec
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public LanguageIf Condition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the rule fails, how serious is the result.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public SeverityLevel? Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional type precondition before the rule is evaluated.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public string[] Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional selector precondition before the rule is evaluated.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public string[] With { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public LanguageIf Where { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,18 @@ using PSRule.Runtime;
|
|||
|
||||
namespace PSRule.Definitions.Rules
|
||||
{
|
||||
/// <summary>
|
||||
/// A rule visitor.
|
||||
/// </summary>
|
||||
[DebuggerDisplay("Id: {Id}")]
|
||||
internal sealed class RuleVisitor : ICondition
|
||||
{
|
||||
private readonly LanguageExpressionOuterFn _Condition;
|
||||
private readonly RunspaceContext _Context;
|
||||
|
||||
public RuleVisitor(ResourceId id, SourceFile source, IRuleSpec spec)
|
||||
public RuleVisitor(RunspaceContext context, ResourceId id, SourceFile source, IRuleSpec spec)
|
||||
{
|
||||
_Context = context;
|
||||
ErrorAction = ActionPreference.Stop;
|
||||
Id = id;
|
||||
Source = source;
|
||||
|
@ -26,6 +31,7 @@ namespace PSRule.Definitions.Rules
|
|||
_Condition = builder
|
||||
.WithSelector(spec.With)
|
||||
.WithType(spec.Type)
|
||||
.WithSubselector(spec.Where)
|
||||
.Build(spec.Condition);
|
||||
}
|
||||
|
||||
|
@ -50,14 +56,14 @@ namespace PSRule.Definitions.Rules
|
|||
|
||||
public IConditionResult If()
|
||||
{
|
||||
var context = new ExpressionContext(Source, ResourceKind.Rule, RunspaceContext.CurrentThread.TargetObject.Value);
|
||||
var context = new ExpressionContext(_Context, Source, ResourceKind.Rule, _Context.TargetObject.Value);
|
||||
context.Debug(PSRuleResources.RuleMatchTrace, Id);
|
||||
context.PushScope(RunspaceScope.Rule);
|
||||
try
|
||||
{
|
||||
var result = _Condition(context, RunspaceContext.CurrentThread.TargetObject.Value);
|
||||
var result = _Condition(context, _Context.TargetObject.Value);
|
||||
if (result.HasValue && !result.Value)
|
||||
RunspaceContext.CurrentThread.WriteReason(context.GetReasons());
|
||||
_Context.WriteReason(context.GetReasons());
|
||||
|
||||
return result.HasValue ? new RuleConditionResult(result.Value ? 1 : 0, 1, false) : null;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Diagnostics;
|
|||
using PSRule.Definitions.Expressions;
|
||||
using PSRule.Pipeline;
|
||||
using PSRule.Resources;
|
||||
using PSRule.Runtime;
|
||||
|
||||
namespace PSRule.Definitions.Selectors
|
||||
{
|
||||
|
@ -18,9 +19,11 @@ namespace PSRule.Definitions.Selectors
|
|||
internal sealed class SelectorVisitor : ISelector
|
||||
{
|
||||
private readonly LanguageExpressionOuterFn _Fn;
|
||||
private readonly RunspaceContext _Context;
|
||||
|
||||
public SelectorVisitor(ResourceId id, SourceFile source, LanguageIf expression)
|
||||
public SelectorVisitor(RunspaceContext context, ResourceId id, SourceFile source, LanguageIf expression)
|
||||
{
|
||||
_Context = context;
|
||||
Id = id;
|
||||
Source = source;
|
||||
InstanceId = Guid.NewGuid();
|
||||
|
@ -42,7 +45,7 @@ namespace PSRule.Definitions.Selectors
|
|||
|
||||
public bool Match(object o)
|
||||
{
|
||||
var context = new ExpressionContext(Source, ResourceKind.Selector, o);
|
||||
var context = new ExpressionContext(_Context, Source, ResourceKind.Selector, o);
|
||||
context.Debug(PSRuleResources.SelectorMatchTrace, Id);
|
||||
return _Fn(context, o).GetValueOrDefault(false);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using System;
|
|||
using PSRule.Definitions.Expressions;
|
||||
using PSRule.Pipeline;
|
||||
using PSRule.Resources;
|
||||
using PSRule.Runtime;
|
||||
|
||||
namespace PSRule.Definitions.SuppressionGroups
|
||||
{
|
||||
|
@ -12,9 +13,11 @@ namespace PSRule.Definitions.SuppressionGroups
|
|||
{
|
||||
private readonly LanguageExpressionOuterFn _Fn;
|
||||
private readonly SuppressionInfo _Info;
|
||||
private readonly RunspaceContext _Context;
|
||||
|
||||
public SuppressionGroupVisitor(ResourceId id, SourceFile source, ISuppressionGroupSpec spec, IResourceHelpInfo info)
|
||||
public SuppressionGroupVisitor(RunspaceContext context, ResourceId id, SourceFile source, ISuppressionGroupSpec spec, IResourceHelpInfo info)
|
||||
{
|
||||
_Context = context;
|
||||
Id = id;
|
||||
Source = source;
|
||||
InstanceId = Guid.NewGuid();
|
||||
|
@ -77,7 +80,7 @@ namespace PSRule.Definitions.SuppressionGroups
|
|||
public bool TryMatch(object o, out ISuppressionInfo suppression)
|
||||
{
|
||||
suppression = null;
|
||||
var context = new ExpressionContext(Source, ResourceKind.SuppressionGroup, o);
|
||||
var context = new ExpressionContext(_Context, Source, ResourceKind.SuppressionGroup, o);
|
||||
context.Debug(PSRuleResources.SelectorMatchTrace, Id);
|
||||
if (_Fn(context, o).GetValueOrDefault(false))
|
||||
{
|
||||
|
|
|
@ -56,11 +56,6 @@ namespace PSRule.Host
|
|||
return builder.Build();
|
||||
}
|
||||
|
||||
internal static IEnumerable<RuleBlock> GetRuleYamlBlocks(Source[] source, RunspaceContext context)
|
||||
{
|
||||
return ToRuleBlockV1(GetYamlLanguageBlocks(source, context), context, skipDuplicateName: true).GetAll();
|
||||
}
|
||||
|
||||
private static IEnumerable<ILanguageBlock> GetYamlJsonLanguageBlocks(Source[] source, RunspaceContext context)
|
||||
{
|
||||
var results = new List<ILanguageBlock>();
|
||||
|
@ -530,7 +525,7 @@ namespace PSRule.Host
|
|||
@ref: block.Ref,
|
||||
level: block.Level,
|
||||
info: info,
|
||||
condition: new RuleVisitor(block.Id, block.Source, block.Spec),
|
||||
condition: new RuleVisitor(context, block.Id, block.Source, block.Spec),
|
||||
alias: block.Alias,
|
||||
tag: block.Metadata.Tags,
|
||||
dependsOn: null, // TODO: No support for DependsOn yet
|
||||
|
@ -706,11 +701,11 @@ namespace PSRule.Host
|
|||
|
||||
// Process module configurations first
|
||||
foreach (var resource in resources.Where(r => r.Kind == ResourceKind.ModuleConfig).ToArray())
|
||||
context.Pipeline.Import(resource);
|
||||
context.Pipeline.Import(context, resource);
|
||||
|
||||
// Process other resources
|
||||
foreach (var resource in resources.Where(r => r.Kind != ResourceKind.ModuleConfig).ToArray())
|
||||
context.Pipeline.Import(resource);
|
||||
context.Pipeline.Import(context, resource);
|
||||
}
|
||||
|
||||
private static void Import(IConvention[] blocks, RunspaceContext context)
|
||||
|
|
|
@ -476,7 +476,7 @@ namespace PSRule.Pipeline
|
|||
{
|
||||
// Nest the previous write action in the new supplied action
|
||||
// Execution chain will be: action -> previous -> previous..n
|
||||
return (string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path) =>
|
||||
return (string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path) =>
|
||||
{
|
||||
return action(propertyNames, caseSensitive, preferTargetInfo, targetObject, previous, out path);
|
||||
};
|
||||
|
@ -484,7 +484,7 @@ namespace PSRule.Pipeline
|
|||
|
||||
private static BindTargetMethod AddBindTargetAction(BindTargetName action, BindTargetMethod previous)
|
||||
{
|
||||
return AddBindTargetAction((string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, BindTargetMethod next, out string path) =>
|
||||
return AddBindTargetAction((string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, BindTargetMethod next, out string path) =>
|
||||
{
|
||||
path = null;
|
||||
var targetType = action(targetObject);
|
||||
|
|
|
@ -161,7 +161,7 @@ namespace PSRule.Pipeline
|
|||
return _Runspace;
|
||||
}
|
||||
|
||||
internal void Import(IResource resource)
|
||||
internal void Import(RunspaceContext context, IResource resource)
|
||||
{
|
||||
TrackIssue(resource);
|
||||
if (TryBaseline(resource, out var baseline) && TryBaselineRef(resource.Id, out var baselineRef))
|
||||
|
@ -170,7 +170,7 @@ namespace PSRule.Pipeline
|
|||
Baseline.Add(new OptionContext.BaselineScope(baselineRef.Type, baseline.BaselineId, resource.Source.Module, baseline.Spec, baseline.Obsolete));
|
||||
}
|
||||
else if (resource.Kind == ResourceKind.Selector && resource is SelectorV1 selector)
|
||||
Selector[selector.Id.Value] = new SelectorVisitor(selector.Id, selector.Source, selector.Spec.If);
|
||||
Selector[selector.Id.Value] = new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If);
|
||||
else if (TryModuleConfig(resource, out var moduleConfig))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(moduleConfig?.Spec?.Rule?.Baseline))
|
||||
|
@ -184,6 +184,7 @@ namespace PSRule.Pipeline
|
|||
else if (resource.Kind == ResourceKind.SuppressionGroup && resource is SuppressionGroupV1 suppressionGroup)
|
||||
{
|
||||
SuppressionGroup.Add(new SuppressionGroupVisitor(
|
||||
context: context,
|
||||
id: suppressionGroup.Id,
|
||||
source: suppressionGroup.Source,
|
||||
spec: suppressionGroup.Spec,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Management.Automation;
|
||||
|
@ -19,9 +20,12 @@ namespace PSRule.Pipeline
|
|||
private const string Property_TargetName = "TargetName";
|
||||
private const string Property_Name = "Name";
|
||||
|
||||
public static string BindTargetName(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path)
|
||||
public static string BindTargetName(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path)
|
||||
{
|
||||
path = null;
|
||||
if (targetObject == null)
|
||||
return null;
|
||||
|
||||
if (preferTargetInfo && TryGetInfoTargetName(targetObject, out var targetName))
|
||||
return targetName;
|
||||
|
||||
|
@ -33,9 +37,12 @@ namespace PSRule.Pipeline
|
|||
return DefaultTargetNameBinding(targetObject);
|
||||
}
|
||||
|
||||
public static string BindTargetType(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path)
|
||||
public static string BindTargetType(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path)
|
||||
{
|
||||
path = null;
|
||||
if (targetObject == null)
|
||||
return null;
|
||||
|
||||
if (preferTargetInfo && TryGetInfoTargetType(targetObject, out var targetType))
|
||||
return targetType;
|
||||
|
||||
|
@ -47,9 +54,12 @@ namespace PSRule.Pipeline
|
|||
return DefaultTargetTypeBinding(targetObject);
|
||||
}
|
||||
|
||||
public static string BindField(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, PSObject targetObject, out string path)
|
||||
public static string BindField(string[] propertyNames, bool caseSensitive, bool preferTargetInfo, object targetObject, out string path)
|
||||
{
|
||||
path = null;
|
||||
if (targetObject == null)
|
||||
return null;
|
||||
|
||||
if (propertyNames != null)
|
||||
return propertyNames.Any(n => n.Contains('.'))
|
||||
? NestedTargetPropertyBinding(propertyNames, caseSensitive, targetObject, DefaultFieldBinding, out path)
|
||||
|
@ -63,7 +73,7 @@ namespace PSRule.Pipeline
|
|||
/// </summary>
|
||||
/// <param name="targetObject">A PSObject to bind.</param>
|
||||
/// <returns>The TargetName of the object.</returns>
|
||||
private static string DefaultTargetNameBinding(PSObject targetObject)
|
||||
private static string DefaultTargetNameBinding(object targetObject)
|
||||
{
|
||||
return TryGetInfoTargetName(targetObject, out var targetName) ||
|
||||
TryGetTargetName(targetObject, propertyName: Property_TargetName, targetName: out targetName) ||
|
||||
|
@ -76,16 +86,17 @@ namespace PSRule.Pipeline
|
|||
/// Get the TargetName of the object by using any of the specified property names.
|
||||
/// </summary>
|
||||
/// <param name="propertyNames">One or more property names to use to bind TargetName.</param>
|
||||
/// <param name="caseSensitive">Determines if binding properties are case-sensitive.</param>
|
||||
/// <param name="targetObject">A PSObject to bind.</param>
|
||||
/// <param name="next">The next delegate function to check if all of the property names can not be found.</param>
|
||||
/// <returns>The TargetName of the object.</returns>
|
||||
private static string CustomTargetPropertyBinding(string[] propertyNames, bool caseSensitive, PSObject targetObject, BindTargetName next, out string path)
|
||||
private static string CustomTargetPropertyBinding(string[] propertyNames, bool caseSensitive, object targetObject, BindTargetName next, out string path)
|
||||
{
|
||||
path = null;
|
||||
string targetName = null;
|
||||
for (var i = 0; i < propertyNames.Length && targetName == null; i++)
|
||||
{
|
||||
targetName = targetObject.ValueAsString(propertyName: propertyNames[i], caseSensitive: caseSensitive);
|
||||
targetName = ValueAsString(targetObject, propertyName: propertyNames[i], caseSensitive: caseSensitive);
|
||||
if (targetName != null)
|
||||
path = propertyNames[i];
|
||||
}
|
||||
|
@ -97,10 +108,11 @@ namespace PSRule.Pipeline
|
|||
/// Get the TargetName of the object by using any of the specified property names.
|
||||
/// </summary>
|
||||
/// <param name="propertyNames">One or more property names to use to bind TargetName.</param>
|
||||
/// <param name="caseSensitive">Determines if binding properties are case-sensitive.</param>
|
||||
/// <param name="targetObject">A PSObject to bind.</param>
|
||||
/// <param name="next">The next delegate function to check if all of the property names can not be found.</param>
|
||||
/// <returns>The TargetName of the object.</returns>
|
||||
private static string NestedTargetPropertyBinding(string[] propertyNames, bool caseSensitive, PSObject targetObject, BindTargetName next, out string path)
|
||||
private static string NestedTargetPropertyBinding(string[] propertyNames, bool caseSensitive, object targetObject, BindTargetName next, out string path)
|
||||
{
|
||||
path = null;
|
||||
string targetName = null;
|
||||
|
@ -128,7 +140,7 @@ namespace PSRule.Pipeline
|
|||
/// </summary>
|
||||
/// <param name="targetObject">A PSObject to hash.</param>
|
||||
/// <returns>The TargetName of the object.</returns>
|
||||
private static string GetUnboundObjectTargetName(PSObject targetObject)
|
||||
private static string GetUnboundObjectTargetName(object targetObject)
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
|
@ -146,9 +158,9 @@ namespace PSRule.Pipeline
|
|||
/// <summary>
|
||||
/// Try to get TargetName from specified property.
|
||||
/// </summary>
|
||||
private static bool TryGetTargetName(PSObject targetObject, string propertyName, out string targetName)
|
||||
private static bool TryGetTargetName(object targetObject, string propertyName, out string targetName)
|
||||
{
|
||||
targetName = targetObject.ValueAsString(propertyName, false);
|
||||
targetName = ValueAsString(targetObject, propertyName, false);
|
||||
return targetName != null;
|
||||
}
|
||||
|
||||
|
@ -157,34 +169,49 @@ namespace PSRule.Pipeline
|
|||
/// </summary>
|
||||
/// <param name="targetObject">A PSObject to bind.</param>
|
||||
/// <returns>The TargetObject of the object.</returns>
|
||||
private static string DefaultTargetTypeBinding(PSObject targetObject)
|
||||
private static string DefaultTargetTypeBinding(object targetObject)
|
||||
{
|
||||
return TryGetInfoTargetType(targetObject, out var targetType) ? targetType : targetObject.TypeNames[0];
|
||||
return TryGetInfoTargetType(targetObject, out var targetType) ? targetType : GetTypeNames(targetObject);
|
||||
}
|
||||
|
||||
private static string DefaultFieldBinding(PSObject targetObject)
|
||||
private static string GetTypeNames(object targetObject)
|
||||
{
|
||||
if (targetObject == null)
|
||||
return null;
|
||||
|
||||
return targetObject is PSObject pso ? pso.TypeNames[0] : targetObject.GetType().FullName;
|
||||
}
|
||||
|
||||
private static string DefaultFieldBinding(object targetObject)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool TryGetInfoTargetName(PSObject targetObject, out string targetName)
|
||||
private static bool TryGetInfoTargetName(object targetObject, out string targetName)
|
||||
{
|
||||
targetName = null;
|
||||
if (!(targetObject.BaseObject is ITargetInfo info))
|
||||
var baseObject = ExpressionHelpers.GetBaseObject(targetObject);
|
||||
if (baseObject is not ITargetInfo info)
|
||||
return false;
|
||||
|
||||
targetName = info.TargetName;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryGetInfoTargetType(PSObject targetObject, out string targetType)
|
||||
private static bool TryGetInfoTargetType(object targetObject, out string targetType)
|
||||
{
|
||||
targetType = null;
|
||||
if (!(targetObject.BaseObject is ITargetInfo info))
|
||||
var baseObject = ExpressionHelpers.GetBaseObject(targetObject);
|
||||
if (baseObject is not ITargetInfo info)
|
||||
return false;
|
||||
|
||||
targetType = info.TargetType;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string ValueAsString(object o, string propertyName, bool caseSensitive)
|
||||
{
|
||||
return ObjectHelper.GetPath(bindingContext: null, targetObject: o, path: propertyName, caseSensitive: caseSensitive, value: out object value) && value != null ? value.ToString() : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
using PSRule.Configuration;
|
||||
using static PSRule.Pipeline.TargetBinder;
|
||||
|
||||
namespace PSRule.Pipeline
|
||||
{
|
||||
|
@ -17,6 +17,8 @@ namespace PSRule.Pipeline
|
|||
void Bind(TargetObject targetObject);
|
||||
|
||||
ITargetBindingContext Using(string languageScope);
|
||||
|
||||
ITargetBindingResult Result(string languageScope);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -26,6 +28,11 @@ namespace PSRule.Pipeline
|
|||
{
|
||||
string LanguageScope { get; }
|
||||
|
||||
ITargetBindingResult Bind(object o);
|
||||
}
|
||||
|
||||
internal interface ITargetBindingResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The bound TargetName of the target object.
|
||||
/// </summary>
|
||||
|
@ -46,8 +53,6 @@ namespace PSRule.Pipeline
|
|||
Hashtable Field { get; }
|
||||
|
||||
bool ShouldFilter { get; }
|
||||
|
||||
void Bind(TargetObject targetObject, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet<string> typeFilter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -56,7 +61,7 @@ namespace PSRule.Pipeline
|
|||
internal sealed class TargetBinderBuilder
|
||||
{
|
||||
private readonly List<ITargetBindingContext> _BindingContext;
|
||||
private readonly string[] _TypeFilter;
|
||||
private readonly HashSet<string> _TypeFilter;
|
||||
private readonly BindTargetMethod _BindTargetName;
|
||||
private readonly BindTargetMethod _BindTargetType;
|
||||
private readonly BindTargetMethod _BindField;
|
||||
|
@ -67,7 +72,8 @@ namespace PSRule.Pipeline
|
|||
_BindTargetType = bindTargetType;
|
||||
_BindField = bindField;
|
||||
_BindingContext = new List<ITargetBindingContext>();
|
||||
_TypeFilter = typeFilter;
|
||||
if (typeFilter != null && typeFilter.Length > 0)
|
||||
_TypeFilter = new HashSet<string>(typeFilter, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -75,7 +81,7 @@ namespace PSRule.Pipeline
|
|||
/// </summary>
|
||||
public ITargetBinder Build()
|
||||
{
|
||||
return new TargetBinder(_BindingContext.ToArray(), _BindTargetName, _BindTargetType, _BindField, _TypeFilter);
|
||||
return new TargetBinder(_BindingContext.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -85,6 +91,14 @@ namespace PSRule.Pipeline
|
|||
{
|
||||
_BindingContext.Add(bindingContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a target binding context.
|
||||
/// </summary>
|
||||
public void With(string languageScope, IBindingOption bindingOption)
|
||||
{
|
||||
_BindingContext.Add(new TargetBindingContext(languageScope, bindingOption, _BindTargetName, _BindTargetType, _BindField, _TypeFilter));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -94,23 +108,13 @@ namespace PSRule.Pipeline
|
|||
{
|
||||
private const string STANDALONE_SCOPE = ".";
|
||||
|
||||
private readonly BindTargetMethod _BindTargetName;
|
||||
|
||||
private readonly BindTargetMethod _BindTargetType;
|
||||
private readonly BindTargetMethod _BindField;
|
||||
private readonly HashSet<string> _TypeFilter;
|
||||
|
||||
private readonly Dictionary<string, ITargetBindingContext> _BindingContext;
|
||||
private readonly Dictionary<string, ITargetBindingResult> _BindingResult;
|
||||
|
||||
internal TargetBinder(ITargetBindingContext[] bindingContext, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, string[] typeFilter)
|
||||
internal TargetBinder(ITargetBindingContext[] bindingContext)
|
||||
{
|
||||
_BindingContext = new Dictionary<string, ITargetBindingContext>();
|
||||
_BindTargetName = bindTargetName;
|
||||
_BindTargetType = bindTargetType;
|
||||
_BindField = bindField;
|
||||
if (typeFilter != null && typeFilter.Length > 0)
|
||||
_TypeFilter = new HashSet<string>(typeFilter, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_BindingResult = new Dictionary<string, ITargetBindingResult>();
|
||||
for (var i = 0; bindingContext != null && i < bindingContext.Length; i++)
|
||||
_BindingContext.Add(bindingContext[i].LanguageScope ?? STANDALONE_SCOPE, bindingContext[i]);
|
||||
}
|
||||
|
@ -166,56 +170,79 @@ namespace PSRule.Pipeline
|
|||
}
|
||||
}
|
||||
|
||||
internal sealed class TargetBindingResult : ITargetBindingResult
|
||||
{
|
||||
public TargetBindingResult(string targetName, string targetNamePath, string targetType, string targetTypePath, bool shouldFilter, Hashtable field)
|
||||
{
|
||||
TargetName = targetName;
|
||||
TargetNamePath = targetNamePath;
|
||||
TargetType = targetType;
|
||||
TargetTypePath = targetTypePath;
|
||||
ShouldFilter = shouldFilter;
|
||||
Field = field;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string TargetName { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string TargetNamePath { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string TargetType { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string TargetTypePath { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool ShouldFilter { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Hashtable Field { get; }
|
||||
}
|
||||
|
||||
internal sealed class TargetBindingContext : ITargetBindingContext
|
||||
{
|
||||
private readonly IBindingOption _BindingOption;
|
||||
private readonly BindTargetMethod _BindTargetName;
|
||||
private readonly BindTargetMethod _BindTargetType;
|
||||
private readonly BindTargetMethod _BindField;
|
||||
private readonly HashSet<string> _TypeFilter;
|
||||
|
||||
public TargetBindingContext(string languageScope, IBindingOption bindingOption)
|
||||
public TargetBindingContext(string languageScope, IBindingOption bindingOption, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet<string> typeFilter)
|
||||
{
|
||||
LanguageScope = languageScope;
|
||||
_BindingOption = bindingOption;
|
||||
_BindTargetName = bindTargetName;
|
||||
_BindTargetType = bindTargetType;
|
||||
_BindField = bindField;
|
||||
_TypeFilter = typeFilter;
|
||||
}
|
||||
|
||||
public string LanguageScope { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The bound TargetName of the target object.
|
||||
/// </summary>
|
||||
public string TargetName { get; private set; }
|
||||
|
||||
public string TargetNamePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The bound TargetType of the target object.
|
||||
/// </summary>
|
||||
public string TargetType { get; private set; }
|
||||
|
||||
public string TargetTypePath { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Additional bound fields of the target object.
|
||||
/// </summary>
|
||||
public Hashtable Field { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the target object should be filtered.
|
||||
/// </summary>
|
||||
public bool ShouldFilter { get; private set; }
|
||||
|
||||
public void Bind(TargetObject targetObject, BindTargetMethod bindTargetName, BindTargetMethod bindTargetType, BindTargetMethod bindField, HashSet<string> typeFilter)
|
||||
public ITargetBindingResult Bind(object o)
|
||||
{
|
||||
TargetName = bindTargetName(_BindingOption.TargetName, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, targetObject.Value, out var targetNamePath);
|
||||
TargetNamePath = targetNamePath;
|
||||
TargetType = bindTargetType(_BindingOption.TargetType, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, targetObject.Value, out var targetTypePath);
|
||||
TargetTypePath = targetTypePath;
|
||||
ShouldFilter = !(typeFilter == null || typeFilter.Contains(TargetType));
|
||||
var targetName = _BindTargetName(_BindingOption.TargetName, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, o, out var targetNamePath);
|
||||
var targetType = _BindTargetType(_BindingOption.TargetType, !_BindingOption.IgnoreCase, _BindingOption.PreferTargetInfo, o, out var targetTypePath);
|
||||
var shouldFilter = !(_TypeFilter == null || _TypeFilter.Contains(targetType));
|
||||
|
||||
// Use qualified name
|
||||
if (_BindingOption.UseQualifiedName)
|
||||
TargetName = string.Concat(TargetType, _BindingOption.NameSeparator, TargetName);
|
||||
targetName = string.Concat(targetType, _BindingOption.NameSeparator, targetName);
|
||||
|
||||
// Bind custom fields
|
||||
Field = BindField(bindField, _BindingOption.Field, !_BindingOption.IgnoreCase, targetObject.Value);
|
||||
var field = BindField(_BindField, _BindingOption.Field, !_BindingOption.IgnoreCase, o);
|
||||
|
||||
return new TargetBindingResult
|
||||
(
|
||||
targetName: targetName,
|
||||
targetNamePath: targetNamePath,
|
||||
targetType: targetType,
|
||||
targetTypePath: targetTypePath,
|
||||
shouldFilter: shouldFilter,
|
||||
field: field
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,7 +254,7 @@ namespace PSRule.Pipeline
|
|||
public void Bind(TargetObject targetObject)
|
||||
{
|
||||
foreach (var bindingContext in _BindingContext.Values)
|
||||
bindingContext.Bind(targetObject, _BindTargetName, _BindTargetType, _BindField, _TypeFilter);
|
||||
_BindingResult[bindingContext.LanguageScope] = bindingContext.Bind(targetObject.Value);
|
||||
}
|
||||
|
||||
public ITargetBindingContext Using(string languageScope)
|
||||
|
@ -235,10 +262,15 @@ namespace PSRule.Pipeline
|
|||
return _BindingContext.TryGetValue(languageScope ?? STANDALONE_SCOPE, out var result) ? result : null;
|
||||
}
|
||||
|
||||
public ITargetBindingResult Result(string languageScope)
|
||||
{
|
||||
return _BindingResult.TryGetValue(languageScope ?? STANDALONE_SCOPE, out var result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bind additional fields.
|
||||
/// </summary>
|
||||
private static Hashtable BindField(BindTargetMethod bindField, FieldMap[] map, bool caseSensitive, PSObject targetObject)
|
||||
private static Hashtable BindField(BindTargetMethod bindField, FieldMap[] map, bool caseSensitive, object o)
|
||||
{
|
||||
if (map == null || map.Length == 0)
|
||||
return null;
|
||||
|
@ -254,7 +286,7 @@ namespace PSRule.Pipeline
|
|||
if (hashtable.ContainsKey(field.Key))
|
||||
continue;
|
||||
|
||||
hashtable.Add(field.Key, bindField(field.Value, caseSensitive, false, targetObject, out _));
|
||||
hashtable.Add(field.Key, bindField(field.Value, caseSensitive, false, o, out _));
|
||||
}
|
||||
}
|
||||
hashtable.Protect();
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -382,4 +382,7 @@
|
|||
<data name="ReadJsonFailedExpectedToken" xml:space="preserve">
|
||||
<value>Read JSON failed because the token ({0}) was not expected.</value>
|
||||
</data>
|
||||
<data name="DebugTargetSubselectorMismatch" xml:space="preserve">
|
||||
<value>Target failed sub-selector pre-condition.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -12,6 +12,9 @@ namespace PSRule.Runtime
|
|||
/// </summary>
|
||||
internal interface ILanguageScope : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the scope.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
|
@ -28,29 +31,42 @@ namespace PSRule.Runtime
|
|||
|
||||
IResourceFilter GetFilter(ResourceKind kind);
|
||||
|
||||
/// <summary>
|
||||
/// Add a service to the scope.
|
||||
/// </summary>
|
||||
void AddService(string name, object service);
|
||||
|
||||
/// <summary>
|
||||
/// Get a previously added service.
|
||||
/// </summary>
|
||||
object GetService(string name);
|
||||
|
||||
bool TryGetType(object o, out string type, out string path);
|
||||
|
||||
bool TryGetName(object o, out string name, out string path);
|
||||
}
|
||||
|
||||
internal sealed class LanguageScope : ILanguageScope
|
||||
{
|
||||
private const string STANDALONE_SCOPENAME = ".";
|
||||
|
||||
private readonly RunspaceContext _Context;
|
||||
private readonly Dictionary<string, object> _Configuration;
|
||||
private readonly Dictionary<string, object> _Service;
|
||||
private readonly Dictionary<ResourceKind, IResourceFilter> _Filter;
|
||||
|
||||
private bool _Disposed;
|
||||
|
||||
public LanguageScope(string name)
|
||||
public LanguageScope(RunspaceContext context, string name)
|
||||
{
|
||||
_Context = context;
|
||||
Name = Normalize(name);
|
||||
_Configuration = new Dictionary<string, object>();
|
||||
_Filter = new Dictionary<ResourceKind, IResourceFilter>();
|
||||
_Service = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name { get; }
|
||||
|
||||
public void Configure(Dictionary<string, object> configuration)
|
||||
|
@ -74,6 +90,7 @@ namespace PSRule.Runtime
|
|||
return _Filter.TryGetValue(kind, out var filter) ? filter : null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void AddService(string name, object service)
|
||||
{
|
||||
if (_Service.ContainsKey(name))
|
||||
|
@ -82,11 +99,54 @@ namespace PSRule.Runtime
|
|||
_Service.Add(name, service);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object GetService(string name)
|
||||
{
|
||||
return _Service.TryGetValue(name, out var service) ? service : null;
|
||||
}
|
||||
|
||||
public bool TryGetType(object o, out string type, out string path)
|
||||
{
|
||||
if (_Context != null && _Context.TargetObject.Value == o)
|
||||
{
|
||||
var binding = _Context.TargetBinder.Result(Name);
|
||||
type = binding.TargetType;
|
||||
path = binding.TargetTypePath;
|
||||
return true;
|
||||
}
|
||||
else if (_Context != null)
|
||||
{
|
||||
var binding = _Context.TargetBinder.Using(Name).Bind(o);
|
||||
type = binding.TargetType;
|
||||
path = binding.TargetTypePath;
|
||||
return true;
|
||||
}
|
||||
type = null;
|
||||
path = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetName(object o, out string name, out string path)
|
||||
{
|
||||
if (_Context != null && _Context.TargetObject.Value == o)
|
||||
{
|
||||
var binding = _Context.TargetBinder.Result(Name);
|
||||
name = binding.TargetName;
|
||||
path = binding.TargetNamePath;
|
||||
return true;
|
||||
}
|
||||
else if (_Context != null)
|
||||
{
|
||||
var binding = _Context.TargetBinder.Using(Name).Bind(o);
|
||||
name = binding.TargetName;
|
||||
path = binding.TargetNamePath;
|
||||
return true;
|
||||
}
|
||||
name = null;
|
||||
path = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static string Normalize(string scope)
|
||||
{
|
||||
return string.IsNullOrEmpty(scope) ? STANDALONE_SCOPENAME : scope;
|
||||
|
@ -123,13 +183,15 @@ namespace PSRule.Runtime
|
|||
|
||||
internal sealed class LanguageScopeSet : IDisposable
|
||||
{
|
||||
private readonly RunspaceContext _Context;
|
||||
private readonly Dictionary<string, ILanguageScope> _Scopes;
|
||||
|
||||
private ILanguageScope _Current;
|
||||
private bool _Disposed;
|
||||
|
||||
public LanguageScopeSet()
|
||||
public LanguageScopeSet(RunspaceContext context)
|
||||
{
|
||||
_Context = context;
|
||||
_Scopes = new Dictionary<string, ILanguageScope>(StringComparer.OrdinalIgnoreCase);
|
||||
Import(null, out _Current);
|
||||
}
|
||||
|
@ -168,7 +230,7 @@ namespace PSRule.Runtime
|
|||
if (_Scopes.TryGetValue(GetScopeName(name), out scope))
|
||||
return false;
|
||||
|
||||
scope = new LanguageScope(name);
|
||||
scope = new LanguageScope(_Context, name);
|
||||
Add(scope);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace PSRule.Runtime.ObjectPath
|
|||
/// <summary>
|
||||
/// An expression function that returns one or more values when successful.
|
||||
/// </summary>
|
||||
internal delegate bool PathExpressionFn(IPathExpressionContext context, object input, out IEnumerable<object> value);
|
||||
internal delegate bool PathExpressionFn(IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable);
|
||||
|
||||
/// <summary>
|
||||
/// A function for filter objects that simply returns true or false.
|
||||
|
@ -92,7 +92,7 @@ namespace PSRule.Runtime.ObjectPath
|
|||
public bool TryGet(object o, bool caseSensitive, out object[] value)
|
||||
{
|
||||
value = null;
|
||||
if (!TryGet(o, caseSensitive, out IEnumerable<object> result))
|
||||
if (!TryGet(o, caseSensitive, out var result, out var enumerable))
|
||||
return false;
|
||||
|
||||
value = result.ToArray();
|
||||
|
@ -106,10 +106,11 @@ namespace PSRule.Runtime.ObjectPath
|
|||
public bool TryGet(object o, bool caseSensitive, out object value)
|
||||
{
|
||||
value = null;
|
||||
if (!TryGet(o, caseSensitive, out object[] result))
|
||||
if (!TryGet(o, caseSensitive, out var result, out var enumerable))
|
||||
return false;
|
||||
|
||||
value = IsArray ? result : result[0];
|
||||
var items = result.ToArray();
|
||||
value = IsArray || enumerable ? items : items[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -120,10 +121,10 @@ namespace PSRule.Runtime.ObjectPath
|
|||
/// <param name="caseSensitive">Determines if member name matching is case-sensitive.</param>
|
||||
/// <param name="value">The values selected from the object.</param>
|
||||
/// <returns>Returns true when the path exists within the object. Returns false if the path does not exist.</returns>
|
||||
private bool TryGet(object o, bool caseSensitive, out IEnumerable<object> value)
|
||||
private bool TryGet(object o, bool caseSensitive, out IEnumerable<object> value, out bool enumerable)
|
||||
{
|
||||
var context = new PathExpressionContext(o, caseSensitive);
|
||||
return _Expression.Invoke(context, o, out value);
|
||||
return _Expression.Invoke(context, o, out value, out enumerable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ namespace PSRule.Runtime.ObjectPath
|
|||
UseArray();
|
||||
var filter = BuildExpression(reader, PathTokenType.EndFilter);
|
||||
var next = BuildSelector(reader);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
|
||||
{
|
||||
var result = new List<object>();
|
||||
var success = 0;
|
||||
|
@ -104,13 +104,14 @@ namespace PSRule.Runtime.ObjectPath
|
|||
if (!filter(context, i))
|
||||
continue;
|
||||
|
||||
if (!next(context, i, out var items))
|
||||
if (!next(context, i, out var items, out _))
|
||||
continue;
|
||||
|
||||
success++;
|
||||
result.AddRange(items);
|
||||
}
|
||||
value = success > 0 ? result.ToArray() : null;
|
||||
enumerable = value != null;
|
||||
return success > 0;
|
||||
};
|
||||
}
|
||||
|
@ -118,10 +119,11 @@ namespace PSRule.Runtime.ObjectPath
|
|||
private PathExpressionFn IndexSelector(ITokenReader reader, int index)
|
||||
{
|
||||
var next = BuildSelector(reader);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
|
||||
{
|
||||
value = null;
|
||||
return TryGetIndex(input, index, out var item) && next(context, item, out value);
|
||||
enumerable = false;
|
||||
return TryGetIndex(input, index, out var item) && next(context, item, out value, out enumerable);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -129,19 +131,20 @@ namespace PSRule.Runtime.ObjectPath
|
|||
{
|
||||
UseArray();
|
||||
var next = BuildSelector(reader);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
|
||||
{
|
||||
var result = new List<object>();
|
||||
var success = 0;
|
||||
foreach (var i in GetAll(input))
|
||||
{
|
||||
if (!next(context, i, out var items))
|
||||
if (!next(context, i, out var items, out _))
|
||||
continue;
|
||||
|
||||
success++;
|
||||
result.AddRange(items);
|
||||
}
|
||||
value = success > 0 ? result.ToArray() : null;
|
||||
enumerable = value != null;
|
||||
return success > 0;
|
||||
};
|
||||
}
|
||||
|
@ -153,18 +156,19 @@ namespace PSRule.Runtime.ObjectPath
|
|||
var step = arg[2].GetValueOrDefault(1);
|
||||
var start = arg[0].GetValueOrDefault(step >= 0 ? 0 : -1);
|
||||
var end = arg[1];
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
|
||||
{
|
||||
var result = new List<object>();
|
||||
var currentIndex = start;
|
||||
while ((!end.HasValue || (step > 0 && currentIndex < end) || (step < 0 && currentIndex > end)) && TryGetIndex(input, currentIndex, out var slice))
|
||||
{
|
||||
currentIndex += step;
|
||||
if (!next(context, slice, out var items))
|
||||
if (!next(context, slice, out var items, out _))
|
||||
continue;
|
||||
|
||||
result.AddRange(items);
|
||||
}
|
||||
enumerable = true;
|
||||
value = result.ToArray();
|
||||
return true;
|
||||
};
|
||||
|
@ -174,11 +178,12 @@ namespace PSRule.Runtime.ObjectPath
|
|||
{
|
||||
var caseSensitiveFlag = option == PathTokenOption.CaseSensitive;
|
||||
var next = BuildSelector(reader);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
|
||||
{
|
||||
value = null;
|
||||
enumerable = false;
|
||||
var caseSensitive = context.CaseSensitive != caseSensitiveFlag;
|
||||
return TryGetField(input, memberName, caseSensitive, out var item) && next(context, item, out value);
|
||||
return TryGetField(input, memberName, caseSensitive, out var item) && next(context, item, out value, out enumerable);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -186,20 +191,21 @@ namespace PSRule.Runtime.ObjectPath
|
|||
{
|
||||
var caseSensitiveFlag = option == PathTokenOption.CaseSensitive;
|
||||
var next = BuildSelector(reader);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
|
||||
{
|
||||
var caseSensitive = context.CaseSensitive != caseSensitiveFlag;
|
||||
var result = new List<object>();
|
||||
var success = 0;
|
||||
foreach (var i in GetAllRecurse(input, memberName, caseSensitive))
|
||||
{
|
||||
if (!next(context, i, out var items))
|
||||
if (!next(context, i, out var items, out _))
|
||||
continue;
|
||||
|
||||
success++;
|
||||
result.AddRange(items);
|
||||
}
|
||||
value = success > 0 ? result.ToArray() : null;
|
||||
enumerable = value != null;
|
||||
return success > 0;
|
||||
};
|
||||
}
|
||||
|
@ -207,13 +213,13 @@ namespace PSRule.Runtime.ObjectPath
|
|||
private PathExpressionFn CurrentRef(ITokenReader reader)
|
||||
{
|
||||
var next = BuildSelector(reader);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) => next(context, input, out value);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) => next(context, input, out value, out enumerable);
|
||||
}
|
||||
|
||||
private PathExpressionFn RootRef(ITokenReader reader)
|
||||
{
|
||||
var next = BuildSelector(reader);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) => next(context, context.Input, out value);
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) => next(context, context.Input, out value, out enumerable);
|
||||
}
|
||||
|
||||
private PathExpressionFilterFn BuildExpression(ITokenReader reader, PathTokenType stop)
|
||||
|
@ -282,7 +288,7 @@ namespace PSRule.Runtime.ObjectPath
|
|||
|
||||
private static PathExpressionFilterFn ExistCondition(PathExpressionFn next)
|
||||
{
|
||||
return (IPathExpressionContext context, object input) => next(context, input, out _);
|
||||
return (IPathExpressionContext context, object input) => next(context, input, out _, out _);
|
||||
}
|
||||
|
||||
private static PathExpressionFilterFn NotCondition(PathExpressionFilterFn next)
|
||||
|
@ -294,7 +300,7 @@ namespace PSRule.Runtime.ObjectPath
|
|||
{
|
||||
return (IPathExpressionContext context, object input) =>
|
||||
{
|
||||
if (!left(context, input, out var leftValue) || !right(context, input, out var rightValue))
|
||||
if (!left(context, input, out var leftValue, out _) || !right(context, input, out var rightValue, out _))
|
||||
return false;
|
||||
|
||||
var operand1 = leftValue.FirstOrDefault();
|
||||
|
@ -328,22 +334,32 @@ namespace PSRule.Runtime.ObjectPath
|
|||
};
|
||||
}
|
||||
|
||||
private static bool Return(IPathExpressionContext context, object input, out IEnumerable<object> value)
|
||||
private static bool Return(IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable)
|
||||
{
|
||||
// Unwrap primitive types
|
||||
if (input is JValue jValue && (jValue.Type == JTokenType.String || jValue.Type == JTokenType.Integer || jValue.Type == JTokenType.Boolean))
|
||||
input = jValue.Value;
|
||||
|
||||
value = new object[] { input };
|
||||
enumerable = false;
|
||||
if (input is object[] eo)
|
||||
{
|
||||
enumerable = true;
|
||||
value = eo;
|
||||
}
|
||||
else
|
||||
value = new object[] { input };
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static PathExpressionFn Literal(object arg)
|
||||
{
|
||||
var result = new object[] { arg };
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value) =>
|
||||
var isEnumerable = arg is object[];
|
||||
var result = isEnumerable ? arg as object[] : new object[] { arg };
|
||||
return (IPathExpressionContext context, object input, out IEnumerable<object> value, out bool enumerable) =>
|
||||
{
|
||||
value = result;
|
||||
enumerable = isEnumerable;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ namespace PSRule.Runtime
|
|||
// Fields exposed to engine
|
||||
internal RuleRecord RuleRecord;
|
||||
internal RuleBlock RuleBlock;
|
||||
internal ITargetBindingContext Binding;
|
||||
internal ITargetBindingResult Binding;
|
||||
|
||||
private readonly bool _InconclusiveWarning;
|
||||
private readonly bool _NotProcessedWarning;
|
||||
|
@ -118,7 +118,7 @@ namespace PSRule.Runtime
|
|||
_RuleTimer = new Stopwatch();
|
||||
_Reason = new List<ResultReason>();
|
||||
_Conventions = new List<IConvention>();
|
||||
_LanguageScopes = new LanguageScopeSet();
|
||||
_LanguageScopes = new LanguageScopeSet(this);
|
||||
_Scope = new Stack<RunspaceScope>();
|
||||
}
|
||||
|
||||
|
@ -643,7 +643,7 @@ namespace PSRule.Runtime
|
|||
/// </summary>
|
||||
public RuleRecord EnterRuleBlock(RuleBlock ruleBlock)
|
||||
{
|
||||
Binding = TargetBinder.Using(ruleBlock.Info.ModuleName);
|
||||
Binding = TargetBinder.Result(ruleBlock.Info.ModuleName);
|
||||
|
||||
_RuleErrors = 0;
|
||||
RuleBlock = ruleBlock;
|
||||
|
@ -791,10 +791,14 @@ namespace PSRule.Runtime
|
|||
Pipeline.BindField,
|
||||
Pipeline.Option.Input.TargetType);
|
||||
|
||||
HashSet<string> _TypeFilter = null;
|
||||
if (Pipeline.Option.Input.TargetType != null && Pipeline.Option.Input.TargetType.Length > 0)
|
||||
_TypeFilter = new HashSet<string>(Pipeline.Option.Input.TargetType, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var languageScope in _LanguageScopes.Get())
|
||||
{
|
||||
var targetBinding = Pipeline.Baseline.GetTargetBinding();
|
||||
builder.With(new TargetBinder.TargetBindingContext(languageScope.Name, targetBinding));
|
||||
builder.With(new TargetBinder.TargetBindingContext(languageScope.Name, targetBinding, Pipeline.BindTargetName, Pipeline.BindTargetType, Pipeline.BindField, _TypeFilter));
|
||||
}
|
||||
TargetBinder = builder.Build();
|
||||
RunConventionInitialize();
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
[
|
||||
{
|
||||
// Synopsis: A rule with sub-selector pre-condition.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "JsonRuleWithPrecondition"
|
||||
},
|
||||
"spec": {
|
||||
"where": {
|
||||
"field": "kind",
|
||||
"equals": "test"
|
||||
},
|
||||
"condition": {
|
||||
"field": "resources",
|
||||
"count": 2
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// Synopsis: A rule with sub-selector filter.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "JsonRuleWithSubselector"
|
||||
},
|
||||
"spec": {
|
||||
"condition": {
|
||||
"field": "resources",
|
||||
"where": {
|
||||
"field": ".",
|
||||
"isString": true
|
||||
},
|
||||
"allOf": [
|
||||
{
|
||||
"field": ".",
|
||||
"equals": "abc"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// Synopsis: A rule with sub-selector filter.
|
||||
"apiVersion": "github.com/microsoft/PSRule/v1",
|
||||
"kind": "Rule",
|
||||
"metadata": {
|
||||
"name": "JsonRuleWithSubselectorReordered"
|
||||
},
|
||||
"spec": {
|
||||
"condition": {
|
||||
"allOf": [
|
||||
{
|
||||
"field": ".",
|
||||
"equals": "abc"
|
||||
}
|
||||
],
|
||||
"field": "resources",
|
||||
"where": {
|
||||
"field": ".",
|
||||
"equals": "abc"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
|
@ -0,0 +1,52 @@
|
|||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
#
|
||||
# YAML-based rules for unit testing
|
||||
#
|
||||
|
||||
---
|
||||
# Synopsis: A rule with sub-selector pre-condition.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: YamlRuleWithPrecondition
|
||||
spec:
|
||||
where:
|
||||
field: 'kind'
|
||||
equals: 'test'
|
||||
condition:
|
||||
field: resources
|
||||
count: 2
|
||||
|
||||
---
|
||||
# Synopsis: A rule with sub-selector filter.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: YamlRuleWithSubselector
|
||||
spec:
|
||||
condition:
|
||||
field: resources
|
||||
where:
|
||||
field: '.'
|
||||
isString: true
|
||||
allOf:
|
||||
- field: '.'
|
||||
equals: abc
|
||||
|
||||
---
|
||||
# Synopsis: A rule with sub-selector filter.
|
||||
apiVersion: github.com/microsoft/PSRule/v1
|
||||
kind: Rule
|
||||
metadata:
|
||||
name: YamlRuleWithSubselectorReordered
|
||||
spec:
|
||||
condition:
|
||||
allOf:
|
||||
- field: '.'
|
||||
equals: abc
|
||||
field: resources
|
||||
where:
|
||||
field: '.'
|
||||
equals: 'abc'
|
|
@ -52,7 +52,7 @@ namespace PSRule
|
|||
context.Init(source);
|
||||
context.Begin();
|
||||
var selector = HostHelper.GetSelector(source, context).ToArray().FirstOrDefault(s => s.Name == name);
|
||||
return new SelectorVisitor(selector.Id, selector.Source, selector.Spec.If);
|
||||
return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If);
|
||||
}
|
||||
|
||||
private static string GetSourcePath(string fileName)
|
||||
|
|
|
@ -264,7 +264,7 @@ namespace PSRule
|
|||
targetObject.Properties.Add(new PSNoteProperty("name", "TestObject1"));
|
||||
var context = new Runtime.RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContextBuilder(GetOption(), null, null, null).Build(), null), null);
|
||||
var s = GetSource();
|
||||
var result = new ExpressionContext(s[0].File[0], Definitions.ResourceKind.Rule, targetObject);
|
||||
var result = new ExpressionContext(context, s[0].File[0], Definitions.ResourceKind.Rule, targetObject);
|
||||
context.Init(s);
|
||||
context.Begin();
|
||||
context.PushScope(Runtime.RunspaceScope.Precondition);
|
||||
|
|
|
@ -70,6 +70,12 @@
|
|||
<None Update="FromFileName.Rule.yaml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="FromFileSubSelector.Rule.jsonc">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="FromFileSubSelector.Rule.yaml">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Functions.Rule.jsonc">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
|
|
|
@ -20,6 +20,9 @@ namespace PSRule
|
|||
{
|
||||
#region Yaml rules
|
||||
|
||||
/// <summary>
|
||||
/// Test that a YAML-based rule can be parsed.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ReadYamlRule()
|
||||
{
|
||||
|
@ -44,6 +47,61 @@ namespace PSRule
|
|||
Assert.Equal("tag", hashtable["feature"]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that a YAML-based rule with sub-selectors can be parsed.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ReadYamlSubSelectorRule()
|
||||
{
|
||||
var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), new TestWriter(GetOption()));
|
||||
context.Init(GetSource("FromFileSubSelector.Rule.yaml"));
|
||||
context.Begin();
|
||||
|
||||
// From current path
|
||||
var rule = HostHelper.GetRule(GetSource("FromFileSubSelector.Rule.yaml"), context, includeDependencies: false);
|
||||
Assert.NotNull(rule);
|
||||
Assert.Equal("YamlRuleWithPrecondition", rule[0].Name);
|
||||
Assert.Equal("YamlRuleWithSubselector", rule[1].Name);
|
||||
Assert.Equal("YamlRuleWithSubselectorReordered", rule[2].Name);
|
||||
|
||||
context.Init(GetSource("FromFileSubSelector.Rule.yaml"));
|
||||
context.Begin();
|
||||
var subselector1 = GetRuleVisitor(context, "YamlRuleWithPrecondition", GetSource("FromFileSubSelector.Rule.yaml"));
|
||||
var subselector2 = GetRuleVisitor(context, "YamlRuleWithSubselector", GetSource("FromFileSubSelector.Rule.yaml"));
|
||||
var subselector3 = GetRuleVisitor(context, "YamlRuleWithSubselectorReordered", GetSource("FromFileSubSelector.Rule.yaml"));
|
||||
context.EnterSourceScope(subselector1.Source);
|
||||
|
||||
var actual1 = GetObject((name: "kind", value: "test"), (name: "resources", value: new string[] { "abc", "abc" }));
|
||||
var actual2 = GetObject((name: "resources", value: new string[] { "abc", "123", "abc" }));
|
||||
|
||||
// YamlRuleWithPrecondition
|
||||
context.EnterTargetObject(actual1);
|
||||
context.EnterRuleBlock(subselector1);
|
||||
Assert.True(subselector1.Condition.If().AllOf());
|
||||
|
||||
context.EnterTargetObject(actual2);
|
||||
context.EnterRuleBlock(subselector1);
|
||||
Assert.True(subselector1.Condition.If().Skipped());
|
||||
|
||||
// YamlRuleWithSubselector
|
||||
context.EnterTargetObject(actual1);
|
||||
context.EnterRuleBlock(subselector2);
|
||||
Assert.True(subselector2.Condition.If().AllOf());
|
||||
|
||||
context.EnterTargetObject(actual2);
|
||||
context.EnterRuleBlock(subselector2);
|
||||
Assert.False(subselector2.Condition.If().AllOf());
|
||||
|
||||
// YamlRuleWithSubselectorReordered
|
||||
context.EnterTargetObject(actual1);
|
||||
context.EnterRuleBlock(subselector3);
|
||||
Assert.True(subselector3.Condition.If().AllOf());
|
||||
|
||||
context.EnterTargetObject(actual2);
|
||||
context.EnterRuleBlock(subselector3);
|
||||
Assert.True(subselector3.Condition.If().AllOf());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EvaluateYamlRule()
|
||||
{
|
||||
|
@ -136,6 +194,9 @@ namespace PSRule
|
|||
|
||||
#region Json rules
|
||||
|
||||
/// <summary>
|
||||
/// Test that a JSON-based rule can be parsed.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ReadJsonRule()
|
||||
{
|
||||
|
@ -160,6 +221,61 @@ namespace PSRule
|
|||
Assert.Equal("tag", hashtable["feature"]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that a JSON-based rule with sub-selectors can be parsed.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ReadJsonSubSelectorRule()
|
||||
{
|
||||
var context = new RunspaceContext(PipelineContext.New(GetOption(), null, null, PipelineHookActions.BindTargetName, PipelineHookActions.BindTargetType, PipelineHookActions.BindField, new OptionContext(), null), new TestWriter(GetOption()));
|
||||
context.Init(GetSource("FromFileSubSelector.Rule.jsonc"));
|
||||
context.Begin();
|
||||
|
||||
// From current path
|
||||
var rule = HostHelper.GetRule(GetSource("FromFileSubSelector.Rule.jsonc"), context, includeDependencies: false);
|
||||
Assert.NotNull(rule);
|
||||
Assert.Equal("JsonRuleWithPrecondition", rule[0].Name);
|
||||
Assert.Equal("JsonRuleWithSubselector", rule[1].Name);
|
||||
Assert.Equal("JsonRuleWithSubselectorReordered", rule[2].Name);
|
||||
|
||||
context.Init(GetSource("FromFileSubSelector.Rule.yaml"));
|
||||
context.Begin();
|
||||
var subselector1 = GetRuleVisitor(context, "JsonRuleWithPrecondition", GetSource("FromFileSubSelector.Rule.jsonc"));
|
||||
var subselector2 = GetRuleVisitor(context, "JsonRuleWithSubselector", GetSource("FromFileSubSelector.Rule.jsonc"));
|
||||
var subselector3 = GetRuleVisitor(context, "JsonRuleWithSubselectorReordered", GetSource("FromFileSubSelector.Rule.jsonc"));
|
||||
context.EnterSourceScope(subselector1.Source);
|
||||
|
||||
var actual1 = GetObject((name: "kind", value: "test"), (name: "resources", value: new string[] { "abc", "abc" }));
|
||||
var actual2 = GetObject((name: "resources", value: new string[] { "abc", "123", "abc" }));
|
||||
|
||||
// JsonRuleWithPrecondition
|
||||
context.EnterTargetObject(actual1);
|
||||
context.EnterRuleBlock(subselector1);
|
||||
Assert.True(subselector1.Condition.If().AllOf());
|
||||
|
||||
context.EnterTargetObject(actual2);
|
||||
context.EnterRuleBlock(subselector1);
|
||||
Assert.True(subselector1.Condition.If().Skipped());
|
||||
|
||||
// JsonRuleWithSubselector
|
||||
context.EnterTargetObject(actual1);
|
||||
context.EnterRuleBlock(subselector2);
|
||||
Assert.True(subselector2.Condition.If().AllOf());
|
||||
|
||||
context.EnterTargetObject(actual2);
|
||||
context.EnterRuleBlock(subselector2);
|
||||
Assert.False(subselector2.Condition.If().AllOf());
|
||||
|
||||
// JsonRuleWithSubselectorReordered
|
||||
context.EnterTargetObject(actual1);
|
||||
context.EnterRuleBlock(subselector3);
|
||||
Assert.True(subselector3.Condition.If().AllOf());
|
||||
|
||||
context.EnterTargetObject(actual2);
|
||||
context.EnterRuleBlock(subselector3);
|
||||
Assert.True(subselector3.Condition.If().AllOf());
|
||||
}
|
||||
|
||||
#endregion Json rules
|
||||
|
||||
#region Helper methods
|
||||
|
@ -190,17 +306,17 @@ namespace PSRule
|
|||
return JsonConvert.DeserializeObject<object[]>(File.ReadAllText(path));
|
||||
}
|
||||
|
||||
private static RuleBlock GetRuleVisitor(RunspaceContext context, string name)
|
||||
private static RuleBlock GetRuleVisitor(RunspaceContext context, string name, Source[] source = null)
|
||||
{
|
||||
var block = HostHelper.GetRuleYamlBlocks(GetSource(), context);
|
||||
var block = HostHelper.GetRuleBlockGraph(source ?? GetSource(), context).GetAll();
|
||||
return block.FirstOrDefault(s => s.Name == name);
|
||||
}
|
||||
|
||||
private static void ImportSelectors(RunspaceContext context)
|
||||
private static void ImportSelectors(RunspaceContext context, Source[] source = null)
|
||||
{
|
||||
var selectors = HostHelper.GetSelector(GetSource(), context).ToArray();
|
||||
var selectors = HostHelper.GetSelector(source ?? GetSource(), context).ToArray();
|
||||
foreach (var selector in selectors)
|
||||
context.Pipeline.Import(selector);
|
||||
context.Pipeline.Import(context, selector);
|
||||
}
|
||||
|
||||
private static string GetSourcePath(string path)
|
||||
|
|
|
@ -1712,7 +1712,7 @@ namespace PSRule
|
|||
context.Init(source);
|
||||
context.Begin();
|
||||
var selector = HostHelper.GetSelector(source, context).ToArray().FirstOrDefault(s => s.Name == name);
|
||||
return new SelectorVisitor(selector.Id, selector.Source, selector.Spec.If);
|
||||
return new SelectorVisitor(context, selector.Id, selector.Source, selector.Spec.If);
|
||||
}
|
||||
|
||||
private static string GetSourcePath(string fileName)
|
||||
|
|
|
@ -7,7 +7,6 @@ using PSRule.Configuration;
|
|||
using PSRule.Definitions.Baselines;
|
||||
using PSRule.Pipeline;
|
||||
using Xunit;
|
||||
using static PSRule.Pipeline.TargetBinder;
|
||||
|
||||
namespace PSRule
|
||||
{
|
||||
|
@ -20,15 +19,15 @@ namespace PSRule
|
|||
var targetObject = GetTargetObject();
|
||||
binder.Bind(targetObject);
|
||||
|
||||
var m1 = binder.Using("Module1");
|
||||
var m1 = binder.Result("Module1");
|
||||
Assert.Equal("Name1", m1.TargetName);
|
||||
Assert.Equal("Type1", m1.TargetType);
|
||||
|
||||
var m2 = binder.Using("Module2");
|
||||
var m2 = binder.Result("Module2");
|
||||
Assert.Equal("Name2", m2.TargetName);
|
||||
Assert.Equal("Type1", m2.TargetType);
|
||||
|
||||
var m0 = binder.Using(".");
|
||||
var m0 = binder.Result(".");
|
||||
Assert.Equal("Name1", m0.TargetName);
|
||||
Assert.Equal("System.Management.Automation.PSCustomObject", m0.TargetType);
|
||||
}
|
||||
|
@ -40,15 +39,15 @@ namespace PSRule
|
|||
var targetObject = new TargetObject(PSObject.AsPSObject(JToken.Parse("{ \"name\": \"Name1\", \"type\": \"Type1\", \"AlternativeName\": \"Name2\", \"AlternativeType\": \"Type2\" }")));
|
||||
binder.Bind(targetObject);
|
||||
|
||||
var m1 = binder.Using("Module1");
|
||||
var m1 = binder.Result("Module1");
|
||||
Assert.Equal("Name1", m1.TargetName);
|
||||
Assert.Equal("Type1", m1.TargetType);
|
||||
|
||||
var m2 = binder.Using("Module2");
|
||||
var m2 = binder.Result("Module2");
|
||||
Assert.Equal("Name2", m2.TargetName);
|
||||
Assert.Equal("Type1", m2.TargetType);
|
||||
|
||||
var m0 = binder.Using(".");
|
||||
var m0 = binder.Result(".");
|
||||
Assert.Equal("Name1", m0.TargetName);
|
||||
Assert.Equal("System.Management.Automation.PSCustomObject", m0.TargetType);
|
||||
}
|
||||
|
@ -93,13 +92,13 @@ namespace PSRule
|
|||
));
|
||||
|
||||
option.UseScope("Module1");
|
||||
builder.With(new TargetBindingContext("Module1", option.GetTargetBinding()));
|
||||
builder.With("Module1", option.GetTargetBinding());
|
||||
|
||||
option.UseScope("Module2");
|
||||
builder.With(new TargetBindingContext("Module2", option.GetTargetBinding()));
|
||||
builder.With("Module2", option.GetTargetBinding());
|
||||
|
||||
option.UseScope(null);
|
||||
builder.With(new TargetBindingContext(".", option.GetTargetBinding()));
|
||||
builder.With(".", option.GetTargetBinding());
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче