Merge remote-tracking branch 'private/main' into v2

This commit is contained in:
Engin Polat 2022-11-11 22:31:38 +00:00 коммит произвёл GitHub
Родитель 2307e7d335 e6e2ca23bf
Коммит 501f5dcc70
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
73 изменённых файлов: 2681 добавлений и 390 удалений

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

@ -1,6 +1,6 @@
# [Choice] .NET version: 6.0, 5.0, 3.1, 6.0-bullseye, 5.0-bullseye, 3.1-bullseye, 6.0-focal, 5.0-focal, 3.1-focal
ARG VARIANT="6.0"
FROM mcr.microsoft.com/vscode/devcontainers/dotnet:${VARIANT}
SHELL ["pwsh", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"]
RUN Install-Module -Name Pester -Force -SkipPublisherCheck
RUN Install-Module -Name Az -Force -SkipPublisherCheck
COPY ./scripts/post-create.sh /benchpress/
RUN chmod +x /benchpress/post-create.sh

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

@ -7,6 +7,7 @@
"VARIANT": "6.0"
}
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
@ -27,7 +28,7 @@
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
// ARM tools
@ -38,6 +39,7 @@
// C#
"ms-dotnettools.csharp",
// Python
"ms-python.python",
"ms-python.vscode-pylance",
@ -52,9 +54,10 @@
// Use 'postCreateCommand' to run commands after the container is created.
// Installs:
// 1. Mega Linter`
// 2. Configures benchpress Python module
"postCreateCommand": "npm install -g mega-linter-runner && pip install --editable ./framework/python/",
// 1. Mega Linter
// 2. Pester
// 3. Configures benchpress Python module
"postCreateCommand": "/benchpress/post-create.sh",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
@ -82,3 +85,4 @@
}
}
}

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

@ -0,0 +1,13 @@
#!/bin/sh
# Install Mega Linter
npm install -g mega-linter-runner
# Install Pester
pwsh -command Install-Module -Name Pester -Force -SkipPublisherCheck
# Installs the gRPC Tools
python -m pip install grpcio-tools
# Configures benchpress Python module
pip install --editable ./framework/python/

13
.github/workflows/pr_docs.yaml поставляемый Normal file → Executable file
Просмотреть файл

@ -1,14 +1,11 @@
name: pr_docs
on:
push:
pull_request:
paths:
- "docs/**.md"
- "**.md"
pull_request:
paths-ignore:
- "docs/**.md"
- "**.md"
- "./**.md"
- ".github/workflows/pr_docs.yaml"
branches:
- main
jobs:
@ -31,6 +28,6 @@ jobs:
SPELL_CSPELL_CONFIG_FILE: /config/megalinter/.cspell.json
MARKDOWN_MARKDOWN_LINK_CHECK_CONFIG_FILE: /config/megalinter/.markdown-link-check.json
DISABLE_LINTERS: REPOSITORY_CHECKOV,REPOSITORY_TRIVY
FILTER_REGEX_EXCLUDE: '(BenchPress/|engine/|examples/|\.github/workflows|\.devcontainer|\.editorconfig|\.gitmodules|/framework/|samples/|\.sln|LICENSE)'
FILTER_REGEX_EXCLUDE: '(BenchPress/|engine/|examples/|\.devcontainer|\.editorconfig|\.gitmodules|/framework/|samples/|\.sln|LICENSE)'
FILTER_REGEX_INCLUDE: '(docs/|\**.md)'
REPORT_OUTPUT_FOLDER: ${GITHUB_WORKSPACE}/megalinter-reports
REPORT_OUTPUT_FOLDER: ${GITHUB_WORKSPACE}/megalinter-reports

19
.github/workflows/pr_dotnet.yaml поставляемый Normal file → Executable file
Просмотреть файл

@ -1,14 +1,13 @@
name: pr_dotnet
on:
push:
pull_request:
paths:
- "framework/dotnet/**"
- "samples/dotnet/**"
pull_request:
paths-ignore:
- "framework/dotnet/**"
- "samples/dotnet/**"
- "engine/**"
- "protos/**"
- ".github/workflows/pr_dotnet.yaml"
branches:
- main
@ -24,10 +23,12 @@ jobs:
os: [ ubuntu-latest ]
steps:
- name: Checkout code
- name: Checkout repository and submodules
uses: actions/checkout@v3
with:
token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
fetch-depth: 0
submodules: recursive
- name: MegaLinter dotnet flavor
uses: oxsecurity/megalinter/flavors/dotnet@v6.12.0
@ -37,12 +38,12 @@ jobs:
PRINT_ALL_FILES: true
DISABLE: SPELL,COPYPASTE,YAML
DISABLE_LINTERS: REPOSITORY_CHECKOV,REPOSITORY_TRIVY
FILTER_REGEX_EXCLUDE: '(BenchPress/|engine/|examples/|/docs|\.github/workflows|\.devcontainer|\.editorconfig|\.gitmodules|\.sln|\.md|LICENSE|/framework/python|samples/python)'
FILTER_REGEX_INCLUDE: '(framework/dotnet|samples/dotnet)'
FILTER_REGEX_INCLUDE: '(framework/dotnet|samples/dotnet|engine/)'
FILTER_REGEX_EXCLUDE: '(examples/|/docs|\.devcontainer|\.editorconfig|\.gitmodules|\.sln|\.md|LICENSE|/framework/python|samples/python)'
REPORT_OUTPUT_FOLDER: ${GITHUB_WORKSPACE}/megalinter-reports
- name: Setup dotnet
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

12
.github/workflows/pr_python.yaml поставляемый Normal file → Executable file
Просмотреть файл

@ -1,14 +1,12 @@
name: pr_python
on:
push:
pull_request:
paths:
- "framework/python/**"
- "samples/python/**"
pull_request:
paths-ignore:
- "framework/python/**"
- "samples/python/**"
- "protos/**"
- ".github/workflows/pr_python.yaml"
branches:
- main
jobs:
@ -29,6 +27,6 @@ jobs:
PRINT_ALL_FILES: true
DISABLE: SPELL,COPYPASTE,YAML
DISABLE_LINTERS: PYTHON_MYPY,REPOSITORY_CHECKOV,REPOSITORY_TRIVY
FILTER_REGEX_EXCLUDE: '(BenchPress/|engine/|examples/|\.github/workflows|\.devcontainer|\.editorconfig|\.gitmodules|/docs|/framework/dotnet|samples/dotnet|\.sln|\.md|LICENSE)'
FILTER_REGEX_EXCLUDE: '(BenchPress/|engine/|examples/|\.devcontainer|\.editorconfig|\.gitmodules|/docs|/framework/dotnet|samples/dotnet|\.sln|\.md|LICENSE)'
FILTER_REGEX_INCLUDE: '(framework/python|samples/python)'
REPORT_OUTPUT_FOLDER: ${GITHUB_WORKSPACE}/megalinter-reports
REPORT_OUTPUT_FOLDER: ${GITHUB_WORKSPACE}/megalinter-reports

2
.gitmodules поставляемый
Просмотреть файл

@ -1,3 +1,3 @@
[submodule "BenchPress/bicep"]
path = BenchPress/bicep
url = https://github.com/azure/bicep
url = https://github.com/Azure/bicep.git

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

