utilities/pipelines/sharedScripts/teamLinter/Invoke-AvmGitHubTeamLinter.ps1 (349 lines of code) (raw):

<# .SYNOPSIS Compares Azure Verified Modules Module Indexes with existing GitHub Teams configurations. Issues with GitHub teams can be converted to Github Issues or verbose output. .DESCRIPTION Compares Azure Verified Modules Module Indexes with existing GitHub Teams configurations. Issues with GitHub teams can be converted to Github Issues or verbose output. .PARAMETER ModuleIndex Required. Modules Index to use as source, allowed strings are: 'Bicep-Resource', 'Bicep-Pattern', 'Terraform-Resource', 'Terraform-Pattern' .PARAMETER TeamFilter Required. Teams to filter on, allowed strings are: 'AllTeams', 'AllResource', 'AllPattern', 'AllBicep', 'AllBicepResource', 'BicepResourceOwners', 'BicepResourceContributors', 'AllBicepPattern', 'BicepPatternOwners', 'BicepPatternContributors', 'AllTerraform', 'AllTerraformResource', 'TerraformResourceOwners', 'TerraformResourceContributors', 'AllTerraformPattern', 'TerraformPatternOwners', 'TerraformPatternContributors' .PARAMETER ValidateBicepParentConfiguration Optional. Validate if Parent Team is configured for Owners Team .PARAMETER ValidateTerraformTeamsPermissons Optional. Validate if correct permissions are configured for Terraform Teams .PARAMETER CreateIssues Optional. Create GitHub Issues for unmatched teams .EXAMPLE Invoke-AvmGitHubTeamLinter -ModuleIndex Bicep-Resource -TeamFilter AllBicepResource -ValidateBicepParentConfiguration -Verbose -CreateIssues Compares all bicep resource modules with GitHub Teams and validates if Parent Team is configured for Owners Team. Verbose output is displayed and GitHub Issues are created for unmatched teams. .EXAMPLE Invoke-AvmGitHubTeamLinter -ModuleIndex Terraform-Resource -TeamFilter AllTerraformResource -ValidateTerraformTeamsPermissons -Verbose -CreateIssues Compares all terraform resource modules with GitHub Teams and validates if Teams have correct permissions on repository. Verbose output is displayed and GitHub Issues are created for unmatched teams. .EXAMPLE Invoke-AvmGitHubTeamLinter -ModuleIndex Bicep-Pattern -TeamFilter AllBicepPattern -ValidateBicepParentConfiguration -Verbose Compares all bicep pattern modules with GitHub Teams and validates if Parent Team is configured for Owners Team. Verbose output is displayed, GitHub Issues are not created for unmatched teams. #> Function Invoke-AvmGitHubTeamLinter { [CmdletBinding()] param ( [Parameter(Mandatory)] [ValidateSet('Bicep-Resource', 'Bicep-Pattern', 'Terraform-Resource', 'Terraform-Pattern')] [string]$ModuleIndex, [Parameter(Mandatory)] [ValidateSet('AllTeams', 'AllResource', 'AllPattern', 'AllBicep', 'AllBicepResource', 'BicepResourceOwners', 'BicepResourceContributors', 'AllBicepPattern', 'BicepPatternOwners', 'BicepPatternContributors', 'AllTerraform', 'AllTerraformResource', 'TerraformResourceOwners', 'TerraformResourceContributors', 'AllTerraformPattern', 'TerraformPatternOwners', 'TerraformPatternContributors' )] [string]$TeamFilter, [Parameter(Mandatory = $false)] [switch]$ValidateBicepParentConfiguration, [Parameter(Mandatory = $false)] [switch]$ValidateTerraformTeamsPermissons, [Parameter(Mandatory = $false)] [switch]$validateTerraformAdminPermissions, [Parameter(Mandatory = $false)] [switch]$CreateIssues, [Parameter(Mandatory = $false)] [array]$TerraformAdminTeamList = @( 'terraform-avm', 'avm-core-team-technical-terraform' ) ) # Load used functions . (Join-Path $PSScriptRoot 'Get-AvmCsvData.ps1') . (Join-Path $PSScriptRoot 'Get-AvmGitHubTeamsData.ps1') . (Join-Path $PSScriptRoot 'Set-AvmGitHubTeamsIssue.ps1') . (Join-Path $PSScriptRoot 'Test-AvmGitHubTeamPermission.ps1') . (Join-Path $PSScriptRoot 'Find-AvmGitHubTeamOwner.ps1') . (Join-Path $PSScriptRoot 'Close-ResolvedGithubIssue.ps1') if ($TeamFilter -like '*All*') { $validateAll = $true } if ($TeamFilter -like '*Owners*') { $validateOwnerTeams = $true } if ($TeamFilter -like '*Contributors*') { $validateContributorTeams = $true } # Retrieve the CSV file $sourceData = Get-AvmCsvData -ModuleIndex $ModuleIndex $gitHubTeamsData = Get-AvmGitHubTeamsData -TeamFilter $TeamFilter $unmatchedTeams = @() # Iterate through each object in $csv foreach ($module in $sourceData) { # Assume no match is found initially $matchFound = $false if ($validateOwnerTeams -Or $validateAll) { # Check each object in $ghTeam for a match foreach ($ghTeam in $gitHubTeamsData) { if ($module.ModuleOwnersGHTeam -eq $ghTeam.name) { # If a match is found, set flag to true and break out of the loop $matchFound = $true # Validate Module Owner is part of team. $testOwner = Find-AvmGitHubTeamOwner -Organization Azure -TeamName $module.ModuleOwnersGHTeam -OwnerGitHubHandle $module.PrimaryModuleOwnerGHHandle if ($testOwner -match "Success") { Write-Verbose "Good News! Team: [$($ghTeam.name)] is configured with the expected owners: [$($module.PrimaryModuleOwnerGHHandle)]" Write-Verbose "Checking if an issue exists for the team: [$($ghTeam.name)]..." Close-ResolvedGithubIssue -title "[GitHub Team Issue] ``$($ghTeam.name)``" } else { Write-Verbose "Uh-oh no incorrect owner configured for [$($ghTeam.name)]" # Create a custom object for the unmatched team $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleContributorsGHTeam Validation = "Owner Not Assigned in Team." Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = $ghTeam.name Resolution = "Please assign the correct owners permissions to the team: [$($ghTeam.name)]. This can be found in [SNFR20](https://azure.github.io/Azure-Verified-Modules/spec/SNFR20)." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam break } # Validate if Parent Team is configured for Owners Team if ($ValidateBicepParentConfiguration -and $matchFound) { # Check if Parent Team is configured for Owners Team if (-not $null -eq $ghTeam.parent -and $ValidateBicepParentConfiguration) { Write-Verbose "Found team: $($module.ModuleOwnersGHTeam) with parent: $($ghTeam.parent.name) owned by $($module.PrimaryModuleOwnerDisplayName)" break } else { Write-Verbose "Uh-oh no parent team configured for $($module.ModuleOwnersGHTeam) ($($module.PrimaryModuleOwnerDisplayName))" # Create a custom object for the unmatched team $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleOwnersGHTeam Validation = "No parent team assigned." Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = $ghTeam.name Resolution = "Assign the correct parent team to the team: $($module.ModuleOwnersGHTeam) [here](https://github.com/orgs/Azure/teams/$($module.ModuleContributorsGHTeam)). Parent information can be found in [SNFR20](https://azure.github.io/Azure-Verified-Modules/spec/SNFR20)." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam break } } elseif ($ValidateTerraformTeamsPermissons -and $matchFound) { Write-Verbose "Found team: $($module.ModuleOwnersGHTeam) Checking Permissions configuration" if ($module.ModuleOwnersGHTeam -like "*-tf") { $repoName = "terraform-azurerm-$($module.ModuleName)" $repoConfiguration = Test-AvmGitHubTeamPermission -Organization Azure -TeamName $module.ModuleOwnersGHTeam -RepoName $repoName -ExpectedPermission "Admin" if ($repoConfiguration -match "Success") { Write-Verbose "Good News! Team: [$($module.ModuleOwnersGHTeam)] is configured with the expected permission: [admin] on Repo: [$repoName] " Write-Verbose "Checking if an issue exists for the team: [$($ghTeam.name)]..." Close-ResolvedGithubIssue -title "[GitHub Team Issue] ``$($ghTeam.name)``" } else { Write-Verbose "Uh-oh no correct permissions configured for $($module.ModuleOwnersGHTeam) ($($module.PrimaryModuleOwnerDisplayName))" # Create a custom object for the unmatched team $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleOwnersGHTeam Validation = "No correct permissions assigned." Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = $ghTeam.name Resolution = "Please assign the correct permissions to the team: $($module.ModuleOwnersGHTeam). This can be found in [SNFR20](https://azure.github.io/Azure-Verified-Modules/spec/SNFR20)." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam break } } else { Write-Verbose "Skipping non Terraform module: $($module.ModuleOwnersGHTeam)" break } } elseif ($matchFound) { # Write verbose output without parent check Write-Verbose "Found team: $($module.ModuleOwnersGHTeam) ($($module.PrimaryModuleOwnerDisplayName))" break } } # Check for match with "@Azure/" prefix # Construct the prefixed team name $prefixedTeamName = "@azure/" + $module.ModuleOwnersGHTeam # Check for match with "@Azure/" prefix if ($prefixedTeamName -eq $ghTeam.name) { $matchFound = $true Write-Verbose "Uh-oh team found with '@azure/' prefix for: $($ghTeam.name), Current Owner is $($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleOwnersGHTeam Validation = "@azure/ prefix found." Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = $ghTeam.name Resolution = "Remove the '@azure/' prefix from the team name." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam break } } # If no match was found, output the item from $csv if (-not $matchFound) { Write-Verbose "No team found for: $($module.ModuleOwnersGHTeam), Current Owner is $($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleOwnersGHTeam Validation = "GitHub team not found. " Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = "N/A" Resolution = "Create a new team with the name $($module.ModuleOwnersGHTeam) [here](https://github.com/orgs/Azure/new-team)." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam } } if ($validateContributorTeams -Or $validateAll) { # Check each object in $ghTeam for a match foreach ($ghTeam in $gitHubTeamsData) { if ($module.ModuleContributorsGHTeam -eq $ghTeam.name) { # If a match is found, set flag to true and break out of the loop $matchFound = $true # Validate if Parent Team is configured for Contributors Team if ($ValidateBicepParentConfiguration -and $matchFound) { # Check if Parent Team is configured for Contributors Team if (-not $null -eq $ghTeam.parent -and $ValidateBicepParentConfiguration) { Write-Verbose "Found team: $($module.ModuleContributorsGHTeam) with parent: $($ghTeam.parent.name) owned by $($module.PrimaryModuleOwnerDisplayName)" break } else { Write-Verbose "Uh-oh no parent team configured for $($module.ModuleContributorsGHTeam) ($($module.PrimaryModuleOwnerDisplayName))" # Create a custom object for the unmatched team $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleContributorsGHTeam Validation = "No parent team assigned." Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = $ghTeam.name Resolution = "Assign the correct parent team to the team: $($module.ModuleContributorsGHTeam) [here](https://github.com/orgs/Azure/teams/$($module.ModuleContributorsGHTeam)). Parent information can be found in [SNFR20](https://azure.github.io/Azure-Verified-Modules/spec/SNFR20)." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam } } elseif ($ValidateTerraformTeamsPermissons -and $matchFound) { Write-Verbose "Found team: $($module.ModuleContributorsGHTeam) Checking Permissions configuration" if ($module.ModuleContributorsGHTeam -like "*-tf") { $repoName = "terraform-azurerm-$($module.ModuleName)" $repoConfiguration = Test-AvmGitHubTeamPermission -Organization Azure -TeamName $module.ModuleContributorsGHTeam -RepoName $repoName -ExpectedPermission "Write" if ($repoConfiguration -match "Success") { Write-Verbose "Good News! Team: [$($module.ModuleOwnersGHTeam)] is configured with the expected permission: [write] on Repo: [$repoName] " Write-Verbose "Checking if an issue exists for the team: [$($ghTeam.name)]..." Close-ResolvedGithubIssue -title "[GitHub Team Issue] ``$($ghTeam.name)``" } else { Write-Verbose "Uh-oh no correct permissions configured for $($module.ModuleContributorsGHTeam) ($($module.PrimaryModuleOwnerDisplayName))" # Create a custom object for the unmatched team $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleContributorsGHTeam Validation = "No correct permissions assigned." Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = $ghTeam.name Resolution = "Please assign the correct permissions to the team: $($module.ModuleContributorsGHTeam). This can be found in [SNFR20](https://azure.github.io/Azure-Verified-Modules/spec/SNFR20)." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam break } } else { Write-Verbose "Skipping non Terraform module: $($module.ModuleContributorsGHTeam)" break } } elseif ($matchFound) { Write-Verbose "Found team: $($module.ModuleContributorsGHTeam) ($($module.PrimaryModuleOwnerDisplayName))" break } } # Check for match with "@Azure/" prefix # Construct the prefixed team name $prefixedTeamName = "@azure/" + $module.ModuleContributorsGHTeam # Check for match with "@Azure/" prefix if ($prefixedTeamName -eq $ghTeam.name) { $matchFound = $true Write-Verbose "Uh-oh team found with '@azure/' prefix for: $($ghTeam.name), Current Owner is $($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleContributorsGHTeam Validation = "@azure/ prefix found." Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = $ghTeam.name Resolution = "Remove the '@azure/' prefix from the team name." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam break } } # If no match was found, output the item from $csv if (-not $matchFound) { Write-Verbose "No team found for: $($module.ModuleContributorsGHTeam), Current Owner is $($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" if (-not $matchFound) { Write-Verbose "No team found for: $($module.ModuleContributorsGHTeam), Current Owner is $($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleContributorsGHTeam Validation = "GitHub team not found. " Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = "N/A" Resolution = "Create a new team with the name $($module.ModuleContributorsGHTeam) [here](https://github.com/orgs/Azure/new-team)." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam } } } if ($validateTerraformAdminPermissions -Or $validateAll) { foreach ($tfAdminteam in $TerraformAdminTeamList) { if ($module.ModuleOwnersGHTeam -like "*-tf") { $repoName = "terraform-azurerm-$($module.ModuleName)" $teamTest = Test-AvmGitHubTeamPermission -Organization Azure -TeamName $tfAdminteam -RepoName $repoName -ExpectedPermission "Admin" if ($teamTest -match "Success") { Write-Verbose "Good News! Team: [$tfAdminteam] is configured with the expected permission: [admin] on Repo: [$repoName] " Write-Verbose "Checking if an issue exists for the team: [$($ghTeam.name)]..." Close-ResolvedGithubIssue -title "[GitHub Team Issue] ``$($ghTeam.name)``" } else { Write-Verbose "Uh-oh no correct permissions configured for [$tfAdminteam]" # Create a custom object for the unmatched team $unmatchedTeam = [PSCustomObject]@{ TeamName = $module.ModuleContributorsGHTeam Validation = "No correct permissions assigned." Owner = "$($module.PrimaryModuleOwnerGHHandle) ($($module.PrimaryModuleOwnerDisplayName))" GitHubTeamName = $tfAdminteam Resolution = "Please assign the correct permissions to the team: [$tfAdminteam]. This can be found in [SNFR20](https://azure.github.io/Azure-Verified-Modules/spec/SNFR20)." } # Add the custom object to the array $unmatchedTeams += $unmatchedTeam break } } } } } # Check if $unmatchedTeams is empty if ($unmatchedTeams.Count -eq 0) { Write-Output "No unmatched teams found." $LASTEXITCODE = 0 } else { $jsonOutput = $unmatchedTeams | ConvertTo-Json -Depth 3 Write-Warning "Unmatched teams found:" Write-Warning $jsonOutput | Out-String if ($CreateIssues) { foreach ($unmatchedTeam in $unmatchedTeams) { Set-AvmGitHubTeamsIssue -TeamName $unmatchedTeam.TeamName -Owner $unmatchedTeam.Owner -ValidationError $unmatchedTeam.Validation -ResolutionInfo $unmatchedTeam.Resolution -CreateIssues:$true -Verbose } } else { foreach ($unmatchedTeam in $unmatchedTeams) { Set-AvmGitHubTeamsIssue -TeamName $unmatchedTeam.TeamName -Owner $unmatchedTeam.Owner -ValidationError $unmatchedTeam.Validation -ResolutionInfo $unmatchedTeam.Resolution -CreateIssues:$false -Verbose } } #Output in JSON for follow on tasks if (-not $CreateIssues) { Write-Output "::warning file=Invoke-AvmGitHubTeamLinter.ps1::Unmatched teams found, Review step warnings for details." Write-Output "## :warning: Unmatched teams found, Review step warnings for details." >> $env.GITHUB_STEP_SUMMARY -Verbose $LASTEXITCODE = 1 } else { Write-Output "Unmatched teams found, Github issues Created." return $jsonOutput } } }