Sunday, December 15, 2019

Updating App config in Azure Pipeline Using Octopus Variable set


This post discuss how to update App.Config in Azure pipeline using  Octopus variable set.

Prerequisites:

  • Octopus environments
  • Octopus variable sets
  • Octopus project with variable sets attached

Let's identify how we configure a project and variable sets in the Octopus. Following image shows the Octopus variable and values defined with the scope.

You can see the variable name and values. As example, we use st-#{projectPrefix}-user value in prod and uat environment. If the deployment done to any other environment, variable value should be #{xamdevStorageAccountName} which is referring to another variable in another variable set. Like wise we can define variable values for each scope. another thing you need to know here is, if variable value starts with #{ it means this is defined in another variable set. You refer another variable set to read the real value.So, once you read the value from this variable set, you should be able to get the real value by referring the other variable set. To do this, you can create an Octopus project and add both variable sets to it. Then it resolve the variable references within another variable set as value of given variables and return the real variable value for you.


Now let's see how we access the octopus REST API and and read variable values using the PowerShell script.
In this script we have defined few parameters. Before reading the variable values, we need to build up a connection with octopus. For that purpose, we have to provide Octopus server URL and Octopus API key. After build up the connection we can provide the octopus space name where we have the octopus project, Octopus project name and environment. In this sample PowerShell script we read values from the variable set and update the values in App.config file. So, we need to provide the path of the App.config file. Further, if we need to read values from Azure key vault, it is possible to do using this script. If you want to read values from key vault, you can provide Azure account user, password and subscription id.

Param(
    [Parameter(Mandatory=$true)]
    [string] $octopusUrl,
    [Parameter(Mandatory=$true)]
    [string] $octopusAPIKey,
    [Parameter(Mandatory=$true)]
    [string] $environmentName = 'DEV',
    [Parameter(Mandatory=$true)]
    [string] $octopusSpaceName = 'Default',
    [Parameter(Mandatory=$true)]
    [string] $octopusProjectName,
    [Parameter(Mandatory=$true)]
    [string] $appConfigFilePath,
    [Parameter(Mandatory=$false)]
    [string] $azureAccoutUser,
    [Parameter(Mandatory=$false)]
    [string] $azureAccoutUserPwd,
    [Parameter(Mandatory=$false)]
    [string] $azureSubscriptionId
)

Next part of the script we get the  App config value to PowerShell variable.Then find the space of the project, find the environment id and find the octopus project id using the API calls.

Write-Host ("Get content of " + $appConfigFilePath +"...")
    # Read the App.config content
    $doc = ((Get-Content -path $appConfigFilePath) -replace "`0", "") -as [xml];

    # Find the space
    $spaceList = Invoke-RestMethod "$OctopusUrl/api/spaces?Name=$spaceName" -Headers $header
    $spaceFilter = @($spaceList.Items | Where {$_.Name -eq $octopusSpaceName})
    $spaceId = $spaceFilter[0].Id
    Write-Host "The spaceId for Space Name $octopusSpaceName is $spaceId"

    # Find the environment
    $environmentList = Invoke-RestMethod "$OctopusUrl/api/$spaceId/environments?skip=0&take=1000&name=$environmentName" -Headers $header
    $environmentFilter = @($environmentList.Items | Where {$_.Name -eq $environmentName})
    $environmentId = $environmentFilter[0].Id
    Write-Host "The environmentId for Environment Name $environmentName in space $octopusSpaceName is $environmentId"

    # Find the project
    $projects = Invoke-RestMethod  -UseBasicParsing "$OctopusUrl/api/$spaceId/projects/all?skip=0&take=1000&name=$projectName&" -Headers $header
    $projectFilter = @($projects | Where {$_.Name -eq $octopusProjectName})
    $projectId = $projectFilter[0].Id
    Write-Host "The projectId for Project Name $octopusProjectName in space $octopusSpaceName is $projectId"

    # Get the evaluated variables for the provided scope
    $evaluatedVariables = (Invoke-RestMethod -UseBasicParsing "$OctopusURL/api/$spaceId/variables/preview?project=$projectId&environment=$environmentId" -Headers $header).Variables

After that read the values from the relevant variable set, replace App config file values using the octopus variable set values.

# Loop each of variable from octopus - begin
    foreach($octopusVariable in $evaluatedVariables)
    {
      #check if the app.config has a key by name of the variable
       $variableName=$octopusVariable.Name;

       Write-Host ("Processing variable " + $variableName +"...")

       $appConfigEntry = $doc.configuration.appSettings.add | where {$_.Key -eq $variableName}

            #if app config has the key with the variable name -begin
            if(-not ([string]::IsNullOrEmpty($appConfigEntry)))
            {
                Write-Host ("App Config entry found for variable " + $variableName)
               
                # Assign octopus variable value to a $variableValue
                $variableValue=$octopusVariable.Value;

                #Check if the variable name starts with @Microsoft.KeyVault - begin
                if($variableValue.startswith('@Microsoft.KeyVault'))
                {
                    Write-Host ("Variable need to be obtained from KV")

                    if ($notLoggedOntoAz)
                    {
                        Write-Host ("Log on to Azure")

                        $notLoggedOntoAz = $false;

                        az login --username $azureAccoutUser --password $azureAccoutUserPwd
                        az account set --subscription $azureSubscriptionId

                        Write-Host ("Log on to Azure sucess.")
                    }

                    $splitSections=$variableValue.split(";");
                    $splitSecretName=$splitSections[1].split("=");
                    $secretName=$splitSecretName[1];
                    $keyVaultName = $splitSections[0].Replace("@Microsoft.KeyVault(VaultName=","");
                    $secretVersion=$splitSections[2].Replace("SecretVersion=","").Replace(")","");

                    #read the variable value from KV and assign to $variableValue
                    $variableValue = (az keyvault secret show -n $secretName --vault-name $keyVaultName --version $secretVersion | ConvertFrom-Json).value

                    Write-Host ("Obtained value from KV for varaible " + $variableName)
                }
                #Check if the variable name starts with @Microsoft.KeyVault - end

                #Update app .config entry with $variableValue
                $appConfigEntry.value = $variableValue
                Write-Host ("Updated app config entry.")
            }#if app config has the key with the variable name -end
       
        Write-Host ("Processed variable " + $variableName +".")
      }# Loop each of variable from octopus - end


Then we save the updated config file content to the app.config.

Write-Host ("Saving config file " + $appConfigFilePath)
    #save the app.config
    $doc.Save($appConfigFilePath)
    Write-Host ("Done.")

The full script can be downloaded from here (https://gist.github.com/PushpaH/5e748ee4b905208cc69b384713a87dfa)
You can use the script with PowerShell task in Azure pipelines as follows.

No comments:

Post a Comment