Skip to content

Commit 3f6ebbe

Browse files
author
Quoc Truong
committed
Merge pull request #95 from KarolKaczmarek/DscExamplesPresentRule
Adding DscExamplesPresent rule
2 parents 2af9cb8 + 8fd01d1 commit 3f6ebbe

File tree

7 files changed

+339
-1
lines changed

7 files changed

+339
-1
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#DscExamplesPresent
2+
**Severity Level: Information**
3+
4+
5+
##Description
6+
7+
Checks that DSC examples for given resource are present.
8+
9+
##How to Fix
10+
11+
To fix a violation of this rule, please make sure Examples directory is present:
12+
* For non-class based resources it should exist at the same folder level as DSCResources folder.
13+
* For class based resources it should be present at the same folder level as resource psm1 file.
14+
15+
Examples folder should contain sample configuration for given resource - file name should contain resource's name.
16+
17+
##Example
18+
19+
### Non-class based resource
20+
21+
Let's assume we have non-class based resource with a following file structure:
22+
23+
* xAzure
24+
* DSCResources
25+
* MSFT_xAzureSubscription
26+
* MSFT_xAzureSubscription.psm1
27+
* MSFT_xAzureSubscription.schema.mof
28+
29+
In this case, to fix this warning, we should add examples in a following way:
30+
31+
* xAzure
32+
* DSCResources
33+
* MSFT_xAzureSubscription
34+
* MSFT_xAzureSubscription.psm1
35+
* MSFT_xAzureSubscription.schema.mof
36+
* Examples
37+
* MSFT_xAzureSubscription_AddSubscriptionExample.ps1
38+
* MSFT_xAzureSubscription_RemoveSubscriptionExample.ps1
39+
40+
### Class based resource
41+
42+
Let's assume we have class based resource with a following file structure:
43+
44+
* MyDscResource
45+
* MyDscResource.psm1
46+
* MyDscresource.psd1
47+
48+
In this case, to fix this warning, we should add examples in a following way:
49+
50+
* MyDscResource
51+
* MyDscResource.psm1
52+
* MyDscresource.psd1
53+
* Tests
54+
* MyDscResource_Example1.ps1
55+
* MyDscResource_Example2.ps1

