Getting started with GitHub Actions and Bicep – Part 6

This is the last part of this blog series, and here I want to show you how to use a JSON configuration file and a PowerShell script to deploy the same environment as you have seen in the first five parts of the series. This deployment type will feel familiar to many consultants since PowerShell is widely used among consultants. This deployment method can ease them into a DevOps mindset. Doing it this way enables flexibility around which part of the code to deploy by using if statements in the PowerShell script.

The first task I will do is creating the JSON file. I will take all variables from the previous execution files and add them to a single JSON file. The JSON file creates a single place to edit the deployment and is therefore easy to use for new environments.

{
    "location": "WestEurope",
    "ResourceGroups": [
        "rg-pwsh-sharedservices-network-001",
        "rg-pwsh-sharedservices-vm-001",
        "rg-pwsh-citrix-network-001",
        "rg-pwsh-citrix-vm-001",
        "rg-pwsh-citrix-workers-001"
    ],
    "SharedService_vNet": [
        {
            "ResourceGroup": "rg-pwsh-sharedservices-network-001",
            "Name": "vnet-pwsh-sharedservices-001",
            "vNetPrefix": "172.16.0.0/16",
            "Subnets": [
                {
                    "SubnetName": "GatewaySubnet",
                    "SubnetPrefix": "172.16.0.0/26"
                },
                {
                    "SubnetName": "snet-sharedservices-adds-001",
                    "SubnetPrefix": "172.16.0.64/26"
                }
            ],
            "vNetDNSServers": ["192.168.10.10","192.168.10.11"]
        }
    ],
    "Citrix_vNet": [
        {
            "ResourceGroup": "rg-pwsh-citrix-network-001",
            "Name": "vnet-pwsh-citrix-001",
            "vNetPrefix": "172.17.0.0/16",
            "Subnets": [
                {
                    "SubnetName": "snet-citrix-vm-001",
                    "SubnetPrefix": "172.17.0.0/26"
                },
                {
                    "SubnetName": "snet-citrix-workers-001",
                    "SubnetPrefix": "172.17.1.0/24"
                },
                {
                    "SubnetName": "snet-citrix-workers-002",
                    "SubnetPrefix": "172.17.2.0/24"
                }
            ],
            "vNetDNSServers": ["192.168.10.10","192.168.10.11"]
        }
    ],
    "VPN": [
        {
            "VirtualNetworkGateway": [
                {
                    "virtualNetworkGatewayName": "vng-sharedservices-001",
                    "VirtualNetworkName": "vnet-pwsh-sharedservices-001",
                    "SubnetName": "GatewaySubnet",
                    "sku": "Basic",
                    "ResourceGroup": "rg-pwsh-sharedservices-network-001",
                    "PublicIpAddressName": "pip-vng-sharedservices-001",
                    "gatewayType": "Vpn",
                    "enableBGP": false
                }
            ],
            "LocalNetworkGateway": [
                {
                    "ResourceGroup": "rg-pwsh-sharedservices-network-001",
                    "LocalAddressPrefixes": ["192.168.1.0/24","192.168.10.0/24"],
                    "LocalGatewayPublicIP": "80.80.80.80",
                    "LocalGatewayName": "lng-sharedservices-001"
                }
            ], 
            "Connection": [
                {
                    "ResourceGroup": "rg-pwsh-sharedservices-network-001",
                    "connectionName": "cnt-sharedservices-001",
                    "connectionType": "IPSec",
                    "enableBgp": false,
                    "sharedKey": "dhsjkdlahldk23e2mda"
                }
            ]
        }
    ],      
    "CloudConnectors": {
        "availabilitySetName": "as-citrix-001",
        "availabilitySetPlatformFaultDomainCount": 2,
        "availabilitySetPlatformUpdateDomainCount": 5,
        "CCVMPrefix": "vm-ctx-cc",
        "domainFQDN": "citrixlab.dk",
        "domainJoinUserName": "domainjoin",
        "domainJoinUserPassword": "Only4BlogPost",
        "ResourceGroup": "rg-pwsh-citrix-vm-001",
        "OS": "Server2019",
        "ouPath": "OU=CloudConnectors,OU=Citrix,OU=Servers,OU=Citrixlab,DC=Citrixlab,DC=dk",
        "SubnetName": "snet-citrix-vm-001",
        "virtualMachineCount": 2,
        "VMPassword": "Only4BlogPost",
        "VMSize": "Standard_B2ms",
        "VMUserName": "azureadmin",
        "vNetName": "vnet-pwsh-citrix-001",
        "vNetResourceGroup": "rg-pwsh-citrix-network-001"
    }
}

Now that I have all the variables, I can start creating my PowerShell code for deploying the resources. I feel pretty comfortable in PowerShell, so I have created the file below to create my deployment. There are 100 lines of code, and that might seem like a lot, but all the sections look quite similar, so under the code snippet, I will explain what I have done with the code.

param (
   $ConfigFile = "..GithubActionsBlogConfigurationVariables.json"
)
  
