Variables - Substitute in Json file

Octopus.Script exported 2017-05-29 by mysterio2465 belongs to ‘Octopus’ category.

Use this step template after the Deploy API step template to substitute variables in a target json file like appsettings.json with Octopus scoped variables. Currently supported data types are string, boolean, interger, decimal and non-empty string arrays and can replace these types inside nested types as well as long as you name them like prop:subprop (like AppMetrics:GlobalTags) in octopus like we do today…this should be enough for most of the needs for files like appsettings.json

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

Path to the json file

JsonFilePath

For example if your appsettings.json file is in c:\web\site1\appsettings.json then set this value to c:\web\site1\appsettings.json. You can use Octopus variables here like #{Deployment_InstallationPath}\#{Octopus.Release.Number}\appsettings.json as the value of this variable. A good idea is to copy the value from the Install to variable in the Deploy API step template and add \appsettings.json if that is your target json file

Script body

Steps based on this template will execute the following PowerShell script.

function Get-Param($Name, [switch]$Required, $Default) {
    $result = $null

    if ($OctopusParameters -ne $null) {
        $result = $OctopusParameters[$Name]
    }

    if ($result -eq $null) {
        $variable = Get-Variable $Name -EA SilentlyContinue    
        if ($variable -ne $null) {
            $result = $variable.Value
        }
    }

    if ($result -eq $null) {
        if ($Required) {
            throw "Missing parameter value $Name"
        } else {
            $result = $Default
        }
    }

    return $result
}

$ErrorActionPreference = "Stop"

function UpdateJsonFile ([hashtable]$variables, [string]$fullpath) {
    Write-Host 'Starting the json file variable substitution' $variables.Count
    if ($variables -eq $null) {
        throw "Missing parameter value $variables"
    }

	$pathExists = Test-Path $fullpath
	if(!$pathExists) {
		Write-Host 'ERROR: Path '$fullpath ' does not exist'
		Exit 1
	}

	$json = Get-Content $fullpath -Raw | ConvertFrom-Json
    Write-Host 'Json content read from file'

	foreach($variable in $variables.GetEnumerator()) {
		$key = $variable.Key
        Write-Host 'Processing' $key
        $keys = $key.Split(':')
		$sub = $json
		$pre = $json
		$found = $true
		$lastKey = ''
		foreach($k in $keys) {
			if($sub | Get-Member -name $k -Membertype Properties){
				$pre = $sub
				$sub = $sub.$k
			}
			else
			{
				$found = $false
				break
			}

			$lastKey = $k
		}

		if($found) {
            Write-Host $key 'found in Json content'
            if($pre.$lastKey -eq $null) {
                Write-Host $key 'is null in the original source json...values CANNOT be null on the source json file...exiting with 1'
                Exit 1
            }

			$typeName = $pre.$lastKey.GetType().Name
			[bool]$b = $true
			[int]$i = 0
			[decimal]$d = 0.0
			if($typeName -eq 'String'){
				$pre.$lastKey = $variable.Value
			}
			elseif($typeName -eq 'Boolean' -and [bool]::TryParse($variable.Value, [ref]$b)) {
				$pre.$lastKey = $b
			}
			elseif($typeName -eq 'Int32' -and [int]::TryParse($variable.Value, [ref]$i)){
				$pre.$lastKey = $i
			}
			elseif($typeName -eq 'Decimal' -and [decimal]::TryParse($variable.Value, [ref]$d)){
				$pre.$lastKey = $d
			}
			elseif($typeName -eq 'Object[]') {
                if($pre.$lastKey.Length -ne 0 -and $pre.$lastKey[0].GetType().Name -eq 'String') {
				    $pre.$lastKey = $variable.Value.TrimStart('[').TrimEnd(']').Split(',')
                }
                else {
                    Write-Host 'ERROR: Cannot handle ' $key ' with type ' $typeName 
				    'Only nonempty string arrays are supported at the moment meaning that it has to be a 
				    string array with atleast one element in it in the original source appsettings.json 
				    file...Skipping update and exiting with 1'
				    Exit 1
                }
			}
			else {
				Write-Host 'ERROR: Cannot handle ' $key ' with type ' $typeName 
				'Only string, boolean, interger, decimal and non-empty string arrays are supported at the moment
                ...Skipping update and exiting with 1'
				Exit 1
			}

            Write-Host $key 'updated in json content with value' $pre.$lastKey 

		}
        else {
            Write-Host $key 'not found in Json content...skipping it'
        }
	}
    
	$json | ConvertTo-Json -depth 99 | Set-Content $fullpath

    
    Write-Host $fullpath 'file variables updated successfully...Done'
}


