Add data, field, and module name properties #30 #31 (#34)

This commit is contained in:
Bernie White 2021-04-20 02:51:05 +10:00 коммит произвёл GitHub
Родитель 91dfa1834d
Коммит a6e2112827
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
17 изменённых файлов: 697 добавлений и 171 удалений

Просмотреть файл

@ -2,17 +2,19 @@
# CI pipeline for PSRule.Monitor
variables:
version: '0.1.0'
version: '0.2.0'
buildConfiguration: 'Release'
disable.coverage.autogenerate: 'true'
imageName: 'ubuntu-18.04'
# Use build number format, i.e. 0.1.0-B1811001
# Use build number format, i.e. 0.2.0-B1811001
name: $(version)-B$(date:yyMM)$(rev:rrr)
trigger:
branches:
include:
- 'main'
- 'release/*'
tags:
include:
- 'v0.*'
@ -21,31 +23,19 @@ pr:
branches:
include:
- 'main'
- 'release/*'
stages:
# Build pipeline
- stage: Build
displayName: Build
dependsOn: []
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'
displayName: 'Module'
steps:
# Install pipeline dependencies
@ -56,43 +46,13 @@ stages:
- powershell: Invoke-Build -Configuration $(buildConfiguration) -Build $(Build.BuildNumber)
displayName: 'Build module'
# Run SonarCloud analysis
- powershell: dotnet tool install --global dotnet-sonarscanner
displayName: 'Install Sonar scanner'
condition: and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['analysis'], 'true'))
- script: dotnet sonarscanner begin /k:"BernieWhite_PSRule_Monitor" /o:"berniewhite-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login=$(sonarQubeToken) /v:"$(Build.BuildNumber)" /d:sonar.cs.vscoveragexml.reportsPaths="reports/" /d:sonar.cs.xunit.reportsPaths="reports/"
displayName: 'Prepare SonarCloud'
condition: and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['analysis'], 'true'))
- script: dotnet build
displayName: 'Build solution for analysis'
condition: and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['analysis'], 'true'))
- script: dotnet sonarscanner end /d:sonar.login=$(sonarQubeToken)
displayName: 'Complete SonarCloud'
condition: and(ne(variables['Build.Reason'], 'PullRequest'), eq(variables['analysis'], 'true'))
# DotNet test results
# - task: PublishTestResults@2
# displayName: 'Publish unit test results'
# inputs:
# testRunTitle: 'DotNet on $(imageName)'
# testRunner: VSTest
# testResultsFiles: 'reports/*.trx'
# mergeTestResults: true
# platform: $(imageName)
# configuration: $(buildConfiguration)
# publishRunAttachments: true
# condition: succeededOrFailed()
# Pester test results
- task: PublishTestResults@2
displayName: 'Publish Pester results'
displayName: 'Publish unit test results'
inputs:
testRunTitle: 'Pester on $(imageName)'
testRunner: NUnit
testResultsFiles: 'reports/pester-unit.xml'
testRunTitle: 'DotNet on $(imageName)'
testRunner: VSTest
testResultsFiles: 'reports/*.trx'
mergeTestResults: true
platform: $(imageName)
configuration: $(buildConfiguration)
@ -100,66 +60,141 @@ stages:
condition: succeededOrFailed()
# PSRule results
# - task: PublishTestResults@2
# displayName: 'Publish PSRule results'
# inputs:
# testRunTitle: 'PSRule on $(imageName)'
# testRunner: NUnit
# testResultsFiles: 'reports/rule.report.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'
- task: PublishTestResults@2
displayName: 'Publish PSRule results'
inputs:
reports: 'reports\pester-coverage.xml'
targetdir: 'reports\coverage'
sourcedirs: 'src\PSRule.Monitor'
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')
testRunTitle: 'PSRule on $(imageName)'
testRunner: NUnit
testResultsFiles: 'reports/ps-rule*.xml'
mergeTestResults: true
platform: $(imageName)
configuration: $(buildConfiguration)
publishRunAttachments: true
condition: succeededOrFailed()
# Generate artifacts
- task: PublishPipelineArtifact@0
- publish: out/modules/PSRule.Monitor
displayName: 'Publish module'
artifact: PSRule.Monitor
# Analysis pipeline
- stage: Analysis
displayName: Analysis
dependsOn: []
variables:
skipComponentGovernanceDetection: true
jobs:
- job:
pool:
vmImage: $(imageName)
displayName: 'SonarCloud'
condition: not(eq(variables['Build.Reason'], 'PullRequest'))
steps:
- script: |
echo "##vso[task.setvariable variable=JAVA_HOME]$(JAVA_HOME_11_X64)"
echo "##vso[task.setvariable variable=PATH]$(JAVA_HOME_11_X64)\bin;$(PATH)"
displayName: 'Set Java version'
# Run SonarCloud analysis
- script: dotnet tool install --global dotnet-sonarscanner
displayName: 'Install Sonar scanner'
- script: $HOME/.dotnet/tools/dotnet-sonarscanner begin /k:"BernieWhite_PSRule_Monitor" /o:"berniewhite-github" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.login=$(sonarQubeToken) /v:"$(Build.BuildNumber)" /d:sonar.cs.vscoveragexml.reportsPaths="reports/" /d:sonar.cs.xunit.reportsPaths="reports/"
displayName: 'Prepare SonarCloud'
- script: dotnet build
displayName: 'Build solution for analysis'
- script: $HOME/.dotnet/tools/dotnet-sonarscanner end /d:sonar.login=$(sonarQubeToken)
displayName: 'Complete SonarCloud'
- job: Secret_Scan
pool: 'Hosted VS2017'
displayName: Secret scan
steps:
- task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2
displayName: 'Scan for secrets'
inputs:
artifactName: PSRule.Monitor
targetPath: out/modules/PSRule.Monitor
condition: and(succeeded(), eq(variables['publish'], 'true'))
debugMode: false
toolMajorVersion: V2
- task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@2
displayName: 'Publish scan logs'
continueOnError: true
- task: securedevelopmentteam.vss-secure-development-tools.build-task-postanalysis.PostAnalysis@1
displayName: 'Check for failures'
inputs:
CredScan: true
ToolLogsNotFoundAction: Error
# Test pipeline
- stage: Test
dependsOn: Build
jobs:
- template: jobs/test.yaml
parameters:
name: ubuntu_18_04_coverage
imageName: 'ubuntu-18.04'
displayName: 'PowerShell coverage'
coverage: 'false'
publishResults: 'false'
- template: jobs/test.yaml
parameters:
name: macOS_10_15
displayName: 'PowerShell 7.1 - macOS-10.15'
imageName: 'macOS-10.15'
- template: jobs/test.yaml
parameters:
name: windows
displayName: 'PowerShell 5.1 - win2016'
imageName: 'vs2017-win2016'
- template: jobs/testContainer.yaml
parameters:
name: ps_7_ubuntu_18_04
displayName: 'PowerShell 7.0 - ubuntu-18.04'
imageName: mcr.microsoft.com/powershell
imageTag: 7.0.3-ubuntu-18.04
- template: jobs/testContainer.yaml
parameters:
name: ps_7_1_ubuntu_20_04
displayName: 'PowerShell 7.1 - ubuntu-20.04'
imageName: mcr.microsoft.com/powershell
imageTag: 7.1.3-ubuntu-20.04
- template: jobs/testContainer.yaml
parameters:
name: ps_6_ubuntu_18_04
displayName: 'PowerShell 6.2 - ubuntu-18.04'
imageName: mcr.microsoft.com/powershell
imageTag: 6.2.4-ubuntu-18.04
# Release pipeline
- stage: Release
displayName: Release
dependsOn: Build
dependsOn: [ 'Test', 'Analysis' ]
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/v0.'))
jobs:
- job:
displayName: Live
pool:
vmImage: 'ubuntu-latest'
vmImage: $(imageName)
variables:
isPreRelease: $[contains(variables['Build.SourceBranchName'], '-B')]
steps:
# Download module from build
- task: DownloadPipelineArtifact@1
- task: DownloadPipelineArtifact@2
displayName: 'Download module'
inputs:
artifactName: PSRule.Monitor
downloadPath: $(Build.SourcesDirectory)/out/modules/PSRule.Monitor
artifact: PSRule.Monitor
path: $(Build.SourcesDirectory)/out/modules/PSRule.Monitor
# Install pipeline dependencies
- powershell: ./.azure-pipelines/pipeline-deps.ps1
@ -170,15 +205,15 @@ stages:
displayName: 'Publish module'
# Update GitHub release
- task: GitHubRelease@0
- task: GitHubRelease@1
displayName: 'GitHub release'
inputs:
gitHubConnection: 'AzureDevOps-PSRule.Monitor'
repositoryName: '$(Build.Repository.Name)'
action: edit
tag: '$(Build.SourceBranchName)'
releaseNotesSource: input
releaseNotes: 'See [change log](https://github.com/BernieWhite/PSRule.Monitor/blob/main/CHANGELOG.md)'
releaseNotesSource: inline
releaseNotesInline: 'See [change log](https://github.com/Microsoft/PSRule.Monitor/blob/main/CHANGELOG.md)'
assetUploadMode: replace
addChangeLog: false
isPreRelease: $(isPreRelease)

