Skip to content

Adding DscExamplesPresent rule #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions RuleDocumentation/DscExamplesPresent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#DscExamplesPresent
**Severity Level: Information**


##Description

Checks that DSC examples for given resource are present.

##How to Fix

To fix a violation of this rule, please make sure Examples directory is present:
* For non-class based resources it should exist at the same folder level as DSCResources folder.
* For class based resources it should be present at the same folder level as resource psm1 file.

Examples folder should contain sample configuration for given resource - file name should contain resource's name.

##Example

### Non-class based resource

Let's assume we have non-class based resource with a following file structure:

* xAzure
* DSCResources
* MSFT_xAzureSubscription
* MSFT_xAzureSubscription.psm1
* MSFT_xAzureSubscription.schema.mof

In this case, to fix this warning, we should add examples in a following way:

* xAzure
* DSCResources
* MSFT_xAzureSubscription
* MSFT_xAzureSubscription.psm1
* MSFT_xAzureSubscription.schema.mof
* Examples
* MSFT_xAzureSubscription_AddSubscriptionExample.ps1
* MSFT_xAzureSubscription_RemoveSubscriptionExample.ps1

### Class based resource

Let's assume we have class based resource with a following file structure:

* MyDscResource
* MyDscResource.psm1
* MyDscresource.psd1

In this case, to fix this warning, we should add examples in a following way:

* MyDscResource
* MyDscResource.psm1
* MyDscresource.psd1
* Tests
* MyDscResource_Example1.ps1
* MyDscResource_Example2.ps1
164 changes: 164 additions & 0 deletions Rules/DscExamplesPresent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//
// Copyright (c) Microsoft Corporation.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//

using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation.Language;
using Microsoft.Windows.Powershell.ScriptAnalyzer.Generic;
using System.ComponentModel.Composition;
using System.Globalization;
using System.IO;
using System.Management.Automation;

namespace Microsoft.Windows.Powershell.ScriptAnalyzer.BuiltinRules
{
/// <summary>
/// DscExamplesPresent: Checks that DSC examples for given resource are present.
/// Rule expects directory Examples to be present:
/// For non-class based resources it should exist at the same folder level as DSCResources folder.
/// For class based resources it should be present at the same folder level as resource psm1 file.
/// Examples folder should contain sample configuration for given resource - file name should contain resource's name.
/// </summary>
[Export(typeof(IDSCResourceRule))]
public class DscExamplesPresent : IDSCResourceRule
{
/// <summary>
/// AnalyzeDSCResource: Analyzes given DSC Resource
/// </summary>
/// <param name="ast">The script's ast</param>
/// <param name="fileName">The name of the script file being analyzed</param>
/// <returns>The results of the analysis</returns>
public IEnumerable<DiagnosticRecord> AnalyzeDSCResource(Ast ast, string fileName)
{
String fileNameOnly = Path.GetFileName(fileName);
String resourceName = Path.GetFileNameWithoutExtension(fileNameOnly);
String examplesQuery = String.Format("*{0}*", resourceName);
Boolean examplesPresent = false;
String expectedExamplesPath = Path.Combine(new String[] {fileName, "..", "..", "..", "Examples"});

// Verify examples are present
if (Directory.Exists(expectedExamplesPath))
{
DirectoryInfo examplesFolder = new DirectoryInfo(expectedExamplesPath);
FileInfo[] exampleFiles = examplesFolder.GetFiles(examplesQuery);
if (exampleFiles.Length != 0)
{
examplesPresent = true;
}
}

// Return error if no examples present
if (!examplesPresent)
{
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentNoExamplesError, resourceName),
ast.Extent, GetName(), DiagnosticSeverity.Information, fileName);
}
}

/// <summary>
/// AnalyzeDSCClass: Analyzes given DSC class
/// </summary>
/// <param name="ast"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public IEnumerable<DiagnosticRecord> AnalyzeDSCClass(Ast ast, string fileName)
{
String resourceName = null;

IEnumerable<Ast> dscClasses = ast.FindAll(item =>
item is TypeDefinitionAst
&& ((item as TypeDefinitionAst).IsClass)
&& (item as TypeDefinitionAst).Attributes.Any(attr => String.Equals("DSCResource", attr.TypeName.FullName, StringComparison.OrdinalIgnoreCase)), true);

foreach (TypeDefinitionAst dscClass in dscClasses)
{
resourceName = dscClass.Name;

String examplesQuery = String.Format("*{0}*", resourceName);
Boolean examplesPresent = false;
String expectedExamplesPath = Path.Combine(new String[] {fileName, "..", "Examples"});

// Verify examples are present
if (Directory.Exists(expectedExamplesPath))
{
DirectoryInfo examplesFolder = new DirectoryInfo(expectedExamplesPath);
FileInfo[] exampleFiles = examplesFolder.GetFiles(examplesQuery);
if (exampleFiles.Length != 0)
{
examplesPresent = true;
}
}

// Return error if no examples present
if (!examplesPresent)
{
yield return new DiagnosticRecord(string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentNoExamplesError, resourceName),
dscClass.Extent, GetName(), DiagnosticSeverity.Information, fileName);
}
}
}

