Xamarin Insights dSYM Upload

Octopus.Script exported 2016-01-12 by Colin.Dabritz@Viewpoint.com belongs to ‘Xamarin’ category.

Uploads a dSYM symbols file to Xamarin Insights, for more readable exceptions.

Parameters

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

Insights Api Token

InsightsAppSpecificApiToken

Your Xamarin API Key for the specific application you are uploading the symbol files to.

Visit: https://insights.xamarin.com

Log in, browse your application, and click Settings. Your application specific API Token should be visible under “API Key”.

Package Step Name

PackageStepName

Name of the previously-deployed package step that contains the dSYM symbol file that you want to deploy.

Nuget Search Path (Optional)

NugetSearchPath

This fully optional search path allows you to look in a specific folder path in your nuget file, such as “bin\release”. This may be needed in cases when the nuget file has multiple dSYM files in different locations.

Script body

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

#####################################
# Xamarin Insights dSYM Upload script
#
# Uploads a dSYM sybmols file to Xamarin insights from a Nuget file
#  extracted in a previous Octopus Deploy step. Allows a variety of parameters.
#
# Uploads to configured application, by API Key.
#
# API Documentation is available at: https://developer.xamarin.com/guides/insights/user-interface/settings/#Uploading_a_dSYM_File
#
# The API key involved is provided in the "Settings" for the particular app on the Xamarin Insights portal.
# https://insights.xamarin.com/
# Log in, open the application, and click settings. The general "settings" tab has the API Key field.
#
# Example curl request:
# curl -F "dsym=@YOUR-APPS-DSYM.zip;type=application/zip" https://xaapi.xamarin.com/api/dsym?apikey=13dd6c82159361ea13ad25a0d9100eb6e228bb17
#
# v0.1 - Initial version, uploads one dSYM file.
# 
# The nuget package must contain the *.app.dSYM.zip file.  
#
# The following *.nuspec example will package a release IPA and associated *.app.dSYM.zip file.
#
# The upload script requires a search path (default package root) with exactly one *.app.dSYM.zip file.
# 
# Specify package path relative to the nuspec file location
#
# https://docs.nuget.org/create/nuspec-reference#file-element-examples
#
# In some cases the ID, Version, and Description may need manually specified.
#

<#

    <?xml version="1.0"?>
    <package>
      <metadata>
        <id>$id$</id>
        <title>$id$</title>
        <version>$version$</version>
        <description>Mobile project packaged for Octopus deploy. $description$</description>
      </metadata>
      <files>
        <!-- Matches mobile package files. Note this will only include the platform being built,
	         and should match only a single file. -->
        
        <!-- iOS -->
        <file src="**/Release/**/*.ipa" target="" />

        <!-- Include release dSYM symbols file -->
        <file src="**/Release/*.app.dSYM.zip" target="" />

      </files>
    </package>

#>

#############################
# Debug Parameter Overrides #
#############################

# These values are set explicitly durring debugging so that the script can
#   be run in the editor.
# For local debugging, uncomment these values and fill in appropriately.

<#

# debug folder with app files
$stepPath = "C:\Temp\powershellscript\"

$OctopusParameters = @{
"InsightsAppSpecificApiToken" = "YourApiKeyhere";
# "NugetSearchPath" = "bin\iPhone"; # Additional path information, reatlive to the nuget file root, e.g. release
}

# #>

###################################
# Octopus Deploy common functions #
###################################

# A collection of functions that can be used by script steps to determine where packages installed
# by previous steps are located on the filesystem.
 
function Find-InstallLocations {
    $result = @()
    $OctopusParameters.Keys | foreach {
        if ($_.EndsWith('].Output.Package.InstallationDirectoryPath')) {
            $result += $OctopusParameters[$_]
        }
    }
    return $result
}
 
function Find-InstallLocation($stepName) {
    $result = $OctopusParameters.Keys | where {
        $_.Equals("Octopus.Action[$stepName].Output.Package.InstallationDirectoryPath",  [System.StringComparison]::OrdinalIgnoreCase)
    } | select -first 1
 
    if ($result) {
        return $OctopusParameters[$result]
    }
 
    throw "No install location found for step: $stepName"
}

function Find-SingleInstallLocation {
    $all = @(Find-InstallLocations)
    if ($all.Length -eq 1) {
        return $all[0]
    }
    if ($all.Length -eq 0) {
        throw "No package steps found"
    }
    throw "Multiple package steps have run; please specify a single step"
}

#####################
# Utility functions #
#####################

