diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f67f52ec14..fff88d76bc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -62,6 +62,7 @@ /avm/res/desktop-virtualization/host-pool/ @Azure/avm-res-desktopvirtualization-hostpool-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/desktop-virtualization/scaling-plan/ @Azure/avm-res-desktopvirtualization-scalingplan-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/desktop-virtualization/workspace/ @Azure/avm-res-desktopvirtualization-workspace-module-owners-bicep @Azure/avm-module-reviewers-bicep +/avm/res/dev-ops-infrastructure/pool/ @Azure/avm-res-devopsinfrastructure-pool-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/dev-test-lab/lab/ @Azure/avm-res-devtestlab-lab-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/digital-twins/digital-twins-instance/ @Azure/avm-res-digitaltwins-digitaltwinsinstance-module-owners-bicep @Azure/avm-module-reviewers-bicep /avm/res/document-db/database-account/ @Azure/avm-res-documentdb-databaseaccount-module-owners-bicep @Azure/avm-module-reviewers-bicep diff --git a/.github/ISSUE_TEMPLATE/avm_module_issue.yml b/.github/ISSUE_TEMPLATE/avm_module_issue.yml index d30ee744be..feded8d600 100644 --- a/.github/ISSUE_TEMPLATE/avm_module_issue.yml +++ b/.github/ISSUE_TEMPLATE/avm_module_issue.yml @@ -97,6 +97,7 @@ body: - "avm/res/desktop-virtualization/host-pool" - "avm/res/desktop-virtualization/scaling-plan" - "avm/res/desktop-virtualization/workspace" + - "avm/res/dev-ops-infrastructure/pool" - "avm/res/dev-test-lab/lab" - "avm/res/digital-twins/digital-twins-instance" - "avm/res/document-db/database-account" diff --git a/.github/workflows/avm.res.dev-ops-infrastructure.pool.yml b/.github/workflows/avm.res.dev-ops-infrastructure.pool.yml new file mode 100644 index 0000000000..dac40abeb8 --- /dev/null +++ b/.github/workflows/avm.res.dev-ops-infrastructure.pool.yml @@ -0,0 +1,88 @@ +name: "avm.res.dev-ops-infrastructure.pool" + +on: + 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 + customLocation: + type: string + description: "Default location overwrite (e.g., eastus)" + required: false + push: + branches: + - main + paths: + - ".github/actions/templates/avm-**" + - ".github/workflows/avm.template.module.yml" + - ".github/workflows/avm.res.dev-ops-infrastructure.pool.yml" + - "avm/res/dev-ops-infrastructure/pool/**" + - "avm/utilities/pipelines/**" + - "!avm/utilities/pipelines/platform/**" + - "!*/**/README.md" + +env: + modulePath: "avm/res/dev-ops-infrastructure/pool" + workflowPath: ".github/workflows/avm.res.dev-ops-infrastructure.pool.yml" + +concurrency: + group: ${{ github.workflow }} + +jobs: + ########################### + # Initialize pipeline # + ########################### + job_initialize_pipeline: + runs-on: ubuntu-latest + 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 module test 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 }} + psRuleModuleTestFilePaths: ${{ steps.get-module-test-file-paths.outputs.psRuleModuleTestFilePaths }} + modulePath: "${{ env.modulePath }}" + + ############################## + # Call reusable workflow # + ############################## + call-workflow-passing-data: + name: "Run" + permissions: + id-token: write # For OIDC + contents: write # For release tags + 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 }}" + psRuleModuleTestFilePaths: "${{ needs.job_initialize_pipeline.outputs.psRuleModuleTestFilePaths }}" + modulePath: "${{ needs.job_initialize_pipeline.outputs.modulePath}}" + secrets: inherit diff --git a/avm/res/dev-ops-infrastructure/pool/README.md b/avm/res/dev-ops-infrastructure/pool/README.md new file mode 100644 index 0000000000..4cd888f4b4 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/README.md @@ -0,0 +1,1320 @@ +# Managed DevOps Pool `[Microsoft.DevOpsInfrastructure/pools]` + +This module deploys the Managed DevOps Pool resource. + +## Navigation + +- [Resource Types](#Resource-Types) +- [Usage examples](#Usage-examples) +- [Parameters](#Parameters) +- [Outputs](#Outputs) +- [Cross-referenced modules](#Cross-referenced-modules) +- [Data Collection](#Data-Collection) + +## Resource Types + +| Resource Type | API Version | +| :-- | :-- | +| `Microsoft.Authorization/locks` | [2020-05-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2020-05-01/locks) | +| `Microsoft.Authorization/roleAssignments` | [2022-04-01](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Authorization/2022-04-01/roleAssignments) | +| `Microsoft.DevOpsInfrastructure/pools` | [2024-04-04-preview](https://learn.microsoft.com/en-us/azure/templates) | +| `Microsoft.Insights/diagnosticSettings` | [2021-05-01-preview](https://learn.microsoft.com/en-us/azure/templates/Microsoft.Insights/2021-05-01-preview/diagnosticSettings) | + +## Notes + +The Managed DevOps Pool resource requires external permissions in Azure DevOps. Make sure that the deployment principal has permission in Azure DevOps: [Managed DevOps Pools - Verify Azure DevOps Permissions](https://learn.microsoft.com/en-us/azure/devops/managed-devops-pools/prerequisites?view=azure-devops&tabs=azure-portal#verify-azure-devops-permissions) + +## 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/dev-ops-infrastructure/pool:`. + +- [Using only defaults](#example-1-using-only-defaults) +- [Using large parameter set](#example-2-using-large-parameter-set) +- [WAF-aligned](#example-3-waf-aligned) + +### Example 1: _Using only defaults_ + +This instance deploys the module with the minimum set of required parameters. + + +
+ +via Bicep module + +```bicep +module pool 'br/public:avm/res/dev-ops-infrastructure/pool:' = { + name: 'poolDeployment' + params: { + // Required parameters + agentProfile: { + kind: 'Stateless' + } + concurrency: 1 + devCenterProjectResourceId: '' + fabricProfileSkuName: 'Standard_DS2_v2' + images: [ + { + wellKnownImageName: 'windows-2022/latest' + } + ] + name: 'mdpmin001' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + url: '' + } + ] + } + // 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 + "agentProfile": { + "value": { + "kind": "Stateless" + } + }, + "concurrency": { + "value": 1 + }, + "devCenterProjectResourceId": { + "value": "" + }, + "fabricProfileSkuName": { + "value": "Standard_DS2_v2" + }, + "images": { + "value": [ + { + "wellKnownImageName": "windows-2022/latest" + } + ] + }, + "name": { + "value": "mdpmin001" + }, + "organizationProfile": { + "value": { + "kind": "AzureDevOps", + "organizations": [ + { + "url": "" + } + ] + } + }, + // Non-required parameters + "location": { + "value": "" + } + } +} +``` + +
+

+ +### Example 2: _Using large parameter set_ + +This instance deploys the module with most of its features enabled. + + +

+ +via Bicep module + +```bicep +module pool 'br/public:avm/res/dev-ops-infrastructure/pool:' = { + name: 'poolDeployment' + params: { + // Required parameters + agentProfile: { + kind: 'Stateless' + resourcePredictions: { + daysData: [ + { + '09:00:00': 1 + '17:00:00': 0 + } + {} + {} + {} + { + '09:00:00': 1 + '17:00:00': 0 + } + {} + {} + ] + timeZone: 'Central Europe Standard Time' + } + resourcePredictionsProfile: { + kind: 'Automatic' + predictionPreference: 'Balanced' + } + } + concurrency: 1 + devCenterProjectResourceId: '' + fabricProfileSkuName: 'Standard_D2_v2' + images: [ + { + aliases: [ + 'windows-2022' + ] + buffer: '*' + wellKnownImageName: 'windows-2022/latest' + } + ] + name: 'mdpmax001' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + parallelism: 1 + projects: [ + '' + ] + url: '' + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + // Non-required parameters + location: '' + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + roleAssignments: [ + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: '' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + } + { + principalId: '' + principalType: 'ServicePrincipal' + roleDefinitionIdOrName: 'Owner' + } + ] + storageProfile: { + dataDisks: [ + { + caching: 'ReadWrite' + diskSizeGiB: 100 + driveLetter: 'B' + storageAccountType: 'Standard_LRS' + } + ] + osDiskStorageAccountType: 'Standard' + } + subnetResourceId: '' + tags: { + Environment: 'Non-Prod' + 'hidden-title': 'This is visible in the resource name' + Role: 'DeploymentValidation' + } + } +} +``` + +
+

+ +

+ +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 + "agentProfile": { + "value": { + "kind": "Stateless", + "resourcePredictions": { + "daysData": [ + { + "09:00:00": 1, + "17:00:00": 0 + }, + {}, + {}, + {}, + { + "09:00:00": 1, + "17:00:00": 0 + }, + {}, + {} + ], + "timeZone": "Central Europe Standard Time" + }, + "resourcePredictionsProfile": { + "kind": "Automatic", + "predictionPreference": "Balanced" + } + } + }, + "concurrency": { + "value": 1 + }, + "devCenterProjectResourceId": { + "value": "" + }, + "fabricProfileSkuName": { + "value": "Standard_D2_v2" + }, + "images": { + "value": [ + { + "aliases": [ + "windows-2022" + ], + "buffer": "*", + "wellKnownImageName": "windows-2022/latest" + } + ] + }, + "name": { + "value": "mdpmax001" + }, + "organizationProfile": { + "value": { + "kind": "AzureDevOps", + "organizations": [ + { + "parallelism": 1, + "projects": [ + "" + ], + "url": "" + } + ], + "permissionProfile": { + "kind": "CreatorOnly" + } + } + }, + // Non-required parameters + "location": { + "value": "" + }, + "lock": { + "value": { + "kind": "CanNotDelete", + "name": "myCustomLockName" + } + }, + "roleAssignments": { + "value": [ + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "b24988ac-6180-42a0-ab88-20f7382dd24c" + }, + { + "principalId": "", + "principalType": "ServicePrincipal", + "roleDefinitionIdOrName": "Owner" + } + ] + }, + "storageProfile": { + "value": { + "dataDisks": [ + { + "caching": "ReadWrite", + "diskSizeGiB": 100, + "driveLetter": "B", + "storageAccountType": "Standard_LRS" + } + ], + "osDiskStorageAccountType": "Standard" + } + }, + "subnetResourceId": { + "value": "" + }, + "tags": { + "value": { + "Environment": "Non-Prod", + "hidden-title": "This is visible in the resource name", + "Role": "DeploymentValidation" + } + } + } +} +``` + +
+

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

