Real World DevOps-Breaking it all down (part 2-Setting DNS)

Table of Content

Part 1 – Provisioning and Restoring DB’s from Backup

Part 2 – Setting up DNS

Part 3 – Setting up HTTPS

Part 4 – Settup up dynamic backend service url’s for mobile app (1 build for all stages) (Coming soon)

Setting up DNS

Setting up DNS in my pipeline was really pretty simple. My DNS information is being held in Cloudflare. Cloudflare has a full set of rest api’s that let you do everything including setting DNS entries and page rules. My setup DNS stage only consists of 2 tasks.

image

First I get all my secrets from Key Vault, then I call my powershell script which in turn calls the Cloudflare rest api’s to set up my DNS settings up in Cloudflare.

My powershell script for setting up DNS in Cloudflare is pretty basic. First I pass in params that I will need and use in the script

[CmdletBinding()]
param(
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipal,
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipalSecret,
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipalTenantId,
    [Parameter(Mandatory = $True)]
    [string]
    $azureSubscriptionName,
    [Parameter(Mandatory = $True)]
    [string]
    $resourceGroupName,
    [Parameter(Mandatory = $True)]
    [string]
    $cloudFlareKey,
    [Parameter(Mandatory = $True)]
    [string]
    $cloudFlareEmail,
    [Parameter(Mandatory = $True)]
    [string]
    $cloudFlareZone,
    [Parameter(Mandatory = $True)]
    [string]
    $dnsName,
    [Parameter(Mandatory = $True)]
    [string]
    $frontDoorName,
    [Parameter(Mandatory = $True)]
    [string]
    $nakedDns
)
Script Parameter List

Next I login

#region Login
# This logs in a service principal
#
Write-Output "Logging in to Azure with a service principal..."
az login `
    --service-principal `
    --username $servicePrincipal `
    --password $servicePrincipalSecret `
    --tenant $servicePrincipalTenantId
Write-Output "Done"
Write-Output ""

Next I get all the dns records that I have up in cloudflare. I want them all so I can determine if I am updating the values up in cloudflare or if I am adding a new entry

    #region get all dns records from cloudflare
    # this lists all dns records from cloudflare
    #
    Write-Output "getting all dns records from cloudflare..."
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("X-Auth-Key", $cloudFlareKey)
    $headers.Add("X-Auth-Email", $cloudFlareEmail)
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
    $listDnsResult=Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records" `
        -Headers $headers
    Write-Output $listDnsResult
    Write-Output "done getting all dns records"
    $numEntries=$listDnsResult.result_info.count
    Write-Output "number of dns entries: $numEntries" 
    Write-Output ""
    #endregion

I look and see if the dns value that I am setting is in the returned list of values from cloudflare

    #region look at all dns records, see if the our dns name has already 
    # been set. This block looks for our dns name, see if it has been set or not
    #
    Write-Output "looking for correct DNS entry"
    $foundDnsEntry = $false
    $foundDnsEntryId = "x"
    $listDnsResult.result | ForEach-Object {
        $dnsEntryName = $_.name
        Write-Output "dns entry name: $dnsEntryName"
        if ($dnsEntryName -eq $dnsName) {
            Write-Output "found correct dns entry"
            $foundDnsEntry =$true
            $foundDnsEntryId = $_.id
            return
        }
    }
    Write-Output "found dns entry: $foundDnsEntry"
    Write-Output "dns entry id: $foundDnsEntryId"
    Write-Output ""
    #endregion

And if it is already set, I update cloudflare with my current dns values and if it hasn’t been set, then I add the new dns value as a CNAME

#region updates/adds dns entry to cloudflare
    # this either updates or adds a new dns entry to cloudflare
    #
    $frontDoorFQDN=$frontDoorName + ".azurewebsites.net"
    Write-Output "front door fqdn: $frontDoorFQDN"
    if ($foundDnsEntry -eq $true) {
        Write-Output "updating dns entry..."
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $updateDnsEntry = @{
            type='CNAME'
            name='www'
            content="$frontDoorFQDN"
            proxied=$false
        }
        $json = $updateDnsEntry | ConvertTo-Json
        $updateDnsResponse = $(Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records/$foundDnsEntryId" `
            -Headers $headers `
            -Method Put `
            -Body $json `
            -ContentType 'application/json')
        Write-Output "done updating dns"
        Write-Output "cloudflare response: "
        Write-Output $updateDnsResponse
        Write-Output ""
    }
    else {
        Write-Output "adding new dns entry..."
        $newDnsResponse = $()
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $newDnsEntry = @{
            type='CNAME'
            name='www'
            content="$frontDoorFQDN"
            proxied=$false
            priority=10
        }
        $json = $newDnsEntry | ConvertTo-Json
        $newDnsResponse = $(Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records" `
        -Headers $headers `
        -Method Post `
        -Body $json `
        -ContentType 'application/json')
        Write-Output "done adding dns"
        Write-Output "cloudflare response: "
        Write-Output $newDnsResponse
        Write-Output ""
    }
    #endregion

Next, I add scripts to add/update the dns values for the apex domain name. This means I’m setting up a CNAME for the apex domain as well as setting up some rules.

    #region gets all dns entries from cloudflare
    # this lists all dns records from cloudflare
    #
    Write-Output "getting all dns records from cloudflare..."
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("X-Auth-Key", $cloudFlareKey)
    $headers.Add("X-Auth-Email", $cloudFlareEmail)
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
    $listDnsResult=Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records" `
        -Headers $headers
    Write-Output $listDnsResult
    Write-Output "done getting all dns records"
    $numEntries=$listDnsResult.result_info.count
    Write-Output "number of dns entries: $numEntries" 
    Write-Output ""
    #endregion
    #region look at all dns records, see if our dns name has already been set
    # this looks for our dns name, see if it has been set or not
    #
    $foundDnsEntry = $false
    $foundDnsEntryId = "x"
    $listDnsResult.result | ForEach-Object {
        $dnsEntryName = $_.name
        if ($dnsEntryName -eq $nakedDns) {
            $foundDnsEntry =$true
            $foundDnsEntryId = $_.id
            return
        }
    }
    Write-Output "found dns entry: $foundDnsEntry"
    Write-Output "dns entry id: $foundDnsEntryId"
    Write-Output ""
    #endregion
    #region update/add  dns entry to cloudflare for apex domain
    # this either updates or adds a new dns entry to cloudflare for
    # the apex domain
    #
    $frontDoorFQDN=$frontDoorName + ".azurewebsites.net"
    if ($foundDnsEntry -eq $true) {
        Write-Output "updating dns entry..."
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $updateDnsEntry = @{
            type='CNAME'
            name='@'
            content="$frontDoorFQDN"
            proxied=$true
        }
        $json = $updateDnsEntry | ConvertTo-Json
        $updateDnsResponse = $(Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records/$foundDnsEntryId" `
            -Headers $headers `
            -Method Put `
            -Body $json `
            -ContentType 'application/json')
        Write-Output "done updating dns"
        Write-Output "cloudflare response: "
        Write-Output $updateDnsResponse
        Write-Output ""
        Write-Output "done updating dns"
        Write-Output ""
    }
    else {
        Write-Output "adding new dns entry..."
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $newDnsEntry = @{
            type='CNAME'
            name='@'
            content="$frontDoorFQDN"
            proxied=$true
            priority=10
        }
        $json = $newDnsEntry | ConvertTo-Json
        $newDnsResponse = $(Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records" `
        -Headers $headers `
        -Method Post `
        -Body $json `
        -ContentType 'application/json')
        Write-Output "done adding dns"
        Write-Output "cloudflare response: "
        Write-Output $newDnsResponse
        Write-Output ""
        Write-Output "done adding new dns entry"
        Write-Output ""
    }
    #endregion
    #region check page rules
    # this looks to see if we need to add a page rule for apex domain
    # first by looking up all the rules
    #
    Write-Output "getting all rules from cloudflare..."
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("X-Auth-Key", $cloudFlareKey)
    $headers.Add("X-Auth-Email", $cloudFlareEmail)
    $headers.Add("Content-Type", "application/json")
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
    $listRulesResult=Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/pagerules?status=active&order=status&direction=desc&match=all" `
        -Headers $headers
    Write-Output $listRulesResult
    Write-Output "done getting all dns records"
    $numEntries=$listRulesResult.result_info.count
    Write-Output "number of dns entries: $numEntries" 
    Write-Output ""
    #endregion
    #region delete old page rules
    # delete these old rule entries
    #
    Write-Output "deleting all rule entries..."
    $listRulesResult.result | ForEach-Object {
        $ruleId = $_.id
        Write-Output "deleting rule with id: $ruleId"
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $deleteResult = Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/pagerules/$ruleId" `
            -Headers $headers `
            -Method Delete
        Write-Output "delete response: "
        Write-Output $deleteResult
    }
    Write-Output "done deleting all rule entries"    
    Write-Output ""
    #endregion
    #region add new apex domain rules
    # Add in the apex domain rule
    #
    Write-Output "adding apex domain rule..."
    $json = '{"targets":[{"target":"url", "constraint":{"operator":"matches","value":"' + $nakedDns + '/*"}}],"actions":[{"id":"forwarding_url","value": {"url": "https://' + $dnsName + '/$1","status_code": 301}}],"priority":1,"status":"active"}'
    Write-Output "body: $json"
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("X-Auth-Key", $cloudFlareKey)
    $headers.Add("X-Auth-Email", $cloudFlareEmail)
    $headers.Add("Content-Type", "application/json")
    $addRuleResponse = Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/pagerules" `
        -Headers $headers `
        -Method Post `
        -Body $json `
        -ContentType 'application/json'
    Write-Output $addRuleResponse
    Write-Output "done adding apex domain rule"
    Write-Output ""
    #endregion

Here is the full script

# This IaC script provisions and configures DNS to the application
#
[CmdletBinding()]
param(
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipal,
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipalSecret,
    [Parameter(Mandatory = $True)]
    [string]
    $servicePrincipalTenantId,
    [Parameter(Mandatory = $True)]
    [string]
    $azureSubscriptionName,
    [Parameter(Mandatory = $True)]
    [string]
    $resourceGroupName,
    [Parameter(Mandatory = $True)]
    [string]
    $cloudFlareKey,
    [Parameter(Mandatory = $True)]
    [string]
    $cloudFlareEmail,
    [Parameter(Mandatory = $True)]
    [string]
    $cloudFlareZone,
    [Parameter(Mandatory = $True)]
    [string]
    $dnsName,
    [Parameter(Mandatory = $True)]
    [string]
    $frontDoorName,
    [Parameter(Mandatory = $True)]
    [string]
    $nakedDns
)
#region Login
# This logs in a service principal
#
Write-Output "Logging in to Azure with a service principal..."
az login `
    --service-principal `
    --username $servicePrincipal `
    --password $servicePrincipalSecret `
    --tenant $servicePrincipalTenantId
Write-Output "Done"
Write-Output ""
# This sets the subscription to the subscription I need all my apps to
# run in
#
Write-Output "Setting default azure subscription..."
az account set `
    --subscription $azureSubscriptionName
Write-Output "Done"
Write-Output ""
#endregion
# this defines my time 1 up function which will deploy and configure the infrastructure 
# for my DNS settings up in cloud flare
#
function 1_Up {
    Write-Output "executing function 1_Up..."
    #region get all dns records from cloudflare
    # this lists all dns records from cloudflare
    #
    Write-Output "getting all dns records from cloudflare..."
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("X-Auth-Key", $cloudFlareKey)
    $headers.Add("X-Auth-Email", $cloudFlareEmail)
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
    $listDnsResult=Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records" `
        -Headers $headers
    Write-Output $listDnsResult
    Write-Output "done getting all dns records"
    $numEntries=$listDnsResult.result_info.count
    Write-Output "number of dns entries: $numEntries" 
    Write-Output ""
    #endregion
    #region look at all dns records, see if the our dns name has already 
    # been set. This block looks for our dns name, see if it has been set or not
    #
    Write-Output "looking for correct DNS entry"
    $foundDnsEntry = $false
    $foundDnsEntryId = "x"
    $listDnsResult.result | ForEach-Object {
        $dnsEntryName = $_.name
        Write-Output "dns entry name: $dnsEntryName"
        if ($dnsEntryName -eq $dnsName) {
            Write-Output "found correct dns entry"
            $foundDnsEntry =$true
            $foundDnsEntryId = $_.id
            return
        }
    }
    Write-Output "found dns entry: $foundDnsEntry"
    Write-Output "dns entry id: $foundDnsEntryId"
    Write-Output ""
    #endregion
    #region updates/adds dns entry to cloudflare
    # this either updates or adds a new dns entry to cloudflare
    #
    $frontDoorFQDN=$frontDoorName + ".azurewebsites.net"
    Write-Output "front door fqdn: $frontDoorFQDN"
    if ($foundDnsEntry -eq $true) {
        Write-Output "updating dns entry..."
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $updateDnsEntry = @{
            type='CNAME'
            name='www'
            content="$frontDoorFQDN"
            proxied=$false
        }
        $json = $updateDnsEntry | ConvertTo-Json
        $updateDnsResponse = $(Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records/$foundDnsEntryId" `
            -Headers $headers `
            -Method Put `
            -Body $json `
            -ContentType 'application/json')
        Write-Output "done updating dns"
        Write-Output "cloudflare response: "
        Write-Output $updateDnsResponse
        Write-Output ""
    }
    else {
        Write-Output "adding new dns entry..."
        $newDnsResponse = $()
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $newDnsEntry = @{
            type='CNAME'
            name='www'
            content="$frontDoorFQDN"
            proxied=$false
            priority=10
        }
        $json = $newDnsEntry | ConvertTo-Json
        $newDnsResponse = $(Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records" `
        -Headers $headers `
        -Method Post `
        -Body $json `
        -ContentType 'application/json')
        Write-Output "done adding dns"
        Write-Output "cloudflare response: "
        Write-Output $newDnsResponse
        Write-Output ""
    }
    #endregion
    Write-Output "done with function 1_Up"
    Write-Output ""
}
# This brings my infrastructure up to version 2 where it sets up the apex domain url (no www)
# in dns to point and direct to the right place
#
function 2_Up {
    Write-Output "Executing function 2_Up"
    #region gets all dns entries from cloudflare
    # this lists all dns records from cloudflare
    #
    Write-Output "getting all dns records from cloudflare..."
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("X-Auth-Key", $cloudFlareKey)
    $headers.Add("X-Auth-Email", $cloudFlareEmail)
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
    $listDnsResult=Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records" `
        -Headers $headers
    Write-Output $listDnsResult
    Write-Output "done getting all dns records"
    $numEntries=$listDnsResult.result_info.count
    Write-Output "number of dns entries: $numEntries" 
    Write-Output ""
    #endregion
    #region look at all dns records, see if our dns name has already been set
    # this looks for our dns name, see if it has been set or not
    #
    $foundDnsEntry = $false
    $foundDnsEntryId = "x"
    $listDnsResult.result | ForEach-Object {
        $dnsEntryName = $_.name
        if ($dnsEntryName -eq $nakedDns) {
            $foundDnsEntry =$true
            $foundDnsEntryId = $_.id
            return
        }
    }
    Write-Output "found dns entry: $foundDnsEntry"
    Write-Output "dns entry id: $foundDnsEntryId"
    Write-Output ""
    #endregion
    #region update/add  dns entry to cloudflare for apex domain
    # this either updates or adds a new dns entry to cloudflare for
    # the apex domain
    #
    $frontDoorFQDN=$frontDoorName + ".azurewebsites.net"
    if ($foundDnsEntry -eq $true) {
        Write-Output "updating dns entry..."
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $updateDnsEntry = @{
            type='CNAME'
            name='@'
            content="$frontDoorFQDN"
            proxied=$true
        }
        $json = $updateDnsEntry | ConvertTo-Json
        $updateDnsResponse = $(Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records/$foundDnsEntryId" `
            -Headers $headers `
            -Method Put `
            -Body $json `
            -ContentType 'application/json')
        Write-Output "done updating dns"
        Write-Output "cloudflare response: "
        Write-Output $updateDnsResponse
        Write-Output ""
        Write-Output "done updating dns"
        Write-Output ""
    }
    else {
        Write-Output "adding new dns entry..."
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $newDnsEntry = @{
            type='CNAME'
            name='@'
            content="$frontDoorFQDN"
            proxied=$true
            priority=10
        }
        $json = $newDnsEntry | ConvertTo-Json
        $newDnsResponse = $(Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/dns_records" `
        -Headers $headers `
        -Method Post `
        -Body $json `
        -ContentType 'application/json')
        Write-Output "done adding dns"
        Write-Output "cloudflare response: "
        Write-Output $newDnsResponse
        Write-Output ""
        Write-Output "done adding new dns entry"
        Write-Output ""
    }
    #endregion
    #region check page rules
    # this looks to see if we need to add a page rule for apex domain
    # first by looking up all the rules
    #
    Write-Output "getting all rules from cloudflare..."
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("X-Auth-Key", $cloudFlareKey)
    $headers.Add("X-Auth-Email", $cloudFlareEmail)
    $headers.Add("Content-Type", "application/json")
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12
    $listRulesResult=Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/pagerules?status=active&order=status&direction=desc&match=all" `
        -Headers $headers
    Write-Output $listRulesResult
    Write-Output "done getting all dns records"
    $numEntries=$listRulesResult.result_info.count
    Write-Output "number of dns entries: $numEntries" 
    Write-Output ""
    #endregion
    #region delete old page rules
    # delete these old rule entries
    #
    Write-Output "deleting all rule entries..."
    $listRulesResult.result | ForEach-Object {
        $ruleId = $_.id
        Write-Output "deleting rule with id: $ruleId"
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("X-Auth-Key", $cloudFlareKey)
        $headers.Add("X-Auth-Email", $cloudFlareEmail)
        $deleteResult = Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/pagerules/$ruleId" `
            -Headers $headers `
            -Method Delete
        Write-Output "delete response: "
        Write-Output $deleteResult
    }
    Write-Output "done deleting all rule entries"    
    Write-Output ""
    #endregion
    #region add new apex domain rules
    # Add in the apex domain rule
    #
    Write-Output "adding apex domain rule..."
    $json = '{"targets":[{"target":"url", "constraint":{"operator":"matches","value":"' + $nakedDns + '/*"}}],"actions":[{"id":"forwarding_url","value": {"url": "https://' + $dnsName + '/$1","status_code": 301}}],"priority":1,"status":"active"}'
    Write-Output "body: $json"
    $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
    $headers.Add("X-Auth-Key", $cloudFlareKey)
    $headers.Add("X-Auth-Email", $cloudFlareEmail)
    $headers.Add("Content-Type", "application/json")
    $addRuleResponse = Invoke-RestMethod "https://api.cloudflare.com/client/v4/zones/$cloudFlareZone/pagerules" `
        -Headers $headers `
        -Method Post `
        -Body $json `
        -ContentType 'application/json'
    Write-Output $addRuleResponse
    Write-Output "done adding apex domain rule"
    Write-Output ""
    #endregion
    Write-Output "done with 2_Up"
    Write-Output ""
}
Install-Module -Name VersionInfrastructure -Force -Scope CurrentUser
Update-InfrastructureVersion `
    -infraToolsFunctionName $Env:INFRATOOLS_FUNCTIONNAME `
    -infraToolsTableName $Env:INFRATOOLS_TABLENAME `
    -deploymentStage $Env:INFRATOOLS_DEPLOYMENTSTAGE
provisionDNS.ps1

Waiting until DNS propagates

One of the cooler things that I do in this pipeline came about because there are certain infrastructures within Azure (looking at you Azure Front Door)  that I can’t provision or configure until my DNS values have propagated locally. What I need is to have my pipeline wait after I set up DNS in cloudflare until DNS has propagated locally. Then, go on to the next stage. Sounds like the perfect place to use a custom automated gate.

Azure Pipelines has the ability to create gates that use automation to help decide if the gate should pass or not. Out of the box, you get a set of automated gates that uses automation based on a work item query, Azure Monitoring, and Azure Policy Compliance. You also have the ability to create your own custom gates using either an Azure Function or a REST API.

Cool. Easy peasy. I just need to create an Azure function that checks if my DNS value has propagated.  I kind of hacked this together using the DnsClient library from michaco.net. Here is my function

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using DnsClient;
using DnsClient.Protocol;
namespace InfraTools
{
    public static class DNSAliasChecker
    {
        [FunctionName("DNSAliasChecker")]
        public static async Task<IActionResult> Run(
             [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");
            // parse fqdn and alias from request
            // fqdn holds the fully qualified domain name you want to set
            // alias holds the original ip address or name. for azurewebsites, this would be something.azurewebsites.net
            string fqdn = req.Query["fqdn"];
            string alias = req.Query["alias"];
            // parse fqdn and alias from request body of a post
            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            // set the value whether it came from param list or post body
            fqdn = fqdn ?? data?.fqdn;
            alias = alias ?? data?.alias;
            // if user didn't pass in fqdn or alias, return with bad request
            if ((fqdn == null) || (alias == null))
            {
                return new BadRequestObjectResult("Please pass the fqdn and alis on the query string or in the request body");
            }
            // query dns for your fqdn and cname/alias, loop through all
            // returned values and see if you cand find your cname
            var lookupClient = new LookupClient();
            var result = lookupClient.Query(fqdn, QueryType.CNAME);
            var foundEntry = false;
            foreach (var record in result.Answers)
            {
                var cnameRecord = record as CNameRecord;
                var cname = cnameRecord.CanonicalName.Value.ToString();
                // check if it has trailing .
                if (cname.EndsWith("."))
                {
                    cname = cname.Remove(cname.Length - 1);
                }
                if (cname.Equals(alias))
                {
                    foundEntry = true;
                    break;
                }
            }
            // return result
            if (foundEntry)
            {
                return new OkObjectResult(true);
            }
            return new OkObjectResult(false);
        }
    }
}
DNSAliasChecker.cs

Pass in the fqdn (fully qualified domain name you want your website to have) and an alias (the ip address or website address, for an azure app service, it will be something.azurewebsites.net) and it returns back whether the fqdn has propagated down locally. True if it has, false if it hasn’t.

And in my pipeline, after I set up DNS for my DNS Beta stage, I create an automated approval gate which calls this function, passing in the DNS name and the alias. I also set the polling frequency to every 5 minutes and a timeout of 24 hours.

image

Conclusion

Before I started in on this pipeline, I had no idea if I could programmatically set DNS. I was pleasantly surprised when I found out Cloudflare has a full set of REST API’s I could call to do everything. I have another project where my DNS settings are saved up in Go Daddy. Go Daddy also has a full set of REST API’s to do everything.

Azure Front Door also threw me for a loop when I couldn’t provision and configure it until DNS has propagated locally. However, I quickly figured out that I could pause my release pipeline by using an automated gate.

So the million dollar question. Do all pipelines need to be able to set DNS? Probably not. But I wanted a pipeline where I could do full disaster recovery. Where even if someone deleted EVERYTHING, I could be back up and running just by queueing up a release.

Mission accomplished. DNS can be dynamically set and I can pause my release pipeline until my DNS value has propagated locally.

Leave a Reply

Your email address will not be published. Required fields are marked *