Merge pull request #42 from AlexanderSehr/users/alsehr/managedPoolAVM

feat: Implemented published 'Managed DevOps Pool' AVM module
This commit is contained in:
Alexander Sehr 2024-10-12 10:57:34 +02:00 коммит произвёл GitHub
Родитель 8fae27ac7b 9fe814d3eb
Коммит ed93813a32
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
7 изменённых файлов: 120 добавлений и 323 удалений

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

@ -12,13 +12,13 @@ variables:
## GENERAL ##
#############
#region shared
vmImage_sbx: '' # 'ubuntu-latest' # Use this for microsoft-hosted agents
vmImage_dev: '' # 'ubuntu-latest' # Use this for microsoft-hosted agents
vmImage_prd: '' # 'ubuntu-latest' # Use this for microsoft-hosted agents
vmImage_sbx: 'ubuntu-latest' # Use this for microsoft-hosted agents
vmImage_dev: 'ubuntu-latest' # Use this for microsoft-hosted agents
vmImage_prd: 'ubuntu-latest' # Use this for microsoft-hosted agents
poolName_sbx: 'core-vmss' # Use this for self-hosted agents
poolName_dev: 'core-vmss' # Use this for self-hosted agents
poolName_prd: 'core-vmss' # Use this for self-hosted agents
poolName_sbx: '' # Use this for self-hosted agents
poolName_dev: '' # Use this for self-hosted agents
poolName_prd: '' # Use this for self-hosted agents
serviceConnection_sbx: '<PrivateConnection>'
serviceConnection_dev: '<PrivateConnection>'

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