+ +via Bicep module + +```bicep +module pool 'br/public:avm/res/dev-ops-infrastructure/pool:' = { + name: 'poolDeployment' + params: { + // Required parameters + agentProfile: { + kind: 'Stateless' + resourcePredictions: { + daysData: [ + { + '09:00:00': 1 + '17:00:00': 0 + } + {} + {} + {} + { + '09:00:00': 1 + '17:00:00': 0 + } + {} + {} + ] + timeZone: 'Central Europe Standard Time' + } + resourcePredictionsProfile: { + kind: 'Automatic' + predictionPreference: 'Balanced' + } + } + concurrency: 1 + devCenterProjectResourceId: '' + fabricProfileSkuName: 'Standard_D2_v2' + images: [ + { + wellKnownImageName: 'windows-2022/latest' + } + ] + name: 'mdpwaf001' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + parallelism: 1 + projects: [ + '' + ] + url: '' + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + // Non-required parameters + location: '' + subnetResourceId: '' + } +} +``` + +
+

+ +

+ +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 + "agentProfile": { + "value": { + "kind": "Stateless", + "resourcePredictions": { + "daysData": [ + { + "09:00:00": 1, + "17:00:00": 0 + }, + {}, + {}, + {}, + { + "09:00:00": 1, + "17:00:00": 0 + }, + {}, + {} + ], + "timeZone": "Central Europe Standard Time" + }, + "resourcePredictionsProfile": { + "kind": "Automatic", + "predictionPreference": "Balanced" + } + } + }, + "concurrency": { + "value": 1 + }, + "devCenterProjectResourceId": { + "value": "" + }, + "fabricProfileSkuName": { + "value": "Standard_D2_v2" + }, + "images": { + "value": [ + { + "wellKnownImageName": "windows-2022/latest" + } + ] + }, + "name": { + "value": "mdpwaf001" + }, + "organizationProfile": { + "value": { + "kind": "AzureDevOps", + "organizations": [ + { + "parallelism": 1, + "projects": [ + "" + ], + "url": "" + } + ], + "permissionProfile": { + "kind": "CreatorOnly" + } + } + }, + // Non-required parameters + "location": { + "value": "" + }, + "subnetResourceId": { + "value": "" + } + } +} +``` + +
+