Rules/DscExamplesPresent.cs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
//
2+
// Copyright (c) Microsoft Corporation.
3+
//
4+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10+
// THE SOFTWARE.
11+
//
12+
13+
using System;
14+
using System.Collections.Generic;
15+
using System.Linq;
16+
using System.Management.Automation.Language;
17+
using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic;
18+
using System.ComponentModel.Composition;
19+
using System.Globalization;
20+
using System.IO;
21+
using System.Management.Automation;
22+
23+
namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
24+
{
25+
/// <summary>
26+
/// DscExamplesPresent: Checks that DSC examples for given resource are present.
27+
/// Rule expects directory Examples to be present:
28+
/// For non-class based resources it should exist at the same folder level as DSCResources folder.
29+
/// For class based resources it should be present at the same folder level as resource psm1 file.
30+
/// Examples folder should contain sample configuration for given resource - file name should contain resource's name.
31+
/// </summary>
32+
[Export(typeof(IDSCResourceRule))]
33+
public class DscExamplesPresent : IDSCResourceRule
34+
{
35+
/// <summary>
36+
/// AnalyzeDSCResource: Analyzes given DSC Resource
37+
/// </summary>
38+
/// <param name="ast">The script's ast</param>
39+
/// <param name="fileName">The name of the script file being analyzed</param>
40+
/// <returns>The results of the analysis</returns>
41+
public IEnumerable<DiagnosticRecord> AnalyzeDSCResource(Ast ast, string fileName)
42+
{
43+
String fileNameOnly = Path.GetFileName(fileName);
44+
String resourceName = Path.GetFileNameWithoutExtension(fileNameOnly);
45+
String examplesQuery = String.Format("*{0}*", resourceName);
46+
Boolean examplesPresent = false;
47+
String expectedExamplesPath = Path.Combine(new String[] {fileName, "..", "..", "..", "Examples"});
48+
49+
// Verify examples are present
50+
if (Directory.Exists(expectedExamplesPath))
51+
{
52+
DirectoryInfo examplesFolder = new DirectoryInfo(expectedExamplesPath);
53+
FileInfo[] exampleFiles = examplesFolder.GetFiles(examplesQuery);
54+
if (exampleFiles.Length != 0)
55+
{
56+
examplesPresent = true;
57+
}
58+
}
59+
60+
// Return error if no examples present
61+
if (!examplesPresent)
62+
{
63+
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentNoExamplesError, resourceName),
64+
ast.Extent, GetName(), DiagnosticSeverity.Information, fileName);
65+
}
66+
}
67+
68+
/// <summary>
69+
/// AnalyzeDSCClass: Analyzes given DSC class
70+
/// </summary>
71+
/// <param name="ast"></param>
72+
/// <param name="fileName"></param>
73+
/// <returns></returns>
74+
public IEnumerable<DiagnosticRecord> AnalyzeDSCClass(Ast ast, string fileName)
75+
{
76+
String resourceName = null;
77+
78+
IEnumerable<Ast> dscClasses = ast.FindAll(item =>
79+
item is TypeDefinitionAst
80+
&& ((item as TypeDefinitionAst).IsClass)
81+
&& (item as TypeDefinitionAst).Attributes.Any(attr => String.Equals("DSCResource", attr.TypeName.FullName, StringComparison.OrdinalIgnoreCase)), true);
82+
83+
foreach (TypeDefinitionAst dscClass in dscClasses)
84+
{
85+
resourceName = dscClass.Name;
86+
87+
String examplesQuery = String.Format("*{0}*", resourceName);
88+
Boolean examplesPresent = false;
89+
String expectedExamplesPath = Path.Combine(new String[] {fileName, "..", "Examples"});
90+
91+
// Verify examples are present
92+
if (Directory.Exists(expectedExamplesPath))
93+
{
94+
DirectoryInfo examplesFolder = new DirectoryInfo(expectedExamplesPath);
95+
FileInfo[] exampleFiles = examplesFolder.GetFiles(examplesQuery);
96+
if (exampleFiles.Length != 0)
97+
{
98+
examplesPresent = true;
99+
}
100+
}
101+
102+
// Return error if no examples present
103+
if (!examplesPresent)
104+
{
105+
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentNoExamplesError, resourceName),
106+
dscClass.Extent, GetName(), DiagnosticSeverity.Information, fileName);
107+
}
108+
}
109+
}
110+
111+
/// <summary>
112+
/// GetName: Retrieves the name of this rule.
113+
/// </summary>
114+
/// <returns>The name of this rule</returns>
115+
public string GetName()
116+
{
117+
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.DscExamplesPresent);
118+
}
119+
120+
/// <summary>
121+
/// GetCommonName: Retrieves the Common name of this rule.
122+
/// </summary>
123+
/// <returns>The common name of this rule</returns>
124+
public string GetCommonName()
125+
{
126+
return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentCommonName);
127+
}
128+
129+
/// <summary>
130+
/// GetDescription: Retrieves the description of this rule.
131+
/// </summary>
132+
/// <returns>The description of this rule</returns>
133+
public string GetDescription()
134+
{
135+
return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentDescription);
136+
}
137+
138+
/// <summary>
139+
/// GetSourceType: Retrieves the type of the rule: builtin, managed or module.
140+
/// </summary>
141+
public SourceType GetSourceType()
142+
{
143+
return SourceType.Builtin;
144+
}
145+
146+
/// <summary>
147+
/// GetSeverity: Retrieves the severity of the rule: error, warning or information.
148+
/// </summary>
149+
/// <returns></returns>
150+
public RuleSeverity GetSeverity()
151+
{
152+
return RuleSeverity.Information;
153+
}
154+
155+
/// <summary>
156+
/// GetSourceName: Retrieves the module/assembly name the rule is from.
157+
/// </summary>
158+
public string GetSourceName()
159+
{
160+
return string.Format(CultureInfo.CurrentCulture, Strings.DSCSourceName);
161+
}
162+
}
163+
164+
}

Rules/ReturnCorrectTypesForDSCFunctions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ public SourceType GetSourceType()
210210
}
211211

