Includes/Download.provider.gitlab.ps1
|
<#
Provider : gitlab (API v4) Dependencies : none (the GitLab API is called directly via Invoke-RestMethod) The token is read from the environment variable named by download.token_env (never stored in the definition). Self-hosted instances via download.gitlab_url (default https://gitlab.com). #> <# .SYNOPSIS Resolve the GitLab release matching the requested version. .DESCRIPTION 'latest' uses the permalink/latest endpoint ; any other value matches the first release whose tag contains it (substring match). TAGNAME is set in the mustache values table. Returns the release object. #> function Get-ReleaseFromGitlab { [CmdletBinding()] [OutputType([object])] Param ( # Resolved package definition [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][object]$pkgdef, # Template values (uppercase keys, mustache compatible) [Parameter(Mandatory = $false)][hashtable]$mustacheValues = @{}, # GitLab base URL (default: gitlab.com) [Parameter(Mandatory = $false)][string]$GitLabBaseUrl = "https://gitlab.com", # GitLab API token [Parameter(Mandatory = $false)][string]$GitLabToken ) Begin { Write-EnterFunction } Process { $projectEncoded = [Uri]::EscapeDataString("$($pkgdef.download.owner)/$($pkgdef.download.repo)") $headers = @{} if ($GitLabToken) { $headers["PRIVATE-TOKEN"] = $GitLabToken } $release = $null if ($pkgdef.version -eq "latest") { $url = "$GitLabBaseUrl/api/v4/projects/$projectEncoded/releases/permalink/latest" $release = Invoke-RestMethod -Uri $url -Headers $headers -Method Get } else { $url = "$GitLabBaseUrl/api/v4/projects/$projectEncoded/releases" $releases = Invoke-RestMethod -Uri $url -Headers $headers -Method Get $release = @($releases | Where-Object { $_.tag_name -like "*$($pkgdef.version)*" }) | Select-Object -First 1 } if ($release) { Write-Verbose "Found release $($release.tag_name) matched requested version $($pkgdef.version)" } else { Write-Fatal "No release found for version $($pkgdef.version)" } if ($mustacheValues) { $mustacheValues.TAGNAME = $release.tag_name } return $release } End { Write-LeaveFunction } } <# .SYNOPSIS Select the asset of a GitLab release matching the ordered pattern list. .DESCRIPTION GitLab stores uploaded files under release.assets.links. The returned asset is normalized to the shape expected downstream : @{ name ; browser_download_url }. #> function Get-AssetFromGitlab { [CmdletBinding()] [OutputType([hashtable])] Param ( # Resolved package definition [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][object]$pkgdef, # Template values (uppercase keys, mustache compatible) [Parameter(Mandatory = $false)][hashtable]$mustacheValues = @{}, # Release found with Get-ReleaseFromGitlab [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][System.Object]$Release ) Begin { Write-EnterFunction } Process { $assets = @($Release.assets.links) if (-not $assets.Count) { Write-Fatal "No assets found for release $($Release.tag_name)" } Write-Verbose "Found $($assets.Count) assets for release $($Release.tag_name)" $match = Select-GenericPackageAsset -Names (@($assets | ForEach-Object { $_.name })) -Assets $assets -AssetSpecs @($pkgdef.download.assets) -mustacheValues $mustacheValues if (-not $match) { Write-Fatal "No asset of release $($Release.tag_name) matches the declared patterns" } $asset = $assets[$match.Index] return @{ name = $asset.name browser_download_url = $asset.direct_asset_url ?? $asset.url } } End { Write-LeaveFunction } } <# .SYNOPSIS Get asset information from a GitLab release without downloading. #> function Get-AssetInfoFromGitlab { [CmdletBinding()] [OutputType([array])] Param ( [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][object]$pkgdef, [Parameter(Mandatory = $false)][string]$CachePath, [Parameter(Mandatory = $false)][hashtable]$mustacheValues = @{}, [Parameter(Mandatory = $false)][string]$GitLabBaseUrl = "https://gitlab.com", [Parameter(Mandatory = $false)][string]$GitLabToken ) Begin { Write-EnterFunction $assets = @() } Process { $release = Get-ReleaseFromGitlab -pkgdef $pkgdef -mustacheValues $mustacheValues -GitLabBaseUrl $GitLabBaseUrl -GitLabToken $GitLabToken $asset = Get-AssetFromGitlab -pkgdef $pkgdef -mustacheValues $mustacheValues -Release $release if ($asset) { $assets += @{ name = $asset.name download_url = $asset.browser_download_url local_path = "$CachePath/$($asset.name)" } } } End { Write-LeaveFunction return $assets } } <# .SYNOPSIS Download an asset from a GitLab release. .DESCRIPTION Resolves the release, selects the asset with the ordered pattern list, downloads it (skipping when already cached) with the auth header for private instances, and verifies the signature-file when declared. #> function Download-AssetFromGitlab { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Download is a more intuitive verb for this function because Get- is too generic.")] [CmdletBinding()] [OutputType([String])] Param ( # Resolved package definition [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][object]$pkgdef, # Destination path (cache directory) [Parameter()][String]$DestinationPath, # Template values (uppercase keys, mustache compatible) [Parameter(Mandatory = $false)][hashtable]$mustacheValues = @{}, # GitLab base URL (default: gitlab.com) [Parameter(Mandatory = $false)][string]$GitLabBaseUrl = "https://gitlab.com", # GitLab API token [Parameter(Mandatory = $false)][string]$GitLabToken ) Begin { Write-EnterFunction } Process { # Resolve DestinationPath: parameter > pkgdef.download.destination > temp path if ([string]::IsNullOrEmpty($DestinationPath)) { if ($pkgdef.download.destination) { $DestinationPath = $pkgdef.download.destination } else { $DestinationPath = [io.path]::GetTempPath() } } $release = Get-ReleaseFromGitlab -pkgdef $pkgdef -mustacheValues $mustacheValues -GitLabBaseUrl $GitLabBaseUrl -GitLabToken $GitLabToken $asset = Get-AssetFromGitlab -pkgdef $pkgdef -mustacheValues $mustacheValues -Release $release if (-not $asset) { return $null } $destFile = "$DestinationPath/$($asset.name)" if (Test-FileExist $destFile) { Write-Verbose "Asset $($asset.name) already exists in $DestinationPath, skipping download" return $destFile } # Inject auth header for private instances $downloadHeaders = @{} if ($GitLabToken) { $downloadHeaders["PRIVATE-TOKEN"] = $GitLabToken } Write-Verbose "Downloading $($asset.name) from $($asset.browser_download_url) to $DestinationPath" $null = Download-File -Url $asset.browser_download_url -To $DestinationPath -Headers $downloadHeaders -PassThru if (-not (Test-FileExist $destFile)) { Write-Fatal "Error downloading asset $($asset.name)" return $null } # Signature-file verification (companion link from the same release) $spec = $null foreach ($a in @($pkgdef.download.assets)) { if ($a.pattern -and $asset.name -match (Render-MustacheField -value $a.pattern -mustacheValues $mustacheValues)) { $spec = $a break } } if ($spec -and $spec.'signature-file') { $sigPattern = Render-MustacheField -value $spec.'signature-file'.pattern -mustacheValues $mustacheValues $sigLink = @($release.assets.links | Where-Object { $_.name -match $sigPattern }) | Select-Object -First 1 if (-not $sigLink) { Write-Fatal "Signature file matching '$sigPattern' not found in release $($release.tag_name)" return $null } $sigUrl = $sigLink.direct_asset_url ?? $sigLink.url $sigFile = Download-File -Url $sigUrl -To $DestinationPath -Headers $downloadHeaders -PassThru if (-not (Test-GenericPackageSignature -AssetPath $destFile -SignaturePath $sigFile -Algorithm $spec.'signature-file'.algorithm)) { return $null } } return $destFile } End { Write-LeaveFunction } } <# .SYNOPSIS Get artifact information from GitLab CI without downloading. #> function Get-AssetInfoFromGitlabArtifact { [CmdletBinding()] [OutputType([array])] Param ( [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][object]$pkgdef, [Parameter(Mandatory = $false)][string]$CachePath, [Parameter(Mandatory = $false)][hashtable]$mustacheValues = @{}, [Parameter(Mandatory = $false)][string]$GitLabBaseUrl = "https://gitlab.com" ) Begin { Write-EnterFunction $assets = @() } Process { $projectEncoded = [Uri]::EscapeDataString("$($pkgdef.download.owner)/$($pkgdef.download.repo)") $ref = $pkgdef.download.artifact.ref $job = $pkgdef.download.artifact.job $artifactPath = $pkgdef.download.artifact.path if (-not $ref) { Write-Fatal "artifact.ref is required in package definition" } if (-not $job) { Write-Fatal "artifact.job is required in package definition" } if ($artifactPath) { $artifactPathEncoded = $artifactPath.TrimStart("/") $download_url = "$GitLabBaseUrl/api/v4/projects/$projectEncoded/jobs/artifacts/$ref/raw/$artifactPathEncoded`?job=$job" $fileName = Split-Path $artifactPath -Leaf } else { $download_url = "$GitLabBaseUrl/api/v4/projects/$projectEncoded/jobs/artifacts/$ref/download?job=$job" $fileName = "$($pkgdef.download.repo)-$($pkgdef.name)-artifacts.zip" } $download_url = Render-MustacheField -value $download_url -mustacheValues $mustacheValues $fileName = Render-MustacheField -value $fileName -mustacheValues $mustacheValues $assets += @{ name = $fileName download_url = $download_url local_path = "$CachePath/$fileName" } } End { Write-LeaveFunction return $assets } } <# .SYNOPSIS Download a CI artifact from GitLab. .DESCRIPTION Downloads an artifact produced by a GitLab CI pipeline job : GET /projects/:id/jobs/artifacts/:ref/download?job=:job_name or, for a specific file inside the artifact : GET /projects/:id/jobs/artifacts/:ref/raw/*artifact_path?job=:job_name #> function Download-ArtifactFromGitlab { [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseApprovedVerbs", "", Justification="Download is a more intuitive verb for this function because Get- is too generic.")] [CmdletBinding()] [OutputType([String])] Param ( # Resolved package definition [Parameter(Mandatory = $true, ValueFromPipeLine = $true)][object]$pkgdef, # Destination path (cache directory) [Parameter()][String]$DestinationPath, # Template values (uppercase keys, mustache compatible) [Parameter(Mandatory = $false)][hashtable]$mustacheValues = @{}, # GitLab base URL (default: gitlab.com) [Parameter(Mandatory = $false)][string]$GitLabBaseUrl = "https://gitlab.com", # GitLab API token [Parameter(Mandatory = $false)][string]$GitLabToken ) Begin { Write-EnterFunction } Process { # Resolve DestinationPath: parameter > pkgdef.download.destination > temp path if ([string]::IsNullOrEmpty($DestinationPath)) { if ($pkgdef.download.destination) { $DestinationPath = $pkgdef.download.destination } else { $DestinationPath = [io.path]::GetTempPath() } } $headers = @{} if ($GitLabToken) { $headers["PRIVATE-TOKEN"] = $GitLabToken } $projectEncoded = [Uri]::EscapeDataString("$($pkgdef.download.owner)/$($pkgdef.download.repo)") $ref = $pkgdef.download.artifact.ref $job = $pkgdef.download.artifact.job $artifactPath = $pkgdef.download.artifact.path if (-not $ref) { Write-Fatal "artifact.ref is required in package definition" } if (-not $job) { Write-Fatal "artifact.job is required in package definition" } if ($artifactPath) { # Download one specific file inside the artifact $artifactPathEncoded = $artifactPath.TrimStart("/") $download_url = "$GitLabBaseUrl/api/v4/projects/$projectEncoded/jobs/artifacts/$ref/raw/$artifactPathEncoded`?job=$job" $fileName = Split-Path $artifactPath -Leaf } else { # Download the full artifact zip of the job $download_url = "$GitLabBaseUrl/api/v4/projects/$projectEncoded/jobs/artifacts/$ref/download?job=$job" $fileName = "$($pkgdef.download.repo)-$($pkgdef.name)-artifacts.zip" } # Mustache resolution on the url and the name when needed $download_url = Render-MustacheField -value $download_url -mustacheValues $mustacheValues $fileName = Render-MustacheField -value $fileName -mustacheValues $mustacheValues $destFile = "$DestinationPath/$fileName" if (Test-FileExist $destFile) { Write-Verbose "Artifact $fileName already exists in $DestinationPath, skipping download" } else { Write-Verbose "Downloading artifact $fileName from $download_url to $DestinationPath" $null = Download-File -Url $download_url -To $DestinationPath -Headers $headers -PassThru } Write-Devel "downloaded artifact = $destFile" if (Test-FileExist $destFile) { return $destFile } else { Write-Fatal "Error downloading artifact $fileName" return $null } } End { Write-LeaveFunction } } |