Wednesday 2 September 2020

Continuous Integration Infrastructure

Previously we setup a basic pipeline that tests and builds our project, but before we can deploy it we need someplace to put it, first lets take a look at our YAML file.


trigger:
dev
prod

pool:
  vmImage'windows-latest'

variables:
  buildConfiguration'Release'

steps:
# Execute xunit tests
taskDotNetCoreCLI@2
  displayName'xUnit unit tests'
  inputs
    commandtest
    projects'**/*tests/*.csproj'
    arguments'--configuration $(buildConfiguration)'

# Build dotnet core project
taskDotNetCoreCLI@2
  displayName'dotnet build Tic tac toe : $(buildConfiguration)'
  inputs:
    commandbuild
    projects'**/*source/*.csproj'

exactly what we had a couple of days ago, so today lets build the infrastructure to deploy our project to, now there are many various ways to deploy infrastructure, personally I go with PowerShell, because that's the way I have done it since 2010 and I don't see a to change now, that and I just don't have the time to pick up the newest flavor of the week.

so lets add a infrastructure powershell file to the root of our project.

with that done, lets start by creating a resource group in our poweshell script


Param (
  [Parameter()][String]$location,
  [Parameter()][String]$name,
  [Parameter()][String]$env)

$name = "$name-$env"


#sub functions
function PrintHeader{
  $doubleStar = "**"
  $line = "*****************************************************************************************"
  $msg = "Creating Azure environment for Tic Tac Toe"
  write-host $line
  write-host $doubleStar -NoNewline
  write-host $doubleStar.padLeft($line.length - 2)
  write-host $doubleStar -NoNewline                                                                                     
  write-host $doubleStar.padLeft($line.length - 2)
  
  write-host $doubleStar.PadRight(($line.Length - $msg.Length)/2) -NoNewline
  write-host $msg -NoNewline
  write-host $doubleStar.padLeft(($line.Length - $msg.Length)/2)
  
  write-host $doubleStar -NoNewline
  write-host $doubleStar.padLeft($line.length - 2)
  write-host $doubleStar -NoNewline                                                                                     
  write-host $doubleStar.padLeft($line.length - 2)
  write-host $line
}