+ + +## Parameters + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`agentProfile`](#parameter-agentprofile) | object | Defines how the machine will be handled once it executed a job. | +| [`concurrency`](#parameter-concurrency) | int | Defines how many resources can there be created at any given time. | +| [`devCenterProjectResourceId`](#parameter-devcenterprojectresourceid) | string | The resource id of the DevCenter Project the pool belongs to. | +| [`fabricProfileSkuName`](#parameter-fabricprofileskuname) | string | The Azure SKU name of the machines in the pool. | +| [`images`](#parameter-images) | array | The VM images of the machines in the pool. | +| [`name`](#parameter-name) | string | Name of the pool. It needs to be globally unique. | +| [`organizationProfile`](#parameter-organizationprofile) | object | Defines the organization in which the pool will be used. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`diagnosticSettings`](#parameter-diagnosticsettings) | array | The diagnostic settings of the service. | +| [`enableTelemetry`](#parameter-enabletelemetry) | bool | Enable/Disable usage telemetry for module. | +| [`location`](#parameter-location) | string | The geo-location where the resource lives. | +| [`lock`](#parameter-lock) | object | The lock settings of the service. | +| [`managedIdentities`](#parameter-managedidentities) | object | The managed service identities assigned to this resource. | +| [`osProfile`](#parameter-osprofile) | object | The OS profile of the agents in the pool. | +| [`roleAssignments`](#parameter-roleassignments) | array | Array of role assignments to create. | +| [`storageProfile`](#parameter-storageprofile) | object | The storage profile of the machines in the pool. | +| [`subnetResourceId`](#parameter-subnetresourceid) | string | The subnet id on which to put all machines created in the pool. | +| [`tags`](#parameter-tags) | object | Tags of the resource. | + +### Parameter: `agentProfile` + +Defines how the machine will be handled once it executed a job. + +- Required: Yes +- Type: object + +### Parameter: `concurrency` + +Defines how many resources can there be created at any given time. + +- Required: Yes +- Type: int + +### Parameter: `devCenterProjectResourceId` + +The resource id of the DevCenter Project the pool belongs to. + +- Required: Yes +- Type: string + +### Parameter: `fabricProfileSkuName` + +The Azure SKU name of the machines in the pool. + +- Required: Yes +- Type: string + +### Parameter: `images` + +The VM images of the machines in the pool. + +- Required: Yes +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`wellKnownImageName`](#parameter-imageswellknownimagename) | string | The image to use from a well-known set of images made available to customers. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`aliases`](#parameter-imagesaliases) | array | List of aliases to reference the image by. | +| [`buffer`](#parameter-imagesbuffer) | string | The percentage of the buffer to be allocated to this image. | +| [`resourceId`](#parameter-imagesresourceid) | string | The resource id of the image. | + +### Parameter: `images.wellKnownImageName` + +The image to use from a well-known set of images made available to customers. + +- Required: Yes +- Type: string + +### Parameter: `images.aliases` + +List of aliases to reference the image by. + +- Required: No +- Type: array + +### Parameter: `images.buffer` + +The percentage of the buffer to be allocated to this image. + +- Required: No +- Type: string + +### Parameter: `images.resourceId` + +The resource id of the image. + +- Required: No +- Type: string + +### Parameter: `name` + +Name of the pool. It needs to be globally unique. + +- Required: Yes +- Type: string + +### Parameter: `organizationProfile` + +Defines the organization in which the pool will be used. + +- Required: Yes +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-organizationprofilekind) | string | Azure DevOps organization profile. | +| [`organizations`](#parameter-organizationprofileorganizations) | array | The list of Azure DevOps organizations the pool should be present in.. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`permissionProfile`](#parameter-organizationprofilepermissionprofile) | object | The type of permission which determines which accounts are admins on the Azure DevOps pool. | + +### Parameter: `organizationProfile.kind` + +Azure DevOps organization profile. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDevOps' + ] + ``` + +### Parameter: `organizationProfile.organizations` + +The list of Azure DevOps organizations the pool should be present in.. + +- Required: Yes +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`url`](#parameter-organizationprofileorganizationsurl) | string | The Azure DevOps organization URL in which the pool should be created. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`parallelism`](#parameter-organizationprofileorganizationsparallelism) | int | How many machines can be created at maximum in this organization out of the maximumConcurrency of the pool. | +| [`projects`](#parameter-organizationprofileorganizationsprojects) | array | List of projects in which the pool should be created. | + +### Parameter: `organizationProfile.organizations.url` + +The Azure DevOps organization URL in which the pool should be created. + +- Required: Yes +- Type: string + +### Parameter: `organizationProfile.organizations.parallelism` + +How many machines can be created at maximum in this organization out of the maximumConcurrency of the pool. + +- Required: No +- Type: int + +### Parameter: `organizationProfile.organizations.projects` + +List of projects in which the pool should be created. + +- Required: No +- Type: array + +### Parameter: `organizationProfile.permissionProfile` + +The type of permission which determines which accounts are admins on the Azure DevOps pool. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`groups`](#parameter-organizationprofilepermissionprofilegroups) | array | Group email addresses. | +| [`kind`](#parameter-organizationprofilepermissionprofilekind) | string | Determines who has admin permissions to the Azure DevOps pool. | +| [`users`](#parameter-organizationprofilepermissionprofileusers) | array | User email addresses. | + +### Parameter: `organizationProfile.permissionProfile.groups` + +Group email addresses. + +- Required: No +- Type: array + +### Parameter: `organizationProfile.permissionProfile.kind` + +Determines who has admin permissions to the Azure DevOps pool. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CreatorOnly' + 'Inherit' + 'SpecificAccounts' + ] + ``` + +### Parameter: `organizationProfile.permissionProfile.users` + +User email addresses. + +- Required: No +- Type: array + +### Parameter: `diagnosticSettings` + +The diagnostic settings of the service. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`eventHubAuthorizationRuleResourceId`](#parameter-diagnosticsettingseventhubauthorizationruleresourceid) | string | Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. | +| [`eventHubName`](#parameter-diagnosticsettingseventhubname) | string | Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`logAnalyticsDestinationType`](#parameter-diagnosticsettingsloganalyticsdestinationtype) | string | A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. | +| [`logCategoriesAndGroups`](#parameter-diagnosticsettingslogcategoriesandgroups) | array | The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. | +| [`marketplacePartnerResourceId`](#parameter-diagnosticsettingsmarketplacepartnerresourceid) | string | The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. | +| [`metricCategories`](#parameter-diagnosticsettingsmetriccategories) | array | The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. | +| [`name`](#parameter-diagnosticsettingsname) | string | The name of diagnostic setting. | +| [`storageAccountResourceId`](#parameter-diagnosticsettingsstorageaccountresourceid) | string | Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | +| [`workspaceResourceId`](#parameter-diagnosticsettingsworkspaceresourceid) | string | Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. | + +### Parameter: `diagnosticSettings.eventHubAuthorizationRuleResourceId` + +Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.eventHubName` + +Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logAnalyticsDestinationType` + +A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'AzureDiagnostics' + 'Dedicated' + ] + ``` + +### Parameter: `diagnosticSettings.logCategoriesAndGroups` + +The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingslogcategoriesandgroupscategory) | string | Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. | +| [`categoryGroup`](#parameter-diagnosticsettingslogcategoriesandgroupscategorygroup) | string | Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. | +| [`enabled`](#parameter-diagnosticsettingslogcategoriesandgroupsenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.category` + +Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.categoryGroup` + +Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.logCategoriesAndGroups.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.marketplacePartnerResourceId` + +The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.metricCategories` + +The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`category`](#parameter-diagnosticsettingsmetriccategoriescategory) | string | Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`enabled`](#parameter-diagnosticsettingsmetriccategoriesenabled) | bool | Enable or disable the category explicitly. Default is `true`. | + +### Parameter: `diagnosticSettings.metricCategories.category` + +Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics. + +- Required: Yes +- Type: string + +### Parameter: `diagnosticSettings.metricCategories.enabled` + +Enable or disable the category explicitly. Default is `true`. + +- Required: No +- Type: bool + +### Parameter: `diagnosticSettings.name` + +The name of diagnostic setting. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.storageAccountResourceId` + +Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `diagnosticSettings.workspaceResourceId` + +Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub. + +- Required: No +- Type: string + +### Parameter: `enableTelemetry` + +Enable/Disable usage telemetry for module. + +- Required: No +- Type: bool +- Default: `True` + +### Parameter: `location` + +The geo-location where the resource lives. + +- Required: No +- Type: string +- Default: `[resourceGroup().location]` + +### Parameter: `lock` + +The lock settings of the service. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`kind`](#parameter-lockkind) | string | Specify the type of lock. | +| [`name`](#parameter-lockname) | string | Specify the name of lock. | + +### Parameter: `lock.kind` + +Specify the type of lock. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'CanNotDelete' + 'None' + 'ReadOnly' + ] + ``` + +### Parameter: `lock.name` + +Specify the name of lock. + +- Required: No +- Type: string + +### Parameter: `managedIdentities` + +The managed service identities assigned to this resource. + +- Required: No +- Type: object +- Example: + ```Bicep + { + systemAssigned: true, + userAssignedResourceIds: [ + '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myManagedIdentity' + ] + } + { + systemAssigned: true + } + ``` + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`systemAssigned`](#parameter-managedidentitiessystemassigned) | bool | Enables system assigned managed identity on the resource. | +| [`userAssignedResourceIds`](#parameter-managedidentitiesuserassignedresourceids) | array | The resource ID(s) to assign to the resource. | + +### Parameter: `managedIdentities.systemAssigned` + +Enables system assigned managed identity on the resource. + +- Required: No +- Type: bool + +### Parameter: `managedIdentities.userAssignedResourceIds` + +The resource ID(s) to assign to the resource. + +- Required: No +- Type: array + +### Parameter: `osProfile` + +The OS profile of the agents in the pool. + +- Required: No +- Type: object +- Default: + ```Bicep + { + logonType: 'Interactive' + secretsManagementSettings: { + keyExportable: false + observedCertificates: [] + } + } + ``` + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`logonType`](#parameter-osprofilelogontype) | string | The logon type of the machine. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`secretsManagementSettings`](#parameter-osprofilesecretsmanagementsettings) | object | The secret management settings of the machines in the pool. | + +### Parameter: `osProfile.logonType` + +The logon type of the machine. + +- Required: Yes +- Type: string +- Allowed: + ```Bicep + [ + 'Interactive' + 'Service' + ] + ``` + +### Parameter: `osProfile.secretsManagementSettings` + +The secret management settings of the machines in the pool. + +- Required: No +- Type: object + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`keyExportable`](#parameter-osprofilesecretsmanagementsettingskeyexportable) | bool | The secret management settings of the machines in the pool. | +| [`observedCertificates`](#parameter-osprofilesecretsmanagementsettingsobservedcertificates) | array | The list of certificates to install on all machines in the pool. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`certificateStoreLocation`](#parameter-osprofilesecretsmanagementsettingscertificatestorelocation) | string | Where to store certificates on the machine. | + +### Parameter: `osProfile.secretsManagementSettings.keyExportable` + +The secret management settings of the machines in the pool. + +- Required: Yes +- Type: bool + +### Parameter: `osProfile.secretsManagementSettings.observedCertificates` + +The list of certificates to install on all machines in the pool. + +- Required: Yes +- Type: array + +### Parameter: `osProfile.secretsManagementSettings.certificateStoreLocation` + +Where to store certificates on the machine. + +- Required: No +- Type: string + +### Parameter: `roleAssignments` + +Array of role assignments to create. + +- Required: No +- Type: array + +**Required parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`principalId`](#parameter-roleassignmentsprincipalid) | string | The principal ID of the principal (user/group/identity) to assign the role to. | +| [`roleDefinitionIdOrName`](#parameter-roleassignmentsroledefinitionidorname) | string | The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. | + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`condition`](#parameter-roleassignmentscondition) | string | The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container". | +| [`conditionVersion`](#parameter-roleassignmentsconditionversion) | string | Version of the condition. | +| [`delegatedManagedIdentityResourceId`](#parameter-roleassignmentsdelegatedmanagedidentityresourceid) | string | The Resource Id of the delegated managed identity resource. | +| [`description`](#parameter-roleassignmentsdescription) | string | The description of the role assignment. | +| [`name`](#parameter-roleassignmentsname) | string | The name (as GUID) of the role assignment. If not provided, a GUID will be generated. | +| [`principalType`](#parameter-roleassignmentsprincipaltype) | string | The principal type of the assigned principal ID. | + +### Parameter: `roleAssignments.principalId` + +The principal ID of the principal (user/group/identity) to assign the role to. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.roleDefinitionIdOrName` + +The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'. + +- Required: Yes +- Type: string + +### Parameter: `roleAssignments.condition` + +The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container". + +- Required: No +- Type: string + +### Parameter: `roleAssignments.conditionVersion` + +Version of the condition. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + '2.0' + ] + ``` + +### Parameter: `roleAssignments.delegatedManagedIdentityResourceId` + +The Resource Id of the delegated managed identity resource. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.description` + +The description of the role assignment. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.name` + +The name (as GUID) of the role assignment. If not provided, a GUID will be generated. + +- Required: No +- Type: string + +### Parameter: `roleAssignments.principalType` + +The principal type of the assigned principal ID. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Device' + 'ForeignGroup' + 'Group' + 'ServicePrincipal' + 'User' + ] + ``` + +### Parameter: `storageProfile` + +The storage profile of the machines in the pool. + +- Required: No +- Type: object + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`dataDisks`](#parameter-storageprofiledatadisks) | array | A list of empty data disks to attach. | +| [`osDiskStorageAccountType`](#parameter-storageprofileosdiskstorageaccounttype) | string | The Azure SKU name of the machines in the pool. | + +### Parameter: `storageProfile.dataDisks` + +A list of empty data disks to attach. + +- Required: No +- Type: array + +**Optional parameters** + +| Parameter | Type | Description | +| :-- | :-- | :-- | +| [`caching`](#parameter-storageprofiledatadiskscaching) | string | The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/. | +| [`diskSizeGiB`](#parameter-storageprofiledatadisksdisksizegib) | int | The initial disk size in gigabytes. | +| [`driveLetter`](#parameter-storageprofiledatadisksdriveletter) | string | The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed. | +| [`storageAccountType`](#parameter-storageprofiledatadisksstorageaccounttype) | string | The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS. | + +### Parameter: `storageProfile.dataDisks.caching` + +The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'None' + 'ReadOnly' + 'ReadWrite' + ] + ``` + +### Parameter: `storageProfile.dataDisks.diskSizeGiB` + +The initial disk size in gigabytes. + +- Required: No +- Type: int + +### Parameter: `storageProfile.dataDisks.driveLetter` + +The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed. + +- Required: No +- Type: string + +### Parameter: `storageProfile.dataDisks.storageAccountType` + +The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Premium_LRS' + 'Premium_ZRS' + 'Standard_LRS' + 'StandardSSD_LRS' + 'StandardSSD_ZRS' + ] + ``` + +### Parameter: `storageProfile.osDiskStorageAccountType` + +The Azure SKU name of the machines in the pool. + +- Required: No +- Type: string +- Allowed: + ```Bicep + [ + 'Premium' + 'Standard' + 'StandardSSD' + ] + ``` + +### Parameter: `subnetResourceId` + +The subnet id on which to put all machines created in the pool. + +- Required: No +- Type: string + +### Parameter: `tags` + +Tags of the resource. + +- Required: No +- Type: object + + +## Outputs + +| Output | Type | Description | +| :-- | :-- | :-- | +| `location` | string | The location the Managed DevOps Pool resource was deployed into. | +| `name` | string | The name of the Managed DevOps Pool. | +| `resourceGroupName` | string | The name of the resource group the Managed DevOps Pool resource was deployed into. | +| `resourceId` | string | The resource ID of the Managed DevOps Pool. | +| `systemAssignedMIPrincipalId` | string | The principal ID of the system assigned identity. | + +## Cross-referenced modules + +_None_ + +## Data Collection + +The software may collect information about you and your use of the software and send it to Microsoft. Microsoft may use this information to provide services and improve our products and services. You may turn off the telemetry as described in the [repository](https://aka.ms/avm/telemetry). There are also some features in the software that may enable you and Microsoft to collect data from users of your applications. If you use these features, you must comply with applicable law, including providing appropriate notices to users of your applications together with a copy of Microsoft’s privacy statement. Our privacy statement is located at . You can learn more about data collection and use in the help documentation and our privacy statement. Your use of the software operates as your consent to these practices. diff --git a/avm/res/dev-ops-infrastructure/pool/main.bicep b/avm/res/dev-ops-infrastructure/pool/main.bicep new file mode 100644 index 0000000000..c64d2bbce5 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/main.bicep @@ -0,0 +1,489 @@ +metadata name = 'Managed DevOps Pool' +metadata description = 'This module deploys the Managed DevOps Pool resource.' +metadata owner = 'Azure/module-maintainers' + +@description('Required. Name of the pool. It needs to be globally unique.') +param name string + +@description('Required. The Azure SKU name of the machines in the pool.') +param fabricProfileSkuName string + +@minValue(1) +@maxValue(10000) +@description('Required. Defines how many resources can there be created at any given time.') +param concurrency int + +@description('Required. The VM images of the machines in the pool.') +param images imageType + +@description('Optional. The geo-location where the resource lives.') +param location string = resourceGroup().location + +@description('Required. The resource id of the DevCenter Project the pool belongs to.') +param devCenterProjectResourceId string + +@description('Optional. The subnet id on which to put all machines created in the pool.') +param subnetResourceId string? + +@description('Required. Defines how the machine will be handled once it executed a job.') +param agentProfile agentProfileType + +@description('Optional. The OS profile of the agents in the pool.') +param osProfile osProfileType = { + logonType: 'Interactive' + secretsManagementSettings: { + keyExportable: false + observedCertificates: [] + } +} + +@description('Optional. The storage profile of the machines in the pool.') +param storageProfile storageProfileType + +@description('Required. Defines the organization in which the pool will be used.') +param organizationProfile organizationProfileType + +@description('Optional. Tags of the resource.') +param tags object? + +@description('Optional. The lock settings of the service.') +param lock lockType + +@description('Optional. Array of role assignments to create.') +param roleAssignments roleAssignmentType + +@description('Optional. Enable/Disable usage telemetry for module.') +param enableTelemetry bool = true + +@description('Optional. The diagnostic settings of the service.') +param diagnosticSettings diagnosticSettingType + +@description('Optional. The managed service identities assigned to this resource.') +@metadata({ + example: ''' + { + systemAssigned: true, + userAssignedResourceIds: [ + '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myManagedIdentity' + ] + } + { + systemAssigned: true + } + ''' +}) +param managedIdentities managedIdentitiesType + +var builtInRoleNames = { + Contributor: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') + Owner: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635') + Reader: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7') + 'Role Based Access Control Administrator (Preview)': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'f58310d9-a9f6-439a-9e8d-f62e7b41a168' + ) + 'User Access Administrator': subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9' + ) +} + +var formattedRoleAssignments = [ + for (roleAssignment, index) in (roleAssignments ?? []): union(roleAssignment, { + roleDefinitionId: builtInRoleNames[?roleAssignment.roleDefinitionIdOrName] ?? (contains( + roleAssignment.roleDefinitionIdOrName, + '/providers/Microsoft.Authorization/roleDefinitions/' + ) + ? roleAssignment.roleDefinitionIdOrName + : subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleAssignment.roleDefinitionIdOrName)) + }) +] + +var formattedUserAssignedIdentities = reduce( + map((managedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }), + {}, + (cur, next) => union(cur, next) +) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} } + +var identity = !empty(managedIdentities) + ? { + type: (managedIdentities.?systemAssigned ?? false) + ? (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'SystemAssigned,UserAssigned' : 'SystemAssigned') + : (!empty(managedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : 'None') + userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null + } + : null + +#disable-next-line no-deployments-resources +resource avmTelemetry 'Microsoft.Resources/deployments@2024-03-01' = if (enableTelemetry) { + name: '46d3xbcp.res.devopsinfrastructure-pool.${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 managedDevOpsPool 'Microsoft.DevOpsInfrastructure/pools@2024-04-04-preview' = { + name: name + location: location + tags: tags + identity: identity + properties: { + agentProfile: agentProfile + devCenterProjectResourceId: devCenterProjectResourceId + fabricProfile: { + sku: { + name: fabricProfileSkuName + } + networkProfile: !empty(subnetResourceId) + ? { + subnetId: subnetResourceId! + } + : null + osProfile: osProfile + storageProfile: storageProfile + kind: 'Vmss' + images: images + } + maximumConcurrency: concurrency + organizationProfile: organizationProfile + } +} + +resource managedDevOpsPool_lock 'Microsoft.Authorization/locks@2020-05-01' = if (!empty(lock ?? {}) && lock.?kind != 'None') { + name: lock.?name ?? 'lock-${name}' + properties: { + level: lock.?kind ?? '' + notes: lock.?kind == 'CanNotDelete' + ? 'Cannot delete resource or child resources.' + : 'Cannot delete or modify the resource or child resources.' + } + scope: managedDevOpsPool +} + +resource managedDevOpsPool_roleAssignments 'Microsoft.Authorization/roleAssignments@2022-04-01' = [ + for (roleAssignment, index) in (formattedRoleAssignments ?? []): { + name: roleAssignment.?name ?? guid( + managedDevOpsPool.id, + roleAssignment.principalId, + roleAssignment.roleDefinitionId + ) + properties: { + roleDefinitionId: roleAssignment.roleDefinitionId + principalId: roleAssignment.principalId + description: roleAssignment.?description + principalType: roleAssignment.?principalType + condition: roleAssignment.?condition + conditionVersion: !empty(roleAssignment.?condition) ? (roleAssignment.?conditionVersion ?? '2.0') : null // Must only be set if condtion is set + delegatedManagedIdentityResourceId: roleAssignment.?delegatedManagedIdentityResourceId + } + scope: managedDevOpsPool + } +] + +resource managedDevOpsPool_diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = [ + for (diagnosticSetting, index) in (diagnosticSettings ?? []): { + name: diagnosticSetting.?name ?? '${name}-diagnosticSettings' + properties: { + storageAccountId: diagnosticSetting.?storageAccountResourceId + workspaceId: diagnosticSetting.?workspaceResourceId + eventHubAuthorizationRuleId: diagnosticSetting.?eventHubAuthorizationRuleResourceId + eventHubName: diagnosticSetting.?eventHubName + metrics: [ + for group in (diagnosticSetting.?metricCategories ?? [{ category: 'AllMetrics' }]): { + category: group.category + enabled: group.?enabled ?? true + timeGrain: null + } + ] + logs: [ + for group in (diagnosticSetting.?logCategoriesAndGroups ?? [{ categoryGroup: 'allLogs' }]): { + categoryGroup: group.?categoryGroup + category: group.?category + enabled: group.?enabled ?? true + } + ] + marketplacePartnerId: diagnosticSetting.?marketplacePartnerResourceId + logAnalyticsDestinationType: diagnosticSetting.?logAnalyticsDestinationType + } + scope: managedDevOpsPool + } +] + +@description('The name of the Managed DevOps Pool.') +output name string = managedDevOpsPool.name + +@description('The resource ID of the Managed DevOps Pool.') +output resourceId string = managedDevOpsPool.id + +@description('The name of the resource group the Managed DevOps Pool resource was deployed into.') +output resourceGroupName string = resourceGroup().name + +@description('The location the Managed DevOps Pool resource was deployed into.') +output location string = managedDevOpsPool.location + +@description('The principal ID of the system assigned identity.') +output systemAssignedMIPrincipalId string? = managedDevOpsPool.?identity.?principalId + +type osProfileType = { + @description('Required. The logon type of the machine.') + logonType: ('Interactive' | 'Service') + + @description('Optional. The secret management settings of the machines in the pool.') + secretsManagementSettings: { + @description('Required. The secret management settings of the machines in the pool.') + keyExportable: bool + + @description('Required. The list of certificates to install on all machines in the pool.') + observedCertificates: string[] + + @description('Optional. Where to store certificates on the machine.') + certificateStoreLocation: string? + }? +} + +type storageProfileType = { + @description('Optional. The Azure SKU name of the machines in the pool.') + osDiskStorageAccountType: ('Premium' | 'StandardSSD' | 'Standard')? + + @description('Optional. A list of empty data disks to attach.') + dataDisks: { + @description('Optional. The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/.') + caching: ('None' | 'ReadOnly' | 'ReadWrite')? + + @description('Optional. The initial disk size in gigabytes.') + diskSizeGiB: int? + + @description('Optional. The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed.') + driveLetter: string? + + @description('Optional. The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS.') + storageAccountType: ('Premium_LRS' | 'Premium_ZRS' | 'StandardSSD_LRS' | 'StandardSSD_ZRS' | 'Standard_LRS')? + }[]? +}? + +type imageType = { + @description('Optional. List of aliases to reference the image by.') + aliases: string[]? + + @description('Optional. The percentage of the buffer to be allocated to this image.') + buffer: string? + + @description('Required. The image to use from a well-known set of images made available to customers.') + wellKnownImageName: string + + @description('Optional. The resource id of the image.') + resourceId: string? +}[] + +type organizationProfileType = { + @description('Required. Azure DevOps organization profile.') + kind: 'AzureDevOps' + + @description('Optional. The type of permission which determines which accounts are admins on the Azure DevOps pool.') + permissionProfile: { + @description('Optional. Determines who has admin permissions to the Azure DevOps pool.') + kind: ('CreatorOnly' | 'Inherit' | 'SpecificAccounts')? + + @description('Optional. Group email addresses.') + groups: string[]? + + @description('Optional. User email addresses.') + users: string[]? + }? + + @description('Required. The list of Azure DevOps organizations the pool should be present in..') + organizations: { + @description('Required. The Azure DevOps organization URL in which the pool should be created.') + url: string + + @description('Optional. List of projects in which the pool should be created.') + projects: string[]? + + @description('Optional. How many machines can be created at maximum in this organization out of the maximumConcurrency of the pool.') + @minValue(1) + @maxValue(10000) + parallelism: int? + }[] +} + +type dataDiskType = { + @description('Optional. The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/.') + caching: ('None' | 'ReadOnly' | 'ReadWrite')? + + @description('Optional. The initial disk size in gigabytes.') + diskSizeGiB: int? + + @description('Optional. The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed.') + driveLetter: string? + + @description('Optional. The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS.') + storageAccountType: ('Premium_LRS' | 'Premium_ZRS' | 'StandardSSD_LRS' | 'StandardSSD_ZRS' | 'Standard_LRS')? +}[]? + +type resourcePredictionsProfileAutomaticType = { + @description('Required. The stand-by agent scheme is determined based on historical demand.') + kind: 'Automatic' + + @description('Required. Determines the balance between cost and performance.') + predictionPreference: 'Balanced' | 'MostCostEffective' | 'MoreCostEffective' | 'MorePerformance' | 'BestPerformance' +} + +type resourcePredictionsProfileManualType = { + @description('Required. Customer provides the stand-by agent scheme.') + kind: 'Manual' +} + +type agentStatefulType = { + @description('Required. Stateful profile meaning that the machines will be returned to the pool after running a job.') + kind: 'Stateful' + + @description('Required. How long should stateful machines be kept around. The maximum is one week.') + maxAgentLifetime: string + + @description('Required. How long should the machine be kept around after it ran a workload when there are no stand-by agents. The maximum is one week.') + gracePeriodTimeSpan: string + + @description('Optional. Defines pool buffer/stand-by agents.') + resourcePredictions: object? + + @discriminator('kind') + @description('Optional. Determines how the stand-by scheme should be provided.') + resourcePredictionsProfile: (resourcePredictionsProfileAutomaticType | resourcePredictionsProfileManualType)? +} + +type agentStatelessType = { + @description('Required. Stateless profile meaning that the machines will be cleaned up after running a job.') + kind: 'Stateless' + + @description('Optional. Defines pool buffer/stand-by agents.') + resourcePredictions: { + @description('Required. The time zone in which the daysData is provided. To see the list of available time zones, see: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11#time-zones or via PowerShell command `(Get-TimeZone -ListAvailable).StandardName`.') + timeZone: string + + @description('Optional. The number of agents needed at a specific time.') + @metadata({ + example: ''' + [ + { // Monday + '09:00': 5 + '22:00': 0 + } + {} // Tuesday + {} // Wednesday + {} // Thursday + { // Friday + '09:00': 5 + '22:00': 0 + } + {} // Saturday + {} // Sunday + ] + ''' + }) + daysData: object[]? + }? + + @discriminator('kind') + @description('Optional. Determines how the stand-by scheme should be provided.') + resourcePredictionsProfile: (resourcePredictionsProfileAutomaticType | resourcePredictionsProfileManualType)? +} + +@discriminator('kind') +type agentProfileType = agentStatefulType | agentStatelessType + +type roleAssignmentType = { + @description('Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated.') + name: string? + + @description('Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: \'/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11\'.') + roleDefinitionIdOrName: string + + @description('Required. The principal ID of the principal (user/group/identity) to assign the role to.') + principalId: string + + @description('Optional. The principal type of the assigned principal ID.') + principalType: ('ServicePrincipal' | 'Group' | 'User' | 'ForeignGroup' | 'Device')? + + @description('Optional. The description of the role assignment.') + description: string? + + @description('Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase "foo_storage_container".') + condition: string? + + @description('Optional. Version of the condition.') + conditionVersion: '2.0'? + + @description('Optional. The Resource Id of the delegated managed identity resource.') + delegatedManagedIdentityResourceId: string? +}[]? + +type lockType = { + @description('Optional. Specify the name of lock.') + name: string? + + @description('Optional. Specify the type of lock.') + kind: ('CanNotDelete' | 'ReadOnly' | 'None')? +}? + +type diagnosticSettingType = { + @description('Optional. The name of diagnostic setting.') + name: string? + + @description('Optional. The name of logs that will be streamed. "allLogs" includes all possible logs for the resource. Set to `[]` to disable log collection.') + logCategoriesAndGroups: { + @description('Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here.') + category: string? + + @description('Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs.') + categoryGroup: string? + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. The name of metrics that will be streamed. "allMetrics" includes all possible metrics for the resource. Set to `[]` to disable metric collection.') + metricCategories: { + @description('Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics.') + category: string + + @description('Optional. Enable or disable the category explicitly. Default is `true`.') + enabled: bool? + }[]? + + @description('Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type.') + logAnalyticsDestinationType: ('Dedicated' | 'AzureDiagnostics')? + + @description('Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + workspaceResourceId: string? + + @description('Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + storageAccountResourceId: string? + + @description('Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to.') + eventHubAuthorizationRuleResourceId: string? + + @description('Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub.') + eventHubName: string? + + @description('Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.') + marketplacePartnerResourceId: string? +}[]? + +type managedIdentitiesType = { + @description('Optional. Enables system assigned managed identity on the resource.') + systemAssigned: bool? + + @description('Optional. The resource ID(s) to assign to the resource.') + userAssignedResourceIds: string[]? +}? diff --git a/avm/res/dev-ops-infrastructure/pool/main.json b/avm/res/dev-ops-infrastructure/pool/main.json new file mode 100644 index 0000000000..38edaf771a --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/main.json @@ -0,0 +1,1013 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "languageVersion": "2.0", + "contentVersion": "1.0.0.0", + "metadata": { + "_generator": { + "name": "bicep", + "version": "0.29.47.4906", + "templateHash": "9138966756251042726" + }, + "name": "Managed DevOps Pool", + "description": "This module deploys the Managed DevOps Pool resource.", + "owner": "Azure/module-maintainers" + }, + "definitions": { + "osProfileType": { + "type": "object", + "properties": { + "logonType": { + "type": "string", + "allowedValues": [ + "Interactive", + "Service" + ], + "metadata": { + "description": "Required. The logon type of the machine." + } + }, + "secretsManagementSettings": { + "type": "object", + "properties": { + "keyExportable": { + "type": "bool", + "metadata": { + "description": "Required. The secret management settings of the machines in the pool." + } + }, + "observedCertificates": { + "type": "array", + "items": { + "type": "string" + }, + "metadata": { + "description": "Required. The list of certificates to install on all machines in the pool." + } + }, + "certificateStoreLocation": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Where to store certificates on the machine." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The secret management settings of the machines in the pool." + } + } + } + }, + "storageProfileType": { + "type": "object", + "properties": { + "osDiskStorageAccountType": { + "type": "string", + "allowedValues": [ + "Premium", + "Standard", + "StandardSSD" + ], + "nullable": true, + "metadata": { + "description": "Optional. The Azure SKU name of the machines in the pool." + } + }, + "dataDisks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/." + } + }, + "diskSizeGiB": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The initial disk size in gigabytes." + } + }, + "driveLetter": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed." + } + }, + "storageAccountType": { + "type": "string", + "allowedValues": [ + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. A list of empty data disks to attach." + } + } + }, + "nullable": true + }, + "imageType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "aliases": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of aliases to reference the image by." + } + }, + "buffer": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The percentage of the buffer to be allocated to this image." + } + }, + "wellKnownImageName": { + "type": "string", + "metadata": { + "description": "Required. The image to use from a well-known set of images made available to customers." + } + }, + "resourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The resource id of the image." + } + } + } + } + }, + "organizationProfileType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "AzureDevOps" + ], + "metadata": { + "description": "Required. Azure DevOps organization profile." + } + }, + "permissionProfile": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "CreatorOnly", + "Inherit", + "SpecificAccounts" + ], + "nullable": true, + "metadata": { + "description": "Optional. Determines who has admin permissions to the Azure DevOps pool." + } + }, + "groups": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. Group email addresses." + } + }, + "users": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. User email addresses." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The type of permission which determines which accounts are admins on the Azure DevOps pool." + } + }, + "organizations": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "metadata": { + "description": "Required. The Azure DevOps organization URL in which the pool should be created." + } + }, + "projects": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. List of projects in which the pool should be created." + } + }, + "parallelism": { + "type": "int", + "nullable": true, + "minValue": 1, + "maxValue": 10000, + "metadata": { + "description": "Optional. How many machines can be created at maximum in this organization out of the maximumConcurrency of the pool." + } + } + } + }, + "metadata": { + "description": "Required. The list of Azure DevOps organizations the pool should be present in.." + } + } + } + }, + "dataDiskType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "caching": { + "type": "string", + "allowedValues": [ + "None", + "ReadOnly", + "ReadWrite" + ], + "nullable": true, + "metadata": { + "description": "Optional. The type of caching to be enabled for the data disks. The default value for caching is readwrite. For information about the caching options see: https://blogs.msdn.microsoft.com/windowsazurestorage/2012/06/27/exploring-windows-azure-drives-disks-and-images/." + } + }, + "diskSizeGiB": { + "type": "int", + "nullable": true, + "metadata": { + "description": "Optional. The initial disk size in gigabytes." + } + }, + "driveLetter": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The drive letter for the empty data disk. If not specified, it will be the first available letter. Letters A, C, D, and E are not allowed." + } + }, + "storageAccountType": { + "type": "string", + "allowedValues": [ + "Premium_LRS", + "Premium_ZRS", + "StandardSSD_LRS", + "StandardSSD_ZRS", + "Standard_LRS" + ], + "nullable": true, + "metadata": { + "description": "Optional. The storage Account type to be used for the data disk. If omitted, the default is Standard_LRS." + } + } + } + }, + "nullable": true + }, + "resourcePredictionsProfileAutomaticType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "Automatic" + ], + "metadata": { + "description": "Required. The stand-by agent scheme is determined based on historical demand." + } + }, + "predictionPreference": { + "type": "string", + "allowedValues": [ + "Balanced", + "BestPerformance", + "MoreCostEffective", + "MorePerformance", + "MostCostEffective" + ], + "metadata": { + "description": "Required. Determines the balance between cost and performance." + } + } + } + }, + "resourcePredictionsProfileManualType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "Manual" + ], + "metadata": { + "description": "Required. Customer provides the stand-by agent scheme." + } + } + } + }, + "agentStatefulType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "Stateful" + ], + "metadata": { + "description": "Required. Stateful profile meaning that the machines will be returned to the pool after running a job." + } + }, + "maxAgentLifetime": { + "type": "string", + "metadata": { + "description": "Required. How long should stateful machines be kept around. The maximum is one week." + } + }, + "gracePeriodTimeSpan": { + "type": "string", + "metadata": { + "description": "Required. How long should the machine be kept around after it ran a workload when there are no stand-by agents. The maximum is one week." + } + }, + "resourcePredictions": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Defines pool buffer/stand-by agents." + } + }, + "resourcePredictionsProfile": { + "type": "object", + "discriminator": { + "propertyName": "kind", + "mapping": { + "Automatic": { + "$ref": "#/definitions/resourcePredictionsProfileAutomaticType" + }, + "Manual": { + "$ref": "#/definitions/resourcePredictionsProfileManualType" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Determines how the stand-by scheme should be provided." + } + } + } + }, + "agentStatelessType": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "allowedValues": [ + "Stateless" + ], + "metadata": { + "description": "Required. Stateless profile meaning that the machines will be cleaned up after running a job." + } + }, + "resourcePredictions": { + "type": "object", + "properties": { + "timeZone": { + "type": "string", + "metadata": { + "description": "Required. The time zone in which the daysData is provided. To see the list of available time zones, see: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/default-time-zones?view=windows-11#time-zones or via PowerShell command `(Get-TimeZone -ListAvailable).StandardName`." + } + }, + "daysData": { + "type": "array", + "items": { + "type": "object" + }, + "nullable": true, + "metadata": { + "example": " [\n { // Monday\n '09:00': 5\n '22:00': 0\n }\n {} // Tuesday\n {} // Wednesday\n {} // Thursday\n { // Friday\n '09:00': 5\n '22:00': 0\n }\n {} // Saturday\n {} // Sunday\n ]\n ", + "description": "Optional. The number of agents needed at a specific time." + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Defines pool buffer/stand-by agents." + } + }, + "resourcePredictionsProfile": { + "type": "object", + "discriminator": { + "propertyName": "kind", + "mapping": { + "Automatic": { + "$ref": "#/definitions/resourcePredictionsProfileAutomaticType" + }, + "Manual": { + "$ref": "#/definitions/resourcePredictionsProfileManualType" + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. Determines how the stand-by scheme should be provided." + } + } + } + }, + "agentProfileType": { + "type": "object", + "discriminator": { + "propertyName": "kind", + "mapping": { + "Stateful": { + "$ref": "#/definitions/agentStatefulType" + }, + "Stateless": { + "$ref": "#/definitions/agentStatelessType" + } + } + } + }, + "roleAssignmentType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name (as GUID) of the role assignment. If not provided, a GUID will be generated." + } + }, + "roleDefinitionIdOrName": { + "type": "string", + "metadata": { + "description": "Required. The role to assign. You can provide either the display name of the role definition, the role definition GUID, or its fully qualified ID in the following format: '/providers/Microsoft.Authorization/roleDefinitions/c2f4ef07-c644-48eb-af81-4b1b4947fb11'." + } + }, + "principalId": { + "type": "string", + "metadata": { + "description": "Required. The principal ID of the principal (user/group/identity) to assign the role to." + } + }, + "principalType": { + "type": "string", + "allowedValues": [ + "Device", + "ForeignGroup", + "Group", + "ServicePrincipal", + "User" + ], + "nullable": true, + "metadata": { + "description": "Optional. The principal type of the assigned principal ID." + } + }, + "description": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The description of the role assignment." + } + }, + "condition": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The conditions on the role assignment. This limits the resources it can be assigned to. e.g.: @Resource[Microsoft.Storage/storageAccounts/blobServices/containers:ContainerName] StringEqualsIgnoreCase \"foo_storage_container\"." + } + }, + "conditionVersion": { + "type": "string", + "allowedValues": [ + "2.0" + ], + "nullable": true, + "metadata": { + "description": "Optional. Version of the condition." + } + }, + "delegatedManagedIdentityResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The Resource Id of the delegated managed identity resource." + } + } + } + }, + "nullable": true + }, + "lockType": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Specify the name of lock." + } + }, + "kind": { + "type": "string", + "allowedValues": [ + "CanNotDelete", + "None", + "ReadOnly" + ], + "nullable": true, + "metadata": { + "description": "Optional. Specify the type of lock." + } + } + }, + "nullable": true + }, + "diagnosticSettingType": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The name of diagnostic setting." + } + }, + "logCategoriesAndGroups": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category for a resource type this setting is applied to. Set the specific logs to collect here." + } + }, + "categoryGroup": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of a Diagnostic Log category group for a resource type this setting is applied to. Set to `allLogs` to collect all logs." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of logs that will be streamed. \"allLogs\" includes all possible logs for the resource. Set to `[]` to disable log collection." + } + }, + "metricCategories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "category": { + "type": "string", + "metadata": { + "description": "Required. Name of a Diagnostic Metric category for a resource type this setting is applied to. Set to `AllMetrics` to collect all metrics." + } + }, + "enabled": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enable or disable the category explicitly. Default is `true`." + } + } + } + }, + "nullable": true, + "metadata": { + "description": "Optional. The name of metrics that will be streamed. \"allMetrics\" includes all possible metrics for the resource. Set to `[]` to disable metric collection." + } + }, + "logAnalyticsDestinationType": { + "type": "string", + "allowedValues": [ + "AzureDiagnostics", + "Dedicated" + ], + "nullable": true, + "metadata": { + "description": "Optional. A string indicating whether the export to Log Analytics should use the default destination type, i.e. AzureDiagnostics, or use a destination type." + } + }, + "workspaceResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic log analytics workspace. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "storageAccountResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic storage account. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "eventHubAuthorizationRuleResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Resource ID of the diagnostic event hub authorization rule for the Event Hubs namespace in which the event hub should be created or streamed to." + } + }, + "eventHubName": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. Name of the diagnostic event hub within the namespace to which logs are streamed. Without this, an event hub is created for each log category. For security reasons, it is recommended to set diagnostic settings to send data to either storage account, log analytics workspace or event hub." + } + }, + "marketplacePartnerResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs." + } + } + } + }, + "nullable": true + }, + "managedIdentitiesType": { + "type": "object", + "properties": { + "systemAssigned": { + "type": "bool", + "nullable": true, + "metadata": { + "description": "Optional. Enables system assigned managed identity on the resource." + } + }, + "userAssignedResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true, + "metadata": { + "description": "Optional. The resource ID(s) to assign to the resource." + } + } + }, + "nullable": true + } + }, + "parameters": { + "name": { + "type": "string", + "metadata": { + "description": "Required. Name of the pool. It needs to be globally unique." + } + }, + "fabricProfileSkuName": { + "type": "string", + "metadata": { + "description": "Required. The Azure SKU name of the machines in the pool." + } + }, + "concurrency": { + "type": "int", + "minValue": 1, + "maxValue": 10000, + "metadata": { + "description": "Required. Defines how many resources can there be created at any given time." + } + }, + "images": { + "$ref": "#/definitions/imageType", + "metadata": { + "description": "Required. The VM images of the machines in the pool." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Optional. The geo-location where the resource lives." + } + }, + "devCenterProjectResourceId": { + "type": "string", + "metadata": { + "description": "Required. The resource id of the DevCenter Project the pool belongs to." + } + }, + "subnetResourceId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "Optional. The subnet id on which to put all machines created in the pool." + } + }, + "agentProfile": { + "$ref": "#/definitions/agentProfileType", + "metadata": { + "description": "Required. Defines how the machine will be handled once it executed a job." + } + }, + "osProfile": { + "$ref": "#/definitions/osProfileType", + "defaultValue": { + "logonType": "Interactive", + "secretsManagementSettings": { + "keyExportable": false, + "observedCertificates": [] + } + }, + "metadata": { + "description": "Optional. The OS profile of the agents in the pool." + } + }, + "storageProfile": { + "$ref": "#/definitions/storageProfileType", + "metadata": { + "description": "Optional. The storage profile of the machines in the pool." + } + }, + "organizationProfile": { + "$ref": "#/definitions/organizationProfileType", + "metadata": { + "description": "Required. Defines the organization in which the pool will be used." + } + }, + "tags": { + "type": "object", + "nullable": true, + "metadata": { + "description": "Optional. Tags of the resource." + } + }, + "lock": { + "$ref": "#/definitions/lockType", + "metadata": { + "description": "Optional. The lock settings of the service." + } + }, + "roleAssignments": { + "$ref": "#/definitions/roleAssignmentType", + "metadata": { + "description": "Optional. Array of role assignments to create." + } + }, + "enableTelemetry": { + "type": "bool", + "defaultValue": true, + "metadata": { + "description": "Optional. Enable/Disable usage telemetry for module." + } + }, + "diagnosticSettings": { + "$ref": "#/definitions/diagnosticSettingType", + "metadata": { + "description": "Optional. The diagnostic settings of the service." + } + }, + "managedIdentities": { + "$ref": "#/definitions/managedIdentitiesType", + "metadata": { + "example": " {\n systemAssigned: true,\n userAssignedResourceIds: [\n '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myManagedIdentity'\n ]\n }\n {\n systemAssigned: true\n }\n ", + "description": "Optional. The managed service identities assigned to this resource." + } + } + }, + "variables": { + "copy": [ + { + "name": "formattedRoleAssignments", + "count": "[length(coalesce(parameters('roleAssignments'), createArray()))]", + "input": "[union(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')], createObject('roleDefinitionId', coalesce(tryGet(variables('builtInRoleNames'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName), if(contains(coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, '/providers/Microsoft.Authorization/roleDefinitions/'), coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName, subscriptionResourceId('Microsoft.Authorization/roleDefinitions', coalesce(parameters('roleAssignments'), createArray())[copyIndex('formattedRoleAssignments')].roleDefinitionIdOrName)))))]" + } + ], + "builtInRoleNames": { + "Contributor": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "Owner": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '8e3af657-a8ff-443c-a75c-2fe8c4bcb635')]", + "Reader": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]", + "Role Based Access Control Administrator (Preview)": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'f58310d9-a9f6-439a-9e8d-f62e7b41a168')]", + "User Access Administrator": "[subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '18d7d88d-d35e-4fb5-a5c3-7773c20a72d9')]" + }, + "formattedUserAssignedIdentities": "[reduce(map(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createArray()), lambda('id', createObject(format('{0}', lambdaVariables('id')), createObject()))), createObject(), lambda('cur', 'next', union(lambdaVariables('cur'), lambdaVariables('next'))))]", + "identity": "[if(not(empty(parameters('managedIdentities'))), createObject('type', if(coalesce(tryGet(parameters('managedIdentities'), 'systemAssigned'), false()), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'SystemAssigned,UserAssigned', 'SystemAssigned'), if(not(empty(coalesce(tryGet(parameters('managedIdentities'), 'userAssignedResourceIds'), createObject()))), 'UserAssigned', 'None')), 'userAssignedIdentities', if(not(empty(variables('formattedUserAssignedIdentities'))), variables('formattedUserAssignedIdentities'), null())), null())]" + }, + "resources": { + "avmTelemetry": { + "condition": "[parameters('enableTelemetry')]", + "type": "Microsoft.Resources/deployments", + "apiVersion": "2024-03-01", + "name": "[format('46d3xbcp.res.devopsinfrastructure-pool.{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" + } + } + } + } + }, + "managedDevOpsPool": { + "type": "Microsoft.DevOpsInfrastructure/pools", + "apiVersion": "2024-04-04-preview", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "tags": "[parameters('tags')]", + "identity": "[variables('identity')]", + "properties": { + "agentProfile": "[parameters('agentProfile')]", + "devCenterProjectResourceId": "[parameters('devCenterProjectResourceId')]", + "fabricProfile": { + "sku": { + "name": "[parameters('fabricProfileSkuName')]" + }, + "networkProfile": "[if(not(empty(parameters('subnetResourceId'))), createObject('subnetId', parameters('subnetResourceId')), null())]", + "osProfile": "[parameters('osProfile')]", + "storageProfile": "[parameters('storageProfile')]", + "kind": "Vmss", + "images": "[parameters('images')]" + }, + "maximumConcurrency": "[parameters('concurrency')]", + "organizationProfile": "[parameters('organizationProfile')]" + } + }, + "managedDevOpsPool_lock": { + "condition": "[and(not(empty(coalesce(parameters('lock'), createObject()))), not(equals(tryGet(parameters('lock'), 'kind'), 'None')))]", + "type": "Microsoft.Authorization/locks", + "apiVersion": "2020-05-01", + "scope": "[format('Microsoft.DevOpsInfrastructure/pools/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(parameters('lock'), 'name'), format('lock-{0}', parameters('name')))]", + "properties": { + "level": "[coalesce(tryGet(parameters('lock'), 'kind'), '')]", + "notes": "[if(equals(tryGet(parameters('lock'), 'kind'), 'CanNotDelete'), 'Cannot delete resource or child resources.', 'Cannot delete or modify the resource or child resources.')]" + }, + "dependsOn": [ + "managedDevOpsPool" + ] + }, + "managedDevOpsPool_roleAssignments": { + "copy": { + "name": "managedDevOpsPool_roleAssignments", + "count": "[length(coalesce(variables('formattedRoleAssignments'), createArray()))]" + }, + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2022-04-01", + "scope": "[format('Microsoft.DevOpsInfrastructure/pools/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'name'), guid(resourceId('Microsoft.DevOpsInfrastructure/pools', parameters('name')), coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId, coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId))]", + "properties": { + "roleDefinitionId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].roleDefinitionId]", + "principalId": "[coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()].principalId]", + "description": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'description')]", + "principalType": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'principalType')]", + "condition": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition')]", + "conditionVersion": "[if(not(empty(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'condition'))), coalesce(tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'conditionVersion'), '2.0'), null())]", + "delegatedManagedIdentityResourceId": "[tryGet(coalesce(variables('formattedRoleAssignments'), createArray())[copyIndex()], 'delegatedManagedIdentityResourceId')]" + }, + "dependsOn": [ + "managedDevOpsPool" + ] + }, + "managedDevOpsPool_diagnosticSettings": { + "copy": { + "name": "managedDevOpsPool_diagnosticSettings", + "count": "[length(coalesce(parameters('diagnosticSettings'), createArray()))]" + }, + "type": "Microsoft.Insights/diagnosticSettings", + "apiVersion": "2021-05-01-preview", + "scope": "[format('Microsoft.DevOpsInfrastructure/pools/{0}', parameters('name'))]", + "name": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'name'), format('{0}-diagnosticSettings', parameters('name')))]", + "properties": { + "copy": [ + { + "name": "metrics", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics'))))]", + "input": { + "category": "[coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')].category]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'metricCategories'), createArray(createObject('category', 'AllMetrics')))[copyIndex('metrics')], 'enabled'), true())]", + "timeGrain": null + } + }, + { + "name": "logs", + "count": "[length(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs'))))]", + "input": { + "categoryGroup": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'categoryGroup')]", + "category": "[tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'category')]", + "enabled": "[coalesce(tryGet(coalesce(tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logCategoriesAndGroups'), createArray(createObject('categoryGroup', 'allLogs')))[copyIndex('logs')], 'enabled'), true())]" + } + } + ], + "storageAccountId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'storageAccountResourceId')]", + "workspaceId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'workspaceResourceId')]", + "eventHubAuthorizationRuleId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubAuthorizationRuleResourceId')]", + "eventHubName": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'eventHubName')]", + "marketplacePartnerId": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'marketplacePartnerResourceId')]", + "logAnalyticsDestinationType": "[tryGet(coalesce(parameters('diagnosticSettings'), createArray())[copyIndex()], 'logAnalyticsDestinationType')]" + }, + "dependsOn": [ + "managedDevOpsPool" + ] + } + }, + "outputs": { + "name": { + "type": "string", + "metadata": { + "description": "The name of the Managed DevOps Pool." + }, + "value": "[parameters('name')]" + }, + "resourceId": { + "type": "string", + "metadata": { + "description": "The resource ID of the Managed DevOps Pool." + }, + "value": "[resourceId('Microsoft.DevOpsInfrastructure/pools', parameters('name'))]" + }, + "resourceGroupName": { + "type": "string", + "metadata": { + "description": "The name of the resource group the Managed DevOps Pool resource was deployed into." + }, + "value": "[resourceGroup().name]" + }, + "location": { + "type": "string", + "metadata": { + "description": "The location the Managed DevOps Pool resource was deployed into." + }, + "value": "[reference('managedDevOpsPool', '2024-04-04-preview', 'full').location]" + }, + "systemAssignedMIPrincipalId": { + "type": "string", + "nullable": true, + "metadata": { + "description": "The principal ID of the system assigned identity." + }, + "value": "[tryGet(tryGet(reference('managedDevOpsPool', '2024-04-04-preview', 'full'), 'identity'), 'principalId')]" + } + } +} \ No newline at end of file diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/dependencies.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/dependencies.bicep new file mode 100644 index 0000000000..367ae50af8 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/dependencies.bicep @@ -0,0 +1,27 @@ +@description('Required. The name of the Dev Center.') +param devCenterName string + +@description('Required. The name of the Dev Center Project.') +param devCenterProjectName string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +resource devCenter 'Microsoft.DevCenter/devcenters@2024-02-01' = { + name: devCenterName + location: location +} + +resource devCenterProject 'Microsoft.DevCenter/projects@2024-02-01' = { + name: devCenterProjectName + location: location + properties: { + devCenterId: devCenter.id + } +} + +@description('The resource ID of the created DevCenter.') +output devCenterResourceId string = devCenter.id + +@description('The resource ID of the created DevCenter Project.') +output devCenterProjectResourceId string = devCenterProject.id diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep new file mode 100644 index 0000000000..c21a95f505 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/defaults/main.test.bicep @@ -0,0 +1,78 @@ +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}-dev-ops-infrastructure.pool-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'mdpmin' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@secure() +param azureDevOpsOrganizationName string = '' + +// ============ // +// Dependencies // +// ============ // +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' + devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' + } +} + +// ================= // +// General resources // +// ================= // +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + agentProfile: { + kind: 'Stateless' + } + concurrency: 1 + devCenterProjectResourceId: nestedDependencies.outputs.devCenterProjectResourceId + images: [ + { + wellKnownImageName: 'windows-2022/latest' + } + ] + fabricProfileSkuName: 'Standard_DS2_v2' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + url: 'https://dev.azure.com/${azureDevOpsOrganizationName}' + } + ] + } + } + } +] diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/dependencies.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/dependencies.bicep new file mode 100644 index 0000000000..11333fc560 --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/dependencies.bicep @@ -0,0 +1,89 @@ +@description('Required. The name of the Dev Center.') +param devCenterName string + +@description('Required. The name of the Dev Center Project.') +param devCenterProjectName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the virtual network to create.') +param virtualNetworkName string + +@description('Required. The object ID of the Entra ID-provided DevOpsInfrastructure principal.') +param devOpsInfrastructureObjectID string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +var addressPrefix = '192.168.1.0' + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource devCenter 'Microsoft.DevCenter/devcenters@2024-02-01' = { + name: devCenterName + location: location +} + +resource devCenterProject 'Microsoft.DevCenter/projects@2024-02-01' = { + name: devCenterProjectName + location: location + properties: { + devCenterId: devCenter.id + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + cidrSubnet(addressPrefix, 24, 0) + ] + } + subnets: [ + { + name: 'default' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 0) + delegations: [ + { + name: 'Microsoft.DevOpsInfrastructure/pools' + properties: { + serviceName: 'Microsoft.DevOpsInfrastructure/pools' + } + } + ] + } + } + ] + } +} + +// Network Contributor role assignment +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(subscription().subscriptionId, 'DevOpsInfrastructure', 'Network Contributor', 'max') + properties: { + principalId: devOpsInfrastructureObjectID // DevOpsInfrastructure service principal + #disable-next-line use-resource-id-functions + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + principalType: 'ServicePrincipal' + } + scope: virtualNetwork +} + +@description('The resource ID of the created DevCenter.') +output devCenterResourceId string = devCenter.id + +@description('The resource ID of the created DevCenter Project.') +output devCenterProjectResourceId string = devCenterProject.id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created subnet.') +output subnetResourceId string = first(virtualNetwork.properties.subnets)!.id diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep new file mode 100644 index 0000000000..7416f2934c --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/max/main.test.bicep @@ -0,0 +1,171 @@ +targetScope = 'subscription' + +metadata name = 'Using large parameter set' +metadata description = 'This instance deploys the module with most of its features enabled.' + +// ========== // +// Parameters // +// ========== // + +@description('Optional. The name of the resource group to deploy for testing purposes.') +@maxLength(90) +param resourceGroupName string = 'dep-${namePrefix}-dev-ops-infrastructure.pool-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'mdpmax' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@secure() +param azureDevOpsOrganizationName string = '' + +@description('Required. Name of the Azure DevOps Max Project. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsProjectName\'.') +@secure() +param azureDevOpsProjectName string = '' + +@description('Required. The object ID of the Entra ID-provided DevOpsInfrastructure principal. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-DevOpsInfrastructureObjectID\'.') +@secure() +param devOpsInfrastructureObjectID string = '' + +// ============ // +// Dependencies // +// ============ // +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' + devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + devOpsInfrastructureObjectID: devOpsInfrastructureObjectID + } +} + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + agentProfile: { + kind: 'Stateless' + resourcePredictions: { + timeZone: 'Central Europe Standard Time' + daysData: [ + // Monday + { + '09:00:00': 1 + '17:00:00': 0 + } + // Tuesday + {} + // Wednesday + {} + // Thursday + {} + // Friday + { + '09:00:00': 1 + '17:00:00': 0 + } + // Saturday + {} + // Sunday + {} + ] + } + resourcePredictionsProfile: { + kind: 'Automatic' + predictionPreference: 'Balanced' + } + } + concurrency: 1 + devCenterProjectResourceId: nestedDependencies.outputs.devCenterProjectResourceId + images: [ + { + aliases: [ + 'windows-2022' + ] + buffer: '*' + wellKnownImageName: 'windows-2022/latest' + } + ] + fabricProfileSkuName: 'Standard_D2_v2' + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + url: 'https://dev.azure.com/${azureDevOpsOrganizationName}' + parallelism: 1 + projects: [ + azureDevOpsProjectName + ] + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + storageProfile: { + osDiskStorageAccountType: 'Standard' + dataDisks: [ + { + caching: 'ReadWrite' + diskSizeGiB: 100 + driveLetter: 'B' + storageAccountType: 'Standard_LRS' + } + ] + } + subnetResourceId: nestedDependencies.outputs.subnetResourceId + roleAssignments: [ + { + roleDefinitionIdOrName: subscriptionResourceId( + 'Microsoft.Authorization/roleDefinitions', + 'acdd72a7-3385-48ef-bd42-f606fba81ae7' + ) + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: 'b24988ac-6180-42a0-ab88-20f7382dd24c' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + { + roleDefinitionIdOrName: 'Owner' + principalId: nestedDependencies.outputs.managedIdentityPrincipalId + principalType: 'ServicePrincipal' + } + ] + lock: { + kind: 'CanNotDelete' + name: 'myCustomLockName' + } + tags: { + 'hidden-title': 'This is visible in the resource name' + Environment: 'Non-Prod' + Role: 'DeploymentValidation' + } + } + } +] diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/dependencies.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/dependencies.bicep new file mode 100644 index 0000000000..0afb813bac --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/dependencies.bicep @@ -0,0 +1,89 @@ +@description('Required. The name of the Dev Center.') +param devCenterName string + +@description('Required. The name of the Dev Center Project.') +param devCenterProjectName string + +@description('Required. The name of the Managed Identity to create.') +param managedIdentityName string + +@description('Required. The name of the virtual network to create.') +param virtualNetworkName string + +@description('Required. The object ID of the Entra ID-provided DevOpsInfrastructure principal.') +param devOpsInfrastructureObjectID string + +@description('Optional. The location to deploy resources to.') +param location string = resourceGroup().location + +var addressPrefix = '192.168.1.0' + +resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2018-11-30' = { + name: managedIdentityName + location: location +} + +resource devCenter 'Microsoft.DevCenter/devcenters@2024-02-01' = { + name: devCenterName + location: location +} + +resource devCenterProject 'Microsoft.DevCenter/projects@2024-02-01' = { + name: devCenterProjectName + location: location + properties: { + devCenterId: devCenter.id + } +} + +resource virtualNetwork 'Microsoft.Network/virtualNetworks@2021-02-01' = { + name: virtualNetworkName + location: location + properties: { + addressSpace: { + addressPrefixes: [ + cidrSubnet(addressPrefix, 24, 0) + ] + } + subnets: [ + { + name: 'default' + properties: { + addressPrefix: cidrSubnet(addressPrefix, 24, 0) + delegations: [ + { + name: 'Microsoft.DevOpsInfrastructure/pools' + properties: { + serviceName: 'Microsoft.DevOpsInfrastructure/pools' + } + } + ] + } + } + ] + } +} + +// Network Contributor role assignment +resource roleAssignments 'Microsoft.Authorization/roleAssignments@2020-04-01-preview' = { + name: guid(subscription().subscriptionId, 'DevOpsInfrastructure', 'Network Contributor', 'waf') + properties: { + principalId: devOpsInfrastructureObjectID // DevOpsInfrastructure service principal + #disable-next-line use-resource-id-functions + roleDefinitionId: '/providers/Microsoft.Authorization/roleDefinitions/4d97b98b-1d4f-4787-a291-c67834d212e7' + principalType: 'ServicePrincipal' + } + scope: virtualNetwork +} + +@description('The resource ID of the created DevCenter.') +output devCenterResourceId string = devCenter.id + +@description('The resource ID of the created DevCenter Project.') +output devCenterProjectResourceId string = devCenterProject.id + +@description('The principal ID of the created Managed Identity.') +output managedIdentityPrincipalId string = managedIdentity.properties.principalId + +@description('The resource ID of the created subnet.') +output subnetResourceId string = first(virtualNetwork.properties.subnets)!.id diff --git a/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep new file mode 100644 index 0000000000..fdf015444f --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/tests/e2e/waf-aligned/main.test.bicep @@ -0,0 +1,127 @@ +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}-dev-ops-infrastructure.pool-${serviceShort}-rg' + +@description('Optional. The location to deploy resources to.') +param resourceLocation 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 = 'mdpwaf' + +@description('Optional. A token to inject into the name of each resource.') +param namePrefix string = '#_namePrefix_#' + +@description('Required. Name of the Azure DevOps Organization. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsOrganizationName\'.') +@secure() +param azureDevOpsOrganizationName string = '' + +@description('Required. Name of the Azure DevOps WAF Project. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-AzureDevOpsProjectName\'.') +@secure() +param azureDevOpsProjectName string = '' + +@description('Required. The object ID of the Entra ID-provided DevOpsInfrastructure principal. This value is tenant-specific and must be stored in the CI Key Vault in a secret named \'CI-DevOpsInfrastructureObjectID\'.') +@secure() +param devOpsInfrastructureObjectID string = '' + +// ============ // +// Dependencies // +// ============ // +module nestedDependencies 'dependencies.bicep' = { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-nestedDependencies' + params: { + devCenterName: 'dep-${namePrefix}-dc-${serviceShort}' + devCenterProjectName: 'dep-${namePrefix}-dcp-${serviceShort}' + managedIdentityName: 'dep-${namePrefix}-msi-${serviceShort}' + virtualNetworkName: 'dep-${namePrefix}-vnet-${serviceShort}' + devOpsInfrastructureObjectID: devOpsInfrastructureObjectID + } +} + +// General resources +// ================= +resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: resourceGroupName + location: resourceLocation +} + +// ============== // +// Test Execution // +// ============== // + +@batchSize(1) +module testDeployment '../../../main.bicep' = [ + for iteration in ['init', 'idem']: { + scope: resourceGroup + name: '${uniqueString(deployment().name, resourceLocation)}-test-${serviceShort}-${iteration}' + params: { + name: '${namePrefix}${serviceShort}001' + location: resourceLocation + agentProfile: { + kind: 'Stateless' + resourcePredictions: { + timeZone: 'Central Europe Standard Time' + daysData: [ + // Monday + { + '09:00:00': 1 + '17:00:00': 0 + } + // Tuesday + {} + // Wednesday + {} + // Thursday + {} + // Friday + { + '09:00:00': 1 + '17:00:00': 0 + } + // Saturday + {} + // Sunday + {} + ] + } + resourcePredictionsProfile: { + kind: 'Automatic' + predictionPreference: 'Balanced' + } + } + concurrency: 1 + devCenterProjectResourceId: nestedDependencies.outputs.devCenterProjectResourceId + images: [ + { + wellKnownImageName: 'windows-2022/latest' + } + ] + fabricProfileSkuName: 'Standard_D2_v2' + subnetResourceId: nestedDependencies.outputs.subnetResourceId + organizationProfile: { + kind: 'AzureDevOps' + organizations: [ + { + url: 'https://dev.azure.com/${azureDevOpsOrganizationName}' + projects: [ + azureDevOpsProjectName + ] + parallelism: 1 + } + ] + permissionProfile: { + kind: 'CreatorOnly' + } + } + } + } +] diff --git a/avm/res/dev-ops-infrastructure/pool/version.json b/avm/res/dev-ops-infrastructure/pool/version.json new file mode 100644 index 0000000000..8def869ede --- /dev/null +++ b/avm/res/dev-ops-infrastructure/pool/version.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://aka.ms/bicep-registry-module-version-file-schema#", + "version": "0.1", + "pathFilters": [ + "./main.json" + ] +}