Просмотреть файл

@ -0,0 +1,71 @@
# Azure DevOps
# CI job for running VM pipelines
parameters:
name: ''
displayName: ''
buildConfiguration: 'Release'
imageName: ''
coverage: 'false'
publishResults: 'true'
jobs:
- job: ${{ parameters.name }}
displayName: ${{ parameters.displayName }}
pool:
vmImage: ${{ parameters.imageName }}
variables:
COVERAGE: ${{ parameters.coverage }}
PUBLISHRESULTS: ${{ parameters.publishResults }}
skipComponentGovernanceDetection: true
steps:
# Install pipeline dependencies
- powershell: ./.azure-pipelines/pipeline-deps.ps1
displayName: 'Install dependencies'
# Download module
- task: DownloadPipelineArtifact@2
displayName: 'Download module'
inputs:
artifact: PSRule.Monitor
path: $(Build.SourcesDirectory)/out/modules/PSRule.Monitor
# Build module
- powershell: Invoke-Build TestModule -Configuration ${{ parameters.buildConfiguration }} -Build $(Build.BuildNumber)
env:
COVERAGE: ${{ parameters.coverage }}
displayName: 'Test module'
# Pester test results
- task: PublishTestResults@2
displayName: 'Publish Pester results'
inputs:
testRunTitle: 'Pester on ${{ parameters.imageName }}'
testRunner: NUnit
testResultsFiles: 'reports/pester-unit.xml'
mergeTestResults: true
platform: ${{ parameters.name }}
configuration: ${{ parameters.buildConfiguration }}
publishRunAttachments: true
condition: and(succeededOrFailed(), eq(variables['PUBLISHRESULTS'], 'true'))
# 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.Monitor'
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;SonarQube;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')