@ -1,221 +1,221 @@
is_global = true
# is_global = true
file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT License.
csharp_using_directive_placement = outside_namespace
csharp_prefer_braces = true
# file_header_template = Copyright (c) Microsoft Corporation.\nLicensed under the MIT License.
# csharp_using_directive_placement = outside_namespace
# csharp_prefer_braces = true
# Require file header
dotnet_diagnostic.IDE0073.severity = warning
# 'using' directive placement
dotnet_diagnostic.IDE0065.severity = warning
# Add braces
dotnet_diagnostic.IDE0011.severity = warning
# # Require file header
# dotnet_diagnostic.IDE0073.severity = warning
# # 'using' directive placement
# dotnet_diagnostic.IDE0065.severity = warning
# # Add braces
# dotnet_diagnostic.IDE0011.severity = warning
# Remove unnecessary import
dotnet_diagnostic.IDE0005.severity = none
# # Remove unnecessary import
# dotnet_diagnostic.IDE0005.severity = none
# Private member is unused
dotnet_diagnostic.IDE0051.severity = warning
# # Private member is unused
# dotnet_diagnostic.IDE0051.severity = warning
# Unused local variables
dotnet_diagnostic.CA1804.severity = warning
# Private methods that are not called from any other code
dotnet_diagnostic.CA1811.severity = warning
# Avoid unused private fields
dotnet_diagnostic.CA1823.severity = warning
# Use string.Contains(char) instead of string.Contains(string) with single characters
dotnet_diagnostic.CA1847.severity = warning
# Review SQL queries for security vulnerabilities
dotnet_diagnostic.CA2100.severity = warning
# Review visible event handlers
dotnet_diagnostic.CA2109.severity = warning
# Seal methods that satisfy private interfaces
dotnet_diagnostic.CA2119.severity = warning
# Do Not Catch Corrupted State Exceptions
dotnet_diagnostic.CA2153.severity = warning
# Do not use insecure deserializer BinaryFormatter
dotnet_diagnostic.CA2300.severity = warning
# Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder
dotnet_diagnostic.CA2301.severity = warning
# Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize
dotnet_diagnostic.CA2302.severity = warning
# Do not use insecure deserializer LosFormatter
dotnet_diagnostic.CA2305.severity = warning
# Do not use insecure deserializer NetDataContractSerializer
dotnet_diagnostic.CA2310.severity = warning
# Do not deserialize without first setting NetDataContractSerializer.Binder
dotnet_diagnostic.CA2311.severity = warning
# Ensure NetDataContractSerializer.Binder is set before deserializing
dotnet_diagnostic.CA2312.severity = warning
# Do not use insecure deserializer ObjectStateFormatter
dotnet_diagnostic.CA2315.severity = warning
# Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver
dotnet_diagnostic.CA2321.severity = warning
# Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing
dotnet_diagnostic.CA2322.severity = warning
# Do not use TypeNameHandling values other than None
dotnet_diagnostic.CA2326.severity = warning
# Do not use insecure JsonSerializerSettings
dotnet_diagnostic.CA2327.severity = warning
# Ensure that JsonSerializerSettings are secure
dotnet_diagnostic.CA2328.severity = warning
# Do not deserialize with JsonSerializer using an insecure configuration
dotnet_diagnostic.CA2329.severity = warning
# Ensure that JsonSerializer has a secure configuration when deserializing
dotnet_diagnostic.CA2330.severity = warning
# Do not use DataTable.ReadXml() with untrusted data
dotnet_diagnostic.CA2350.severity = warning
# Do not use DataSet.ReadXml() with untrusted data
dotnet_diagnostic.CA2351.severity = warning
# Unsafe DataSet or DataTable in serializable type can be vulnerable to remote code execution attacks
dotnet_diagnostic.CA2352.severity = warning
# Unsafe DataSet or DataTable in serializable type
dotnet_diagnostic.CA2353.severity = warning
# Unsafe DataSet or DataTable in deserialized object graph can be vulnerable to remote code execution attacks
dotnet_diagnostic.CA2354.severity = warning
# Unsafe DataSet or DataTable type found in deserializable object graph
dotnet_diagnostic.CA2355.severity = warning
# Unsafe DataSet or DataTable type in web deserializable object graph
dotnet_diagnostic.CA2356.severity = warning
# Ensure autogenerated class containing DataSet.ReadXml() is not used with untrusted data
dotnet_diagnostic.CA2361.severity = warning
# Unsafe DataSet or DataTable in autogenerated serializable type can be vulnerable to remote code execution attacks
dotnet_diagnostic.CA2362.severity = warning
# Review code for SQL injection vulnerabilities
dotnet_diagnostic.CA3001.severity = warning
# Review code for XSS vulnerabilities
dotnet_diagnostic.CA3002.severity = warning
# Review code for file path injection vulnerabilities
dotnet_diagnostic.CA3003.severity = warning
# Review code for information disclosure vulnerabilities
dotnet_diagnostic.CA3004.severity = warning
# Review code for LDAP injection vulnerabilities
dotnet_diagnostic.CA3005.severity = warning
# Review code for process command injection vulnerabilities
dotnet_diagnostic.CA3006.severity = warning
# Review code for open redirect vulnerabilities
dotnet_diagnostic.CA3007.severity = warning
# Review code for XPath injection vulnerabilities
dotnet_diagnostic.CA3008.severity = warning
# Review code for XML injection vulnerabilities
dotnet_diagnostic.CA3009.severity = warning
# Review code for XAML injection vulnerabilities
dotnet_diagnostic.CA3010.severity = warning
# Review code for DLL injection vulnerabilities
dotnet_diagnostic.CA3011.severity = warning
# Review code for regex injection vulnerabilities
dotnet_diagnostic.CA3012.severity = warning
# Do Not Add Schema By URL
dotnet_diagnostic.CA3061.severity = warning
# Insecure DTD processing in XML
dotnet_diagnostic.CA3075.severity = warning
# Insecure XSLT script processing.
dotnet_diagnostic.CA3076.severity = warning
# Insecure Processing in API Design, XmlDocument and XmlTextReader
dotnet_diagnostic.CA3077.severity = warning
# Mark Verb Handlers With Validate Antiforgery Token
dotnet_diagnostic.CA3147.severity = warning
# Do Not Use Weak Cryptographic Algorithms
dotnet_diagnostic.CA5350.severity = warning
# Do Not Use Broken Cryptographic Algorithms
dotnet_diagnostic.CA5351.severity = warning
# Review cipher mode usage with cryptography experts
dotnet_diagnostic.CA5358.severity = warning
# Do Not Disable Certificate Validation
dotnet_diagnostic.CA5359.severity = warning
# Do Not Call Dangerous Methods In Deserialization
dotnet_diagnostic.CA5360.severity = warning
# Do Not Disable SChannel Use of Strong Crypto
dotnet_diagnostic.CA5361.severity = warning
# Potential reference cycle in deserialized object graph
dotnet_diagnostic.CA5362.severity = warning
# Do Not Disable Request Validation
dotnet_diagnostic.CA5363.severity = warning
# Do Not Use Deprecated Security Protocols
dotnet_diagnostic.CA5364.severity = warning
# Do Not Disable HTTP Header Checking
dotnet_diagnostic.CA5365.severity = warning
# Use XmlReader For DataSet Read Xml
dotnet_diagnostic.CA5366.severity = warning
# Do Not Serialize Types With Pointer Fields
dotnet_diagnostic.CA5367.severity = warning
# Set ViewStateUserKey For Classes Derived From Page
dotnet_diagnostic.CA5368.severity = warning
# Use XmlReader For Deserialize
dotnet_diagnostic.CA5369.severity = warning
# Use XmlReader For Validating Reader
dotnet_diagnostic.CA5370.severity = warning
# Use XmlReader For Schema Read
dotnet_diagnostic.CA5371.severity = warning
# Use XmlReader For XPathDocument
dotnet_diagnostic.CA5372.severity = warning
# Do not use obsolete key derivation function
dotnet_diagnostic.CA5373.severity = warning
# Do Not Use XslTransform
dotnet_diagnostic.CA5374.severity = warning
# Do Not Use Account Shared Access Signature
dotnet_diagnostic.CA5375.severity = warning
# Use SharedAccessProtocol HttpsOnly
dotnet_diagnostic.CA5376.severity = warning
# Use Container Level Access Policy
dotnet_diagnostic.CA5377.severity = warning
# Do not disable ServicePointManagerSecurityProtocols
dotnet_diagnostic.CA5378.severity = warning
# Do Not Use Weak Key Derivation Function Algorithm
dotnet_diagnostic.CA5379.severity = warning
# Do Not Add Certificates To Root Store
dotnet_diagnostic.CA5380.severity = warning
# Ensure Certificates Are Not Added To Root Store
dotnet_diagnostic.CA5381.severity = warning
# Use Secure Cookies In ASP.Net Core
dotnet_diagnostic.CA5382.severity = warning
# Ensure Use Secure Cookies In ASP.Net Core
dotnet_diagnostic.CA5383.severity = warning
# Do Not Use Digital Signature Algorithm (DSA)
dotnet_diagnostic.CA5384.severity = warning
# Use Rivest–Shamir–Adleman (RSA) Algorithm With Sufficient Key Size
dotnet_diagnostic.CA5385.severity = warning
# Avoid hardcoding SecurityProtocolType value
dotnet_diagnostic.CA5386.severity = warning
# Do Not Use Weak Key Derivation Function With Insufficient Iteration Count
dotnet_diagnostic.CA5387.severity = warning
# Ensure Sufficient Iteration Count When Using Weak Key Derivation Function
dotnet_diagnostic.CA5388.severity = warning
# Do Not Add Archive Item's Path To The Target File System Path
dotnet_diagnostic.CA5389.severity = warning
# Do not hard-code encryption key
dotnet_diagnostic.CA5390.severity = warning
# Use antiforgery tokens in ASP.NET Core MVC controllers
dotnet_diagnostic.CA5391.severity = warning
# Use DefaultDllImportSearchPaths attribute for P/Invokes
dotnet_diagnostic.CA5392.severity = warning
# Do not use unsafe DllImportSearchPath value
dotnet_diagnostic.CA5393.severity = warning
# Do not use insecure randomness
dotnet_diagnostic.CA5394.severity = warning
# Miss HttpVerb attribute for action methods
dotnet_diagnostic.CA5395.severity = warning
# Set HttpOnly to true for HttpCookie
dotnet_diagnostic.CA5396.severity = warning
# Do not use deprecated SslProtocols values
dotnet_diagnostic.CA5397.severity = warning
# Avoid hardcoded SslProtocols values
dotnet_diagnostic.CA5398.severity = warning
# HttpClients should enable certificate revocation list checks
dotnet_diagnostic.CA5399.severity = warning
# Ensure HttpClient certificate revocation list check is not disabled
dotnet_diagnostic.CA5400.severity = warning
# Do not use CreateEncryptor with non-default IV
dotnet_diagnostic.CA5401.severity = warning
# Use CreateEncryptor with the default IV
dotnet_diagnostic.CA5402.severity = warning
# Do not hard-code certificate
dotnet_diagnostic.CA5403.severity = warning
# # Unused local variables
# dotnet_diagnostic.CA1804.severity = warning
# # Private methods that are not called from any other code
# dotnet_diagnostic.CA1811.severity = warning
# # Avoid unused private fields
# dotnet_diagnostic.CA1823.severity = warning
# # Use string.Contains(char) instead of string.Contains(string) with single characters
# dotnet_diagnostic.CA1847.severity = warning
# # Review SQL queries for security vulnerabilities
# dotnet_diagnostic.CA2100.severity = warning
# # Review visible event handlers
# dotnet_diagnostic.CA2109.severity = warning
# # Seal methods that satisfy private interfaces
# dotnet_diagnostic.CA2119.severity = warning
# # Do Not Catch Corrupted State Exceptions
# dotnet_diagnostic.CA2153.severity = warning
# # Do not use insecure deserializer BinaryFormatter
# dotnet_diagnostic.CA2300.severity = warning
# # Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder
# dotnet_diagnostic.CA2301.severity = warning
# # Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize
# dotnet_diagnostic.CA2302.severity = warning
# # Do not use insecure deserializer LosFormatter
# dotnet_diagnostic.CA2305.severity = warning
# # Do not use insecure deserializer NetDataContractSerializer
# dotnet_diagnostic.CA2310.severity = warning
# # Do not deserialize without first setting NetDataContractSerializer.Binder
# dotnet_diagnostic.CA2311.severity = warning
# # Ensure NetDataContractSerializer.Binder is set before deserializing
# dotnet_diagnostic.CA2312.severity = warning
# # Do not use insecure deserializer ObjectStateFormatter
# dotnet_diagnostic.CA2315.severity = warning
# # Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver
# dotnet_diagnostic.CA2321.severity = warning
# # Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing
# dotnet_diagnostic.CA2322.severity = warning
# # Do not use TypeNameHandling values other than None
# dotnet_diagnostic.CA2326.severity = warning
# # Do not use insecure JsonSerializerSettings
# dotnet_diagnostic.CA2327.severity = warning
# # Ensure that JsonSerializerSettings are secure
# dotnet_diagnostic.CA2328.severity = warning
# # Do not deserialize with JsonSerializer using an insecure configuration
# dotnet_diagnostic.CA2329.severity = warning
# # Ensure that JsonSerializer has a secure configuration when deserializing
# dotnet_diagnostic.CA2330.severity = warning
# # Do not use DataTable.ReadXml() with untrusted data
# dotnet_diagnostic.CA2350.severity = warning
# # Do not use DataSet.ReadXml() with untrusted data
# dotnet_diagnostic.CA2351.severity = warning
# # Unsafe DataSet or DataTable in serializable type can be vulnerable to remote code execution attacks
# dotnet_diagnostic.CA2352.severity = warning
# # Unsafe DataSet or DataTable in serializable type
# dotnet_diagnostic.CA2353.severity = warning
# # Unsafe DataSet or DataTable in deserialized object graph can be vulnerable to remote code execution attacks
# dotnet_diagnostic.CA2354.severity = warning
# # Unsafe DataSet or DataTable type found in deserializable object graph
# dotnet_diagnostic.CA2355.severity = warning
# # Unsafe DataSet or DataTable type in web deserializable object graph
# dotnet_diagnostic.CA2356.severity = warning
# # Ensure autogenerated class containing DataSet.ReadXml() is not used with untrusted data
# dotnet_diagnostic.CA2361.severity = warning
# # Unsafe DataSet or DataTable in autogenerated serializable type can be vulnerable to remote code execution attacks
# dotnet_diagnostic.CA2362.severity = warning
# # Review code for SQL injection vulnerabilities
# dotnet_diagnostic.CA3001.severity = warning
# # Review code for XSS vulnerabilities
# dotnet_diagnostic.CA3002.severity = warning
# # Review code for file path injection vulnerabilities
# dotnet_diagnostic.CA3003.severity = warning
# # Review code for information disclosure vulnerabilities
# dotnet_diagnostic.CA3004.severity = warning
# # Review code for LDAP injection vulnerabilities
# dotnet_diagnostic.CA3005.severity = warning
# # Review code for process command injection vulnerabilities
# dotnet_diagnostic.CA3006.severity = warning
# # Review code for open redirect vulnerabilities
# dotnet_diagnostic.CA3007.severity = warning
# # Review code for XPath injection vulnerabilities
# dotnet_diagnostic.CA3008.severity = warning
# # Review code for XML injection vulnerabilities
# dotnet_diagnostic.CA3009.severity = warning
# # Review code for XAML injection vulnerabilities
# dotnet_diagnostic.CA3010.severity = warning
# # Review code for DLL injection vulnerabilities
# dotnet_diagnostic.CA3011.severity = warning
# # Review code for regex injection vulnerabilities
# dotnet_diagnostic.CA3012.severity = warning
# # Do Not Add Schema By URL
# dotnet_diagnostic.CA3061.severity = warning
# # Insecure DTD processing in XML
# dotnet_diagnostic.CA3075.severity = warning
# # Insecure XSLT script processing.
# dotnet_diagnostic.CA3076.severity = warning
# # Insecure Processing in API Design, XmlDocument and XmlTextReader
# dotnet_diagnostic.CA3077.severity = warning
# # Mark Verb Handlers With Validate Antiforgery Token
# dotnet_diagnostic.CA3147.severity = warning
# # Do Not Use Weak Cryptographic Algorithms
# dotnet_diagnostic.CA5350.severity = warning
# # Do Not Use Broken Cryptographic Algorithms
# dotnet_diagnostic.CA5351.severity = warning
# # Review cipher mode usage with cryptography experts
# dotnet_diagnostic.CA5358.severity = warning
# # Do Not Disable Certificate Validation
# dotnet_diagnostic.CA5359.severity = warning
# # Do Not Call Dangerous Methods In Deserialization
# dotnet_diagnostic.CA5360.severity = warning
# # Do Not Disable SChannel Use of Strong Crypto
# dotnet_diagnostic.CA5361.severity = warning
# # Potential reference cycle in deserialized object graph
# dotnet_diagnostic.CA5362.severity = warning
# # Do Not Disable Request Validation
# dotnet_diagnostic.CA5363.severity = warning
# # Do Not Use Deprecated Security Protocols
# dotnet_diagnostic.CA5364.severity = warning
# # Do Not Disable HTTP Header Checking
# dotnet_diagnostic.CA5365.severity = warning
# # Use XmlReader For DataSet Read Xml
# dotnet_diagnostic.CA5366.severity = warning
# # Do Not Serialize Types With Pointer Fields
# dotnet_diagnostic.CA5367.severity = warning
# # Set ViewStateUserKey For Classes Derived From Page
# dotnet_diagnostic.CA5368.severity = warning
# # Use XmlReader For Deserialize
# dotnet_diagnostic.CA5369.severity = warning
# # Use XmlReader For Validating Reader
# dotnet_diagnostic.CA5370.severity = warning
# # Use XmlReader For Schema Read
# dotnet_diagnostic.CA5371.severity = warning
# # Use XmlReader For XPathDocument
# dotnet_diagnostic.CA5372.severity = warning
# # Do not use obsolete key derivation function
# dotnet_diagnostic.CA5373.severity = warning
# # Do Not Use XslTransform
# dotnet_diagnostic.CA5374.severity = warning
# # Do Not Use Account Shared Access Signature
# dotnet_diagnostic.CA5375.severity = warning
# # Use SharedAccessProtocol HttpsOnly
# dotnet_diagnostic.CA5376.severity = warning
# # Use Container Level Access Policy
# dotnet_diagnostic.CA5377.severity = warning
# # Do not disable ServicePointManagerSecurityProtocols
# dotnet_diagnostic.CA5378.severity = warning
# # Do Not Use Weak Key Derivation Function Algorithm
# dotnet_diagnostic.CA5379.severity = warning
# # Do Not Add Certificates To Root Store
# dotnet_diagnostic.CA5380.severity = warning
# # Ensure Certificates Are Not Added To Root Store
# dotnet_diagnostic.CA5381.severity = warning
# # Use Secure Cookies In ASP.Net Core
# dotnet_diagnostic.CA5382.severity = warning
# # Ensure Use Secure Cookies In ASP.Net Core
# dotnet_diagnostic.CA5383.severity = warning
# # Do Not Use Digital Signature Algorithm (DSA)
# dotnet_diagnostic.CA5384.severity = warning
# # Use Rivest–Shamir–Adleman (RSA) Algorithm With Sufficient Key Size
# dotnet_diagnostic.CA5385.severity = warning
# # Avoid hardcoding SecurityProtocolType value
# dotnet_diagnostic.CA5386.severity = warning
# # Do Not Use Weak Key Derivation Function With Insufficient Iteration Count
# dotnet_diagnostic.CA5387.severity = warning
# # Ensure Sufficient Iteration Count When Using Weak Key Derivation Function
# dotnet_diagnostic.CA5388.severity = warning
# # Do Not Add Archive Item's Path To The Target File System Path
# dotnet_diagnostic.CA5389.severity = warning
# # Do not hard-code encryption key
# dotnet_diagnostic.CA5390.severity = warning
# # Use antiforgery tokens in ASP.NET Core MVC controllers
# dotnet_diagnostic.CA5391.severity = warning
# # Use DefaultDllImportSearchPaths attribute for P/Invokes
# dotnet_diagnostic.CA5392.severity = warning
# # Do not use unsafe DllImportSearchPath value
# dotnet_diagnostic.CA5393.severity = warning
# # Do not use insecure randomness
# dotnet_diagnostic.CA5394.severity = warning
# # Miss HttpVerb attribute for action methods
# dotnet_diagnostic.CA5395.severity = warning
# # Set HttpOnly to true for HttpCookie
# dotnet_diagnostic.CA5396.severity = warning
# # Do not use deprecated SslProtocols values
# dotnet_diagnostic.CA5397.severity = warning
# # Avoid hardcoded SslProtocols values
# dotnet_diagnostic.CA5398.severity = warning
# # HttpClients should enable certificate revocation list checks
# dotnet_diagnostic.CA5399.severity = warning
# # Ensure HttpClient certificate revocation list check is not disabled
# dotnet_diagnostic.CA5400.severity = warning
# # Do not use CreateEncryptor with non-default IV
# dotnet_diagnostic.CA5401.severity = warning
# # Use CreateEncryptor with the default IV
# dotnet_diagnostic.CA5402.severity = warning
# # Do not hard-code certificate
# dotnet_diagnostic.CA5403.severity = warning
# Parameter 'parameter' has no matching param tag in the XML comment for 'parameter' (but other parameters do)
dotnet_diagnostic.CS1573.severity = suggestion
# Missing XML comment for publicly visible type or member 'Type_or_Member'
dotnet_diagnostic.CS1591.severity = none
# # Parameter 'parameter' has no matching param tag in the XML comment for 'parameter' (but other parameters do)
# dotnet_diagnostic.CS1573.severity = suggestion
# # Missing XML comment for publicly visible type or member 'Type_or_Member'
# dotnet_diagnostic.CS1591.severity = none
# VSTHRD200: Use "Async" suffix for async methods
dotnet_diagnostic.VSTHRD200.severity = none
# # VSTHRD200: Use "Async" suffix for async methods
# dotnet_diagnostic.VSTHRD200.severity = none

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

