From bc011ec1699d01606854d3e034c309b72f925af7 Mon Sep 17 00:00:00 2001 From: Alexander Sehr Date: Thu, 9 Nov 2023 23:15:25 +0100 Subject: [PATCH 1/2] fix: Updated deployment name generation (#593) ## Description Updated deployment name generation to work with avm folder Convert, e.g., - `C:\myFork\avm\res\kubernetes-configuration\flux-configuration\tests\e2e\defaults\main.test.bicep` to - `a-r-kc-fc-defaults` ![image](https://github.com/Azure/bicep-registry-modules/assets/5365358/f14a850d-da2c-4f36-90e4-c248b1024080) | Pipeline | | - | | [![avm.res.key-vault.vault](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml/badge.svg?branch=users%2Falsehr%2FdeploymentName)](https://github.com/AlexanderSehr/bicep-registry-modules/actions/workflows/avm.res.key-vault.vault.yml) | --------- Co-authored-by: Erika Gressi <56914614+eriqua@users.noreply.github.com> --- .../avm-validateModuleDeployment/action.yml | 2 + .../New-TemplateDeployment.ps1 | 67 ++++++++++--------- .../Test-TemplateDeployment.ps1 | 13 ++-- avm/utilities/tools/Test-ModuleLocally.ps1 | 19 ++---- 4 files changed, 54 insertions(+), 47 deletions(-) diff --git a/.github/actions/templates/avm-validateModuleDeployment/action.yml b/.github/actions/templates/avm-validateModuleDeployment/action.yml index b785322ff6..09cf3b70ee 100644 --- a/.github/actions/templates/avm-validateModuleDeployment/action.yml +++ b/.github/actions/templates/avm-validateModuleDeployment/action.yml @@ -163,6 +163,7 @@ runs: SubscriptionId = $subscriptionId ManagementGroupId = $managementGroupId AdditionalParameters = @{} + RepoRoot = $env:GITHUB_WORKSPACE } Write-Verbose 'Invoke task with' -Verbose @@ -212,6 +213,7 @@ runs: SubscriptionId = $subscriptionId ManagementGroupId = $managementGroupId DoNotThrow = $true + RepoRoot = $env:GITHUB_WORKSPACE AdditionalParameters = @{} } diff --git a/avm/utilities/pipelines/e2eValidation/resourceDeployment/New-TemplateDeployment.ps1 b/avm/utilities/pipelines/e2eValidation/resourceDeployment/New-TemplateDeployment.ps1 index 8b6db377ae..52871b73a0 100644 --- a/avm/utilities/pipelines/e2eValidation/resourceDeployment/New-TemplateDeployment.ps1 +++ b/avm/utilities/pipelines/e2eValidation/resourceDeployment/New-TemplateDeployment.ps1 @@ -101,6 +101,9 @@ Optional. Maximum retry limit if the deployment fails. Default is 3. .PARAMETER doNotThrow Optional. Do not throw an exception if it failed. Still returns the error message though +.PARAMETER RepoRoot +Mandatory. Path to the root of the repository. + .EXAMPLE New-TemplateDeploymentInner -templateFilePath 'C:/key-vault/vault/main.json' -parameterFilePath 'C:/key-vault/vault/.test/parameters.json' -location 'WestEurope' -resourceGroupName 'aLegendaryRg' @@ -148,14 +151,14 @@ function New-TemplateDeploymentInner { [switch] $doNotThrow, [Parameter(Mandatory = $false)] - [int]$retryLimit = 3 + [int]$retryLimit = 3, + + [Parameter(Mandatory = $false)] + [string] $RepoRoot ) begin { Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) - - # Load helper - . (Join-Path (Get-Item -Path $PSScriptRoot).parent.parent.FullName 'sharedScripts' 'Get-ScopeOfTemplateFile.ps1') } process { @@ -164,20 +167,19 @@ function New-TemplateDeploymentInner { $deploymentNamePrefix = 'templateDeployment-{0}' -f (Split-Path $templateFilePath -LeafBase) } - $modulesRegex = '.+[\\|\/]modules[\\|\/]' - if ($templateFilePath -match $modulesRegex) { - # If we can assume we're operating in a module structure, we can further fetch the provider namespace & resource type - $shortPathElem = (($templateFilePath -split $modulesRegex)[1] -replace '\\', '/') -split '/' # e.g., app-configuration, configuration-store, .test, common, main.test.bicep - $providerNamespace = $shortPathElem[0] # e.g., app-configuration - $providerNamespaceShort = ($providerNamespace -split '-' | ForEach-Object { $_[0] }) -join '' # e.g., ac - - $resourceType = $shortPathElem[1] # e.g., configuration-store - $resourceTypeShort = ($resourceType -split '-' | ForEach-Object { $_[0] }) -join '' # e.g. cs - - $testFolderShort = Split-Path (Split-Path $templateFilePath -Parent) -Leaf # e.g., common - - $deploymentNamePrefix = "$providerNamespaceShort-$resourceTypeShort-$testFolderShort" # e.g., ac-cs-common + # Convert, e.g., [C:\myFork\avm\res\kubernetes-configuration\flux-configuration\tests\e2e\defaults\main.test.bicep] to [a-r-kc-fc-defaults] + $shortPathElems = ((Split-Path $templateFilePath) -replace ('{0}[\\|\/]' -f [regex]::Escape($repoRoot))) -split '[\\|\/]' | Where-Object { $_ -notin @('tests', 'e2e') } + # Shorten all elements but the last + $reducedElem = $shortPathElems[0 .. ($shortPathElems.Count - 2)] | ForEach-Object { + $shortPathElem = $_ + if ($shortPathElem -match '-') { + ($shortPathElem -split '-' | ForEach-Object { $_[0] }) -join '' + } else { + $shortPathElem[0] + } } + # Add the last back and join the elements together + $deploymentNamePrefix = ($reducedElem + @($shortPathElems[-1])) -join '-' $DeploymentInputs = @{ TemplateFile = $templateFilePath @@ -288,8 +290,7 @@ function New-TemplateDeploymentInner { throw "Deployed failed with provisioning state [Failed]. Error Message: [$exceptionMessage]. Please review the Azure logs of deployment [$deploymentName] in scope [$deploymentScope] for further details." } $Stoploop = $true - } - catch { + } catch { if ($retryCount -ge $retryLimit) { if ($doNotThrow) { @@ -301,8 +302,7 @@ function New-TemplateDeploymentInner { ResourceGroupName = $resourceGroupName } $exceptionMessage = Get-ErrorMessageForScope @errorInputObject - } - else { + } else { $exceptionMessage = $PSitem.Exception.Message } @@ -310,13 +310,11 @@ function New-TemplateDeploymentInner { DeploymentNames = $usedDeploymentNames Exception = $exceptionMessage } - } - else { + } else { throw $PSitem.Exception.Message } $Stoploop = $true - } - else { + } else { Write-Verbose "Resource deployment Failed.. ($retryCount/$retryLimit) Retrying in 5 Seconds.. `n" Write-Verbose ($PSitem.Exception.Message | Out-String) -Verbose Start-Sleep -Seconds 5 @@ -379,6 +377,9 @@ Optional. Maximum retry limit if the deployment fails. Default is 3. .PARAMETER doNotThrow Optional. Do not throw an exception if it failed. Still returns the error message though +.PARAMETER RepoRoot +Optional. Path to the root of the repository. + .EXAMPLE New-TemplateDeployment -templateFilePath 'C:/key-vault/vault/main.bicep' -parameterFilePath 'C:/key-vault/vault/.test/parameters.json' -location 'WestEurope' -resourceGroupName 'aLegendaryRg' @@ -426,11 +427,17 @@ function New-TemplateDeployment { [switch] $doNotThrow, [Parameter(Mandatory = $false)] - [int]$retryLimit = 3 + [int]$retryLimit = 3, + + [Parameter(Mandatory = $false)] + [string] $RepoRoot = (Get-Item -Path $PSScriptRoot).parent.parent.parent.parent.parent.FullName ) begin { Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) + + # Load helper functions + . (Join-Path $repoRoot 'avm' 'utilities' 'pipelines' 'sharedScripts' 'Get-ScopeOfTemplateFile.ps1') } process { @@ -462,14 +469,12 @@ function New-TemplateDeployment { } } return $deploymentResult - } - else { + } else { if ($PSCmdlet.ShouldProcess("Deployment for single parameter file [$parameterFilePath]", 'Trigger')) { return New-TemplateDeploymentInner @deploymentInputObject -parameterFilePath $parameterFilePath } } - } - else { + } else { if ($PSCmdlet.ShouldProcess('Deployment without parameter file', 'Trigger')) { return New-TemplateDeploymentInner @deploymentInputObject } @@ -479,4 +484,4 @@ function New-TemplateDeployment { end { Write-Debug ('{0} exited' -f $MyInvocation.MyCommand) } -} +} \ No newline at end of file diff --git a/avm/utilities/pipelines/e2eValidation/resourceDeployment/Test-TemplateDeployment.ps1 b/avm/utilities/pipelines/e2eValidation/resourceDeployment/Test-TemplateDeployment.ps1 index 561fe424db..581d9f6da9 100644 --- a/avm/utilities/pipelines/e2eValidation/resourceDeployment/Test-TemplateDeployment.ps1 +++ b/avm/utilities/pipelines/e2eValidation/resourceDeployment/Test-TemplateDeployment.ps1 @@ -30,6 +30,9 @@ Optional. Name of the management group to deploy into. Mandatory if deploying in .PARAMETER additionalParameters Optional. Additional parameters you can provide with the deployment. E.g. @{ resourceGroupName = 'myResourceGroup' } +.PARAMETER RepoRoot +Optional. Path to the root of the repository. + .EXAMPLE Test-TemplateDeployment -templateFilePath 'C:/key-vault/vault/main.bicep' -parameterFilePath 'C:/key-vault/vault/.test/parameters.json' -location 'WestEurope' -resourceGroupName 'aLegendaryRg' @@ -68,14 +71,17 @@ function Test-TemplateDeployment { [string] $managementGroupId, [Parameter(Mandatory = $false)] - [Hashtable] $additionalParameters + [Hashtable] $additionalParameters, + + [Parameter(Mandatory = $false)] + [string] $RepoRoot = (Get-Item -Path $PSScriptRoot).parent.parent.parent.parent.parent.FullName ) begin { Write-Debug ('{0} entered' -f $MyInvocation.MyCommand) # Load helper - . (Join-Path (Get-Item -Path $PSScriptRoot).parent.parent.FullName 'sharedScripts' 'Get-ScopeOfTemplateFile.ps1') + . (Join-Path $RepoRoot 'avm' 'utilities' 'pipelines' 'sharedScripts' 'Get-ScopeOfTemplateFile.ps1') } process { @@ -174,8 +180,7 @@ function Test-TemplateDeployment { if ($res.Details) { Write-Warning ($res.Details | ConvertTo-Json -Depth 10 | Out-String) } if ($res.Message) { Write-Warning $res.Message } Write-Error 'Template is not valid.' - } - else { + } else { Write-Verbose 'Template is valid' -Verbose } } diff --git a/avm/utilities/tools/Test-ModuleLocally.ps1 b/avm/utilities/tools/Test-ModuleLocally.ps1 index fe45024f36..7d23be4b67 100644 --- a/avm/utilities/tools/Test-ModuleLocally.ps1 +++ b/avm/utilities/tools/Test-ModuleLocally.ps1 @@ -190,8 +190,7 @@ function Test-ModuleLocally { # ------------------------- if ((Get-Item -Path $ModuleTestFilePath) -is [System.IO.DirectoryInfo]) { $moduleTestFiles = (Get-ChildItem -Path $ModuleTestFilePath -File).FullName - } - else { + } else { $moduleTestFiles = @($ModuleTestFilePath) } @@ -239,8 +238,7 @@ function Test-ModuleLocally { Verbosity = 'Detailed' } } - } - catch { + } catch { $PSItem.Exception.Message } } @@ -263,6 +261,7 @@ function Test-ModuleLocally { subscriptionId = $ValidateOrDeployParameters.SubscriptionId managementGroupId = $ValidateOrDeployParameters.ManagementGroupId additionalParameters = $additionalParameters + RepoRoot = Split-Path (Split-Path (Split-Path $PSScriptRoot)) Verbose = $true } @@ -275,8 +274,7 @@ function Test-ModuleLocally { Write-Verbose ('Validating module [{0}] with test file [{1}]' -f $ModuleName, (Split-Path $moduleTestFile -Leaf)) -Verbose if ((Split-Path $moduleTestFile -Extension) -eq '.json') { Test-TemplateDeployment @functionInput -ParameterFilePath $moduleTestFile - } - else { + } else { $functionInput['TemplateFilePath'] = $moduleTestFile Test-TemplateDeployment @functionInput } @@ -294,8 +292,7 @@ function Test-ModuleLocally { if ($PSCmdlet.ShouldProcess(('Module [{0}] with test file [{1}]' -f $ModuleName, (Split-Path $moduleTestFile -Leaf)), 'Deploy')) { New-TemplateDeployment @functionInput -ParameterFilePath $moduleTestFile } - } - else { + } else { $functionInput['TemplateFilePath'] = $moduleTestFile if ($PSCmdlet.ShouldProcess(('Module [{0}] with test file [{1}]' -f $ModuleName, (Split-Path $moduleTestFile -Leaf)), 'Deploy')) { New-TemplateDeployment @functionInput @@ -304,11 +301,9 @@ function Test-ModuleLocally { } } - } - catch { + } catch { Write-Error $_ - } - finally { + } finally { # Restore test files # ------------------ if (($ValidationTest -or $DeploymentTest) -and $ValidateOrDeployParameters) { From 6771210aa627d542db52e52e39b960b405c9f1e6 Mon Sep 17 00:00:00 2001 From: Kris Baranek Date: Thu, 9 Nov 2023 23:47:53 +0100 Subject: [PATCH 2/2] feat: New Module `avm/res/operations-management/solution` (#610) ## Description New Module `avm/res/operations-management/solution`, migrated from CARML. ## Adding a new module - [ ] A proposal has been submitted and approved. - [ ] I have included "Closes #{module_proposal_issue_number}" in the PR description. - [x] I have run `brm validate` locally to verify the module files. - [x] I have run deployment tests locally to ensure the module is deployable. ### Pipeline references | Pipeline | | - | | [![avm.res.operations-management.solution](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.operations-management.solution.yml/badge.svg?branch=krbar-solution-module)](https://github.com/krbar/bicep-registry-modules/actions/workflows/avm.res.operations-management.solution.yml) | --- ...avm.res.operations-management.solution.yml | 81 +++++ .../operations-management/solution/README.md | 335 ++++++++++++++++++ .../operations-management/solution/main.bicep | 73 ++++ .../operations-management/solution/main.json | 127 +++++++ .../tests/e2e/defaults/dependencies.bicep | 13 + .../tests/e2e/defaults/main.test.bicep | 55 +++ .../solution/tests/e2e/ms/dependencies.bicep | 13 + .../solution/tests/e2e/ms/main.test.bicep | 57 +++ .../tests/e2e/nonms/dependencies.bicep | 13 + .../solution/tests/e2e/nonms/main.test.bicep | 57 +++ .../tests/e2e/waf-aligned/dependencies.bicep | 13 + .../tests/e2e/waf-aligned/main.test.bicep | 58 +++ .../solution/version.json | 7 + 13 files changed, 902 insertions(+) create mode 100644 .github/workflows/avm.res.operations-management.solution.yml create mode 100644 avm/res/operations-management/solution/README.md create mode 100644 avm/res/operations-management/solution/main.bicep create mode 100644 avm/res/operations-management/solution/main.json create mode 100644 avm/res/operations-management/solution/tests/e2e/defaults/dependencies.bicep create mode 100644 avm/res/operations-management/solution/tests/e2e/defaults/main.test.bicep create mode 100644 avm/res/operations-management/solution/tests/e2e/ms/dependencies.bicep create mode 100644 avm/res/operations-management/solution/tests/e2e/ms/main.test.bicep create mode 100644 avm/res/operations-management/solution/tests/e2e/nonms/dependencies.bicep create mode 100644 avm/res/operations-management/solution/tests/e2e/nonms/main.test.bicep create mode 100644 avm/res/operations-management/solution/tests/e2e/waf-aligned/dependencies.bicep create mode 100644 avm/res/operations-management/solution/tests/e2e/waf-aligned/main.test.bicep create mode 100644 avm/res/operations-management/solution/version.json diff --git a/.github/workflows/avm.res.operations-management.solution.yml b/.github/workflows/avm.res.operations-management.solution.yml new file mode 100644 index 0000000000..6378665a69 --- /dev/null +++ b/.github/workflows/avm.res.operations-management.solution.yml @@ -0,0 +1,81 @@ +name: "avm.res.operations-management.solution" + +on: + schedule: + - cron: "0 12 1/15 * *" # Bi-Weekly Test (on 1st & 15th of month) + workflow_dispatch: + inputs: + staticValidation: + type: boolean + description: "Execute static validation" + required: false + default: true + deploymentValidation: + type: boolean + description: "Execute deployment validation" + required: false + default: true + removeDeployment: + type: boolean + description: "Remove deployed module" + required: false + default: true + + push: + branches: + - main + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.res.operations-management.solution.yml" + - "avm/res/operations-management/solution/**" + - "avm/utilities/pipelines/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/operations-management/solution" + workflowPath: ".github/workflows/avm.res.operations-management.solution.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-20.04 + name: "Initialize pipeline" + steps: + - name: "Checkout" + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: "Set input parameters to output variables" + id: get-workflow-param + uses: ./.github/actions/templates/avm-getWorkflowInput + with: + workflowPath: "${{ env.workflowPath}}" + - name: "Get parameter file paths" + id: get-module-test-file-paths + uses: ./.github/actions/templates/avm-getModuleTestFiles + with: + modulePath: "${{ env.modulePath }}" + outputs: + workflowInput: ${{ steps.get-workflow-param.outputs.workflowInput }} + moduleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.moduleTestFilePaths }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Module" + needs: + - job_initialize_pipeline + uses: ./.github/workflows/avm.template.module.yml + with: + workflowInput: "${{ needs.job_initialize_pipeline.outputs.workflowInput }}" + moduleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.moduleTestFilePaths }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/res/operations-management/solution/README.md b/avm/res/operations-management/solution/README.md new file mode 100644 index 0000000000..badf475a35 --- /dev/null +++ b/avm/res/operations-management/solution/README.md @@ -0,0 +1,335 @@ +# Operations Management Solutions `[Microsoft.OperationsManagement/solutions]` + +This module deploys an Operations Management Solution. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.OperationsManagement/solutions` | [2015-11-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.OperationsManagement/2015-11-01-preview/solutions) | + +## Usage examples + +The following section provides usage examples for the module, which were used to validate and deploy the module successfully. For a full reference, please review the module's test folder in its repository. + +>**Note**: Each example lists all the required parameters first, followed by the rest - each in alphabetical order. + +>**Note**: To reference the module, please use the following syntax `br/public:avm/res/operations-management/solution:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [Microsoft solution](#example-2-microsoft-solution) +- [Non-Microsoft solution](#example-3-non-microsoft-solution) +- [WAF-aligned](#example-4-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +
+ +via Bicep module + +```bicep +module solution 'br/public:avm/res/operations-management/solution:' = { + name: '${uniqueString(deployment().name, location)}-test-omsmin' + params: { + // Required parameters + logAnalyticsWorkspaceName: '' + name: 'Updates' + // Non-required parameters + location: '' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "logAnalyticsWorkspaceName": { + "value": "" + }, + "name": { + "value": "Updates" + }, + // Non-required parameters + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Microsoft solution_ + +This instance deploys the module with a Microsoft solution. + + +

+ +via Bicep module + +```bicep +module solution 'br/public:avm/res/operations-management/solution:' = { + name: '${uniqueString(deployment().name, location)}-test-omsms' + params: { + // Required parameters + logAnalyticsWorkspaceName: '' + name: 'AzureAutomation' + // Non-required parameters + location: '' + product: 'OMSGallery' + publisher: 'Microsoft' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "logAnalyticsWorkspaceName": { + "value": "" + }, + "name": { + "value": "AzureAutomation" + }, + // Non-required parameters + "location": { + "value": "" + }, + "product": { + "value": "OMSGallery" + }, + "publisher": { + "value": "Microsoft" + } + } +} +``` + +
+

+ +### Example 3: _Non-Microsoft solution_ + +This instance deploys the module with a third party (Non-Microsoft) solution. + + +

+ +via Bicep module + +```bicep +module solution 'br/public:avm/res/operations-management/solution:' = { + name: '${uniqueString(deployment().name, location)}-test-omsnonms' + params: { + // Required parameters + logAnalyticsWorkspaceName: '' + name: 'omsnonms001' + // Non-required parameters + location: '' + product: 'nonmsTestSolutionProduct' + publisher: 'nonmsTestSolutionPublisher' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "logAnalyticsWorkspaceName": { + "value": "" + }, + "name": { + "value": "omsnonms001" + }, + // Non-required parameters + "location": { + "value": "" + }, + "product": { + "value": "nonmsTestSolutionProduct" + }, + "publisher": { + "value": "nonmsTestSolutionPublisher" + } + } +} +``` + +
+

+ +### Example 4: _WAF-aligned_ + +This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework. + + +

+ +via Bicep module + +```bicep +module solution 'br/public:avm/res/operations-management/solution:' = { + name: '${uniqueString(deployment().name, location)}-test-omswaf' + params: { + // Required parameters + logAnalyticsWorkspaceName: '' + name: 'AzureAutomation' + // Non-required parameters + location: '' + product: 'OMSGallery' + publisher: 'Microsoft' + } +} +``` + +
+

+ +

+ +via JSON Parameter file + +```json +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + // Required parameters + "logAnalyticsWorkspaceName": { + "value": "" + }, + "name": { + "value": "AzureAutomation" + }, + // Non-required parameters + "location": { + "value": "" + }, + "product": { + "value": "OMSGallery" + }, + "publisher": { + "value": "Microsoft" + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`logAnalyticsWorkspaceName`](#parameter-loganalyticsworkspacename) | string | Name of the Log Analytics workspace where the solution will be deployed/enabled. | +| [`name`](#parameter-name) | string | Name of the solution. For Microsoft published gallery solution the target solution resource name will be composed as `{name}({logAnalyticsWorkspaceName})`. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`location`](#parameter-location) | string | Location for all resources. | +| [`product`](#parameter-product) | string | The product of the deployed solution. For Microsoft published gallery solution it should be `OMSGallery` and the target solution resource product will be composed as `OMSGallery/{name}`. For third party solution, it can be anything. This is case sensitive. | +| [`publisher`](#parameter-publisher) | string | The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`. | + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `location` + +Location for all resources. +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `logAnalyticsWorkspaceName` + +Name of the Log Analytics workspace where the solution will be deployed/enabled. +- Required: Yes +- Type: string + +### Parameter: `name` + +Name of the solution. For Microsoft published gallery solution the target solution resource name will be composed as `{name}({logAnalyticsWorkspaceName})`. +- Required: Yes +- Type: string + +### Parameter: `product` + +The product of the deployed solution. For Microsoft published gallery solution it should be `OMSGallery` and the target solution resource product will be composed as `OMSGallery/{name}`. For third party solution, it can be anything. This is case sensitive. +- Required: No +- Type: string +- Default: `'OMSGallery'` + +### Parameter: `publisher` + +The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`. +- Required: No +- Type: string +- Default: `'Microsoft'` + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the resource was deployed into. | +| `name` | string | The name of the deployed solution. | +| `resourceGroupName` | string | The resource group where the solution is deployed. | +| `resourceId` | string | The resource ID of the deployed solution. | + +## Cross-referenced modules + +_None_ diff --git a/avm/res/operations-management/solution/main.bicep b/avm/res/operations-management/solution/main.bicep new file mode 100644 index 0000000000..5b5a475ae2 --- /dev/null +++ b/avm/res/operations-management/solution/main.bicep @@ -0,0 +1,73 @@ +metadata name = 'Operations Management Solutions' +metadata description = 'This module deploys an Operations Management Solution.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the solution. For Microsoft published gallery solution the target solution resource name will be composed as `{name}({logAnalyticsWorkspaceName})`.') +param name string + +@description('Required. Name of the Log Analytics workspace where the solution will be deployed/enabled.') +param logAnalyticsWorkspaceName string + +@description('Optional. Location for all resources.') +param location string = resourceGroup().location + +@description('Optional. The product of the deployed solution. For Microsoft published gallery solution it should be `OMSGallery` and the target solution resource product will be composed as `OMSGallery/{name}`. For third party solution, it can be anything. This is case sensitive.') +param product string = 'OMSGallery' + +@description('Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`.') +param publisher string = 'Microsoft' + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +resource avmTelemetry 'Microsoft.Resources/deployments@2023-07-01' = if (enableTelemetry) { + name: '46d3xbcp.res.operationsmanagement-solution.${replace('-..--..-', '.', '-')}.${substring(uniqueString(deployment().name, location), 0, 4)}' + properties: { + mode: 'Incremental' + template: { + '$schema': 'https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#' + contentVersion: '1.0.0.0' + resources: [] + outputs: { + telemetry: { + type: 'String' + value: 'For more information, see https://aka.ms/avm/TelemetryInfo' + } + } + } + } +} + +resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' existing = { + name: logAnalyticsWorkspaceName +} + +var solutionName = publisher == 'Microsoft' ? '${name}(${logAnalyticsWorkspace.name})' : name + +var solutionProduct = publisher == 'Microsoft' ? 'OMSGallery/${name}' : product + +resource solution 'Microsoft.OperationsManagement/solutions@2015-11-01-preview' = { + name: solutionName + location: location + properties: { + workspaceResourceId: logAnalyticsWorkspace.id + } + plan: { + name: solutionName + promotionCode: '' + product: solutionProduct + publisher: publisher + } +} + +@description('The name of the deployed solution.') +output name string = solution.name + +@description('The resource ID of the deployed solution.') +output resourceId string = solution.id + +@description('The resource group where the solution is deployed.') +output resourceGroupName string = resourceGroup().name + +@description('The location the resource was deployed into.') +output location string = solution.location diff --git a/avm/res/operations-management/solution/main.json b/avm/res/operations-management/solution/main.json new file mode 100644 index 0000000000..3aaa024230 --- /dev/null +++ b/avm/res/operations-management/solution/main.json @@ -0,0 +1,127 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.23.1.45101", + "templateHash": "18444780972506374592" + }, + "name": "Operations Management Solutions", + "description": "This module deploys an Operations Management Solution.", + "owner": "Azure/module-maintainers" + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the solution. For Microsoft published gallery solution the target solution resource name will be composed as `{name}({logAnalyticsWorkspaceName})`." + } + }, + "logAnalyticsWorkspaceName": { + "type": "string", + "metadata": { + "description": "Required. Name of the Log Analytics workspace where the solution will be deployed/enabled." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. Location for all resources." + } + }, + "product": { + "type": "string", + "defaultValue": "OMSGallery", + "metadata": { + "description": "Optional. The product of the deployed solution. For Microsoft published gallery solution it should be `OMSGallery` and the target solution resource product will be composed as `OMSGallery/{name}`. For third party solution, it can be anything. This is case sensitive." + } + }, + "publisher": { + "type": "string", + "defaultValue": "Microsoft", + "metadata": { + "description": "Optional. The publisher name of the deployed solution. For Microsoft published gallery solution, it is `Microsoft`." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + } + }, + "variables": { + "solutionName": "[if(equals(parameters('publisher'), 'Microsoft'), format('{0}({1})', parameters('name'), parameters('logAnalyticsWorkspaceName')), parameters('name'))]", + "solutionProduct": "[if(equals(parameters('publisher'), 'Microsoft'), format('OMSGallery/{0}', parameters('name')), parameters('product'))]" + }, + "resources": [ + { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2023-07-01", + "name": "[format('46d3xbcp.res.operationsmanagement-solution.{0}.{1}', replace('-..--..-', '.', '-'), substring(uniqueString(deployment().name, parameters('location')), 0, 4))]", + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "resources": [], + "outputs": { + "telemetry": { + "type": "String", + "value": "For more information, see https://aka.ms/avm/TelemetryInfo" + } + } + } + } + }, + { + "type": "Microsoft.OperationsManagement/solutions", + "apiVersion": "2015-11-01-preview", + "name": "[variables('solutionName')]", + "location": "[parameters('location')]", + "properties": { + "workspaceResourceId": "[resourceId('Microsoft.OperationalInsights/workspaces', parameters('logAnalyticsWorkspaceName'))]" + }, + "plan": { + "name": "[variables('solutionName')]", + "promotionCode": "", + "product": "[variables('solutionProduct')]", + "publisher": "[parameters('publisher')]" + } + } + ], + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the deployed solution." + }, + "value": "[variables('solutionName')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the deployed solution." + }, + "value": "[resourceId('Microsoft.OperationsManagement/solutions', variables('solutionName'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The resource group where the solution is deployed." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the resource was deployed into." + }, + "value": "[reference(resourceId('Microsoft.OperationsManagement/solutions', variables('solutionName')), '2015-11-01-preview', 'full').location]" + } + } +} \ No newline at end of file diff --git a/avm/res/operations-management/solution/tests/e2e/defaults/dependencies.bicep b/avm/res/operations-management/solution/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 0000000000..ef3592fb5f --- /dev/null +++ b/avm/res/operations-management/solution/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Log Analytics Workspace to create.') +param logAnalyticsWorkspaceName string + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { + name: logAnalyticsWorkspaceName + location: location +} + +@description('The name of the created Log Analytics Workspace.') +output logAnalyticsWorkspaceName string = logAnalytics.name diff --git a/avm/res/operations-management/solution/tests/e2e/defaults/main.test.bicep b/avm/res/operations-management/solution/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..937829c24f --- /dev/null +++ b/avm/res/operations-management/solution/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,55 @@ +targetScope = 'subscription' + +metadata name = 'Using only defaults' +metadata description = 'This instance deploys the module with the minimum set of required parameters.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-operationsmanagement.solutions-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'omsmin' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: 'Updates' + location: location + logAnalyticsWorkspaceName: nestedDependencies.outputs.logAnalyticsWorkspaceName + } +} diff --git a/avm/res/operations-management/solution/tests/e2e/ms/dependencies.bicep b/avm/res/operations-management/solution/tests/e2e/ms/dependencies.bicep new file mode 100644 index 0000000000..ef3592fb5f --- /dev/null +++ b/avm/res/operations-management/solution/tests/e2e/ms/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Log Analytics Workspace to create.') +param logAnalyticsWorkspaceName string + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { + name: logAnalyticsWorkspaceName + location: location +} + +@description('The name of the created Log Analytics Workspace.') +output logAnalyticsWorkspaceName string = logAnalytics.name diff --git a/avm/res/operations-management/solution/tests/e2e/ms/main.test.bicep b/avm/res/operations-management/solution/tests/e2e/ms/main.test.bicep new file mode 100644 index 0000000000..aa5fbbde0b --- /dev/null +++ b/avm/res/operations-management/solution/tests/e2e/ms/main.test.bicep @@ -0,0 +1,57 @@ +targetScope = 'subscription' + +metadata name = 'Microsoft solution' +metadata description = 'This instance deploys the module with a Microsoft solution.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-operationsmanagement.solutions-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'omsms' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: 'AzureAutomation' + location: location + logAnalyticsWorkspaceName: nestedDependencies.outputs.logAnalyticsWorkspaceName + product: 'OMSGallery' + publisher: 'Microsoft' + } +} diff --git a/avm/res/operations-management/solution/tests/e2e/nonms/dependencies.bicep b/avm/res/operations-management/solution/tests/e2e/nonms/dependencies.bicep new file mode 100644 index 0000000000..ef3592fb5f --- /dev/null +++ b/avm/res/operations-management/solution/tests/e2e/nonms/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Log Analytics Workspace to create.') +param logAnalyticsWorkspaceName string + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { + name: logAnalyticsWorkspaceName + location: location +} + +@description('The name of the created Log Analytics Workspace.') +output logAnalyticsWorkspaceName string = logAnalytics.name diff --git a/avm/res/operations-management/solution/tests/e2e/nonms/main.test.bicep b/avm/res/operations-management/solution/tests/e2e/nonms/main.test.bicep new file mode 100644 index 0000000000..2153dc3d75 --- /dev/null +++ b/avm/res/operations-management/solution/tests/e2e/nonms/main.test.bicep @@ -0,0 +1,57 @@ +targetScope = 'subscription' + +metadata name = 'Non-Microsoft solution' +metadata description = 'This instance deploys the module with a third party (Non-Microsoft) solution.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-operationsmanagement.solutions-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'omsnonms' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +module testDeployment '../../../main.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}' + params: { + name: '${namePrefix}${serviceShort}001' + location: location + logAnalyticsWorkspaceName: nestedDependencies.outputs.logAnalyticsWorkspaceName + product: 'nonmsTestSolutionProduct' + publisher: 'nonmsTestSolutionPublisher' + } +} diff --git a/avm/res/operations-management/solution/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/operations-management/solution/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..ef3592fb5f --- /dev/null +++ b/avm/res/operations-management/solution/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,13 @@ +@description('Optional. The location to deploy to.') +param location string = resourceGroup().location + +@description('Required. The name of the Log Analytics Workspace to create.') +param logAnalyticsWorkspaceName string + +resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { + name: logAnalyticsWorkspaceName + location: location +} + +@description('The name of the created Log Analytics Workspace.') +output logAnalyticsWorkspaceName string = logAnalytics.name diff --git a/avm/res/operations-management/solution/tests/e2e/waf-aligned/main.test.bicep b/avm/res/operations-management/solution/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..9e7f561694 --- /dev/null +++ b/avm/res/operations-management/solution/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,58 @@ +targetScope = 'subscription' + +metadata name = 'WAF-aligned' +metadata description = 'This instance deploys the module in alignment with the best-practices of the Azure Well-Architected Framework.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-operationsmanagement.solutions-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param location string = deployment().location + +@description('Optional. A short identifier for the kind of deployment. Should be kept short to not run into resource-name length-constraints.') +param serviceShort string = 'omswaf' + +@description('Optional. A token to inject into the name of each resource. This value can be automatically injected by the CI.') +param namePrefix string = '#_namePrefix_#' + +// ============ // +// Dependencies // +// ============ // + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: location +} + +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-nestedDependencies' + params: { + logAnalyticsWorkspaceName: 'dep-${namePrefix}-law-${serviceShort}' + location: location + } +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [for iteration in [ 'init', 'idem' ]: { + scope: resourceGroup + name: '${uniqueString(deployment().name, location)}-test-${serviceShort}-${iteration}' + params: { + name: 'AzureAutomation' + location: location + logAnalyticsWorkspaceName: nestedDependencies.outputs.logAnalyticsWorkspaceName + product: 'OMSGallery' + publisher: 'Microsoft' + } +}] diff --git a/avm/res/operations-management/solution/version.json b/avm/res/operations-management/solution/version.json new file mode 100644 index 0000000000..83083db694 --- /dev/null +++ b/avm/res/operations-management/solution/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +} \ No newline at end of file