Просмотреть файл

@ -0,0 +1,76 @@
# Azure DevOps
# CI job for running container pipelines
parameters:
name: ''
displayName: ''
buildConfiguration: 'Release'
vmImage: 'ubuntu-16.04'
imageName: ''
imageTag: ''
coverage: 'false'
publishResults: 'true'
jobs:
- job: ${{ parameters.name }}
displayName: ${{ parameters.displayName }}
pool:
vmImage: ${{ parameters.vmImage }}
container:
image: '${{ parameters.imageName }}:${{ parameters.imageTag }}'
env:
COVERAGE: ${{ parameters.coverage }}
PUBLISHRESULTS: ${{ parameters.publishResults }}
variables:
COVERAGE: ${{ parameters.coverage }}
PUBLISHRESULTS: ${{ parameters.publishResults }}
skipComponentGovernanceDetection: true
steps:
# Install pipeline dependencies
- powershell: ./.azure-pipelines/pipeline-deps.ps1
displayName: 'Install dependencies'
# Download module
- task: DownloadPipelineArtifact@2
displayName: 'Download module'
inputs:
artifact: PSRule.Monitor
path: $(Build.SourcesDirectory)/out/modules/PSRule.Monitor
# Build module
- powershell: Invoke-Build TestModule -Configuration ${{ parameters.buildConfiguration }} -Build $(Build.BuildNumber)
displayName: 'Test module'
# Pester test results
- task: PublishTestResults@2
displayName: 'Publish Pester results'
inputs:
testRunTitle: 'Pester on ${{ parameters.imageTag }}'
testRunner: NUnit
testResultsFiles: 'reports/pester-unit.xml'
mergeTestResults: true
platform: ${{ parameters.imageTag }}
configuration: ${{ parameters.buildConfiguration }}
publishRunAttachments: true
condition: and(succeededOrFailed(), eq(variables['PUBLISHRESULTS'], 'true'))
# 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.Monitor'
reporttypes: 'HtmlInline_AzurePipelines;Cobertura;SonarQube;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')