@ -5,7 +5,7 @@ APPLY_FIXES: all # all, none, or list of linter keys
# DISABLE:
# - COPYPASTE # Uncomment to disable checks of excessive copy-pastes
# - SPELL # Uncomment to disable checks of spelling mistakes
DISABLE_LINTERS:
DISABLE_LINTERS:
- PYTHON_PYRIGHT
- PYTHON_MYPY
SHOW_ELAPSED_TIME: true

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

@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{4596A4
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BenchPress.Sample", "samples\dotnet\BenchPress.Sample\BenchPress.Sample.csproj", "{89C81EDE-90E0-47CD-93DB-2E5024244D88}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Cli", "BenchPress\bicep\src\Bicep.Cli\Bicep.Cli.csproj", "{4C58A322-C05A-4E83-A702-2BED16E121B6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -52,6 +54,10 @@ Global
{89C81EDE-90E0-47CD-93DB-2E5024244D88}.Debug|Any CPU.Build.0 = Debug|Any CPU
{89C81EDE-90E0-47CD-93DB-2E5024244D88}.Release|Any CPU.ActiveCfg = Release|Any CPU
{89C81EDE-90E0-47CD-93DB-2E5024244D88}.Release|Any CPU.Build.0 = Release|Any CPU
{4C58A322-C05A-4E83-A702-2BED16E121B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C58A322-C05A-4E83-A702-2BED16E121B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C58A322-C05A-4E83-A702-2BED16E121B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C58A322-C05A-4E83-A702-2BED16E121B6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{19C5E14A-FCA3-4335-A25C-D6DA88BE3099} = {29011A2E-34D3-468C-AA6F-C5AD5597970D}

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

@ -1,42 +1,50 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.3.32708.82
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generators", "./Generators/Generators.csproj", "{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generators", ".\Generators\Generators.csproj", "{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.Cli", "./bicep/src/Bicep.Cli/Bicep.Cli.csproj", "{0E331097-42A8-4B2A-A66A-189035BA2AD0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.Decompiler", ".\bicep\src\Bicep.Decompiler\Bicep.Decompiler.csproj", "{2B418AB3-40D2-4F64-9795-93B4299118E8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.Decompiler", "./bicep/src/Bicep.Decompiler/Bicep.Decompiler.csproj", "{2B418AB3-40D2-4F64-9795-93B4299118E8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.Core", ".\bicep\src\Bicep.Core\Bicep.Core.csproj", "{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bicep.Core", "./bicep/src/Bicep.Core/Bicep.Core.csproj", "{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "bicep", "bicep", "{E06A5B8A-FB46-4713-B89C-8B033C8FA400}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{02F8A98A-1887-4887-BBE8-01F3679D9EE5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Cli", "bicep\src\Bicep.Cli\Bicep.Cli.csproj", "{49CCB6E9-1C8F-4FAA-877F-9A28E7B06976}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}.Release|Any CPU.Build.0 = Release|Any CPU
{0E331097-42A8-4B2A-A66A-189035BA2AD0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E331097-42A8-4B2A-A66A-189035BA2AD0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E331097-42A8-4B2A-A66A-189035BA2AD0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E331097-42A8-4B2A-A66A-189035BA2AD0}.Release|Any CPU.Build.0 = Release|Any CPU
{2B418AB3-40D2-4F64-9795-93B4299118E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B418AB3-40D2-4F64-9795-93B4299118E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B418AB3-40D2-4F64-9795-93B4299118E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B418AB3-40D2-4F64-9795-93B4299118E8}.Release|Any CPU.Build.0 = Release|Any CPU
{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {64B8D145-6451-43EA-B0D6-B46165A4B25D}
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C779329A-EF48-47D7-8EC1-5D3CA3CC2E58}.Release|Any CPU.Build.0 = Release|Any CPU
{2B418AB3-40D2-4F64-9795-93B4299118E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B418AB3-40D2-4F64-9795-93B4299118E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B418AB3-40D2-4F64-9795-93B4299118E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B418AB3-40D2-4F64-9795-93B4299118E8}.Release|Any CPU.Build.0 = Release|Any CPU
{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6B26AAD-D21F-48A5-9FB3-6FD3B00DC8F1}.Release|Any CPU.Build.0 = Release|Any CPU
{49CCB6E9-1C8F-4FAA-877F-9A28E7B06976}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{49CCB6E9-1C8F-4FAA-877F-9A28E7B06976}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49CCB6E9-1C8F-4FAA-877F-9A28E7B06976}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49CCB6E9-1C8F-4FAA-877F-9A28E7B06976}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {64B8D145-6451-43EA-B0D6-B46165A4B25D}
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{02F8A98A-1887-4887-BBE8-01F3679D9EE5} = {E06A5B8A-FB46-4713-B89C-8B033C8FA400}
{49CCB6E9-1C8F-4FAA-877F-9A28E7B06976} = {02F8A98A-1887-4887-BBE8-01F3679D9EE5}
EndGlobalSection
EndGlobal

2
BenchPress/Generators/.gitignore поставляемый
Просмотреть файл

@ -1,4 +1,4 @@
bin
obj
output
.vscode
.vscode

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

@ -1,11 +1,11 @@
# Bicep Feature receives three parameters.
# 1. Bicep file path. 2. parameters 3. resourceGroupName
function Deploy-BicepFeature([string]$path, $params){
function Deploy-BicepFeature([string]$path, $params) {
# armPath will be assigned for same bicep file name with json extension
$fileName = [System.IO.Path]::GetFileNameWithoutExtension($path)
$folder = Split-Path $path
$armPath = Join-Path -Path $folder -ChildPath "$fileName.json"
$armPath = Join-Path -Path $folder -ChildPath "$fileName.json"
# az bicep build will create arm template from bicep file.
# Arm template will same as bicep name with json extension
@ -20,13 +20,13 @@ function Deploy-BicepFeature([string]$path, $params){
# 1. TenantDeployment 2.ResourceGroupDeployment 3. ManagementGroupDeployment 4. SubscriptionDeployment
New-AzSubscriptionDeployment -Name "$deploymentName" -Location "$location" -TemplateFile "$armPath" -TemplateParameterObject $params -SkipTemplateParameterPrompt
}
Write-Host "Removing Arm template json"
Remove-Item "$armPath"
}
function Remove-BicepFeature($resourceGroupName){
function Remove-BicepFeature($resourceGroupName) {
Get-AzResourceGroup -Name $resourceGroupName | Remove-AzResourceGroup -Force
}

1
BenchPress/bicep Submodule

@ -0,0 +1 @@
Subproject commit e3b8f88809b8d0232625951e0bd314b174ed7154

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

@ -1,11 +1,13 @@
# Bicep testing framework
This framework is intended to work as a testing framework for Azure deployment features by using [Bicep](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep).
This is a testing framework for Azure deployments using [Bicep](https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview?tabs=bicep).
In order to see how you can work with this framework you can find one sample bicep file in the folder [samples](https://github.com/Azure/benchpress/tree/main/examples)
that will be deployed by using one PowerShell script.
Process is the following:
An example of how to use this framework can be found in the [Powershell Test Sample](docs/powershell_test_sample.md) guide.
The process of the tests is the following:
```mermaid
flowchart LR
@ -14,13 +16,37 @@ A[Creation] -->|Bicep| B[Verification]
B --> C[Remove]
```
**Creation**: New Features are gonna be deployed through Bicep files
**Verification**: Test is going to confirm the resource exists and also assert if it matches the expected value
**Remove**: Optionally resources can be removed after being tested
**Creation**: New Features are deployed using Bicep files
**Verification**: Tests confirm that the resource exists and that it matches the expected values
**Remove**: Optionally, resources can be removed after being tested
## Benchpress Architecture
BenchPress uses [gRPC](https://grpc.io/docs/what-is-grpc/introduction/) to create a multi-language testing framework.
The BenchPress Test Engine is a C# gRPC Server located under `/engine/BenchPress.TestEngine`. The Test Engine is responsible
for the business logic of deploying Bicep files, obtaining information about deployed resources in Azure, and cleaning up the
deployment afterward.
The BenchPress Test Frameworks (located under `/framework/`) are gRPC Clients of multiple languages. The Test Frameworks are
responsible interfacing between the user's tests (written in their chosen language) and the Test Engine. They are also
responsible for managing the life cycle of the Test Engine.
gRPC uses [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview). The `/protos/` folder contains BenchPress's .proto files. These define the API that the gRPC
Server and Clients use to communicate.
![From left to right: 1). There is a Powershell Test Script and a Python Test Script. 2). The Powershell Test Script calls into a Powershell Test Framework. The Python Test Script calls into a Python Test Framework. 3) Both Test Frameworks call through a gRPC Boundary. 4) The gRPC Boundary wraps a language agnostic Test Engine. 5). The Test Engine calls into both the Bicep CLI and the Azure Resource Manager.](docs/images/architecture-diagram.png)
## Getting started
See [Getting Started](docs/getting_started.md) guide on how to start development on *Benchpress*.
## Deveplopment Tips
See [Github Actions Lint Workflow](docs/github_actions_lint_workflow.md) for how to maintain the CI/CD pipeline.
See [Manually Testing the Test Engine](docs/manually_testing_the_test_engine.md) for guidance on exploring the gRPC endpoints.
## Contributing
This project welcomes contributions and suggestions. Most contributions require you to agree to a

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

@ -33,7 +33,9 @@
"pwsh",
"upskilling",
"mitigations",
"fmok"
"fmok",
"proto",
"protos"
],
"version": "0.2",
"patterns": [

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

@ -8,10 +8,12 @@
#)
#IncludeDefaultRules=${true}
ExcludeRules = @(
'PSMissingModuleManifestField'
'PSMissingModuleManifestField',
'PSProvideCommentHelp',
'PSAvoidUsingWriteHost',
'PSUseSingularNouns',
'PSAvoidUsingPositionalParameters',
'PSAvoidUsingInvokeExpression',
'PSUseShouldProcessForStateChangingFunctions'
)
#IncludeRules = @(
# 'PSAvoidUsingWriteHost',
# 'MyCustomRuleName'
#)
}
}

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

@ -0,0 +1,4 @@
public class BenchpressTestAttribute : Attribute
{
public BenchpressTestAttribute() { }
}

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

@ -0,0 +1,4 @@
public class OnDoneAttribute : Attribute
{
public OnDoneAttribute() { }
}

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

@ -0,0 +1,4 @@
public class OnEngineStartFailureAttribute : Attribute
{
public OnEngineStartFailureAttribute() { }
}

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

@ -0,0 +1,4 @@
public class OnEngineStartSuccessAttribute : Attribute
{
public OnEngineStartSuccessAttribute() { }
}

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

@ -0,0 +1,5 @@
public class OnInitizationAttribute : Attribute
{
public OnInitizationAttribute() { }
}

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

@ -0,0 +1,4 @@
public class OnShutdownAttribute : Attribute
{
public OnShutdownAttribute() { }
}

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

@ -0,0 +1,4 @@
public class OnTestExecuteAttribute : Attribute
{
public OnTestExecuteAttribute() { }
}

238
designs/BenchPress.cs Normal file
Просмотреть файл

@ -0,0 +1,238 @@
using System.Reflection;
public enum State
{
PreInitialization,
Initialization,
EngineStarting,
EngineStartSuccess,
EngineStartFailure,
TestExecute,
Shutdown,
EngineShutdownSuccess,
Done
}
// The choice of name of the class is not significant and is subject
// to change by the desginer.
//
// At time of writing, this class is not intended to be in a
// compilable or executable state.
//
// The objective of this class design artifact is serve as a guide
// for a developer to programatically manage the lifecycle of the
// BenchPress Engine and the State Machine it will implement.
public class BenchPress : ILifecycleManager
{
private static readonly object lockObj = new object();
private static BenchPress? instance = null;
private const int MaxRestart = 2;
public int EnginePID { get; private set; } = -1;
public State CurrentState { get; private set; } = State.PreInitialization;
public static BenchPress Instance
{
get
{
if (instance == null)
{
lock (lockObj)
{
instance = new BenchPress();
}
}
return instance;
}
}
private BenchPress()
{
TransitionToNextState(State.Initialization);
}
private void Process()
{
switch (CurrentState)
{
case State.Initialization: Init(); break;
case State.EngineStarting:
PreEngineStart();
StartEngine();
break;
case State.EngineStartSuccess: OnEngineStartSuccess(); break;
case State.EngineStartFailure: OnEngineStartFailure(); break;
case State.TestExecute: OnTestExecute(); break;
case State.Shutdown: StopEngine(); break;
case State.EngineShutdownSuccess: Teardown(); break;
case State.Done: Done(); break;
}
}
private void TransitionToNextState(State nextState)
{
if (nextState != CurrentState)
{
CurrentState = nextState;
Process();
}
}
public void Init()
{
TransitionToNextState(State.EngineStarting);
}
public void PreEngineStart(int retryCount=3, int httpTimeout=60000, bool keepAlive=true) { }
public async void StartEngine()
{
var isStarted = false;
try
{
Monitor.Enter(lockObj);
try
{
var restartCount = 0;
while (!isStarted && restartCount < MAX_RESTART)
{
restartCount++;
int enginePID = await MockStartProcess();
if (enginePID > 0)
{
isStarted = true;
}
}
}
finally
{
Monitor.Exit(lockObj);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine("A SynchronizationLockException occurred. Message: ");
Console.WriteLine(SyncEx.Message);
}
if (isStarted)
{
TransitionToNextState(State.EngineStartSuccess);
}
else
{
TransitionToNextState(State.EngineStartFailure);
}
}
private void OnEngineStartSuccess()
{
TransitionToNextState(State.TestExecute);
}
private void OnEngineStartFailure()
{
TransitionToNextState(State.Shutdown);
}
private void PreTestExecute() { }
private void OnTestExecute()
{
var testMethods = GetMethodsWithAttribute(typeof(BenchpressTestAttribute));
// Invoke the test
testMethods.ToList().ForEach(method =>
{
InvokeTest(method);
});
TransitionToNextState(State.Shutdown);
}
public async void StopEngine()
{
try
{
Monitor.Enter(lockObj);
try
{
int exitCode = await MockStopProcess(EnginePID);
}
finally
{
Monitor.Exit(lockObj);
}
}
catch (SynchronizationLockException SyncEx)
{
Console.WriteLine("A SynchronizationLockException occurred. Message: ");
Console.WriteLine(SyncEx.Message);
}
TransitionToNextState(State.EngineShutdownSuccess);
}
private void Teardown()
{
InvokeMethodsMarkedWithAttribute(typeof(OnShutdownAttribute));
TransitionToNextState(State.Done);
}
private void Done() { }
private void InvokeTest(MethodInfo method)
{
// pre
//InvokeMethodsMarkedWithAttribute(BenchPress.Attributes.PreTestExecute);
// execute
//InvokeMethodsMarkedWithAttribute(BenchPress.Attributes.Test);
// post
InvokeMethodsMarkedWithAttribute(typeof(OnTestExecuteAttribute));
}
private MethodInfo[] GetMethodsWithAttribute(params Type[] attributes)
{
return new MethodInfo[] { };
}
private void InvokeMethodsMarkedWithAttribute(params Type[] attributes)
{
/* logic */
/*foreach (Attribute attr in attributes)
{
foreach (MethodInfo method in attr)
{
if (HasAttribute(method, attr))
{
method.Invoke();
}
}
}*/
}
private async Task<int> MockStartProcess()
{
int PID = -1;
await Task.Run(() =>
{
PID = 1;
});
return PID;
}
private async Task<int> MockStopProcess(int PID)
{
int EXIT_CODE = -1;
await Task.Run(() =>
{
EXIT_CODE = 0;
});
return EXIT_CODE;
}
}

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

@ -0,0 +1,3 @@
interface IAllocateResourceGroup {
bool CreateResourceGroup(String AzureSubscritionId);
}

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

@ -0,0 +1,3 @@
interface ICreateResource {
bool CreateResources(String resourceGroupName, List<String> resources);
}

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

@ -0,0 +1,6 @@
interface ILifecycleManager {
void Init();
void PreEngineStart(int retryCount=3, int httpTimeout=60000, bool keepAlive=true);
void StartEngine();
void StopEngine();
}

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

@ -0,0 +1,3 @@
interface IValidateResource {
bool ValidateResoure(String validStateJson);
}

188
designs/README.md Normal file
Просмотреть файл

@ -0,0 +1,188 @@
# Benchpress Testing Framework Design
## Overview
The core Benchpress engine proposes an *Inversion of Control* (IoC) design paradigm. In this design, we transfer the control of objects or portions of a program to a container or framework for orchestrating and execution.
Few advantages of IoC design is that it enables test writers to use any language-specific framework of their choice. For example, in C# *NUnit*, *XUnit*, and *MSTest* are some of the most popular choices.
The primary goal of the Benchpress testing framework is to start and stop the *Core testing Engine* in a thread-safe manner, while also supporting injecting optional runtime configuration into the engine.
Some examples of runtime configuration that the framework may provide to the engine include:
- Number of automatic restarts due to exception conditions the engine should undergo before reporting a fatal error to the framework.
- Number of automatic retries should the engine make when interacting over the network before reporting a fatal error to the framework.
- How long of a time period should the engine wait during network interactions before entering a timeout condition and reporting a fatal error to the framework.
Our API allows test authors to include declarative decorators/annotations/attributes on their test methods and/or test classes that will intercept the flow-of-control at runtime to execute the various pre/post lifecycle management steps - including ensuring an engine process instance is available - allowing for massively concurrent test execution.
## State Diagram
Internally the testing framework has a state machine that allows it to track and execute events and test methods.
```mermaid
flowchart TD
PreInitialization --> Initialization
Initialization --> EngineStarting
EngineStarting -->|Success| EngineStartSuccess
EngineStarting -->|Failure| EngineStartFailure
EngineStartSuccess --> TestExecute
TestExecute -->|1...n| TestExecute
EngineStartFailure --> ShutDown
TestExecute --> ShutDown
ShutDown --> EngineShutdownSuccess
EngineShutdownSuccess --> Done
```
## Public API
The public API **only** needs to be called in frameworks/languages where IoC is not possible, e.g., Powershell/Pester. In such cases, the expectation is that users will call `Start()` to configure the testing enviornment and on test teardown call `Stop()`.
- `Init()` - Init singleton instance
- `PreEngineStart` - Optional Configuration
- `StartEngine()` - Start the engine
- `StopEngine()` - Stop the engine
## Example Test
### Method-level
```c#
class Test1
{
// Annotate at method level
[BenchpressTest]
public void TestRG()
{
var result = Benchpress.DoesResourceGroupExist("my-rg");
Debug.Assert(result, true);
}
// Annotate at method level
[BenchpressTest]
public void Test_ResourceGroupExists()
{
var result = Benchpress.DoesResourceGroupExist("my-rg");
Debug.Assert(result, true);
}
}
```
### Class-level
```c#
// Annotate at class level
[BenchpressTest]
class Test2
{
public void TestRG()
{
var result = Benchpress.DoesResourceGroupExist("my-rg");
Debug.Assert(result, true);
}
public void Test_ResourceGroupExists()
{
var result = Benchpress.DoesResourceGroupExist("my-rg");
Debug.Assert(result, true);
}
}
```
### Subscribe to life-cycle events
```c#
// Listen to life-cycle events
[BenchpressTest]
class Test3
{
[Initization]
public void OnInitization() { /* ... */ }
[EngineStartSuccess]
public void OnEngineStartSuccess() { /* ... */ }
[EngineStartFailure]
public void OnEngineStartFailure() { /* ... */ }
[TestExecute]
public void OnTestExecute(MethodInfo method) { /* ... */ }
[Shutdown]
public void OnShutdown() { /* ... */ }
[Done]
public void OnDone() { /* ... */ }
[Fact]
public void TestResourceGroupExists()
{
var result = Benchpress.DoesStorageAccountExists("mystorage");
Debug.Assert(result == true, "Test failed");
}
[Fact]
public void TestStorageAccountPolicy()
{
var result = Benchpress.DoesStorageAccountExists("mystorage--222");
Debug.Assert(result == true, "Test failed");
}
}
```
### Lifecycle Event Descriptions
```
Init() // Called once at the very start of test execution before the engine is started. This method can be used to perform or pre-execution steps.
PreEngineStart(RetryCount = 3, HttpTimeout = 60000, KeepAlive = true, ...more configuration) – Called once to start the engine if not already started
StartEngine() // Performs resource locking and starts the engine
OnEngineStartSuccess() // Called once if the engine is started successfully
OnEngineStartFailure() // Called once the engine is started successfully.
PreTestExecute(method) // Called once before each test executes.
OnTestExecute(method) // Called once after the test executes.
PreTestExecute(method) // Called once before each test executes.
StopEngine() // Performs resource locking and stops the engine
TearDown() // Called once after the engine is stopped and before Done() is called
Done() // Called at the end once of framework
```
### Proposed Sequence Diagram
```mermaid
sequenceDiagram
Runtime/OS->>+Engine: main([...args])
Engine->Engine: Init
Engine->Engine: If RestartCount>2 then Destroy
Engine->Engine: Setup
par Engine Ready State. ASync Comms allowed
Runtime/OS->>+Engine: Shutdown Signal
Engine->Engine: Destroy
Engine-->>-Runtime/OS: $EXIT_CODE
and
Engine->Engine: Exceptional Condition
Engine->Engine: Teardown
and
Framework->>+Engine: Create Resource Group given $AZURE_SUBSCRIPTION_ID
Engine->>+Azure: Create Resource Group
Azure-->>-Engine:
Engine->>-Framework: Resource Group Creation Status
and
Framework->>+Engine: Create Resource(s) given $RESOURCE_GROUP_NAME
Engine->>+Azure: Create Resource(s)
Azure-->>-Engine:
Engine-->>-Framework:
and
Framework->>+Engine: Validate Resource(s) given $VALID_STAE
Engine->>+Azure: Get Resource(s) State(s)
Azure-->>-Engine:
Engine-->>-Framework:
end
Engine->Engine: Teardown
Engine->Engine: If AutoRestart==True then Setup
Engine->Engine: Destroy
Engine-->>-Runtime/OS: $EXIT_CODE
```

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

@ -1,21 +1,26 @@
# Getting started
This guide walks you through the process of starting development on *Benchpress*.
## Setting up the development environment
## Development environment setup within VS Code
If youre using [Visual Studio Code](https://code.visualstudio.com/) as your IDE of choice, then this project contains all of the necessary configurations to bootstrap your development environment using a container known as a [Dev Container](https://code.visualstudio.com/docs/remote/containers).
If youre using [Visual Studio Code](https://code.visualstudio.com/) as your IDE of choice, then this project contains all of the necessary configurations to bootstrap your development environment. See the section on [Development environment setup within VS Code
](#development-environment-setup-within-vs-code)
To use the Dev Container, please follow the installation guide to install Docker and the VS Code extension: https://code.visualstudio.com/docs/remote/containers#_installation
### Development environment setup within VS Code
Visual Studio Code supports compilation and development on a container known as [Dev Containers](https://code.visualstudio.com/docs/remote/containers).
Then launch the environment by opening the command palette <kbd>Shift</kbd>+<kbd>Command</kbd>+<kbd>P</kbd> (Mac) / <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> (Windows/Linux) and running `Dev Containers: Open Folder in Container`
If youre using VS Code, please install see the installation guide to install Docker and VS Code extension: https://code.visualstudio.com/docs/remote/containers#_installation
The Dev Container configuration also contains VS Code extensions for linting, formatting, testing, and compilation.
Then launch the environment by opening the command pallete <kbd>Shift</kbd>+<kbd>Command</kbd>+<kbd>P</kbd> (Mac) / <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>P</kbd> (Windows/Linux) and running `Dev Containers: Open Folder in Container`
### Authenticating git within the dev container
For MacOs, make sure your ssh key is properly added to your key-chain
The Dev Container configuration also contains VS Code extensions for linting/formatting/testing/compilation.
1. Call `ssh-add -l` in your **host** terminal. If your key is not in your key-chain, it will say `The agent has no identities` or the identities listed will not include the key you use to authenticate with git.
2. To add your key to the key-chain, call `shh-add <the path to your private key>` (most likely, `ssh-add ~/.ssh/id_rsa`).
3. Call `ssh-add -l` on your **host** terminal again to verify the identity has been added.
4. Call `ssh-add -l` in your **dev container** terminal to verify that it is accessible in the container. Now you should be able to authenticate with git from within the dev container.
### Development dependencies
For Windows, try following [this stack overflow post](https://stackoverflow.com/questions/56490194/vs-code-bitbucket-ssh-permission-denied-publickey/72029153#72029153)
## Development dependencies if not using VS Code
Depending on the feature/language you are working on, you may need to download and install language-specific packages, e.g., Python 3.
List of requirements on development machine:
@ -24,11 +29,19 @@ List of requirements on development machine:
- DotNet Core (version 6.0)
- Node (>= version 14)
- PowerShell 8
- Python 3
- Python 3.5
- pip 9.0.1
### Python setup
Install the gRPC Tools:
```bash
python -m pip install grpcio-tools
```
#### Python setup
From the root directory, execute to install benchpress as a module that can be referenced:
> pip install --editable ./framework/python/
```bash
pip install --editable ./framework/python/
```

91
docs/github_actions_lint_workflow.md Normal file → Executable file
Просмотреть файл

@ -1,6 +1,6 @@
# Github Actions Lint Workflow
Current repo has three linting workflows in the `.github/workflows`. Each workflow uses specific Megalinter flavor to lint specific folders when new code gets pushed. All configuration files are located under `config/megalinter` directory
Current repo has three linting workflows in the `.github/workflows` directory. Each workflow uses specific Megalinter flavor to lint specific folders when new code gets pushed. All configuration files are located under `config/megalinter` directory
- pr_dotnet - uses `oxsecurity/megalinter/flavors/dotnet@v6.12.0` to lint dotnet code in the `framework/` and `samples/` directories
- pr_python - uses `oxsecurity/megalinter/flavors/python@v6.12.0` to lint python code in the `framework/` and `samples/` directories
@ -29,29 +29,44 @@ Rather than having to commit/push every time you want to test out the changes yo
This is an example of running `.github/workflows/pr_dotnet.yml` file to lint dotnet specific folders locally using `act` command
Run the workflow locally using `act` command
```sh
act pull_request --workflows .\.github\workflows\pr_docs.yaml
```
```yaml
name: pr_dotnet
on:
push:
pull_request:
paths:
- "framework/dotnet/**"
- "samples/dotnet/**"
pull_request:
paths-ignore:
- "framework/dotnet/**"
- "samples/dotnet/**"
- "engine/**"
- "protos/**"
- ".github/workflows/pr_dotnet.yaml"
branches:
- main
env:
DOTNET_VERSION: '6.0.x'
jobs:
lint:
runs-on: ubuntu-latest
name: lint-${{matrix.os}}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest ]
steps:
- name: Checkout code
- name: Checkout repository and submodules
uses: actions/checkout@v3
with:
token: ${{ secrets.PAT || secrets.GITHUB_TOKEN }}
fetch-depth: 0
submodules: recursive
- name: MegaLinter dotnet flavor
uses: oxsecurity/megalinter/flavors/dotnet@v6.12.0
@ -61,52 +76,18 @@ jobs:
PRINT_ALL_FILES: true
DISABLE: SPELL,COPYPASTE,YAML
DISABLE_LINTERS: REPOSITORY_CHECKOV,REPOSITORY_TRIVY
FILTER_REGEX_EXCLUDE: '(BenchPress/|engine/|examples/|/docs|\.github/workflows|\.devcontainer|\.editorconfig|\.gitmodules|\.sln|\.md|LICENSE|/framework/python|samples/python)'
FILTER_REGEX_INCLUDE: "(framework/dotnet|samples/dotnet)"
FILTER_REGEX_INCLUDE: '(framework/dotnet|samples/dotnet|engine/)'
FILTER_REGEX_EXCLUDE: '(examples/|/docs|\.devcontainer|\.editorconfig|\.gitmodules|\.sln|\.md|LICENSE|/framework/python|samples/python)'
REPORT_OUTPUT_FOLDER: ${GITHUB_WORKSPACE}/megalinter-reports
```
Run workflow using `act` command
```sh
act pull_request --workflows .\.github\workflows\pr_docs.yaml
```
#### Linting overview
When new code gets pushed to dotnet directories, the linting gets triggered
```yaml
on:
push:
paths:
- "framework/dotnet/**"
- "samples/dotnet/**"
```
When pull_request is created, following directories gets skipped from linting to allow the merge
```yaml
pull_request:
paths-ignore:
- "framework/dotnet/**"
- "samples/dotnet/**"
branches:
- main
```
In this pr_dotnet workflow, we are using Megalinter flavor for dotnet
```yaml
- name: MegaLinter dotnet flavor
uses: oxsecurity/megalinter/flavors/dotnet@v6.12.0
```
Finally, using Megalinter env variables to exclude and include directories for linting, and disable linters for this pr_dotnet
```yaml
env:
DISABLE_LINTERS: REPOSITORY_CHECKOV,REPOSITORY_TRIVY
FILTER_REGEX_EXCLUDE: '(BenchPress/|engine/|examples/|/docs|\.github/workflows|\.devcontainer|\.editorconfig|\.gitmodules|\.sln|\.md|LICENSE|/framework/python|samples/python)'
FILTER_REGEX_INCLUDE: "(framework/dotnet|samples/dotnet)"
- name: Setup dotnet
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: Install dependencies
run: dotnet restore
- name: Build
run: dotnet build --configuration Release --no-restore
```

Двоичные данные
docs/images/architecture-diagram.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 69 KiB

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

@ -0,0 +1,199 @@
# Manually Testing the Test Engine
Ideally, when a Test Engine method needs to be E2E tested, we would run one of the Test Frameworks against the Engine. The Test Framework would already know how to spin up the Engine, make gRPC calls, and kill the Engine. So, testing the Engine would be as simple as calling the Engine method from within the Framework.
Unfortunately, a Test Framework might not be ready to successfully call the Test Engine method that you want to test. In this case, we can write our own gRPC Client to call the Engine's gRPC endpoint and verify that it works.
## Running the Engine
First, ensure that your machine is authenticated with Azure by running `az login`.
From the `/engine/BenchPress.TestEngine/` folder, start the server with `dotnet run`. When the gRPC Server starts, it will output what port it is running on (in this case, `5152`).
```bash
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5152
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /workspaces/benchpress-private/engine/BenchPress.TestEngine/
```
## Writing a Client
The following Client code examples can be found under `/samples/manual-testers/`.
### C#
Create a new C# Project to make calls to the Test Engine.
Ensure that the `.csproj` file references the necessary gRPC packages...
```xml
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.21.6" />
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
<PackageReference Include="Grpc.Tools" Version="2.49.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
```
... and the `.proto` files. Make sure they are marked as Client, not Server.
```xml
<ItemGroup>
<Protobuf Include="../../../protos/deployment.proto" GrpcServices="Client" />
<Protobuf Include="../../../protos/resource_group.proto" GrpcServices="Client" />
</ItemGroup>
```
In the `.cs` file that will be calling the Test Engine method, include the following `using` statements:
```csharp
using Grpc.Net.Client;
using BenchPress.TestEngine;
```
Your editor's intellisense should now be able to pick up on the objects and interfaces defined in the .proto files.
Start by creating the gRPC Channel using the port that the Test Engine is running on:
```csharp
using var channel = GrpcChannel.ForAddress("http://localhost:5152");
```
Then, create the client(s) that define the Test Engine method(s) you wish to call:
```csharp
var deployClient = new Deployment.DeploymentClient(channel);
var rgClient = new ResourceGroup.ResourceGroupClient(channel);
```
Now you can build your Request objects...
```csharp
var request = new DeploymentGroupRequest {
BicepFilePath = "main.bicep",
ParameterFilePath = "params.json",
ResourceGroupName = "rg-jsmith-benchpress-test",
SubscriptionNameOrId = "John-Smiths-Subscription"
};
```
... and make calls to the Test Engine
```csharp
var result = await deployClient.DeploymentGroupCreateAsync(request);
```
Altogether, you may have a simple console app `Program.cs` that looks like this:
```csharp
using Grpc.Net.Client;
using BenchPress.TestEngine;
using var channel = GrpcChannel.ForAddress("http://localhost:5152");
var client = new Deployment.DeploymentClient(channel);
var request = new DeploymentSubRequest {
BicepFilePath = "<path to your bicep file>",
ParameterFilePath = "<path to your params file, if needed>",
Location = "eastus",
SubscriptionNameOrId = "<your subscription id>"
};
var result = await client.DeploymentSubCreateAsync(request);
Console.WriteLine($"Success? {result.Success}");
Console.WriteLine(result.ErrorMessage);
```
While the Test Engine is running in a separate terminal, run your Client code. Using this Client, you can manually test the Test Engine.
### Python
If needed, generate the `pb2` scripts for the `.proto` files you will be using. (They might already exist in under `/framework/python/src/benchpress`).
```bash
python -m grpc_tools.protoc -I./protos --python_out=. --grpc_python_out=. ./protos/deployment.proto
```
The above example ran at the root of the project directory will create a `deployment_pb2.py` and `deployment_pb2_grpc.py` file.
In the python script you will be using for your tests, include the following import statements (for whatever `pb2` files you are going to use):
```python
import grpc
import deployment_pb2
import deployment_pb2_grpc
import resource_group_pb2
import resource_group_pb2_grpc
```
Start by creating the gRPC Channel using the port that the Test Engine is running on:
```python
with grpc.insecure_channel('localhost:5152') as channel:
```
Then, create the client(s) that define the Test Engine method(s) you wish to call:
```python
deployStub = deployment_pb2_grpc.DeploymentStub(channel)
rgStub = resource_group_pb2_grpc.ResourceGroupStub(channel)
```
Now you can build your Request objects...
```python
req = deployment_pb2.DeploymentGroupRequest(
bicep_file_path = 'main.bicep',
parameter_file_path = 'params.json',
resource_group_name = 'rg-jsmith-benchpress-test',
subscription_name_or_id = 'John-Smiths-Subscription'
)
```
... and make calls to the Test Engine
```python
response = deployStub.DeploymentGroupCreate(req)
```
Altogether, you may have a python script like this:
```python
from __future__ import print_function
import logging
import grpc
import deployment_pb2
import deployment_pb2_grpc
def run():
with grpc.insecure_channel('localhost:5152') as channel:
stub = deployment_pb2_grpc.DeploymentStub(channel)
req = deployment_pb2.DeploymentSubRequest(
bicep_file_path = '<path to your bicep file>',
parameter_file_path = '<path to your params file, if needed>',
location = 'eastus',
subscription_name_or_id = '<your subscription id>'
)
response = stub.DeploymentSubCreate(req)
print("Success? " + response.success)
print(response.error_message)
if __name__ == '__main__':
logging.basicConfig()
run()
```
While the Test Engine is running in a separate terminal, run your Client script. Using this Client, you can manually test the Test Engine.

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

@ -1,19 +1,38 @@
# How to test PowerShell Test Sample Code
# PowerShell How To Test Example
Following instruction shows how to run StorageAccount.Tests.ps1 file
Following instructions shows how to run StorageAccount.Tests.ps1 test.
File is located under following directory: `/samples/dotnet/samples/pwsh/Test`
`StorageAccount.Test.ps1`:
Login to Azure Subscription `Connect-AzAccount`
if you are using docker container `Connect-AzAccount -UseDeviceAuthentication` and follow additional login instruction on the terminal
- Deploys Azure resources to the Azure Cloud
- tests deployed resource properties
- Finally deletes deployed resources.
Run the test `.\StorageAccount.Tests.ps1`
## Prerequisites
- Azure Subscription
- Azure CLI
- Pester [Pester Install Guide](https://pester.dev/docs/introduction/installation)
- Optional: Docker
Test files are located under the following directory: `/samples/dotnet/samples/pwsh/Test`
## Step by Step Guide
### Auth to Azure
Login to Azure Subscription `Connect-AzAccount` from Azure CLI
if you are using docker container `Connect-AzAccount -UseDeviceAuthentication` and follow additional login instructions prompted by terminal
### Running the Test
Storage Account deployment uses two bicep files:
- `storageAccountDeploy.bicep` - to deploy resource group and storage account module. This is an actual deployment file
- `storageAccount.bicep` - storage account bicep file which is called by `storageAccountDeploy.bicep` as a module file
To run the test simply navigate to the `StorageAccount.Tests.ps1` in CLI and execute the PowerShell test file `.\StorageAccount.Tests.ps1`
`StorageAccount.Tests.ps1` - Content.
```powershell
@ -79,5 +98,4 @@ Describe 'Spin up , Tear down Storage Account' {
Remove-BicepFeature $resourceGroupName
}
}
#EOF
```

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

@ -0,0 +1,254 @@
using Azure;
using Azure.ResourceManager;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.Resources.Models;
namespace BenchPress.TestEngine.Tests;
public class ArmDeploymentServiceTests {
private readonly ArmDeploymentService armDeploymentService;
private readonly Mock<ArmClient> armClientMock;
private readonly Mock<IFileService> fileServiceMock;
private readonly Mock<ArmDeploymentCollection> groupDeploymentsMock;
private readonly Mock<ArmDeploymentCollection> subscriptionDeploymentsMock;
private const string validSubId = "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d";
private const string validRgName = "test-rg";
private const string validLocation = "eastus";
private const string standaloneTemplate = "storage-account.json";
private const string templateWithParams = "storage-account-needs-params.json";
private const string parameters = "params.json";
public ArmDeploymentServiceTests()
{
armClientMock = new Mock<ArmClient>(MockBehavior.Strict);
fileServiceMock = new Mock<IFileService>(MockBehavior.Strict);
groupDeploymentsMock = new Mock<ArmDeploymentCollection>();
subscriptionDeploymentsMock = new Mock<ArmDeploymentCollection>();
armDeploymentService = new TestArmDeploymentService(groupDeploymentsMock.Object, subscriptionDeploymentsMock.Object, armClientMock.Object, fileServiceMock.Object);
fileServiceMock.Setup(fs => fs.ReadAllTextAsync(It.IsAny<string>())).ThrowsAsync(new FileNotFoundException());
fileServiceMock.Setup(fs => fs.ReadAllTextAsync(It.IsIn(new [] {standaloneTemplate, templateWithParams, parameters}))).ReturnsAsync("");
}
[Fact]
public async Task DeployArmToResourceGroupAsync_Deploys()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, templateWithParams, parameters);
VerifyDeploymentsMock(groupDeploymentsMock);
}
[Theory]
[InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("", "rg-test", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "rg-test", "")]
public async Task DeployArmToResourceGroupAsync_MissingParameter_ThrowsException(string templatePath, string rgName, string subId)
{
var subMock = SetUpSubscriptionMock(subId);
var rgMock = SetUpResourceGroupMock(subMock, rgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<ArgumentException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(subId, rgName, templatePath)
);
Assert.Equal("One or more parameters were missing or empty", ex.Message);
}
[Fact]
public async Task DeployArmToResourceGroupAsync_InvalidTemplate_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
var expectedMessage = "Deployment template validation failed";
SetUpDeploymentExceptionMock(groupDeploymentsMock, new RequestFailedException(expectedMessage));
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, templateWithParams)
);
Assert.Equal(expectedMessage, ex.Message);
}
[Fact]
public async Task DeployArmToResourceGroupAsync_SubscriptionNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync("The Wrong Subscription", validRgName, standaloneTemplate)
);
Assert.Equal("Subscription Not Found", ex.Message);
}
[Fact]
public async Task DeployArmToResourceGroupAsync_ResourceGroupNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, "the-wrong-rg", standaloneTemplate)
);
Assert.Equal("Resource Group Not Found", ex.Message);
}
[Fact]
public async Task DeployArmToSubscriptionAsync_Deploys()
{
var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, validLocation, templateWithParams, parameters);
VerifyDeploymentsMock(subscriptionDeploymentsMock);
}
[Theory]
[InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("", "eastus", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "eastus", "")]
public async Task DeployArmToSubscriptionAsync_MissingParameter_ThrowsException(string templatePath, string location, string subId)
{
var subMock = SetUpSubscriptionMock(subId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
var ex = await Assert.ThrowsAsync<ArgumentException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync(subId, location, templatePath)
);
Assert.Equal("One or more parameters were missing or empty", ex.Message);
}
[Fact]
public async Task DeployArmToSubscriptionAsync_InvalidTemplate_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var excepectedMessage = "Deployment template validation failed";
SetUpDeploymentExceptionMock(subscriptionDeploymentsMock, new RequestFailedException(excepectedMessage));
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, validLocation, templateWithParams)
);
Assert.Equal(excepectedMessage, ex.Message);
}
[Fact]
public async Task DeployArmToSubscriptionAsync_SubscriptionNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
var ex = await Assert.ThrowsAsync<RequestFailedException>(
async () => await armDeploymentService.DeployArmToSubscriptionAsync("The Wrong Subscription", validLocation, standaloneTemplate)
);
Assert.Equal("Subscription Not Found", ex.Message);
}
[Fact]
public async Task CreateDeploymentContent_WithoutParameters()
{
var subMock = SetUpSubscriptionMock(validSubId);
SetUpDeploymentsMock(subscriptionDeploymentsMock);
await armDeploymentService.DeployArmToSubscriptionAsync(validSubId, validLocation, standaloneTemplate);
VerifyDeploymentsMock(subscriptionDeploymentsMock);
}
[Fact]
public async Task CreateDeploymentContent_WithoutLocation()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, standaloneTemplate);
VerifyDeploymentsMock(groupDeploymentsMock);
}
[Fact]
public async Task CreateDeploymentContent_TemplateNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<FileNotFoundException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, "invalid-file.json")
);
}
[Fact]
public async Task CreateDeploymentContent_ParametersNotFound_ThrowsException()
{
var subMock = SetUpSubscriptionMock(validSubId);
var rgMock = SetUpResourceGroupMock(subMock, validRgName);
SetUpDeploymentsMock(groupDeploymentsMock);
var ex = await Assert.ThrowsAsync<FileNotFoundException>(
async () => await armDeploymentService.DeployArmToResourceGroupAsync(validSubId, validRgName, standaloneTemplate, "invalid-file.json")
);
}
private Mock<SubscriptionResource> SetUpSubscriptionMock(string nameOrId)
{
var subMock = new Mock<SubscriptionResource>();
var collectionMock = new Mock<SubscriptionCollection>();
var response = Azure.Response.FromValue(subMock.Object, Mock.Of<Azure.Response>());
collectionMock.Setup(x => x.GetAsync(It.IsAny<string>(), default)).ThrowsAsync(new Azure.RequestFailedException("Subscription Not Found"));
collectionMock.Setup(x => x.GetAsync(nameOrId, default)).ReturnsAsync(response);
armClientMock.Setup(x => x.GetSubscriptions()).Returns(collectionMock.Object);
return subMock;
}
private Mock<ResourceGroupResource> SetUpResourceGroupMock(Mock<SubscriptionResource> subMock, string rgName)
{
var rgMock = new Mock<ResourceGroupResource>();
var collectionMock = new Mock<ResourceGroupCollection>();
var response = Azure.Response.FromValue(rgMock.Object, Mock.Of<Azure.Response>());
collectionMock.Setup(x => x.GetAsync(It.IsAny<string>(), default)).ThrowsAsync(new Azure.RequestFailedException("Resource Group Not Found"));
collectionMock.Setup(x => x.GetAsync(rgName, default)).ReturnsAsync(response);
subMock.Setup(x => x.GetResourceGroups()).Returns(collectionMock.Object);
return rgMock;
}
private void SetUpDeploymentsMock(Mock<ArmDeploymentCollection> deploymentsMock)
{
deploymentsMock.Setup(x => x.CreateOrUpdateAsync(
It.IsAny<Azure.WaitUntil>(),
It.IsAny<string>(),
It.IsAny<ArmDeploymentContent>(),
default))
.ReturnsAsync(Mock.Of<ArmOperation<ArmDeploymentResource>>());
}
private void SetUpDeploymentExceptionMock<T>(Mock<ArmDeploymentCollection> deploymentsMock, T exception) where T : Exception
{
deploymentsMock.Setup(x => x.CreateOrUpdateAsync(
It.IsAny<Azure.WaitUntil>(),
It.IsAny<string>(),
It.IsAny<ArmDeploymentContent>(),
default))
.ThrowsAsync(exception);
}
private void VerifyDeploymentsMock(Mock<ArmDeploymentCollection> deploymentsMock)
{
deploymentsMock.Verify(x => x.CreateOrUpdateAsync(
It.IsAny<Azure.WaitUntil>(),
It.IsAny<string>(),
It.IsAny<ArmDeploymentContent>(),
default),
Times.Once);
}
// This test class mocks the extension methods for GetArmDeployments()
private class TestArmDeploymentService : ArmDeploymentService {
private readonly ArmDeploymentCollection rgDeploymentCollection;
private readonly ArmDeploymentCollection subDeploymentCollection;
public TestArmDeploymentService(ArmDeploymentCollection rgDeploymentCollection, ArmDeploymentCollection subDeploymentCollection, ArmClient client, IFileService fileService) : base(client, fileService)
{
this.rgDeploymentCollection = rgDeploymentCollection;
this.subDeploymentCollection = subDeploymentCollection;
}
protected override Task<ArmOperation<ArmDeploymentResource>> CreateGroupDeployment(ResourceGroupResource rg, WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
{
return rgDeploymentCollection.CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
}
protected override Task<ArmOperation<ArmDeploymentResource>> CreateSubscriptionDeployment(SubscriptionResource sub, WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
{
return subDeploymentCollection.CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
}
}
}

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

@ -9,7 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.ResourceManager" Version="1.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -25,4 +27,8 @@
<GlobalAnalyzerConfigFiles Include="../../.globalconfig" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BenchPress.TestEngine\BenchPress.TestEngine.csproj" />
</ItemGroup>
</Project>

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

@ -0,0 +1,76 @@
namespace BenchPress.TestEngine.Tests;
public class BicepTranspileServiceTests
{
private readonly Mock<IBicepExecute> mockBicepSubmodule;
private readonly Mock<IFileService> mockFileService;
private readonly BicepTranspileService bicepTranspileService;
public BicepTranspileServiceTests()
{
mockBicepSubmodule = new Mock<IBicepExecute>();
mockFileService = new Mock<IFileService>();
var logger = Mock.Of<ILogger<BicepTranspileService>>();
bicepTranspileService = new BicepTranspileService(mockBicepSubmodule.Object, logger, mockFileService.Object);
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ReturnsAsync(0);
mockFileService.Setup(fs => fs.FileExists(It.IsAny<string>())).Returns(true);
mockFileService.Setup(fs => fs.GetFileExtension(It.IsAny<string>())).Returns(".bicep");
mockFileService.Setup(fs => fs.ChangeFileExtension(It.IsAny<string>(),It.IsAny<string>())).Returns("a/b/c/test.json");
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public async Task Build_NullInputPath_Throws(string inputFile)
{
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
await Assert.ThrowsAsync<ArgumentNullException>(async () => await bicepTranspileService.BuildAsync(inputFile));
mockBicepSubmodule.Verify(p => p.ExecuteCommandAsync(It.IsAny<string[]>()), Times.Never);
}
[Theory]
[InlineData("a/b/c/test.bicep")]
public async Task Build_GeneratedArmTemplateExist(string inputFile)
{
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
var outFile = "a/b/c/test.json";
var armPath = await bicepTranspileService.BuildAsync(inputFile);
var args = new[] { "build", inputFile, "--outfile", outFile };
Assert.Equal(outFile, armPath);
mockBicepSubmodule.Verify(p => p.ExecuteCommandAsync(args), Times.Once);
}
[Theory]
[InlineData("a/b/c/test.txt")]
public async Task Build_NonBicepFileInputPath_Throws(string inputFile)
{
mockFileService.Setup(fs => fs.GetFileExtension(It.IsAny<string>())).Returns(".txt");
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
await Assert.ThrowsAsync<ArgumentException>(async () => await bicepTranspileService.BuildAsync(inputFile));
}
[Theory]
[InlineData("a/b/c/test.bicep")]
public async Task Build_BicepModuleNotImplemented_Throws(string inputFile)
{
mockBicepSubmodule.Setup(p => p.ExecuteCommandAsync(It.IsAny<string[]>())).ThrowsAsync(new ApplicationException("Bicep transpilation failed"));
mockFileService.Setup(fs => fs.GetFileFullPath(It.IsAny<string>())).Returns(inputFile);
await Assert.ThrowsAsync<ApplicationException>(async () => await bicepTranspileService.BuildAsync(inputFile));
}
[Theory]
[InlineData("a/b/c/test.bicep")]
public async Task Build_FileNotFoundException_Throws(string inputFile)
{
mockFileService.Setup(fs => fs.FileExists(It.IsAny<string>())).Returns(false);
await Assert.ThrowsAsync<FileNotFoundException>(async () => await bicepTranspileService.BuildAsync(inputFile));
}
}

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

@ -0,0 +1,324 @@
using Azure.ResourceManager;
using Azure.ResourceManager.Resources;
namespace BenchPress.TestEngine.Tests;
public class DeploymentServiceTests
{
private readonly DeploymentService deploymentService;
private readonly ServerCallContext context;
private readonly Mock<IBicepTranspileService> bicepTranspileServiceMock;
private readonly Mock<IArmDeploymentService> armDeploymentMock;
private const string templatePath = "main.json";
public DeploymentServiceTests()
{
var logger = Mock.Of<ILogger<DeploymentService>>();
bicepTranspileServiceMock = new Mock<IBicepTranspileService>(MockBehavior.Strict);
armDeploymentMock = new Mock<IArmDeploymentService>(MockBehavior.Strict);
deploymentService = new DeploymentService(logger, bicepTranspileServiceMock.Object, armDeploymentMock.Object);
context = new MockServerCallContext();
}
[Fact]
public async Task DeploymentGroupCreate_DeploysResourceGroup_WithTranspiledFiles()
{
SetUpSuccessfulTranspilation(validGroupRequest.BicepFilePath, templatePath);
SetUpSuccessfulGroupDeployment(validGroupRequest, templatePath);
var result = await deploymentService.DeploymentGroupCreate(validGroupRequest, context);
Assert.True(result.Success);
VerifyGroupDeployment(validGroupRequest, templatePath);
}
[Theory]
[InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("", "rg-test", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "rg-test", "")]
public async Task DeploymentGroupCreate_FailsOnMissingParameters(string bicepFilePath, string resourceGroupName, string subscriptionNameOrId)
{
var request = SetUpGroupRequest(bicepFilePath, resourceGroupName, subscriptionNameOrId);
SetUpSuccessfulTranspilation(validGroupRequest.BicepFilePath, templatePath);
SetUpSuccessfulGroupDeployment(request, templatePath);
var result = await deploymentService.DeploymentGroupCreate(request, context);
Assert.False(result.Success);
VerifyNoTranspilation();
VerifyNoDeployments();
}
[Fact]
public async Task DeploymentGroupCreate_ReturnsFailureOnTranspileException()
{
var expectedMessage = "the bicep file was malformed";
SetUpExceptionThrowingTranspilation(new Exception(expectedMessage));
SetUpSuccessfulGroupDeployment(validGroupRequest, "template.json");
var result = await deploymentService.DeploymentGroupCreate(validGroupRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact]
public async Task DeploymentGroupCreate_ReturnsFailureOnFailedDeployment()
{
SetUpSuccessfulTranspilation(validGroupRequest.BicepFilePath, templatePath);
var expectedReason = "Failure occured during deployment";
SetUpFailedGroupDeployment(expectedReason);
var result = await deploymentService.DeploymentGroupCreate(validGroupRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedReason, result.ErrorMessage);
}
[Fact]
public async Task DeploymentGroupCreate_ReturnsFailureOnDeploymentException()
{
SetUpSuccessfulTranspilation(validGroupRequest.BicepFilePath, templatePath);
var expectedMessage = "the template was malformed";
SetUpExceptionThrowingGroupDeployment(new Exception(expectedMessage));
var result = await deploymentService.DeploymentGroupCreate(validGroupRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact]
public async Task DeploymentSubCreate_DeploysToSub_WithTranspiledFiles()
{
SetUpSuccessfulTranspilation(validSubRequest.BicepFilePath, templatePath);
SetUpSuccessfulSubDeployment(validSubRequest, templatePath);
var result = await deploymentService.DeploymentSubCreate(validSubRequest, context);
Assert.True(result.Success);
VerifySubDeployment(validSubRequest, templatePath);
}
[Theory]
[InlineData("main.bicep", "", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("", "eastus", "a3a01f37-665c-4ee8-9bc3-3adf7ebcec0d")]
[InlineData("main.bicep", "eastus", "")]
public async Task DeploymentSubCreate_FailsOnMissingParameters(string bicepFilePath, string location, string subscriptionNameOrId)
{
var request = SetUpSubRequest(bicepFilePath, location, subscriptionNameOrId);
SetUpSuccessfulTranspilation(validSubRequest.BicepFilePath, templatePath);
SetUpSuccessfulSubDeployment(request, templatePath);
var result = await deploymentService.DeploymentSubCreate(request, context);
Assert.False(result.Success);
VerifyNoTranspilation();
VerifyNoDeployments();
}
[Fact]
public async Task DeploymentSubCreate_ReturnsFailureOnTranspileException()
{
var expectedMessage = "the bicep file was malformed";
SetUpExceptionThrowingTranspilation(new Exception(expectedMessage));
SetUpSuccessfulSubDeployment(validSubRequest, "template.json");
var result = await deploymentService.DeploymentSubCreate(validSubRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact]
public async Task DeploymentSubCreate_ReturnsFailureOnFailedDeployment()
{
SetUpSuccessfulTranspilation(validSubRequest.BicepFilePath, templatePath);
var expectedReason = "Failure occured during deployment";
SetUpFailedSubDeployment(expectedReason);
var result = await deploymentService.DeploymentSubCreate(validSubRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedReason, result.ErrorMessage);
}
[Fact]
public async Task DeploymentSubpCreate_ReturnsFailureOnDeploymentException()
{
SetUpSuccessfulTranspilation(validSubRequest.BicepFilePath, templatePath);
var expectedMessage = "the template was malformed";
SetUpExceptionThrowingSubDeployment(new Exception(expectedMessage));
var result = await deploymentService.DeploymentSubCreate(validSubRequest, context);
Assert.False(result.Success);
Assert.Equal(expectedMessage, result.ErrorMessage);
}
[Fact(Skip = "Not Implemented")]
public async Task DeleteGroup_DeletesAllResources()
{
var request = new DeleteGroupRequest
{
ResourceGroupName = "test-rg",
SubscriptionNameOrId = Guid.NewGuid().ToString()
};
var result = await deploymentService.DeleteGroup(request, context);
Assert.True(result.Success);
}
private readonly DeploymentGroupRequest validGroupRequest = new DeploymentGroupRequest
{
BicepFilePath = "main.bicep",
ResourceGroupName = "test-rg",
SubscriptionNameOrId = Guid.NewGuid().ToString()
};
private readonly DeploymentSubRequest validSubRequest = new DeploymentSubRequest
{
BicepFilePath = "main.bicep",
Location = "eastus",
SubscriptionNameOrId = Guid.NewGuid().ToString()
};
private DeploymentGroupRequest SetUpGroupRequest(string bicepFilePath, string resourceGroupName, string subscriptionNameOrId)
{
return new DeploymentGroupRequest
{
BicepFilePath = bicepFilePath,
ResourceGroupName = resourceGroupName,
SubscriptionNameOrId = subscriptionNameOrId
};
}
private DeploymentSubRequest SetUpSubRequest(string bicepFilePath, string location, string subscriptionNameOrId)
{
return new DeploymentSubRequest
{
BicepFilePath = bicepFilePath,
Location = location,
SubscriptionNameOrId = subscriptionNameOrId
};
}
private void SetUpSuccessfulTranspilation(string bicepFilePath, string armTemplatePath)
{
bicepTranspileServiceMock.Setup(x => x.BuildAsync(bicepFilePath)).ReturnsAsync(armTemplatePath);
}
private void SetUpExceptionThrowingTranspilation(Exception ex)
{
bicepTranspileServiceMock.Setup(x => x.BuildAsync(It.IsAny<string>())).ThrowsAsync(ex);
}
private void VerifyTranspilation(string bicepFilePath)
{
bicepTranspileServiceMock.Verify(x => x.BuildAsync(bicepFilePath), Times.Once);
}
private void VerifyNoTranspilation()
{
bicepTranspileServiceMock.Verify(x => x.BuildAsync(It.IsAny<string>()), Times.Never);
}
private ArmOperation<ArmDeploymentResource> SetupDeploymentOperation(bool success, string reason)
{
var responseMock = new Mock<Azure.Response>();
responseMock.Setup(x => x.IsError).Returns(!success);
responseMock.Setup(x => x.ReasonPhrase).Returns(reason);
var operationMock = new Mock<ArmOperation<ArmDeploymentResource>>();
operationMock.Setup(x => x.WaitForCompletionResponse(default)).Returns(responseMock.Object);
return operationMock.Object;
}
private void SetUpSuccessfulGroupDeployment(DeploymentGroupRequest request, string templatePath)
{
var operation = SetupDeploymentOperation(true, "OK");
armDeploymentMock.Setup(x => x.DeployArmToResourceGroupAsync(
request.SubscriptionNameOrId,
request.ResourceGroupName,
templatePath,
request.ParameterFilePath,
It.IsAny<Azure.WaitUntil>()))
.ReturnsAsync(operation);
}
private void SetUpFailedGroupDeployment(string reason)
{
var operation = SetupDeploymentOperation(false, reason);
armDeploymentMock.Setup(x => x.DeployArmToResourceGroupAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()))
.ReturnsAsync(operation);
}
private void SetUpExceptionThrowingGroupDeployment(Exception ex)
{
armDeploymentMock.Setup(x => x.DeployArmToResourceGroupAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()))
.ThrowsAsync(ex);
}
private void VerifyGroupDeployment(DeploymentGroupRequest request, string templatePath) {
armDeploymentMock.Verify(x => x.DeployArmToResourceGroupAsync(
request.SubscriptionNameOrId,
request.ResourceGroupName,
templatePath,
request.ParameterFilePath,
It.IsAny<Azure.WaitUntil>()),
Times.Once);
}
private void SetUpSuccessfulSubDeployment(DeploymentSubRequest request, string templatePath)
{
var operation = SetupDeploymentOperation(true, "OK");
armDeploymentMock.Setup(x => x.DeployArmToSubscriptionAsync(
request.SubscriptionNameOrId,
request.Location,
templatePath,
request.ParameterFilePath,
It.IsAny<Azure.WaitUntil>()))
.ReturnsAsync(operation);
}
private void SetUpFailedSubDeployment(string reason)
{
var operation = SetupDeploymentOperation(false, reason);
armDeploymentMock.Setup(x => x.DeployArmToSubscriptionAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()))
.ReturnsAsync(operation);
}
private void SetUpExceptionThrowingSubDeployment(Exception ex)
{
armDeploymentMock.Setup(x => x.DeployArmToSubscriptionAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()))
.ThrowsAsync(ex);
}
private void VerifySubDeployment(DeploymentSubRequest request, string templatePath)
{
armDeploymentMock.Verify(x => x.DeployArmToSubscriptionAsync(
request.SubscriptionNameOrId,
request.Location,
templatePath,
request.ParameterFilePath,
It.IsAny<Azure.WaitUntil>()),
Times.Once);
}
private void VerifyNoDeployments()
{
armDeploymentMock.Verify(x => x.DeployArmToResourceGroupAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()),
Times.Never);
armDeploymentMock.Verify(x => x.DeployArmToSubscriptionAsync(
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<string>(),
It.IsAny<Azure.WaitUntil>()),
Times.Never);
}
}

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

@ -0,0 +1,33 @@
namespace BenchPress.TestEngine.Tests;
public class MockServerCallContext : ServerCallContext
{
protected override string MethodCore => throw new NotImplementedException();
protected override string HostCore => throw new NotImplementedException();
protected override string PeerCore => throw new NotImplementedException();
protected override DateTime DeadlineCore => throw new NotImplementedException();
protected override Metadata RequestHeadersCore => throw new NotImplementedException();
protected override CancellationToken CancellationTokenCore => throw new NotImplementedException();
protected override Metadata ResponseTrailersCore => throw new NotImplementedException();
protected override Status StatusCore { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
protected override WriteOptions WriteOptionsCore { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
protected override AuthContext AuthContextCore => throw new NotImplementedException();
protected override ContextPropagationToken CreatePropagationTokenCore(ContextPropagationOptions options)
{
throw new NotImplementedException();
}
protected override Task WriteResponseHeadersAsyncCore(Metadata responseHeaders)
{
throw new NotImplementedException();
}
}

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

@ -0,0 +1,26 @@
namespace BenchPress.TestEngine.Tests;
public class ResourceGroupServiceTests
{
private readonly ResourceGroupService resourceGroupService;
private readonly ServerCallContext context;
public ResourceGroupServiceTests()
{
var logger = new Mock<ILogger<ResourceGroupService>>().Object;
resourceGroupService = new ResourceGroupService(logger);
context = new MockServerCallContext();
}
[Fact(Skip = "Not Implemented")]
public async Task GetResourceGroup_ResturnsResoureGroup()
{
var request = new ResourceGroupRequest
{
ResourceGroupName = "test-rg",
SubscriptionNameOrId = new Guid().ToString()
};
var result = await resourceGroupService.GetResourceGroup(request, context);
Assert.True(result.Existed);
}
}

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

@ -1,10 +0,0 @@
namespace BenchPress.TestEngine.Tests;
public class UnitTest1
{
[Fact]
public void Test1()
{
}
}

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

@ -1 +1,5 @@
global using Xunit;
global using Xunit;
global using Moq;
global using Grpc.Core;
global using BenchPress.TestEngine.Services;
global using Microsoft.Extensions.Logging;

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

@ -1,12 +1,28 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="..\..\protos\deployment.proto" GrpcServices="Server" />
<Protobuf Include="..\..\protos\resource_group.proto" GrpcServices="Server" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.7.0" />
<PackageReference Include="Azure.ResourceManager" Version="1.3.1" />
<PackageReference Include="Azure.ResourceManager.Resources" Version="1.3.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\BenchPress\bicep\src\Bicep.Cli\Bicep.Cli.csproj" />
<PackageReference Include="Microsoft.Extensions.Azure" Version="1.6.0" />
</ItemGroup>
<ItemGroup>
<GlobalAnalyzerConfigFiles Include="../../.globalconfig" />
</ItemGroup>

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

@ -1,2 +1,36 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
using BenchPress.TestEngine.Services;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.Extensions.Azure;
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
// Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
builder.WebHost.ConfigureKestrel(options =>
{
// Setup a HTTP/2 endpoint without TLS.
options.ListenLocalhost(5152, o => o.Protocols =
HttpProtocols.Http2);
});
// Add services to the container.
builder.Services.AddGrpc();
builder.Services.AddAzureClients(builder => {
builder.AddClient<ArmClient, ArmClientOptions>(options => {
return new ArmClient(new DefaultAzureCredential());
});
});
builder.Services.AddSingleton<IArmDeploymentService, ArmDeploymentService>();
builder.Services.AddSingleton<IBicepTranspileService, BicepTranspileService>();
builder.Services.AddSingleton<IFileService, FileService>();
builder.Services.AddSingleton<IBicepExecute, BicepExecute>();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<DeploymentService>();
app.MapGrpcService<ResourceGroupService>();
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
app.Run();

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

@ -0,0 +1,75 @@
using Azure;
using Azure.ResourceManager.Resources.Models;
namespace BenchPress.TestEngine.Services;
public class ArmDeploymentService : IArmDeploymentService
{
private readonly ArmClient client;
private readonly IFileService fileService;
private string NewDeploymentName { get { return $"benchpress-{Guid.NewGuid().ToString()}"; } }
public ArmDeploymentService(ArmClient client, IFileService fileService)
{
this.client = client;
this.fileService = fileService;
}
public async Task<ArmOperation<ArmDeploymentResource>> DeployArmToResourceGroupAsync(string subscriptionNameOrId, string resourceGroupName, string armTemplatePath, string? parametersPath = null, WaitUntil waitUntil = WaitUntil.Completed)
{
ValidateParameters(subscriptionNameOrId, resourceGroupName, armTemplatePath);
SubscriptionResource sub = await client.GetSubscriptions().GetAsync(subscriptionNameOrId);
ResourceGroupResource rg = await sub.GetResourceGroups().GetAsync(resourceGroupName);
var deploymentContent = await CreateDeploymentContent(armTemplatePath, parametersPath);
return await CreateGroupDeployment(rg, waitUntil, NewDeploymentName, deploymentContent);
}
public async Task<ArmOperation<ArmDeploymentResource>> DeployArmToSubscriptionAsync(string subscriptionNameOrId, string location, string armTemplatePath, string? parametersPath = null, WaitUntil waitUtil = WaitUntil.Completed)
{
ValidateParameters(subscriptionNameOrId, location, armTemplatePath);
SubscriptionResource sub = await client.GetSubscriptions().GetAsync(subscriptionNameOrId);
var deploymentContent = await CreateDeploymentContent(armTemplatePath, parametersPath, location);
return await CreateSubscriptionDeployment(sub, waitUtil, NewDeploymentName, deploymentContent);
}
private void ValidateParameters(params string[] parameters)
{
if (parameters.Any(s => string.IsNullOrWhiteSpace(s)))
{
throw new ArgumentException("One or more parameters were missing or empty");
}
}
private async Task<ArmDeploymentContent> CreateDeploymentContent(string armTemplatePath, string? parametersPath, string? location = null) {
var templateContent = (await fileService.ReadAllTextAsync(armTemplatePath)).TrimEnd();
var properties = new ArmDeploymentProperties(ArmDeploymentMode.Incremental)
{
Template = BinaryData.FromString(templateContent)
};
if (!string.IsNullOrWhiteSpace(parametersPath))
{
var parametersContent = (await fileService.ReadAllTextAsync(parametersPath)).TrimEnd();
properties.Parameters = BinaryData.FromString(parametersContent);
}
var content = new ArmDeploymentContent(properties);
if (!string.IsNullOrWhiteSpace(location))
{
content.Location = location;
}
return content;
}
// These extension methods are wrapped to allow mocking in our tests
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateGroupDeployment(ResourceGroupResource rg, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
{
return await rg.GetArmDeployments().CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
}
protected virtual async Task<ArmOperation<ArmDeploymentResource>> CreateSubscriptionDeployment(SubscriptionResource sub, Azure.WaitUntil waitUntil, string deploymentName, ArmDeploymentContent deploymentContent)
{
return await sub.GetArmDeployments().CreateOrUpdateAsync(waitUntil, deploymentName, deploymentContent);
}
}

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

@ -0,0 +1,11 @@
using Bicep.Cli;
namespace BenchPress.TestEngine.Services;
public class BicepExecute : IBicepExecute
{
public Task<int> ExecuteCommandAsync(string[] args)
{
return Bicep.Cli.Program.Main(args);
}
}

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

@ -0,0 +1,63 @@
using System.IO;
namespace BenchPress.TestEngine.Services;
public class BicepTranspileService : IBicepTranspileService
{
private IBicepExecute bicepExecute;
private IFileService fileService;
private readonly ILogger<BicepTranspileService> logger;
public BicepTranspileService(IBicepExecute bicepExecute, ILogger<BicepTranspileService> logger, IFileService fileService)
{
this.bicepExecute = bicepExecute;
this.logger = logger;
this.fileService = fileService;
}
public async Task<string> BuildAsync(string inputPath)
{
if (string.IsNullOrWhiteSpace(inputPath))
{
throw new ArgumentNullException(nameof(inputPath));
}
if(!fileService.FileExists(inputPath))
{
throw new FileNotFoundException(nameof(inputPath));
}
var extension = fileService.GetFileExtension(inputPath);
if (extension != ".bicep")
{
throw new ArgumentException("Passed file is not a bicep file. File path: " + inputPath);
}
inputPath = fileService.GetFileFullPath(inputPath);
string outputPath = fileService.ChangeFileExtension(inputPath, ".json");
logger.LogInformation("Invoking Bicep Submodule");
try
{
var result = await bicepExecute.ExecuteCommandAsync(new string[]{
"build",
inputPath,
"--outfile",
outputPath
});
if (result != 0)
{
throw new ApplicationException("Bicep transpilation failed");
}
}
catch (Exception ex)
{
logger.LogError(ex.Message, ex);
throw;
}
return outputPath;
}
}

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

@ -0,0 +1,91 @@
namespace BenchPress.TestEngine.Services;
public class DeploymentService : Deployment.DeploymentBase
{
private readonly ILogger<DeploymentService> logger;
private readonly IBicepTranspileService bicepTranspileService;
private readonly IArmDeploymentService armDeploymentService;
public DeploymentService(ILogger<DeploymentService> logger, IBicepTranspileService bicepTranspileService, IArmDeploymentService armDeploymentService)
{
this.logger = logger;
this.bicepTranspileService = bicepTranspileService;
this.armDeploymentService = armDeploymentService;
}
public override async Task<DeploymentResult> DeploymentGroupCreate(DeploymentGroupRequest request, ServerCallContext context)
{
if (string.IsNullOrWhiteSpace(request.BicepFilePath)
|| string.IsNullOrWhiteSpace(request.ResourceGroupName)
|| string.IsNullOrWhiteSpace(request.SubscriptionNameOrId))
{
return new DeploymentResult
{
Success = false,
ErrorMessage = $"One or more of the following required parameters was missing: {nameof(request.BicepFilePath)}, {nameof(request.ResourceGroupName)}, and {nameof(request.SubscriptionNameOrId)}"
};
}
try
{
var armTemplatePath = await bicepTranspileService.BuildAsync(request.BicepFilePath);
var deployment = await armDeploymentService.DeployArmToResourceGroupAsync(request.SubscriptionNameOrId, request.ResourceGroupName, armTemplatePath, request.ParameterFilePath);
var response = deployment.WaitForCompletionResponse();
return new DeploymentResult
{
Success = !response.IsError,
ErrorMessage = response.ReasonPhrase
};
}
catch (Exception ex)
{
return new DeploymentResult
{
Success = false,
ErrorMessage = ex.Message
};
}
}
public override async Task<DeploymentResult> DeploymentSubCreate(DeploymentSubRequest request, ServerCallContext context)
{
if (string.IsNullOrWhiteSpace(request.BicepFilePath)
|| string.IsNullOrWhiteSpace(request.Location)
|| string.IsNullOrWhiteSpace(request.SubscriptionNameOrId))
{
return new DeploymentResult
{
Success = false,
ErrorMessage = $"One or more of the following required parameters was missing: {nameof(request.BicepFilePath)}, {nameof(request.Location)}, and {nameof(request.SubscriptionNameOrId)}"
};
}
try
{
var armTemplatePath = await bicepTranspileService.BuildAsync(request.BicepFilePath);
var deployment = await armDeploymentService.DeployArmToSubscriptionAsync(request.SubscriptionNameOrId, request.Location, armTemplatePath, request.ParameterFilePath);
var response = deployment.WaitForCompletionResponse();
return new DeploymentResult
{
Success = !response.IsError,
ErrorMessage = response.ReasonPhrase
};
}
catch (Exception ex)
{
return new DeploymentResult
{
Success = false,
ErrorMessage = ex.Message
};
}
}
public override async Task<DeploymentResult> DeleteGroup(DeleteGroupRequest request, ServerCallContext context)
{
throw new NotImplementedException();
}
}

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

@ -0,0 +1,26 @@
namespace BenchPress.TestEngine.Services;
public class FileService : IFileService
{
public bool FileExists(string filePath)
{
FileInfo file = new FileInfo(filePath);
return file.Exists;
}
public string GetFileFullPath(string filePath)
{
return Path.GetFullPath(filePath);
}
public string GetFileExtension(string filePath)
{
return Path.GetExtension(filePath);
}
public string ChangeFileExtension(string filePath, string extension)
{
return Path.ChangeExtension(filePath, extension);
}
public async Task<string> ReadAllTextAsync(string filePath)
{
return await File.ReadAllTextAsync(filePath);
}
}

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

@ -0,0 +1,6 @@
namespace BenchPress.TestEngine.Services;
public interface IArmDeploymentService {
Task<ArmOperation<ArmDeploymentResource>> DeployArmToResourceGroupAsync(string subscriptionNameOrId, string resourceGroupName, string armTemplatePath, string? parametersPath = null, Azure.WaitUntil waitUtil = Azure.WaitUntil.Completed);
Task<ArmOperation<ArmDeploymentResource>> DeployArmToSubscriptionAsync(string subscriptionNameOrId, string location, string armTemplatePath, string? parametersPath = null, Azure.WaitUntil waitUtil = Azure.WaitUntil.Completed);
}

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

@ -0,0 +1,6 @@
namespace BenchPress.TestEngine.Services;
public interface IBicepExecute
{
Task<int> ExecuteCommandAsync(string[] args);
}

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

@ -0,0 +1,6 @@
namespace BenchPress.TestEngine.Services;
public interface IBicepTranspileService
{
Task<string> BuildAsync(string inputPath);
}

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

@ -0,0 +1,10 @@
namespace BenchPress.TestEngine.Services;
public interface IFileService
{
public bool FileExists(string filePath);
string GetFileFullPath(string filePath);
string ChangeFileExtension(string filePath, string extension);
string GetFileExtension(string filePath);
Task<string> ReadAllTextAsync(string filePath);
}

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

@ -0,0 +1,16 @@
namespace BenchPress.TestEngine.Services;
public class ResourceGroupService : ResourceGroup.ResourceGroupBase
{
private readonly ILogger<ResourceGroupService> logger;
public ResourceGroupService(ILogger<ResourceGroupService> logger)
{
this.logger = logger;
}
public override async Task<ResourceGroupResponse> GetResourceGroup(ResourceGroupRequest rg, ServerCallContext context)
{
throw new NotImplementedException();
}
}

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

@ -0,0 +1,4 @@
global using Grpc.Core;
global using Azure.ResourceManager;
global using Azure.ResourceManager.Resources;
global using BenchPress.TestEngine;

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

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

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

@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
}

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

@ -11,4 +11,18 @@
<GlobalAnalyzerConfigFiles Include="../../../.globalconfig" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="../../../protos/deployment.proto" GrpcServices="Client" />
<Protobuf Include="../../../protos/resource_group.proto" GrpcServices="Client" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.21.6" />
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
<PackageReference Include="Grpc.Tools" Version="2.49.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

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

@ -1,2 +1 @@
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");
Console.WriteLine("Hello World!");

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

@ -13,6 +13,7 @@ zip_safe = True
include_package_data = True
install_requires =
protobuf == 4.21.9
grpcio
pytest
[options.packages.find]

37
protos/deployment.proto Normal file
Просмотреть файл

@ -0,0 +1,37 @@
syntax = "proto3";
package benchpress;
option csharp_namespace = "BenchPress.TestEngine";
// Currently only supports deployments with the target scope of resource group.
// Other scopes: subscription, management group, and tenant.
service Deployment {
rpc DeploymentGroupCreate (DeploymentGroupRequest) returns (DeploymentResult);
rpc DeploymentSubCreate (DeploymentSubRequest) returns (DeploymentResult);
rpc DeleteGroup (DeleteGroupRequest) returns (DeploymentResult);
}
message DeploymentGroupRequest {
string bicep_file_path = 1;
string parameter_file_path = 2;
string resource_group_name = 3;
string subscription_name_or_id = 4;
}
message DeploymentSubRequest {
string bicep_file_path = 1;
string parameter_file_path = 2;
string location = 3;
string subscription_name_or_id = 4;
}
message DeleteGroupRequest {
string resource_group_name = 1;
string subscription_name_or_id = 2;
}
message DeploymentResult {
bool success = 1;
string error_message = 2;
}

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

@ -0,0 +1,19 @@
syntax = "proto3";
package benchpress;
option csharp_namespace = "BenchPress.TestEngine";
service ResourceGroup {
rpc GetResourceGroup (ResourceGroupRequest) returns (ResourceGroupResponse);
}
message ResourceGroupRequest {
string resource_group_name = 1;
string subscription_name_or_id = 2;
}
message ResourceGroupResponse {
bool existed = 1;
string resource_json = 2;
}

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

@ -0,0 +1,9 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"value": "jern"
}
}
}

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