# Get JSON content from file
$Config = Get-Content -Raw -Path $ConfigFile | ConvertFrom-Json

# Create resource groups
foreach ($ResourceGroup in $Config.ResourceGroups) {
   $TestResourceGroup = Get-AzResourceGroup -Name $ResourceGroup -ErrorAction SilentlyContinue
   
   If($TestResourceGroup.Length -eq 0) {
      New-AzResourceGroup -Name $ResourceGroup -Location $Config.location
   }    
}

$SharedSubnets = @()
foreach ($subnet in $($Config.SharedService_vNet.Subnets)) {
   $SharedSubnets += @{
      Name = $subnet.SubnetName
      Prefix = $subnet.SubnetPrefix
   }
}
# Shared service vNet Creation
$SharedServicevNetParams = New-Object -TypeName hashtable 
$SharedServicevNetParams['vNetName']    = $Config.SharedService_vNet.Name
$SharedServicevNetParams['location']    = $Config.Location
$SharedServicevNetParams['Prefix']      = $Config.SharedService_vNet.vNetPrefix
$SharedServicevNetParams['dnsServers']  = $Config.SharedService_vNet.vNetDNSServers 
$SharedServicevNetParams['Subnets']     = $SharedSubnets
New-AzResourceGroupDeployment -Name "vNet" -ResourceGroupName $($Config.SharedService_vNet.ResourceGroup) -Mode Incremental -TemplateFile ..GithubActionsBlogNetworkTemplatesvNet.bicep -TemplateParameterObject $SharedServicevNetParams 

# Citrix vNet Creation
$CitrixSubnets = @()
foreach ($subnet in $($Config.Citrix_vNet.Subnets)) {
   $CitrixSubnets += @{
      Name = $subnet.SubnetName
      Prefix = $subnet.SubnetPrefix
   }
}
$CitrixvNetParams = New-Object -TypeName hashtable 
$CitrixvNetParams['vNetName']    = $Config.Citrix_vNet.Name
$CitrixvNetParams['location']    = $Config.Location
$CitrixvNetParams['Prefix']      = $Config.Citrix_vNet.vNetPrefix
$CitrixvNetParams['dnsServers']  = $Config.Citrix_vNet.vNetDNSServers 
$CitrixvNetParams['Subnets']     = $CitrixSubnets
New-AzResourceGroupDeployment -Name "vNet" -ResourceGroupName $($Config.Citrix_vNet.ResourceGroup) -Mode Incremental -TemplateFile ..GithubActionsBlogNetworkTemplatesvNet.bicep -TemplateParameterObject $CitrixvNetParams 

# Create virtual network gateway
$VNGParams = New-Object -TypeName hashtable 
$VNGParams['virtualNetworkGatewayName']   = $Config.VPN.VirtualNetworkGateway.virtualNetworkGatewayName
$VNGParams['VirtualNetworkName']          = $Config.VPN.VirtualNetworkGateway.VirtualNetworkName
$VNGParams['SubnetName']                  = $Config.VPN.VirtualNetworkGateway.SubnetName
$VNGParams['sku']                         = $Config.VPN.VirtualNetworkGateway.sku
$VNGParams['rgName']                      = $Config.VPN.VirtualNetworkGateway.ResourceGroup
$VNGParams['PublicIpAddressName']         = $Config.VPN.VirtualNetworkGateway.PublicIpAddressName
$VNGParams['gatewayType']                 = $Config.VPN.VirtualNetworkGateway.gatewayType
$VNGParams['enableBGP']                   = $Config.VPN.VirtualNetworkGateway.enableBGP
$VNGParams['location']                    = $Config.location
$VNG = New-AzResourceGroupDeployment -Name "VNG" -ResourceGroupName $($Config.VPN.VirtualNetworkGateway.ResourceGroup) -Mode Incremental -TemplateFile ..GithubActionsBlogNetworkTemplatesVirtualNetworkGateway.bicep -TemplateParameterObject $VNGParams 

# Create local network gateway
$LNGParams = New-Object -TypeName hashtable 
$LNGParams['addressPrefixes'] = $Config.VPN.LocalNetworkGateway.LocalAddressPrefixes
$LNGParams['gatewayIpAddress']         = $Config.VPN.LocalNetworkGateway.LocalGatewayPublicIP
$LNGParams['localNetworkGatewayName']  = $Config.VPN.LocalNetworkGateway.LocalGatewayName
$LNGParams['location']                 = $Config.location
$LNG = New-AzResourceGroupDeployment -Name "LNG" -ResourceGroupName $($Config.VPN.LocalNetworkGateway.ResourceGroup) -Mode Incremental -TemplateFile ..GithubActionsBlogNetworkTemplatesLocalNetworkGateway.bicep -TemplateParameterObject $LNGParams 

