Shawn Sesna Shawn Sesna August 15, 2019

Verify App Settings with JSON

In my last post, I showed you how to verify all of your App Settings in a web.config file had corresponding Octopus project variables, and how to ensure all of your files configured for variable replacement don't leave any placeholders behind. This post will focus on verifying application settings stored in a JSON configuration file. In this case, we're looking at at .NET Core appSettings.json file but it could be extended to any JSON config file.

Comparing Environmental Files

It is not uncommon to have environmental app settings files for a .NET core application such as appSettings.Developement.json. The caveat to using this approach is that if something is added to the appSettings.Deveopment.json file, it needs to be added to the appSettings.json file as well. To address this issue, we can create a PowerShell Function with a recursive call to traverse the JSON file keys and then compare the entries to the environmental files. Similar to the last post, we can define any settings we want to ignore using the $settingsToIgnore array:

Function Get-AppSettings
{
    # Define parameters
    param($jsonObject,
        $parentNamespace,
        $settingsToIgnore)

    $namespace = ""
    $tempArray = @()


    if (![string]::IsNullOrEmpty($jsonObject) -and ($jsonObject.GetType().Name -eq "PSCustomObject"))
    {

        # Get number of properties
        $properties = $jsonObject | Get-Member -MemberType Properties | Select-Object -ExpandProperty Name

        # Check to see if it has properties
        if ($properties.Length -gt 0)
        {
            # Loop through returned properties
            foreach($property in $properties)
            {
                # Make sure we not supposed to ignore it
                if ($null -eq $parentNamespace)
                {
                    # Assign the property value
                    $namespace = $property
                }
                else
                {
                    $namespace = "$parentNamespace.$property"
                }

                # Make sure we're not supposed to ignore it
                if ($settingsToIgnore -notcontains $namespace)
                {
                    # Add the namespace to the array
                    $tempArray += $namespace

                    # Add the returned array from the recursive call
                    $tempArray += Get-AppSettings -jsonObject ($jsonObject.$property) -parentNamespace $namespace -settingsToIgnore $settingsToIgnore
                }
            }
        }
        else
        {
            # Add the value to the array
            if ($null -eq $parentNamespace)
            {
                $namespace = $property
            }
            else
            {
                $namespace = "$parentNamespace.$property"
            }

            $tempArray += $namespace
        }
    }

    return $tempArray
}

# Load the json file
$appSettingsFile = Get-Content -Path (Get-ChildItem -Path $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath'] | Where-Object {$_.Name -eq "appsettigs.config"}).FullName | ConvertFrom-Json

# Define working variable
$appSettings = @()
$settingsToIgnore = @("octofront.azure")

# Get all the json keys
$appSettings = Get-AppSettings -jsonObject $appSettingsFile -parentNamespace $null -settingsToIgnore $settingsToIgnore

# Get all files that are appsettings.something.json
$otherAppSettingsFiles = Get-ChildItem -Path  $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath']  | Where-Object {$_.Name -match "^appSettings.*..json"}

# Loop through other files
foreach ($otherFile in $otherAppSettingsFiles)
{
    # Convert the json to PowerShell objects
    $otherSettingsFile = Get-Content -Path $otherFile.FullName  | ConvertFrom-Json

    # Get the json keys
    $otherSettings = Get-AppSettings -jsonObject $otherSettingsFile -parentNamespace $null -settingsToIgnore $settingsToIgnore

    # Compare the properties to see if they are identical
    $results = Compare-Object -ReferenceObject $appSettings -DifferenceObject $otherSettings 

    # Check to see if something was returned
    if ($null -ne $results)
    {
        # Extract results
        $resultsError = $results | Out-String

        throw "Differences found in $($otherFile.FullName)`n $resultsError"
    }
}

Now, if we forget to add a setting to the main appsettings.json file, we can detect it and prevent the deployment from executing!

Ensuring App Settings have Octopus Parameters

Using the function Get-AppSettings defined above and the $settingsToIgnore array, we can use the following code to ensure the settings defined in App Settings have corresponding Octopus Deploy Variables:

# Load the json file
$appSettingsFile = Get-Content -Path (Get-ChildItem -Path $OctopusParameters['Octopus.Action.Package.InstallationDirectoryPath'] | Where-Object {$_.Name -eq "appsettigs.config"}).FullName | ConvertFrom-Json

# Define working variable
$appSettings = @()
$settingsToIgnore = @("octofront.azure")

# Get all the json keys
$appSettings = Get-AppSettings -jsonObject $appSettingsFile -parentNamespace $null -settingsToIgnore $settingsToIgnore

foreach ($appSetting in $appSettings)
{
    # Check to see if key is present
    if (!$OctopusParameters.ContainsKey($appSetting.Replace(".", ":"))) # Variables have : delimeters for nested json keys
    {
        # Fail the deployment
        throw "Octopus Parameter collection does not contain a value for $($appSetting)"
    }    
}

With these steps in place, you can help prevent deployments with missing values in the .json configuration file(s). Of course, this solution doesn't cover every scenario, but hopefully it gives you an idea of the preventative measures you can put in place.

Happy Deployments!

configuration