Skip to content

Instantly share code, notes, and snippets.

@bondarenkod
Last active April 30, 2024 15:25
Show Gist options
  • Save bondarenkod/46a8ef534dc5234fea27b0a1ec754a39 to your computer and use it in GitHub Desktop.
Save bondarenkod/46a8ef534dc5234fea27b0a1ec754a39 to your computer and use it in GitHub Desktop.
Azure DevOps Git Submodules Authentication, VSTS git submodules
<#
.SYNOPSIS
Invokes authentication for Git submodules within a repository.
.DESCRIPTION
The Invoke-GitSubmodulesAuthentication function is used to authenticate Git submodules within a repository. It searches for .gitmodules files within the specified repository path and restores the submodules using the provided Personal Access Token (PAT).
.PARAMETER pat
The Personal Access Token (PAT) used for authentication.
.PARAMETER gitModulesSearchingDepth
The depth to search for .gitmodules files within the repository. Default value is 5.
.PARAMETER recursionLimit
The recursion limit for restoring submodules. Default value is 7.
.PARAMETER repoPath
The path to the repository. If not provided, the current location is used.
.EXAMPLE
Invoke-GitSubmodulesAuthentication -pat "your_personal_access_token"
This example invokes authentication for Git submodules within the current repository using the provided Personal Access Token.
#>
function Invoke-GitSubmodulesAuthentication {
param(
[string]$pat,
[int]$gitModulesSearchingDepth = 5,
[int]$recursionLimit = 7,
[string]$repoPath = (Get-Location)
)
# check pat
if ([string]::IsNullOrEmpty($pat)) {
Write-Error "PAT token is not provided"
return
}
Invoke-ValidateAzurePATToken -patToken $pat
# Get all top-level directories within the specified path
$topLevelDirs = Get-ChildItem -Path $repoPath -Directory
# Iterate over each directory to check for .gitmodules file
foreach ($dir in $topLevelDirs) {
$gitmodulesPath = Join-Path -Path $dir.FullName -ChildPath ".gitmodules"
# Check if the .gitmodules file exists in the directory
if (Test-Path -Path $gitmodulesPath) {
# Output the directory path that contains a .gitmodules file
Write-Output "found .gitmodules file in $($dir.FullName)"
RestoreSubmodules $pat $dir.FullName $recursionLimit $false
}
}
}
function RestoreSubmodules {
param (
[string]$pat,
[string]$repoDirectory,
[int]$recursionLimit,
[bool]$isRecursion = $true
)
Write-Host "This function does not work with nested submodules in submodules, should be updated when needed!"
Write-Warning "This function does not work with nested submodules in submodules, should be updated when needed!"
try {
Push-Location $repoDirectory
# check if the directory exists
if (-not (Test-Path $repoDirectory)) {
Write-Error "The directory '$repoDirectory' does not exist"
return
}
Write-Host "Restoring submodules in '$repoDirectory'"
$gitSms = Get-GitSubmodules -repoPath $repoDirectory
Write-Host "Found submodules in $gitSms"
# check if we have submodules
if ($gitSms.Count -eq 0) {
if ($isRecursion -eq $false) {
# exit with error
Write-Error "No submodules found in '$repoDirectory'"
}
Write-Host "No submodules found in '$repoDirectory'"
}
# verify submodules
foreach ($gitsm in $gitSms) {
Write-Host "found submodule at '$($gitsm.RelativePath)'with url: '$($gitsm.Url)'"
# check if .git exists in the submodule, if not - remove the directory and init
$smDir = Join-Path -Path $repoDirectory -ChildPath $($gitsm.RelativePath)
if ($true -eq (Test-Path -Path $smDir)) {
Write-Host "Checking if submodule directory contains any files..."
# get all the top files and folders from the submodule files, in some cases,
# the submodule directory may be empty, the checkout will fail in this case
# this issue can happen at self-hosted agents
$smFiles = Get-ChildItem -Path $smDir -Force
if ($smFiles.Count -le 1) {
# need to remove the submodule directory from the .git/modules directory
$gitModulePath = Join-Path -Path $repoDirectory -ChildPath ".git/modules/$($gitsm.RelativePath)"
if ((Test-Path -Path $gitModulePath)) {
Write-Host "Removing submodule from .git/modules directory: '$smDir'..."
Remove-Item -Path $gitModulePath -Force -Recurse
}
Write-Host "Removing submodule directory '$smDir'..."
Remove-Item -Path $smDir -Force -Recurse
}
}
}
# read gitmodules file
$gitmodulesPath = Join-Path -Path $repoDirectory -ChildPath ".gitmodules"
$content = Get-Content -Path $gitmodulesPath
$replacementMap = @{}
# inject PAT token
foreach ($gitsm in $gitSms) {
# if the URL contains the PAT token, skip it
if ($gitsm.Url -match $pat) {
continue
}
# if the URL contains the @ symbol, this if format #1: https://ORG@dev.azure.com/ORG/PROJECT/_git/REPO
if ($gitsm.Url -match "@") {
# Extract the username from the URL
$user = $gitsm.Url -split '@' | Select-Object -First 1 -Skip 0
$user = $user -replace 'https://', ''
# Replace the first occurrence of username with 'username:PAT'
$newUrl = $gitsm.Url -replace "https://$user@", "https://$($pat)@"
$replacementMap[$gitsm.Url] = $newUrl
}
else {
# if the URL does not contain the @ symbol, this is format #2: https://dev.azure.com/ORG/PROJECT/_git/REPO
$replacementMap[$gitsm.Url] = $gitsm.Url -replace "https://", "https://$($pat)@"
}
}
# print the replacement map
foreach ($key in $replacementMap.Keys) {
Write-Host "Replacing '$key' with '$($replacementMap[$key])'"
$content = $content -replace $key, $replacementMap[$key]
}
# write the content back to the file
Set-Content -Path $gitmodulesPath -Value $content
Write-Host "Setting up git config..."
Write-Host "git config --global credential.interactive false"
git config credential.interactive false
# git config --global credential.interactive false
# git config --global credential.azreposCredentialType oauth
git submodule sync
$gitCommand = "git submodule update --init --depth 1"
Write-Host "Executing command: '$gitCommand'"
Invoke-Expression $gitCommand
# TBD - add recursion here for nested submodules, dont forget to update the warning message and -1 for recursionLimit
}
catch {
<#Do this if a terminating exception happens#>
}
finally {
Pop-Location
}
}
# RestoreSubmodules -pat "token" -repoDirectory "D:\src\repo" -recursionLimit 2
function Invoke-ValidateAzurePATToken {
param (
[string]$patToken,
[string]$organization = $null, # This gets the organization URL from the pipeline environment
[string]$apiVersion = '6.0' # API version, update if necessary
)
if (-not $organization) {
$organization = "${env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}"
}
$organization = $organization.TrimEnd('/')
$apiUrl = "$organization/_apis/projects?api-version=$apiVersion"
Write-Host "Validating PAT token..."
Write-Host "URL: $apiUrl"
$res = Test-VstsPATPatToken -url $apiUrl -personalAccessToken $patToken
if ($res -eq $true) {
Write-Output "PAT token is valid"
}
else {
Write-Error "PAT token is invalid"
}
}
function Test-VstsPATPatToken {
param (
[string]$url,
[string]$personalAccessToken
)
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$($personalAccessToken)"))
try {
$response = Invoke-RestMethod -Uri $url -Headers @{Authorization = ("Basic $base64AuthInfo") } -Method Get -StatusCodeVariable "statusCode"
Write-Host "Response status code: $statusCode"
if ($statusCode -ne 200) {
Write-Host "Failed to validate PAT token. Status code: $statusCode"
return $false
}
else {
Write-Host "Successfully validated PAT token. Status code: $statusCode"
}
Write-Host "Response: $response"
return $true
}
catch {
Write-Host "Failed to validate PAT token. Status code: $($_.Exception.Response.StatusCode.Value__) Error: $($_.Exception.Message)"
return $false
}
}
# $token = "1"
# Invoke-ValidateAzurePATToken -patToken $token -organization "https://ORG.visualstudio.com/"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment