Skip to content

Commit

Permalink
🚀 [Feature]: Implement GitHub App creation and conversion functions
Browse files Browse the repository at this point in the history
  • Loading branch information
MariusStorhaug committed Feb 6, 2025
1 parent e01341e commit 6170aad
Show file tree
Hide file tree
Showing 5 changed files with 384 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .github/workflows/Process-PSModule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,5 @@ jobs:
TEST_USER_ORG_FG_PAT: ${{ secrets.TEST_USER_ORG_FG_PAT }}
TEST_USER_USER_FG_PAT: ${{ secrets.TEST_USER_USER_FG_PAT }}
TEST_USER_PAT: ${{ secrets.TEST_USER_PAT }}
with:
SkipTests: All
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
function Convert-GitHubAppManifest {
<#
.SYNOPSIS
Converts a GitHub App Manifest into a full GitHub App.
.DESCRIPTION
Converts a temporary GitHub App Manifest into a full GitHub App by exchanging the provided code for app credentials.
This function requires authentication and will return key details such as the App ID, Client ID, Private Key, and Webhook Secret.
.EXAMPLE
Convert-GitHubAppManifest -Code 'example-code' -Context $GitHubContext
Converts the GitHub App Manifest associated with 'example-code' using the specified context.
Returns the App ID, Client ID, Private Key, and Webhook Secret.
.NOTES
[GitHub API Docs - Convert a GitHub App Manifest](https://docs.github.com/en/rest/apps/apps#create-a-github-app-from-a-manifest)
#>

[CmdletBinding()]
param(
# The code received from GitHub to convert the app manifest into an installation.
[Parameter(Mandatory)]
[string] $Code,

# The context to run the command in. Used to get the details for the API call.
[Parameter(Mandatory)]
[object] $Context
)

begin {
$stackPath = $MyInvocation.MyCommand.Name
Write-Debug "[$stackPath] - Start"
Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT
}

process {
$inputObject = @{
Context = $Context
APIEndpoint = "/app-manifests/$Code/conversions"
Method = 'GET'
}

$response = Invoke-GitHubAPI @inputObject | Select-Object -ExpandProperty Response

Write-Verbose 'GitHub App converted successfully.'
Write-Verbose ($response | Format-List | Out-String)

[PSCustomObject]@{
AppId = $response.id
ClientId = $response.client_id
PrivateKey = $response.pem
WebhookSecret = $response.webhook_secret
}
}

end {
Write-Debug "[$stackPath] - End"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
function Invoke-GitHubAppCreationForm {
<#
.SYNOPSIS
Submits a GitHub App manifest to GitHub and returns a temporary creation code.
.DESCRIPTION
This function builds the manifest JSON payload from provided parameters and sends a POST request
to the GitHub App creation endpoint (personal, organization, or enterprise). It then extracts the
temporary code from the redirect URL. If something goes wrong, it writes an error.
The function supports different parameter sets for creating apps under personal, organization, or
enterprise accounts.
.EXAMPLE
$code = Invoke-GitHubAppCreationForm -Name "MyApp" -Url "https://example.com" -WebhookURL "https://example.com/webhook"
Creates a GitHub App with the given name, homepage URL, and webhook URL, then returns a temporary
creation code.
.EXAMPLE
$code = Invoke-GitHubAppCreationForm -Name "MyOrgApp" -Url "https://myorg.com" -Organization "MyOrg"
Registers a GitHub App under the "MyOrg" organization and returns a temporary creation code.
.NOTES
[Registering a GitHub App from a manifest](https://docs.github.com/en/apps/sharing-github-apps/registering-a-github-app-from-a-manifest)
#>

[CmdletBinding(DefaultParameterSetName = 'Personal')]
param(
# The name of the GitHub App.
[Parameter(Mandatory)]
[string] $Name,

# The homepage URL of the GitHub App.
[Parameter(Mandatory)]
[string] $Url,

# Enables webhook support for the GitHub App.
[Parameter()]
[switch] $WebhookEnabled,

# The webhook URL where GitHub will send event payloads.
[Parameter()]
[string] $WebhookURL,

# The redirect URL after app creation.
[Parameter()]
[string] $RedirectUrl,

# List of callback URLs for OAuth flows.
[Parameter()]
[string[]] $CallbackUrls,

# The setup URL for the GitHub App.
[Parameter()]
[string] $SetupUrl,

# A description of the GitHub App.
[Parameter()]
[string] $Description,

# Indicates whether the app is public.
[Parameter()]
[switch] $Public,

# List of default webhook events the GitHub App will subscribe to.
[Parameter()]
[string[]] $Events,

# Permissions requested by the GitHub App.
[Parameter()]
[hashtable] $Permissions,

# Determines if OAuth authorization should be requested upon installation.
[Parameter()]
[switch] $RequestOAuthOnInstall,

# Determines if setup should be prompted when the app is updated.
[Parameter()]
[switch] $SetupOnUpdate,

# The organization under which the app is being created (Organization parameter set).
[Parameter(ParameterSetName = 'Organization', Mandatory)]
[string] $Organization,

# The enterprise under which the app is being created (Enterprise parameter set).
[Parameter(ParameterSetName = 'Enterprise', Mandatory)]
[string] $Enterprise,

# Optional state parameter to pass during app creation.
[Parameter()]
[string] $State,

# The context to run the command in. Used to get the details for the API call.
[Parameter(Mandatory)]
[object] $Context
)

begin {
$stackPath = $MyInvocation.MyCommand.Name
Write-Debug "[$stackPath] - Start"
}

process {
Write-Verbose 'Building GitHub App manifest JSON payload...'
# Build the manifest object
$manifest = @{
name = $Name
url = $Url
hook_attributes = @{
url = $WebhookURL
active = $WebhookEnabled
}
redirect_url = $RedirectUrl
callback_urls = $CallbackUrls
setup_url = $SetupUrl
description = $Description
public = $Public
default_events = $Events
default_permissions = $Permissions
request_oauth_on_install = $RequestOAuthOnInstall
setup_on_update = $SetupOnUpdate
} | ConvertTo-Json -Depth 10 -Compress

# Determine target URL based on Org value
switch ($PSCmdlet.ParameterSetName) {
'Enterprise' {
$targetUrl = "$($Context.ApiBaseUri)/enterprises/$Enterprise/settings/apps/new"
}
'Organization' {
$targetUrl = "$($Context.ApiBaseUri)/organizations/$Organization/settings/apps/new"
}
'Personal' {
$targetUrl = "$($Context.ApiBaseUri)/settings/apps/new"
}
}

if ($State) {
if ($targetUrl -notlike '*?*') {
$targetUrl = "$targetUrl?state=$State"
} else {
$targetUrl = "$targetUrl&state=$State"
}
}
Write-Verbose "Sending manifest to GitHub App creation URL: $targetUrl"

# Prepare the request body and headers
$body = @{ manifest = $manifest }

try {
$inputObject = @{
Method = 'POST'
Uri = $targetUrl
Body = $body
MaximumRedirection = 0
Authentication = 'Bearer'
Token = $Context.Token
ErrorAction = 'Stop'
}
$response = Invoke-WebRequest @inputObject
} catch {
Write-Error "Error sending manifest: $_"
return
}

# Extract the 'code' from the redirect Location header
$location = $response.Headers['Location']
Write-Verbose "Received redirect location: $location"
if (-not $location) {
Write-Error 'No redirect location found. The app may not have been created.'
return
}
$code = $null
if ($location -match 'code=([^&]+)') {
$code = $matches[1]
}
if (-not $code) {
Write-Error 'Failed to parse the app creation code from redirect URL.'
return
}
Write-Verbose "Extracted temporary code: $code"
return $code
}

end {
Write-Debug "[$stackPath] - End"
}
}
132 changes: 132 additions & 0 deletions src/functions/public/Apps/GitHub App/New-GitHubApp.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
function New-GitHubApp {
<#
.SYNOPSIS
Orchestrates the creation of a GitHub App.
.DESCRIPTION
This function ties together the manifest submission and conversion functions.
It takes all the necessary app parameters, calls the function to send the manifest form,
and then uses the temporary code to retrieve the final GitHub App configuration.
.EXAMPLE
$appDetails = New-GitHubApp -Name "MyApp" -Url "https://example.com" -WebhookURL "https://example.com/webhook" -Token "myToken"
Creates a new GitHub App with the specified name, URL, webhook URL, and authentication token.
.NOTES
[GitHub Apps](https://docs.github.com/apps)
#>
[CmdletBinding(DefaultParameterSetName = 'Personal', SupportsShouldProcess)]
param(
# The name of the GitHub App.
[Parameter()]
[string] $Name,

# The main URL of the GitHub App.
[Parameter(Mandatory)]
[string] $Url,

# The webhook URL for event notifications.
[Parameter()]
[string] $WebhookURL,

# Enables or disables webhooks.
[Parameter()]
[switch] $WebhookEnabled,

# The redirect URL after authentication.
[Parameter()]
[string] $RedirectUrl,

# List of callback URLs for OAuth authentication.
[Parameter()]
[string[]] $CallbackUrls,

# The setup URL for the GitHub App.
[Parameter()]
[string] $SetupUrl,

# A brief description of the GitHub App.
[Parameter()]
[string] $Description,

# Specifies whether the app should be publicly visible.
[Parameter()]
[switch] $Public,

# List of GitHub events the app subscribes to.
[Parameter()]
[string[]] $Events,

# The permissions required by the GitHub App.
[Parameter()]
[hashtable] $Permissions,

# Whether the app requests OAuth authorization on installation.
[Parameter()]
[switch] $RequestOAuthOnInstall,

# Whether the setup process should run again when updating the app.
[Parameter()]
[switch] $SetupOnUpdate,

# The organization under which the app is being created (Organization parameter set).
[Parameter(ParameterSetName = 'Organization', Mandatory)]
[string] $Organization,

# The enterprise under which the app is being created (Enterprise parameter set).
[Parameter(ParameterSetName = 'Enterprise', Mandatory)]
[string] $Enterprise,

# The state parameter for additional configuration.
[Parameter()]
[string] $State
)
begin {
$stackPath = $MyInvocation.MyCommand.Name
Write-Debug "[$stackPath] - Start"
}
process {
Write-Verbose 'Initiating GitHub App creation process...'
# Step 1: Send manifest and get the temporary code
$params = @{
Name = $Name
Url = $Url
WebhookEnabled = $WebhookEnabled
WebhookURL = $WebhookURL
RedirectUrl = $RedirectUrl
CallbackUrls = $CallbackUrls
SetupUrl = $SetupUrl
Description = $Description
Public = $Public
Events = $Events
Permissions = $Permissions
RequestOAuthOnInstall = $RequestOAuthOnInstall
SetupOnUpdate = $SetupOnUpdate
Enterprise = $Enterprise
Org = $Organization
State = $State
}
$code = Invoke-GitHubAppCreationForm @params

if (-not $code) {
Write-Error 'Failed to retrieve temporary code from GitHub App manifest submission.'
return
}

# Step 2: Convert the temporary code into final app details
if ($PSCmdlet.ShouldProcess("$Name", 'Create GitHub App')) {
$appDetails = Convert-GitHubAppManifest -Code $code -Context $Context
}
if (-not $appDetails) {
Write-Error 'Failed to convert GitHub App manifest into final configuration.'
return
}

Write-Verbose 'GitHub App created successfully.'
return $appDetails
}
end {
Write-Debug "[$stackPath] - End"
}
}
2 changes: 1 addition & 1 deletion tools/utilities/GitHubAPI.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ $response = Invoke-RestMethod -Uri $APIDocURI -Method Get
# @{n = 'PUT'; e = { (($_.value.psobject.Properties.Name) -contains 'PUT') } }, `
# @{n = 'PATCH'; e = { (($_.value.psobject.Properties.Name) -contains 'PATCH') } } | Format-Table

$path = '/markdown/raw'
$path = '/app-manifests/{code}/conversions'
$method = 'post'
$response.paths.$path.$method
$response.paths.$path.$method.tags | clip # -> Namespace/foldername
Expand Down

0 comments on commit 6170aad

Please sign in to comment.