# Create VPN connection
$CNTParams = New-Object -TypeName hashtable 
$CNTParams['connectionName']           = $Config.VPN.Connection.connectionName
$CNTParams['connectionType']           = $Config.VPN.Connection.connectionType
$CNTParams['enableBgp']                = $Config.VPN.Connection.enableBgp
$CNTParams['location']                 = $Config.location
$CNTParams['sharedKey']                = $Config.VPN.Connection.sharedKey
$CNTParams['localNetworkGatewayId']    = $LNG.Outputs['lngid'].value
$CNTParams['virtualNetworkGatewayId']  = $VNG.Outputs['vngid'].value
New-AzResourceGroupDeployment -Name "CNT" -ResourceGroupName $($Config.VPN.Connection.ResourceGroup) -Mode Incremental -TemplateFile ..GithubActionsBlogNetworkTemplatesConnection.bicep -TemplateParameterObject $CNTParams 

# Create Citrix Cloud Connectors
$CCParams = New-Object -TypeName hashtable 
$CCParams['availabilitySetName']                                           = $Config.CloudConnectors.availabilitySetName
$CCParams['availabilitySetPlatformFaultDomainCount']                       = $Config.CloudConnectors.availabilitySetPlatformFaultDomainCount
$CCParams['availabilitySetPlatformUpdateDomainCount']                      = $Config.CloudConnectors.availabilitySetPlatformUpdateDomainCount
$CCParams['CCVMPrefix']                                                    = $Config.CloudConnectors.CCVMPrefix
$CCParams['domainFQDN']                                                    = $Config.CloudConnectors.domainFQDN
$CCParams['domainJoinUserName']                                            = $Config.CloudConnectors.domainJoinUserName
$CCParams['domainJoinUserPassword']                                        = $Config.CloudConnectors.domainJoinUserPassword
$CCParams['location']                                                      = $Config.location
$CCParams['OS']                                                            = $Config.CloudConnectors.OS
$CCParams['ouPath']                                                        = $Config.CloudConnectors.ouPath
$CCParams['SubnetName']                                                    = $Config.CloudConnectors.SubnetName
$CCParams['virtualMachineCount']                                           = $Config.CloudConnectors.virtualMachineCount
$CCParams['VMSize']                                                        = $Config.CloudConnectors.VMSize
$CCParams['vNetName']                                                      = $Config.CloudConnectors.vNetName
$CCParams['VMUserName']                                                    = $Config.CloudConnectors.VMUserName
$CCParams['vNetResourceGroup']                                             = $Config.CloudConnectors.vNetResourceGroup
$CCParams['VMPassword']                                                    = $Config.CloudConnectors.VMPassword
New-AzResourceGroupDeployment -Name "CitrixCloudConnectors" -ResourceGroupName $($Config.CloudConnectors.ResourceGroup) -Mode Incremental -TemplateFile ..GithubActionsBlogVirtualMachineTemplatesCitrixCloudConnector.bicep -TemplateParameterObject $CCParams

I want to explain just a snippet of the code, so I have chosen the most complex one. Below I have pasted in my code for creating a vNet. Most of the code is straight forward where I get the values from the JSON files and add them to a hashtable that, in turn, will be used as the parameter object for the resource group deployment. The one section that falls out of this simple format is the subnets. I didn’t create an array in my JSON file, which the bicep template will use, but the array is formatted differently to work directly from the JSON. Therefore, I have created a small for each loop that will get each subnet and put it into a new array that the bicep template can understand. This seems a bit cumbersome, but I have to live with it for the time being. As you can see in the snippet below, I am using the “New-AzResourceGroupDeployment” command but passing in the Bicep file instead of an ARM template. Microsoft has ensured that we can use both ARM and Bicep native, and they will see to the conversion from Bicep to ARM when deployed.

$SharedSubnets = @()
foreach ($subnet in $($Config.SharedService_vNet.Subnets)) {
   $SharedSubnets += @{
      Name = $subnet.SubnetName
      Prefix = $subnet.SubnetPrefix
   }
}
# Shared service vNet Creation
$SharedServicevNetParams = New-Object -TypeName hashtable 
$SharedServicevNetParams['vNetName']    = $Config.SharedService_vNet.Name
$SharedServicevNetParams['location']    = $Config.Location
$SharedServicevNetParams['Prefix']      = $Config.SharedService_vNet.vNetPrefix
$SharedServicevNetParams['dnsServers']  = $Config.SharedService_vNet.vNetDNSServers 
$SharedServicevNetParams['Subnets']     = $SharedSubnets
New-AzResourceGroupDeployment -Name "vNet" -ResourceGroupName $($Config.SharedService_vNet.ResourceGroup) -Mode Incremental -TemplateFile ..GithubActionsBlogNetworkTemplatesvNet.bicep -TemplateParameterObject $SharedServicevNetParams 

As mentioned in the first section of this post, I have included this option of deployment to show that it is possible to create and deploy resources in Azure in multiple ways, and the two I have used are not the only ones either. For my own preference, I am leaning towards the pure Bicep deployments and having pipelines to ensure that each part I want to be deployed is handled directly in the pipeline instead of in the JSON file. I won’t say that I will always use this method, but that is my personal preference for the time being.

I hope this blog series has been informative, and as always, leave a comment and provide feedback so I can improve my work.

Comments