Akamai - Content Fast Purge

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

Allows to purge content using the Content Control Utility (CCU) v3 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.

Hostname

AkamaiHostname

The hostname for which to execute the purge operation

Objects

AkamaiObjects

A comma separated list of objects to purge, only URLs

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

Script body

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

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

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-akamaiopen2purgeccuv3production.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 Request-Purge {
    param ($objects,[string]$hostname,[string]$action="remove",[string]$domain="production")

    $body = @{
        objects = $objects
    }

    if ($hostname -ne $null -and $hostname -ne "") {
        $body = @{
            hostname = $hostname
            objects = $objects
        }
    } 

    Perform-AkamaiRequest "/ccu/v3/$action/$domain" "Post" 201 $body
}

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

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

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": "f8e2e47b-62ba-44a0-881c-d1911dc14428",
  "Name": "Akamai - Content Fast Purge",
  "Description": "Allows to purge content using the Content Control Utility (CCU) v3 REST API.",
  "Version": 1,
  "ExportedAt": "2016-07-11T14:30:28.569+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": "AkamaiHostname",
      "Label": "Hostname",
      "HelpText": "The hostname for which to execute the purge operation",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "AkamaiObjects",
      "Label": "Objects",
      "HelpText": "A comma separated list of objects to purge, only URLs",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "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"
      }
    }
  ],
  "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$hostname = $OctopusParameters['AkamaiHostname']\n$objects = $OctopusParameters['AkamaiObjects'] -split \",\"\n$action = $OctopusParameters['AkamaiAction']\n$domain = $OctopusParameters['AkamaiDomain']\n$proxyUser = $OctopusParameters['ProxyUser']\n$proxyPassword = $OctopusParameters['ProxyPassword']\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-akamaiopen2purgeccuv3production.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 Request-Purge {\n    param ($objects,[string]$hostname,[string]$action=\"remove\",[string]$domain=\"production\")\n\n    $body = @{\n        objects = $objects\n    }\n\n    if ($hostname -ne $null -and $hostname -ne \"\") {\n        $body = @{\n            hostname = $hostname\n            objects = $objects\n        }\n    } \n\n    Perform-AkamaiRequest \"/ccu/v3/$action/$domain\" \"Post\" 201 $body\n}\n\n$purge = Request-Purge $objects $hostname $action $domain\n\nWrite-Output \"Purge request created\"\nWrite-Output \"PurgeId: $($purge.purgeId)\"\nWrite-Output \"SupportId: $($purge.supportId)\" ",
    "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-fast-content-purge.json",
  "Website": "/step-templates/f8e2e47b-62ba-44a0-881c-d1911dc14428",
  "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