diff --git a/examples/Organizations/Organization.ps1 b/examples/Organizations/Organization.ps1 new file mode 100644 index 000000000..4e4fcfbe3 --- /dev/null +++ b/examples/Organizations/Organization.ps1 @@ -0,0 +1,41 @@ +$orgParam = @{ + Enterprise = 'msx' + Name = 'Marius-Test7' + Owner = 'GitHub-Automation' + BillingEmail = 'post@msx.no' +} +$org = New-GitHubOrganization @orgParam + +$installAppParam = @{ + Enterprise = 'msx' + Organization = $org.login + ClientID = (Get-GitHubContext).ClientID + RepositorySelection = 'all' +} +Install-GitHubApp @installAppParam + +$updateOrgParam = @{ + Name = $org.login + Description = 'Test organization created by PowerShell script' + Location = 'Oslo, Norway' + BillingEmail = 'post@msx.no' + Company = 'MSX AS' + Email = 'info@msx.no' + Blog = 'https://msx.no/blog' + TwitterUsername = 'msx_no' + HasOrganizationProjects = $true + DefaultRepositoryPermission = 'read' + MembersCanCreateRepositories = $true + MembersCanCreatePublicRepositories = $true + MembersCanCreatePrivateRepositories = $true + MembersCanCreateInternalRepositories = $true + MembersCanCreatePages = $true + MembersCanCreatePublicPages = $true + MembersCanCreatePrivatePages = $true + MembersCanForkPrivateRepositories = $true + WebCommitSignoffRequired = $false + SecretScanningPushProtectionEnabledForNewRepositories = $true + SecretScanningPushProtectionCustomLinkEnabled = $false + SecretScanningPushProtectionCustomLink = '' +} +Update-GitHubOrganization @updateOrgParam diff --git a/src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1 b/src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1 index 48528dba2..a99927110 100644 --- a/src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1 +++ b/src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1 @@ -225,7 +225,7 @@ $this.MembersCanInviteCollaborators = $Object.members_can_invite_collaborators $this.MembersCanCreatePages = $Object.members_can_create_pages $this.MembersCanForkPrivateRepositories = $Object.members_can_fork_private_repositories ?? $Object.membersCanForkPrivateRepositories - $this.RequireWebCommitSignoff = $Object.web_commit_signoff_required ?? $Object.requiresTwoFactorAuthentication + $this.RequireWebCommitSignoff = $Object.web_commit_signoff_required ?? $Object.webCommitSignoffRequired $this.DeployKeysEnabledForRepositories = $Object.deploy_keys_enabled_for_repositories $this.MembersCanCreatePublicPages = $Object.members_can_create_public_pages $this.MembersCanCreatePrivatePages = $Object.members_can_create_private_pages diff --git a/src/functions/private/Apps/GitHub Apps/Install-GitHubAppOnEnterpriseOrganization.ps1 b/src/functions/private/Apps/GitHub Apps/Install-GitHubAppOnEnterpriseOrganization.ps1 index b66c98e66..d8d847476 100644 --- a/src/functions/private/Apps/GitHub Apps/Install-GitHubAppOnEnterpriseOrganization.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Install-GitHubAppOnEnterpriseOrganization.ps1 @@ -11,6 +11,7 @@ .EXAMPLE Install-GitHubAppOnEnterpriseOrganization -Enterprise 'msx' -Organization 'org' -ClientID '123456' #> + [OutputType([GitHubAppInstallation])] [CmdletBinding()] param( # The enterprise slug or ID. @@ -29,7 +30,7 @@ # - all - all repositories that the authenticated GitHub App installation can access. # - selected - select specific repositories. [Parameter(Mandatory)] - [ValidateSet('all', 'selected')] + [ValidateSet('All', 'Selected')] [string] $RepositorySelection, # The names of the repositories to which the installation will be granted access. @@ -49,6 +50,9 @@ } process { + if ($RepositorySelection) { + $RepositorySelection = $RepositorySelection.ToLower() + } $body = @{ client_id = $ClientID repository_selection = $RepositorySelection @@ -64,7 +68,7 @@ } Invoke-GitHubAPI @inputObject | ForEach-Object { - Write-Output $_.Response + [GitHubAppInstallation]::new($_.Response) } } diff --git a/src/functions/public/API/Invoke-GitHubAPI.ps1 b/src/functions/public/API/Invoke-GitHubAPI.ps1 index 9261893b5..2c4d9279b 100644 --- a/src/functions/public/API/Invoke-GitHubAPI.ps1 +++ b/src/functions/public/API/Invoke-GitHubAPI.ps1 @@ -315,6 +315,10 @@ function Invoke-GitHubAPI { } $exception = @" + +---------------------------------- +Context: +$($Context | Format-List | Out-String) ---------------------------------- Request: $([pscustomobject]$APICall | Select-Object -ExcludeProperty Body, Headers | Format-List | Out-String) diff --git a/src/functions/public/Organization/New-GitHubOrganization.ps1 b/src/functions/public/Organization/New-GitHubOrganization.ps1 new file mode 100644 index 000000000..7279e9358 --- /dev/null +++ b/src/functions/public/Organization/New-GitHubOrganization.ps1 @@ -0,0 +1,88 @@ +function New-GitHubOrganization { + <# + .SYNOPSIS + Creates a new GitHub organization within a specified enterprise. + + .DESCRIPTION + This function creates a new GitHub organization within the specified enterprise. + + .EXAMPLE + New-GitHubOrganization -Enterprise 'my-enterprise' -Name 'my-org' -Owner 'user1' -BillingEmail 'billing@example.com' + + .OUTPUTS + GitHubOrganization + + .LINK + https://psmodule.io/GitHub/Functions/Organization/New-GitHubOrganization/ + #> + [OutputType([GitHubOrganization])] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] + param( + # The name of the enterprise to create the organization in. + [Parameter()] + [string]$Enterprise, + + # The name of the organization to create. + [Parameter(Mandatory)] + [string]$Name, + + # The owners of the organization. This should be a list of GitHub usernames. + [Parameter(Mandatory)] + [string[]]$Owner, + + # The billing email for the organization. + [Parameter()] + [string]$BillingEmail, + + # 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 + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $Context = Resolve-GitHubContext -Context $Context + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + } + + process { + $enterpriseObject = Get-GitHubEnterprise -Name $Enterprise -Context $Context + Write-Verbose "Creating organization in enterprise: $($enterpriseObject.Name)" + $graphQLFields = ([GitHubOrganization]::PropertyToGraphQLMap).Values + + $inputParams = @{ + adminLogins = $Owner + billingEmail = $BillingEmail + enterpriseId = $enterpriseObject.NodeID + login = $Name + profileName = $Name + } + + $updateGraphQLInputs = @{ + Query = @" +mutation(`$input:CreateEnterpriseOrganizationInput!) { + createEnterpriseOrganization(input:`$input) { + organization { + $graphQLFields + } + } +} +"@ + Variables = @{ + input = $inputParams + } + Context = $Context + } + if ($PSCmdlet.ShouldProcess("Creating organization '$Name' in enterprise '$Enterprise'")) { + $orgresult = Invoke-GitHubGraphQLQuery @updateGraphQLInputs + [GitHubOrganization]::new($orgresult.createEnterpriseOrganization.organization, $Context) + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} + diff --git a/src/functions/public/Organization/Remove-GitHubOrganization.ps1 b/src/functions/public/Organization/Remove-GitHubOrganization.ps1 index 0d9f3c310..8f31b57c3 100644 --- a/src/functions/public/Organization/Remove-GitHubOrganization.ps1 +++ b/src/functions/public/Organization/Remove-GitHubOrganization.ps1 @@ -24,16 +24,16 @@ https://psmodule.io/GitHub/Functions/Organization/Remove-GitHubOrganization #> [OutputType([void])] - [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + [CmdletBinding(DefaultParameterSetName = 'Remove an organization', SupportsShouldProcess, ConfirmImpact = 'High')] param( # The organization name. The name is not case sensitive. - [Parameter( - Mandatory, - ValueFromPipeline, - ValueFromPipelineByPropertyName - )] + [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [string] $Name, + # The input object to process. Can be a single or an array of GitHubOrganization objects. + [Parameter(Mandatory, ParameterSetName = 'ArrayInput', ValueFromPipeline)] + [GitHubOrganization[]] $InputObject, + # 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()] @@ -48,14 +48,28 @@ } process { - $inputObject = @{ - Method = 'DELETE' - APIEndpoint = "/orgs/$Name" - Context = $Context - } + switch ($PSCmdlet.ParameterSetName) { + 'ArrayInput' { + foreach ($item in $InputObject) { + $params = @{ + Name = $item.Name + Context = $Context + } + Remove-GitHubOrganization @params + } + break + } + default { + $apiParams = @{ + Method = 'DELETE' + APIEndpoint = "/orgs/$Name" + Context = $Context + } - if ($PSCmdlet.ShouldProcess("organization [$Name]", 'DELETE')) { - $null = Invoke-GitHubAPI @inputObject + if ($PSCmdlet.ShouldProcess("organization [$Name]", 'Delete')) { + $null = Invoke-GitHubAPI @apiParams + } + } } } @@ -63,5 +77,3 @@ Write-Debug "[$stackPath] - End" } } - -#SkipTest:FunctionTest:Will add a test for this function in a future PR diff --git a/tests/Organizations.Tests.ps1 b/tests/Organizations.Tests.ps1 index 4136b6b53..7d1740e5c 100644 --- a/tests/Organizations.Tests.ps1 +++ b/tests/Organizations.Tests.ps1 @@ -20,7 +20,9 @@ param() BeforeAll { - # DEFAULTS ACCROSS ALL TESTS + $testName = 'MsxOrgTests' + $os = $env:RUNNER_OS + $number = Get-Random } Describe 'Organizations' { @@ -32,25 +34,21 @@ Describe 'Organizations' { LogGroup 'Context' { Write-Host ($context | Select-Object * | Out-String) } + $orgPrefix = "$testName-$os-" + $orgName = "$orgPrefix$number" + + if ($AuthType -eq 'APP') { + $installationContext = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent + LogGroup 'Context - Installation' { + Write-Host ($installationContext | Select-Object * | Out-String) + } + } } AfterAll { Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent Write-Host ('-' * 60) } - # Tests for APP goes here - if ($AuthType -eq 'APP') { - It 'Connect-GitHubApp - Connects as a GitHub App to ' { - $context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent - LogGroup 'Context' { - Write-Host ($context | Select-Object * | Out-String) - } - } - } - - # Tests for runners goes here - if ($Type -eq 'GitHub Actions') {} - It "Get-GitHubOrganization - Gets a specific organization 'PSModule'" { $organization = Get-GitHubOrganization -Name 'PSModule' LogGroup 'Organization' { @@ -58,6 +56,7 @@ Describe 'Organizations' { } $organization | Should -Not -BeNullOrEmpty } + It "Get-GitHubOrganization - List public organizations for the user 'psmodule-user'" { $organizations = Get-GitHubOrganization -Username 'psmodule-user' LogGroup 'Organization' { @@ -65,6 +64,7 @@ Describe 'Organizations' { } $organizations | Should -Not -BeNullOrEmpty } + It 'Get-GitHubOrganizationMember - Gets the members of a specific organization' -Skip:($OwnerType -in ('user', 'enterprise')) { $members = Get-GitHubOrganizationMember -Organization $owner LogGroup 'Members' { @@ -73,29 +73,78 @@ Describe 'Organizations' { $members | Should -Not -BeNullOrEmpty } - # Tests for IAT UAT and PAT goes here - It 'Get-GitHubOrganization - Gets the organizations for the authenticated user' -Skip:($OwnerType -notin ('user')) { + It 'Get-GitHubOrganization - Gets the organizations for the authenticated user' -Skip:($Type -eq 'GitHub Actions') { + $orgs = Get-GitHubOrganization | Where-Object { $_.Name -like "$orgPrefix*" } | Out-String + LogGroup 'Organizations' { + $orgs | Format-Table -AutoSize | Out-String + } { Get-GitHubOrganization } | Should -Not -Throw } - if ($OwnerType -eq 'organization' -and $Type -ne 'GitHub Actions') { - It 'Update-GitHubOrganization - Sets the organization configuration' { - { Update-GitHubOrganization -Name $owner -Company 'ABC' } | Should -Not -Throw - { - $email = (New-Guid).Guid + '@psmodule.io' - Update-GitHubOrganization -Name $owner -BillingEmail $email - } | Should -Not -Throw - { - $email = (New-Guid).Guid + '@psmodule.io' - Update-GitHubOrganization -Name $owner -Email $email - } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -TwitterUsername 'PSModule' } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -Location 'USA' } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -Description 'Test Organization' } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -DefaultRepositoryPermission read } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -MembersCanCreateRepositories $true } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -Website 'https://psmodule.io' } | Should -Not -Throw + It 'Update-GitHubOrganization - Sets the organization configuration' -Skip:($OwnerType -ne 'organization' -or $Type -eq 'GitHub Actions') { + { Update-GitHubOrganization -Name $owner -Company 'ABC' } | Should -Not -Throw + { + $email = (New-Guid).Guid + '@psmodule.io' + Update-GitHubOrganization -Name $owner -BillingEmail $email + } | Should -Not -Throw + { + $email = (New-Guid).Guid + '@psmodule.io' + Update-GitHubOrganization -Name $owner -Email $email + } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -TwitterUsername 'PSModule' } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -Location 'USA' } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -Description 'Test Organization' } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -DefaultRepositoryPermission read } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -MembersCanCreateRepositories $true } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -Website 'https://psmodule.io' } | Should -Not -Throw + } + + It 'New-GitHubOrganization - Creates a new organization' -Skip:($OwnerType -ne 'enterprise') { + $orgParam = @{ + Enterprise = 'msx' + Name = $orgName + Owner = 'MariusStorhaug' + BillingEmail = 'post@msx.no' + } + LogGroup 'Organization' { + $org = New-GitHubOrganization @orgParam + Write-Host ($org | Select-Object * | Out-String) + } + } + + It 'Update-GitHubOrganization - Updates the organization location using enterprise installation' -Skip:($OwnerType -ne 'enterprise') { + { Update-GitHubOrganization -Name $orgName -Location 'New Location' } | Should -Throw + } + + It 'Remove-GitHubOrganization - Removes an organization using enterprise installation' -Skip:($OwnerType -ne 'enterprise') { + { Remove-GitHubOrganization -Name $orgName -Confirm:$false } | Should -Throw + } + + It 'Install-GitHubApp - Installs a GitHub App to an organization' -Skip:($OwnerType -ne 'enterprise') { + $installation = Install-GitHubApp -Enterprise $owner -Organization $orgName -ClientID $installationContext.ClientID -RepositorySelection 'all' + LogGroup 'Installed App' { + Write-Host ($installation | Select-Object * | Out-String) + } + $installation | Should -Not -BeNullOrEmpty + $installation | Should -BeOfType 'GitHubAppInstallation' + } + + It 'Connect-GitHubApp - Connects as a GitHub App to the organization' -Skip:($OwnerType -ne 'enterprise') { + $orgContext = Connect-GitHubApp -Organization $orgName -Context $context -PassThru -Silent + LogGroup 'Context' { + Write-Host ($orgContext | Select-Object * | Out-String) } + $orgContext | Should -Not -BeNullOrEmpty + } + + It 'Update-GitHubOrganization - Updates the organization location using organization installation' -Skip:($OwnerType -ne 'enterprise') { + $orgContext = Connect-GitHubApp -Organization $orgName -Context $context -PassThru -Silent + Update-GitHubOrganization -Name $orgName -Location 'New Location' -Context $orgContext + } + + It 'Remove-GitHubOrganization - Removes an organization using organization installation' -Skip:($OwnerType -ne 'enterprise') { + $orgContext = Connect-GitHubApp -Organization $orgName -Context $context -PassThru -Silent + Remove-GitHubOrganization -Name $orgName -Confirm:$false -Context $orgContext } Context 'Invitations' -Skip:($Owner -notin 'psmodule-test-org', 'psmodule-test-org2') {