@ -0,0 +1,17 @@
targetScope = 'subscription'
param resourceGroupName string
param location string
param environment string
// https://docs.microsoft.com/en-us/azure/templates/microsoft.resources/resourcegroups?tabs=bicep
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: resourceGroupName
location: location
tags: {
EnvironmentName: environment
}
}
output name string = resourceGroup.name
output id string = resourceGroup.id

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

@ -0,0 +1,62 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"type": "string",
"minLength": 3,
"maxLength": 11,
"metadata": {
"description": "Specify a project name that is used to generate resource names."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Specify a location for the resources."
}
},
"storageSKU": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_RAGRS",
"Standard_ZRS",
"Premium_LRS",
"Premium_ZRS",
"Standard_GZRS",
"Standard_RAGZRS"
],
"metadata": {
"description": "Specify the storage account type."
}
}
},
"variables": {
"storageAccountName": "[concat(parameters('projectName'), uniqueString(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageSKU')]"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
],
"outputs": {
"storageEndpoint": {
"type": "object",
"value": "[reference(variables('storageAccountName')).primaryEndpoints]"
}
}
}

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

@ -0,0 +1,63 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"type": "string",
"defaultValue": "jern",
"minLength": 3,
"maxLength": 11,
"metadata": {
"description": "Specify a project name that is used to generate resource names."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Specify a location for the resources."
}
},
"storageSKU": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_RAGRS",
"Standard_ZRS",
"Premium_LRS",
"Premium_ZRS",
"Standard_GZRS",
"Standard_RAGZRS"
],
"metadata": {
"description": "Specify the storage account type."
}
}
},
"variables": {
"storageAccountName": "[concat(parameters('projectName'), uniqueString(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2021-04-01",
"name": "[variables('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "[parameters('storageSKU')]"
},
"kind": "StorageV2",
"properties": {
"supportsHttpsTrafficOnly": true
}
}
],
"outputs": {
"storageEndpoint": {
"type": "object",
"value": "[reference(variables('storageAccountName')).primaryEndpoints]"
}
}
}

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