if($OctopusParameters -eq $null) {
    Write-Host 'OctopusParameters is null...exiting with 1'
    Exit 1    
}

function ConvertListToHashtable([object[]]$list) {
    $h = @{}
    foreach ($element in $list) {
	    $h.Add($element.Key, $element.Value)
    }
    return $h
}

$oParams = $OctopusParameters.getenumerator() | where-object {$_.key -notlike "Octopus*" -and $_.key -notlike "env:*"}

UpdateJsonFile `
(ConvertListToHashtable $oParams) `
(Get-Param "JsonFilePath" -Required)

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "9E85B3EB-7C19-4F3A-9E9E-71923198EE09",
  "Name": "Variables - Substitute in Json file",
  "Description": "Use this step template after the Deploy API step template to substitute variables in a target json file like appsettings.json with Octopus scoped variables. Currently supported data types are string, boolean, interger, decimal and non-empty string arrays and can replace these types inside nested types as well as long as you name them like prop:subprop:subsubprop (like AppMetrics:GlobalTags:env) in octopus like we do today...this should be enough for most of the needs for files like appsettings.json",
  "Version": 4,
  "ExportedAt": "2017-05-29T11:51:18.310Z",
  "ActionType": "Octopus.Script",
  "Author": "mysterio2465",
  "Parameters": [
    {
      "Name": "JsonFilePath",
      "Label": "Path to the json file",
      "HelpText": "For example if your appsettings.json file is in \nc:\\web\\site1\\appsettings.json then set this value to\n**c:\\web\\site1\\appsettings.json**. You can use Octopus \nvariables here like \n**#{Deployment_InstallationPath}\\\\#{Octopus.Release.Number}\\appsettings.json** as the value of this variable. A good idea is to copy the value from the Install to variable in the Deploy API step template and add \\appsettings.json if that is your target json file",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "function Get-Param($Name, [switch]$Required, $Default) {\n    $result = $null\n\n    if ($OctopusParameters -ne $null) {\n        $result = $OctopusParameters[$Name]\n    }\n\n    if ($result -eq $null) {\n        $variable = Get-Variable $Name -EA SilentlyContinue    \n        if ($variable -ne $null) {\n            $result = $variable.Value\n        }\n    }\n\n    if ($result -eq $null) {\n        if ($Required) {\n            throw \"Missing parameter value $Name\"\n        } else {\n            $result = $Default\n        }\n    }\n\n    return $result\n}\n\n$ErrorActionPreference = \"Stop\"\n\nfunction UpdateJsonFile ([hashtable]$variables, [string]$fullpath) {\n    Write-Host 'Starting the json file variable substitution' $variables.Count\n    if ($variables -eq $null) {\n        throw \"Missing parameter value $variables\"\n    }\n\n\t$pathExists = Test-Path $fullpath\n\tif(!$pathExists) {\n\t\tWrite-Host 'ERROR: Path '$fullpath ' does not exist'\n\t\tExit 1\n\t}\n\n\t$json = Get-Content $fullpath -Raw | ConvertFrom-Json\n    Write-Host 'Json content read from file'\n\n\tforeach($variable in $variables.GetEnumerator()) {\n\t\t$key = $variable.Key\n        Write-Host 'Processing' $key\n        $keys = $key.Split(':')\n\t\t$sub = $json\n\t\t$pre = $json\n\t\t$found = $true\n\t\t$lastKey = ''\n\t\tforeach($k in $keys) {\n\t\t\tif($sub | Get-Member -name $k -Membertype Properties){\n\t\t\t\t$pre = $sub\n\t\t\t\t$sub = $sub.$k\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\t$found = $false\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t$lastKey = $k\n\t\t}\n\n\t\tif($found) {\n            Write-Host $key 'found in Json content'\n            if($pre.$lastKey -eq $null) {\n                Write-Host $key 'is null in the original source json...values CANNOT be null on the source json file...exiting with 1'\n                Exit 1\n            }\n\n\t\t\t$typeName = $pre.$lastKey.GetType().Name\n\t\t\t[bool]$b = $true\n\t\t\t[int]$i = 0\n\t\t\t[decimal]$d = 0.0\n\t\t\tif($typeName -eq 'String'){\n\t\t\t\t$pre.$lastKey = $variable.Value\n\t\t\t}\n\t\t\telseif($typeName -eq 'Boolean' -and [bool]::TryParse($variable.Value, [ref]$b)) {\n\t\t\t\t$pre.$lastKey = $b\n\t\t\t}\n\t\t\telseif($typeName -eq 'Int32' -and [int]::TryParse($variable.Value, [ref]$i)){\n\t\t\t\t$pre.$lastKey = $i\n\t\t\t}\n\t\t\telseif($typeName -eq 'Decimal' -and [decimal]::TryParse($variable.Value, [ref]$d)){\n\t\t\t\t$pre.$lastKey = $d\n\t\t\t}\n\t\t\telseif($typeName -eq 'Object[]') {\n                if($pre.$lastKey.Length -ne 0 -and $pre.$lastKey[0].GetType().Name -eq 'String') {\n\t\t\t\t    $pre.$lastKey = $variable.Value.TrimStart('[').TrimEnd(']').Split(',')\n                }\n                else {\n                    Write-Host 'ERROR: Cannot handle ' $key ' with type ' $typeName \n\t\t\t\t    'Only nonempty string arrays are supported at the moment meaning that it has to be a \n\t\t\t\t    string array with atleast one element in it in the original source appsettings.json \n\t\t\t\t    file...Skipping update and exiting with 1'\n\t\t\t\t    Exit 1\n                }\n\t\t\t}\n\t\t\telse {\n\t\t\t\tWrite-Host 'ERROR: Cannot handle ' $key ' with type ' $typeName \n\t\t\t\t'Only string, boolean, interger, decimal and non-empty string arrays are supported at the moment\n                ...Skipping update and exiting with 1'\n\t\t\t\tExit 1\n\t\t\t}\n\n            Write-Host $key 'updated in json content with value' $pre.$lastKey \n\n\t\t}\n        else {\n            Write-Host $key 'not found in Json content...skipping it'\n        }\n\t}\n    \n\t$json | ConvertTo-Json -depth 99 | Set-Content $fullpath\n\n    \n    Write-Host $fullpath 'file variables updated successfully...Done'\n}\n\n\nif($OctopusParameters -eq $null) {\n    Write-Host 'OctopusParameters is null...exiting with 1'\n    Exit 1    \n}\n\nfunction ConvertListToHashtable([object[]]$list) {\n    $h = @{}\n    foreach ($element in $list) {\n\t    $h.Add($element.Key, $element.Value)\n    }\n    return $h\n}\n\n$oParams = $OctopusParameters.getenumerator() | where-object {$_.key -notlike \"Octopus*\" -and $_.key -notlike \"env:*\"}\n\nUpdateJsonFile `\n(ConvertListToHashtable $oParams) `\n(Get-Param \"JsonFilePath\" -Required)\n"
  },
  "Category": "Octopus",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/variables-substitute-in-json-file.json",
  "Website": "/step-templates/9E85B3EB-7C19-4F3A-9E9E-71923198EE09",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1QTFRFT6Tl////L5Pg8vj9Y67omsvwPJrisdfzfbzs5fL7y+T32Ov5isLucLXqvt31CJPHWwAABMJJREFUeNrs3deW4jAMAFDF3U75/89dlp0ZhiU4blJEjvQ8hYubLJsA00UCBCIQgQhEIAIRiEAEIhCBCEQgAhGIQAQiEIEIhD8kJm+t+QprfdKfB9HbYpx6CWfspj8HMi+gMgHL/AmQA8W3JTKH+ALFvzCeL0RbpyoCPE9IJeNOSQwh5Z3qd6yRGWQ2qi2cZQWxqj1WzQYSjeoJmJlAklOd4VlArOqPhQEkqBERToeMcfRJBkC0Uep8CfBpjz4JsHJ0zF3dkEWNje0kiB/sUC6eApndaIiCMyAa1PiwJ0AWhRGJHJJQHG2dC7h1rNbO1QOxSA7lNCkkKrQIpJCAB1GREILYIC1NAiwbpKFJgGWDNExcwGstfExcZBCHC6nOglshHtmhViLIig1RNBCN7qjtW8C0Z1UvJcC1Z9XmwMBzzvobmgAyEzgq91dtEEsBsQSQQAFZCSBAATEEEApHZbrVBIkkEIUPSVeB+KtALA0kXQUSrwKZBCIQBnk8Y4i5CsReBeKvkqLM+BCSDWJlrZFvGk9SRTHshkgjZCGAaArIxm3H3grhVzFlW2msfl1ca79UJ1bofYvsDHHlNdTZnlh5MghuPd5NdBDUNZHyCkfktIh03XzALGRPlBDPac7qgWjHZzWcmF5zmmkhidMQ6boKiDXcDTUEaylZqCGJ0Vjvu/fLJtHqhSANEvqb2OYqkOUqEHuVMbJcZdZCGiPhKhC4yjqiIjEE7XThMp8fAWII3mY3kUIQD+AMKQTzPiBhgQ63HlT/KSvgtoi0dq5mCPah1UIE0eh3sT0NhOByvKeAkFzi8PgQomumFhsyOxpIzZN4gLOj5plVwNpR0b2AuePWKBEHQu24pSsJA+LVCeHHQxZ1SiyDIdqok8IOhSSnTottHEQTdyt4ettAj4KkzA4dMikk2Dht2S5ptm1vswnPDxn0YyDZ5oDM3iToo2T5voWaYe+Q+vdjH80QyAzZhCgcDtLMI1Tmtz9w++XHgziHQHJJu/OZ3bs9Xn8gQ72NcP3dKqEfkp10F51xhoIi2I91R+LurXV/5q7pH+wx061CzO16oSQleMyr8fXvwMA0Pro8432DPD/ySx8XrHfSuDAM8n6UhnjQabaiXf5Bq/lREHvEeNtn1rJ08+C/uXkQZHeguxAPC3UvtcJYUogLzZX5hhZZvS6onG5lxXtzWGaygwb79vT/IXhdlNibwlKYOR6T8xjI7W8n+xV7T+GH4tMzWwR+lZhRkJYSsC0thpmCYqyngOz3rN2FLBZ2wZflBCggUHF0Vnp88JKienzIXLSEZCZqU7IKr/gQW9yx3pzV7Y9kvWZWTRRIqDmTtRUnU7b2lLcTYmoqHqnmiO1poER0SPkAeZMAZxaJx0Y3TCdAclsIqDz03ALcyxfTCZBsthoGXWmigGyVhWPLFJJfuuKQWycoEFdXbH4dJJoJxNR1eD/kshz6yn48cF8yW8sFoitflB1w6Q8n+/15Za7oA17/pYNmYgP5fmWm8L1NOHPWgK8kuFew1/JXtOA0yJCv7ah7X8ObUuT5kObU30+fDZm8+zqP+HTIpK0xQ796b5Kv2hSIQAQiEIEIRCACEYhABCIQgQhEIAIRiEAEIpBf8UeAAQAEjtYmlDTcCgAAAABJRU5ErkJggg==",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Monday, May 29, 2017