/// <summary>
/// GetName: Retrieves the name of this rule.
/// </summary>
/// <returns>The name of this rule</returns>
public string GetName()
{
return string.Format(CultureInfo.CurrentCulture, Strings.NameSpaceFormat, GetSourceName(), Strings.DscExamplesPresent);
}

/// <summary>
/// GetCommonName: Retrieves the Common name of this rule.
/// </summary>
/// <returns>The common name of this rule</returns>
public string GetCommonName()
{
return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentCommonName);
}

/// <summary>
/// GetDescription: Retrieves the description of this rule.
/// </summary>
/// <returns>The description of this rule</returns>
public string GetDescription()
{
return string.Format(CultureInfo.CurrentCulture, Strings.DscExamplesPresentDescription);
}

/// <summary>
/// GetSourceType: Retrieves the type of the rule: builtin, managed or module.
/// </summary>
public SourceType GetSourceType()
{
return SourceType.Builtin;
}

/// <summary>
/// GetSeverity: Retrieves the severity of the rule: error, warning or information.
/// </summary>
/// <returns></returns>
public RuleSeverity GetSeverity()
{
return RuleSeverity.Information;
}

/// <summary>
/// GetSourceName: Retrieves the module/assembly name the rule is from.
/// </summary>
public string GetSourceName()
{
return string.Format(CultureInfo.CurrentCulture, Strings.DSCSourceName);
}
}

}
2 changes: 1 addition & 1 deletion Rules/ReturnCorrectTypesForDSCFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public SourceType GetSourceType()
}

/// <summary>
/// GetSeverity: Retrieves the severity of the rule: error, warning of information.
/// GetSeverity: Retrieves the severity of the rule: error, warning or information.
/// </summary>
/// <returns></returns>
public RuleSeverity GetSeverity()
Expand Down
1 change: 1 addition & 0 deletions Rules/ScriptAnalyzerBuiltinRules.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<Compile Include="AvoidUsingPlainTextForPassword.cs" />
<Compile Include="AvoidUsingWMIObjectCmdlet.cs" />
<Compile Include="AvoidUsingWriteHost.cs" />
<Compile Include="DscExamplesPresent.cs" />
<Compile Include="UseOutputTypeCorrectly.cs" />
<Compile Include="MissingModuleManifestField.cs" />
<Compile Include="PossibleIncorrectComparisonWithNull.cs" />
Expand Down
36 changes: 36 additions & 0 deletions Rules/Strings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions Rules/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -690,4 +690,16 @@
<data name="UseOutputTypeCorrectlyName" xml:space="preserve">
<value>UseOutputTypeCorrectly</value>
</data>
<data name="DscExamplesPresent" xml:space="preserve">
<value>DscExamplesPresent</value>
</data>
<data name="DscExamplesPresentCommonName" xml:space="preserve">
<value>DSC examples are present</value>
</data>
<data name="DscExamplesPresentDescription" xml:space="preserve">
<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>
</data>
<data name="DscExamplesPresentNoExamplesError" xml:space="preserve">
<value>No examples found for resource '{0}'</value>
</data>
</root>
70 changes: 70 additions & 0 deletions Tests/Rules/DscExamplesPresent.tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
Import-Module -Verbose PSScriptAnalyzer

$currentPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$ruleName = "PSDSCDscExamplesPresent"

Describe "DscExamplesPresent rule in class based resource" {

$examplesPath = "$currentPath\DSCResources\MyDscResource\Examples"
$classResourcePath = "$currentPath\DSCResources\MyDscResource\MyDscResource.psm1"

Context "When examples absent" {

$violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName}
$violationMessage = "No examples found for resource 'FileResource'"

It "has 1 missing examples violation" {
$violations.Count | Should Be 1
}

It "has the correct description message" {
$violations[0].Message | Should Match $violationMessage
}
}

Context "When examples present" {
New-Item -Path $examplesPath -ItemType Directory
New-Item -Path "$examplesPath\FileResource_Example.psm1" -ItemType File

$noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $classResourcePath | Where-Object {$_.RuleName -eq $ruleName}

It "returns no violations" {
$noViolations.Count | Should Be 0
}

Remove-Item -Path $examplesPath -Recurse -Force
}
}

Describe "DscExamplesPresent rule in regular (non-class) based resource" {

$examplesPath = "$currentPath\Examples"
$resourcePath = "$currentPath\DSCResources\MSFT_WaitForAll\MSFT_WaitForAll.psm1"

Context "When examples absent" {

$violations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName}
$violationMessage = "No examples found for resource 'MSFT_WaitForAll'"

It "has 1 missing examples violation" {
$violations.Count | Should Be 1
}

It "has the correct description message" {
$violations[0].Message | Should Match $violationMessage
}
}

Context "When examples present" {
New-Item -Path $examplesPath -ItemType Directory
New-Item -Path "$examplesPath\MSFT_WaitForAll_Example.psm1" -ItemType File

$noViolations = Invoke-ScriptAnalyzer -ErrorAction SilentlyContinue $resourcePath | Where-Object {$_.RuleName -eq $ruleName}

It "returns no violations" {
$noViolations.Count | Should Be 0
}

Remove-Item -Path $examplesPath -Recurse -Force
}
}