Currently, I have a job called create-rg
that creates a resource group used by azure/arm-deploy@v2
to deploy resources into at the resourcegroup
scope.
I don’t like this approach, as I want the resource group to be part of Bicep IaC. At the same time, I would like to deploy the resource group with the resources in one operation, with resources "depending" on the resource group. Is this possible?
GitHub Actions (deploy.yml
)
...
create-rg:
name: Create resource group
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- uses: azure/cli@v2
name: Create resource group
with:
inlineScript: |
az group create
--name ${{ inputs.resourceGroupName }}
--location ${{ vars.AZURE_DEFAULT_LOCATION }}
...
deploy-infra:
name: Deploy infrastructure
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
outputs:
appServiceAppName: ${{ steps.deploy.outputs.appServiceAppName }}
appServiceAppHostName: ${{ steps.deploy.outputs.appServiceAppHostName }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Azure
uses: azure/login@v2
with:
client-id: ${{ vars.AZURE_CLIENT_ID }}
tenant-id: ${{ vars.AZURE_TENANT_ID }}
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
- name: Deploy infrastructure
uses: azure/arm-deploy@v2
id: deploy
with:
deploymentName: ${{ github.run_number }}
scope: resourcegroup
resourceGroupName: ${{ inputs.resourceGroupName }}
template: ./infra/main.bicep
parameters: >
environment=${{ inputs.environment }}
deploymentMode: Complete
failOnStdErr: false
needs: [validate-infra]
Bicep (main.bicep
):
@description('Location for all resources.')
param location string = resourceGroup().location
@description('Select the type of environment you want to provision. Allowed values are "production" and "test".')
@allowed([
'prod'
'staging'
'dev'
])
param environment string
@description('The name of the Azure Function app.')
param functionAppName string = 'func-${uniqueString(resourceGroup().id)}-${environment}'
@description('Storage Account type')
@allowed([
'Standard_LRS'
'Standard_GRS'
'Standard_RAGRS'
])
param storageAccountType string = 'Standard_LRS'
var hostingPlanName = 'plan-${uniqueString(resourceGroup().id)}-${environment}'
var applicationInsightsName = 'ai-${uniqueString(resourceGroup().id)}-${environment}'
var storageAccountName = 'st${uniqueString(resourceGroup().id)}-${environment}'
var logAnalyticsName = 'log-${uniqueString(resourceGroup().id)}-${environment}'
// https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#storage-blob-data-owner
var storageBlobDataOwnerRoleId = subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'b7e6dc6d-f1e8-4753-8033-0f276bb0955b'
)
var storageFunctionRoleAssignment = guid(resourceGroup().id, storageBlobDataOwnerRoleId)
// https://learn.microsoft.com/azure/role-based-access-control/built-in-roles#azure-event-hubs-data-receiver
var eventHubDataReceiverRoleId = subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'a638d3c7-ab3a-418d-83e6-5f17a39d4fde'
)
var eventHubFunctionRoleAssignment = guid(resourceGroup().id, eventHubDataReceiverRoleId)
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: logAnalyticsName
location: location
properties: {
sku: {
name: 'PerGB2018'
}
}
}
resource applicationInsight 'Microsoft.Insights/components@2020-02-02' = {
name: applicationInsightsName
location: location
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: logAnalyticsWorkspace.id
}
}
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = {
name: storageAccountName
location: location
sku: {
name: storageAccountType
}
kind: 'Storage'
properties: {
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
}
}
resource hostingPlan 'Microsoft.Web/serverfarms@2022-03-01' = {
name: hostingPlanName
location: location
sku: {
name: 'Y1'
tier: 'Dynamic'
size: 'Y1'
family: 'Y'
}
properties: {
reserved: true
}
}
resource functionApp 'Microsoft.Web/sites@2022-03-01' = {
name: functionAppName
location: location
kind: 'functionapp,linux'
identity: {
type: 'SystemAssigned'
}
properties: {
reserved: true
serverFarmId: hostingPlan.id
httpsOnly: true
siteConfig: {
linuxFxVersion: 'DOTNETCORE|8.0'
appSettings: [
{
name: 'APPLICATIONINSIGHTS_CONNECTION_STRING'
value: applicationInsight.properties.ConnectionString
}
{
name: 'AzureWebJobsStorage__accountName'
value: storageAccountName
}
{
name: 'FUNCTIONS_EXTENSION_VERSION'
value: '~4'
}
{
name: 'FUNCTIONS_WORKER_RUNTIME'
value: 'dotnet-isolated'
}
{
name: 'WEBSITE_RUN_FROM_PACKAGE'
value: '1'
}
]
}
}
resource config 'config' = {
name: 'web'
properties: {
ftpsState: 'Disabled'
minTlsVersion: '1.2'
}
}
}
resource storageRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: storageFunctionRoleAssignment
properties: {
principalId: functionApp.identity.principalId
roleDefinitionId: storageBlobDataOwnerRoleId
}
scope: storageAccount
}
resource eventHubRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: eventHubFunctionRoleAssignment
properties: {
principalId: functionApp.identity.principalId
roleDefinitionId: eventHubDataReceiverRoleId
}
}
2
Answers
You can run your main deploy task at subscription level:
and then create a new resource group and deploy all other resources at new created resource group scope.
for example:
Here is the Bicep code to create a resource group and deploy resources within it. Follow the MS Doc for more details.
File structure of below code.
venkat.bicep
storage.bicep
Bicep deployment command
Output
Reference: az deployment sub create