function Get-ExactlyOneDsymFileInfo($searchPath)
{
    $symbolFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.app.dSYM.zip
    
    $fileCount = $symbolFiles.count

    if($fileCount -ne 1)
    {
        throw "Did not find exactly one (1) symbols file. Found $fileCount dSYM file(s). Searched under path: $searchPath"
    }

    return $symbolFiles
}

####################
# Basic Parameters #
####################

$apiToken = $OctopusParameters['InsightsAppSpecificApiToken']

$octopusFilePathOverride = $OctopusParameters['NugetSearchPath']

$stepName = $OctopusParameters['MobileAppPackageStepName']

# set step path, if not already set
If([string]::IsNullOrEmpty($stepPath))
{
    if (![string]::IsNullOrEmpty($stepName)) {
        Write-Host "Finding path to package step: $stepName"
        $stepPath = Find-InstallLocation $stepName
    } else {
        $stepPath = Find-SingleInstallLocation
    }
}

Write-Host "##octopus[stderr-progress]"

# if we were not provided a file name, search for a single package file
if([string]::IsNullOrWhiteSpace($octopusFilePathOverride))
{
    $appFileInfo = Get-ExactlyOneDsymFileInfo $stepPath
    $appFullFilePath = $appFileInfo.FullName
}
else
{
    $searchPathOverride = Join-Path $stepPath $octopusFilePathOverride
    $appFileInfo = Get-ExactlyOneDsymFileInfo $searchPathOverride
    $appFullFilePath = $appFileInfo.FullName
}

$fileName = [System.IO.Path]::GetFileName($appFullFilePath)

$apiUploadUri = "https://xaapi.xamarin.com/api/dsym?apikey=$apiToken"

# Request token details
$uniqueBoundaryToken = [Guid]::NewGuid().ToString()

$contentType = "multipart/form-data; boundary=$uniqueBoundaryToken"


Write-Host "File Location: $appFullFilePath"

################################
# Set up Hockey App parameters #
################################

$formSectionSeparator = @"

--$uniqueBoundaryToken

"@

############################
# Prepare request wrappers #
############################

# Standard for multipart form data
# http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4

$stringEncoding = [System.Text.Encoding]::ASCII

# Note the hard-coded "ipa" name here is per HockeyApp API documentation
#  and it applies to ALL platform application files.

