Update to build process and tool chain (#231)
This commit is contained in:
Родитель
6a60136b96
Коммит
260cdb4de0
|
@ -1,165 +0,0 @@
|
|||
# Azure DevOps
|
||||
# CI pipeline for PSRule.Rules.GitHub
|
||||
|
||||
variables:
|
||||
version: '0.3.0'
|
||||
buildConfiguration: 'Release'
|
||||
disable.coverage.autogenerate: 'true'
|
||||
imageName: 'ubuntu-20.04'
|
||||
|
||||
# Use build number format, i.e. 0.3.0-B2202001
|
||||
name: $(version)-B$(date:yyMM)$(rev:rrr)
|
||||
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- 'main'
|
||||
tags:
|
||||
include:
|
||||
- 'v0.*'
|
||||
|
||||
pr:
|
||||
branches:
|
||||
include:
|
||||
- 'main'
|
||||
|
||||
stages:
|
||||
|
||||
# Build pipeline
|
||||
- stage: Build
|
||||
displayName: Build
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- job:
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
displayName: 'Module'
|
||||
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'
|
||||
|
||||
# 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()
|
||||
|
||||
# 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 artifacts
|
||||
- publish: out/modules/PSRule.Rules.GitHub
|
||||
displayName: 'Publish module'
|
||||
artifact: PSRule.Rules.GitHub
|
||||
|
||||
# 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_Rules_GitHub" /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:
|
||||
vmImage: 'windows-2019'
|
||||
displayName: Secret scan
|
||||
|
||||
steps:
|
||||
- task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@2
|
||||
displayName: 'Scan for secrets'
|
||||
inputs:
|
||||
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
|
||||
variables:
|
||||
skipComponentGovernanceDetection: 'true'
|
||||
jobs:
|
||||
|
||||
- template: jobs/test.yaml
|
||||
parameters:
|
||||
name: ubuntu_22_04_coverage
|
||||
imageName: 'ubuntu-22.04'
|
||||
displayName: 'PowerShell coverage'
|
||||
coverage: 'true'
|
||||
publishResults: 'false'
|
||||
|
||||
- template: jobs/test.yaml
|
||||
parameters:
|
||||
name: macOS_11
|
||||
displayName: 'PowerShell 7.1 - macOS-11'
|
||||
imageName: 'macOS-11'
|
||||
|
||||
- template: jobs/test.yaml
|
||||
parameters:
|
||||
name: windows_2022
|
||||
displayName: 'PowerShell 7.1 - Windows 2022'
|
||||
imageName: 'windows-2022'
|
||||
pwsh: 'false'
|
||||
|
||||
- template: jobs/testContainer.yaml
|
||||
parameters:
|
||||
name: ps_7_3_ubuntu_22_04
|
||||
displayName: 'PowerShell 7.3 - ubuntu-22.04'
|
||||
imageName: mcr.microsoft.com/powershell
|
||||
imageTag: 7.3-ubuntu-22.04
|
|
@ -1,36 +0,0 @@
|
|||
# Azure DevOps
|
||||
# Pipeline to run integration tests for PSRule.Rules.GitHub
|
||||
|
||||
variables:
|
||||
buildConfiguration: 'Release'
|
||||
imageName: 'ubuntu-20.04'
|
||||
|
||||
resources:
|
||||
pipelines:
|
||||
- pipeline: CI
|
||||
source: 'PSRule.Rules.GitHub-CI'
|
||||
branch: main
|
||||
trigger:
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
|
||||
# Use build number format, i.e. I2007009
|
||||
name: I$(date:yyMM)$(rev:rrr)
|
||||
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
stages:
|
||||
|
||||
# Test pipeline
|
||||
- stage: Test
|
||||
variables:
|
||||
skipComponentGovernanceDetection: 'true'
|
||||
jobs:
|
||||
- template: jobs/integrationContainer.yaml
|
||||
parameters:
|
||||
name: gh_integration
|
||||
displayName: 'GitHub Integration Tests'
|
||||
imageName: mcr.microsoft.com/powershell
|
||||
imageTag: 7.2.2-ubuntu-20.04
|
|
@ -1,61 +0,0 @@
|
|||
# Azure DevOps
|
||||
# Job for running integration tests pipelines in a container
|
||||
|
||||
parameters:
|
||||
name: ''
|
||||
displayName: ''
|
||||
buildConfiguration: 'Release'
|
||||
vmImage: 'ubuntu-22.04'
|
||||
imageName: ''
|
||||
imageTag: ''
|
||||
publishResults: 'true'
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.name }}
|
||||
displayName: ${{ parameters.displayName }}
|
||||
condition: not(eq(variables['Build.Reason'], 'PullRequest'))
|
||||
pool:
|
||||
vmImage: ${{ parameters.vmImage }}
|
||||
container:
|
||||
image: '${{ parameters.imageName }}:${{ parameters.imageTag }}'
|
||||
env:
|
||||
PUBLISHRESULTS: ${{ parameters.publishResults }}
|
||||
variables:
|
||||
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.Rules.GitHub
|
||||
source: 'specific'
|
||||
runVersion: 'specific'
|
||||
project: 'PSRule.Rules.GitHub'
|
||||
pipeline: $(resources.pipeline.CI.pipelineID)
|
||||
runId: $(resources.pipeline.CI.runID)
|
||||
path: $(Build.SourcesDirectory)/out/modules/PSRule.Rules.GitHub
|
||||
|
||||
# Build module
|
||||
- powershell: Invoke-Build IntegrationTest -Configuration ${{ parameters.buildConfiguration }} -Build $(Build.BuildNumber)
|
||||
displayName: 'Test module'
|
||||
env:
|
||||
GITHUB_TOKEN: $(GITHUB_TOKEN)
|
||||
|
||||
# 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'))
|
|
@ -1,76 +0,0 @@
|
|||
# Azure DevOps
|
||||
# CI job for running VM pipelines
|
||||
|
||||
parameters:
|
||||
name: ''
|
||||
displayName: ''
|
||||
buildConfiguration: 'Release'
|
||||
imageName: ''
|
||||
coverage: 'false'
|
||||
publishResults: 'true'
|
||||
pwsh: '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.Rules.GitHub
|
||||
path: $(Build.SourcesDirectory)/out/modules/PSRule.Rules.GitHub
|
||||
|
||||
# Build module
|
||||
- task: PowerShell@2
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: Invoke-Build TestModule -Configuration ${{ parameters.buildConfiguration }} -Build $(Build.BuildNumber)
|
||||
pwsh: ${{ eq(parameters.pwsh, 'true') }}
|
||||
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.Rules.GitHub'
|
||||
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')
|
|
@ -1,76 +0,0 @@
|
|||
# Azure DevOps
|
||||
# CI job for running container pipelines
|
||||
|
||||
parameters:
|
||||
name: ''
|
||||
displayName: ''
|
||||
buildConfiguration: 'Release'
|
||||
vmImage: 'ubuntu-22.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.Rules.GitHub
|
||||
path: $(Build.SourcesDirectory)/out/modules/PSRule.Rules.GitHub
|
||||
|
||||
# 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.Rules.GitHub'
|
||||
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')
|
|
@ -1,27 +1,47 @@
|
|||
{
|
||||
"name": "PSRule for GitHub dev",
|
||||
"settings": {
|
||||
"name": "PSRule for GitHub Developer Codespace",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "pwsh",
|
||||
"terminal.integrated.profiles.linux": {
|
||||
"pwsh": {
|
||||
"path": "/bin/pwsh"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extensions": [
|
||||
"ms-azure-devops.azure-pipelines",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"pwsh": {
|
||||
"path": "/opt/microsoft/powershell/7/pwsh"
|
||||
}
|
||||
},
|
||||
"powershell.powerShellDefaultVersion": "PowerShell"
|
||||
},
|
||||
"extensions": [
|
||||
"ms-dotnettools.vscode-dotnet-runtime",
|
||||
"ms-dotnettools.csdevkit",
|
||||
"ms-vscode.powershell",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"github.vscode-github-actions",
|
||||
"bewhite.psrule-vscode-preview",
|
||||
"msazurermtools.azurerm-vscode-tools",
|
||||
"ms-azuretools.vscode-bicep",
|
||||
"ms-dotnettools.csharp",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"eamodio.gitlens",
|
||||
"github.vscode-pull-request-github",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
],
|
||||
"features": {
|
||||
"github-cli": "latest"
|
||||
]
|
||||
}
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/github-cli": {
|
||||
"version": "latest"
|
||||
},
|
||||
"onCreateCommand": "/bin/pwsh -f .devcontainer/container-build.ps1",
|
||||
"postStartCommand": "/bin/pwsh -f .devcontainer/container-start.ps1"
|
||||
"ghcr.io/devcontainers/features/powershell": {
|
||||
"version": "latest"
|
||||
}
|
||||
},
|
||||
"onCreateCommand": "/opt/microsoft/powershell/7/pwsh -f .devcontainer/container-build.ps1",
|
||||
"postStartCommand": "/opt/microsoft/powershell/7/pwsh -f .devcontainer/container-start.ps1",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile",
|
||||
"args": {
|
||||
"VARIANT": "7.0-bullseye-slim"
|
||||
}
|
||||
},
|
||||
"remoteUser": "vscode",
|
||||
"forwardPorts": [
|
||||
8000
|
||||
]
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ root = true
|
|||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
|
||||
# Source code
|
||||
[*.{cs,ps1,psd1,psm1}]
|
||||
|
@ -20,12 +21,15 @@ indent_size = 2
|
|||
[*.cs]
|
||||
|
||||
# Code style defaults
|
||||
csharp_using_directive_placement = outside_namespace:suggestion
|
||||
csharp_using_directive_placement = outside_namespace:error
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
|
||||
dotnet_sort_system_directives_first = true
|
||||
dotnet_style_readonly_field = true:suggestion
|
||||
csharp_style_namespace_declarations = file_scoped:error
|
||||
|
||||
# License header
|
||||
file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT License.
|
||||
dotnet_diagnostic.IDE0073.severity = error
|
||||
|
||||
# Suggest more modern language features when available
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
|
@ -41,7 +45,38 @@ dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
|||
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
|
||||
# Pattern matching
|
||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
||||
csharp_style_prefer_not_pattern = true:suggestion
|
||||
csharp_style_prefer_switch_expression = false:none
|
||||
csharp_style_prefer_pattern_matching = false:none
|
||||
|
||||
# Prefer "var" everywhere
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_style_inlined_variable_declaration = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
|
||||
# Define the 'private_fields' symbol group:
|
||||
dotnet_naming_symbols.private_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_fields.applicable_accessibilities = private
|
||||
|
||||
# Define the 'private_static_fields' symbol group
|
||||
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
||||
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
|
||||
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
||||
|
||||
# Define the 'underscored' naming style
|
||||
dotnet_naming_style.underscored.capitalization = pascal_case
|
||||
dotnet_naming_style.underscored.required_prefix = _
|
||||
|
||||
# Private instance fields must use pascal case with a leading '_'
|
||||
dotnet_naming_rule.private_fields_underscored.symbols = private_fields
|
||||
dotnet_naming_rule.private_fields_underscored.style = underscored
|
||||
dotnet_naming_rule.private_fields_underscored.severity = error
|
||||
|
||||
# Exclude private static fields from underscored style
|
||||
dotnet_naming_rule.private_static_fields_none.symbols = private_static_fields
|
||||
dotnet_naming_rule.private_static_fields_none.style = underscored
|
||||
dotnet_naming_rule.private_static_fields_none.severity = none
|
||||
|
|
|
@ -12,29 +12,52 @@
|
|||
name: Analyze
|
||||
on:
|
||||
push:
|
||||
branches: [ main, 'release/*' ]
|
||||
branches: [main, 'release/*']
|
||||
pull_request:
|
||||
branches: [ main, 'release/*' ]
|
||||
branches: [main, 'release/*']
|
||||
schedule:
|
||||
- cron: '42 20 * * 0' # At 08:42 PM, on Sunday each week
|
||||
- cron: '24 22 * * 0' # At 10:24 PM, on Sunday each week
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
oss:
|
||||
name: Analyze with PSRule
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Run PSRule analysis
|
||||
uses: microsoft/ps-rule@v2.9.0
|
||||
with:
|
||||
modules: PSRule.Rules.MSFT.OSS
|
||||
prerelease: true
|
||||
outputFormat: Sarif
|
||||
outputPath: reports/ps-rule-results.sarif
|
||||
|
||||
- name: Run PSRule analysis
|
||||
uses: microsoft/ps-rule@v2.9.0
|
||||
with:
|
||||
modules: PSRule.Rules.MSFT.OSS
|
||||
prerelease: true
|
||||
- name: Upload results to security tab
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: reports/ps-rule-results.sarif
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: PSRule-Sarif
|
||||
path: reports/ps-rule-results.sarif
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
devskim:
|
||||
name: Analyze with DevSkim
|
||||
|
@ -44,20 +67,29 @@ jobs:
|
|||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run DevSkim scanner
|
||||
uses: microsoft/DevSkim-Action@v1
|
||||
with:
|
||||
directory-to-scan: src/
|
||||
directory-to-scan: .
|
||||
|
||||
- name: Upload results to security tab
|
||||
uses: github/codeql-action/upload-sarif@v2
|
||||
uses: github/codeql-action/upload-sarif@v3
|
||||
if: always()
|
||||
with:
|
||||
sarif_file: devskim-results.sarif
|
||||
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: DevSkim-Sarif
|
||||
path: devskim-results.sarif
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
codeql:
|
||||
name: Analyze with CodeQL
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -66,17 +98,26 @@ jobs:
|
|||
contents: read
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: 'csharp'
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: 'csharp'
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
id: codeql-analyze
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
- name: Upload results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: CodeQL-Sarif
|
||||
path: ${{ steps.codeql-analyze.outputs.sarif-output }}
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
|
|
@ -1,30 +1,143 @@
|
|||
# CI workflow for validating action
|
||||
#
|
||||
# CI Pipeline
|
||||
#
|
||||
|
||||
# NOTES:
|
||||
# This workflow builds and tests module updates.
|
||||
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches: [ main, 'release/*' ]
|
||||
branches: [main, 'release/*']
|
||||
pull_request:
|
||||
branches: [ main, 'release/*' ]
|
||||
branches: [main, 'release/*']
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DOTNET_NOLOGO: true
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
|
||||
test:
|
||||
name: Test
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
checks: read
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
|
||||
- name: Run PSRule self analysis
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
. ./.azure-pipelines/pipeline-deps.ps1
|
||||
Invoke-Build Build -AssertStyle GitHubActions
|
||||
Invoke-Build IntegrationTest -AssertStyle GitHubActions
|
||||
- name: Install dependencies
|
||||
shell: pwsh
|
||||
timeout-minutes: 3
|
||||
run: ./scripts/pipeline-deps.ps1
|
||||
|
||||
- name: Build module
|
||||
shell: pwsh
|
||||
timeout-minutes: 5
|
||||
run: Invoke-Build -Configuration Release -AssertStyle GitHubActions
|
||||
|
||||
- name: Run PSRule self analysis
|
||||
shell: pwsh
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
Invoke-Build IntegrationTest -AssertStyle GitHubActions
|
||||
|
||||
- name: Upload module
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Module
|
||||
path: ./out/modules/PSRule.Rules.GitHub/*
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload PSRule Results
|
||||
uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: Results-PSRule
|
||||
path: ./reports/ps-rule*.xml
|
||||
retention-days: 3
|
||||
if-no-files-found: error
|
||||
|
||||
test:
|
||||
name: Test (${{ matrix.rid }}-${{ matrix.shell }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
needs: build
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
# Get full test results from all platforms.
|
||||
fail-fast: false
|
||||
|
||||
matrix:
|
||||
os: ['ubuntu-latest']
|
||||
rid: ['linux-x64']
|
||||
shell: ['pwsh']
|
||||
include:
|
||||
- os: windows-latest
|
||||
rid: win-x64
|
||||
shell: pwsh
|
||||
- os: windows-latest
|
||||
rid: win-x64
|
||||
shell: powershell
|
||||
- os: ubuntu-latest
|
||||
rid: linux-x64
|
||||
shell: pwsh
|
||||
- os: ubuntu-latest
|
||||
rid: linux-musl-x64
|
||||
shell: pwsh
|
||||
- os: macos-latest
|
||||
rid: osx-x64
|
||||
shell: pwsh
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
|
||||
- if: ${{ matrix.shell == 'pwsh' }}
|
||||
name: Install dependencies (PowerShell)
|
||||
shell: pwsh
|
||||
timeout-minutes: 3
|
||||
run: ./scripts/pipeline-deps.ps1
|
||||
|
||||
- if: ${{ matrix.shell == 'powershell' }}
|
||||
name: Install dependencies (Windows PowerShell)
|
||||
shell: powershell
|
||||
timeout-minutes: 3
|
||||
run: ./scripts/pipeline-deps.ps1
|
||||
|
||||
- name: Download module
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: Module
|
||||
path: ./out/modules/PSRule.Rules.GitHub
|
||||
|
||||
- if: ${{ matrix.shell == 'pwsh' }}
|
||||
name: Test module (PowerShell)
|
||||
shell: pwsh
|
||||
timeout-minutes: 15
|
||||
run: Invoke-Build TestModule -Configuration Release -AssertStyle GitHubActions
|
||||
|
||||
- if: ${{ matrix.shell == 'powershell' }}
|
||||
name: Test module (Windows PowerShell)
|
||||
shell: powershell
|
||||
timeout-minutes: 30
|
||||
run: Invoke-Build TestModule -Configuration Release -AssertStyle GitHubActions
|
||||
|
|
|
@ -8,12 +8,14 @@
|
|||
name: Dependencies
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * 1' # At 01:30 AM, on Monday each week
|
||||
- cron: '30 1 * * 1' # At 01:30 AM, on Monday each week
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
WORKING_BRANCH: dependencies/powershell-bump
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
dependencies:
|
||||
name: Bump dependencies
|
||||
|
@ -23,9 +25,8 @@ jobs:
|
|||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
#
|
||||
# Stale item management
|
||||
#
|
||||
|
||||
# NOTES:
|
||||
# This workflow greets a person for their a first issue or PR.
|
||||
|
||||
name: First interaction
|
||||
|
||||
on: [pull_request_target, issues]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
greeting:
|
||||
name: Greeting
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'microsoft/PSRule.Rules.GitHub'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/first-interaction@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-message: 'Thanks for raising your first issue, the team appreciates the time you have taken 😉'
|
||||
pr-message: 'Thank you for your contribution, one of the team will evaluate shortly.'
|
|
@ -1,39 +1,40 @@
|
|||
#
|
||||
# Stale issues
|
||||
# Stale item management
|
||||
#
|
||||
|
||||
# NOTES:
|
||||
# Repository stale issue management.
|
||||
# Issues with open ended labels are automatically closed if no activity occurs.
|
||||
# Issues are marked stale after 14 days, then closed after a further 7 days.
|
||||
# This workflow manages stale work items on the repository.
|
||||
|
||||
name: 'Close stale issues'
|
||||
name: Stale maintenance
|
||||
on:
|
||||
schedule:
|
||||
- cron: '30 1 * * *' # At 1:30 AM, daily
|
||||
- cron: '30 1 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
issue:
|
||||
name: Close stale issues
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'microsoft/PSRule.Rules.GitHub'
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs within 7 days.
|
||||
Thank you for your contributions.
|
||||
|
||||
- uses: actions/stale@v8
|
||||
with:
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs within 7 days.
|
||||
Thank you for your contributions.
|
||||
close-issue-message: 'This issue was closed because it has not had any recent activity.'
|
||||
|
||||
close-issue-message: 'This issue was closed because it has not had any recent activity.'
|
||||
days-before-stale: 14
|
||||
days-before-pr-stale: -1
|
||||
|
||||
days-before-stale: 14
|
||||
days-before-pr-stale: -1
|
||||
days-before-close: 7
|
||||
days-before-pr-close: -1
|
||||
|
||||
days-before-close: 7
|
||||
days-before-pr-close: -1
|
||||
|
||||
any-of-labels: 'question,duplicate,incomplete,waiting-feedback'
|
||||
stale-issue-label: stale
|
||||
any-of-labels: 'question,duplicate,incomplete,waiting-feedback'
|
||||
stale-issue-label: stale
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
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.'
|
||||
$Assert.LessOrEqual($TargetObject, 'RuleName', 35)
|
||||
$Assert.StartsWith($TargetObject, 'RuleName', "$($Configuration.RULE_AUTHORING_PREFIX).")
|
||||
$Assert.LessOrEqual($PSRule, 'TargetName', 35)
|
||||
$Assert.StartsWith($PSRule, 'TargetName', "$($Configuration.RULE_AUTHORING_PREFIX).")
|
||||
}
|
||||
|
||||
# Synopsis: Complete help documentation
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"ms-vscode.powershell",
|
||||
"ms-azure-devops.azure-pipelines",
|
||||
"redhat.vscode-yaml",
|
||||
"bewhite.psrule-vscode-preview",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
"recommendations": [
|
||||
"ms-vscode.powershell",
|
||||
"redhat.vscode-yaml",
|
||||
"bewhite.psrule-vscode-preview",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,48 +1,59 @@
|
|||
{
|
||||
"files.exclude": {
|
||||
"reports/": true,
|
||||
"out/": true,
|
||||
".vs/": true,
|
||||
"**/bin/": true,
|
||||
"**/obj/": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"out/": true
|
||||
},
|
||||
"editor.insertSpaces": true,
|
||||
"files.exclude": {
|
||||
"reports/": true,
|
||||
"out/": true,
|
||||
".vs/": true,
|
||||
"**/bin/": true,
|
||||
"**/obj/": true,
|
||||
".venv/": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"out/": true
|
||||
},
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.detectIndentation": false,
|
||||
"files.insertFinalNewline": true,
|
||||
"yaml.format.singleQuote": true,
|
||||
"[markdown]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[csharp]": {
|
||||
"editor.tabSize": 4,
|
||||
"[yaml]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.tabSize": 2
|
||||
},
|
||||
"files.associations": {
|
||||
"**/.azure-pipelines/*.yaml": "azure-pipelines",
|
||||
"**/.azure-pipelines/jobs/*.yaml": "azure-pipelines"
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Octokit",
|
||||
"cmdlets"
|
||||
],
|
||||
"cSpell.enabledLanguageIds": [
|
||||
"csharp",
|
||||
"git-commit",
|
||||
"markdown",
|
||||
"plaintext",
|
||||
"powershell",
|
||||
"text",
|
||||
"yaml",
|
||||
"yml"
|
||||
],
|
||||
"[csharp]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"omnisharp.enableImportCompletion": true,
|
||||
"omnisharp.organizeImportsOnFormat": true,
|
||||
"omnisharp.enableEditorConfigSupport": true,
|
||||
"omnisharp.enableRoslynAnalyzers": true,
|
||||
"git.branchProtection": [
|
||||
"main"
|
||||
]
|
||||
},
|
||||
"[yaml]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[powershell]": {
|
||||
"editor.tabSize": 4,
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[html]": {
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"files.associations": {
|
||||
"**/CODEOWNERS": "text"
|
||||
},
|
||||
"cSpell.words": [
|
||||
"Octokit",
|
||||
"cmdlets"
|
||||
],
|
||||
"cSpell.enabledLanguageIds": [
|
||||
"csharp",
|
||||
"git-commit",
|
||||
"markdown",
|
||||
"plaintext",
|
||||
"powershell",
|
||||
"text",
|
||||
"yaml",
|
||||
"yml"
|
||||
],
|
||||
"omnisharp.organizeImportsOnFormat": true,
|
||||
"omnisharp.enableEditorConfigSupport": true,
|
||||
"git.branchProtection": [
|
||||
"main",
|
||||
"release/*"
|
||||
],
|
||||
"dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true
|
||||
}
|
||||
|
|
|
@ -1,84 +1,84 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "test",
|
||||
"type": "shell",
|
||||
"detail": "Build and test module.",
|
||||
"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",
|
||||
"detail": "Build module.",
|
||||
"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": []
|
||||
},
|
||||
{
|
||||
"label": "Rule docs",
|
||||
"detail": "Generate rule table of contents.",
|
||||
"type": "shell",
|
||||
"command": "Invoke-Build BuildRuleDocs",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "test",
|
||||
"type": "shell",
|
||||
"detail": "Build and test module.",
|
||||
"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",
|
||||
"detail": "Build module.",
|
||||
"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": []
|
||||
},
|
||||
{
|
||||
"label": "Rule docs",
|
||||
"detail": "Generate rule table of contents.",
|
||||
"type": "shell",
|
||||
"command": "Invoke-Build BuildRuleDocs",
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
What's changed since pre-release v0.4.0-B0016:
|
||||
|
||||
- Engineering:
|
||||
- Bump PSRule to v2.9.0.
|
||||
[#231](https://github.com/microsoft/PSRule.Rules.GitHub/pull/231)
|
||||
- Bump Octokit to v6.0.0.
|
||||
[#202](https://github.com/microsoft/PSRule.Rules.GitHub/pull/202)
|
||||
- Bump Microsoft.NET.Test.Sdk to v17.6.1.
|
||||
|
|
|
@ -64,11 +64,11 @@ jobs:
|
|||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# STEP 2: Run analysis against exported data
|
||||
- name: Analyze repository
|
||||
uses: microsoft/ps-rule@v2.2.0
|
||||
uses: microsoft/ps-rule@v2.9.0
|
||||
with:
|
||||
modules: 'PSRule.Rules.GitHub'
|
||||
```
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"PSRule": {
|
||||
"version": "2.7.0"
|
||||
"version": "2.9.0"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"Pester": {
|
||||
"version": "5.4.0"
|
||||
"version": "5.4.1"
|
||||
},
|
||||
"platyPS": {
|
||||
"version": "0.14.2"
|
||||
|
@ -16,6 +16,9 @@
|
|||
},
|
||||
"PSScriptAnalyzer": {
|
||||
"version": "1.21.0"
|
||||
},
|
||||
"PSRule.Rules.MSFT.OSS": {
|
||||
"version": "1.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -159,18 +159,7 @@ task BuildDotNet {
|
|||
}
|
||||
|
||||
task TestDotNet {
|
||||
if ($CodeCoverage) {
|
||||
exec {
|
||||
# Test library
|
||||
dotnet test --collect:"Code Coverage" --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Rules.GitHub.Tests
|
||||
}
|
||||
}
|
||||
else {
|
||||
exec {
|
||||
# Test library
|
||||
dotnet test --logger trx -r (Join-Path $PWD -ChildPath reports/) tests/PSRule.Rules.GitHub.Tests
|
||||
}
|
||||
}
|
||||
dotnet test
|
||||
}
|
||||
|
||||
task CopyModule {
|
||||
|
@ -278,17 +267,18 @@ task IntegrationTest ModuleDependencies, {
|
|||
# Synopsis: Run validation
|
||||
task Rules Dependencies, {
|
||||
$assertParams = @{
|
||||
Path = './.ps-rule/'
|
||||
Style = $AssertStyle
|
||||
Path = './.ps-rule/'
|
||||
Style = $AssertStyle
|
||||
OutputFormat = 'NUnit3'
|
||||
ErrorAction = 'Stop'
|
||||
As = 'Summary'
|
||||
ErrorAction = 'Stop'
|
||||
As = 'Summary'
|
||||
Outcome = 'Problem'
|
||||
}
|
||||
Import-Module (Join-Path -Path $PWD -ChildPath out/modules/PSRule.Rules.GitHub) -Force;
|
||||
Assert-PSRule @assertParams -InputPath $PWD -Module PSRule.Rules.MSFT.OSS -Format File -OutputPath reports/ps-rule-file.xml;
|
||||
Assert-PSRule @assertParams -InputPath $PWD -Module PSRule.Rules.MSFT.OSS -Format File -OutputPath ./reports/ps-rule-file.xml;
|
||||
|
||||
$rules = Get-PSRule -Module PSRule.Rules.GitHub;
|
||||
$rules | Assert-PSRule @assertParams -OutputPath reports/ps-rule-file2.xml;
|
||||
$rules | Assert-PSRule @assertParams -OutputPath ./reports/ps-rule-file2.xml;
|
||||
}
|
||||
|
||||
# Synopsis: Run script analyzer
|
||||
|
|
12
ps-rule.yaml
12
ps-rule.yaml
|
@ -6,11 +6,13 @@
|
|||
# https://microsoft.github.io/PSRule/
|
||||
|
||||
requires:
|
||||
PSRule: '@pre >=2.2.0'
|
||||
PSRule: '@pre >=2.9.0'
|
||||
|
||||
input:
|
||||
pathIgnore:
|
||||
- '.vscode/'
|
||||
- '.github/workflows/'
|
||||
- 'docs/examples*.json'
|
||||
- '*.md'
|
||||
- '*.Designer.cs'
|
||||
- '*.resx'
|
||||
|
@ -18,6 +20,14 @@ input:
|
|||
- '*.txt'
|
||||
- '*.html'
|
||||
- '*.ico'
|
||||
- '*.png'
|
||||
- 'ps-docs.yaml'
|
||||
- 'ps-project.yaml'
|
||||
- 'ps-rule.yaml'
|
||||
- 'mkdocs.yml'
|
||||
- '**/.editorconfig'
|
||||
- '.markdownlint.json'
|
||||
- '.github/dependabot.yml'
|
||||
|
||||
include:
|
||||
path: []
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
<!-- Project defaults -->
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<LangVersion>12.0</LangVersion>
|
||||
<!-- <Nullable>enable</Nullable> -->
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>portable</DebugType>
|
||||
|
|
|
@ -4,22 +4,21 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace PSRule.Rules.GitHub
|
||||
{
|
||||
internal static class DictionaryExtensions
|
||||
{
|
||||
[DebuggerStepThrough]
|
||||
public static bool TryPopValue(this IDictionary<string, object> dictionary, string key, out object value)
|
||||
{
|
||||
return dictionary.TryGetValue(key, out value) && dictionary.Remove(key);
|
||||
}
|
||||
namespace PSRule.Rules.GitHub;
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public static void AddUnique(this IDictionary<string, object> dictionary, IEnumerable<KeyValuePair<string, object>> values)
|
||||
{
|
||||
foreach (var kv in values)
|
||||
if (!dictionary.ContainsKey(kv.Key))
|
||||
dictionary.Add(kv.Key, kv.Value);
|
||||
}
|
||||
internal static class DictionaryExtensions
|
||||
{
|
||||
[DebuggerStepThrough]
|
||||
public static bool TryPopValue(this IDictionary<string, object> dictionary, string key, out object value)
|
||||
{
|
||||
return dictionary.TryGetValue(key, out value) && dictionary.Remove(key);
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
public static void AddUnique(this IDictionary<string, object> dictionary, IEnumerable<KeyValuePair<string, object>> values)
|
||||
{
|
||||
foreach (var kv in values)
|
||||
if (!dictionary.ContainsKey(kv.Key))
|
||||
dictionary.Add(kv.Key, kv.Value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,28 +4,27 @@
|
|||
using System.Net.Http;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSRule.Rules.GitHub
|
||||
{
|
||||
internal static class HttpClientExtensions
|
||||
{
|
||||
public static T Get<T>(this HttpClient client, string requestUri, string[] headers)
|
||||
{
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
for (var i = 0; i < headers.Length; i++)
|
||||
message.Headers.Accept.ParseAdd(headers[i]);
|
||||
namespace PSRule.Rules.GitHub;
|
||||
|
||||
var requestTask = client.SendAsync(message);
|
||||
requestTask.Wait();
|
||||
var response = requestTask.Result;
|
||||
response.EnsureSuccessStatusCode();
|
||||
var contentTask = response.Content.ReadAsStringAsync();
|
||||
contentTask.Wait();
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
settings.Converters.Add(new GitHubCommunityConverter());
|
||||
return JsonConvert.DeserializeObject<T>(contentTask.Result, settings);
|
||||
}
|
||||
internal static class HttpClientExtensions
|
||||
{
|
||||
public static T Get<T>(this HttpClient client, string requestUri, string[] headers)
|
||||
{
|
||||
var message = new HttpRequestMessage(HttpMethod.Get, requestUri);
|
||||
for (var i = 0; i < headers.Length; i++)
|
||||
message.Headers.Accept.ParseAdd(headers[i]);
|
||||
|
||||
var requestTask = client.SendAsync(message);
|
||||
requestTask.Wait();
|
||||
var response = requestTask.Result;
|
||||
response.EnsureSuccessStatusCode();
|
||||
var contentTask = response.Content.ReadAsStringAsync();
|
||||
contentTask.Wait();
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
settings.Converters.Add(new GitHubCommunityConverter());
|
||||
return JsonConvert.DeserializeObject<T>(contentTask.Result, settings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,214 +9,213 @@ using Newtonsoft.Json;
|
|||
using PSRule.Rules.GitHub.Pipeline;
|
||||
using PSRule.Rules.GitHub.Resources;
|
||||
|
||||
namespace PSRule.Rules.GitHub
|
||||
namespace PSRule.Rules.GitHub;
|
||||
|
||||
/// <summary>
|
||||
/// A custom serializer to correctly convert PSObject properties to JSON instead of CLIXML.
|
||||
/// </summary>
|
||||
internal sealed class PSObjectJsonConverter : JsonConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom serializer to correctly convert PSObject properties to JSON instead of CLIXML.
|
||||
/// </summary>
|
||||
internal sealed class PSObjectJsonConverter : JsonConverter
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
return objectType == typeof(PSObject);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is not PSObject obj)
|
||||
throw new ArgumentException(message: PSRuleResources.SerializeNullPSObject, paramName: nameof(value));
|
||||
|
||||
if (value is FileSystemInfo fileSystemInfo)
|
||||
{
|
||||
return objectType == typeof(PSObject);
|
||||
WriteFileSystemInfo(writer, fileSystemInfo, serializer);
|
||||
return;
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
writer.WriteStartObject();
|
||||
foreach (var property in obj.Properties)
|
||||
{
|
||||
if (!(value is PSObject obj))
|
||||
throw new ArgumentException(message: PSRuleResources.SerializeNullPSObject, paramName: nameof(value));
|
||||
// Ignore properties that are not readable or can cause race condition
|
||||
if (!property.IsGettable || property.Value is PSDriveInfo || property.Value is ProviderInfo || property.Value is DirectoryInfo)
|
||||
continue;
|
||||
|
||||
if (value is FileSystemInfo fileSystemInfo)
|
||||
{
|
||||
WriteFileSystemInfo(writer, fileSystemInfo, serializer);
|
||||
return;
|
||||
}
|
||||
writer.WriteStartObject();
|
||||
foreach (var property in obj.Properties)
|
||||
{
|
||||
// Ignore properties that are not readable or can cause race condition
|
||||
if (!property.IsGettable || property.Value is PSDriveInfo || property.Value is ProviderInfo || property.Value is DirectoryInfo)
|
||||
continue;
|
||||
|
||||
writer.WritePropertyName(property.Name);
|
||||
serializer.Serialize(writer, property.Value);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
writer.WritePropertyName(property.Name);
|
||||
serializer.Serialize(writer, property.Value);
|
||||
}
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
// Create target object based on JObject
|
||||
var result = existingValue as PSObject ?? new PSObject();
|
||||
|
||||
// Read tokens
|
||||
ReadObject(value: result, reader: reader);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ReadObject(PSObject value, JsonReader reader)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
{
|
||||
// Create target object based on JObject
|
||||
var result = existingValue as PSObject ?? new PSObject();
|
||||
|
||||
// Read tokens
|
||||
ReadObject(value: result, reader: reader);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ReadObject(PSObject value, JsonReader reader)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
|
||||
case JsonToken.StartObject:
|
||||
var child = new PSObject();
|
||||
ReadObject(value: child, reader: reader);
|
||||
value.Properties.Add(new PSNoteProperty(name: name, value: child));
|
||||
break;
|
||||
case JsonToken.StartObject:
|
||||
var child = new PSObject();
|
||||
ReadObject(value: child, reader: reader);
|
||||
value.Properties.Add(new PSNoteProperty(name: name, value: child));
|
||||
break;
|
||||
|
||||
case JsonToken.StartArray:
|
||||
var items = new List<object>();
|
||||
case JsonToken.StartArray:
|
||||
var items = new List<object>();
|
||||
reader.Read();
|
||||
|
||||
while (reader.TokenType != JsonToken.EndArray)
|
||||
{
|
||||
items.Add(ReadValue(reader));
|
||||
reader.Read();
|
||||
}
|
||||
|
||||
while (reader.TokenType != JsonToken.EndArray)
|
||||
{
|
||||
items.Add(ReadValue(reader));
|
||||
reader.Read();
|
||||
}
|
||||
value.Properties.Add(new PSNoteProperty(name: name, value: items.ToArray()));
|
||||
break;
|
||||
|
||||
value.Properties.Add(new PSNoteProperty(name: name, value: items.ToArray()));
|
||||
break;
|
||||
|
||||
default:
|
||||
value.Properties.Add(new PSNoteProperty(name: name, value: reader.Value));
|
||||
break;
|
||||
}
|
||||
reader.Read();
|
||||
default:
|
||||
value.Properties.Add(new PSNoteProperty(name: name, value: reader.Value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static object ReadValue(JsonReader reader)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
return reader.Value;
|
||||
|
||||
var value = new PSObject();
|
||||
ReadObject(value, reader);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void WriteFileSystemInfo(JsonWriter writer, FileSystemInfo value, JsonSerializer serializer)
|
||||
{
|
||||
serializer.Serialize(writer, value.FullName);
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class GitHubCommunityConverter : JsonConverter
|
||||
private static object ReadValue(JsonReader reader)
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
return reader.Value;
|
||||
|
||||
var value = new PSObject();
|
||||
ReadObject(value, reader);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void WriteFileSystemInfo(JsonWriter writer, FileSystemInfo value, JsonSerializer serializer)
|
||||
{
|
||||
serializer.Serialize(writer, value.FullName);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class GitHubCommunityConverter : JsonConverter
|
||||
{
|
||||
public override bool CanConvert(Type objectType)
|
||||
{
|
||||
return objectType == typeof(Data.CommunityProfile);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
// Create target object based on JObject
|
||||
var result = existingValue as Data.CommunityProfile ?? new Data.CommunityProfile();
|
||||
|
||||
// Read tokens
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
{
|
||||
return objectType == typeof(Data.CommunityProfile);
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
// Create target object based on JObject
|
||||
var result = existingValue as Data.CommunityProfile ?? new Data.CommunityProfile();
|
||||
|
||||
// Read tokens
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
|
||||
case JsonToken.StartObject:
|
||||
if (name == "files")
|
||||
{
|
||||
ReadFiles(result, reader);
|
||||
}
|
||||
break;
|
||||
}
|
||||
reader.Read();
|
||||
case JsonToken.StartObject:
|
||||
if (name == "files")
|
||||
{
|
||||
ReadFiles(result, reader);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void ReadFiles(Data.CommunityProfile value, JsonReader reader)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
private static void ReadFiles(Data.CommunityProfile value, JsonReader reader)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.StartObject)
|
||||
throw new PipelineSerializationException(PSRuleResources.ReadJsonFailed);
|
||||
|
||||
reader.Read();
|
||||
string name = null;
|
||||
|
||||
// Read each token
|
||||
while (reader.TokenType != JsonToken.EndObject)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
case JsonToken.PropertyName:
|
||||
name = reader.Value.ToString();
|
||||
break;
|
||||
|
||||
case JsonToken.Null:
|
||||
if (name == "issue_template")
|
||||
case JsonToken.Null:
|
||||
if (name == "issue_template")
|
||||
value.IssueTemplate = true;
|
||||
|
||||
break;
|
||||
|
||||
case JsonToken.StartObject:
|
||||
switch (name)
|
||||
{
|
||||
case "code_of_conduct":
|
||||
value.CodeOfConduct = true;
|
||||
break;
|
||||
|
||||
case "contributing":
|
||||
value.Contributing = true;
|
||||
break;
|
||||
|
||||
case "issue_template":
|
||||
value.IssueTemplate = true;
|
||||
break;
|
||||
|
||||
break;
|
||||
case "pull_request_template":
|
||||
value.PullRequestTemplate = true;
|
||||
break;
|
||||
|
||||
case JsonToken.StartObject:
|
||||
switch (name)
|
||||
{
|
||||
case "code_of_conduct":
|
||||
value.CodeOfConduct = true;
|
||||
break;
|
||||
case "license":
|
||||
value.License = true;
|
||||
break;
|
||||
|
||||
case "contributing":
|
||||
value.Contributing = true;
|
||||
break;
|
||||
case "readme":
|
||||
value.ReadMe = true;
|
||||
break;
|
||||
|
||||
case "issue_template":
|
||||
value.IssueTemplate = true;
|
||||
break;
|
||||
|
||||
case "pull_request_template":
|
||||
value.PullRequestTemplate = true;
|
||||
break;
|
||||
|
||||
case "license":
|
||||
value.License = true;
|
||||
break;
|
||||
|
||||
case "readme":
|
||||
value.ReadMe = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
reader.Read();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
reader.Skip();
|
||||
break;
|
||||
}
|
||||
reader.Read();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace PSRule.Rules.GitHub
|
||||
namespace PSRule.Rules.GitHub;
|
||||
|
||||
internal static class PSObjectExtensions
|
||||
{
|
||||
internal static class PSObjectExtensions
|
||||
internal static T GetPropertyValue<T>(this PSObject obj, string propertyName)
|
||||
{
|
||||
internal static T GetPropertyValue<T>(this PSObject obj, string propertyName)
|
||||
{
|
||||
return (T)obj.Properties[propertyName].Value;
|
||||
}
|
||||
return (T)obj.Properties[propertyName].Value;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,49 +5,48 @@ using System;
|
|||
using System.ComponentModel;
|
||||
using YamlDotNet.Serialization;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Configuration
|
||||
namespace PSRule.Rules.GitHub.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// A set of configuration values that can be used within rule definitions.
|
||||
/// </summary>
|
||||
public sealed class ConfigurationOption
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of configuration values that can be used within rule definitions.
|
||||
/// </summary>
|
||||
public sealed class ConfigurationOption
|
||||
public ConfigurationOption()
|
||||
{
|
||||
public ConfigurationOption()
|
||||
{
|
||||
DefaultOrg = null;
|
||||
}
|
||||
|
||||
public ConfigurationOption(ConfigurationOption option)
|
||||
{
|
||||
if (option == null)
|
||||
throw new ArgumentNullException(nameof(option));
|
||||
|
||||
DefaultOrg = option.DefaultOrg;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ConfigurationOption option && Equals(option);
|
||||
}
|
||||
|
||||
public bool Equals(ConfigurationOption other)
|
||||
{
|
||||
return other != null &&
|
||||
DefaultOrg == other.DefaultOrg;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked // Overflow is fine
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + (DefaultOrg != null ? DefaultOrg.GetHashCode() : 0);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
[DefaultValue(null)]
|
||||
[YamlMember(Alias = "GitHub_DefaultOrg")]
|
||||
public string DefaultOrg { get; set; }
|
||||
DefaultOrg = null;
|
||||
}
|
||||
|
||||
public ConfigurationOption(ConfigurationOption option)
|
||||
{
|
||||
if (option == null)
|
||||
throw new ArgumentNullException(nameof(option));
|
||||
|
||||
DefaultOrg = option.DefaultOrg;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ConfigurationOption option && Equals(option);
|
||||
}
|
||||
|
||||
public bool Equals(ConfigurationOption other)
|
||||
{
|
||||
return other != null &&
|
||||
DefaultOrg == other.DefaultOrg;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked // Overflow is fine
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 23 + (DefaultOrg != null ? DefaultOrg.GetHashCode() : 0);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
[DefaultValue(null)]
|
||||
[YamlMember(Alias = "GitHub_DefaultOrg")]
|
||||
public string DefaultOrg { get; set; }
|
||||
}
|
||||
|
|
|
@ -4,21 +4,20 @@
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Configuration
|
||||
namespace PSRule.Rules.GitHub.Configuration;
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum OutputEncoding
|
||||
{
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum OutputEncoding
|
||||
{
|
||||
Default = 0,
|
||||
Default = 0,
|
||||
|
||||
UTF8 = 1,
|
||||
UTF8 = 1,
|
||||
|
||||
UTF7 = 2,
|
||||
UTF7 = 2,
|
||||
|
||||
Unicode = 3,
|
||||
Unicode = 3,
|
||||
|
||||
UTF32 = 4,
|
||||
UTF32 = 4,
|
||||
|
||||
ASCII = 5
|
||||
}
|
||||
ASCII = 5
|
||||
}
|
||||
|
|
|
@ -4,68 +4,67 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Configuration
|
||||
namespace PSRule.Rules.GitHub.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Options for generating and formatting output.
|
||||
/// </summary>
|
||||
public sealed class OutputOption : IEquatable<OutputOption>
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for generating and formatting output.
|
||||
/// </summary>
|
||||
public sealed class OutputOption : IEquatable<OutputOption>
|
||||
private const OutputEncoding DEFAULT_ENCODING = OutputEncoding.Default;
|
||||
|
||||
internal static readonly OutputOption Default = new()
|
||||
{
|
||||
private const OutputEncoding DEFAULT_ENCODING = OutputEncoding.Default;
|
||||
Encoding = DEFAULT_ENCODING
|
||||
};
|
||||
|
||||
internal static readonly OutputOption Default = new OutputOption
|
||||
{
|
||||
Encoding = DEFAULT_ENCODING
|
||||
};
|
||||
|
||||
public OutputOption()
|
||||
{
|
||||
Encoding = null;
|
||||
Path = null;
|
||||
}
|
||||
|
||||
public OutputOption(OutputOption option)
|
||||
{
|
||||
if (option == null)
|
||||
throw new ArgumentNullException(nameof(option));
|
||||
|
||||
Encoding = option.Encoding;
|
||||
Path = option.Path;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is OutputOption option && Equals(option);
|
||||
}
|
||||
|
||||
public bool Equals(OutputOption other)
|
||||
{
|
||||
return other != null &&
|
||||
Encoding == other.Encoding &&
|
||||
Path == other.Path;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked // Overflow is fine
|
||||
{
|
||||
int hash = 17;
|
||||
hash = hash * 23 + (Encoding.HasValue ? Encoding.Value.GetHashCode() : 0);
|
||||
hash = hash * 23 + (Path != null ? Path.GetHashCode() : 0);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The encoding to use when writing results to file.
|
||||
/// </summary>
|
||||
[DefaultValue(null)]
|
||||
public OutputEncoding? Encoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The file path location to save results.
|
||||
/// </summary>
|
||||
[DefaultValue(null)]
|
||||
public string Path { get; set; }
|
||||
public OutputOption()
|
||||
{
|
||||
Encoding = null;
|
||||
Path = null;
|
||||
}
|
||||
|
||||
public OutputOption(OutputOption option)
|
||||
{
|
||||
if (option == null)
|
||||
throw new ArgumentNullException(nameof(option));
|
||||
|
||||
Encoding = option.Encoding;
|
||||
Path = option.Path;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is OutputOption option && Equals(option);
|
||||
}
|
||||
|
||||
public bool Equals(OutputOption other)
|
||||
{
|
||||
return other != null &&
|
||||
Encoding == other.Encoding &&
|
||||
Path == other.Path;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked // Overflow is fine
|
||||
{
|
||||
var hash = 17;
|
||||
hash = hash * 23 + (Encoding.HasValue ? Encoding.Value.GetHashCode() : 0);
|
||||
hash = hash * 23 + (Path != null ? Path.GetHashCode() : 0);
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The encoding to use when writing results to file.
|
||||
/// </summary>
|
||||
[DefaultValue(null)]
|
||||
public OutputEncoding? Encoding { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The file path location to save results.
|
||||
/// </summary>
|
||||
[DefaultValue(null)]
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
|
|
@ -8,107 +8,106 @@ using System.Management.Automation;
|
|||
using System.Net;
|
||||
using System.Security;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Configuration
|
||||
namespace PSRule.Rules.GitHub.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// A delgate to allow callback to PowerShell to get current working path.
|
||||
/// </summary>
|
||||
internal delegate string PathDelegate();
|
||||
|
||||
public sealed class PSRuleOption
|
||||
{
|
||||
/// <summary>
|
||||
/// A delgate to allow callback to PowerShell to get current working path.
|
||||
/// </summary>
|
||||
internal delegate string PathDelegate();
|
||||
|
||||
public sealed class PSRuleOption
|
||||
internal static readonly PSRuleOption Default = new()
|
||||
{
|
||||
internal static readonly PSRuleOption Default = new PSRuleOption
|
||||
{
|
||||
Output = OutputOption.Default
|
||||
};
|
||||
Output = OutputOption.Default
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// A callback that is overridden by PowerShell so that the current working path can be retrieved.
|
||||
/// </summary>
|
||||
private static PathDelegate _GetWorkingPath = () => Directory.GetCurrentDirectory();
|
||||
/// <summary>
|
||||
/// A callback that is overridden by PowerShell so that the current working path can be retrieved.
|
||||
/// </summary>
|
||||
private static PathDelegate _GetWorkingPath = () => Directory.GetCurrentDirectory();
|
||||
|
||||
public PSRuleOption()
|
||||
public PSRuleOption()
|
||||
{
|
||||
// Set defaults
|
||||
Configuration = new ConfigurationOption();
|
||||
Output = new OutputOption();
|
||||
}
|
||||
|
||||
public ConfigurationOption Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Options that affect how output is generated.
|
||||
/// </summary>
|
||||
public OutputOption Output { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set working path from PowerShell host environment.
|
||||
/// </summary>
|
||||
/// <param name="executionContext">An $ExecutionContext object.</param>
|
||||
/// <remarks>
|
||||
/// Called from PowerShell.
|
||||
/// </remarks>
|
||||
public static void UseExecutionContext(EngineIntrinsics executionContext)
|
||||
{
|
||||
if (executionContext == null)
|
||||
{
|
||||
// Set defaults
|
||||
Configuration = new ConfigurationOption();
|
||||
Output = new OutputOption();
|
||||
_GetWorkingPath = () => Directory.GetCurrentDirectory();
|
||||
return;
|
||||
}
|
||||
_GetWorkingPath = () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path;
|
||||
}
|
||||
|
||||
public ConfigurationOption Configuration { get; set; }
|
||||
public static string GetWorkingPath()
|
||||
{
|
||||
return _GetWorkingPath();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options that affect how output is generated.
|
||||
/// </summary>
|
||||
public OutputOption Output { get; set; }
|
||||
/// <summary>
|
||||
/// Get a full path instead of a relative path that may be passed from PowerShell.
|
||||
/// </summary>
|
||||
internal static string GetRootedPath(string path)
|
||||
{
|
||||
return Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(GetWorkingPath(), path));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set working path from PowerShell host environment.
|
||||
/// </summary>
|
||||
/// <param name="executionContext">An $ExecutionContext object.</param>
|
||||
/// <remarks>
|
||||
/// Called from PowerShell.
|
||||
/// </remarks>
|
||||
public static void UseExecutionContext(EngineIntrinsics executionContext)
|
||||
{
|
||||
if (executionContext == null)
|
||||
{
|
||||
_GetWorkingPath = () => Directory.GetCurrentDirectory();
|
||||
return;
|
||||
}
|
||||
_GetWorkingPath = () => executionContext.SessionState.Path.CurrentFileSystemLocation.Path;
|
||||
}
|
||||
/// <summary>
|
||||
/// Get a full path instead of a relative path that may be passed from PowerShell.
|
||||
/// </summary>
|
||||
internal static string GetRootedBasePath(string path)
|
||||
{
|
||||
var rootedPath = GetRootedPath(path);
|
||||
if (rootedPath.Length > 0 && IsSeparator(rootedPath[rootedPath.Length - 1]))
|
||||
return rootedPath;
|
||||
|
||||
public static string GetWorkingPath()
|
||||
{
|
||||
return _GetWorkingPath();
|
||||
}
|
||||
return string.Concat(rootedPath, Path.DirectorySeparatorChar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a full path instead of a relative path that may be passed from PowerShell.
|
||||
/// </summary>
|
||||
internal static string GetRootedPath(string path)
|
||||
{
|
||||
return Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(GetWorkingPath(), path));
|
||||
}
|
||||
internal static bool TryGetEnvironmentVariableString(string variable, out string value)
|
||||
{
|
||||
value = null;
|
||||
var v = Environment.GetEnvironmentVariable(variable);
|
||||
if (string.IsNullOrEmpty(v))
|
||||
return false;
|
||||
|
||||
/// <summary>
|
||||
/// Get a full path instead of a relative path that may be passed from PowerShell.
|
||||
/// </summary>
|
||||
internal static string GetRootedBasePath(string path)
|
||||
{
|
||||
var rootedPath = GetRootedPath(path);
|
||||
if (rootedPath.Length > 0 && IsSeparator(rootedPath[rootedPath.Length - 1]))
|
||||
return rootedPath;
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
return string.Concat(rootedPath, Path.DirectorySeparatorChar);
|
||||
}
|
||||
internal static bool TryGetEnvironmentVariableSecureString(string variable, out SecureString value)
|
||||
{
|
||||
value = null;
|
||||
var v = Environment.GetEnvironmentVariable(variable);
|
||||
if (string.IsNullOrEmpty(v))
|
||||
return false;
|
||||
|
||||
internal static bool TryGetEnvironmentVariableString(string variable, out string value)
|
||||
{
|
||||
value = null;
|
||||
var v = Environment.GetEnvironmentVariable(variable);
|
||||
if (string.IsNullOrEmpty(v))
|
||||
return false;
|
||||
value = new NetworkCredential("na", v).SecurePassword;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool TryGetEnvironmentVariableSecureString(string variable, out SecureString value)
|
||||
{
|
||||
value = null;
|
||||
var v = Environment.GetEnvironmentVariable(variable);
|
||||
if (string.IsNullOrEmpty(v))
|
||||
return false;
|
||||
|
||||
value = new NetworkCredential("na", v).SecurePassword;
|
||||
return true;
|
||||
}
|
||||
|
||||
[DebuggerStepThrough]
|
||||
private static bool IsSeparator(char c)
|
||||
{
|
||||
return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
|
||||
}
|
||||
[DebuggerStepThrough]
|
||||
private static bool IsSeparator(char c)
|
||||
{
|
||||
return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,205 +3,204 @@
|
|||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Data
|
||||
namespace PSRule.Rules.GitHub.Data;
|
||||
|
||||
public sealed class Repository
|
||||
{
|
||||
public sealed class Repository
|
||||
private const string OBJECT_TYPE = "api.github.com/repos";
|
||||
|
||||
internal Repository(string owner, string name)
|
||||
{
|
||||
private const string OBJECT_TYPE = "api.github.com/repos";
|
||||
|
||||
internal Repository(string owner, string name)
|
||||
{
|
||||
Owner = owner;
|
||||
Name = name;
|
||||
FullName = string.Concat(owner, '/', name);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Used for object serialization")]
|
||||
public string Type => OBJECT_TYPE;
|
||||
|
||||
public string Owner { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string FullName { get; }
|
||||
|
||||
public string RepositoryName => FullName;
|
||||
|
||||
public string Description { get; internal set; }
|
||||
|
||||
public bool Private { get; internal set; }
|
||||
|
||||
public bool Fork { get; internal set; }
|
||||
|
||||
public bool Archived { get; internal set; }
|
||||
|
||||
public string DefaultBranch { get; internal set; }
|
||||
|
||||
public string License { get; internal set; }
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "Used for object serialization")]
|
||||
public string HtmlUrl { get; internal set; }
|
||||
|
||||
public string Homepage { get; internal set; }
|
||||
|
||||
public string Language { get; internal set; }
|
||||
|
||||
public bool? AllowRebaseMerge { get; internal set; }
|
||||
|
||||
public bool? AllowSquashMerge { get; internal set; }
|
||||
|
||||
public bool? AllowMergeCommit { get; internal set; }
|
||||
|
||||
public bool HasIssues { get; internal set; }
|
||||
|
||||
public bool HasWiki { get; internal set; }
|
||||
|
||||
public bool HasDownloads { get; internal set; }
|
||||
|
||||
public bool HasPages { get; internal set; }
|
||||
|
||||
public CommunityProfile CommunityProfile { get; internal set; }
|
||||
|
||||
public IEnumerable<string> CommunityFiles { get; internal set; }
|
||||
|
||||
public bool IsTemplate { get; internal set; }
|
||||
|
||||
public bool? DeleteBranchOnMerge { get; internal set; }
|
||||
|
||||
public string Visibility { get; internal set; }
|
||||
Owner = owner;
|
||||
Name = name;
|
||||
FullName = string.Concat(owner, '/', name);
|
||||
}
|
||||
|
||||
public sealed class Branch
|
||||
{
|
||||
private const string OBJECT_TYPE = "api.github.com/repos/branches";
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Used for object serialization")]
|
||||
public string Type => OBJECT_TYPE;
|
||||
|
||||
internal Branch(string repositoryName, string name)
|
||||
{
|
||||
Name = name;
|
||||
RepositoryName = repositoryName;
|
||||
}
|
||||
public string Owner { get; }
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Used for object serialization")]
|
||||
public string Type => OBJECT_TYPE;
|
||||
public string Name { get; }
|
||||
|
||||
public string Name { get; }
|
||||
public string FullName { get; }
|
||||
|
||||
public string BranchName => Name;
|
||||
public string RepositoryName => FullName;
|
||||
|
||||
public string RepositoryName { get; }
|
||||
public string Description { get; internal set; }
|
||||
|
||||
public BranchProtection Protection { get; internal set; }
|
||||
public bool Private { get; internal set; }
|
||||
|
||||
public IEnumerable<BranchStatus> Status { get; internal set; }
|
||||
}
|
||||
public bool Fork { get; internal set; }
|
||||
|
||||
public sealed class BranchProtection
|
||||
{
|
||||
public bool Archived { get; internal set; }
|
||||
|
||||
internal BranchProtection(bool enabled)
|
||||
{
|
||||
Enabled = enabled;
|
||||
}
|
||||
public string DefaultBranch { get; internal set; }
|
||||
|
||||
public bool Enabled { get; }
|
||||
public string License { get; internal set; }
|
||||
|
||||
public bool? EnforceAdmins { get; internal set; }
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1056:Uri properties should not be strings", Justification = "Used for object serialization")]
|
||||
public string HtmlUrl { get; internal set; }
|
||||
|
||||
public bool? RequireUpToDate { get; internal set; }
|
||||
public string Homepage { get; internal set; }
|
||||
|
||||
public IEnumerable<string> RequireStatusChecks { get; internal set; }
|
||||
public string Language { get; internal set; }
|
||||
|
||||
public bool RequirePullRequestReviews { get; internal set; }
|
||||
public bool? AllowRebaseMerge { get; internal set; }
|
||||
|
||||
public bool? DismissStaleReviews { get; internal set; }
|
||||
public bool? AllowSquashMerge { get; internal set; }
|
||||
|
||||
public bool? RequireCodeOwnerReviews { get; internal set; }
|
||||
public bool? AllowMergeCommit { get; internal set; }
|
||||
|
||||
public int? RequiredApprovingReviewCount { get; internal set; }
|
||||
}
|
||||
public bool HasIssues { get; internal set; }
|
||||
|
||||
public sealed class BranchStatus
|
||||
{
|
||||
internal BranchStatus(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
public bool HasWiki { get; internal set; }
|
||||
|
||||
public string Name { get; }
|
||||
public bool HasDownloads { get; internal set; }
|
||||
|
||||
public string Status { get; internal set; }
|
||||
public bool HasPages { get; internal set; }
|
||||
|
||||
public string Conclusion { get; internal set; }
|
||||
}
|
||||
public CommunityProfile CommunityProfile { get; internal set; }
|
||||
|
||||
public sealed class Label
|
||||
{
|
||||
internal Label(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
public IEnumerable<string> CommunityFiles { get; internal set; }
|
||||
|
||||
public string Name { get; }
|
||||
public bool IsTemplate { get; internal set; }
|
||||
|
||||
public string Description { get; internal set; }
|
||||
public bool? DeleteBranchOnMerge { get; internal set; }
|
||||
|
||||
public string Color { get; internal set; }
|
||||
|
||||
public bool Default { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class Milestone
|
||||
{
|
||||
internal Milestone(int number)
|
||||
{
|
||||
Number = number;
|
||||
}
|
||||
|
||||
public int Number { get; }
|
||||
|
||||
public string Title { get; internal set; }
|
||||
|
||||
public string Description { get; internal set; }
|
||||
|
||||
public string State { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class Release
|
||||
{
|
||||
internal Release(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public bool Prerelease { get; internal set; }
|
||||
|
||||
public string TagName { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class RepositoryTag
|
||||
{
|
||||
internal RepositoryTag(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
|
||||
public sealed class CommunityProfile
|
||||
{
|
||||
public bool CodeOfConduct { get; set; }
|
||||
|
||||
public bool Contributing { get; set; }
|
||||
|
||||
public bool IssueTemplate { get; set; }
|
||||
|
||||
public bool PullRequestTemplate { get; set; }
|
||||
|
||||
public bool License { get; set; }
|
||||
|
||||
public bool ReadMe { get; set; }
|
||||
}
|
||||
public string Visibility { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class Branch
|
||||
{
|
||||
private const string OBJECT_TYPE = "api.github.com/repos/branches";
|
||||
|
||||
internal Branch(string repositoryName, string name)
|
||||
{
|
||||
Name = name;
|
||||
RepositoryName = repositoryName;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Used for object serialization")]
|
||||
public string Type => OBJECT_TYPE;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string BranchName => Name;
|
||||
|
||||
public string RepositoryName { get; }
|
||||
|
||||
public BranchProtection Protection { get; internal set; }
|
||||
|
||||
public IEnumerable<BranchStatus> Status { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class BranchProtection
|
||||
{
|
||||
|
||||
internal BranchProtection(bool enabled)
|
||||
{
|
||||
Enabled = enabled;
|
||||
}
|
||||
|
||||
public bool Enabled { get; }
|
||||
|
||||
public bool? EnforceAdmins { get; internal set; }
|
||||
|
||||
public bool? RequireUpToDate { get; internal set; }
|
||||
|
||||
public IEnumerable<string> RequireStatusChecks { get; internal set; }
|
||||
|
||||
public bool RequirePullRequestReviews { get; internal set; }
|
||||
|
||||
public bool? DismissStaleReviews { get; internal set; }
|
||||
|
||||
public bool? RequireCodeOwnerReviews { get; internal set; }
|
||||
|
||||
public int? RequiredApprovingReviewCount { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class BranchStatus
|
||||
{
|
||||
internal BranchStatus(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Status { get; internal set; }
|
||||
|
||||
public string Conclusion { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class Label
|
||||
{
|
||||
internal Label(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Description { get; internal set; }
|
||||
|
||||
public string Color { get; internal set; }
|
||||
|
||||
public bool Default { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class Milestone
|
||||
{
|
||||
internal Milestone(int number)
|
||||
{
|
||||
Number = number;
|
||||
}
|
||||
|
||||
public int Number { get; }
|
||||
|
||||
public string Title { get; internal set; }
|
||||
|
||||
public string Description { get; internal set; }
|
||||
|
||||
public string State { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class Release
|
||||
{
|
||||
internal Release(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public bool Prerelease { get; internal set; }
|
||||
|
||||
public string TagName { get; internal set; }
|
||||
}
|
||||
|
||||
public sealed class RepositoryTag
|
||||
{
|
||||
internal RepositoryTag(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
}
|
||||
|
||||
public sealed class CommunityProfile
|
||||
{
|
||||
public bool CodeOfConduct { get; set; }
|
||||
|
||||
public bool Contributing { get; set; }
|
||||
|
||||
public bool IssueTemplate { get; set; }
|
||||
|
||||
public bool PullRequestTemplate { get; set; }
|
||||
|
||||
public bool License { get; set; }
|
||||
|
||||
public bool ReadMe { get; set; }
|
||||
}
|
||||
|
|
|
@ -5,118 +5,117 @@ using System;
|
|||
using System.Runtime.Serialization;
|
||||
using System.Security.Permissions;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
/// <summary>
|
||||
/// A base class for all pipeline exceptions.
|
||||
/// </summary>
|
||||
public abstract class PipelineException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// A base class for all pipeline exceptions.
|
||||
/// Creates a pipeline exception.
|
||||
/// </summary>
|
||||
public abstract class PipelineException : Exception
|
||||
protected PipelineException()
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a pipeline exception.
|
||||
/// </summary>
|
||||
protected PipelineException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a pipeline exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
protected PipelineException(string message)
|
||||
: base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a pipeline exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
/// <param name="innerException">A nested exception that caused the issue.</param>
|
||||
protected PipelineException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
|
||||
protected PipelineException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A serialization exception.
|
||||
/// Creates a pipeline exception.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class PipelineSerializationException : PipelineException
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
protected PipelineException(string message)
|
||||
: base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a pipeline exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
/// <param name="innerException">A nested exception that caused the issue.</param>
|
||||
protected PipelineException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
|
||||
protected PipelineException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A serialization exception.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class PipelineSerializationException : PipelineException
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a serialization exception.
|
||||
/// </summary>
|
||||
public PipelineSerializationException()
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a serialization exception.
|
||||
/// </summary>
|
||||
public PipelineSerializationException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a serialization exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
public PipelineSerializationException(string message)
|
||||
: base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a serialization exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
/// <param name="innerException">A nested exception that caused the issue.</param>
|
||||
public PipelineSerializationException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
|
||||
private PipelineSerializationException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) { }
|
||||
|
||||
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An exception related to template linking.
|
||||
/// Creates a serialization exception.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class InvalidTemplateLinkException : PipelineException
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
public PipelineSerializationException(string message)
|
||||
: base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a serialization exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
/// <param name="innerException">A nested exception that caused the issue.</param>
|
||||
public PipelineSerializationException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
|
||||
private PipelineSerializationException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) { }
|
||||
|
||||
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a template linking exception.
|
||||
/// </summary>
|
||||
public InvalidTemplateLinkException()
|
||||
{
|
||||
}
|
||||
if (info == null)
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
|
||||
/// <summary>
|
||||
/// Creates a template linking exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
public InvalidTemplateLinkException(string message)
|
||||
: base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a template linking exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
/// <param name="innerException">A nested exception that caused the issue.</param>
|
||||
public InvalidTemplateLinkException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
|
||||
private InvalidTemplateLinkException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) { }
|
||||
|
||||
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An exception related to template linking.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public sealed class InvalidTemplateLinkException : PipelineException
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a template linking exception.
|
||||
/// </summary>
|
||||
public InvalidTemplateLinkException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a template linking exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
public InvalidTemplateLinkException(string message)
|
||||
: base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a template linking exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The detail of the exception.</param>
|
||||
/// <param name="innerException">A nested exception that caused the issue.</param>
|
||||
public InvalidTemplateLinkException(string message, Exception innerException)
|
||||
: base(message, innerException) { }
|
||||
|
||||
private InvalidTemplateLinkException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context) { }
|
||||
|
||||
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
|
||||
public override void GetObjectData(SerializationInfo info, StreamingContext context)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException(nameof(info));
|
||||
|
||||
base.GetObjectData(info, context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,175 +3,173 @@
|
|||
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using PSRule.Rules.GitHub.Configuration;
|
||||
using PSRule.Rules.GitHub.Pipeline.Output;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
public interface IExportPipelineBuilder : IPipelineBuilder
|
||||
{
|
||||
public interface IExportPipelineBuilder : IPipelineBuilder
|
||||
{
|
||||
void Repository(string[] repo);
|
||||
void Repository(string[] repo);
|
||||
|
||||
void Credential(PSCredential credential);
|
||||
void Credential(PSCredential credential);
|
||||
}
|
||||
|
||||
internal sealed class ExportPipelineBuilder : PipelineBuilderBase, IExportPipelineBuilder
|
||||
{
|
||||
private const string OUTPUTFILE_PREFIX = "github-";
|
||||
private const string OUTPUTFILE_EXTENSION = ".json";
|
||||
|
||||
private const string GITHUB_REPOSITORY = "GITHUB_REPOSITORY";
|
||||
private const string GITHUB_TOKEN = "GITHUB_TOKEN";
|
||||
|
||||
private string[] _Repository;
|
||||
private bool _UseGitHubToken;
|
||||
private PSCredential _Credential;
|
||||
private bool _PassThru;
|
||||
|
||||
public ExportPipelineBuilder(PSRuleOption option)
|
||||
{
|
||||
_PassThru = false;
|
||||
Configure(option);
|
||||
}
|
||||
|
||||
internal sealed class ExportPipelineBuilder : PipelineBuilderBase, IExportPipelineBuilder
|
||||
public void Repository(string[] repository)
|
||||
{
|
||||
private const string OUTPUTFILE_PREFIX = "github-";
|
||||
private const string OUTPUTFILE_EXTENSION = ".json";
|
||||
|
||||
private const string GITHUB_REPOSITORY = "GITHUB_REPOSITORY";
|
||||
private const string GITHUB_TOKEN = "GITHUB_TOKEN";
|
||||
|
||||
private string[] _Repository;
|
||||
private bool _UseGitHubToken;
|
||||
private PSCredential _Credential;
|
||||
private bool _PassThru;
|
||||
|
||||
public ExportPipelineBuilder(PSRuleOption option)
|
||||
if (repository == null)
|
||||
{
|
||||
_PassThru = false;
|
||||
Configure(option);
|
||||
}
|
||||
|
||||
public void Repository(string[] repository)
|
||||
{
|
||||
if (repository == null)
|
||||
{
|
||||
if (PSRuleOption.TryGetEnvironmentVariableString(GITHUB_REPOSITORY, out var repo))
|
||||
_Repository = new string[] { repo };
|
||||
|
||||
return;
|
||||
}
|
||||
_Repository = repository;
|
||||
}
|
||||
|
||||
public void UseGitHubToken(bool useGitHubToken)
|
||||
{
|
||||
_UseGitHubToken = useGitHubToken;
|
||||
}
|
||||
|
||||
public void Credential(PSCredential credential)
|
||||
{
|
||||
if (_UseGitHubToken && credential == null)
|
||||
{
|
||||
if (PSRuleOption.TryGetEnvironmentVariableSecureString(GITHUB_TOKEN, out var token))
|
||||
_Credential = new PSCredential("token", token);
|
||||
|
||||
return;
|
||||
}
|
||||
_Credential = credential;
|
||||
}
|
||||
|
||||
public void PassThru(bool passThru)
|
||||
{
|
||||
_PassThru = passThru;
|
||||
}
|
||||
|
||||
public override IPipeline Build()
|
||||
{
|
||||
return new ExportPipeline(PrepareContext(), PrepareWriter(), GetGitHubContext());
|
||||
}
|
||||
|
||||
protected override PipelineWriter PrepareWriter()
|
||||
{
|
||||
return _PassThru ? base.PrepareWriter() : new JsonOutputWriter(GetOutput(), Option);
|
||||
}
|
||||
|
||||
protected override PipelineWriter GetOutput()
|
||||
{
|
||||
// Redirect to file instead
|
||||
if (!string.IsNullOrEmpty(Option.Output.Path))
|
||||
{
|
||||
return new FileOutputWriter(
|
||||
inner: base.GetOutput(),
|
||||
option: Option,
|
||||
encoding: GetEncoding(Option.Output.Encoding),
|
||||
path: Option.Output.Path,
|
||||
defaultFile: string.Concat(OUTPUTFILE_PREFIX, Guid.NewGuid().ToString().Substring(0, 8), OUTPUTFILE_EXTENSION),
|
||||
shouldProcess: CmdletContext.ShouldProcess
|
||||
);
|
||||
}
|
||||
return base.GetOutput();
|
||||
}
|
||||
|
||||
private GitHubContext GetGitHubContext()
|
||||
{
|
||||
return new GitHubContext(_Repository, _Credential);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the character encoding for the specified output encoding.
|
||||
/// </summary>
|
||||
/// <param name="encoding"></param>
|
||||
/// <returns></returns>
|
||||
private static Encoding GetEncoding(OutputEncoding? encoding)
|
||||
{
|
||||
switch (encoding)
|
||||
{
|
||||
case OutputEncoding.UTF8:
|
||||
return Encoding.UTF8;
|
||||
|
||||
case OutputEncoding.UTF7:
|
||||
return Encoding.UTF7;
|
||||
|
||||
case OutputEncoding.Unicode:
|
||||
return Encoding.Unicode;
|
||||
|
||||
case OutputEncoding.UTF32:
|
||||
return Encoding.UTF32;
|
||||
|
||||
case OutputEncoding.ASCII:
|
||||
return Encoding.ASCII;
|
||||
|
||||
default:
|
||||
return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
}
|
||||
if (PSRuleOption.TryGetEnvironmentVariableString(GITHUB_REPOSITORY, out var repo))
|
||||
_Repository = new string[] { repo };
|
||||
|
||||
return;
|
||||
}
|
||||
_Repository = repository;
|
||||
}
|
||||
|
||||
internal sealed class ExportPipeline : PipelineBase
|
||||
public void UseGitHubToken(bool useGitHubToken)
|
||||
{
|
||||
private readonly GitHubContext _ServiceContext;
|
||||
private readonly RepositoryHelper _Helper;
|
||||
_UseGitHubToken = useGitHubToken;
|
||||
}
|
||||
|
||||
// Track whether Dispose has been called.
|
||||
private bool _Disposed;
|
||||
|
||||
internal ExportPipeline(PipelineContext context, PipelineWriter writer, GitHubContext serviceContext)
|
||||
: base(context, writer)
|
||||
public void Credential(PSCredential credential)
|
||||
{
|
||||
if (_UseGitHubToken && credential == null)
|
||||
{
|
||||
_ServiceContext = serviceContext;
|
||||
_Helper = new RepositoryHelper(serviceContext);
|
||||
if (PSRuleOption.TryGetEnvironmentVariableSecureString(GITHUB_TOKEN, out var token))
|
||||
_Credential = new PSCredential("token", token);
|
||||
|
||||
return;
|
||||
}
|
||||
_Credential = credential;
|
||||
}
|
||||
|
||||
public override void End()
|
||||
public void PassThru(bool passThru)
|
||||
{
|
||||
_PassThru = passThru;
|
||||
}
|
||||
|
||||
public override IPipeline Build()
|
||||
{
|
||||
return new ExportPipeline(PrepareContext(), PrepareWriter(), GetGitHubContext());
|
||||
}
|
||||
|
||||
protected override PipelineWriter PrepareWriter()
|
||||
{
|
||||
return _PassThru ? base.PrepareWriter() : new JsonOutputWriter(GetOutput(), Option);
|
||||
}
|
||||
|
||||
protected override PipelineWriter GetOutput()
|
||||
{
|
||||
// Redirect to file instead
|
||||
if (!string.IsNullOrEmpty(Option.Output.Path))
|
||||
{
|
||||
for (var i = 0; _ServiceContext.Repository != null && i < _ServiceContext.Repository.Length; i++)
|
||||
ProcessRepository(_ServiceContext.Repository[i]);
|
||||
|
||||
base.End();
|
||||
return new FileOutputWriter(
|
||||
inner: base.GetOutput(),
|
||||
option: Option,
|
||||
encoding: GetEncoding(Option.Output.Encoding),
|
||||
path: Option.Output.Path,
|
||||
defaultFile: string.Concat(OUTPUTFILE_PREFIX, Guid.NewGuid().ToString().Substring(0, 8), OUTPUTFILE_EXTENSION),
|
||||
shouldProcess: CmdletContext.ShouldProcess
|
||||
);
|
||||
}
|
||||
return base.GetOutput();
|
||||
}
|
||||
|
||||
internal void ProcessRepository(string repositorySlug)
|
||||
private GitHubContext GetGitHubContext()
|
||||
{
|
||||
return new GitHubContext(_Repository, _Credential);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the character encoding for the specified output encoding.
|
||||
/// </summary>
|
||||
/// <param name="encoding"></param>
|
||||
/// <returns></returns>
|
||||
private static Encoding GetEncoding(OutputEncoding? encoding)
|
||||
{
|
||||
switch (encoding)
|
||||
{
|
||||
var o = _Helper.Get(repositorySlug);
|
||||
if (o.Length > 0)
|
||||
Writer.WriteObject(o, true);
|
||||
}
|
||||
case OutputEncoding.UTF8:
|
||||
return Encoding.UTF8;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
_ServiceContext.Dispose();
|
||||
case OutputEncoding.UTF7:
|
||||
return Encoding.UTF7;
|
||||
|
||||
_Disposed = true;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
case OutputEncoding.Unicode:
|
||||
return Encoding.Unicode;
|
||||
|
||||
case OutputEncoding.UTF32:
|
||||
return Encoding.UTF32;
|
||||
|
||||
case OutputEncoding.ASCII:
|
||||
return Encoding.ASCII;
|
||||
|
||||
default:
|
||||
return new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ExportPipeline : PipelineBase
|
||||
{
|
||||
private readonly GitHubContext _ServiceContext;
|
||||
private readonly RepositoryHelper _Helper;
|
||||
|
||||
// Track whether Dispose has been called.
|
||||
private bool _Disposed;
|
||||
|
||||
internal ExportPipeline(PipelineContext context, PipelineWriter writer, GitHubContext serviceContext)
|
||||
: base(context, writer)
|
||||
{
|
||||
_ServiceContext = serviceContext;
|
||||
_Helper = new RepositoryHelper(serviceContext);
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
for (var i = 0; _ServiceContext.Repository != null && i < _ServiceContext.Repository.Length; i++)
|
||||
ProcessRepository(_ServiceContext.Repository[i]);
|
||||
|
||||
base.End();
|
||||
}
|
||||
|
||||
internal void ProcessRepository(string repositorySlug)
|
||||
{
|
||||
var o = _Helper.Get(repositorySlug);
|
||||
if (o.Length > 0)
|
||||
Writer.WriteObject(o, true);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (!_Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
_ServiceContext.Dispose();
|
||||
|
||||
_Disposed = true;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,370 +8,369 @@ using System.Net.Http;
|
|||
using System.Threading.Tasks;
|
||||
using PSRule.Rules.GitHub.Data;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
internal sealed class GitHubClient
|
||||
{
|
||||
internal sealed class GitHubClient
|
||||
private readonly string[] _GITHUB_HEADERS_COMMUNITY_PROFILE = new string[] { "application/vnd.github.v3+json", "application/vnd.github.black-panther+json" };
|
||||
|
||||
private readonly Octokit.GitHubClient _Client;
|
||||
private readonly HttpClient _HttpClient;
|
||||
|
||||
#region Constructor
|
||||
|
||||
public GitHubClient(GitHubContext serviceContext)
|
||||
{
|
||||
private readonly string[] GITHUB_HEADERS_COMMUNITY_PROFILE = new string[] { "application/vnd.github.v3+json", "application/vnd.github.black-panther+json" };
|
||||
_Client = serviceContext.GetClient();
|
||||
_HttpClient = serviceContext.GetHttpClient();
|
||||
}
|
||||
|
||||
private readonly Octokit.GitHubClient _Client;
|
||||
private readonly HttpClient _HttpClient;
|
||||
#endregion Constructor
|
||||
|
||||
#region Constructor
|
||||
#region Public methods
|
||||
|
||||
public GitHubClient(GitHubContext serviceContext)
|
||||
public Repository[] GetRepository(string repositorySlug)
|
||||
{
|
||||
var items = GetRepositoryInternal(repositorySlug);
|
||||
var results = new Repository[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
_Client = serviceContext.GetClient();
|
||||
_HttpClient = serviceContext.GetHttpClient();
|
||||
}
|
||||
|
||||
#endregion Constructor
|
||||
|
||||
#region Public methods
|
||||
|
||||
public Repository[] GetRepository(string repositorySlug)
|
||||
{
|
||||
var items = GetRepositoryInternal(repositorySlug);
|
||||
var results = new Repository[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
var communityFiles = GetCommunityFiles(items[i].Owner.Login, items[i].Name);
|
||||
var communityProfile = GetCommunityProfileInternal(items[i].Owner.Login, items[i].Name);
|
||||
results[i] = new Repository(items[i].Owner.Login, items[i].Name)
|
||||
{
|
||||
var communityFiles = GetCommunityFiles(items[i].Owner.Login, items[i].Name);
|
||||
var communityProfile = GetCommunityProfileInternal(items[i].Owner.Login, items[i].Name);
|
||||
results[i] = new Repository(items[i].Owner.Login, items[i].Name)
|
||||
Description = items[i].Description,
|
||||
Private = items[i].Private,
|
||||
Fork = items[i].Fork,
|
||||
Archived = items[i].Archived,
|
||||
DefaultBranch = items[i].DefaultBranch,
|
||||
License = items[i].License?.SpdxId,
|
||||
HtmlUrl = items[i].HtmlUrl,
|
||||
Homepage = items[i].Homepage,
|
||||
Language = items[i].Language,
|
||||
AllowMergeCommit = items[i].AllowMergeCommit,
|
||||
AllowRebaseMerge = items[i].AllowRebaseMerge,
|
||||
AllowSquashMerge = items[i].AllowSquashMerge,
|
||||
HasIssues = items[i].HasIssues,
|
||||
HasWiki = items[i].HasWiki,
|
||||
HasDownloads = items[i].HasDownloads,
|
||||
HasPages = items[i].HasPages,
|
||||
CommunityProfile = communityProfile,
|
||||
CommunityFiles = communityFiles,
|
||||
IsTemplate = items[i].IsTemplate,
|
||||
DeleteBranchOnMerge = items[i].DeleteBranchOnMerge,
|
||||
Visibility = items[i].Visibility.HasValue ? Enum.GetName(typeof(Octokit.RepositoryVisibility), items[i].Visibility) : null,
|
||||
};
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public Branch[] GetBranches(string owner, string name)
|
||||
{
|
||||
var items = GetBranchesInternal(owner, name);
|
||||
var results = new Data.Branch[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
var protection = items[i].Protected ? GetBranchProtectionInternal(owner, name, items[i].Name) : null;
|
||||
var status = GetBranchStatus(owner, name, items[i].Name);
|
||||
results[i] = new Branch(string.Concat(owner, '/', name), items[i].Name)
|
||||
{
|
||||
Protection = new BranchProtection(items[i].Protected)
|
||||
{
|
||||
Description = items[i].Description,
|
||||
Private = items[i].Private,
|
||||
Fork = items[i].Fork,
|
||||
Archived = items[i].Archived,
|
||||
DefaultBranch = items[i].DefaultBranch,
|
||||
License = items[i].License?.SpdxId,
|
||||
HtmlUrl = items[i].HtmlUrl,
|
||||
Homepage = items[i].Homepage,
|
||||
Language = items[i].Language,
|
||||
AllowMergeCommit = items[i].AllowMergeCommit,
|
||||
AllowRebaseMerge = items[i].AllowRebaseMerge,
|
||||
AllowSquashMerge = items[i].AllowSquashMerge,
|
||||
HasIssues = items[i].HasIssues,
|
||||
HasWiki = items[i].HasWiki,
|
||||
HasDownloads = items[i].HasDownloads,
|
||||
HasPages = items[i].HasPages,
|
||||
CommunityProfile = communityProfile,
|
||||
CommunityFiles = communityFiles,
|
||||
IsTemplate = items[i].IsTemplate,
|
||||
DeleteBranchOnMerge = items[i].DeleteBranchOnMerge,
|
||||
Visibility = items[i].Visibility.HasValue ? Enum.GetName(typeof(Octokit.RepositoryVisibility), items[i].Visibility) : null,
|
||||
};
|
||||
}
|
||||
return results;
|
||||
EnforceAdmins = protection?.EnforceAdmins?.Enabled,
|
||||
RequireUpToDate = protection?.RequiredStatusChecks?.Strict,
|
||||
RequireStatusChecks = protection?.RequiredStatusChecks?.Contexts?.ToArray(),
|
||||
RequirePullRequestReviews = protection?.RequiredPullRequestReviews != null && protection?.RequiredPullRequestReviews?.RequiredApprovingReviewCount > 0,
|
||||
DismissStaleReviews = protection?.RequiredPullRequestReviews?.DismissStaleReviews,
|
||||
RequireCodeOwnerReviews = protection?.RequiredPullRequestReviews?.RequireCodeOwnerReviews,
|
||||
RequiredApprovingReviewCount = protection?.RequiredPullRequestReviews?.RequiredApprovingReviewCount,
|
||||
},
|
||||
Status = status,
|
||||
};
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public Branch[] GetBranches(string owner, string name)
|
||||
public Label[] GetLabels(string owner, string name)
|
||||
{
|
||||
var items = GetLabelsInternal(owner, name);
|
||||
var results = new Label[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
var items = GetBranchesInternal(owner, name);
|
||||
var results = new Data.Branch[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
results[i] = new Label(items[i].Name)
|
||||
{
|
||||
var protection = items[i].Protected ? GetBranchProtectionInternal(owner, name, items[i].Name) : null;
|
||||
var status = GetBranchStatus(owner, name, items[i].Name);
|
||||
results[i] = new Branch(string.Concat(owner, '/', name), items[i].Name)
|
||||
{
|
||||
Protection = new BranchProtection(items[i].Protected)
|
||||
{
|
||||
EnforceAdmins = protection?.EnforceAdmins?.Enabled,
|
||||
RequireUpToDate = protection?.RequiredStatusChecks?.Strict,
|
||||
RequireStatusChecks = protection?.RequiredStatusChecks?.Contexts?.ToArray(),
|
||||
RequirePullRequestReviews = protection?.RequiredPullRequestReviews != null && protection?.RequiredPullRequestReviews?.RequiredApprovingReviewCount > 0,
|
||||
DismissStaleReviews = protection?.RequiredPullRequestReviews?.DismissStaleReviews,
|
||||
RequireCodeOwnerReviews = protection?.RequiredPullRequestReviews?.RequireCodeOwnerReviews,
|
||||
RequiredApprovingReviewCount = protection?.RequiredPullRequestReviews?.RequiredApprovingReviewCount,
|
||||
},
|
||||
Status = status,
|
||||
};
|
||||
}
|
||||
return results;
|
||||
Description = items[i].Description,
|
||||
Color = items[i].Color,
|
||||
Default = items[i].Default
|
||||
};
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public Label[] GetLabels(string owner, string name)
|
||||
public Milestone[] GetMilestones(string owner, string name)
|
||||
{
|
||||
var items = GetMilestonesInternal(owner, name);
|
||||
var results = new Milestone[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
var items = GetLabelsInternal(owner, name);
|
||||
var results = new Label[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
results[i] = new Milestone(items[i].Number)
|
||||
{
|
||||
results[i] = new Label(items[i].Name)
|
||||
{
|
||||
Description = items[i].Description,
|
||||
Color = items[i].Color,
|
||||
Default = items[i].Default
|
||||
};
|
||||
}
|
||||
return results;
|
||||
Title = items[i].Title,
|
||||
Description = items[i].Description,
|
||||
State = items[i].State.StringValue,
|
||||
};
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public Milestone[] GetMilestones(string owner, string name)
|
||||
public Release[] GetReleases(string owner, string name)
|
||||
{
|
||||
var items = GetReleasesInternal(owner, name);
|
||||
var results = new Release[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
var items = GetMilestonesInternal(owner, name);
|
||||
var results = new Milestone[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
results[i] = new Release(items[i].Name)
|
||||
{
|
||||
results[i] = new Milestone(items[i].Number)
|
||||
{
|
||||
Title = items[i].Title,
|
||||
Description = items[i].Description,
|
||||
State = items[i].State.StringValue,
|
||||
};
|
||||
}
|
||||
return results;
|
||||
Prerelease = items[i].Prerelease,
|
||||
TagName = items[i].TagName,
|
||||
};
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public Release[] GetReleases(string owner, string name)
|
||||
public RepositoryTag[] GetTags(string owner, string name)
|
||||
{
|
||||
var items = GetTagsInternal(owner, name);
|
||||
var results = new RepositoryTag[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
var items = GetReleasesInternal(owner, name);
|
||||
var results = new Release[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
results[i] = new RepositoryTag(items[i].Name);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
#endregion Public methods
|
||||
|
||||
#region Private methods
|
||||
|
||||
private IEnumerable<BranchStatus> GetBranchStatus(string owner, string name, string branch)
|
||||
{
|
||||
var result = new List<BranchStatus>();
|
||||
var statuses = GetBranchStatusInternal(owner, name, branch);
|
||||
for (var i = 0; i < statuses.Count; i++)
|
||||
{
|
||||
result.Add(new BranchStatus(statuses[i].Name)
|
||||
{
|
||||
results[i] = new Release(items[i].Name)
|
||||
{
|
||||
Prerelease = items[i].Prerelease,
|
||||
TagName = items[i].TagName,
|
||||
};
|
||||
}
|
||||
return results;
|
||||
Status = statuses[i].Status.StringValue,
|
||||
Conclusion = statuses[i].Conclusion?.StringValue,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public RepositoryTag[] GetTags(string owner, string name)
|
||||
/// <summary>
|
||||
/// Get matching repositories for the GitHub organization.
|
||||
/// </summary>
|
||||
private Octokit.Repository[] GetRepositoryInternal(string repositorySlug)
|
||||
{
|
||||
var slugParts = repositorySlug.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var owner = slugParts[0];
|
||||
if (slugParts.Length == 2)
|
||||
return GetSingleRepository(owner, slugParts[1]);
|
||||
|
||||
var isOrg = IsOrg(owner);
|
||||
return isOrg ? GetOrgRepository(owner) : GetUserRepository(owner);
|
||||
}
|
||||
|
||||
private Data.CommunityProfile GetCommunityProfileInternal(string owner, string name)
|
||||
{
|
||||
var profile = _HttpClient.Get<Data.CommunityProfile>($"https://api.github.com/repos/{owner}/{name}/community/profile", headers: _GITHUB_HEADERS_COMMUNITY_PROFILE);
|
||||
return profile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get branches for the repository.
|
||||
/// </summary>
|
||||
private Octokit.Branch[] GetBranchesInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Repository.Branch.GetAll(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
private Octokit.BranchProtectionSettings GetBranchProtectionInternal(string owner, string name, string branch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var items = GetTagsInternal(owner, name);
|
||||
var results = new RepositoryTag[items.Length];
|
||||
for (var i = 0; i < items.Length; i++)
|
||||
{
|
||||
results[i] = new RepositoryTag(items[i].Name);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
#endregion Public methods
|
||||
|
||||
#region Private methods
|
||||
|
||||
private IEnumerable<BranchStatus> GetBranchStatus(string owner, string name, string branch)
|
||||
{
|
||||
var result = new List<BranchStatus>();
|
||||
var statuses = GetBranchStatusInternal(owner, name, branch);
|
||||
for (var i = 0; i < statuses.Count; i++)
|
||||
{
|
||||
result.Add(new BranchStatus(statuses[i].Name)
|
||||
{
|
||||
Status = statuses[i].Status.StringValue,
|
||||
Conclusion = statuses[i].Conclusion?.StringValue,
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get matching repositories for the GitHub organization.
|
||||
/// </summary>
|
||||
private Octokit.Repository[] GetRepositoryInternal(string repositorySlug)
|
||||
{
|
||||
var slugParts = repositorySlug.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var owner = slugParts[0];
|
||||
if (slugParts.Length == 2)
|
||||
return GetSingleRepository(owner, slugParts[1]);
|
||||
|
||||
var isOrg = IsOrg(owner);
|
||||
return isOrg ? GetOrgRepository(owner) : GetUserRepository(owner);
|
||||
}
|
||||
|
||||
private Data.CommunityProfile GetCommunityProfileInternal(string owner, string name)
|
||||
{
|
||||
var profile = _HttpClient.Get<Data.CommunityProfile>($"https://api.github.com/repos/{owner}/{name}/community/profile", headers: GITHUB_HEADERS_COMMUNITY_PROFILE);
|
||||
return profile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get branches for the repository.
|
||||
/// </summary>
|
||||
private Octokit.Branch[] GetBranchesInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Repository.Branch.GetAll(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
private Octokit.BranchProtectionSettings GetBranchProtectionInternal(string owner, string name, string branch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = _Client.Repository.Branch.GetBranchProtection(owner, name, branch);
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
var baseException = e.GetBaseException();
|
||||
if (baseException is Octokit.NotFoundException)
|
||||
return null;
|
||||
|
||||
// TODO: Should raise a warning
|
||||
if (baseException is Octokit.ForbiddenException)
|
||||
return null;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<Octokit.CheckRun> GetBranchStatusInternal(string owner, string name, string branch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = _Client.Check.Run.GetAllForReference(owner, name, branch);
|
||||
task.Wait();
|
||||
return task.Result.CheckRuns;
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
var baseException = e.GetBaseException();
|
||||
if (baseException is Octokit.NotFoundException)
|
||||
return null;
|
||||
|
||||
// TODO: Should raise a warning
|
||||
if (baseException is Octokit.ForbiddenException)
|
||||
return null;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get issue milestones for the repository.
|
||||
/// </summary>
|
||||
private Octokit.Milestone[] GetMilestonesInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Issue.Milestone.GetAllForRepository(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get releases for the repository.
|
||||
/// </summary>
|
||||
private Octokit.Release[] GetReleasesInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Repository.Release.GetAll(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get issue labels for the repository.
|
||||
/// </summary>
|
||||
private Octokit.Label[] GetLabelsInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Issue.Labels.GetAllForRepository(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
private Octokit.RepositoryTag[] GetTagsInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Repository.GetAllTags(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
private static readonly string[] GitHubPaths = new string[]
|
||||
{
|
||||
".github",
|
||||
".github/ISSUE_TEMPLATE",
|
||||
".github/PULL_REQUEST_TEMPLATE",
|
||||
"docs",
|
||||
"docs/PULL_REQUEST_TEMPLATE",
|
||||
"PULL_REQUEST_TEMPLATE"
|
||||
};
|
||||
|
||||
private string[] GetCommunityFiles(string owner, string name)
|
||||
{
|
||||
var contentFiles = GetGitHubFiles(owner, name, GitHubPaths);
|
||||
var files = new List<string>();
|
||||
for (var i = 0; i < contentFiles.Length; i++)
|
||||
IncludeCommunityFile(contentFiles[i], files);
|
||||
|
||||
return files.ToArray();
|
||||
}
|
||||
|
||||
private Octokit.RepositoryContent[] GetGitHubFiles(string owner, string name, string[] paths)
|
||||
{
|
||||
var tasks = new Task<IReadOnlyList<Octokit.RepositoryContent>>[paths.Length + 1];
|
||||
tasks[0] = _Client.Repository.Content.GetAllContents(owner, name);
|
||||
for (var i = 0; i < paths.Length; i++)
|
||||
tasks[i + 1] = _Client.Repository.Content.GetAllContents(owner, name, paths[i]);
|
||||
|
||||
try
|
||||
{
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
catch (AggregateException)
|
||||
{
|
||||
// Discard AggregateExceptions for tasks
|
||||
}
|
||||
var result = new List<Octokit.RepositoryContent>();
|
||||
for (var i = 0; i < tasks.Length; i++)
|
||||
{
|
||||
if (!tasks[i].IsFaulted)
|
||||
result.AddRange(tasks[i].Result);
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static void IncludeCommunityFile(Octokit.RepositoryContent content, List<string> files)
|
||||
{
|
||||
if (content.Type.Value == Octokit.ContentType.File && IsCommunityFile(content.Path))
|
||||
files.Add(content.Path);
|
||||
}
|
||||
|
||||
private static bool IsCommunityFile(string path)
|
||||
{
|
||||
return path.StartsWith(".github/", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.StartsWith("README", StringComparison.OrdinalIgnoreCase) || path.StartsWith("LICENSE", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("CODE_OF_CONDUCT.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("CONTRIBUTING.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("SECURITY.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("SUPPORT.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("pull_request_template.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("pull_request_template.txt", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private Octokit.Repository[] GetUserRepository(string user)
|
||||
{
|
||||
var task = _Client.Repository.GetAllForUser(user);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
private Octokit.Repository[] GetOrgRepository(string org)
|
||||
{
|
||||
var task = _Client.Repository.GetAllForOrg(org);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do a lookup against a login to determine if it is an organization.
|
||||
/// </summary>
|
||||
private bool IsOrg(string login)
|
||||
{
|
||||
var task = _Client.User.Get(login).ContinueWith(u => u.Result.Type == Octokit.AccountType.Organization);
|
||||
var task = _Client.Repository.Branch.GetBranchProtection(owner, name, branch);
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
private Octokit.Repository[] GetSingleRepository(string owner, string name)
|
||||
catch (AggregateException e)
|
||||
{
|
||||
var task = _Client.Repository.Get(owner, name).ContinueWith(r => r.Result);
|
||||
task.Wait();
|
||||
return new Octokit.Repository[] { task.Result };
|
||||
}
|
||||
var baseException = e.GetBaseException();
|
||||
if (baseException is Octokit.NotFoundException)
|
||||
return null;
|
||||
|
||||
#endregion Private methods
|
||||
// TODO: Should raise a warning
|
||||
if (baseException is Octokit.ForbiddenException)
|
||||
return null;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<Octokit.CheckRun> GetBranchStatusInternal(string owner, string name, string branch)
|
||||
{
|
||||
try
|
||||
{
|
||||
var task = _Client.Check.Run.GetAllForReference(owner, name, branch);
|
||||
task.Wait();
|
||||
return task.Result.CheckRuns;
|
||||
}
|
||||
catch (AggregateException e)
|
||||
{
|
||||
var baseException = e.GetBaseException();
|
||||
if (baseException is Octokit.NotFoundException)
|
||||
return null;
|
||||
|
||||
// TODO: Should raise a warning
|
||||
if (baseException is Octokit.ForbiddenException)
|
||||
return null;
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get issue milestones for the repository.
|
||||
/// </summary>
|
||||
private Octokit.Milestone[] GetMilestonesInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Issue.Milestone.GetAllForRepository(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get releases for the repository.
|
||||
/// </summary>
|
||||
private Octokit.Release[] GetReleasesInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Repository.Release.GetAll(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get issue labels for the repository.
|
||||
/// </summary>
|
||||
private Octokit.Label[] GetLabelsInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Issue.Labels.GetAllForRepository(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
private Octokit.RepositoryTag[] GetTagsInternal(string owner, string name)
|
||||
{
|
||||
var task = _Client.Repository.GetAllTags(owner, name);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
private static readonly string[] GitHubPaths = new string[]
|
||||
{
|
||||
".github",
|
||||
".github/ISSUE_TEMPLATE",
|
||||
".github/PULL_REQUEST_TEMPLATE",
|
||||
"docs",
|
||||
"docs/PULL_REQUEST_TEMPLATE",
|
||||
"PULL_REQUEST_TEMPLATE"
|
||||
};
|
||||
|
||||
private string[] GetCommunityFiles(string owner, string name)
|
||||
{
|
||||
var contentFiles = GetGitHubFiles(owner, name, GitHubPaths);
|
||||
var files = new List<string>();
|
||||
for (var i = 0; i < contentFiles.Length; i++)
|
||||
IncludeCommunityFile(contentFiles[i], files);
|
||||
|
||||
return files.ToArray();
|
||||
}
|
||||
|
||||
private Octokit.RepositoryContent[] GetGitHubFiles(string owner, string name, string[] paths)
|
||||
{
|
||||
var tasks = new Task<IReadOnlyList<Octokit.RepositoryContent>>[paths.Length + 1];
|
||||
tasks[0] = _Client.Repository.Content.GetAllContents(owner, name);
|
||||
for (var i = 0; i < paths.Length; i++)
|
||||
tasks[i + 1] = _Client.Repository.Content.GetAllContents(owner, name, paths[i]);
|
||||
|
||||
try
|
||||
{
|
||||
Task.WaitAll(tasks);
|
||||
}
|
||||
catch (AggregateException)
|
||||
{
|
||||
// Discard AggregateExceptions for tasks
|
||||
}
|
||||
var result = new List<Octokit.RepositoryContent>();
|
||||
for (var i = 0; i < tasks.Length; i++)
|
||||
{
|
||||
if (!tasks[i].IsFaulted)
|
||||
result.AddRange(tasks[i].Result);
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
private static void IncludeCommunityFile(Octokit.RepositoryContent content, List<string> files)
|
||||
{
|
||||
if (content.Type.Value == Octokit.ContentType.File && IsCommunityFile(content.Path))
|
||||
files.Add(content.Path);
|
||||
}
|
||||
|
||||
private static bool IsCommunityFile(string path)
|
||||
{
|
||||
return path.StartsWith(".github/", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.StartsWith("README", StringComparison.OrdinalIgnoreCase) || path.StartsWith("LICENSE", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("CODE_OF_CONDUCT.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("CONTRIBUTING.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("SECURITY.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("SUPPORT.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("pull_request_template.md", StringComparison.OrdinalIgnoreCase) ||
|
||||
path.EndsWith("pull_request_template.txt", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private Octokit.Repository[] GetUserRepository(string user)
|
||||
{
|
||||
var task = _Client.Repository.GetAllForUser(user);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
private Octokit.Repository[] GetOrgRepository(string org)
|
||||
{
|
||||
var task = _Client.Repository.GetAllForOrg(org);
|
||||
task.Wait();
|
||||
return task.Result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do a lookup against a login to determine if it is an organization.
|
||||
/// </summary>
|
||||
private bool IsOrg(string login)
|
||||
{
|
||||
var task = _Client.User.Get(login).ContinueWith(u => u.Result.Type == Octokit.AccountType.Organization);
|
||||
task.Wait();
|
||||
return task.Result;
|
||||
}
|
||||
|
||||
private Octokit.Repository[] GetSingleRepository(string owner, string name)
|
||||
{
|
||||
var task = _Client.Repository.Get(owner, name).ContinueWith(r => r.Result);
|
||||
task.Wait();
|
||||
return new Octokit.Repository[] { task.Result };
|
||||
}
|
||||
|
||||
#endregion Private methods
|
||||
}
|
||||
|
|
|
@ -9,61 +9,60 @@ using System.Net.Http.Headers;
|
|||
using System.Reflection;
|
||||
using Octokit;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
internal sealed class GitHubContext : IDisposable
|
||||
{
|
||||
internal sealed class GitHubContext : IDisposable
|
||||
// Details for user-agent header
|
||||
private const string GITHUB_PRODUCT_HEADER = "PSRule.Rules.GitHub";
|
||||
private static readonly string ProductVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
|
||||
|
||||
private readonly PSCredential _Credential;
|
||||
|
||||
private bool _Disposed;
|
||||
|
||||
public GitHubContext(string[] repository, PSCredential credential)
|
||||
{
|
||||
// Details for user-agent header
|
||||
private const string GITHUB_PRODUCT_HEADER = "PSRule.Rules.GitHub";
|
||||
private static readonly string ProductVersion = FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion;
|
||||
Repository = repository;
|
||||
_Credential = credential;
|
||||
}
|
||||
|
||||
private readonly PSCredential _Credential;
|
||||
public string[] Repository { get; set; }
|
||||
|
||||
private bool _Disposed;
|
||||
public Octokit.GitHubClient GetClient()
|
||||
{
|
||||
var client = new Octokit.GitHubClient(new Octokit.ProductHeaderValue(GITHUB_PRODUCT_HEADER, ProductVersion));
|
||||
if (_Credential != null)
|
||||
client.Credentials = new Credentials(_Credential.GetNetworkCredential().Password);
|
||||
|
||||
public GitHubContext(string[] repository, PSCredential credential)
|
||||
return client;
|
||||
}
|
||||
|
||||
public HttpClient GetHttpClient()
|
||||
{
|
||||
var client = new HttpClient();
|
||||
if (_Credential != null)
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token", _Credential.GetNetworkCredential().Password);
|
||||
|
||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GITHUB_PRODUCT_HEADER, ProductVersion));
|
||||
return client;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_Disposed)
|
||||
{
|
||||
Repository = repository;
|
||||
_Credential = credential;
|
||||
}
|
||||
|
||||
public string[] Repository { get; set; }
|
||||
|
||||
public Octokit.GitHubClient GetClient()
|
||||
{
|
||||
var client = new Octokit.GitHubClient(new Octokit.ProductHeaderValue(GITHUB_PRODUCT_HEADER, ProductVersion));
|
||||
if (_Credential != null)
|
||||
client.Credentials = new Credentials(_Credential.GetNetworkCredential().Password);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
public HttpClient GetHttpClient()
|
||||
{
|
||||
var client = new HttpClient();
|
||||
if (_Credential != null)
|
||||
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("token", _Credential.GetNetworkCredential().Password);
|
||||
|
||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(GITHUB_PRODUCT_HEADER, ProductVersion));
|
||||
return client;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_Disposed)
|
||||
if (disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
|
||||
}
|
||||
_Disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
_Disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for logging to the pipeline.
|
||||
/// </summary>
|
||||
internal static class LoggingExtensions
|
||||
{
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for logging to the pipeline.
|
||||
/// </summary>
|
||||
internal static class LoggingExtensions
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,48 +6,47 @@ using System.Text;
|
|||
using PSRule.Rules.GitHub.Configuration;
|
||||
using PSRule.Rules.GitHub.Resources;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline.Output
|
||||
namespace PSRule.Rules.GitHub.Pipeline.Output;
|
||||
|
||||
/// <summary>
|
||||
/// An output writer that writes output to disk.
|
||||
/// </summary>
|
||||
internal sealed class FileOutputWriter : PipelineWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// An output writer that writes output to disk.
|
||||
/// </summary>
|
||||
internal sealed class FileOutputWriter : PipelineWriter
|
||||
private readonly Encoding _Encoding;
|
||||
private readonly string _Path;
|
||||
private readonly string _DefaultFile;
|
||||
private readonly ShouldProcess _ShouldProcess;
|
||||
|
||||
internal FileOutputWriter(PipelineWriter inner, PSRuleOption option, Encoding encoding, string path, string defaultFile, ShouldProcess shouldProcess)
|
||||
: base(inner, option)
|
||||
{
|
||||
private readonly Encoding _Encoding;
|
||||
private readonly string _Path;
|
||||
private readonly string _DefaultFile;
|
||||
private readonly ShouldProcess _ShouldProcess;
|
||||
_Encoding = encoding;
|
||||
_Path = path;
|
||||
_DefaultFile = defaultFile;
|
||||
_ShouldProcess = shouldProcess;
|
||||
}
|
||||
|
||||
internal FileOutputWriter(PipelineWriter inner, PSRuleOption option, Encoding encoding, string path, string defaultFile, ShouldProcess shouldProcess)
|
||||
: base(inner, option)
|
||||
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
|
||||
{
|
||||
WriteToFile(sendToPipeline);
|
||||
}
|
||||
|
||||
private void WriteToFile(object o)
|
||||
{
|
||||
var rootedPath = PSRuleOption.GetRootedPath(_Path);
|
||||
if (!Path.HasExtension(rootedPath) || Directory.Exists(rootedPath))
|
||||
rootedPath = Path.Combine(rootedPath, _DefaultFile);
|
||||
|
||||
var parentPath = Directory.GetParent(rootedPath);
|
||||
if (!parentPath.Exists && _ShouldProcess(target: parentPath.FullName, action: PSRuleResources.ShouldCreatePath))
|
||||
Directory.CreateDirectory(path: parentPath.FullName);
|
||||
|
||||
if (_ShouldProcess(target: rootedPath, action: PSRuleResources.ShouldWriteFile))
|
||||
{
|
||||
_Encoding = encoding;
|
||||
_Path = path;
|
||||
_DefaultFile = defaultFile;
|
||||
_ShouldProcess = shouldProcess;
|
||||
}
|
||||
|
||||
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
|
||||
{
|
||||
WriteToFile(sendToPipeline);
|
||||
}
|
||||
|
||||
private void WriteToFile(object o)
|
||||
{
|
||||
var rootedPath = PSRuleOption.GetRootedPath(_Path);
|
||||
if (!Path.HasExtension(rootedPath) || Directory.Exists(rootedPath))
|
||||
rootedPath = Path.Combine(rootedPath, _DefaultFile);
|
||||
|
||||
var parentPath = Directory.GetParent(rootedPath);
|
||||
if (!parentPath.Exists && _ShouldProcess(target: parentPath.FullName, action: PSRuleResources.ShouldCreatePath))
|
||||
Directory.CreateDirectory(path: parentPath.FullName);
|
||||
|
||||
if (_ShouldProcess(target: rootedPath, action: PSRuleResources.ShouldWriteFile))
|
||||
{
|
||||
File.WriteAllText(path: rootedPath, contents: o.ToString(), encoding: _Encoding);
|
||||
var info = new FileInfo(rootedPath);
|
||||
base.WriteObject(info, false);
|
||||
}
|
||||
File.WriteAllText(path: rootedPath, contents: o.ToString(), encoding: _Encoding);
|
||||
var info = new FileInfo(rootedPath);
|
||||
base.WriteObject(info, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,21 +4,20 @@
|
|||
using Newtonsoft.Json;
|
||||
using PSRule.Rules.GitHub.Configuration;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline.Output
|
||||
{
|
||||
internal sealed class JsonOutputWriter : SerializationOutputWriter<object>
|
||||
{
|
||||
internal JsonOutputWriter(PipelineWriter inner, PSRuleOption option)
|
||||
: base(inner, option) { }
|
||||
namespace PSRule.Rules.GitHub.Pipeline.Output;
|
||||
|
||||
protected override string Serialize(object[] o)
|
||||
internal sealed class JsonOutputWriter : SerializationOutputWriter<object>
|
||||
{
|
||||
internal JsonOutputWriter(PipelineWriter inner, PSRuleOption option)
|
||||
: base(inner, option) { }
|
||||
|
||||
protected override string Serialize(object[] o)
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
settings.Converters.Add(new PSObjectJsonConverter());
|
||||
return JsonConvert.SerializeObject(o, settings: settings);
|
||||
}
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
settings.Converters.Add(new PSObjectJsonConverter());
|
||||
return JsonConvert.SerializeObject(o, settings: settings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,172 +5,171 @@ using System;
|
|||
using System.Management.Automation;
|
||||
using PSRule.Rules.GitHub.Configuration;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline.Output
|
||||
namespace PSRule.Rules.GitHub.Pipeline.Output;
|
||||
|
||||
/// <summary>
|
||||
/// An output writer that returns output to the host PowerShell runspace.
|
||||
/// </summary>
|
||||
internal sealed class PSPipelineWriter : PipelineWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// An output writer that returns output to the host PowerShell runspace.
|
||||
/// </summary>
|
||||
internal sealed class PSPipelineWriter : PipelineWriter
|
||||
private const string Source = "PSRule";
|
||||
private const string HostTag = "PSHOST";
|
||||
|
||||
private Action<string> _OnWriteWarning;
|
||||
private Action<string> _OnWriteVerbose;
|
||||
private Action<ErrorRecord> _OnWriteError;
|
||||
private Action<InformationRecord> _OnWriteInformation;
|
||||
private Action<string> _OnWriteDebug;
|
||||
internal Action<object, bool> OnWriteObject;
|
||||
|
||||
private bool _LogError;
|
||||
private bool _LogWarning;
|
||||
private bool _LogVerbose;
|
||||
private bool _LogInformation;
|
||||
private bool _LogDebug;
|
||||
|
||||
internal PSPipelineWriter(PSRuleOption option)
|
||||
: base(null, option) { }
|
||||
|
||||
internal void UseCommandRuntime(PSCmdlet commandRuntime)
|
||||
{
|
||||
private const string Source = "PSRule";
|
||||
private const string HostTag = "PSHOST";
|
||||
if (commandRuntime == null)
|
||||
return;
|
||||
|
||||
private Action<string> OnWriteWarning;
|
||||
private Action<string> OnWriteVerbose;
|
||||
private Action<ErrorRecord> OnWriteError;
|
||||
private Action<InformationRecord> OnWriteInformation;
|
||||
private Action<string> OnWriteDebug;
|
||||
internal Action<object, bool> OnWriteObject;
|
||||
|
||||
private bool _LogError;
|
||||
private bool _LogWarning;
|
||||
private bool _LogVerbose;
|
||||
private bool _LogInformation;
|
||||
private bool _LogDebug;
|
||||
|
||||
internal PSPipelineWriter(PSRuleOption option)
|
||||
: base(null, option) { }
|
||||
|
||||
internal void UseCommandRuntime(PSCmdlet commandRuntime)
|
||||
{
|
||||
if (commandRuntime == null)
|
||||
return;
|
||||
|
||||
OnWriteVerbose = commandRuntime.WriteVerbose;
|
||||
OnWriteWarning = commandRuntime.WriteWarning;
|
||||
OnWriteError = commandRuntime.WriteError;
|
||||
OnWriteInformation = commandRuntime.WriteInformation;
|
||||
OnWriteDebug = commandRuntime.WriteDebug;
|
||||
OnWriteObject = commandRuntime.WriteObject;
|
||||
}
|
||||
|
||||
internal void UseExecutionContext(EngineIntrinsics executionContext)
|
||||
{
|
||||
if (executionContext == null)
|
||||
return;
|
||||
|
||||
_LogError = GetPreferenceVariable(executionContext, ErrorPreference);
|
||||
_LogWarning = GetPreferenceVariable(executionContext, WarningPreference);
|
||||
_LogVerbose = GetPreferenceVariable(executionContext, VerbosePreference);
|
||||
_LogInformation = GetPreferenceVariable(executionContext, InformationPreference);
|
||||
_LogDebug = GetPreferenceVariable(executionContext, DebugPreference);
|
||||
}
|
||||
|
||||
private static bool GetPreferenceVariable(EngineIntrinsics executionContext, string variableName)
|
||||
{
|
||||
var preference = GetPreferenceVariable(executionContext.SessionState, variableName);
|
||||
if (preference == ActionPreference.Ignore)
|
||||
return false;
|
||||
|
||||
return !(preference == ActionPreference.SilentlyContinue && (
|
||||
variableName == VerbosePreference ||
|
||||
variableName == DebugPreference)
|
||||
);
|
||||
}
|
||||
|
||||
#region Internal logging methods
|
||||
|
||||
/// <summary>
|
||||
/// Core methods to hand off to logger.
|
||||
/// </summary>
|
||||
/// <param name="errorRecord">A valid PowerShell error record.</param>
|
||||
public override void WriteError(ErrorRecord errorRecord)
|
||||
{
|
||||
if (OnWriteError == null || !ShouldWriteError())
|
||||
return;
|
||||
|
||||
OnWriteError(errorRecord);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core method to hand off verbose messages to logger.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to log.</param>
|
||||
public override void WriteVerbose(string message)
|
||||
{
|
||||
if (OnWriteVerbose == null || !ShouldWriteVerbose())
|
||||
return;
|
||||
|
||||
OnWriteVerbose(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core method to hand off warning messages to logger.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to log</param>
|
||||
public override void WriteWarning(string message)
|
||||
{
|
||||
if (OnWriteWarning == null || !ShouldWriteWarning())
|
||||
return;
|
||||
|
||||
OnWriteWarning(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core method to hand off information messages to logger.
|
||||
/// </summary>
|
||||
public override void WriteInformation(InformationRecord informationRecord)
|
||||
{
|
||||
if (OnWriteInformation == null || !ShouldWriteInformation())
|
||||
return;
|
||||
|
||||
OnWriteInformation(informationRecord);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core method to hand off debug messages to logger.
|
||||
/// </summary>
|
||||
public override void WriteDebug(DebugRecord debugRecord)
|
||||
{
|
||||
if (OnWriteDebug == null || !ShouldWriteDebug())
|
||||
return;
|
||||
|
||||
OnWriteDebug(debugRecord.Message);
|
||||
}
|
||||
|
||||
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
|
||||
{
|
||||
if (OnWriteObject == null)
|
||||
return;
|
||||
|
||||
OnWriteObject(sendToPipeline, enumerateCollection);
|
||||
}
|
||||
|
||||
public override void WriteHost(HostInformationMessage info)
|
||||
{
|
||||
if (OnWriteInformation == null)
|
||||
return;
|
||||
|
||||
var record = new InformationRecord(info, Source);
|
||||
record.Tags.Add(HostTag);
|
||||
OnWriteInformation(record);
|
||||
}
|
||||
|
||||
public override bool ShouldWriteVerbose()
|
||||
{
|
||||
return _LogVerbose;
|
||||
}
|
||||
|
||||
public override bool ShouldWriteInformation()
|
||||
{
|
||||
return _LogInformation;
|
||||
}
|
||||
|
||||
public override bool ShouldWriteDebug()
|
||||
{
|
||||
return _LogDebug;
|
||||
}
|
||||
|
||||
public override bool ShouldWriteError()
|
||||
{
|
||||
return _LogError;
|
||||
}
|
||||
|
||||
public override bool ShouldWriteWarning()
|
||||
{
|
||||
return _LogWarning;
|
||||
}
|
||||
|
||||
#endregion Internal logging methods
|
||||
_OnWriteVerbose = commandRuntime.WriteVerbose;
|
||||
_OnWriteWarning = commandRuntime.WriteWarning;
|
||||
_OnWriteError = commandRuntime.WriteError;
|
||||
_OnWriteInformation = commandRuntime.WriteInformation;
|
||||
_OnWriteDebug = commandRuntime.WriteDebug;
|
||||
OnWriteObject = commandRuntime.WriteObject;
|
||||
}
|
||||
|
||||
internal void UseExecutionContext(EngineIntrinsics executionContext)
|
||||
{
|
||||
if (executionContext == null)
|
||||
return;
|
||||
|
||||
_LogError = GetPreferenceVariable(executionContext, ErrorPreference);
|
||||
_LogWarning = GetPreferenceVariable(executionContext, WarningPreference);
|
||||
_LogVerbose = GetPreferenceVariable(executionContext, VerbosePreference);
|
||||
_LogInformation = GetPreferenceVariable(executionContext, InformationPreference);
|
||||
_LogDebug = GetPreferenceVariable(executionContext, DebugPreference);
|
||||
}
|
||||
|
||||
private static bool GetPreferenceVariable(EngineIntrinsics executionContext, string variableName)
|
||||
{
|
||||
var preference = GetPreferenceVariable(executionContext.SessionState, variableName);
|
||||
if (preference == ActionPreference.Ignore)
|
||||
return false;
|
||||
|
||||
return !(preference == ActionPreference.SilentlyContinue && (
|
||||
variableName == VerbosePreference ||
|
||||
variableName == DebugPreference)
|
||||
);
|
||||
}
|
||||
|
||||
#region Internal logging methods
|
||||
|
||||
/// <summary>
|
||||
/// Core methods to hand off to logger.
|
||||
/// </summary>
|
||||
/// <param name="errorRecord">A valid PowerShell error record.</param>
|
||||
public override void WriteError(ErrorRecord errorRecord)
|
||||
{
|
||||
if (_OnWriteError == null || !ShouldWriteError())
|
||||
return;
|
||||
|
||||
_OnWriteError(errorRecord);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core method to hand off verbose messages to logger.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to log.</param>
|
||||
public override void WriteVerbose(string message)
|
||||
{
|
||||
if (_OnWriteVerbose == null || !ShouldWriteVerbose())
|
||||
return;
|
||||
|
||||
_OnWriteVerbose(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core method to hand off warning messages to logger.
|
||||
/// </summary>
|
||||
/// <param name="message">A message to log</param>
|
||||
public override void WriteWarning(string message)
|
||||
{
|
||||
if (_OnWriteWarning == null || !ShouldWriteWarning())
|
||||
return;
|
||||
|
||||
_OnWriteWarning(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core method to hand off information messages to logger.
|
||||
/// </summary>
|
||||
public override void WriteInformation(InformationRecord informationRecord)
|
||||
{
|
||||
if (_OnWriteInformation == null || !ShouldWriteInformation())
|
||||
return;
|
||||
|
||||
_OnWriteInformation(informationRecord);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Core method to hand off debug messages to logger.
|
||||
/// </summary>
|
||||
public override void WriteDebug(DebugRecord debugRecord)
|
||||
{
|
||||
if (_OnWriteDebug == null || !ShouldWriteDebug())
|
||||
return;
|
||||
|
||||
_OnWriteDebug(debugRecord.Message);
|
||||
}
|
||||
|
||||
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
|
||||
{
|
||||
if (OnWriteObject == null)
|
||||
return;
|
||||
|
||||
OnWriteObject(sendToPipeline, enumerateCollection);
|
||||
}
|
||||
|
||||
public override void WriteHost(HostInformationMessage info)
|
||||
{
|
||||
if (_OnWriteInformation == null)
|
||||
return;
|
||||
|
||||
var record = new InformationRecord(info, Source);
|
||||
record.Tags.Add(HostTag);
|
||||
_OnWriteInformation(record);
|
||||
}
|
||||
|
||||
public override bool ShouldWriteVerbose()
|
||||
{
|
||||
return _LogVerbose;
|
||||
}
|
||||
|
||||
public override bool ShouldWriteInformation()
|
||||
{
|
||||
return _LogInformation;
|
||||
}
|
||||
|
||||
public override bool ShouldWriteDebug()
|
||||
{
|
||||
return _LogDebug;
|
||||
}
|
||||
|
||||
public override bool ShouldWriteError()
|
||||
{
|
||||
return _LogError;
|
||||
}
|
||||
|
||||
public override bool ShouldWriteWarning()
|
||||
{
|
||||
return _LogWarning;
|
||||
}
|
||||
|
||||
#endregion Internal logging methods
|
||||
}
|
||||
|
|
|
@ -6,152 +6,151 @@ using System.Management.Automation;
|
|||
using PSRule.Rules.GitHub.Configuration;
|
||||
using PSRule.Rules.GitHub.Pipeline.Output;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
internal delegate bool ShouldProcess(string target, string action);
|
||||
|
||||
/// <summary>
|
||||
/// A helper class for building a pipeline from PowerShell.
|
||||
/// </summary>
|
||||
public static class PipelineBuilder
|
||||
{
|
||||
internal delegate bool ShouldProcess(string target, string action);
|
||||
|
||||
/// <summary>
|
||||
/// A helper class for building a pipeline from PowerShell.
|
||||
/// </summary>
|
||||
public static class PipelineBuilder
|
||||
public static IExportPipelineBuilder Export(PSRuleOption option)
|
||||
{
|
||||
public static IExportPipelineBuilder Export(PSRuleOption option)
|
||||
{
|
||||
return new ExportPipelineBuilder(option);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPipelineBuilder
|
||||
{
|
||||
void UseCommandRuntime(PSCmdlet commandRuntime);
|
||||
|
||||
void UseExecutionContext(EngineIntrinsics executionContext);
|
||||
|
||||
IPipelineBuilder Configure(PSRuleOption option);
|
||||
|
||||
IPipeline Build();
|
||||
}
|
||||
|
||||
public interface IPipeline
|
||||
{
|
||||
void Begin();
|
||||
|
||||
void Process(PSObject sourceObject);
|
||||
|
||||
void End();
|
||||
}
|
||||
|
||||
internal abstract class PipelineBuilderBase : IPipelineBuilder
|
||||
{
|
||||
private readonly PSPipelineWriter _Output;
|
||||
|
||||
protected readonly PSRuleOption Option;
|
||||
|
||||
protected PSCmdlet CmdletContext;
|
||||
protected EngineIntrinsics ExecutionContext;
|
||||
|
||||
protected PipelineBuilderBase()
|
||||
{
|
||||
Option = new PSRuleOption();
|
||||
_Output = new PSPipelineWriter(Option);
|
||||
}
|
||||
|
||||
public virtual void UseCommandRuntime(PSCmdlet commandRuntime)
|
||||
{
|
||||
CmdletContext = commandRuntime;
|
||||
_Output.UseCommandRuntime(commandRuntime);
|
||||
}
|
||||
|
||||
public void UseExecutionContext(EngineIntrinsics executionContext)
|
||||
{
|
||||
ExecutionContext = executionContext;
|
||||
_Output.UseExecutionContext(executionContext);
|
||||
}
|
||||
|
||||
public virtual IPipelineBuilder Configure(PSRuleOption option)
|
||||
{
|
||||
if (option == null)
|
||||
return this;
|
||||
|
||||
Option.Output = new OutputOption(option.Output);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract IPipeline Build();
|
||||
|
||||
protected PipelineContext PrepareContext()
|
||||
{
|
||||
return new PipelineContext(Option);
|
||||
}
|
||||
|
||||
protected virtual PipelineWriter PrepareWriter()
|
||||
{
|
||||
var writer = new PSPipelineWriter(Option);
|
||||
writer.UseCommandRuntime(CmdletContext);
|
||||
writer.UseExecutionContext(ExecutionContext);
|
||||
return writer;
|
||||
}
|
||||
|
||||
protected virtual PipelineWriter GetOutput()
|
||||
{
|
||||
return _Output;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class PipelineBase : IDisposable, IPipeline
|
||||
{
|
||||
protected readonly PipelineContext Context;
|
||||
protected readonly PipelineWriter Writer;
|
||||
|
||||
// Track whether Dispose has been called.
|
||||
private bool _Disposed = false;
|
||||
|
||||
|
||||
protected PipelineBase(PipelineContext context, PipelineWriter writer)
|
||||
{
|
||||
Context = context;
|
||||
Writer = writer;
|
||||
}
|
||||
|
||||
#region IPipeline
|
||||
|
||||
public virtual void Begin()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public virtual void Process(PSObject sourceObject)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public virtual void End()
|
||||
{
|
||||
Writer.End();
|
||||
}
|
||||
|
||||
#endregion IPipeline
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
_Disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion IDisposable
|
||||
return new ExportPipelineBuilder(option);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IPipelineBuilder
|
||||
{
|
||||
void UseCommandRuntime(PSCmdlet commandRuntime);
|
||||
|
||||
void UseExecutionContext(EngineIntrinsics executionContext);
|
||||
|
||||
IPipelineBuilder Configure(PSRuleOption option);
|
||||
|
||||
IPipeline Build();
|
||||
}
|
||||
|
||||
public interface IPipeline
|
||||
{
|
||||
void Begin();
|
||||
|
||||
void Process(PSObject sourceObject);
|
||||
|
||||
void End();
|
||||
}
|
||||
|
||||
internal abstract class PipelineBuilderBase : IPipelineBuilder
|
||||
{
|
||||
private readonly PSPipelineWriter _Output;
|
||||
|
||||
protected readonly PSRuleOption Option;
|
||||
|
||||
protected PSCmdlet CmdletContext;
|
||||
protected EngineIntrinsics ExecutionContext;
|
||||
|
||||
protected PipelineBuilderBase()
|
||||
{
|
||||
Option = new PSRuleOption();
|
||||
_Output = new PSPipelineWriter(Option);
|
||||
}
|
||||
|
||||
public virtual void UseCommandRuntime(PSCmdlet commandRuntime)
|
||||
{
|
||||
CmdletContext = commandRuntime;
|
||||
_Output.UseCommandRuntime(commandRuntime);
|
||||
}
|
||||
|
||||
public void UseExecutionContext(EngineIntrinsics executionContext)
|
||||
{
|
||||
ExecutionContext = executionContext;
|
||||
_Output.UseExecutionContext(executionContext);
|
||||
}
|
||||
|
||||
public virtual IPipelineBuilder Configure(PSRuleOption option)
|
||||
{
|
||||
if (option == null)
|
||||
return this;
|
||||
|
||||
Option.Output = new OutputOption(option.Output);
|
||||
return this;
|
||||
}
|
||||
|
||||
public abstract IPipeline Build();
|
||||
|
||||
protected PipelineContext PrepareContext()
|
||||
{
|
||||
return new PipelineContext(Option);
|
||||
}
|
||||
|
||||
protected virtual PipelineWriter PrepareWriter()
|
||||
{
|
||||
var writer = new PSPipelineWriter(Option);
|
||||
writer.UseCommandRuntime(CmdletContext);
|
||||
writer.UseExecutionContext(ExecutionContext);
|
||||
return writer;
|
||||
}
|
||||
|
||||
protected virtual PipelineWriter GetOutput()
|
||||
{
|
||||
return _Output;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class PipelineBase : IDisposable, IPipeline
|
||||
{
|
||||
protected readonly PipelineContext Context;
|
||||
protected readonly PipelineWriter Writer;
|
||||
|
||||
// Track whether Dispose has been called.
|
||||
private bool _Disposed = false;
|
||||
|
||||
|
||||
protected PipelineBase(PipelineContext context, PipelineWriter writer)
|
||||
{
|
||||
Context = context;
|
||||
Writer = writer;
|
||||
}
|
||||
|
||||
#region IPipeline
|
||||
|
||||
public virtual void Begin()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public virtual void Process(PSObject sourceObject)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
public virtual void End()
|
||||
{
|
||||
Writer.End();
|
||||
}
|
||||
|
||||
#endregion IPipeline
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_Disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
_Disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion IDisposable
|
||||
}
|
||||
|
|
|
@ -3,15 +3,14 @@
|
|||
|
||||
using PSRule.Rules.GitHub.Configuration;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
{
|
||||
internal sealed class PipelineContext
|
||||
{
|
||||
internal readonly PSRuleOption Option;
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
public PipelineContext(PSRuleOption option)
|
||||
{
|
||||
Option = option;
|
||||
}
|
||||
internal sealed class PipelineContext
|
||||
{
|
||||
internal readonly PSRuleOption Option;
|
||||
|
||||
public PipelineContext(PSRuleOption option)
|
||||
{
|
||||
Option = option;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,181 +7,180 @@ using System.Management.Automation;
|
|||
using System.Threading;
|
||||
using PSRule.Rules.GitHub.Configuration;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
internal interface ILogger
|
||||
{
|
||||
internal interface ILogger
|
||||
{
|
||||
void WriteVerbose(string message);
|
||||
void WriteVerbose(string message);
|
||||
|
||||
void WriteVerbose(string format, params object[] args);
|
||||
void WriteVerbose(string format, params object[] args);
|
||||
}
|
||||
|
||||
internal abstract class PipelineWriter : ILogger
|
||||
{
|
||||
protected const string ErrorPreference = "ErrorActionPreference";
|
||||
protected const string WarningPreference = "WarningPreference";
|
||||
protected const string VerbosePreference = "VerbosePreference";
|
||||
protected const string InformationPreference = "InformationPreference";
|
||||
protected const string DebugPreference = "DebugPreference";
|
||||
|
||||
private readonly PipelineWriter _Writer;
|
||||
|
||||
protected readonly PSRuleOption Option;
|
||||
|
||||
protected PipelineWriter(PipelineWriter inner, PSRuleOption option)
|
||||
{
|
||||
_Writer = inner;
|
||||
Option = option;
|
||||
}
|
||||
|
||||
internal abstract class PipelineWriter : ILogger
|
||||
public virtual void Begin()
|
||||
{
|
||||
protected const string ErrorPreference = "ErrorActionPreference";
|
||||
protected const string WarningPreference = "WarningPreference";
|
||||
protected const string VerbosePreference = "VerbosePreference";
|
||||
protected const string InformationPreference = "InformationPreference";
|
||||
protected const string DebugPreference = "DebugPreference";
|
||||
if (_Writer == null)
|
||||
return;
|
||||
|
||||
private readonly PipelineWriter _Writer;
|
||||
|
||||
protected readonly PSRuleOption Option;
|
||||
|
||||
protected PipelineWriter(PipelineWriter inner, PSRuleOption option)
|
||||
{
|
||||
_Writer = inner;
|
||||
Option = option;
|
||||
}
|
||||
|
||||
public virtual void Begin()
|
||||
{
|
||||
if (_Writer == null)
|
||||
return;
|
||||
|
||||
_Writer.Begin();
|
||||
}
|
||||
|
||||
public virtual void WriteObject(object sendToPipeline, bool enumerateCollection)
|
||||
{
|
||||
if (_Writer == null || sendToPipeline == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteObject(sendToPipeline, enumerateCollection);
|
||||
}
|
||||
|
||||
public virtual void End()
|
||||
{
|
||||
if (_Writer == null)
|
||||
return;
|
||||
|
||||
_Writer.End();
|
||||
}
|
||||
|
||||
public void WriteVerbose(string format, params object[] args)
|
||||
{
|
||||
if (!ShouldWriteVerbose())
|
||||
return;
|
||||
|
||||
WriteVerbose(string.Format(Thread.CurrentThread.CurrentCulture, format, args));
|
||||
}
|
||||
|
||||
public virtual void WriteVerbose(string message)
|
||||
{
|
||||
if (_Writer == null || string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
_Writer.WriteVerbose(message);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteVerbose()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteVerbose();
|
||||
}
|
||||
|
||||
public virtual void WriteWarning(string message)
|
||||
{
|
||||
if (_Writer == null || string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
_Writer.WriteWarning(message);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteWarning()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteWarning();
|
||||
}
|
||||
|
||||
public void WriteError(Exception exception, string errorId, ErrorCategory errorCategory, object targetObject)
|
||||
{
|
||||
if (!ShouldWriteError())
|
||||
return;
|
||||
|
||||
WriteError(new ErrorRecord(exception, errorId, errorCategory, targetObject));
|
||||
}
|
||||
|
||||
public virtual void WriteError(ErrorRecord errorRecord)
|
||||
{
|
||||
if (_Writer == null || errorRecord == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteError(errorRecord);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteError()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteError();
|
||||
}
|
||||
|
||||
public virtual void WriteInformation(InformationRecord informationRecord)
|
||||
{
|
||||
if (_Writer == null || informationRecord == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteInformation(informationRecord);
|
||||
}
|
||||
|
||||
public virtual void WriteHost(HostInformationMessage info)
|
||||
{
|
||||
if (_Writer == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteHost(info);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteInformation()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteInformation();
|
||||
}
|
||||
|
||||
public virtual void WriteDebug(DebugRecord debugRecord)
|
||||
{
|
||||
if (_Writer == null || debugRecord == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteDebug(debugRecord);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteDebug()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteDebug();
|
||||
}
|
||||
|
||||
protected static ActionPreference GetPreferenceVariable(SessionState sessionState, string variableName)
|
||||
{
|
||||
return (ActionPreference)sessionState.PSVariable.GetValue(variableName);
|
||||
}
|
||||
_Writer.Begin();
|
||||
}
|
||||
|
||||
internal abstract class SerializationOutputWriter<T> : PipelineWriter
|
||||
public virtual void WriteObject(object sendToPipeline, bool enumerateCollection)
|
||||
{
|
||||
private readonly List<T> _Result;
|
||||
if (_Writer == null || sendToPipeline == null)
|
||||
return;
|
||||
|
||||
protected SerializationOutputWriter(PipelineWriter inner, PSRuleOption option)
|
||||
: base(inner, option)
|
||||
{
|
||||
_Result = new List<T>();
|
||||
}
|
||||
_Writer.WriteObject(sendToPipeline, enumerateCollection);
|
||||
}
|
||||
|
||||
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
|
||||
{
|
||||
Add(sendToPipeline);
|
||||
}
|
||||
public virtual void End()
|
||||
{
|
||||
if (_Writer == null)
|
||||
return;
|
||||
|
||||
protected void Add(object o)
|
||||
{
|
||||
if (o is T[] collection)
|
||||
_Result.AddRange(collection);
|
||||
else if (o is T item)
|
||||
_Result.Add(item);
|
||||
}
|
||||
_Writer.End();
|
||||
}
|
||||
|
||||
public sealed override void End()
|
||||
{
|
||||
var results = _Result.ToArray();
|
||||
base.WriteObject(Serialize(results), false);
|
||||
}
|
||||
public void WriteVerbose(string format, params object[] args)
|
||||
{
|
||||
if (!ShouldWriteVerbose())
|
||||
return;
|
||||
|
||||
protected abstract string Serialize(T[] o);
|
||||
WriteVerbose(string.Format(Thread.CurrentThread.CurrentCulture, format, args));
|
||||
}
|
||||
|
||||
public virtual void WriteVerbose(string message)
|
||||
{
|
||||
if (_Writer == null || string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
_Writer.WriteVerbose(message);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteVerbose()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteVerbose();
|
||||
}
|
||||
|
||||
public virtual void WriteWarning(string message)
|
||||
{
|
||||
if (_Writer == null || string.IsNullOrEmpty(message))
|
||||
return;
|
||||
|
||||
_Writer.WriteWarning(message);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteWarning()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteWarning();
|
||||
}
|
||||
|
||||
public void WriteError(Exception exception, string errorId, ErrorCategory errorCategory, object targetObject)
|
||||
{
|
||||
if (!ShouldWriteError())
|
||||
return;
|
||||
|
||||
WriteError(new ErrorRecord(exception, errorId, errorCategory, targetObject));
|
||||
}
|
||||
|
||||
public virtual void WriteError(ErrorRecord errorRecord)
|
||||
{
|
||||
if (_Writer == null || errorRecord == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteError(errorRecord);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteError()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteError();
|
||||
}
|
||||
|
||||
public virtual void WriteInformation(InformationRecord informationRecord)
|
||||
{
|
||||
if (_Writer == null || informationRecord == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteInformation(informationRecord);
|
||||
}
|
||||
|
||||
public virtual void WriteHost(HostInformationMessage info)
|
||||
{
|
||||
if (_Writer == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteHost(info);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteInformation()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteInformation();
|
||||
}
|
||||
|
||||
public virtual void WriteDebug(DebugRecord debugRecord)
|
||||
{
|
||||
if (_Writer == null || debugRecord == null)
|
||||
return;
|
||||
|
||||
_Writer.WriteDebug(debugRecord);
|
||||
}
|
||||
|
||||
public virtual bool ShouldWriteDebug()
|
||||
{
|
||||
return _Writer != null && _Writer.ShouldWriteDebug();
|
||||
}
|
||||
|
||||
protected static ActionPreference GetPreferenceVariable(SessionState sessionState, string variableName)
|
||||
{
|
||||
return (ActionPreference)sessionState.PSVariable.GetValue(variableName);
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class SerializationOutputWriter<T> : PipelineWriter
|
||||
{
|
||||
private readonly List<T> _Result;
|
||||
|
||||
protected SerializationOutputWriter(PipelineWriter inner, PSRuleOption option)
|
||||
: base(inner, option)
|
||||
{
|
||||
_Result = new List<T>();
|
||||
}
|
||||
|
||||
public override void WriteObject(object sendToPipeline, bool enumerateCollection)
|
||||
{
|
||||
Add(sendToPipeline);
|
||||
}
|
||||
|
||||
protected void Add(object o)
|
||||
{
|
||||
if (o is T[] collection)
|
||||
_Result.AddRange(collection);
|
||||
else if (o is T item)
|
||||
_Result.Add(item);
|
||||
}
|
||||
|
||||
public sealed override void End()
|
||||
{
|
||||
var results = _Result.ToArray();
|
||||
base.WriteObject(Serialize(results), false);
|
||||
}
|
||||
|
||||
protected abstract string Serialize(T[] o);
|
||||
}
|
||||
|
|
|
@ -4,51 +4,50 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Management.Automation;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Pipeline
|
||||
namespace PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
internal sealed class RepositoryHelper
|
||||
{
|
||||
internal sealed class RepositoryHelper
|
||||
private const string PROPERTY_BRANCHES = "Branches";
|
||||
private const string PROPERTY_LABELS = "Labels";
|
||||
private const string PROPERTY_MILESTONES = "Milestones";
|
||||
private const string PROPERTY_RELEASES = "Releases";
|
||||
private const string PROPERTY_TAGS = "Tags";
|
||||
|
||||
private readonly GitHubClient _Client;
|
||||
|
||||
public RepositoryHelper(GitHubContext serviceContext)
|
||||
{
|
||||
private const string PROPERTY_BRANCHES = "Branches";
|
||||
private const string PROPERTY_LABELS = "Labels";
|
||||
private const string PROPERTY_MILESTONES = "Milestones";
|
||||
private const string PROPERTY_RELEASES = "Releases";
|
||||
private const string PROPERTY_TAGS = "Tags";
|
||||
_Client = new GitHubClient(serviceContext);
|
||||
}
|
||||
|
||||
private readonly GitHubClient _Client;
|
||||
|
||||
public RepositoryHelper(GitHubContext serviceContext)
|
||||
public PSObject[] Get(string repositorySlug)
|
||||
{
|
||||
var result = new List<PSObject>();
|
||||
var repos = _Client.GetRepository(repositorySlug);
|
||||
for (var r = 0; r < repos.Length; r++)
|
||||
{
|
||||
_Client = new GitHubClient(serviceContext);
|
||||
}
|
||||
var repo = PSObject.AsPSObject(repos[r]);
|
||||
var branches = _Client.GetBranches(repos[r].Owner, repos[r].Name);
|
||||
var labels = _Client.GetLabels(repos[r].Owner, repos[r].Name);
|
||||
var milestones = _Client.GetMilestones(repos[r].Owner, repos[r].Name);
|
||||
var releases = _Client.GetReleases(repos[r].Owner, repos[r].Name);
|
||||
var tags = _Client.GetTags(repos[r].Owner, repos[r].Name);
|
||||
|
||||
public PSObject[] Get(string repositorySlug)
|
||||
{
|
||||
var result = new List<PSObject>();
|
||||
var repos = _Client.GetRepository(repositorySlug);
|
||||
for (var r = 0; r < repos.Length; r++)
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_BRANCHES, branches));
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_LABELS, labels));
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_MILESTONES, milestones));
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_RELEASES, releases));
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_TAGS, tags));
|
||||
result.Add(repo);
|
||||
|
||||
// Write branches as separate objects
|
||||
for (var b = 0; b < branches.Length; b++)
|
||||
{
|
||||
var repo = PSObject.AsPSObject(repos[r]);
|
||||
var branches = _Client.GetBranches(repos[r].Owner, repos[r].Name);
|
||||
var labels = _Client.GetLabels(repos[r].Owner, repos[r].Name);
|
||||
var milestones = _Client.GetMilestones(repos[r].Owner, repos[r].Name);
|
||||
var releases = _Client.GetReleases(repos[r].Owner, repos[r].Name);
|
||||
var tags = _Client.GetTags(repos[r].Owner, repos[r].Name);
|
||||
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_BRANCHES, branches));
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_LABELS, labels));
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_MILESTONES, milestones));
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_RELEASES, releases));
|
||||
repo.Properties.Add(new PSNoteProperty(PROPERTY_TAGS, tags));
|
||||
result.Add(repo);
|
||||
|
||||
// Write branches as separate objects
|
||||
for (var b = 0; b < branches.Length; b++)
|
||||
{
|
||||
var branch = PSObject.AsPSObject(branches[b]);
|
||||
result.Add(branch);
|
||||
}
|
||||
var branch = PSObject.AsPSObject(branches[b]);
|
||||
result.Add(branch);
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,30 +3,28 @@
|
|||
|
||||
using System;
|
||||
using System.Management.Automation;
|
||||
using System.Security;
|
||||
using PSRule.Rules.GitHub.Configuration;
|
||||
using PSRule.Rules.GitHub.Pipeline;
|
||||
|
||||
namespace PSRule.Rules.GitHub.Runtime
|
||||
namespace PSRule.Rules.GitHub.Runtime;
|
||||
|
||||
/// <summary>
|
||||
/// External helper to be referenced within rules.
|
||||
/// </summary>
|
||||
public static class Helper
|
||||
{
|
||||
/// <summary>
|
||||
/// External helper to be referenced within rules.
|
||||
/// </summary>
|
||||
public static class Helper
|
||||
private const string GITHUB_REPOSITORY = "GITHUB_REPOSITORY";
|
||||
private const string GITHUB_TOKEN = "GITHUB_TOKEN";
|
||||
|
||||
public static PSObject[] GetRepository()
|
||||
{
|
||||
private const string GITHUB_REPOSITORY = "GITHUB_REPOSITORY";
|
||||
private const string GITHUB_TOKEN = "GITHUB_TOKEN";
|
||||
var repos = PSRuleOption.TryGetEnvironmentVariableString(GITHUB_REPOSITORY, out var repo) ? new string[] { repo } : null;
|
||||
var credential = PSRuleOption.TryGetEnvironmentVariableSecureString(GITHUB_TOKEN, out var token) ? new PSCredential("token", token) : null;
|
||||
if (repos == null || repos.Length == 0 || credential == null)
|
||||
return Array.Empty<PSObject>();
|
||||
|
||||
public static PSObject[] GetRepository()
|
||||
{
|
||||
var repos = PSRuleOption.TryGetEnvironmentVariableString(GITHUB_REPOSITORY, out var repo) ? new string[] { repo } : null;
|
||||
var credential = PSRuleOption.TryGetEnvironmentVariableSecureString(GITHUB_TOKEN, out var token) ? new PSCredential("token", token) : null;
|
||||
if (repos == null || repos.Length == 0 || credential == null)
|
||||
return Array.Empty<PSObject>();
|
||||
|
||||
var context = new GitHubContext(repos, credential);
|
||||
var helper = new RepositoryHelper(context);
|
||||
return helper.Get(repo);
|
||||
}
|
||||
var context = new GitHubContext(repos, credential);
|
||||
var helper = new RepositoryHelper(context);
|
||||
return helper.Get(repo);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<ProjectGuid>{9ff459f0-c7bc-4936-9fc3-4d92b30b02a1}</ProjectGuid>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
@ -11,6 +11,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.1" />
|
||||
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.3.7" />
|
||||
<PackageReference Include="System.Management.Automation" Version="7.3.7" />
|
||||
<PackageReference Include="xunit" Version="2.4.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
Загрузка…
Ссылка в новой задаче