@ -0,0 +1,14 @@
param name string
param location string
resource sa 'Microsoft.Storage/storageAccounts@2019-06-01' = {
name: name
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
}
}

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

@ -0,0 +1,17 @@
using Grpc.Net.Client;
using BenchPress.TestEngine;
using var channel = GrpcChannel.ForAddress("http://localhost:5152");
var client = new Deployment.DeploymentClient(channel);
var request = new DeploymentSubRequest {
BicepFilePath = "<path to your bicep file>",
ParameterFilePath = "<path to your params file, if needed>",
Location = "eastus",
SubscriptionNameOrId = "<your subscription id>"
};
var result = await client.DeploymentSubCreateAsync(request);
Console.WriteLine($"Success? {result.Success}");
Console.WriteLine(result.ErrorMessage);

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

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="../../../protos/deployment.proto" GrpcServices="Client" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.21.6" />
<PackageReference Include="Grpc.Net.Client" Version="2.49.0" />
<PackageReference Include="Grpc.Tools" Version="2.49.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

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

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: deployment.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10\x64\x65ployment.proto\x12\nbenchpress\"\x8c\x01\n\x16\x44\x65ploymentGroupRequest\x12\x17\n\x0f\x62icep_file_path\x18\x01 \x01(\t\x12\x1b\n\x13parameter_file_path\x18\x02 \x01(\t\x12\x1b\n\x13resource_group_name\x18\x03 \x01(\t\x12\x1f\n\x17subscription_name_or_id\x18\x04 \x01(\t\"R\n\x12\x44\x65leteGroupRequest\x12\x1b\n\x13resource_group_name\x18\x01 \x01(\t\x12\x1f\n\x17subscription_name_or_id\x18\x02 \x01(\t\":\n\x10\x44\x65ploymentResult\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\x15\n\rerror_message\x18\x02 \x01(\t2\xb4\x01\n\nDeployment\x12Y\n\x15\x44\x65ploymentGroupCreate\x12\".benchpress.DeploymentGroupRequest\x1a\x1c.benchpress.DeploymentResult\x12K\n\x0b\x44\x65leteGroup\x12\x1e.benchpress.DeleteGroupRequest\x1a\x1c.benchpress.DeploymentResultB\x18\xaa\x02\x15\x42\x65nchPress.TestEngineb\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'deployment_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\252\002\025BenchPress.TestEngine'
_DEPLOYMENTGROUPREQUEST._serialized_start=33
_DEPLOYMENTGROUPREQUEST._serialized_end=173
_DELETEGROUPREQUEST._serialized_start=175
_DELETEGROUPREQUEST._serialized_end=257
_DEPLOYMENTRESULT._serialized_start=259
_DEPLOYMENTRESULT._serialized_end=317
_DEPLOYMENT._serialized_start=320
_DEPLOYMENT._serialized_end=500
# @@protoc_insertion_point(module_scope)

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

