Akamai - Content Purge

Octopus.Script exported 2016-07-11 by roberto-mardeni belongs to ‘Akamai’ category.

Allows to purge content using the Content Control Utility (CCU) v2 REST API.

Parameters

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

Client Token

AkamaiClientToken

Authentication token used in client authentication. Available in Luna Portal.

Client Access Token

AkamaiClientAccessToken

Authentication token used in client authentication. Available in Luna Portal.

Secret

AkamaiSecret

Authentication password used in client authentication. Available in Luna Portal.

Queue

AkamaiQueue = default

Purge requests queue

Objects

AkamaiObjects

A comma separated list of objects to purge, either URLs or CPCODEs

Type

AkamaiType = arl

The type associated with the items in the Objects parameter

Action

AkamaiAction = invalidate

The action to execute on the purge operation

Domain

AkamaiDomain = production

The Akamai domain to perform the purge operation on

Proxy User

ProxyUser

Optional, a user name for the proxy if required in the network

Proxy Password

ProxyPassword

Optional, the password for the account to use if a Proxy User was specified

Wait

WaitForPurgeToComplete = false

Indicates if the step should wait for the purge operation to complete

Check Limit

CheckLimit = 2

Maximum number of times to check for the purge operation to complete if set to Wait

Script body

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

$clientToken = $OctopusParameters['AkamaiClientToken']
$clientAccessToken = $OctopusParameters['AkamaiClientAccessToken']
$clientSecret = $OctopusParameters['AkamaiSecret']
$queueName = $OctopusParameters['AkamaiQueue']
$objects = $OctopusParameters['AkamaiObjects'] -split ","
$type = $OctopusParameters['AkamaiType']
$action = $OctopusParameters['AkamaiAction']
$domain = $OctopusParameters['AkamaiDomain']
$proxyUser = $OctopusParameters['ProxyUser']
$proxyPassword = $OctopusParameters['ProxyPassword']

$wait = [bool]::Parse($OctopusParameters['WaitForPurgeToComplete'])
$maxChecks = [int]::Parse($OctopusParameters['CheckLimit'])

if ($proxyUser) {
    $securePassword = ConvertTo-SecureString $proxyPassword -AsPlainText -Force
    $proxyCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $proxyUser,$securePassword

    (New-Object System.Net.WebClient).Proxy.Credentials=$proxyCredential
}

