Skip to content

Instantly share code, notes, and snippets.

@jhochwald
Last active October 19, 2018 23:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jhochwald/9464db7e232b50be52db4027b9abefbc to your computer and use it in GitHub Desktop.
Save jhochwald/9464db7e232b50be52db4027b9abefbc to your computer and use it in GitHub Desktop.
Upload given file to BitBucket cloud service downloads section.
function Publish-BitbucketDownload
{
<#
.SYNOPSIS
Upload given file to BitBucket cloud service downloads section.
.DESCRIPTION
Upload given file to BitBucket cloud service downloads section.
I use this to upload build artifacts to the BitBucket Download section.
The code might not be perfect, and we still use the AUTH Header instead of OAuth yet,
but I needed a quick and dirty solution to get things going.
I might change a few things soon, but for now; this function is doing what it should.
.PARAMETER username
BitBucket cloud username, as plain text
.PARAMETER password
BitBucket cloud password, as plain text
.PARAMETER FilePath
File to upload, full path needed
.PARAMETER team
BitBucket cloud team aka username (Might not be the login username!!!)
.PARAMETER Project
BitBucket cloud project name
.EXAMPLE
PS ~> Publish-BitbucketDownload -username 'MyUsername' -password 'MySectretPassword' -FilePath 'Y:\dev\release\myproject-current.zip' -team 'dummyTeam' -Project 'myproject'
# Upload the artifact 'Y:\dev\release\myproject-current.zip' to the Download sections of the 'myproject' project of the 'dummyTeam', It uses User name and password (Both in plain ASC) to authenticate. However, both are converted to base64 to prevent any clear text header transfers.
.EXAMPLE
PS ~> Publish-BitbucketDownload -username 'MyUsername' -password 'MySectretPassword' -FilePath 'Y:\dev\release\myproject.nuget' -team 'dummyTeam' -Project 'myproject'
# Upload the artifact 'Y:\dev\release\myproject.nuget' to the Download sections of the 'myproject' project of the 'dummyTeam', It uses Username and password (Both in plain ASC) to authenticate. However, both are converted to base64 to prevent any clear text header transfers.
.NOTES
I created this because I did not have CURL installed on my build system.
With Curl this is an absolute no brainer:
curl -X POST "https://MyUsername:MySectretPassword@api.bitbucket.org/2.0/repositories/dummyTeam/myproject/downloads" --form files=@"/home/dev/release\myproject-current.zip"
INFO: Max. CPU: 16 % Max. Memory: 28.48 MB
TODO: Convert the request to use OAuth ASAP
#>
[CmdletBinding()]
param
(
[Parameter(Mandatory,
ValueFromPipeline,
HelpMessage = 'BitBucket cloud username, as plain text')]
[ValidateNotNullOrEmpty()]
[Alias('user')]
[string]
$username,
[Parameter(Mandatory,
ValueFromPipeline,
HelpMessage = 'BitBucket cloud password, as plain text')]
[ValidateNotNullOrEmpty()]
[Alias('pass')]
[string]
$password,
[Parameter(Mandatory,
ValueFromPipeline,
HelpMessage = 'File to upload, full path needed')]
[ValidateNotNullOrEmpty()]
[string]
$FilePath,
[Parameter(Mandatory,
ValueFromPipeline,
HelpMessage = 'BitBucket cloud team name')]
[ValidateNotNullOrEmpty()]
[string]
$team,
[Parameter(Mandatory,
ValueFromPipeline,
HelpMessage = 'BitBucket cloud project name')]
[ValidateNotNullOrEmpty()]
[Alias('ProjectName')]
[string]
$Project
)
process
{
# Build the URI for our request
$URI = 'https://api.bitbucket.org/2.0/repositories/' + $team + '/' + $Project + '/downloads'
# Create our authentication header
# TODO: Migrate to OAUTH
$pair = ($username + ':' + $password)
$encodedCreds = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))
$basicAuthValue = ('Basic {0}' -f $encodedCreds)
$Headers = @{
Authorization = $basicAuthValue
}
# Cleanup the plain text stuff
$pair = $null
$encodedCreds = $null
# The boundary is essential - Trust me, very essential
$boundary = [Guid]::NewGuid().ToString()
<#
This is the crappy part: Build a body for a multipart request with PowerShell
This is something that should be changed in PowerShell ASAP (I mean it is really crappy and really bad).
It is an absolute no brainer with Curl.
#>
$bodyStart = @"
--$boundary
Content-Disposition: form-data; name="token"
--$boundary
Content-Disposition: form-data; name="files"; filename="$(Split-Path -Leaf -Path $FilePath)"
Content-Type: application/octet-stream
"@
# Generate the end of the request body to finish it.
$bodyEnd = @"
--$boundary--
"@
# Now we create a temp file (Another crappy/bad thing)
$requestInFile = (Join-Path -Path $env:TEMP -ChildPath ([IO.Path]::GetRandomFileName()))
try
{
# Create a new object for the brand new temporary file
$fileStream = (New-Object -TypeName 'System.IO.FileStream' -ArgumentList ($requestInFile, [IO.FileMode]'Create', [IO.FileAccess]'Write'))
try
{
# The Body start
$bytes = [Text.Encoding]::UTF8.GetBytes($bodyStart)
$fileStream.Write($bytes, 0, $bytes.Length)
# The original File
$bytes = [IO.File]::ReadAllBytes($FilePath)
$fileStream.Write($bytes, 0, $bytes.Length)
# Append the end of the body part
$bytes = [Text.Encoding]::UTF8.GetBytes($bodyEnd)
$fileStream.Write($bytes, 0, $bytes.Length)
}
finally
{
# End the Stream to close the file
$fileStream.Close()
# Cleanup
$fileStream = $null
# PowerShell garbage collector
[GC]::Collect()
}
# Make it multipart, this is the magic part...
$contentType = 'multipart/form-data; boundary={0}' -f $boundary
<#
The request itself is simple and easy, also works fine with Invoke-WebRequest instead of Invoke-RestMethod
I use Microsoft.PowerShell.Utility\Invoke-RestMethod to make sure the build in (Windows PowerShell native) function is used.
If PowerShell Core is installed or any Module provides a tweaked version... Just in case!
#>
try
{
$null = (Microsoft.PowerShell.Utility\Invoke-RestMethod -Uri $URI -Method Post -InFile $requestInFile -ContentType $contentType -Headers $Headers -ErrorAction Stop -WarningAction SilentlyContinue)
}
catch
{
# Remove the temp file
$null = (Remove-Item -Path $requestInFile -Force -Confirm:$false)
# Cleanup
$contentType = $null
# PowerShell garbage collector
[GC]::Collect()
# For the Build logs (will not break the build)
Write-Warning -Message 'StatusCode:' $_.Exception.Response.StatusCode.value__
Write-Warning -Message 'StatusDescription:' $_.Exception.Response.StatusDescription
# Saved in the verbose logs for this build
Write-Verbose -Message $_
# Inform the build and terminate (Will break the build)
Write-Error -Message 'We were unable to upload your file to the BitBucket downloads section, please check the build logs for further information.' -ErrorAction Stop
}
}
finally
{
# Remove the temp file
$null = (Remove-Item -Path $requestInFile -Force -Confirm:$false)
# Cleanup
$contentType = $null
# PowerShell garbage collector
[GC]::Collect()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment