functions/github/Assert-GitHubTeamRepoAccess.ps1

function Assert-GitHubTeamRepoAccess
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true, ParameterSetName="ByName")]
        [string] $Org,

        [Parameter(Mandatory=$true, ValueFromPipeline=$true, ParameterSetName="ByObject")]
        $Team,

        [Parameter(Mandatory=$true, ParameterSetName="ByName")]
        [string] $TeamName,

        [Parameter(Mandatory=$true)]
        [string[]] $Repositories,

        [Parameter(Mandatory=$true)]
        [ValidateSet("pull","push","admin","triage","maintain")]
        [string] $RepoAccess
    )

    if ($PSCmdlet.ParameterSetName -eq "ByObject") {
        $Org = $Team.organization.login
        $TeamName = $Team.slug
    }
    else {
        $Team = Invoke-GitHubRestMethod -Uri "https://api.github.com/orgs/$Org/teams/$TeamName"
        if (!$Team) {
            throw "Something went wrong processing the team repository access - the team '$TeamName' could not be found"
        }
    }

    # Setup results data structure
    $teamReposAccessResults = @{
        team_name = $TeamName
        repos_permission_added = @()
        repos_permission_removed = @()
        repos_permission_updated = @()
    }
    
    Write-Information "Processing team repository access for '$TeamName'"

    $existingRepositories = _getGitHubTeamRepos

    # Add the team to any repos it should have access to
    $reposToGrant = $Repositories | ? { $_ -notin $existingRepositories.name }
    foreach ($repoToGrant in $reposToGrant) {
        Write-Information "Granting team '$TeamName' '$RepoAccess' access to '$Org/$repoToGrant'"
        _addRepoPermissions $repoToGrant
        $teamReposAccessResults.repos_permission_added += "$Org/$repoToGrant"
    }

    # Remove any access the team should not have
    $reposToRevoke = $existingRepositories.name | ? { $_ -notin $Repositories }
    foreach ($repoToRevoke in $reposToRevoke) {
        Write-Information "Removing team '$TeamName' '$RepoAccess' access to '$Org/$repoToRevoke'"
        _removeRepoPermissions $repoToRevoke
        $teamReposAccessResults.repos_permission_removed += "$Org/$repoToRevoke"
    }

    # Handle where a team should have access, but currently has the wrong permissions
    $existingPermissionsProjection = @{}
    $existingRepositories | ? { $_.name -in $Repositories } |                                   # this filters out any repos the team doesn't have explicit access to
                            % { $existingPermissionsProjection.Add($_.name, ($_.permissions | ConvertTo-Json | ConvertFrom-Json -AsHashtable)) }    # extract a key/value pair "<repoName>=<currentPermission>" (convert the PSObject from the API into a Hashtable by roundtripping via JSON)
    $reposToUpdate = $existingPermissionsProjection.Keys | ? { `
                            $_ -and -not (_hasCorrectRepoPermissions $RepoAccess $existingPermissionsProjection[$_])        # use the helper to check whether the set of permission flags returned by the API equate to the required access
                        }
    foreach ($repoToUpdate in $reposToUpdate) {
        Write-Information "Updating team '$TeamName' '$RepoAccess' access to '$Org/$repoToUpdate'"
        _updateRepoPermissions $repoToUpdate
        $teamReposAccessResults.repos_permission_updated += "$Org/$repoToUpdate"
    }

    $teamReposAccessResults
}

#region Extracted functions to simplify mocking
function _getGitHubTeamRepos
{
    Invoke-GitHubRestMethod -Uri "https://api.github.com/orgs/$Org/teams/$TeamName/repos" -AllPages
}
function _addRepoPermissions
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $RepoName
    )
    _invokeUpdateTeamRepoPermissions @PSBoundParameters
}
function _removeRepoPermissions
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $RepoName
    )

    $resp = Invoke-GitHubRestMethod `
                        -Uri "https://api.github.com/orgs/$Org/teams/$TeamName/repos/$Org/$RepoName" `
                        -Verb "DELETE"
}
function _updateRepoPermissions
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $RepoName
    )

    _invokeUpdateTeamRepoPermissions @PSBoundParameters
}
function _invokeUpdateTeamRepoPermissions
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $RepoName
    )

    # $permissions = _getRepoPermissionsApiObject $RepoAccess
    $resp = Invoke-GitHubRestMethod -Uri "https://api.github.com/orgs/$Org/teams/$TeamName/repos/$Org/$RepoName" `
                            -Verb "PUT" `
                            -Body @{
                                permission = $RepoAccess
                            }
}
#endregion

#region Helper functions
function _getRepoPermissionsApiObject
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [ValidateSet("pull","push","admin","triage","maintain")]
        [string] $RequiredPermission
    )

    #
    # NOTE: The seemingly boolean values are set similarly to a bitmask
    #
    # Pull = pull
    # Push = push, pull, triage
    # Admin = admin, push, pull, triage, maintain
    # Maintain = maintain, triage, push, pull
    # Triage = triage, pull

    # Set no permissions by default
    $permissionsObject = [ordered]@{
        pull = $false
        triage = $false
        push = $false
        maintain = $false
        admin = $false
    }

    # Use a switch statement that will process mulitple matches to
    # apply the required bitmask-esque combinations
    switch($RequiredPermission) {
        "admin" {
            $permissionsObject.admin = $true
        }
        {$_ -in "admin","maintain"} {
            $permissionsObject.maintain = $true
        }
        {$_ -in "admin","maintain","push"} {
            $permissionsObject.push = $true
        }
        {$_ -in "admin","maintain","push","triage"} {
            $permissionsObject.triage = $true
        }
    }

    # any permission will have 'pull'
    $permissionsObject.pull = $true

    return $permissionsObject
}
function _hasCorrectRepoPermissions
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string] $RequiredPermission,

        [Parameter(Mandatory=$true)]
        [hashtable] $PermissionsTable
    )

    # A given permission maps to a set of boolean flags for each possible permission that resembles a bitmask
    $expectedPermissions = _getRepoPermissionsApiObject -RequiredPermission $RequiredPermission

    $isCorrect = $true
    foreach ($perm in $PermissionsTable.Keys) {
        if ($PermissionsTable[$perm] -ne $expectedPermissions[$perm]) {
            $isCorrect = $false
        }
    }
    return $isCorrect
}
#endregion