# Copied from https://github.com/akamai-open/AkamaiOPEN-powershell/blob/master/Invoke-AkamaiOPEN.ps1
function Invoke-AkamaiOpenRequest {
    param(
        [Parameter(Mandatory=$true)][string]$Method, 
        [Parameter(Mandatory=$true)][string]$ClientToken, 
        [Parameter(Mandatory=$true)][string]$ClientAccessToken, 
        [Parameter(Mandatory=$true)][string]$ClientSecret, 
        [Parameter(Mandatory=$true)][string]$ReqURL, 
        [Parameter(Mandatory=$false)][string]$Body)

    #Function to generate HMAC SHA256 Base64
    Function Crypto ($secret, $message)
    {
	    [byte[]] $keyByte = [System.Text.Encoding]::ASCII.GetBytes($secret)
	    [byte[]] $messageBytes = [System.Text.Encoding]::ASCII.GetBytes($message)
	    $hmac = new-object System.Security.Cryptography.HMACSHA256((,$keyByte))
	    [byte[]] $hashmessage = $hmac.ComputeHash($messageBytes)
	    $Crypt = [System.Convert]::ToBase64String($hashmessage)

	    return $Crypt
    }

    #ReqURL Verification
    If (($ReqURL -as [System.URI]).AbsoluteURI -eq $null -or $ReqURL -notmatch "akamai.com")
    {
	    throw "Error: Ivalid Request URI"
    }

    #Sanitize Method param
    $Method = $Method.ToUpper()

    #Split $ReqURL for inclusion in SignatureData
    $ReqArray = $ReqURL -split "(.*\/{2})(.*?)(\/)(.*)"

    #Timestamp for request signing
    $TimeStamp = [DateTime]::UtcNow.ToString("yyyyMMddTHH:mm:sszz00")

    #GUID for request signing
    $Nonce = [GUID]::NewGuid()

    #Build data string for signature generation
    $SignatureData = $Method + "`thttps`t"
    $SignatureData += $ReqArray[2] + "`t" + $ReqArray[3] + $ReqArray[4]

    if (($Body -ne $null) -and ($Method -ceq "POST"))
    {
	    $Body_SHA256 = [System.Security.Cryptography.SHA256]::Create()
	    $Post_Hash = [System.Convert]::ToBase64String($Body_SHA256.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($Body.ToString())))

	    $SignatureData += "`t`t" + $Post_Hash + "`t"
    }
    else
    {
	    $SignatureData += "`t`t`t"
    }

    $SignatureData += "EG1-HMAC-SHA256 "
    $SignatureData += "client_token=" + $ClientToken + ";"
    $SignatureData += "access_token=" + $ClientAccessToken + ";"
    $SignatureData += "timestamp=" + $TimeStamp  + ";"
    $SignatureData += "nonce=" + $Nonce + ";"

    #Generate SigningKey
    $SigningKey = Crypto -secret $ClientSecret -message $TimeStamp

    #Generate Auth Signature
    $Signature = Crypto -secret $SigningKey -message $SignatureData

    #Create AuthHeader
    $AuthorizationHeader = "EG1-HMAC-SHA256 "
    $AuthorizationHeader += "client_token=" + $ClientToken + ";"
    $AuthorizationHeader += "access_token=" + $ClientAccessToken + ";"
    $AuthorizationHeader += "timestamp=" + $TimeStamp + ";"
    $AuthorizationHeader += "nonce=" + $Nonce + ";"
    $AuthorizationHeader += "signature=" + $Signature

    #Create IDictionary to hold request headers
    $Headers = @{}

    #Add Auth header
    $Headers.Add('Authorization',$AuthorizationHeader)

    #Add additional headers if POSTing or PUTing
    If (($Method -ceq "POST") -or ($Method -ceq "PUT"))
    {
	    $Body_Size = [System.Text.Encoding]::UTF8.GetByteCount($Body)
	    $Headers.Add('max-body',$Body_Size.ToString())

        # turn off the "Expect: 100 Continue" header
        # as it's not supported on the Akamai side.
        [System.Net.ServicePointManager]::Expect100Continue = $false
    }

    #Check for valid Methods and required switches
    If (($Method -ceq "POST") -and ($Body -ne $null))
    {
        Invoke-RestMethod -Method $Method -Uri $ReqURL -Headers $Headers -Body $Body -ContentType 'application/json'
    }
    elseif  (($Method -ceq "PUT") -and ($Body -ne $null))
    {
	    #Invoke API call with PUT and return
	    Invoke-RestMethod -Method $Method -Uri $ReqURL -Headers $Headers -Body $Body -ContentType 'application/json'
    }
    elseif (($Method -ceq "GET") -or ($Method -ceq "DELETE"))
    {
	    #Invoke API call with GET or DELETE and return
	    Invoke-RestMethod -Method $Method -Uri $ReqURL -Headers $Headers
    }
    else
    {
	    throw "Error: Invalid -Method specified or missing required parameter"
    }
}

function Perform-AkamaiRequest {
    param (
        [string]$request, 
        [string]$method="Get", 
        [int]$expectedStatusCode=200, 
        $body)

    $baseUrl = "http://private-anon-3934daf8d-akamaiopen2purgeccuv2production.apiary-mock.com"
    # $baseUrl = "https://api.ccu.akamai.com"
    $uri = "{0}{1}" -f $baseUrl,$request

    if ($uri -match "mock"){
        $requestHeaders = @{'Cache-Control'='no-cache,proxy-revalidate'}
        $response = Invoke-RestMethod -Uri $uri -Method $method -DisableKeepAlive -Headers $requestHeaders -Body $body
    } else {
        $json = ConvertTo-Json $body -Compress
        $response = Invoke-AkamaiOpenRequest -Method $method -ClientToken $clientToken -ClientAccessToken $clientAccessToken -ClientSecret $clientSecret -ReqURL $uri -Body $json
    }

    if ($response.httpStatus -ne $expectedStatusCode){
        Write-Error "Request not processed correctly: $($response.detail)"
    } elseif ($response.detail) {
        Write-Verbose $response.detail
    }

    Write-Verbose $response

    $response
}

