Knowledge Base

Preserving for the future: Shell scripts, AoC, and more

Get email addresses for Bitbucket Cloud users

For some convoluted logic, the admins of a paid space in Bitbucket Cloud cannot programmatically retrieve user email addresses. This is ridiculous because one adds new users via email address! If your organization also uses Jira Cloud, there is a way to mash the data together and retrieve some email addresses for Bitbucket users. The user uuids are not always the same across the products, but this will give your list a good head start.

# correlating Bitbucket accounts with email addresses
# References:
#    https://community.atlassian.com/t5/Bitbucket-questions/How-to-get-user-email-address-using-BitBucket-API/qaq-p/123783#M54771

# Bitbucket creds
$org = "example"
$username = "bgstack15@example.com"
$password = "ASTERISKS"
$CloudHeaders = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($username):$($password)")) }

# jira creds
$jiraCloudHeaders = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($username):$($password)")) }

# Get jira users
$jira0 = Invoke-RestMethod -Headers $jiraCloudHeaders -Uri "https://example.atlassian.net/rest/api/3/users/search?startAt=0&maxResults=1000"
$jira1 = Invoke-RestMethod -Headers $jiraCloudHeaders -Uri "https://example.atlassian.net/rest/api/3/users/search?startAt=1000&maxResults=1000"
$jira2 = Invoke-RestMethod -Headers $jiraCloudHeaders -Uri "https://example.atlassian.net/rest/api/3/users/search?startAt=2000&maxResults=1000"
$jira3 = Invoke-RestMethod -Headers $jiraCloudHeaders -Uri "https://example.atlassian.net/rest/api/3/users/search?startAt=3000&maxResults=1000"
$jira = $jira0 + $jira1 +$jira2 + $jira3
$jiranew = $jira | Group-Object accountId -AsHashTable

# Get bitbucket users
$bitbucket = Iterate-Values-From-BB-API -StartUri "https://api.bitbucket.org/2.0/workspaces/${org}/members" -AuthHeaders $CloudHeaders -SkipCount $True

# Combine them.
$bitbucket_expanded = $bitbucket | select-object -ExpandProperty user | Select-Object display_name, nickname, account_id
$bitbucket_expanded | Select-Object display_name, @{n='email';e={$jiranew[$_.account_id][0].emailAddress }} | Export-Csv -NoTypeInformation "bitbucket-emails.csv"

So you probably noticed the Iterate-Value-From-BB-API function. I wrote this myself, because I use it in a soon-to-be-published Powershell library for interacting with Bitbucket Cloud and Server instances.

Function _Iterate-Values-From-BB-API {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $False)][Hashtable]$AuthHeaders = ${global:CloudHeaders},
        [Parameter(Mandatory = $True )][string]$StartUri,
        [Parameter(Mandatory = $False)][string]$Location = "cloud", # "cloud" or "local" which sets page defaults
        [Parameter(Mandatory = $False)][string]$Descriptor = "item",
        [Parameter(Mandatory = $False)][boolean]$SkipCount = $False
    )
    # StartUri could be any of these, for example:
    # https://api.bitbucket.org/2.0/repositories/${org}
    # https://api.bitbucket.org/2.0/workspaces/${org}/projects
    # https://git.example.com/rest/api/1.0/repos
    # https://git.example.com/rest/api/1.0/projects
    # https://api.bitbucket.org/2.0/teams/${org}/permissions/repositories?q=permission%3D%22write%22
    Begin {
        $done = $False
        $output = @()
        $size = -1
        $count = 0
        $Baseuri = $StartUri
        $keepcounting = $false

        # All this just to get object count for the local Bitbucket, because its API does not list total object count on each return page.
        if (!($SkipCount)) {
            if ($Location -like "*local*" ) {
                $pageSize = 250
                $CountUri = "$($Baseuri)?limit=$($pageSize)"
                $localcount = 0
                While (!$done) {
                    Write-Progress -Activity "Counting objects" -Status "$($localcount)"
                    $interim = Invoke-RestMethod $CountUri -Method "GET" -Headers $AuthHeaders
                    if ( !($interim.isLastPage) ) {
                        $localcount += $pageSize
                        $CountUri = "$($Baseuri)?limit=$($pageSize)&start=$($localcount)"
                    } else {
                        $done = $True
                        $localcount += $interim.size
                    }
                }
                $done = $False
                $size = $localcount
            } Elseif ($Location -like "*cloud*" ) {
                $CountUri = "$($Baseuri)"
                $cloudcount = 0
                While (!$done) {
                    Write-Progress -Activity "Counting $($Descriptor)s" -Status "$($cloudcount)"
                    $interim = Invoke-RestMethod $CountUri -Method "GET" -Headers $AuthHeaders
                    # short circuit if size is provided
                    If ($interim.size -ne $null) {
                        $cloudcount = $interim.size
                        $done = $True
                    } Elseif ($interim.next -eq $null) {
                        $cloudcount += $interim.pagelen
                        $done = $True
                    } Else {
                        $cloudcount += $interim.pagelen
                        $Counturi = $interim.next
                    }
                }
                $done = $False
                $size = $cloudcount
            }
        } Else {
            # skip the count!
            $size = 10
        }
    }

    Process {
        Write-Verbose "Will look for $($size) $($descriptor)s"
        $Uri = $StartUri
        While (!$done) {
            $interim = Invoke-RestMethod -Uri $Uri -Method "GET" -Headers $AuthHeaders
            if (!($SkipCount) -And $size -eq -1) { # only run once because it will always be the same
                if ($interim.size -ne $null) { $size = $interim.size }
                    Else { $keepcounting = $True; $size += $interim.values.count }
            }
            if ($keepcounting) { $size += $interim.values.count }
            $interim.values | % {
                $output += $_ ;
                $count += 1 ;
                [int]$percent = ($count/$size)*100
                $percent = (@($percent, 100) | Measure -Minimum).Minimum
                $percent = (@($percent, 0) | Measure -Maximum).Maximum
                Write-Progress -Activity "Listing $($descriptor)" -Status "$count/$size" -PercentComplete $percent
            }
            # Bitbucket Cloud uses property "next" but on-prem server uses "nextPageStart"
            If ( $interim.next -ne $Null ) {
                $Uri = $interim.next
            } Elseif ( $interim.nextPageStart -ne $Null -And (!($interim.isLastPage)) ) {
                $Uri = "$($Baseuri)?start=$($interim.nextPageStart)"
            } else { $done = $True }
        }
    }

    End { $output }
}

References

Web links

Ripped off and enhanced from a user in the Atlassian community: How to get user email address using BitBucket API?

Comments