#create resource group 
function CreateResourceGroup {
    Param(
        [Parameter(Mandatory=$true)][String]$name,
        [Parameter(Mandatory=$true)][String]$location)
    $rgName = "rg-$name"
    Write-Host "Createing $rgName resource group in $location" -ForegroundColor Magenta
    #check if resource group already exists
    $resourceGroup = Get-AzResourceGroup -Name $rgName -ErrorAction SilentlyContinue
    if($resourceGroup){
        Write-host "Resource group already exists" -foregroundcolor yellow
        return
    }

    #create resource group
    try {
        $resourceGroup = New-AzResourceGroup -Name $rgName -Location $location -Tag @{Type="Incubation"Enviroments="ALL"
        Write-host "Resource group created" -foregroundcolor Green
        return
    }
    catch {
        #failed to create resource group   
         Write-error "Resource group NOT created" -ErrorAction Stop  
    }
}

Clear-Host
PrintHeader

# create resource group              
CreateResourceGroup $name $location

the above powershell script outputs a nice message notifies us what the script does and then creates a resource group.

to execute this from our yml file edit the pipline and add an azure powershell task, doing it form the Devops GUI will make your life much easier.

once you select an Azure PowerShell task ensure that you select the correct azure subscription and make sure you authorize it.

you'll know that you authorized this connection correctly if you see the following

under the service connections section


# setup azure infrastructure
taskAzurePowerShell@5
  displayName'Create Azure infrastructure'
  inputs:
    azurePowerShellVersion'LatestVersion'
    azureSubscription'Pavs Subscription(00000000-0000-0000-0000-000000000000)'
    errorActionPreference'continue'
    ScriptType'FilePath'
    ScriptPath'$(Build.SourcesDirectory)\infrastructure.ps1'
    ScriptArguments'-location switzerlandnorth -name tictactoe -env dev'

keep in mind that often times this process doesn't go smoothly, for example i had to log into my azure portal and get the subscription guid as well as the subscription name

now that we have our task set up copy it to your local yml file and push the changes, the first time you push you will see something along the lines of 

just click the view button

and grant the permission, once this is done the job will start on its own and you wont have to do this again moving forward, now that our run is complete lets take a look at our azure environment, and sure enough we have a resource group named rg-tictactoe-dev.

which is great, but now lets add the creation of  a service plan

# Create app service plan
function CreateAppServicePlan {
  Param(
    [Parameter(Mandatory=$true)] [String$name,
    [Parameter(Mandatory=$true)] [String$location)
  $rgName = "rg-$name"
  $planName = "plan-$name"
  Write-Host "Createing $planName ($location) app service plan in $rgName resource group" -ForegroundColor Magenta
 

  #check if service plan already exists
  $servicePlan = Get-AzAppServicePlan -ResourceGroupName $rgName -Name $planName -ErrorAction SilentlyContinue
  if($servicePlan){
      Write-host "App service already exists" -foregroundcolor yellow
      return  $servicePlan
  }

  #create service plan
  try{
      $servicePlan = New-AzAppServicePlan -ResourceGroupName $rgName -Name $planName -Location $location -Tier "Free" -NumberofWorkers 1 -WorkerSize "Small" 
      Write-host "App service plan Created" -foregroundcolor Green
      return  $servicePlan
  }
  catch{
      Write-error "App service plan NOT created" -ErrorAction Stop
  }
}

and an actual app service 

# create app service
function CreateAppService {
  Param(
      [Parameter(Mandatory=$true)] [String$name,
      [Parameter(Mandatory=$true)] [String$prefix,
      [Parameter(Mandatory=$true)] [String$location,
      [Parameter(Mandatory=$true)] [Microsoft.Azure.Management.WebSites.Models.AppServicePlan$servicePlan)
  $rgName = "rg-$name"
  $appName = "app-$prefix-$name"
  Write-Host "Createing $appName web app in $rgName resource group" -ForegroundColor Magenta
  
  #check if web app already exists
  $webApp = Get-AzWebApp -ResourceGroupName $rgName -Name $appName -ErrorAction SilentlyContinue
  if($webApp){
      Write-host "Web app ""$appName"" already exists" -foregroundcolor yellow
      return $webApp
  }
  
  #create web app
  try{
      $webApp = New-AzWebApp -ResourceGroupName $rgName -Name $appName -Location $location -AppServicePlan $servicePlan.Name
      Write-host "App service ""$appName"" Created" -foregroundcolor Green
      return $webApp
  }
  catch{
      Write-error "App service NOT created" -ErrorAction Stop
  }
}

with those complete make sure to actually call them 

Clear-Host
PrintHeader

# create resource group              
CreateResourceGroup $name $location

#create service plan
$servicePlan = CreateAppServicePlan $name $location

#create app service
CreateAppService $name "API" $location $servicePlan

And with our successful deployment we now have a place to deploy our project.

and next lets set up Continuous deliver portion of our DevOps pipeline


and for good measure our full PowerShell file


Param (
  [Parameter()][String]$location,
  [Parameter()][String]$name,
  [Parameter()][String]$env)

$name = "$name-$env"


#sub functions
function PrintHeader{
  $doubleStar = "**"
  $line = "*****************************************************************************************"
  $msg = "Creating Azure enviroment for Tic Tac Toe"
  write-host $line
  write-host $doubleStar -NoNewline
  write-host $doubleStar.padLeft($line.length - 2)
  write-host $doubleStar -NoNewline                                                                                     
  write-host $doubleStar.padLeft($line.length - 2)
  
  write-host $doubleStar.PadRight(($line.Length - $msg.Length)/2) -NoNewline
  write-host $msg -NoNewline
  write-host $doubleStar.padLeft(($line.Length - $msg.Length)/2)
  
  write-host $doubleStar -NoNewline
  write-host $doubleStar.padLeft($line.length - 2)
  write-host $doubleStar -NoNewline                                                                                     
  write-host $doubleStar.padLeft($line.length - 2)
  write-host $line
}

#create resource group 
function CreateResourceGroup {
    Param(
        [Parameter(Mandatory=$true)][String]$name,
        [Parameter(Mandatory=$true)][String]$location)
    $rgName = "rg-$name"
    Write-Host "Createing $rgName resource group in $location" -ForegroundColor Magenta
    #check if resource group already exists
    $resourceGroup = Get-AzResourceGroup -Name $rgName -ErrorAction SilentlyContinue
    if($resourceGroup){
        Write-host "Resource group already exists" -foregroundcolor yellow
        return
    }

    #create resource group
    try {
        $resourceGroup = New-AzResourceGroup -Name $rgName -Location $location -Tag @{Type="Incubation"Enviroments="ALL"
        Write-host "Resource group created" -foregroundcolor Green
        return
    }
    catch {
        #failed to create resourcec group   
         Write-error "Resource group NOT created" -ErrorAction Stop  
    }
}

# Create app service plan
function CreateAppServicePlan {
  Param(
    [Parameter(Mandatory=$true)] [String$name,
    [Parameter(Mandatory=$true)] [String$location)
  $rgName = "rg-$name"
  $planName = "plan-$name"
  Write-Host "Createing $planName ($location) app service plan in $rgName resource group" -ForegroundColor Magenta
 

  #check if service plan already exists
  $servicePlan = Get-AzAppServicePlan -ResourceGroupName $rgName -Name $planName -ErrorAction SilentlyContinue
  if($servicePlan){
      Write-host "App service already exists" -foregroundcolor yellow
      return  $servicePlan
  }

  #create service plan
  try{
      $servicePlan = New-AzAppServicePlan -ResourceGroupName $rgName -Name $planName -Location $location -Tier "Free" -NumberofWorkers 1 -WorkerSize "Small" 
      Write-host "App service plan Created" -foregroundcolor Green
      return  $servicePlan
  }
  catch{
      Write-error "App service plan NOT created" -ErrorAction Stop
  }
}

# create app service
function CreateAppService {
  Param(
      [Parameter(Mandatory=$true)] [String$name,
      [Parameter(Mandatory=$true)] [String$prefix,
      [Parameter(Mandatory=$true)] [String$location,
      [Parameter(Mandatory=$true)] [Microsoft.Azure.Management.WebSites.Models.AppServicePlan$servicePlan)
  $rgName = "rg-$name"
  $appName = "app-$prefix-$name"
  Write-Host "Createing $appName web app in $rgName resource group" -ForegroundColor Magenta
  
  #check if web app already exists
  $webApp = Get-AzWebApp -ResourceGroupName $rgName -Name $appName -ErrorAction SilentlyContinue
  if($webApp){
      Write-host "Web app ""$appName"" already exists" -foregroundcolor yellow
      return $webApp
  }
  
  #create web app
  try{
      $webApp = New-AzWebApp -ResourceGroupName $rgName -Name $appName -Location $location -AppServicePlan $servicePlan.Name
      Write-host "App service ""$appName"" Created" -foregroundcolor Green
      return $webApp
  }
  catch{
      Write-error "App service NOT created" -ErrorAction Stop
  }
}

Clear-Host
PrintHeader

# create resource group              
CreateResourceGroup $name $location

#create service plan
$servicePlan = CreateAppServicePlan $name $location

#create app service
CreateAppService $name "API" $location $servicePlan