$preFileBytes = $stringEncoding.GetBytes(
$formSectionSeparator +
@"
Content-Disposition: form-data; name="dsym"; filename="$fileName"
Content-Type: application/zip


"@)

# file bytes will go in between

$postFileBytes = $stringEncoding.GetBytes(@"

--$uniqueBoundaryToken--
"@)

######################
# Invoke the request #
######################

# Note, previous approach was Invoke-RestMethod based. It worked, but was NOT memory
# efficient, leading to high memory usage and "out of memory" errors.

# Based on examples from
# http://stackoverflow.com/questions/566462/upload-files-with-httpwebrequest-multipart-form-data
# and 
# https://gist.github.com/nolim1t/271018

# Uses a dot net WebRequest and streaming to limit memory usage

$WebRequest = [System.Net.WebRequest]::Create("$apiUploadUri")

$WebRequest.ContentType = $contentType
$WebRequest.Method = "POST"
$WebRequest.KeepAlive = $true;

$RequestStream = $WebRequest.GetRequestStream()

# before file bytes
$RequestStream.Write($preFileBytes, 0, $preFileBytes.Length);

#files bytes

$fileMode = [System.IO.FileMode]::Open
$fileAccess = [System.IO.FileAccess]::Read

$fileStream = New-Object IO.FileStream $appFullFilePath,$fileMode,$fileAccess
$bufferSize = 4096 # 4k at a time
$byteBuffer = New-Object Byte[] ($bufferSize)

# read bytes. While bytes are read...
while(($bytesRead = $fileStream.Read($byteBuffer,0,$byteBuffer.Length)) -ne 0)
{
    # write those byes to the request stream
    $RequestStream.Write($byteBuffer, 0, $bytesRead)
}

$fileStream.Close()

# after file bytes
$RequestStream.Write($postFileBytes, 0, $postFileBytes.Length);

$RequestStream.Close()

$response = $WebRequest.GetResponse();

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": "a1c51946-abd0-434c-99f3-b7a1b5af74c5",
  "Name": "Xamarin Insights dSYM Upload",
  "Description": "Uploads a dSYM symbols file to Xamarin Insights, for more readable exceptions.",
  "Version": 0,
  "ExportedAt": "2016-01-12T23:40:54.635+00:00",
  "ActionType": "Octopus.Script",
  "Author": "Colin.Dabritz@Viewpoint.com",
  "Parameters": [
    {
      "Name": "InsightsAppSpecificApiToken",
      "Label": "Insights Api Token",
      "HelpText": "Your Xamarin API Key for the specific application you are uploading the symbol files to.\n\nVisit:\nhttps://insights.xamarin.com\n\nLog in, browse your application, and click Settings. Your application specific API Token should be visible under \"API Key\".",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Name": "PackageStepName",
      "Label": "Package Step Name",
      "HelpText": "Name of the previously-deployed package step that contains the dSYM symbol file that you want to deploy.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "StepName"
      }
    },
    {
      "Name": "NugetSearchPath",
      "Label": "Nuget Search Path (Optional)",
      "HelpText": "This fully optional search path allows you to look in a specific folder path in your nuget file, such as \"bin\\release\". This may be needed in cases when the nuget file has multiple dSYM files in different locations.",
      "DefaultValue": null,
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptBody": "#####################################\n# Xamarin Insights dSYM Upload script\n#\n# Uploads a dSYM sybmols file to Xamarin insights from a Nuget file\n#  extracted in a previous Octopus Deploy step. Allows a variety of parameters.\n#\n# Uploads to configured application, by API Key.\n#\n# API Documentation is available at: https://developer.xamarin.com/guides/insights/user-interface/settings/#Uploading_a_dSYM_File\n#\n# The API key involved is provided in the \"Settings\" for the particular app on the Xamarin Insights portal.\n# https://insights.xamarin.com/\n# Log in, open the application, and click settings. The general \"settings\" tab has the API Key field.\n#\n# Example curl request:\n# curl -F \"dsym=@YOUR-APPS-DSYM.zip;type=application/zip\" https://xaapi.xamarin.com/api/dsym?apikey=13dd6c82159361ea13ad25a0d9100eb6e228bb17\n#\n# v0.1 - Initial version, uploads one dSYM file.\n# \n# The nuget package must contain the *.app.dSYM.zip file.  \n#\n# The following *.nuspec example will package a release IPA and associated *.app.dSYM.zip file.\n#\n# The upload script requires a search path (default package root) with exactly one *.app.dSYM.zip file.\n# \n# Specify package path relative to the nuspec file location\n#\n# https://docs.nuget.org/create/nuspec-reference#file-element-examples\n#\n# In some cases the ID, Version, and Description may need manually specified.\n#\n\n<#\n\n    <?xml version=\"1.0\"?>\n    <package>\n      <metadata>\n        <id>$id$</id>\n        <title>$id$</title>\n        <version>$version$</version>\n        <description>Mobile project packaged for Octopus deploy. $description$</description>\n      </metadata>\n      <files>\n        <!-- Matches mobile package files. Note this will only include the platform being built,\n\t         and should match only a single file. -->\n        \n        <!-- iOS -->\n        <file src=\"**/Release/**/*.ipa\" target=\"\" />\n\n        <!-- Include release dSYM symbols file -->\n        <file src=\"**/Release/*.app.dSYM.zip\" target=\"\" />\n\n      </files>\n    </package>\n\n#>\n\n#############################\n# Debug Parameter Overrides #\n#############################\n\n# These values are set explicitly durring debugging so that the script can\n#   be run in the editor.\n# For local debugging, uncomment these values and fill in appropriately.\n\n<#\n\n# debug folder with app files\n$stepPath = \"C:\\Temp\\powershellscript\\\"\n\n$OctopusParameters = @{\n\"InsightsAppSpecificApiToken\" = \"YourApiKeyhere\";\n# \"NugetSearchPath\" = \"bin\\iPhone\"; # Additional path information, reatlive to the nuget file root, e.g. release\n}\n\n# #>\n\n###################################\n# Octopus Deploy common functions #\n###################################\n\n# A collection of functions that can be used by script steps to determine where packages installed\n# by previous steps are located on the filesystem.\n \nfunction Find-InstallLocations {\n    $result = @()\n    $OctopusParameters.Keys | foreach {\n        if ($_.EndsWith('].Output.Package.InstallationDirectoryPath')) {\n            $result += $OctopusParameters[$_]\n        }\n    }\n    return $result\n}\n \nfunction Find-InstallLocation($stepName) {\n    $result = $OctopusParameters.Keys | where {\n        $_.Equals(\"Octopus.Action[$stepName].Output.Package.InstallationDirectoryPath\",  [System.StringComparison]::OrdinalIgnoreCase)\n    } | select -first 1\n \n    if ($result) {\n        return $OctopusParameters[$result]\n    }\n \n    throw \"No install location found for step: $stepName\"\n}\n\nfunction Find-SingleInstallLocation {\n    $all = @(Find-InstallLocations)\n    if ($all.Length -eq 1) {\n        return $all[0]\n    }\n    if ($all.Length -eq 0) {\n        throw \"No package steps found\"\n    }\n    throw \"Multiple package steps have run; please specify a single step\"\n}\n\n#####################\n# Utility functions #\n#####################\n\nfunction Get-ExactlyOneDsymFileInfo($searchPath)\n{\n    $symbolFiles = Get-ChildItem -Path $searchPath -Recurse -Filter *.app.dSYM.zip\n    \n    $fileCount = $symbolFiles.count\n\n    if($fileCount -ne 1)\n    {\n        throw \"Did not find exactly one (1) symbols file. Found $fileCount dSYM file(s). Searched under path: $searchPath\"\n    }\n\n    return $symbolFiles\n}\n\n####################\n# Basic Parameters #\n####################\n\n$apiToken = $OctopusParameters['InsightsAppSpecificApiToken']\n\n$octopusFilePathOverride = $OctopusParameters['NugetSearchPath']\n\n$stepName = $OctopusParameters['MobileAppPackageStepName']\n\n# set step path, if not already set\nIf([string]::IsNullOrEmpty($stepPath))\n{\n    if (![string]::IsNullOrEmpty($stepName)) {\n        Write-Host \"Finding path to package step: $stepName\"\n        $stepPath = Find-InstallLocation $stepName\n    } else {\n        $stepPath = Find-SingleInstallLocation\n    }\n}\n\nWrite-Host \"##octopus[stderr-progress]\"\n\n# if we were not provided a file name, search for a single package file\nif([string]::IsNullOrWhiteSpace($octopusFilePathOverride))\n{\n    $appFileInfo = Get-ExactlyOneDsymFileInfo $stepPath\n    $appFullFilePath = $appFileInfo.FullName\n}\nelse\n{\n    $searchPathOverride = Join-Path $stepPath $octopusFilePathOverride\n    $appFileInfo = Get-ExactlyOneDsymFileInfo $searchPathOverride\n    $appFullFilePath = $appFileInfo.FullName\n}\n\n$fileName = [System.IO.Path]::GetFileName($appFullFilePath)\n\n$apiUploadUri = \"https://xaapi.xamarin.com/api/dsym?apikey=$apiToken\"\n\n# Request token details\n$uniqueBoundaryToken = [Guid]::NewGuid().ToString()\n\n$contentType = \"multipart/form-data; boundary=$uniqueBoundaryToken\"\n\n\nWrite-Host \"File Location: $appFullFilePath\"\n\n################################\n# Set up Hockey App parameters #\n################################\n\n$formSectionSeparator = @\"\n\n--$uniqueBoundaryToken\n\n\"@\n\n############################\n# Prepare request wrappers #\n############################\n\n# Standard for multipart form data\n# http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4\n\n$stringEncoding = [System.Text.Encoding]::ASCII\n\n# Note the hard-coded \"ipa\" name here is per HockeyApp API documentation\n#  and it applies to ALL platform application files.\n\n$preFileBytes = $stringEncoding.GetBytes(\n$formSectionSeparator +\n@\"\nContent-Disposition: form-data; name=\"dsym\"; filename=\"$fileName\"\nContent-Type: application/zip\n\n\n\"@)\n\n# file bytes will go in between\n\n$postFileBytes = $stringEncoding.GetBytes(@\"\n\n--$uniqueBoundaryToken--\n\"@)\n\n######################\n# Invoke the request #\n######################\n\n# Note, previous approach was Invoke-RestMethod based. It worked, but was NOT memory\n# efficient, leading to high memory usage and \"out of memory\" errors.\n\n# Based on examples from\n# http://stackoverflow.com/questions/566462/upload-files-with-httpwebrequest-multipart-form-data\n# and \n# https://gist.github.com/nolim1t/271018\n\n# Uses a dot net WebRequest and streaming to limit memory usage\n\n$WebRequest = [System.Net.WebRequest]::Create(\"$apiUploadUri\")\n\n$WebRequest.ContentType = $contentType\n$WebRequest.Method = \"POST\"\n$WebRequest.KeepAlive = $true;\n\n$RequestStream = $WebRequest.GetRequestStream()\n\n# before file bytes\n$RequestStream.Write($preFileBytes, 0, $preFileBytes.Length);\n\n#files bytes\n\n$fileMode = [System.IO.FileMode]::Open\n$fileAccess = [System.IO.FileAccess]::Read\n\n$fileStream = New-Object IO.FileStream $appFullFilePath,$fileMode,$fileAccess\n$bufferSize = 4096 # 4k at a time\n$byteBuffer = New-Object Byte[] ($bufferSize)\n\n# read bytes. While bytes are read...\nwhile(($bytesRead = $fileStream.Read($byteBuffer,0,$byteBuffer.Length)) -ne 0)\n{\n    # write those byes to the request stream\n    $RequestStream.Write($byteBuffer, 0, $bytesRead)\n}\n\n$fileStream.Close()\n\n# after file bytes\n$RequestStream.Write($postFileBytes, 0, $postFileBytes.Length);\n\n$RequestStream.Close()\n\n$response = $WebRequest.GetResponse();\n",
    "Octopus.Action.Script.Syntax": "PowerShell"
  },
  "Category": "Xamarin",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/xamarin-Insights-upload-dysm.json",
  "Website": "/step-templates/a1c51946-abd0-434c-99f3-b7a1b5af74c5",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRF////NJjbzOX2QZ7d8vn9TaXgZ7Lkmcvt5vL6gL/ps9jx2ez4dLjmptLvjcXrv9/0WqviJo8DRgAAA/pJREFUeNrsnd2S5CAIhcekjfk1ef+n3anau96kA4jYYc+5nJqaGtR8ICj+/EAQBEEQBEEQBEEQBEEQBEEQBBnp1Sno1dqKMcegopjHlpORg6KmZtOyxKCquDSyow/K6ptY8lK349eSFqtrDRW02tvRhSrqzA2JdQyJ1nZsoZK253/pTb73HKopm0YmoaIsY5WhpiHD49Frj+BY1xAzBKdQWenp6DVG8BSqazKJ3oOBlqej1xDBezDR/nT0miE4BSNVRvDYWxnSj09HrwmCu2Co7unoNUDwHEw1Vwuyoq0h8fV09FZGsB16KyN4DeZaH4/emgg+aDm1WUCk6+TSYYrene9rBmpErY7gT/vbgb8ER6rt6rveRF/JhHhsopuujOCRsXm4x/TbMEfDxOPKCSYSz9HNhgjuWEN8ly+KrN9WRfChOsbM+VPc9W7cYCLS0UtI7G8W6BVwiM04NQRnfjAxUD9ektfJFuhlL5iRHwcoIZi4v91pKyZL0n2DBXrP4XL1XbF8oS6CyfvbjULVTVbkjhbo5Qx2ZONQC8Gcqk6+94q7tMhdjGBWavEWSIMAhzqJR15V59ZFdAVJgMUCvcR/dBLhUAPB3KrOzdIZSwapqPbDTi1+DGxZIbIqgvmpxU94FfpChV2vpKCerp3QXDhIcgRLqjrXwx6LB0mKYFlq8ZJMXfEgSUMuYVXnAk0FvrAQwdKqznDuUReV0pcg8Siv6pyuoUlh1cpqP/KqznGSrntjwCH+46m+L/zI2aSyaiVesaQa8paAi2c/MauYFNUL/xn/udbfrlxne0vXHYdiLXK1cSKnXnHfFXyh1JW4MURzacWWS8vNx66I3/6f+oklft04RO0QZWwWorgJGpXC+K55GO9nYyWc/8v6hzjtW15d8JJ8UEgHJXb9pE5G3kuCrjhlmr8lZeonia1cVlhLqFhW2V0KqHJb6OFRsfCST0Hp7fim0hsLwYSPWbwxKT+PIi1Pv76sPK19YCA1OzAgPMIxft8RDiqCiUQSbUx0rpJIjjkt7DCfgcO6B8/IG6eB76i0mg64OQqofDiTna5TbMzBPC67sdNFtdFLhQvvSHLf7gCzmyPlNwg+mLhmpeuU71kxrl2sqozTvvmmfBGG7HX02z/M1AkRXE3qlFOLUgQnQUA709Zthctifq7vublQ6eeKq5tLx36ugbu5mO+nVYKf5hVu2on4afDipuWOnyZIftpSuWkU5qd1m5tmen7aG7ppOOmnBaifpqxu2uT6aVzsppW0n+bebtqt+2mA7+dJAjePRPh5tsPNQyp+nrb5XV2qW5Pc8g0oJ88//Z0WFw9yQRAEQRAEQRAEQRAEQRAEQRD0/+iPAAMARZVI2Ng2lE8AAAAASUVORK5CYII=",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Tuesday, January 12, 2016