212212
/// <summary>
213-
/// GetSeverity: Retrieves the severity of the rule: error, warning of information.
213+
/// GetSeverity: Retrieves the severity of the rule: error, warning or information.
214214
/// </summary>
215215
/// <returns></returns>
216216
public RuleSeverity GetSeverity()

Rules/ScriptAnalyzerBuiltinRules.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
<Compile Include="AvoidUsingPlainTextForPassword.cs" />
6969
<Compile Include="AvoidUsingWMIObjectCmdlet.cs" />
7070
<Compile Include="AvoidUsingWriteHost.cs" />
71+
<Compile Include="DscExamplesPresent.cs" />
7172
<Compile Include="UseOutputTypeCorrectly.cs" />
7273
<Compile Include="MissingModuleManifestField.cs" />
7374
<Compile Include="PossibleIncorrectComparisonWithNull.cs" />

Rules/Strings.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rules/Strings.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,4 +690,16 @@
690690
<data name="UseOutputTypeCorrectlyName" xml:space="preserve">
691691
<value>UseOutputTypeCorrectly</value>
692692
</data>
693+
<data name="DscExamplesPresent" xml:space="preserve">
694+
<value>DscExamplesPresent</value>
695+
</data>
696+
<data name="DscExamplesPresentCommonName" xml:space="preserve">
697+
<value>DSC examples are present</value>
698+
</data>
699+
<data name="DscExamplesPresentDescription" xml:space="preserve">
700+
<value>Every DSC resource module should contain folder "Examples" with sample configurations for every resource. Sample configurations should have resource name they are demonstrating in the title.</value>
701+
</data>
702+
<data name="DscExamplesPresentNoExamplesError" xml:space="preserve">
703+
<value>No examples found for resource '{0}'</value>
704+
</data>
693705
</root>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
Import-Module -Verbose PSScriptAnalyzer
2+
3+
$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path
4+
$ruleName = "PSDSCDscExamplesPresent"
5+
6+
Describe "DscExamplesPresent rule in class based resource" {
7+
8+
$examplesPath = "$currentPath\DSCResources\MyDscResource\Examples"
9+
$classResourcePath = "$currentPath\DSCResources\MyDscResource\MyDscResource.psm1"
10+
11+
Context "When examples absent" {
12+
13+
$violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName}
14+
$violationMessage = "No examples found for resource 'FileResource'"
15+
16+
It "has 1 missing examples violation" {
17+
$violations.Count | Should Be 1
18+
}
19+
20+
It "has the correct description message" {
21+
$violations[0].Message | Should Match $violationMessage
22+
}
23+
}
24+
25+
Context "When examples present" {
26+
New-Item -Path $examplesPath -ItemType Directory
27+
New-Item -Path "$examplesPath\FileResource_Example.psm1" -ItemType File
28+
29+
$noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName}
30+
31+
It "returns no violations" {
32+
$noViolations.Count | Should Be 0
33+
}
34+
35+
Remove-Item -Path $examplesPath -Recurse -Force
36+
}
37+
}
38+
39+
Describe "DscExamplesPresent rule in regular (non-class) based resource" {
40+
41+
$examplesPath = "$currentPath\Examples"
42+
$resourcePath = "$currentPath\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1"
43+
44+
Context "When examples absent" {
45+
46+
$violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName}
47+
$violationMessage = "No examples found for resource 'MSFT_WaitForAll'"
48+
49+
It "has 1 missing examples violation" {
50+
$violations.Count | Should Be 1
51+
}
52+
53+
It "has the correct description message" {
54+
$violations[0].Message | Should Match $violationMessage
55+
}
56+
}
57+
58+
Context "When examples present" {
59+
New-Item -Path $examplesPath -ItemType Directory
60+
New-Item -Path "$examplesPath\MSFT_WaitForAll_Example.psm1" -ItemType File
61+
62+
$noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName}
63+
64+
It "returns no violations" {
65+
$noViolations.Count | Should Be 0
66+
}
67+
68+
Remove-Item -Path $examplesPath -Recurse -Force
69+
}
70+
}

0 commit comments

Comments
 (0)