From 06df7beb08be814dda5f952df87a5fa3824c6358 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Sun, 5 Jan 2020 21:47:22 +1000 Subject: [PATCH] Add CI and update readme (#8) --- .azure-pipelines/azure-pipelines.yaml | 154 +++++++++++++ .ps-rule/OpenSource.Rule.ps1 | 54 +++++ .ps-rule/Rule.Rule.ps1 | 22 ++ .vscode/tasks.json | 75 ++++++ README.md | 6 + RuleToc.Doc.ps1 | 31 +++ docs/rules/en/module.md | 28 +++ pipeline.build.ps1 | 316 ++++++++++++++++++++++++++ ps-rule.yaml | 6 + 9 files changed, 692 insertions(+) create mode 100644 .azure-pipelines/azure-pipelines.yaml create mode 100644 .ps-rule/OpenSource.Rule.ps1 create mode 100644 .ps-rule/Rule.Rule.ps1 create mode 100644 .vscode/tasks.json create mode 100644 RuleToc.Doc.ps1 create mode 100644 docs/rules/en/module.md create mode 100644 pipeline.build.ps1 create mode 100644 ps-rule.yaml diff --git a/.azure-pipelines/azure-pipelines.yaml b/.azure-pipelines/azure-pipelines.yaml new file mode 100644 index 0000000..cbfa1e7 --- /dev/null +++ b/.azure-pipelines/azure-pipelines.yaml @@ -0,0 +1,154 @@ +# Azure DevOps +# CI pipeline for PSRule.Rules.CAF + +variables: + version: '0.1.0' + buildConfiguration: 'Release' + disable.coverage.autogenerate: 'true' + + # Use build number format, i.e. 0.1.0-B1811001 +name: $(version)-B$(date:yyMM)$(rev:rrr) + +trigger: + branches: + include: + - 'master' + tags: + include: + - 'v0.*' + +pr: + branches: + include: + - 'master' + +stages: + +# Build pipeline +- stage: Build + displayName: Build + jobs: + - job: + strategy: + matrix: + Linux: + displayName: 'Linux' + imageName: 'ubuntu-latest' + MacOS: + displayName: 'MacOS' + imageName: 'macOS-latest' + Windows: + displayName: 'Windows' + imageName: 'vs2017-win2016' + publish: 'true' + analysis: 'true' + coverage: 'true' + pool: + vmImage: $(imageName) + displayName: 'PowerShell' + steps: + + # Install pipeline dependencies + - powershell: ./.azure-pipelines/pipeline-deps.ps1 + displayName: 'Install dependencies' + + # Build module + - powershell: Invoke-Build -Configuration $(buildConfiguration) -Build $(Build.BuildNumber) + displayName: 'Build module' + + # Pester test results + - task: PublishTestResults@2 + displayName: 'Publish Pester results' + inputs: + testRunTitle: 'Pester on $(imageName)' + testRunner: NUnit + testResultsFiles: 'reports/pester-unit.xml' + mergeTestResults: true + platform: $(imageName) + configuration: $(buildConfiguration) + publishRunAttachments: true + condition: succeededOrFailed() + + # PSRule results + - task: PublishTestResults@2 + displayName: 'Publish PSRule results' + inputs: + testRunTitle: 'PSRule on $(imageName)' + testRunner: NUnit + testResultsFiles: 'reports/ps-rule*.xml' + mergeTestResults: true + platform: $(imageName) + configuration: $(buildConfiguration) + publishRunAttachments: true + condition: succeededOrFailed() + + # Generate Code Coverage report + - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 + displayName: 'Code coverage report generator' + inputs: + reports: 'reports\pester-coverage.xml' + targetdir: 'reports\coverage' + sourcedirs: 'src\PSRule.Rules.CAF' + reporttypes: 'HtmlInline_AzurePipelines;Cobertura;Badges' + tag: $(Build.BuildNumber) + condition: eq(variables['coverage'], 'true') + + # Publish Code Coverage report + - task: PublishCodeCoverageResults@1 + displayName: 'Publish Pester code coverage' + inputs: + codeCoverageTool: 'Cobertura' + summaryFileLocation: 'reports/coverage/Cobertura.xml' + reportDirectory: 'reports/coverage' + condition: eq(variables['coverage'], 'true') + + # Generate artifacts + - task: PublishPipelineArtifact@0 + displayName: 'Publish module' + inputs: + artifactName: PSRule.Rules.CAF + targetPath: out/modules/PSRule.Rules.CAF + condition: and(succeeded(), eq(variables['publish'], 'true')) + +# Release pipeline +# - stage: Release +# displayName: Release +# dependsOn: Build +# condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v0.')) +# jobs: +# - job: +# displayName: Live +# pool: +# vmImage: 'ubuntu-16.04' +# variables: +# isPreRelease: $[contains(variables['Build.SourceBranchName'], '-B')] +# steps: + +# # Download module from build +# - task: DownloadPipelineArtifact@1 +# displayName: 'Download module' +# inputs: +# artifactName: PSRule.Rules.CAF +# downloadPath: $(Build.SourcesDirectory)/out/modules/PSRule.Rules.CAF + +# # Install pipeline dependencies +# - powershell: ./.azure-pipelines/pipeline-deps.ps1 +# displayName: 'Install dependencies' + +# # Install pipeline dependencies and build module +# - powershell: Invoke-Build Release -ApiKey $(apiKey) +# displayName: 'Publish module' + +# # Update GitHub release +# - task: GitHubRelease@0 +# displayName: 'GitHub release' +# inputs: +# gitHubConnection: 'AzureDevOps-PSRule.Rules.CAF' +# repositoryName: '$(Build.Repository.Name)' +# action: edit +# tag: '$(Build.SourceBranchName)' +# releaseNotesSource: input +# releaseNotes: 'See [change log](https://github.com/Microsoft/PSRule.Rules.CAF/blob/master/CHANGELOG.md)' +# assetUploadMode: replace +# addChangeLog: false +# isPreRelease: $(isPreRelease) diff --git a/.ps-rule/OpenSource.Rule.ps1 b/.ps-rule/OpenSource.Rule.ps1 new file mode 100644 index 0000000..2b56edb --- /dev/null +++ b/.ps-rule/OpenSource.Rule.ps1 @@ -0,0 +1,54 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Synopsis: Check for recommended community files +Rule 'OpenSource.Community' -Type 'System.IO.DirectoryInfo' { + $requiredFiles = @( + 'CHANGELOG.md' + 'LICENSE' + 'CODE_OF_CONDUCT.md' + 'CONTRIBUTING.md' + 'SECURITY.md' + 'README.md' + '.github/CODEOWNERS' + '.github/PULL_REQUEST_TEMPLATE.md' + ) + Test-Path -Path $TargetObject.FullName; + for ($i = 0; $i -lt $requiredFiles.Length; $i++) { + $filePath = Join-Path -Path $TargetObject.FullName -ChildPath $requiredFiles[$i]; + $Assert.Create((Test-Path -Path $filePath -PathType Leaf), "$($requiredFiles[$i]) does not exist"); + } +} + +# Synopsis: Check for license in code files +Rule 'OpenSource.License' -Type 'System.IO.FileInfo' -If { $TargetObject.Extension -in '.cs', '.ps1', '.psd1', '.psm1' } { + $commentPrefix = "`# "; + if ($TargetObject.Extension -eq '.cs') { + $commentPrefix = '// ' + } + $header = GetLicenseHeader -CommentPrefix $commentPrefix; + $content = Get-Content -Path $TargetObject.FullName -Raw; + $content.StartsWith($header); +} + +function global:GetLicenseHeader { + [CmdletBinding()] + [OutputType([String])] + param ( + [Parameter(Mandatory = $True)] + [String]$CommentPrefix + ) + process { + $text = @( + 'Copyright (c) Microsoft Corporation.' + 'Licensed under the MIT License.' + ) + $builder = [System.Text.StringBuilder]::new(); + foreach ($line in $text) { + $Null = $builder.Append($CommentPrefix); + $Null = $builder.Append($line); + $Null = $builder.Append([System.Environment]::NewLine); + } + return $builder.ToString(); + } +} diff --git a/.ps-rule/Rule.Rule.ps1 b/.ps-rule/Rule.Rule.ps1 new file mode 100644 index 0000000..b92f609 --- /dev/null +++ b/.ps-rule/Rule.Rule.ps1 @@ -0,0 +1,22 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Synopsis: Use short rule names +Rule 'Rule.Name' -Type 'PSRule.Rules.Rule' { + Recommend 'Rule name should be less than 35 characters to prevent being truncated.' + Reason "The rule name is too long." + $TargetObject.RuleName.Length -le 35 + $TargetObject.RuleName.StartsWith('CAF.') +} + +# Synopsis: Complete help documentation +Rule 'Rule.Help' -Type 'PSRule.Rules.Rule' { + $Assert.HasFieldValue($TargetObject, 'Info.Synopsis') + $Assert.HasFieldValue($TargetObject, 'Info.Description') + $Assert.HasFieldValue($TargetObject, 'Info.Recommendation') +} + +# Synopsis: Use online help +Rule 'Rule.OnlineHelp' -Type 'PSRule.Rules.Rule' { + $Assert.HasFieldValue($TargetObject, 'Info.Annotations.''online version''') +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..60689ab --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,75 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "test", + "type": "shell", + "command": "Invoke-Build Test -AssertStyle Client", + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": [ + "$pester" + ], + "presentation": { + "clear": true, + "panel": "dedicated" + } + }, + { + "label": "coverage", + "type": "shell", + "command": "Invoke-Build Test -CodeCoverage", + "problemMatcher": [ "$pester" ], + "presentation": { + "clear": true, + "panel": "dedicated" + } + }, + { + "label": "build", + "type": "shell", + "command": "Invoke-Build Build", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [], + "presentation": { + "clear": true, + "panel": "dedicated" + } + }, + { + "label": "clean", + "type": "shell", + "command": "Invoke-Build Clean", + "problemMatcher": [] + }, + { + "label": "script-analyzer", + "type": "shell", + "command": "Invoke-Build Analyze", + "problemMatcher": [], + "presentation": { + "clear": true, + "panel": "dedicated" + } + }, + { + "label": "build-docs", + "type": "shell", + "command": "Invoke-Build BuildHelp", + "problemMatcher": [] + }, + { + "label": "scaffold-docs", + "type": "shell", + "command": "Invoke-Build ScaffoldHelp", + "problemMatcher": [] + } + ] +} diff --git a/README.md b/README.md index 5b6bbcd..f939c5c 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ If you do not see your problem captured, please file a new issue and follow the If you have any problems with the [PSRule][engine] engine, please check the project GitHub [issues](https://github.com/Microsoft/PSRule/issues) page instead. +## Rule reference + +For a list of rules included in the `PSRule.Rules.CAF` module see: + +- [Module rule reference](docs/rules/en/module.md) + ## Changes and versioning Modules in this repository will use the [semantic versioning](http://semver.org/) model to declare breaking changes from v1.0.0. diff --git a/RuleToc.Doc.ps1 b/RuleToc.Doc.ps1 new file mode 100644 index 0000000..be1b2b3 --- /dev/null +++ b/RuleToc.Doc.ps1 @@ -0,0 +1,31 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Document 'module' { + Title 'Module rule reference' + + Import-Module .\out\modules\PSRule.Rules.CAF + $rules = Get-PSRule -Module PSRule.Rules.CAF -WarningAction SilentlyContinue | + Add-Member -MemberType ScriptProperty -Name Category -Value { $this.Info.Annotations.category } -PassThru | + Sort-Object -Property Category; + + Section 'Baselines' { + # 'The following baselines are included within `PSRule.Rules.CAF`.' + } + + Section 'Rules' { + 'The following rules are included within `PSRule.Rules.CAF`.' + + $categories = $rules | Group-Object -Property Category; + + foreach ($category in $categories) { + Section "$($category.Name)" { + $category.Group | + Sort-Object -Property RuleName | + Table -Property @{ Name = 'Name'; Expression = { + "[$($_.RuleName)]($($_.RuleName).md)" + }}, Synopsis + } + } + } +} diff --git a/docs/rules/en/module.md b/docs/rules/en/module.md new file mode 100644 index 0000000..95c9e1e --- /dev/null +++ b/docs/rules/en/module.md @@ -0,0 +1,28 @@ +# Module rule reference + +## Rules + +The following rules are included within `PSRule.Rules.CAF`. + +### Naming + +Name | Synopsis +---- | -------- +[CAF.Name.Connection](CAF.Name.Connection.md) | Virtual network gateway connection names should use a standard prefix and meet naming requirements. +[CAF.Name.LoadBalancer](CAF.Name.LoadBalancer.md) | Load balancer names should use a standard prefix and meet naming requirements. +[CAF.Name.NSG](CAF.Name.NSG.md) | Network security group (NSG) names should use a standard prefix and meet naming requirements. +[CAF.Name.PublicIP](CAF.Name.PublicIP.md) | Public IP address names should use a standard prefix and meet naming requirements. +[CAF.Name.RG](CAF.Name.RG.md) | Resource group names should use a standard prefix and meet naming requirements. +[CAF.Name.Route](CAF.Name.Route.md) | Route table names should use a standard prefix and meet naming requirements. +[CAF.Name.Storage](CAF.Name.Storage.md) | Storage account names should use a standard prefix and meet naming requirements. +[CAF.Name.Subnet](CAF.Name.Subnet.md) | Subnet names should use a standard prefix and meet naming requirements. +[CAF.Name.VM](CAF.Name.VM.md) | Virtual machine names should use a standard prefix and meet naming requirements. +[CAF.Name.VNET](CAF.Name.VNET.md) | Virtual network names should use a standard prefix and meet naming requirements. +[CAF.Name.VNG](CAF.Name.VNG.md) | Virtual network gateway names should use a standard prefix and meet naming requirements. + +### Tagging + +Name | Synopsis +---- | -------- +[CAF.Tag.Environment](CAF.Tag.Environment.md) | Tag resources and resource groups with a valid environment. +[CAF.Tag.Required](CAF.Tag.Required.md) | Tag resources and resource groups with mandatory tags. diff --git a/pipeline.build.ps1 b/pipeline.build.ps1 new file mode 100644 index 0000000..c4bd83b --- /dev/null +++ b/pipeline.build.ps1 @@ -0,0 +1,316 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[CmdletBinding()] +param ( + [Parameter(Mandatory = $False)] + [String]$Build = '0.0.1', + + [Parameter(Mandatory = $False)] + [String]$Configuration = 'Debug', + + [Parameter(Mandatory = $False)] + [String]$ApiKey, + + [Parameter(Mandatory = $False)] + [Switch]$CodeCoverage = $False, + + [Parameter(Mandatory = $False)] + [String]$ArtifactPath = (Join-Path -Path $PWD -ChildPath out/modules), + + [Parameter(Mandatory = $False)] + [String]$AssertStyle = 'AzurePipelines' +) + +Write-Host -Object "[Pipeline] -- PWD: $PWD" -ForegroundColor Green; +Write-Host -Object "[Pipeline] -- ArtifactPath: $ArtifactPath" -ForegroundColor Green; +Write-Host -Object "[Pipeline] -- BuildNumber: $($Env:BUILD_BUILDNUMBER)" -ForegroundColor Green; +Write-Host -Object "[Pipeline] -- SourceBranch: $($Env:BUILD_SOURCEBRANCH)" -ForegroundColor Green; +Write-Host -Object "[Pipeline] -- SourceBranchName: $($Env:BUILD_SOURCEBRANCHNAME)" -ForegroundColor Green; + +if ($Env:SYSTEM_DEBUG -eq 'true') { + $VerbosePreference = 'Continue'; +} + +if ($Env:BUILD_SOURCEBRANCH -like '*/tags/*' -and $Env:BUILD_SOURCEBRANCHNAME -like 'v0.*') { + $Build = $Env:BUILD_SOURCEBRANCHNAME.Substring(1); +} + +$version = $Build; +$versionSuffix = [String]::Empty; + +if ($version -like '*-*') { + [String[]]$versionParts = $version.Split('-', [System.StringSplitOptions]::RemoveEmptyEntries); + $version = $versionParts[0]; + + if ($versionParts.Length -eq 2) { + $versionSuffix = $versionParts[1]; + } +} + +Write-Host -Object "[Pipeline] -- Using version: $version" -ForegroundColor Green; +Write-Host -Object "[Pipeline] -- Using versionSuffix: $versionSuffix" -ForegroundColor Green; + +if ($Env:coverage -eq 'true') { + $CodeCoverage = $True; +} + +# Copy the PowerShell modules files to the destination path +function CopyModuleFiles { + param ( + [Parameter(Mandatory = $True)] + [String]$Path, + + [Parameter(Mandatory = $True)] + [String]$DestinationPath + ) + + process { + $sourcePath = Resolve-Path -Path $Path; + + Get-ChildItem -Path $sourcePath -Recurse -File -Include *.ps1,*.psm1,*.psd1,*.ps1xml,*.yaml | Where-Object -FilterScript { + ($_.FullName -notmatch '(\.(cs|csproj)|(\\|\/)(obj|bin))') + } | ForEach-Object -Process { + $filePath = $_.FullName.Replace($sourcePath, $destinationPath); + + $parentPath = Split-Path -Path $filePath -Parent; + + if (!(Test-Path -Path $parentPath)) { + $Null = New-Item -Path $parentPath -ItemType Directory -Force; + } + + Copy-Item -Path $_.FullName -Destination $filePath -Force; + }; + } +} + +function Get-RepoRuleData { + [CmdletBinding()] + param ( + [Parameter(Position = 0, Mandatory = $False)] + [String]$Path = $PWD + ) + process { + GetPathInfo -Path $Path -Verbose:$VerbosePreference; + } +} + +function GetPathInfo { + [CmdletBinding()] + param ( + [Parameter(Mandatory = $True)] + [String]$Path + ) + begin { + $items = New-Object -TypeName System.Collections.ArrayList; + } + process { + $Null = $items.Add((Get-Item -Path $Path)); + $files = @(Get-ChildItem -Path $Path -File -Recurse -Include *.ps1,*.psm1,*.psd1,*.cs | Where-Object { + !($_.FullName -like "*.Designer.cs") -and + !($_.FullName -like "*/bin/*") -and + !($_.FullName -like "*/obj/*") -and + !($_.FullName -like "*\obj\*") -and + !($_.FullName -like "*\bin\*") -and + !($_.FullName -like "*\out\*") -and + !($_.FullName -like "*/out/*") + }); + $Null = $items.AddRange($files); + } + end { + $items; + } +} + +task VersionModule ModuleDependencies, { + $modulePath = Join-Path -Path $ArtifactPath -ChildPath PSRule.Rules.CAF; + $manifestPath = Join-Path -Path $modulePath -ChildPath PSRule.Rules.CAF.psd1; + Write-Verbose -Message "[VersionModule] -- Checking module path: $modulePath"; + + if (![String]::IsNullOrEmpty($Build)) { + # Update module version + if (![String]::IsNullOrEmpty($version)) { + Write-Verbose -Message "[VersionModule] -- Updating module manifest ModuleVersion"; + Update-ModuleManifest -Path $manifestPath -ModuleVersion $version; + } + + # Update pre-release version + if (![String]::IsNullOrEmpty($versionSuffix)) { + Write-Verbose -Message "[VersionModule] -- Updating module manifest Prerelease"; + Update-ModuleManifest -Path $manifestPath -Prerelease $versionSuffix; + } + } + + $manifest = Test-ModuleManifest -Path $manifestPath; + $requiredModules = $manifest.RequiredModules | ForEach-Object -Process { + if ($_.Name -eq 'PSRule' -and $Configuration -eq 'Release') { + @{ ModuleName = 'PSRule'; ModuleVersion = '0.13.0' } + } + else { + @{ ModuleName = $_.Name; ModuleVersion = $_.Version } + } + }; + Update-ModuleManifest -Path $manifestPath -RequiredModules $requiredModules; + $manifestContent = Get-Content -Path $manifestPath -Raw; + $manifestContent = $manifestContent -replace 'PSRule = ''System.Collections.Hashtable''', 'PSRule = @{ Baseline = ''CAF.Default'' }'; + $manifestContent | Set-Content -Path $manifestPath; +} + +# Synopsis: Publish to PowerShell Gallery +task ReleaseModule VersionModule, { + $modulePath = (Join-Path -Path $ArtifactPath -ChildPath PSRule.Rules.CAF); + Write-Verbose -Message "[ReleaseModule] -- Checking module path: $modulePath"; + + if (!(Test-Path -Path $modulePath)) { + Write-Error -Message "[ReleaseModule] -- Module path does not exist"; + } + elseif (![String]::IsNullOrEmpty($ApiKey)) { + Publish-Module -Path $modulePath -NuGetApiKey $ApiKey; + } +} + +# Synopsis: Install NuGet provider +task NuGet { + if ($Null -eq (Get-PackageProvider -Name NuGet -ErrorAction Ignore)) { + Install-PackageProvider -Name NuGet -Force -Scope CurrentUser; + } +} + +# Synopsis: Install Pester module +task Pester NuGet, { + if ($Null -eq (Get-InstalledModule -Name Pester -MinimumVersion 4.9.0 -ErrorAction Ignore)) { + Install-Module -Name Pester -MinimumVersion 4.9.0 -Scope CurrentUser -Force -SkipPublisherCheck; + } + Import-Module -Name Pester -Verbose:$False; +} + +# Synopsis: Install PSScriptAnalyzer module +task PSScriptAnalyzer NuGet, { + if ($Null -eq (Get-InstalledModule -Name PSScriptAnalyzer -MinimumVersion 1.18.3 -ErrorAction Ignore)) { + Install-Module -Name PSScriptAnalyzer -MinimumVersion 1.18.3 -Scope CurrentUser -Force; + } + Import-Module -Name PSScriptAnalyzer -Verbose:$False; +} + +# Synopsis: Install PSRule +task PSRule NuGet, { + if ($Null -eq (Get-InstalledModule -Name PSRule -MinimumVersion 0.13.0 -ErrorAction Ignore)) { + Install-Module -Name PSRule -Repository PSGallery -MinimumVersion 0.13.0 -Scope CurrentUser -Force; + } + if ($Null -eq (Get-InstalledModule -Name PSRule.Rules.Azure -MinimumVersion 0.7.0 -ErrorAction Ignore)) { + Install-Module -Name PSRule.Rules.Azure -Repository PSGallery -MinimumVersion 0.7.0 -Scope CurrentUser -Force; + } + Import-Module -Name PSRule.Rules.Azure -Verbose:$False; +} + +# Synopsis: Install PSDocs +task PSDocs NuGet, { + if ($Null -eq (Get-InstalledModule -Name PSDocs -MinimumVersion 0.6.3 -ErrorAction Ignore)) { + Install-Module -Name PSDocs -Repository PSGallery -MinimumVersion 0.6.3 -Scope CurrentUser -Force; + } + Import-Module -Name PSDocs -Verbose:$False; +} + +# Synopsis: Install PlatyPS module +task platyPS { + if ($Null -eq (Get-InstalledModule -Name PlatyPS -MinimumVersion 0.14.0 -ErrorAction Ignore)) { + Install-Module -Name PlatyPS -Scope CurrentUser -MinimumVersion 0.14.0 -Force; + } + Import-Module -Name PlatyPS -Verbose:$False; +} + +# Synopsis: Install module dependencies +task ModuleDependencies NuGet, PSRule, { +} + +task CopyModule { + CopyModuleFiles -Path src/PSRule.Rules.CAF -DestinationPath out/modules/PSRule.Rules.CAF; +} + +# Synopsis: Build modules only +task BuildModule CopyModule + +task TestModule PSRule, Pester, PSScriptAnalyzer, { + # Run Pester tests + $pesterParams = @{ Path = (Join-Path -Path $PWD -ChildPath tests/PSRule.Rules.CAF.Tests); OutputFile = 'reports/pester-unit.xml'; OutputFormat = 'NUnitXml'; PesterOption = @{ IncludeVSCodeMarker = $True }; PassThru = $True; }; + + if ($CodeCoverage) { + $pesterParams.Add('CodeCoverage', (Join-Path -Path $PWD -ChildPath 'out/modules/**/*.psm1')); + $pesterParams.Add('CodeCoverageOutputFile', (Join-Path -Path $PWD -ChildPath reports/pester-coverage.xml)); + } + + if (!(Test-Path -Path reports)) { + $Null = New-Item -Path reports -ItemType Directory -Force; + } + + $results = Invoke-Pester @pesterParams; + + # Throw an error if pester tests failed + if ($Null -eq $results) { + throw 'Failed to get Pester test results.'; + } + elseif ($results.FailedCount -gt 0) { + throw "$($results.FailedCount) tests failed."; + } +} + +# Synopsis: Run validation +task Rules PSRule, { + $assertParams = @{ + Path = './.ps-rule/' + Style = $AssertStyle + OutputFormat = 'NUnit3'; + } + Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.CAF) -Force; + Get-RepoRuleData -Path $PWD | + Assert-PSRule @assertParams -OutputPath reports/ps-rule-file.xml; + + $rules = Get-PSRule -Module PSRule.Rules.CAF; + $rules | Assert-PSRule @assertParams -OutputPath reports/ps-rule-file2.xml; +} + +# Synopsis: Run script analyzer +task Analyze Build, PSScriptAnalyzer, { + Invoke-ScriptAnalyzer -Path out/modules/PSRule.Rules.CAF; +} + +# Synopsis: Build table of content for rules +task BuildRuleDocs Build, PSRule, PSDocs, { + Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.CAF) -Force; + $Null = Invoke-PSDocument -Name module -OutputPath .\docs\rules\en\ -Path .\RuleToc.Doc.ps1; +} + +# Synopsis: Build help +task BuildHelp BuildModule, PlatyPS, { + if (!(Test-Path out/modules/PSRule.Rules.CAF/en/)) { + $Null = New-Item -Path out/modules/PSRule.Rules.CAF/en/ -ItemType Directory -Force; + } + + # Copy generated help into module out path + $Null = Copy-Item -Path docs/rules/en/*.md -Destination out/modules/PSRule.Rules.CAF/en/; +} + +task ScaffoldHelp Build, BuildRuleDocs, { + # Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.CAF) -Force; + # Update-MarkdownHelp -Path '.\docs\commands\PSRule.Rules.CAF\en-US'; +} + +# Synopsis: Add shipit build tag +task TagBuild { + if ($Null -ne $Env:BUILD_DEFINITIONNAME) { + Write-Host "`#`#vso[build.addbuildtag]shipit"; + } +} + +# Synopsis: Remove temp files. +task Clean { + Remove-Item -Path out,reports -Recurse -Force -ErrorAction SilentlyContinue; +} + +task Build Clean, BuildModule, VersionModule, BuildHelp + +task Test Build, Rules, TestModule + +task Release ReleaseModule, TagBuild + +task . Build, Test diff --git a/ps-rule.yaml b/ps-rule.yaml new file mode 100644 index 0000000..4ac8bb2 --- /dev/null +++ b/ps-rule.yaml @@ -0,0 +1,6 @@ +# PSRule options for QA + +binding: + targetName: + - RuleName + - FullName