@ -23,7 +23,7 @@ param waitForImageBuild bool = true
/////////////////////////////
module imageDeployment '../templates/image.deploy.bicep' = {
name: '${uniqueString(deployment().name)}-image-sbx'
name: '${uniqueString(deployment().name, resourceLocation)}-image-sbx'
params: {
resourceLocation: resourceLocation
deploymentsToPerform: deploymentsToPerform

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

@ -107,7 +107,7 @@ param deploymentsToPerform string = 'Only assets & image'
// =========== //
module imageConstruct 'br/public:avm/ptn/virtual-machine-images/azure-image-builder:0.1.1' = {
name: '${deployment().name}-image-construct'
name: '${uniqueString(deployment().name, resourceLocation)}-image-construct'
params: {
deploymentsToPerform: deploymentsToPerform
resourceGroupName: resourceGroupName

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

@ -7,15 +7,11 @@ targetScope = 'subscription'
@description('Optional. Specifies the location for resources.')
param resourceLocation string = 'NorthEurope'
///////////////////////////////
// Deployment Properties //
///////////////////////////////
/////////////////////////////
// Template Deployment //
/////////////////////////////
module managedDevOpsPoolDeployment '../templates/pool.deploy.bicep' = {
name: '${uniqueString(deployment().name)}-managedPool-sbx'
name: '${uniqueString(deployment().name, resourceLocation)}-managedPool-sbx'
params: {
resourceLocation: resourceLocation
computeGalleryName: '<computeGalleryName>'

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

@ -0,0 +1,28 @@
@description('Required. ')
param location string
@description('Required. The name of the Dev Center to use for the DevOps Infrastructure Pool. Must be lower case and may contain hyphens.')
@minLength(3)
@maxLength(26)
param devCenterName string
@description('Required. The name of the Dev Center project to use for the DevOps Infrastructure Pool.')
@minLength(3)
@maxLength(63)
param devCenterProjectName string
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 Dev Center project.')
output devCenterProjectResourceId string = devCenterProject.id

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

@ -1,290 +0,0 @@
@description('Required. ')
param location string
@description('Required. Defines how many resources can there be created at any given time.')
@minValue(1)
@maxValue(10000)
param maximumConcurrency int
@description('Required. The name of the subnet the agents should be deployed into.')
param subnetName string
@description('Required. The resource Id of the Virtual Network the agents should be deployed into.')
param virtualNetworkResourceId string
@description('Required. The name of the Azure DevOps agent pool to create.')
param poolName string
@description('Required. The name of the Azure DevOps organization to register the agent pools in.')
param organizationName string
@description('Optional. The Azure DevOps projects to register the agent pools in. In none is provided, the pool is only registered in the organization.')
param projectNames string[]?
@description('Required. The name of the Dev Center to use for the DevOps Infrastructure Pool. Must be lower case and may contain hyphens.')
@minLength(3)
@maxLength(26)
param devCenterName string
@description('Required. The name of the Dev Center project to use for the DevOps Infrastructure Pool.')
@minLength(3)
@maxLength(63)
param devCenterProjectName string
@description('Optional. The Azure SKU name of the machines in the pool.')
param poolSize string = 'Standard_B1ms'
@description('Optional. Defines how the machine will be handled once it executed a job.')
param agentProfile agentProfileType = {
kind: 'Stateless'
}
@description('Required. The object ID (principal id) of the \'DevOpsInfrastructure\' Enterprise Application in your tenant.')
param devOpsInfrastructureEnterpriseApplicationObjectId string
@description('Required. The name of the Azure Compute Gallery that hosts the image of the Managed DevOps Pool.')
param computeGalleryName string
@description('Required. The name of Image Definition of the Azure Compute Gallery that hosts the image of the Managed DevOps Pool.')
param computeGalleryImageDefinitionName string
@description('Optional. The version of the image to use in the Managed DevOps Pool.')
param imageVersion string = 'latest' // Note, 'latest' is not supported by resource type
@description('Optional. The managed identity definition for the Managed DevOps Pool.')
param poolManagedIdentities managedIdentitiesType?
var formattedUserAssignedIdentities = reduce(
map((poolManagedIdentities.?userAssignedResourceIds ?? []), (id) => { '${id}': {} }),
{},
(cur, next) => union(cur, next)
) // Converts the flat array to an object like { '${id1}': {}, '${id2}': {} }
var poolIdentity = !empty(poolManagedIdentities)
? {
type: !empty(poolManagedIdentities.?userAssignedResourceIds ?? {}) ? 'UserAssigned' : 'None'
userAssignedIdentities: !empty(formattedUserAssignedIdentities) ? formattedUserAssignedIdentities : null
}
: null
resource computeGallery 'Microsoft.Compute/galleries@2022-03-03' existing = {
name: computeGalleryName
resource imageDefinition 'images@2022-03-03' existing = {
name: computeGalleryImageDefinitionName
resource version 'versions@2022-03-03' existing = {
name: imageVersion
}
}
}
resource vnet 'Microsoft.Network/virtualNetworks@2024-01-01' existing = {
name: last(split(virtualNetworkResourceId, '/'))
resource subnet 'subnets@2024-01-01' existing = {
name: subnetName
}
}
resource imageVersionPermission 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(
computeGallery::imageDefinition.id,
devOpsInfrastructureEnterpriseApplicationObjectId,
subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')
)
properties: {
principalId: devOpsInfrastructureEnterpriseApplicationObjectId
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'acdd72a7-3385-48ef-bd42-f606fba81ae7'
) // Reader
principalType: 'ServicePrincipal'
}
scope: computeGallery::imageDefinition // ::imageVersion Not using imageVersion as scope to enable to principal to find 'latest'. A role assignment on 'latest' is not possible
}
resource vnetPermission 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(
vnet.id,
devOpsInfrastructureEnterpriseApplicationObjectId,
subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4d97b98b-1d4f-4787-a291-c67834d212e7')
)
properties: {
principalId: devOpsInfrastructureEnterpriseApplicationObjectId
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'4d97b98b-1d4f-4787-a291-c67834d212e7'
) // Network Contributor
principalType: 'ServicePrincipal'
}
scope: vnet
}
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
}
}
// Requires: https://github.com/Azure/bicep-registry-modules/pull/3401
// module pool 'br/public:avm/res/dev-ops-infrastructure/pool:0.1.0' = {
// name:
// params: {
// name: poolName
// agentProfile: agentProfile
// concurrency: maximumConcurrency
// devCenterProjectResourceId: devCenterProject.id
// fabricProfileSkuName: devOpsInfrastructurePoolSize
// images: [
// {
// resourceId: computeGallery::imageDefinition::imageVersion.id
// }
// ]
// organizationProfile: {
// kind: 'AzureDevOps'
// organizations: [
// {
// url: 'https://dev.azure.com/${organizationName}'
// projects: projectNames
// }
// ]
// }
// }
// }
resource name 'Microsoft.DevOpsInfrastructure/pools@2024-04-04-preview' = {
name: poolName
location: location
identity: poolIdentity
properties: {
maximumConcurrency: maximumConcurrency
agentProfile: agentProfile
organizationProfile: {
kind: 'AzureDevOps'
organizations: [
{
url: 'https://dev.azure.com/${organizationName}'
projects: projectNames
}
]
}
devCenterProjectResourceId: devCenterProject.id
fabricProfile: {
sku: {
name: poolSize
}
kind: 'Vmss'
images: [
{
resourceId: computeGallery::imageDefinition::version.id
}
]
networkProfile: {
subnetId: vnet::subnet.id
}
}
}
dependsOn: [
imageVersionPermission
vnetPermission
]
}
/////////////////////
// Definitions //
/////////////////////
@export()
@discriminator('kind')
type agentProfileType = agentStatefulType | agentStatelessType
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: '''
[
{} // Sunday
{ // Monday
'09:00:00': 1
'17:00:00': 0
}
{ // Tuesday
'09:00:00': 1
'17:00:00': 0
}
{ // Wednesday
'09:00:00': 1
'17:00:00': 0
}
{ // Thursday
'09:00:00': 1
'17:00:00': 0
}
{ // Friday
'09:00:00': 1
'17:00:00': 0
}
{} // Saturday
]
'''
})
daysData: object[]?
}?
@discriminator('kind')
@description('Optional. Determines how the stand-by scheme should be provided.')
resourcePredictionsProfile: (resourcePredictionsProfileAutomaticType | resourcePredictionsProfileManualType)?
}
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'
}
@export()
type managedIdentitiesType = {
@description('Optional. The resource ID(s) to assign to the resource. Required if a user assigned identity is used for encryption.')
userAssignedResourceIds: string[]?
}

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

