diff --git a/Coverage.md b/Coverage.md
index 984361d8a..5dc35128d 100644
--- a/Coverage.md
+++ b/Coverage.md
@@ -5,7 +5,7 @@
| Available functions |
- 985 |
+ 980 |
| Covered functions |
@@ -13,11 +13,11 @@
| Missing functions |
- 830 |
+ 825 |
| Coverage |
- 15.74% |
+ 15.82% |
@@ -52,13 +52,8 @@
| `/codes_of_conduct` | | :x: | | | |
| `/codes_of_conduct/{key}` | | :x: | | | |
| `/emojis` | | :white_check_mark: | | | |
-| `/enterprises/{enterprise}/copilot/billing/seats` | | :x: | | | |
-| `/enterprises/{enterprise}/copilot/metrics` | | :x: | | | |
-| `/enterprises/{enterprise}/copilot/usage` | | :x: | | | |
| `/enterprises/{enterprise}/dependabot/alerts` | | :x: | | | |
| `/enterprises/{enterprise}/secret-scanning/alerts` | | :x: | | | |
-| `/enterprises/{enterprise}/team/{team_slug}/copilot/metrics` | | :x: | | | |
-| `/enterprises/{enterprise}/team/{team_slug}/copilot/usage` | | :x: | | | |
| `/events` | | :x: | | | |
| `/feeds` | | :x: | | | |
| `/gists` | | :x: | | :x: | |
diff --git a/src/classes/public/Context/GitHubContext/AppGitHubContext.ps1 b/src/classes/public/Context/GitHubContext/AppGitHubContext.ps1
index 3bd58725f..68a010afd 100644
--- a/src/classes/public/Context/GitHubContext/AppGitHubContext.ps1
+++ b/src/classes/public/Context/GitHubContext/AppGitHubContext.ps1
@@ -9,11 +9,14 @@
[string] $OwnerType
# The permissions that the app is requesting on the target
- [string[]] $Permissions
+ [pscustomobject] $Permissions
# The events that the app is subscribing to once installed
[string[]] $Events
+ # Simple parameterless constructor
+ AppGitHubContext() {}
+
# Creates a context object from a hashtable of key-vaule pairs.
AppGitHubContext([hashtable]$Properties) {
foreach ($Property in $Properties.Keys) {
diff --git a/src/classes/public/Context/GitHubContext/InstallationGitHubContext.ps1 b/src/classes/public/Context/GitHubContext/InstallationGitHubContext.ps1
index d4a97368c..ed02de33c 100644
--- a/src/classes/public/Context/GitHubContext/InstallationGitHubContext.ps1
+++ b/src/classes/public/Context/GitHubContext/InstallationGitHubContext.ps1
@@ -10,7 +10,7 @@
[int] $InstallationID
# The permissions that the app is requesting on the target
- [string[]] $Permissions
+ [pscustomobject] $Permissions
# The events that the app is subscribing to once installed
[string[]] $Events
@@ -21,6 +21,9 @@
# The target login of the installation.
[string] $TargetName
+ # Simple parameterless constructor
+ InstallationGitHubContext() {}
+
# Creates a context object from a hashtable of key-vaule pairs.
InstallationGitHubContext([hashtable]$Properties) {
foreach ($Property in $Properties.Keys) {
diff --git a/src/classes/public/Context/GitHubContext/UserGitHubContext.ps1 b/src/classes/public/Context/GitHubContext/UserGitHubContext.ps1
index d0fa35ebf..345aaf867 100644
--- a/src/classes/public/Context/GitHubContext/UserGitHubContext.ps1
+++ b/src/classes/public/Context/GitHubContext/UserGitHubContext.ps1
@@ -22,6 +22,9 @@
# 2024-01-01-00:00:00
[datetime] $RefreshTokenExpirationDate
+ # Simple parameterless constructor
+ UserGitHubContext() {}
+
# Creates a context object from a hashtable of key-vaule pairs.
UserGitHubContext([hashtable]$Properties) {
foreach ($Property in $Properties.Keys) {
diff --git a/src/functions/public/Auth/Connect-GitHubAccount.ps1 b/src/functions/public/Auth/Connect-GitHubAccount.ps1
index ffac8c5ac..725b6338e 100644
--- a/src/functions/public/Auth/Connect-GitHubAccount.ps1
+++ b/src/functions/public/Auth/Connect-GitHubAccount.ps1
@@ -103,17 +103,11 @@
)]
[string] $PrivateKey,
- # Skip loading GitHub App contexts.
+ # Automatically load installations for the GitHub App.
[Parameter(
ParameterSetName = 'App'
)]
- [switch] $SkipAppAutoload,
-
- # Do not load credentials for the GitHub App Installations, just metadata.
- [Parameter(
- ParameterSetName = 'App'
- )]
- [switch] $Shallow,
+ [switch] $AutoloadInstallations,
# The default enterprise to use in commands.
[Parameter()]
@@ -181,19 +175,6 @@
if (-not $customTokenProvided -and $gitHubTokenPresent) {
$authType = 'Token'
$Token = $gitHubToken
- $gitHubEvent = Get-Content -Path $env:GITHUB_EVENT_PATH -Raw | ConvertFrom-Json
- 'Enterprise: ' + $gitHubEvent.enterprise.slug
- 'Organization: ' + $gitHubEvent.organization.login
- 'Repository: ' + $gitHubEvent.repository.name
- 'Repository Owner: ' + $gitHubEvent.repository.owner.login
- 'Repository Owner Type: ' + $gitHubEvent.repository.owner.type
- 'Sender: ' + $gitHubEvent.sender.login
-
- $Enterprise = [string]$gitHubEvent.enterprise.slug
- $TargetType = [string]$gitHubEvent.repository.owner.type
- $TargetName = [string]$gitHubEvent.repository.owner.login
- $Owner = [string]$gitHubEvent.repository.owner.login
- $Repo = [string]$gitHubEvent.repository.name
}
}
@@ -299,10 +280,8 @@
'ghs' {
Write-Verbose 'Logging in using an installation access token...'
$context += @{
- Token = ConvertTo-SecureString -AsPlainText $Token
- TokenType = $tokenType
- TargetType = $TargetType
- TargetName = $TargetName
+ Token = ConvertTo-SecureString -AsPlainText $Token
+ TokenType = $tokenType
}
$context['AuthType'] = 'IAT'
}
@@ -322,9 +301,9 @@
Write-Host "Logged in as $name!"
}
- if ($authType -eq 'App' -and -not $SkipAppAutoload) {
- Write-Verbose 'Loading GitHub App contexts...'
- Connect-GitHubApp -Shallow:$Shallow
+ if ($authType -eq 'App' -and $AutoloadInstallations) {
+ Write-Verbose 'Loading GitHub App Installation contexts...'
+ Connect-GitHubApp
}
} catch {
diff --git a/src/functions/public/Auth/Connect-GitHubApp.ps1 b/src/functions/public/Auth/Connect-GitHubApp.ps1
index c6751d7da..498ab089b 100644
--- a/src/functions/public/Auth/Connect-GitHubApp.ps1
+++ b/src/functions/public/Auth/Connect-GitHubApp.ps1
@@ -64,96 +64,86 @@
# The context to run the command in. Used to get the details for the API call.
# Can be either a string or a GitHubContext object.
[Parameter()]
- [object] $Context = (Get-GitHubContext),
-
- # Do not load credentials for the GitHub App Installations, just metadata.
- [Parameter()]
- [switch] $Shallow
+ [object] $Context = (Get-GitHubContext)
)
- $commandName = $MyInvocation.MyCommand.Name
- Write-Verbose "[$commandName] - Start"
+ begin {
+ $commandName = $MyInvocation.MyCommand.Name
+ Write-Verbose "[$commandName] - Start"
+ }
- $Context = $Context | Resolve-GitHubContext
- $Context | Assert-GitHubContext -AuthType 'App'
+ process {
+ try {
+ $Context = $Context | Resolve-GitHubContext
+ $Context | Assert-GitHubContext -AuthType 'App'
- try {
- $defaultContextData = @{
- ApiBaseUri = $Context.ApiBaseUri
- ApiVersion = $Context.ApiVersion
- HostName = $Context.HostName
- ClientID = $Context.ClientID
- AuthType = 'IAT'
- TokenType = 'ghs'
- }
-
- $installations = Get-GitHubAppInstallation
- Write-Verbose "Found [$($installations.Count)] installations."
- switch ($PSCmdlet.ParameterSetName) {
- 'User' {
- Write-Verbose "Filtering installations for user [$User]."
- $installations = $installations | Where-Object { $_.target_type -eq 'User' -and $_.account.login -in $User }
- }
- 'Organization' {
- Write-Verbose "Filtering installations for organization [$Organization]."
- $installations = $installations | Where-Object { $_.target_type -eq 'Organization' -and $_.account.login -in $Organization }
- }
- 'Enterprise' {
- Write-Verbose "Filtering installations for enterprise [$Enterprise]."
- $installations = $installations | Where-Object { $_.target_type -eq 'Enterprise' -and $_.account.slug -in $Enterprise }
- }
- }
-
- Write-Verbose "Found [$($installations.Count)] installations for the target type."
- $installations | ForEach-Object {
- $installation = $_
- $contextParams = @{} + $defaultContextData.Clone()
- if ($Shallow) {
- $token = [PSCustomObject]@{
- Token = [securestring]::new()
- ExpiresAt = [datetime]::MinValue
- }
- } else {
- $token = New-GitHubAppInstallationAccessToken -InstallationID $installation.id
- }
- $contextParams += @{
- InstallationID = $installation.id
- Token = $token.Token
- TokenExpirationDate = $token.ExpiresAt
- Permissions = $installation.permissions
- Events = $installation.events
- TargetType = $installation.target_type
- }
- switch ($installation.target_type) {
+ $installations = Get-GitHubAppInstallation -Context $Context
+ Write-Verbose "Found [$($installations.Count)] installations."
+ switch ($PSCmdlet.ParameterSetName) {
'User' {
- $contextParams += @{
- TargetName = $installation.account.login
- }
+ Write-Verbose "Filtering installations for user [$User]."
+ $installations = $installations | Where-Object { $_.target_type -eq 'User' -and $_.account.login -in $User }
}
'Organization' {
- $contextParams += @{
- TargetName = $installation.account.login
- }
+ Write-Verbose "Filtering installations for organization [$Organization]."
+ $installations = $installations | Where-Object { $_.target_type -eq 'Organization' -and $_.account.login -in $Organization }
}
'Enterprise' {
- $contextParams += @{
- TargetName = $installation.account.slug
- }
+ Write-Verbose "Filtering installations for enterprise [$Enterprise]."
+ $installations = $installations | Where-Object { $_.target_type -eq 'Enterprise' -and $_.account.slug -in $Enterprise }
}
}
- Write-Verbose 'Logging in using an installation access token...'
- Write-Verbose ($contextParams | Format-Table | Out-String)
- $tmpContext = [InstallationGitHubContext]::new((Set-GitHubContext -Context $contextParams -PassThru))
- Write-Verbose ($tmpContext | Format-List | Out-String)
- if (-not $Silent) {
- $name = $tmpContext.name
- Write-Host "Connected $name"
+
+ Write-Verbose "Found [$($installations.Count)] installations for the target."
+ $installations | ForEach-Object {
+ $installation = $_
+ Write-Verbose "Processing installation [$($installation.account.login)] [$($installation.id)]"
+ $token = New-GitHubAppInstallationAccessToken -Context $Context -InstallationID $installation.id
+
+ $contextParams = @{
+ AuthType = [string]'IAT'
+ TokenType = [string]'ghs'
+ DisplayName = [string]$Context.DisplayName
+ ApiBaseUri = [string]$Context.ApiBaseUri
+ ApiVersion = [string]$Context.ApiVersion
+ HostName = [string]$Context.HostName
+ ClientID = [string]$Context.ClientID
+ InstallationID = [string]$installation.id
+ Permissions = [pscustomobject]$installation.permissions
+ Events = [string[]]$installation.events
+ TargetType = [string]$installation.target_type
+ Token = [securestring]$token.Token
+ TokenExpirationDate = [string]$token.ExpiresAt
+ }
+
+ switch ($installation.target_type) {
+ 'User' {
+ $contextParams['TargetName'] = $installation.account.login
+ }
+ 'Organization' {
+ $contextParams['TargetName'] = $installation.account.login
+ }
+ 'Enterprise' {
+ $contextParams['TargetName'] = $installation.account.slug
+ }
+ }
+ Write-Verbose 'Logging in using a managed installation access token...'
+ Write-Verbose ($contextParams | Format-Table | Out-String)
+ $tmpContext = [InstallationGitHubContext]::new((Set-GitHubContext -Context $contextParams.Clone() -PassThru))
+ Write-Verbose ($tmpContext | Format-List | Out-String)
+ if (-not $Silent) {
+ $name = $tmpContext.name
+ Write-Host "Connected $name"
+ }
+ $contextParams.Clear()
}
+ } catch {
+ Write-Error $_
+ Write-Error (Get-PSCallStack | Format-Table | Out-String)
+ throw 'Failed to connect to GitHub using a GitHub App.'
}
- } catch {
- Write-Error $_
- Write-Error (Get-PSCallStack | Format-Table | Out-String)
- throw 'Failed to connect to GitHub using a GitHub App.'
}
- Write-Verbose "[$commandName] - End"
+ end {
+ Write-Verbose "[$commandName] - End"
+ }
}
diff --git a/src/functions/public/Auth/Context/Set-GitHubContext.ps1 b/src/functions/public/Auth/Context/Set-GitHubContext.ps1
index 590e5983f..cb420df53 100644
--- a/src/functions/public/Auth/Context/Set-GitHubContext.ps1
+++ b/src/functions/public/Auth/Context/Set-GitHubContext.ps1
@@ -42,80 +42,124 @@ function Set-GitHubContext {
$commandName = $MyInvocation.MyCommand.Name
Write-Verbose "[$commandName] - Start"
$null = Get-GitHubConfig
+ $contextObj = @{} + $Context
}
process {
Write-Verbose 'Context:'
- $Context | Out-String -Stream | ForEach-Object { Write-Verbose $_ }
+ $contextObj | Out-String -Stream | ForEach-Object { Write-Verbose $_ }
# Run functions to get info on the temporary context.
try {
- Write-Verbose "Getting info on the context [$($Context['AuthType'])]."
- switch -Regex ($($Context['AuthType'])) {
+ Write-Verbose "Getting info on the context [$($contextObj['AuthType'])]."
+ switch -Regex ($($contextObj['AuthType'])) {
'PAT|UAT|IAT' {
- $viewer = Get-GitHubViewer -Context $Context
+ $viewer = Get-GitHubViewer -Context $contextObj
$viewer | Out-String -Stream | ForEach-Object { Write-Verbose $_ }
- $login = [string]$viewer.login
- $Context += @{
- DisplayName = [string]$viewer.name
- Username = $login
- NodeID = [string]$viewer.id
- DatabaseID = [string]$viewer.databaseId
+ if ([string]::IsNullOrEmpty($contextObj['DisplayName'])) {
+ $contextObj['DisplayName'] = [string]$viewer.name
+ }
+ if ([string]::IsNullOrEmpty($contextObj['Username'])) {
+ $login = [string]($viewer.login -Replace '\[bot\]')
+ $contextObj['Username'] = $login
+ }
+ if ([string]::IsNullOrEmpty($contextObj['NodeID'])) {
+ $contextObj['NodeID'] = [string]$viewer.id
+ }
+ if ([string]::IsNullOrEmpty($contextObj['DatabaseID'])) {
+ $contextObj['DatabaseID'] = [string]$viewer.databaseId
}
}
'PAT|UAT' {
- $ContextName = "$($Context['HostName'])/$login"
- $Context += @{
- Name = $ContextName
- Type = 'User'
- }
+ $contextName = "$($contextObj['HostName'])/$login"
+ $contextObj['Name'] = $contextName
+ $contextObj['Type'] = 'User'
}
'IAT' {
- $ContextName = "$($Context['HostName'])/$login/$($Context.TargetType)/$($Context.TargetName)" -Replace '\[bot\]'
- $Context += @{
- Name = $ContextName
- Type = 'Installation'
+ $contextObj['Type'] = 'Installation'
+ if ([string]::IsNullOrEmpty($contextObj['DisplayName'])) {
+ try {
+ $app = Get-GitHubApp -AppSlug $contextObj['Username'] -Context $contextObj
+ $contextObj['DisplayName'] = [string]$app.name
+ } catch {
+ Write-Warning "Failed to get the GitHub App with the slug: [$($contextObj['Username'])]."
+ }
+ }
+ if ($script:GitHub.EnvironmentType -eq 'GHA') {
+ $gitHubEvent = Get-Content -Path $env:GITHUB_EVENT_PATH -Raw | ConvertFrom-Json
+ $targetType = $gitHubEvent.repository.owner.type
+ $targetName = $gitHubEvent.repository.owner.login
+ $enterprise = $gitHubEvent.enterprise.slug
+ $organization = $gitHubEvent.organization.login
+ $owner = $gitHubEvent.repository.owner.login
+ $repo = $gitHubEvent.repository.name
+ $gh_sender = $gitHubEvent.sender.login # sender is an automatic variable in Powershell
+ Write-Verbose "Enterprise: $enterprise"
+ Write-Verbose "Organization: $organization"
+ Write-Verbose "Repository: $repo"
+ Write-Verbose "Repository Owner: $owner"
+ Write-Verbose "Repository Owner Type: $targetType"
+ Write-Verbose "Sender: $gh_sender"
+ if ([string]::IsNullOrEmpty($contextObj['Enterprise'])) {
+ $contextObj['Enterprise'] = [string]$enterprise
+ }
+ if ([string]::IsNullOrEmpty($contextObj['Owner'])) {
+ $contextObj['Owner'] = [string]$owner
+ }
+ if ([string]::IsNullOrEmpty($contextObj['Repo'])) {
+ $contextObj['Repo'] = [string]$repo
+ }
+ if ([string]::IsNullOrEmpty($contextObj['TargetType'])) {
+ $contextObj['TargetType'] = [string]$targetType
+ }
+ if ([string]::IsNullOrEmpty($contextObj['TargetName'])) {
+ $contextObj['TargetName'] = [string]$targetName
+ }
+ $contextObj['Name'] = "$($contextObj['HostName'])/$($contextObj['Username'])/" +
+ "$($contextObj['TargetType'])/$($contextObj['TargetName'])"
+ } else {
+ $contextObj['Name'] = "$($contextObj['HostName'])/$($contextObj['Username'])/" +
+ "$($contextObj['TargetType'])/$($contextObj['TargetName'])"
}
}
'App' {
- $app = Get-GitHubApp -Context $Context
- $ContextName = "$($Context['HostName'])/$($app.slug)"
- $Context += @{
- Name = $ContextName
- DisplayName = [string]$app.name
- Username = [string]$app.slug
- NodeID = [string]$app.node_id
- DatabaseID = [string]$app.id
- Permissions = [string]$app.permissions
- Events = [string]$app.events
- OwnerName = [string]$app.owner.login
- OwnerType = [string]$app.owner.type
- Type = 'App'
- }
+ $app = Get-GitHubApp -Context $contextObj
+ $contextObj['Name'] = "$($contextObj['HostName'])/$($app.slug)"
+ $contextObj['DisplayName'] = [string]$app.name
+ $contextObj['Username'] = [string]$app.slug
+ $contextObj['NodeID'] = [string]$app.node_id
+ $contextObj['DatabaseID'] = [string]$app.id
+ $contextObj['Permissions'] = [PSCustomObject]$app.permissions
+ $contextObj['Events'] = [string[]]$app.events
+ $contextObj['OwnerName'] = [string]$app.owner.login
+ $contextObj['OwnerType'] = [string]$app.owner.type
+ $contextObj['Type'] = 'App'
}
default {
throw 'Failed to get info on the context. Unknown logon type.'
}
}
- Write-Verbose "Found [$($Context['Type'])] with login: [$($Context['Name'])]"
- $Context | Out-String -Stream | ForEach-Object { Write-Verbose $_ }
+ Write-Verbose "Found [$($contextObj['Type'])] with login: [$($contextObj['Name'])]"
+ $contextObj | Out-String -Stream | ForEach-Object { Write-Verbose $_ }
Write-Verbose '----------------------------------------------------'
if ($PSCmdlet.ShouldProcess('Context', 'Set')) {
- Write-Verbose "Saving context: [$($script:GitHub.Config.ID)/$($Context['Name'])]"
- Set-Context -ID "$($script:GitHub.Config.ID)/$($Context['Name'])" -Context $Context
+ Write-Verbose "Saving context: [$($script:GitHub.Config.ID)/$($contextObj['Name'])]"
+ Set-Context -ID "$($script:GitHub.Config.ID)/$($contextObj['Name'])" -Context $contextObj
if ($Default) {
- Set-GitHubDefaultContext -Context $Context['Name']
- if ($Context['AuthType'] -eq 'IAT' -and $script:GitHub.EnvironmentType -eq 'GHA') {
- Set-GitHubGitConfig -Context $Context['Name']
+ Set-GitHubDefaultContext -Context $contextObj['Name']
+ if ($contextObj['AuthType'] -eq 'IAT' -and $script:GitHub.EnvironmentType -eq 'GHA') {
+ Set-GitHubGitConfig -Context $contextObj['Name']
}
}
if ($PassThru) {
- Get-GitHubContext -Context $($Context['Name'])
+ Get-GitHubContext -Context $($contextObj['Name'])
}
}
} catch {
Write-Error $_ | Select-Object *
throw 'Failed to set the GitHub context.'
+ } finally {
+ $contextObj.Clear()
}
}
diff --git a/tests/GitHub.Tests.ps1 b/tests/GitHub.Tests.ps1
index 8faeacb6a..7916d4b19 100644
--- a/tests/GitHub.Tests.ps1
+++ b/tests/GitHub.Tests.ps1
@@ -50,22 +50,35 @@ Describe 'GitHub' {
}
It 'Can be called with a GitHub App' {
- { Connect-GitHubAccount -ClientID $env:TEST_APP_CLIENT_ID -PrivateKey $env:TEST_APP_PRIVATE_KEY } | Should -Not -Throw
- { Connect-GitHubAccount -ClientID $env:TEST_APP_CLIENT_ID -PrivateKey $env:TEST_APP_PRIVATE_KEY } | Should -Not -Throw
- Write-Verbose (Get-GitHubContext | Out-String) -Verbose
- }
-
- It 'Can list all contexts' {
- Write-Verbose (Get-GitHubContext -ListAvailable | Out-String) -Verbose
- (Get-GitHubContext -ListAvailable).Count | Should -Be 7
+ $params = @{
+ ClientID = $env:TEST_APP_CLIENT_ID
+ PrivateKey = $env:TEST_APP_PRIVATE_KEY
+ }
+ { Connect-GitHubAccount @params } | Should -Not -Throw
+ $contexts = Get-GitHubContext -ListAvailable -Verbose:$false
+ Write-Verbose ($contexts | Out-String) -Verbose
+ ($contexts).Count | Should -Be 3
+ }
+ It 'Can be called with a GitHub App and autoload installations' {
+ $params = @{
+ ClientID = $env:TEST_APP_CLIENT_ID
+ PrivateKey = $env:TEST_APP_PRIVATE_KEY
+ }
+ { Connect-GitHubAccount @params -AutoloadInstallations } | Should -Not -Throw
+ $contexts = Get-GitHubContext -ListAvailable -Verbose:$false
+ Write-Verbose ($contexts | Out-String) -Verbose
+ ($contexts).Count | Should -Be 7
}
It 'Can disconnect a specific context' {
- { Disconnect-GitHubAccount -Context 'github.com/github-actions/Organization/PSModule' -Silent } | Should -Not -Throw
- (Get-GitHubContext -ListAvailable).Count | Should -Be 6
- Connect-GitHubAccount
- Connect-GitHubAccount -ClientID $env:TEST_APP_CLIENT_ID -PrivateKey $env:TEST_APP_PRIVATE_KEY
- (Get-GitHubContext -ListAvailable).Count | Should -Be 7
+ { Disconnect-GitHubAccount -Context 'github.com/psmodule-test-app/Organization/PSModule' -Silent } | Should -Not -Throw
+ $contexts = Get-GitHubContext -ListAvailable -Verbose:$false
+ Write-Verbose ($contexts | Out-String) -Verbose
+ ($contexts).Count | Should -Be 6
+ Connect-GitHubAccount -ClientID $env:TEST_APP_CLIENT_ID -PrivateKey $env:TEST_APP_PRIVATE_KEY -AutoloadInstallations
+ $contexts = Get-GitHubContext -ListAvailable -Verbose:$false
+ Write-Verbose ($contexts | Out-String) -Verbose
+ ($contexts).Count | Should -Be 7
}
It 'Can get the authenticated GitHubApp' {