@ -0,0 +1,105 @@
# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
"""Client and server classes corresponding to protobuf-defined services."""
import grpc
import deployment_pb2 as deployment__pb2
class DeploymentStub(object):
"""Currently only supports deployments with the target scope of resource group.
Other scopes: subscription, management group, and tenant.
"""
def __init__(self, channel):
"""Constructor.
Args:
channel: A grpc.Channel.
"""
self.DeploymentGroupCreate = channel.unary_unary(
'/benchpress.Deployment/DeploymentGroupCreate',
request_serializer=deployment__pb2.DeploymentGroupRequest.SerializeToString,
response_deserializer=deployment__pb2.DeploymentResult.FromString,
)
self.DeleteGroup = channel.unary_unary(
'/benchpress.Deployment/DeleteGroup',
request_serializer=deployment__pb2.DeleteGroupRequest.SerializeToString,
response_deserializer=deployment__pb2.DeploymentResult.FromString,
)
class DeploymentServicer(object):
"""Currently only supports deployments with the target scope of resource group.
Other scopes: subscription, management group, and tenant.
"""
def DeploymentGroupCreate(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def DeleteGroup(self, request, context):
"""Missing associated documentation comment in .proto file."""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_DeploymentServicer_to_server(servicer, server):
rpc_method_handlers = {
'DeploymentGroupCreate': grpc.unary_unary_rpc_method_handler(
servicer.DeploymentGroupCreate,
request_deserializer=deployment__pb2.DeploymentGroupRequest.FromString,
response_serializer=deployment__pb2.DeploymentResult.SerializeToString,
),
'DeleteGroup': grpc.unary_unary_rpc_method_handler(
servicer.DeleteGroup,
request_deserializer=deployment__pb2.DeleteGroupRequest.FromString,
response_serializer=deployment__pb2.DeploymentResult.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'benchpress.Deployment', rpc_method_handlers)
server.add_generic_rpc_handlers((generic_handler,))
# This class is part of an EXPERIMENTAL API.
class Deployment(object):
"""Currently only supports deployments with the target scope of resource group.
Other scopes: subscription, management group, and tenant.
"""
@staticmethod
def DeploymentGroupCreate(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/benchpress.Deployment/DeploymentGroupCreate',
deployment__pb2.DeploymentGroupRequest.SerializeToString,
deployment__pb2.DeploymentResult.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
@staticmethod
def DeleteGroup(request,
target,
options=(),
channel_credentials=None,
call_credentials=None,
insecure=False,
compression=None,
wait_for_ready=None,
timeout=None,
metadata=None):
return grpc.experimental.unary_unary(request, target, '/benchpress.Deployment/DeleteGroup',
deployment__pb2.DeleteGroupRequest.SerializeToString,
deployment__pb2.DeploymentResult.FromString,
options, channel_credentials,
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)

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

@ -0,0 +1,41 @@
# Copyright 2015 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""The Python implementation of the GRPC helloworld.Greeter client."""
from __future__ import print_function
import logging
import grpc
import deployment_pb2
import deployment_pb2_grpc
def run():
print("Will try to greet world ...")
with grpc.insecure_channel('localhost:5152') as channel:
stub = deployment_pb2_grpc.DeploymentStub(channel)
req = deployment_pb2.DeploymentGroupRequest(
bicep_file_path = '/Users/jessicaern/Projects/benchpress-private/engine/BenchPress.TestEngine.Tests/SampleFiles/storageAccount.bicep',
resource_group_name = 'jern-benchpress-playground',
subscription_name_or_id = '519c3e33-0884-4604-bad7-6964e6ef55f8'
)
response = stub.DeploymentGroupCreate(req)
print("Success? " + response.success)
print(response.error_message)
if __name__ == '__main__':
logging.basicConfig()
run()