Skip to content

Commit

Permalink
🚀 [Feature]: GitHub App creation and installation management (#281)
Browse files Browse the repository at this point in the history
## Description

### Added functions for Enterprise App management.
src/functions/private/Apps/GitHub
Apps/Get-GitHubEnterpriseOrganizationAppInstallation.ps1

- Fixes #277

### General alignments:

* Adds a guidance file that defines conventions for a module
`CodingStandards.md`
    - will be promoted to the `docs` repository later.
  * Remove most re-throw `try{...} catch { throw $_ }` statements.
    * Fixes #289
* Added `#Requires -Modules` statements to ensure necessary modules are
marked as requirements for the module.
  * Parameter hashtables for Invoke-GitHubAPI is now sorted like:
    - Method: UPPERCASED
    - Endpoint
    - Header related stuff
    - Body related stuff
    - Context
  * Function name aligning:
  * Parameter name aligning:
* Changed `Repo` to `Repository` - throughout the module. The repo
abbreviation can still be used, but in classes, object and commands, we
standardize on Repository.
  * Clean up aliases:
    * Remove all but one alias for Connect/Disconnect-GitHub
    * Remove org alias for Organization parameters

#### Private functions:

* Made `Context` mandatory in private functions
* Removed `Resolve-GitHubContext` in private functions


#### Public functions:



- Fixes #297
- Fixes #249

## Type of change

<!-- Use the check-boxes [x] on the options that are relevant. -->

- [ ] 📖 [Docs]
- [ ] 🪲 [Fix]
- [ ] 🩹 [Patch]
- [ ] ⚠️ [Security fix]
- [x] 🚀 [Feature]
- [ ] 🌟 [Breaking change]

## Checklist

<!-- Use the check-boxes [x] on the options that are relevant. -->

- [x] I have performed a self-review of my own code
- [x] I have commented my code, particularly in hard-to-understand areas
  • Loading branch information
MariusStorhaug authored Feb 5, 2025
1 parent 048b37c commit e01341e
Show file tree
Hide file tree
Showing 258 changed files with 4,725 additions and 5,329 deletions.
186 changes: 186 additions & 0 deletions CodingStandard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Coding Standards for `GitHub`

Start by reading the general coding standards for [`PSModule`](https://psmodule.io/docs) which is the basis for all modules in the framework.
Additions or adjustments to those defaults are covered in this document to ensure that the modules drive consistancy for all developers.

## General Coding Standards

1. **PowerShell Keywords**
- All PowerShell keywords (e.g. `if`, `return`, `throw`, `function`, `param`) **must** be in lowercase.

2. **Brace Style**
- Use **One True Bracing Style (OTBS)**.
- Opening brace on the same line as the statement; closing brace on its own line.

3. **Coverage**
- We do **not** need 100% coverage of the GitHub API.
- Maintain a separate file listing the endpoints you intentionally **do not** cover, so the coverage report can mark them accordingly (e.g., ⚠️).

4. **Convert Filter Types**
- Wherever filters are used, ensure they are implemented as standard PowerShell functions with `begin`, `process`, and `end` blocks.

---

## Functions

- **Grouping**
- Group functions by the *object type* they handle, using folders named for that object type (e.g. “Repository”), **not** by the API endpoint URL.

- **Naming**
- Public function name format: **`Verb-GitHubNoun`**.
- Private function name format: **`Verb-GitHubNoun`** (same style but no aliases).
- **`Get-`** functions must **not** include `[CmdletBinding(SupportsShouldProcess)]`. You only use `SupportsShouldProcess` on commands that change or remove data (`Set-`, `Remove-`, `Add-`, etc.).

- **Default Parameter Sets**
- Do **not** declare `DefaultParameterSetName = '__AllParameterSets'`.
- Only specify a `DefaultParameterSetName` if it is actually different from the first parameter set.

- **One API Call = One Function**
- If you find that a single function is handling multiple distinct API calls, split it into multiple functions.

- **Public vs. Private**
1. **Public Functions**
- Support pipeline input if appropriate.
- Should begin by calling `Resolve-GitHubContext` to handle the `Context` parameter, which can be either a string or a `GitHubContext` object.
- Use parameter sets (with `begin`, `process`, `end`) if you have multiple ways to call the same logical operation.
- If choosing among multiple underlying private functions, use a `switch` statement in the `process` block keyed on the parameter set name.
- If a parameter like `$Repository` is missing, you can default to `$Context.Repo`. If no value is found, **throw** an error.
2. **Private Functions**
- **No pipeline input**.
- No aliases on either the function or its parameters.
- **`Context` is mandatory** (type `GitHubContext`), since public functions should already have resolved it.
- **`Owner`, `Organization`, `ID`, `Repository`** are also mandatory if required by the endpoint.
- Must not contain logic to default parameters from `Context`; that is resolved in public functions.

---

## Documentation for Functions

All function documentation follows standard PowerShell help conventions, with some notes:

1. **.SYNOPSIS**, **.DESCRIPTION**, **.EXAMPLES**
- Examples in your code should include fencing (e.g., triple backticks) because the PSModule framework removes default fences.

2. **.PARAMETER**
- Do **not** store parameter documentation in a comment block separate from the parameter. Instead, use inline parameter documentation via the `[Parameter()]` attribute and descriptions in triple-slash (`///`) comments above each parameter.

3. **.NOTES**
- Include a link to the official documentation (if any) that the function is based on, so it’s discoverable via online help.

4. **.LINK**
- First link should be the function’s own local documentation (generated for the PowerShell module).
- Additional links can point to official GitHub or API documentation.

---

## Parameter Guidelines

1. **Always Declare [Parameter()]**
- Every parameter must explicitly have a `[Parameter()]` attribute, even if empty.
- Place these attributes in a consistent order (see **Parameter Attributes Order** below).

2. **Parameter Types**
- Always specify a type, e.g. `[string] $Owner` (rather than `$Owner` alone).

3. **Parameter Naming**
- Use **PascalCase** for parameters.
- Convert snake_case from the API docs to **PascalCase** in the function.
- **`ID`** should be the short name. If needed, add an alias for a long form (e.g., `[Alias('SomeLongName')]`) or for a different style (`'id'`, `'Id'`), depending on user expectations.
- If the function name implies the object (e.g., `Get-GitHubRepository`), do **not** name the parameter `RepositoryId`. Just `ID` (or `Name`, etc.) suffices. Keep it object-oriented rather than repeating the context.
- `Owner` should always have the aliases: `Organization` and `User`.
- `Username` can have the alias `Login` if relevant for a particular API.
- Use `Repository` (not `Repo`). If you need an alias for backward compatibility, add `[Alias('Repo')]`.

4. **Parameter Attribute Order**
1. `[Parameter()]`
2. `[ValidateNotNullOrEmpty()]` or other validation attributes
3. `[Alias()]` if any
4. Then the parameter definition itself: `[string] $ParamName`

5. **Parameter Defaulting**
- For **public** functions, if the user hasn’t provided a parameter (like `$Repository`), default it from the context:
```powershell
if (-not $Repository) {
$Repository = $Context.Repo
}
if (-not $Repository) {
throw "Repository not specified and not found in the context."
}
```
- For **private** functions, the calling function should already have done this. Private functions assume mandatory parameters.
6. **Remove `[org]` Alias**
- Do not use `[Alias('org')]` on the `$Organization` parameter. Use `[Alias('User','Organization')]` on `$Owner` instead.
---
## Function Content & Flow
1. **Structure**
- Always use `begin`, `process`, and `end` blocks.
- **`begin`**: Validate parameters, call `Assert-GitHubContext` if needed, set up any local state.
- Add a comment stating which permissions are required for the API call.
- **`process`**: Main logic, including pipeline handling if public.
- **`end`**: Cleanup if necessary.
2. **ShouldProcess**
- Only use `[CmdletBinding(SupportsShouldProcess)]` for commands that create, update, or remove data. **Do not** apply it to `Get-` commands.
3. **API Method Naming**
- Use PascalCase for the method in your splat (e.g., `Post`, `Delete`, `Put`, `Get`).
- The `Method` property in your hashtable to `Invoke-GitHubAPI` (or other REST calls) should reflect that standard.
4. **Splatting**
- Always splat the API call. The standard order in the splat is:
1. `Method`
2. `APIEndpoint` (or `Endpoint`, with `APIEndpoint` as an alias if necessary)
3. `Body`
4. `Context`
- Body is always a hashtable containing the payload for `POST`, `PATCH`, or `PUT` calls.
5. **Removing String Checks**
- Do **not** use `if ([string]::IsNullOrEmpty($Param))`. Instead, check `-not $Param` or rely on `[ValidateNotNullOrEmpty()]`.
6. **Pipeline Output**
- After calling `Invoke-GitHubAPI @inputObject`, you can **either**:
- `ForEach-Object { Write-Output $_.Response }`
- or `Select-Object -ExpandProperty Response`
- Choose which pattern best fits your scenario, but be consistent within a function.
---
## Classes
1. **One Class per Resource**
- Each distinct resource type gets its own `.ps1` or `.psm1` with a single class definition.
2. **Property and Method Naming**
- Use PascalCase for all public properties and methods.
3. **Return Types / Interfaces**
- Each class that you return should have a consistent interface.
- Remove any properties that are purely “API wrapper” fields (e.g., raw HTTP artifacts that aren’t relevant to the user).
---
## Additional Notes
1. **Endpoint Coverage File**
- Maintain a list of endpoints you’re deliberately **not** implementing, so that your coverage reporting can include a ⚠️ for them.
2. **Parameter Name Design**
- Use object-oriented naming that reflects the entity. For example, if the function is `Remove-GitHubRepository`, simply use `-ID` (or `-Name`) rather than `-RepositoryID`.
3. **Aliases**
- Private functions have **no** aliases (function-level or parameter-level).
- Public functions can add aliases where it makes sense (`Owner` has `-User`/`-Organization`, `Repository` might have `-Repo` alias if needed, `Username` might have `-Login`).
4. **Mandatory Context for Private**
- Private functions must always expect a resolved `[GitHubContext] $Context`. Public functions handle any string-based or null context resolution logic.
5. **We Do Not Have to Cover Every Possible API**
- Some endpoints (e.g., “hovercards” or other rarely used features) can be excluded.
---
That’s it. This spec captures all the bullet points and original guidelines in one place. Use it as the authoritative reference for coding style, function naming, parameter declarations, and general best practices in your module.
2 changes: 1 addition & 1 deletion examples/Apps/AppManagement.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
$appIDs = @(
'Iv1.f26b61bc99e69405'
)
$orgs = Get-GitHubEnterpriseInstallableOrganization -Enterprise 'msx'
$orgs = Get-GitHubAppInstallableOrganization -Enterprise 'msx'
foreach ($org in $orgs) {
foreach ($appID in $appIDs) {
Install-GitHubAppOnEnterpriseOrganization -Enterprise msx -Organization $org.login -ClientID $appID -RepositorySelection all
Expand Down
12 changes: 2 additions & 10 deletions examples/Apps/EnterpriseApps.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,8 @@ filter Install-GithubApp {
[string] $AppID
)

begin {

}

process {
$installableOrgs = Get-GitHubEnterpriseInstallableOrganization -Enterprise $Enterprise -Debug -Verbose
$installableOrgs = Get-GitHubAppInstallableOrganization -Enterprise $Enterprise -Debug -Verbose
$orgs = $installableOrgs | Where-Object { $_.login -like $organization }
foreach ($org in $orgs) {
foreach ($appIDitem in $AppID) {
Expand All @@ -37,12 +33,8 @@ filter Install-GithubApp {
}
}
}

end {

}
}

$appIDs | Install-GithubApp -Organization $organization -Debug -Verbose
$appIDs | Install-GitHubApp -Organization $organization -Debug -Verbose

$installation = Get-GitHubAppInstallation
5 changes: 4 additions & 1 deletion examples/Connecting.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
###
#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '6.0.0' }
#Requires -Modules @{ ModuleName = 'Microsoft.PowerShell.SecretManagement'; RequiredVersion = '1.1.2' }

###
### CONNECTING
###

Expand Down
1 change: 0 additions & 1 deletion examples/Teams/Get-AllTeams.ps1

This file was deleted.

2 changes: 1 addition & 1 deletion scripts/Update-CoverageReport.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#Requires -Modules MarkdownPS
#Requires -Modules @{ ModuleName = 'MarkdownPS'; RequiredVersion = '1.10' }

[CmdletBinding()]
param()
Expand Down
2 changes: 1 addition & 1 deletion src/classes/public/Config/GitHubConfig.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
# The default value for the HTTP protocol version.
[string] $HttpVersion

# The default value for the 'per_page' API parameter used in 'Get' functions that support paging.
# The default value for the 'per_page' API parameter used in 'GET' functions that support paging.
[int] $PerPage

# Simple parameterless constructor
Expand Down
6 changes: 3 additions & 3 deletions src/classes/public/Context/GitHubContext.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@
# The default value for the Owner parameter.
[string] $Owner

# The default value for the Repo parameter.
[string] $Repo
# The default value for the Repository parameter.
[string] $Repository

# The default value for the HTTP protocol version.
[string] $HttpVersion

# The default value for the 'per_page' API parameter used in 'Get' functions that support paging.
# The default value for the 'per_page' API parameter used in 'GET' functions that support paging.
[int] $PerPage

# Simple parameterless constructor
Expand Down
67 changes: 26 additions & 41 deletions src/functions/private/Actions/Get-GitHubWorkflowRunByRepo.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@
`created`, `event`, `head_sha`, `status`.
.EXAMPLE
Get-GitHubWorkflowRunByRepo -Owner 'owner' -Repo 'repo'
Get-GitHubWorkflowRunByRepo -Owner 'owner' -Repository 'repo'
Lists all workflow runs for a repository.
.EXAMPLE
Get-GitHubWorkflowRunByRepo -Owner 'owner' -Repo 'repo' -Actor 'octocat' -Branch 'main' -Event 'push' -Status 'success'
Get-GitHubWorkflowRunByRepo -Owner 'owner' -Repository 'repo' -Actor 'octocat' -Branch 'main' -Event 'push' -Status 'success'
Lists all workflow runs for a repository with the specified actor, branch, event, and status.
.NOTES
[List workflow runs for a repository](https://docs.github.com/rest/actions/workflow-runs?apiVersion=2022-11-28#list-workflow-runs-for-a-repository)
#>
[CmdletBinding(DefaultParameterSetName = 'Repo')]
[CmdletBinding()]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains a long link.')]
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidAssignmentToAutomaticVariable', 'Event',
Justification = 'A parameter that is used in the api call.')]
Expand All @@ -35,7 +35,7 @@

# The name of the repository. The name is not case sensitive.
[Parameter(Mandatory)]
[string] $Repo,
[string] $Repository,

# Returns someone's workflow runs. Use the login for the user who created the push associated with the check suite or workflow run.
[Parameter()]
Expand Down Expand Up @@ -82,53 +82,38 @@
[int] $PerPage,

# 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)
[Parameter(Mandatory)]
[object] $Context
)

begin {
$stackPath = Get-PSCallStackPath
Write-Debug "[$stackPath] - Start"
$Context = Resolve-GitHubContext -Context $Context
Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT
if ([string]::IsNullOrEmpty($Owner)) {
$Owner = $Context.Owner
}

process {
$body = @{
actor = $Actor
branch = $Branch
event = $Event
status = $Status
created = $Created
exclude_pull_requests = $ExcludePullRequests
check_suite_id = $CheckSuiteID
head_sha = $HeadSHA
per_page = $PerPage
}
Write-Debug "Owner: [$Owner]"

if ([string]::IsNullOrEmpty($Repo)) {
$Repo = $Context.Repo
$inputObject = @{
Method = 'GET'
APIEndpoint = "/repos/$Owner/$Repository/actions/runs"
Body = $body
Context = $Context
}
Write-Debug "Repo: [$Repo]"
}

process {
try {
$body = @{
actor = $Actor
branch = $Branch
event = $Event
status = $Status
created = $Created
exclude_pull_requests = $ExcludePullRequests
check_suite_id = $CheckSuiteID
head_sha = $HeadSHA
per_page = $PerPage
}

$inputObject = @{
Context = $Context
APIEndpoint = "/repos/$Owner/$Repo/actions/runs"
Method = 'GET'
Body = $body
}

Invoke-GitHubAPI @inputObject | ForEach-Object {
Write-Output $_.Response.workflow_runs
}
} catch {
throw $_
Invoke-GitHubAPI @inputObject | ForEach-Object {
Write-Output $_.Response.workflow_runs
}
}

Expand Down
Loading

0 comments on commit e01341e

Please sign in to comment.