Skip to content

Commit

Permalink
Add tag mapping and tag existance filter (#20)
Browse files Browse the repository at this point in the history
- Added rule tags to results to enable grouping and sorting
- Added support to check for rule tag existence
  • Loading branch information
BernieWhite authored Dec 4, 2018
1 parent ac2394e commit dfd53c7
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 13 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@

## Unreleased

- Added rule tags to results to enable grouping and sorting [#14](https://github.com/BernieWhite/PSRule/issues/14)
- Added support to check for rule tag existence. Use `*` for tag value on `-Tag` parameter with `Invoke-PSRule` and `Get-PSRule`

## v0.1.0-B181212

- Initial pre-release
2 changes: 2 additions & 0 deletions docs/commands/PSRule/en-US/Get-PSRule.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ Accept wildcard characters: False
Only get rules with the specified tags set. If this parameter is not specified all rules in search paths will be returned.
When more then one tag is used, all tags must match. Tag names are not case sensitive, tag values are case sensitive. A tag value of `*` may be used to filter rules to any rule with the tag set, regardless of tag value.

```yaml
Type: Hashtable
Parameter Sets: (All)
Expand Down
6 changes: 5 additions & 1 deletion docs/commands/PSRule/en-US/Invoke-PSRule.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ Accept wildcard characters: False
Only evaluate rules with the specified tags set. If this parameter is not specified all rules in search paths will be evaluated.
When more then one tag is used, all tags must match. Tag names are not case sensitive, tag values are case sensitive. A tag value of `*` may be used to filter rules to any rule with the tag set, regardless of tag value.

```yaml
Type: Hashtable
Parameter Sets: (All)
Expand Down Expand Up @@ -141,8 +143,10 @@ This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable

## OUTPUTS

### System.Object
### PSRule.Rules.RuleResult

## NOTES

## RELATED LINKS

[Get-PSRule](Get-PSRule.md)
5 changes: 3 additions & 2 deletions src/PSRule/Host/HostHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ private static IEnumerable<ILanguageBlock> GetLanguageBlock(PSRuleOption option,

if (ps.HadErrors)
{
throw new System.Exception(ps.Streams.Error[0].Exception.Message, ps.Streams.Error[0].Exception);
throw new Exception(ps.Streams.Error[0].Exception.Message, ps.Streams.Error[0].Exception);
}

foreach (var ir in invokeResults)
Expand All @@ -140,7 +140,8 @@ public static RuleResult InvokeRuleBlock(PSRuleOption option, RuleBlock block, P
var result = new RuleResult(block.Id)
{
TargetObject = inputObject,
TargetName = BindName(inputObject)
TargetName = BindName(inputObject),
Tag = block.Tag?.ToHashtable()
};

LanguageContext._Rule = result;
Expand Down
15 changes: 9 additions & 6 deletions src/PSRule/Rules/RuleResult.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Management.Automation;
using System.Collections;
using System.Management.Automation;

namespace PSRule.Rules
{
Expand All @@ -10,16 +11,18 @@ public RuleResult(string ruleId)
Status = RuleResultOutcome.None;
}

public string RuleName { get; set; }
public string RuleName { get; private set; }

public bool Success { get; set; }
public bool Success { get; internal set; }

public RuleResultOutcome Status { get; set; }
public RuleResultOutcome Status { get; internal set; }

public string Message { get; set; }
public string Message { get; internal set; }

public string TargetName { get; set; }
public string TargetName { get; internal set; }

public PSObject TargetObject { get; internal set; }

public Hashtable Tag { get; internal set; }
}
}
34 changes: 32 additions & 2 deletions src/PSRule/Rules/TagSet.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Dynamic;

namespace PSRule.Rules
{
public sealed class TagSet
public sealed class TagSet : DynamicObject
{
private readonly IEqualityComparer<string> _Comparer;
private readonly Dictionary<string, string> _Tag;

public TagSet()
{
_Comparer = StringComparer.Ordinal;
_Tag = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}

private TagSet(Dictionary<string, string> tag)
{
_Comparer = StringComparer.Ordinal;
_Tag = tag;
}

Expand All @@ -35,8 +39,12 @@ public bool Contains(object key, object value)
{
return false;
}
else if (v == "*")
{
return true;
}

return StringComparer.OrdinalIgnoreCase.Equals(v, _Tag[k]);
return _Comparer.Equals(v, _Tag[k]);
}

public static TagSet FromHashtable(Hashtable hashtable)
Expand All @@ -55,5 +63,27 @@ public static TagSet FromHashtable(Hashtable hashtable)

return new TagSet(dictionary);
}

public Hashtable ToHashtable()
{
return new Hashtable(_Tag, StringComparer.OrdinalIgnoreCase);
}

public bool ContainsKey(string key)
{
return _Tag.ContainsKey(key);
}

public bool TryGetValue(string key, out string value)
{
return _Tag.TryGetValue(key, out value);
}

public override bool TryGetMember(GetMemberBinder binder, out object result)
{
var found = _Tag.TryGetValue(binder.Name, out string value);
result = value;
return found;
}
}
}
25 changes: 25 additions & 0 deletions tests/PSRule/FromFile.Rule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,31 @@ Rule 'FromFile3' -Tag @{ category = "group1" } {
# Inconclusive
}