Просмотреть файл

@ -2,6 +2,12 @@
## Unreleased
What's changed since v0.1.0:
- New features:
- Added support for passing through data and field properties. [#31](https://github.com/microsoft/PSRule.Monitor/issues/31)
- Added support for passing through rule module name. [#30](https://github.com/microsoft/PSRule.Monitor/issues/30)
## v0.1.0
What's changed since pre-release v0.1.0-B1912005:

Просмотреть файл

@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29418.71
MinimumVisualStudioVersion = 15.0.26124.0
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSRule.Monitor", "src\PSRule.Monitor\PSRule.Monitor.csproj", "{E6BB91E3-5A15-48F8-9722-1F3AC50DDD75}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSRule.Monitor", "src\PSRule.Monitor\PSRule.Monitor.csproj", "{BC87E736-4B87-48C2-AC35-3686C86C13A7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PSRule.Monitor.Tests", "tests\PSRule.Monitor.Tests\PSRule.Monitor.Tests.csproj", "{5A666E79-F8B6-4613-8E4C-0F969CBA68E3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -11,10 +13,14 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E6BB91E3-5A15-48F8-9722-1F3AC50DDD75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E6BB91E3-5A15-48F8-9722-1F3AC50DDD75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E6BB91E3-5A15-48F8-9722-1F3AC50DDD75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E6BB91E3-5A15-48F8-9722-1F3AC50DDD75}.Release|Any CPU.Build.0 = Release|Any CPU
{BC87E736-4B87-48C2-AC35-3686C86C13A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BC87E736-4B87-48C2-AC35-3686C86C13A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC87E736-4B87-48C2-AC35-3686C86C13A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC87E736-4B87-48C2-AC35-3686C86C13A7}.Release|Any CPU.Build.0 = Release|Any CPU
{5A666E79-F8B6-4613-8E4C-0F969CBA68E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A666E79-F8B6-4613-8E4C-0F969CBA68E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A666E79-F8B6-4613-8E4C-0F969CBA68E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A666E79-F8B6-4613-8E4C-0F969CBA68E3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

9
nuget.config Normal file
Просмотреть файл

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<disabledPackageSources>
<add key="Microsoft Visual Studio Offline Packages" value="true" />
</disabledPackageSources>
</configuration>

Просмотреть файл

@ -180,16 +180,16 @@ task PSScriptAnalyzer NuGet, {
# Synopsis: Install PSRule
task PSRule NuGet, {
if ($Null -eq (Get-InstalledModule -Name PSRule -MinimumVersion 0.18.0 -ErrorAction Ignore)) {
Install-Module -Name PSRule -Repository PSGallery -MinimumVersion 0.18.0 -Scope CurrentUser -Force;
if ($Null -eq (Get-InstalledModule -Name PSRule -MinimumVersion 1.2.0 -ErrorAction Ignore)) {
Install-Module -Name PSRule -Repository PSGallery -MinimumVersion 1.2.0 -Scope CurrentUser -Force;
}
Import-Module -Name PSRule -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;
if ($Null -eq (Get-InstalledModule -Name PSDocs -MinimumVersion 0.8.0 -ErrorAction Ignore)) {
Install-Module -Name PSDocs -Repository PSGallery -MinimumVersion 0.8.0 -Scope CurrentUser -Force;
}
Import-Module -Name PSDocs -Verbose:$False;
}
@ -199,7 +199,6 @@ 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
@ -217,13 +216,13 @@ task TestDotNet {
if ($CodeCoverage) {
exec {
# Test library
# dotnet test --collect:"Code Coverage" --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Monitor.Tests
dotnet test --collect:"Code Coverage" --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Monitor.Tests
}
}
else {
exec {
# Test library
# dotnet test --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Monitor.Tests
dotnet test --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Monitor.Tests
}
}
}
@ -238,13 +237,13 @@ task CopyModule {
# Synopsis: Build modules only
task BuildModule BuildDotNet, CopyModule
task TestRules TestDotNet, PSRule, Pester, PSScriptAnalyzer, {
task TestModule ModuleDependencies, Pester, PSScriptAnalyzer, {
# Run Pester tests
$pesterParams = @{ Path = (Join-Path -Path $PWD -ChildPath tests/PSRule.Monitor.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));
$pesterParams.Add('CodeCoverageOutputFile', (Join-Path -Path $PWD -ChildPath 'reports/pester-coverage.xml'));
}
if (!(Test-Path -Path reports)) {
@ -281,8 +280,6 @@ task Analyze Build, PSScriptAnalyzer, {
# Synopsis: Build help
task BuildHelp BuildModule, PlatyPS, {
# Generate MAML and about topics
$Null = New-ExternalHelp -OutputPath out/docs/PSRule.Monitor -Path '.\docs\commands\PSRule.Monitor\en-US' -Force;
# Copy generated help into module out path
if (!(Test-Path -Path out/modules/PSRule.Monitor/en-US/)) {
@ -294,9 +291,25 @@ task BuildHelp BuildModule, PlatyPS, {
if (!(Test-Path -Path out/modules/PSRule.Monitor/en-GB/)) {
$Null = New-Item -Path out/modules/PSRule.Monitor/en-GB -Force -ItemType Directory;
}
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-US/ -Recurse;
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-AU/ -Recurse;
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-GB/ -Recurse;
# Avoid YamlDotNet issue in same app domain
exec {
$pwshPath = (Get-Process -Id $PID).Path;
&$pwshPath -Command {
# Generate MAML and about topics
Import-Module -Name PlatyPS -Verbose:$False;
$Null = New-ExternalHelp -OutputPath 'out/docs/PSRule.Monitor' -Path '.\docs\commands\PSRule.Monitor\en-US' -Force;
# Copy generated help into module out path
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-US/ -Recurse;
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-AU/ -Recurse;
$Null = Copy-Item -Path out/docs/PSRule.Monitor/* -Destination out/modules/PSRule.Monitor/en-GB/ -Recurse;
}
}
if (!(Test-Path -Path 'out/docs/PSRule.Monitor/PSRule.Monitor-help.xml')) {
throw 'Failed find generated cmdlet help.';
}
}
task ScaffoldHelp Build, {
@ -318,8 +331,9 @@ task Clean {
task Build Clean, BuildModule, VersionModule, BuildHelp
task Test Build, TestRules
task Test Build, Rules, TestDotNet, TestModule
task Release ReleaseModule, TagBuild
task . Build, Test
# Synopsis: Build and test. Entry point for CI Build stage
task . Build, Rules, TestDotNet

Просмотреть файл

@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Newtonsoft.Json;
using System;
using System.Collections;
namespace PSRule.Monitor
{
/// <summary>
/// A JSON converter to convert an object into a flat string.
/// </summary>
internal sealed class StringifyMapConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Hashtable).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!(value is Hashtable map))
return;
var v = JsonConvert.SerializeObject(map);
writer.WriteValue(v);
}
}
}

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections;
using Newtonsoft.Json;
namespace PSRule.Monitor.Data
@ -12,6 +13,10 @@ namespace PSRule.Monitor.Data
{
public string RuleName { get; set; }
public string DisplayName { get; set; }
public string ModuleName { get; set; }
public string TargetName { get; set; }
public string TargetType { get; set; }
@ -20,5 +25,14 @@ namespace PSRule.Monitor.Data
[JsonIgnore]
public string ResourceId { get; set; }
[JsonConverter(typeof(StringifyMapConverter))]
public Hashtable Field { get; set; }
[JsonConverter(typeof(StringifyMapConverter))]
public Hashtable Data { get; set; }
[JsonConverter(typeof(StringifyMapConverter))]
public Hashtable Annotations { get; set; }
}
}

Просмотреть файл

@ -4,6 +4,7 @@
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>PSRule.Monitor</RootNamespace>
<OutputType>Library</OutputType>
<ProjectGuid>{bc87e736-4b87-48c2-ac35-3686c86c13a7}</ProjectGuid>
<DebugType>portable</DebugType>
<NeutralLanguage>en-US</NeutralLanguage>
<DebugSymbols>true</DebugSymbols>
@ -25,7 +26,7 @@ This project is open source and not a supported product.</Description>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.3.2">
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

Просмотреть файл

@ -5,9 +5,7 @@ using Newtonsoft.Json;
using PSRule.Monitor.Data;
using System;
using System.Collections;
using System.Globalization;
using System.Management.Automation;
using System.Net.Http;
using System.Security;
using System.Text;
@ -47,42 +45,28 @@ namespace PSRule.Monitor.Pipeline
public override IPipeline Build()
{
return new InjestPipeline(PrepareContext(), PrepareReader(), _WorkspaceId, _SharedKey, _LogName);
var logClient = new LogClient(_WorkspaceId, _LogName);
return new InjestPipeline(PrepareContext(), PrepareReader(), _WorkspaceId, _SharedKey, logClient);
}
}
internal sealed class InjestPipeline : PipelineBase
{
private const string CONTENTTYPE = "application/json";
private const string TIMESTAMPFIELD = "";
private const string APIVERSION = "2016-04-01";
private const string HEADER_ACCEPT = "Accept";
private const string HEADER_AUTHORIZATION = "Authorization";
private const string HEADER_LOGTYPE = "Log-Type";
private const string HEADER_DATE = "x-ms-date";
private const string HEADER_RESOURCEID = "x-ms-AzureResourceId";
private const string HEADER_TIMEGENERATED = "time-generated-field";
private static readonly CultureInfo FormatCulture = new CultureInfo("en-US");
private readonly string _LogName;
private readonly Uri _EndpointUri;
private readonly CollectionHash _Hash;
private readonly BatchQueue _SubmissionQueue;
private readonly HttpClient _HttpClient;
private readonly ILogClient _LogClient;
// Track whether Dispose has been called.
private bool _Disposed;
internal InjestPipeline(PipelineContext context, PipelineReader reader, string workspaceId, SecureString sharedKey, string logName)
internal InjestPipeline(PipelineContext context, PipelineReader reader, string workspaceId, SecureString sharedKey, ILogClient logClient)
: base(context, reader)
{
_LogName = logName;
_EndpointUri = new Uri(string.Concat("https://", workspaceId, ".ods.opinsights.azure.com/api/logs?api-version=", APIVERSION));
_Hash = new CollectionHash(workspaceId, sharedKey);
_SubmissionQueue = new BatchQueue();
_HttpClient = GetClient();
_LogClient = logClient;
}
public override void Process(PSObject sourceObject)
@ -113,15 +97,23 @@ namespace PSRule.Monitor.Pipeline
var targetName = GetPropertyValue(sourceObject, "targetName");
var targetType = GetPropertyValue(sourceObject, "targetType");
var outcome = GetPropertyValue(sourceObject, "outcome");
var data = GetProperty<object>(sourceObject, "data");
var field = GetProperty<object>(sourceObject, "field");
var resourceId = GetField(field, "resourceId");
var info = GetProperty<object>(sourceObject, "info");
var resourceId = GetField(data, "resourceId") ?? GetField(field, "resourceId");
var displayName = GetField(info, "displayName") ?? ruleName;
var moduleName = GetField(info, "moduleName");
var record = new LogRecord
{
RuleName = ruleName,
DisplayName = displayName,
ModuleName = moduleName,
TargetName = targetName,
TargetType = targetType,
Outcome = outcome,
ResourceId = resourceId
ResourceId = resourceId,
Data = GetPropertyMap(data),
Field = GetPropertyMap(field),
};
return record;
}
@ -136,12 +128,34 @@ namespace PSRule.Monitor.Pipeline
return obj.Properties[propertyName] == null ? default(T) : (T)obj.Properties[propertyName].Value;
}
private static string GetField(object obj, string propertyName)
private static Hashtable GetPropertyMap(object o)
{
if (obj is IDictionary dictionary && TryDictionary(dictionary, propertyName, out object value) && value != null)
if (o == null)
return null;
var result = new Hashtable();
if (o is IDictionary dictionary)
{
foreach (DictionaryEntry kv in dictionary)
result[kv.Key] = kv.Value;
}
else if (o is PSObject pso)
{
foreach (var p in pso.Properties)
{
if (p.MemberType == PSMemberTypes.NoteProperty)
result[p.Name] = p.Value;
}
}
return result;
}
private static string GetField(object o, string propertyName)
{
if (o is IDictionary dictionary && TryDictionary(dictionary, propertyName, out object value) && value != null)
return value.ToString();
if (obj is PSObject pso)
if (o is PSObject pso)
return GetPropertyValue(pso, propertyName);
return null;
@ -182,31 +196,7 @@ namespace PSRule.Monitor.Pipeline
/// </summary>
private void PostData(string signature, DateTime date, string resourceId, string json)
{
using (var request = PrepareRequest(signature, date, resourceId, json))
{
var response = _HttpClient.SendAsync(request);
response.Wait();
var result = response.Result.Content.ReadAsStringAsync().Result;
}
}
private HttpClient GetClient()
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add(HEADER_ACCEPT, CONTENTTYPE);
client.DefaultRequestHeaders.Add(HEADER_LOGTYPE, _LogName);
return client;
}
private HttpRequestMessage PrepareRequest(string signature, DateTime date, string resourceId, string json)
{
var request = new HttpRequestMessage(HttpMethod.Post, _EndpointUri);
request.Headers.Add(HEADER_AUTHORIZATION, signature);
request.Headers.Add(HEADER_DATE, date.ToString("r", FormatCulture));
request.Headers.Add(HEADER_TIMEGENERATED, TIMESTAMPFIELD);
request.Headers.Add(HEADER_RESOURCEID, resourceId);
request.Content = new StringContent(json, Encoding.UTF8, CONTENTTYPE);
return request;
_LogClient.Post(signature, date, resourceId, json);
}
#region IDisposable
@ -218,7 +208,7 @@ namespace PSRule.Monitor.Pipeline
if (disposing)
{
_Hash.Dispose();
_HttpClient.Dispose();
_LogClient.Dispose();
}
_Disposed = true;
}

Просмотреть файл

@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Globalization;
using System.Net.Http;
using System.Text;
namespace PSRule.Monitor.Pipeline
{
internal interface ILogClient : IDisposable
{
void Post(string signature, DateTime date, string resourceId, string json);
}
internal sealed class LogClient : ILogClient
{
private const string CONTENTTYPE = "application/json";
private const string TIMESTAMPFIELD = "";
private const string APIVERSION = "2016-04-01";
private const string HEADER_ACCEPT = "Accept";
private const string HEADER_AUTHORIZATION = "Authorization";
private const string HEADER_LOGTYPE = "Log-Type";
private const string HEADER_DATE = "x-ms-date";
private const string HEADER_RESOURCEID = "x-ms-AzureResourceId";
private const string HEADER_TIMEGENERATED = "time-generated-field";
private static readonly CultureInfo FormatCulture = new CultureInfo("en-US");
private readonly HttpClient _HttpClient;
private readonly Uri _EndpointUri;
// Track whether Dispose has been called.
private bool _Disposed;
public LogClient(string workspaceId, string logName)
{
_EndpointUri = new Uri(string.Concat("https://", workspaceId, ".ods.opinsights.azure.com/api/logs?api-version=", APIVERSION));
_HttpClient = GetClient(logName);
}
/// <summary>
/// Post log data to Azure Monitor endpoint.
/// </summary>
public void Post(string signature, DateTime date, string resourceId, string json)
{
using (var request = PrepareRequest(signature, date, resourceId, json))
{
var response = _HttpClient.SendAsync(request);
response.Wait();
var result = response.Result.Content.ReadAsStringAsync().Result;
}
}
private static HttpClient GetClient(string logName)
{
var client = new HttpClient();
client.DefaultRequestHeaders.Add(HEADER_ACCEPT, CONTENTTYPE);
client.DefaultRequestHeaders.Add(HEADER_LOGTYPE, logName);
return client;
}
private HttpRequestMessage PrepareRequest(string signature, DateTime date, string resourceId, string json)
{
var request = new HttpRequestMessage(HttpMethod.Post, _EndpointUri);
request.Headers.Add(HEADER_AUTHORIZATION, signature);
request.Headers.Add(HEADER_DATE, date.ToString("r", FormatCulture));
request.Headers.Add(HEADER_TIMEGENERATED, TIMESTAMPFIELD);
request.Headers.Add(HEADER_RESOURCEID, resourceId);
request.Content = new StringContent(json, Encoding.UTF8, CONTENTTYPE);
return request;
}
#region IDisposable
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_Disposed)
{
if (disposing)
{
_HttpClient.Dispose();
}
_Disposed = true;
}
}
#endregion IDisposable
}
}

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("PSRule.Monitor.Tests")]

Просмотреть файл

@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using PSRule.Monitor.Configuration;
using PSRule.Monitor.Pipeline;
using System;
using System.Management.Automation;
using System.Security;
using System.Text;
using Xunit;
namespace PSRule.Monitor
{
public sealed class InjestPipelineTests
{
[Fact]
public void InvokePipeline()
{
var pipeline = GetPipeline(out TestLogClient logClient);
pipeline.Begin();
var o = GetObject();
pipeline.Process(o);
pipeline.End();
Assert.Single(logClient.Output);
var actual = logClient.Output[0];
var actualJson = JsonConvert.DeserializeObject<JArray>(actual.Json);
Assert.Equal("test-resource-id", actual.ResourceId);
Assert.NotEmpty(actual.Signature);
Assert.Equal(o.Properties["ruleName"].Value, actualJson[0]["RuleName"].Value<string>());
Assert.Equal(o.Properties["targetName"].Value, actualJson[0]["TargetName"].Value<string>());
Assert.Equal(o.Properties["targetType"].Value, actualJson[0]["TargetType"].Value<string>());
Assert.Equal(o.Properties["outcome"].Value, actualJson[0]["Outcome"].Value<string>());
Assert.Equal(o.Properties["ruleName"].Value, actualJson[0]["DisplayName"].Value<string>());
Assert.Equal("test-module", actualJson[0]["ModuleName"].Value<string>());
}
#region Helper methods
private const string _WorkspaceId = "00000000-0000-0000-0000-000000000000";
private static PSObject GetObject()
{
var info = new PSObject();
info.Properties.Add(new PSNoteProperty("moduleName", "test-module"));
var data = new PSObject();
data.Properties.Add(new PSNoteProperty("resourceId", "test-resource-id"));
var o = new PSObject();
o.Properties.Add(new PSNoteProperty("ruleName", "test-rule"));
o.Properties.Add(new PSNoteProperty("targetName", "test-name"));
o.Properties.Add(new PSNoteProperty("targetType", "test-type"));
o.Properties.Add(new PSNoteProperty("outcome", "Fail"));
o.Properties.Add(new PSNoteProperty("info", info));
o.Properties.Add(new PSNoteProperty("data", data));
return o;
}
private static IPipeline GetPipeline(out TestLogClient logClient)
{
var key = new SecureString();
foreach (var c in Convert.ToBase64String(Encoding.UTF8.GetBytes(_WorkspaceId)))
key.AppendChar(c);
logClient = new TestLogClient();
return new InjestPipeline(GetContent(), GetReader(), _WorkspaceId, key, logClient);
}
private static PipelineReader GetReader()
{
return new PipelineReader();
}
private static PipelineContext GetContent()
{
return new PipelineContext(new PSRuleOption());
}
#endregion Helper methods
}
}

Просмотреть файл

@ -6,9 +6,7 @@
#
[CmdletBinding()]
param (
)
param ()
# Setup error handling
$ErrorActionPreference = 'Stop';
@ -54,7 +52,7 @@ Describe 'PSRule.Monitor' -Tag 'PowerShellGallery' {
Context 'Static analysis' {
It 'Has no quality errors' {
$modulePath = (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Monitor);
$result = @(Invoke-ScriptAnalyzer -Path $modulePath -Verbose);
$result = @(Invoke-ScriptAnalyzer -Path $modulePath);
$warningCount = ($result | Where-Object { $_.Severity -eq 'Warning' } | Measure-Object).Count;
$errorCount = ($result | Where-Object { $_.Severity -eq 'Error' } | Measure-Object).Count;

Просмотреть файл

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<ProjectGuid>{22bf9268-67e2-4c43-b254-1c43262cddcc}</ProjectGuid>
<IsTestProject>true</IsTestProject>
<IsPackable>false</IsPackable>
<RootNamespace>PSRule.Monitor</RootNamespace>
<DebugType>Full</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.0.6" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="3.0.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\PSRule.Monitor\PSRule.Monitor.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using PSRule.Monitor.Pipeline;
using System;
using System.Collections.Generic;
namespace PSRule.Monitor
{
internal sealed class TestLogClient : ILogClient
{
public TestLogClient()
{
Output = new List<LogEntry>();
}
public List<LogEntry> Output { get; }
public void Dispose()
{
// Test class only
}
public void Post(string signature, DateTime date, string resourceId, string json)
{
Output.Add(new LogEntry(
signature,
date,
resourceId,
json
));
}
}
internal sealed class LogEntry
{
public LogEntry(string signature, DateTime date, string resourceId, string json)
{
Signature = signature;
Date = date;
ResourceId = resourceId;
Json = json;
}
public string Signature { get; }
public DateTime Date { get; }
public string ResourceId { get; }
public string Json { get; }
}
}