@ -28,6 +28,9 @@ param virtualNetworkSubnets array = [
}
]
@description('Required. The name of the Resource Group containing the Azure Compute Gallery that hosts the image of the Managed DevOps Pool.')
param computeGalleryResourceGroupName string = resourceGroupName
@description('Required. The name of the Azure Compute Gallery that hosts the image of the Managed DevOps Pool.')
param computeGalleryName string
@ -49,11 +52,14 @@ param poolMaximumConcurrency int = 1
param poolSize string = 'Standard_B1ms'
@description('Optional. The managed identity definition for the Managed DevOps Pool.')
import { managedIdentitiesType } from './nestedPool.bicep'
param poolManagedIdentities managedIdentitiesType?
import { managedIdentitiesType } from 'br/public:avm/res/dev-ops-infrastructure/pool:0.1.1'
param poolManagedIdentities managedIdentitiesType
// import { managedIdentityOnlyUserAssignedType } from 'br/public:avm/utl/types/avm-common-types:0.1.0'
// param poolManagedIdentities managedIdentityOnlyUserAssignedType?
@description('Optional. Defines how the machine will be handled once it executed a job.')
import { agentProfileType } from './nestedPool.bicep'
import { agentProfileType } from 'br/public:avm/res/dev-ops-infrastructure/pool:0.1.1'
param poolAgentProfile agentProfileType = {
kind: 'Stateless'
}
@ -93,7 +99,7 @@ resource rg 'Microsoft.Resources/resourceGroups@2024-03-01' = {
// Network Security Group
module nsg 'br/public:avm/res/network/network-security-group:0.3.0' = {
name: '${deployment().name}-nsg'
name: '${uniqueString(deployment().name, resourceLocation)}-nsg'
scope: rg
params: {
name: networkSecurityGroupName
@ -103,7 +109,7 @@ module nsg 'br/public:avm/res/network/network-security-group:0.3.0' = {
// Virtual Network
module vnet 'br/public:avm/res/network/virtual-network:0.4.0' = {
name: '${deployment().name}-vnet'
name: '${uniqueString(deployment().name, resourceLocation)}-vnet'
scope: rg
params: {
name: virtualNetworkName
@ -115,25 +121,82 @@ module vnet 'br/public:avm/res/network/virtual-network:0.4.0' = {
}
}
module pool 'nestedPool.bicep' = {
module devCenter 'devCenter.bicep' = {
scope: rg
name: '${deployment().name}-pool'
name: '${uniqueString(deployment().name, resourceLocation)}-devCenter'
params: {
location: resourceLocation
devCenterName: devCenterName
devCenterProjectName: devCenterProjectName
maximumConcurrency: poolMaximumConcurrency
poolName: poolName
poolSize: poolSize
poolManagedIdentities: poolManagedIdentities
agentProfile: poolAgentProfile
organizationName: organizationName
projectNames: projectNames
virtualNetworkResourceId: vnet.outputs.resourceId
subnetName: vnet.outputs.subnetNames[0]
computeGalleryImageDefinitionName: computeGalleryImageDefinitionName
computeGalleryName: computeGalleryName
imageVersion: imageVersion
devOpsInfrastructureEnterpriseApplicationObjectId: devOpsInfrastructureEnterpriseApplicationObjectId
}
}
resource computeGallery 'Microsoft.Compute/galleries@2022-03-03' existing = {
name: computeGalleryName
scope: resourceGroup(computeGalleryResourceGroupName)
resource imageDefinition 'images@2022-03-03' existing = {
name: computeGalleryImageDefinitionName
resource version 'versions@2022-03-03' existing = {
name: imageVersion
}
}
}
module imagePermission 'br/public:avm/ptn/authorization/resource-role-assignment:0.1.1' = {
scope: resourceGroup(computeGalleryResourceGroupName)
name: '${uniqueString(deployment().name, resourceLocation)}-devOpsInfrastructureEAObjectId-permission-image'
params: {
principalId: devOpsInfrastructureEnterpriseApplicationObjectId
resourceId: computeGallery::imageDefinition.id
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'acdd72a7-3385-48ef-bd42-f606fba81ae7'
) // Reader
}
}
module vnetPermission 'br/public:avm/ptn/authorization/resource-role-assignment:0.1.1' = {
scope: rg
name: '${uniqueString(deployment().name, resourceLocation)}-devOpsInfrastructureEAObjectId-permission-vnet'
params: {
principalId: devOpsInfrastructureEnterpriseApplicationObjectId
resourceId: vnet.outputs.resourceId
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'4d97b98b-1d4f-4787-a291-c67834d212e7'
) // Network Contributor
}
}
module pool 'br/public:avm/res/dev-ops-infrastructure/pool:0.1.1' = {
name: '${uniqueString(deployment().name, resourceLocation)}-pool'
scope: rg
params: {
name: poolName
managedIdentities: poolManagedIdentities
agentProfile: poolAgentProfile
concurrency: poolMaximumConcurrency
devCenterProjectResourceId: devCenter.outputs.devCenterProjectResourceId
fabricProfileSkuName: poolSize
images: [
{
resourceId: computeGallery::imageDefinition::version.id
}
]
organizationProfile: {
kind: 'AzureDevOps'
organizations: [
{
url: 'https://dev.azure.com/${organizationName}'
projects: projectNames
}
]
}
subnetResourceId: vnet.outputs.subnetResourceIds[0]
}
dependsOn: [
imagePermission
vnetPermission
]
}