# Description: Test for tags
Rule 'WithTag' -Tag @{ severity = 'critical'; feature = 'tag' } {
$True;
}

# Description: Test for tags
Rule 'WithTag2' -Tag @{ feature = 'tag' } {
$True;
}

# Description: Test for tags
Rule 'WithTag3' -Tag @{ severity = 'information'; feature = 'tag' } {
$True;
}

# Description: Test for tags
Rule 'WithTag4' -Tag @{ Severity = 'critical'; feature = 'tag' } {
$True;
}

# Description: Test for tags
Rule 'WithTag5' -Tag @{ severity = 'Critical'; feature = 'tag' } {
$True;
}

Rule 'WithPreconditionTrue' -If { $True } -Tag @{ category = 'precondition' } {
$True;
}
Expand Down
26 changes: 24 additions & 2 deletions tests/PSRule/PSRule.Common.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ Describe 'Invoke-PSRule' {
Value = 1
}

It 'Return passed' {
It 'Returns passed' {
$result = $testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Name 'FromFile1';
$result | Should -Not -BeNullOrEmpty;
$result.Success | Should -Be $True;
$result.TargetName | Should -Be 'TestObject1';
}

It 'Return failure' {
It 'Returns failure' {
$result = $testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Name 'FromFile2';
$result | Should -Not -BeNullOrEmpty;
$result.Success | Should -Be $False;
Expand All @@ -50,6 +50,28 @@ Describe 'Invoke-PSRule' {
$result.Status | Should -Be 'Inconclusive';
}

It 'Processes rule tags' {
# Ensure that rules can be selected by tag and that tags are mapped back to the rule results
$result = $testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Tag @{ feature = 'tag' };
$result | Should -Not -BeNullOrEmpty;
$result.Count | Should -Be 5;
$result.Tag.feature | Should -BeIn 'tag';

# Ensure that tag selection is and'ed together, requiring all tags to be selected
# Tag values, will be matched without case sensitivity, but values are case sensitive
$result = $testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Tag @{ feature = 'tag'; severity = 'critical'; };
$result | Should -Not -BeNullOrEmpty;
$result.Count | Should -Be 2;
$result.Tag.feature | Should -BeIn 'tag';

# Using a * wildcard in tag filter, matches rules with the tag regardless of value
$result = $testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Tag @{ feature = 'tag'; severity = '*'; };
$result | Should -Not -BeNullOrEmpty;
$result.Count | Should -Be 4;
$result.Tag.feature | Should -BeIn 'tag';
$result.Tag.severity | Should -BeIn 'critical', 'information';
}

It 'Processes rule preconditions' {
$result = $testObject | Invoke-PSRule -Path (Join-Path -Path $here -ChildPath 'FromFile.Rule.ps1') -Tag @{ category = 'precondition' } -Status All;
$result | Should -Not -BeNullOrEmpty;
Expand Down

0 comments on commit dfd53c7

Please sign in to comment.