function Get-QueueSize {
    param ([string]$queueName)

    $queueSize = Perform-AkamaiRequest "/ccu/v2/queues/$queueName"

    $queueSize 
}

function Request-Purge {
    param ($objects,[string]$type="arl",[string]$action="remove",[string]$domain="production")

    $body = @{
        objects = $objects
        action = $action
        type = $type
        domain = $domain
    }

    Perform-AkamaiRequest "/ccu/v2/queues/$queueName" "Post" 201 $body
}

function Get-PurgeStatus {
    param ([string]$purgeId)

    $status = Perform-AkamaiRequest "/ccu/v2/purges/$purgeId"

    Write-Host "Purge status: $($status.purgeStatus)"

    $status
}

$queueSize = Get-QueueSize $queueName
Write-Output "$($queueName) queue size is $($queueSize.queueLength)"

$purge = Request-Purge $objects $type $action $domain

Write-Output "Purge request created"
Write-Output "PurgeId: $($purge.purgeId)"
Write-Output "SupportId: $($purge.supportId)" 

if ($wait) {
    $check = 1
    $purgeStatus = "Unknown"

    do {
        if ($check -gt 1) {
            Write-Output "Waiting $($purge.pingAfterSeconds) seconds before checking purge status again"
            Start-Sleep -Seconds $purge.pingAfterSeconds
        }
        $status = Get-PurgeStatus $purge.purgeId 
        $purgeStatus = $status.purgeStatus
        $check = $check + 1
        if ($check -gt $maxChecks) { 
            Write-Output "Maximum number of checks reached"
        }
    } while ($status.purgeStatus -ne "Done" -and $check -le $maxChecks)

    if ($status.purgeStatus -ne "Done") {
        Write-Error "Purge status is not Done"
    }
}

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": "0bfe41dc-50b0-44a7-abe3-e6ba2a6d317a",
  "Name": "Akamai - Content Purge",
  "Description": "Allows to purge content using the Content Control Utility (CCU) v2 REST API.",
  "Version": 1,
  "ExportedAt": "2016-07-11T14:27:34.748+00:00",
  "ActionType": "Octopus.Script",
  "Author": "roberto-mardeni",
  "Parameters": [
    {
      "Name": "AkamaiClientToken",
      "Label": "Client Token",
      "HelpText": "Authentication token used in client authentication. Available in Luna Portal.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "AkamaiClientAccessToken",
      "Label": "Client Access Token",
      "HelpText": "Authentication token used in client authentication. Available in Luna Portal.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "AkamaiSecret",
      "Label": "Secret",
      "HelpText": "Authentication password used in client authentication. Available in Luna Portal.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Name": "AkamaiQueue",
      "Label": "Queue",
      "HelpText": "Purge requests queue",
      "DefaultValue": "default",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "default\nemergency"
      }
    },
    {
      "Name": "AkamaiObjects",
      "Label": "Objects",
      "HelpText": "A comma separated list of objects to purge, either URLs or CPCODEs",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "AkamaiType",
      "Label": "Type",
      "HelpText": "The type associated with the items in the Objects parameter",
      "DefaultValue": "arl",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "arl\ncpcode"
      }
    },
    {
      "Name": "AkamaiAction",
      "Label": "Action",
      "HelpText": "The action to execute on the purge operation",
      "DefaultValue": "invalidate",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "invalidate\nremove"
      }
    },
    {
      "Name": "AkamaiDomain",
      "Label": "Domain",
      "HelpText": "The Akamai domain to perform the purge operation on",
      "DefaultValue": "production",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "production\nstaging"
      }
    },
    {
      "Name": "ProxyUser",
      "Label": "Proxy User",
      "HelpText": "Optional, a user name for the proxy if required in the network",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "ProxyPassword",
      "Label": "Proxy Password",
      "HelpText": "Optional, the password for the account to use if a Proxy User was specified",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Name": "WaitForPurgeToComplete",
      "Label": "Wait",
      "HelpText": "Indicates if the step should wait for the purge operation to complete",
      "DefaultValue": "false",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "false|No\ntrue|Yes"
      }
    },
    {
      "Name": "CheckLimit",
      "Label": "Check Limit",
      "HelpText": "Maximum number of times to check for the purge operation to complete if set to **Wait**",
      "DefaultValue": "2",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText",
        "Octopus.SelectOptions": "0\n5\n10"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.ScriptBody": "$clientToken = $OctopusParameters['AkamaiClientToken']\n$clientAccessToken = $OctopusParameters['AkamaiClientAccessToken']\n$clientSecret = $OctopusParameters['AkamaiSecret']\n$queueName = $OctopusParameters['AkamaiQueue']\n$objects = $OctopusParameters['AkamaiObjects'] -split \",\"\n$type = $OctopusParameters['AkamaiType']\n$action = $OctopusParameters['AkamaiAction']\n$domain = $OctopusParameters['AkamaiDomain']\n$proxyUser = $OctopusParameters['ProxyUser']\n$proxyPassword = $OctopusParameters['ProxyPassword']\n\n$wait = [bool]::Parse($OctopusParameters['WaitForPurgeToComplete'])\n$maxChecks = [int]::Parse($OctopusParameters['CheckLimit'])\n\nif ($proxyUser) {\n    $securePassword = ConvertTo-SecureString $proxyPassword -AsPlainText -Force\n    $proxyCredential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $proxyUser,$securePassword\n\n    (New-Object System.Net.WebClient).Proxy.Credentials=$proxyCredential\n}\n\n# Copied from https://github.com/akamai-open/AkamaiOPEN-powershell/blob/master/Invoke-AkamaiOPEN.ps1\nfunction Invoke-AkamaiOpenRequest {\n    param(\n        [Parameter(Mandatory=$true)][string]$Method, \n        [Parameter(Mandatory=$true)][string]$ClientToken, \n        [Parameter(Mandatory=$true)][string]$ClientAccessToken, \n        [Parameter(Mandatory=$true)][string]$ClientSecret, \n        [Parameter(Mandatory=$true)][string]$ReqURL, \n        [Parameter(Mandatory=$false)][string]$Body)\n\n    #Function to generate HMAC SHA256 Base64\n    Function Crypto ($secret, $message)\n    {\n\t    [byte[]] $keyByte = [System.Text.Encoding]::ASCII.GetBytes($secret)\n\t    [byte[]] $messageBytes = [System.Text.Encoding]::ASCII.GetBytes($message)\n\t    $hmac = new-object System.Security.Cryptography.HMACSHA256((,$keyByte))\n\t    [byte[]] $hashmessage = $hmac.ComputeHash($messageBytes)\n\t    $Crypt = [System.Convert]::ToBase64String($hashmessage)\n\n\t    return $Crypt\n    }\n\n    #ReqURL Verification\n    If (($ReqURL -as [System.URI]).AbsoluteURI -eq $null -or $ReqURL -notmatch \"akamai.com\")\n    {\n\t    throw \"Error: Ivalid Request URI\"\n    }\n\n    #Sanitize Method param\n    $Method = $Method.ToUpper()\n\n    #Split $ReqURL for inclusion in SignatureData\n    $ReqArray = $ReqURL -split \"(.*\\/{2})(.*?)(\\/)(.*)\"\n\n    #Timestamp for request signing\n    $TimeStamp = [DateTime]::UtcNow.ToString(\"yyyyMMddTHH:mm:sszz00\")\n\n    #GUID for request signing\n    $Nonce = [GUID]::NewGuid()\n\n    #Build data string for signature generation\n    $SignatureData = $Method + \"`thttps`t\"\n    $SignatureData += $ReqArray[2] + \"`t\" + $ReqArray[3] + $ReqArray[4]\n\n    if (($Body -ne $null) -and ($Method -ceq \"POST\"))\n    {\n\t    $Body_SHA256 = [System.Security.Cryptography.SHA256]::Create()\n\t    $Post_Hash = [System.Convert]::ToBase64String($Body_SHA256.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($Body.ToString())))\n\n\t    $SignatureData += \"`t`t\" + $Post_Hash + \"`t\"\n    }\n    else\n    {\n\t    $SignatureData += \"`t`t`t\"\n    }\n\n    $SignatureData += \"EG1-HMAC-SHA256 \"\n    $SignatureData += \"client_token=\" + $ClientToken + \";\"\n    $SignatureData += \"access_token=\" + $ClientAccessToken + \";\"\n    $SignatureData += \"timestamp=\" + $TimeStamp  + \";\"\n    $SignatureData += \"nonce=\" + $Nonce + \";\"\n\n    #Generate SigningKey\n    $SigningKey = Crypto -secret $ClientSecret -message $TimeStamp\n\n    #Generate Auth Signature\n    $Signature = Crypto -secret $SigningKey -message $SignatureData\n\n    #Create AuthHeader\n    $AuthorizationHeader = \"EG1-HMAC-SHA256 \"\n    $AuthorizationHeader += \"client_token=\" + $ClientToken + \";\"\n    $AuthorizationHeader += \"access_token=\" + $ClientAccessToken + \";\"\n    $AuthorizationHeader += \"timestamp=\" + $TimeStamp + \";\"\n    $AuthorizationHeader += \"nonce=\" + $Nonce + \";\"\n    $AuthorizationHeader += \"signature=\" + $Signature\n\n    #Create IDictionary to hold request headers\n    $Headers = @{}\n\n    #Add Auth header\n    $Headers.Add('Authorization',$AuthorizationHeader)\n\n    #Add additional headers if POSTing or PUTing\n    If (($Method -ceq \"POST\") -or ($Method -ceq \"PUT\"))\n    {\n\t    $Body_Size = [System.Text.Encoding]::UTF8.GetByteCount($Body)\n\t    $Headers.Add('max-body',$Body_Size.ToString())\n\n        # turn off the \"Expect: 100 Continue\" header\n        # as it's not supported on the Akamai side.\n        [System.Net.ServicePointManager]::Expect100Continue = $false\n    }\n\n    #Check for valid Methods and required switches\n    If (($Method -ceq \"POST\") -and ($Body -ne $null))\n    {\n        Invoke-RestMethod -Method $Method -Uri $ReqURL -Headers $Headers -Body $Body -ContentType 'application/json'\n    }\n    elseif  (($Method -ceq \"PUT\") -and ($Body -ne $null))\n    {\n\t    #Invoke API call with PUT and return\n\t    Invoke-RestMethod -Method $Method -Uri $ReqURL -Headers $Headers -Body $Body -ContentType 'application/json'\n    }\n    elseif (($Method -ceq \"GET\") -or ($Method -ceq \"DELETE\"))\n    {\n\t    #Invoke API call with GET or DELETE and return\n\t    Invoke-RestMethod -Method $Method -Uri $ReqURL -Headers $Headers\n    }\n    else\n    {\n\t    throw \"Error: Invalid -Method specified or missing required parameter\"\n    }\n}\n\nfunction Perform-AkamaiRequest {\n    param (\n        [string]$request, \n        [string]$method=\"Get\", \n        [int]$expectedStatusCode=200, \n        $body)\n\n    $baseUrl = \"http://private-anon-3934daf8d-akamaiopen2purgeccuv2production.apiary-mock.com\"\n    # $baseUrl = \"https://api.ccu.akamai.com\"\n    $uri = \"{0}{1}\" -f $baseUrl,$request\n\n    if ($uri -match \"mock\"){\n        $requestHeaders = @{'Cache-Control'='no-cache,proxy-revalidate'}\n        $response = Invoke-RestMethod -Uri $uri -Method $method -DisableKeepAlive -Headers $requestHeaders -Body $body\n    } else {\n        $json = ConvertTo-Json $body -Compress\n        $response = Invoke-AkamaiOpenRequest -Method $method -ClientToken $clientToken -ClientAccessToken $clientAccessToken -ClientSecret $clientSecret -ReqURL $uri -Body $json\n    }\n\n    if ($response.httpStatus -ne $expectedStatusCode){\n        Write-Error \"Request not processed correctly: $($response.detail)\"\n    } elseif ($response.detail) {\n        Write-Verbose $response.detail\n    }\n\n    Write-Verbose $response\n\n    $response\n}\n\nfunction Get-QueueSize {\n    param ([string]$queueName)\n\n    $queueSize = Perform-AkamaiRequest \"/ccu/v2/queues/$queueName\"\n\n    $queueSize \n}\n\nfunction Request-Purge {\n    param ($objects,[string]$type=\"arl\",[string]$action=\"remove\",[string]$domain=\"production\")\n\n    $body = @{\n        objects = $objects\n        action = $action\n        type = $type\n        domain = $domain\n    }\n\n    Perform-AkamaiRequest \"/ccu/v2/queues/$queueName\" \"Post\" 201 $body\n}\n\nfunction Get-PurgeStatus {\n    param ([string]$purgeId)\n\n    $status = Perform-AkamaiRequest \"/ccu/v2/purges/$purgeId\"\n\n    Write-Host \"Purge status: $($status.purgeStatus)\"\n\n    $status\n}\n\n$queueSize = Get-QueueSize $queueName\nWrite-Output \"$($queueName) queue size is $($queueSize.queueLength)\"\n\n$purge = Request-Purge $objects $type $action $domain\n\nWrite-Output \"Purge request created\"\nWrite-Output \"PurgeId: $($purge.purgeId)\"\nWrite-Output \"SupportId: $($purge.supportId)\" \n\nif ($wait) {\n    $check = 1\n    $purgeStatus = \"Unknown\"\n\n    do {\n        if ($check -gt 1) {\n            Write-Output \"Waiting $($purge.pingAfterSeconds) seconds before checking purge status again\"\n            Start-Sleep -Seconds $purge.pingAfterSeconds\n        }\n        $status = Get-PurgeStatus $purge.purgeId \n        $purgeStatus = $status.purgeStatus\n        $check = $check + 1\n        if ($check -gt $maxChecks) { \n            Write-Output \"Maximum number of checks reached\"\n        }\n    } while ($status.purgeStatus -ne \"Done\" -and $check -le $maxChecks)\n\n    if ($status.purgeStatus -ne \"Done\") {\n        Write-Error \"Purge status is not Done\"\n    }\n}",
    "Octopus.Action.Script.ScriptFileName": null,
    "Octopus.Action.Package.NuGetFeedId": null,
    "Octopus.Action.Package.NuGetPackageId": null
  },
  "Category": "Akamai",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/akamai-content-purge.json",
  "Website": "/step-templates/0bfe41dc-50b0-44a7-abe3-e6ba2a6d317a",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABhQTFRFseDvTrjc1+/3f8zlKqrU////AJnM7/n8E0I9ngAABR5JREFUeNrsndly6zAIQAFJ1v//8c16GydGK3IgA9O3tqOcsCNZhvQjAg7iIA7iIA7iIA7iIA7iIA7iIA7iIA7iIA7iIA5yIAQUnkI2QS4AGLedmAPJEN4QbhJsgdAhxFKMBSAcBYIpZ4djigjZUtTKYTtWBqX1IgeS8Rgj5JQMgdB3MaRAOKM6DUMIhPON8zBEQNhIxWdLoOePHhDWOY7zPX78ecQQ4PsgjFXF/Fk74rHmHv8wCzP377lRHVy6F0z+UyDAfCJ4M6gWitncCfIc+2BF2I4xo5YJkBazAty6JcKpIJx7vJaHNIAxijIKkmPVPfIgxpivgCwHVQNzq4RTQOocFLdJibQepM4RNgEJq0GqHDPeMVp1wgKOebPiKx1BkCoHbHLS7igglQdhBcc+DsqC1PJ5Iwdeq/c/QWQrGVoDwjWD7RyXgp37bHA0n2z0k04QKDtlhmpJeIHgP9n1N5fma4SkD4Qq6geZwjbvmzCUB6kUWFC0qL5RxGvdDNIgFQcB2S6DQo/Dg4Bh5fKvx5ul/y6HsiBlw8orWvHn5C9IglQMKzY18KMNXJYDyWXDWjc0hSaVQK+SmZQO7T0rwS2R7374JPlUSpYC4VJhybDwaEzHhwRee5eEH6RAyrViaPGO+oArchvYhCgEUp5hUUO11zgZGt2lAxGFYLUn6pg2bkPnCmBOIWwq3LsHdPaMA7EO1igEB2e/43vybSBUDFlU5DjAuMdc2WkjrFAIHk5Nbzs674P663kVkX3UJhAuqTMKwc+yBsv7OPTRTXWP6JpAykk9MDnyWSe1bkbRJwwIg5QHQGzcvQ2OkHos5H1/LoiCUDH2Aj83xZGMsE+cKAkSijshKFq1fwQ6lAPhRouH1YnQmayXYIdiIGXLCoyjz8pf2xCkxkGhI4mIHmh6TuuCEEjRsvLSw35BchxULk9gjWG9GZjIOAiK2RClI9Zhn9swNa2vXa6z4kjM7w/GKAASS3VWXubpb64C0yC5Oa1jWia52rLXQaBYY4fPAdeXBBojYD2LYNINgsXKF0/wkLUgH5Hgywqpg5QHjGoUUgUpB620MKkLg1ROyZ3zSMVCkPCuEdIOUp47kBrLmgQBNZYlBULpR0wrqQfBJo2gdZCkxkWEQEA9SG4Dyb+ikfQjIGgApBx+H5yYfwQk2NdI+BUQMANS3mB/1ChgAIQqx2PNgKTK8Xs0DrLtfMgESHkcdPchMgwCrxozoZFQAUErINTwdIIJkMqo8TbByyZAinuhd9tKJkBqTgJWQKB2xC1GGyC54iQpkw2QVH+a1QhIEDo++XUQqtmWFRAbtjVx8CyYAzFhWwKHMw2BVBp3OyCVp5LsgFhwd4lD/oZAqg2vGZCsXiUyjyYZAlHvJbOP7wVzINpzSbuRK88lHd6qu+LqefhHtb/3xE/V/t4DkjfFxtWV0TQbV19qVhy5OmuMqLZS6fwIetNi73dJ0zd6KQFR27/3W3fQSTLgpjpbk5HlVZIMra6RZGxxhSSDazdcLWsDRJ9OhlfWVtOPf4XKMuOELYCqamXGqKn3Cn+tIM3XxqsH4S/yJ2sg7N2+wRoIezfd2UpZ99qOM9+ikta+SOXUPC+0Vti+HYmlVmJf/QTGQK4o8ZsooqswNxjGM9xe+Oti31cHxkB4lhjIGEjKLAvYArnTHF6PB/ZAbpqB93dt2gR50Ly8/ZSSXRBzmd1BHMRBHMRBHMRBHMRBHMRBHMRBHMRBHMRBHORM+SfAAMOMGvrIfh0UAAAAAElFTkSuQmCC",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Monday, July 11, 2016