ReCrutch.GitLab.psm1

[System.Reflection.Assembly]::LoadWithPartialName("System.Web")

function Set-ReCrGitLabSettings($apiUrl, $prj, $privTokenEnvVarName, $privTokenScripts, $privTokenValue)
{
    $ReCr.GitLab = @{
        ApiUrl = $apiUrl
        Prj = $prj
        PrivateToken = @{
            EnvVarName = $privTokenEnvVarName
            Scripts = $privTokenScripts
            PrivateToken = $privTokenValue
        }
    }
}

function Get-ReCrGitLabPrivateToken()
{
    try
    {
        $settings = $ReCr.GitLab.PrivateToken
        if($settings.PrivateToken -ne $null)
        {
            return $settings.PrivateToken
        }
        
        if($settings.EnvVarName -ne $null)
        {
            $pt = [System.Environment]::GetEnvironmentVariable($settings.EnvVarName)
            if($pt -ne $null)
            {
                return $pt
            }
        }

        if($settings.Scripts -ne $null)
        {
            foreach($script in $settings.Scripts)
            {
                if((Test-Path $script) -eq $true)
                {
                    . $script
                    if($settings.PrivateToken -ne $null)
                    {
                        return $settings.PrivateToken
                    }
                }
            }
        }

        if($settings.PrivateToken -eq $null)
        {
            $settings.PrivateToken = Read-Host -Prompt 'Input private token for accsess GitLab'
        }

        return $settings.PrivateToken
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

function Invoke-ReCrGitLabRestApi($method, [string]$url, $urlParameters, $if404ToNull, $prj = $null)
{
    $oldSecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol;
    try
    {
        $settings = $ReCr.GitLab

        $pt = Get-ReCrGitLabPrivateToken

        [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;
        $urlParametersStr = ""
        if($urlParameters -ne $null)
        {
            $urlParametersStr = ($urlParameters.Keys | ForEach-Object {
                "{0}={1}" -f [System.Uri]::EscapeDataString($_), [System.Uri]::EscapeDataString($urlParameters[$_])
                }) -join "&"
        }

        $con = ""
        if([string]::IsNullOrEmpty($urlParametersStr) -ne $true -and $url.Contains("?") -ne $true -and $url.Contains("&") -ne $true)
        {
            $con = "?"
        }
        elseif([string]::IsNullOrEmpty($urlParametersStr) -ne $true -and $url.EndsWith("?") -eq $true)
        {
            $con = ""
        }
        elseif([string]::IsNullOrEmpty($urlParametersStr) -ne $true -and $url.EndsWith("&") -eq $true)
        {
            $con = ""
        }
        elseif([string]::IsNullOrEmpty($urlParametersStr) -ne $true -and $url.Contains("?") -eq $true)
        {
            $con = "&"
        }

        if($prj -eq $null)
        {
            $prj = $settings.Prj
        }

        $fullUrl = "$($settings.ApiUrl)/projects/$([System.Web.HttpUtility]::UrlEncode($prj))$($url)$con$($urlParametersStr)"

        $uri = New-Object System.Uri -ArgumentList ($fullUrl)
        Repair-ReCrUri $uri
        $resp = Invoke-WebRequest -Uri $uri -Method $method -Headers @{
            "PRIVATE-TOKEN" = $pt
            "Content-Type" = "application/json;charset=utf-8"
        }
        $str=[system.Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray());

        return ConvertFrom-Json $str
    }
    catch
    {
        if($if404ToNull -eq $true -and $_.Exception.GetType() -eq [System.Net.WebException])
        {
            if($_.Exception.Response.StatusCode -eq 404)
            {
                return $null
            }
        }

        throw new-object System.Exception ("$fullUrl", $_.Exception)
    }
    finally
    {
        [System.Net.ServicePointManager]::SecurityProtocol = $oldSecurityProtocol
    }
}

function Invoke-ReCrGitLabRestApiGet([string]$url, $urlParameters, $if404ToNull, $prj = $null)
{
    return Invoke-ReCrGitLabRestApi Get $url $urlParameters $if404ToNull $prj
}

function Invoke-ReCrGitLabRestApiPost([string]$url, $urlParameters, $if404ToNull, $prj = $null)
{
    return Invoke-ReCrGitLabRestApi Post $url $urlParameters $if404ToNull $prj
}

function Invoke-ReCrGitLabRestApiPut([string]$url, $urlParameters, $if404ToNull, $prj = $null)
{
    return Invoke-ReCrGitLabRestApi Put $url $urlParameters $if404ToNull $prj
}



# tags b

function Get-ReCrGitLabTag($tagName, $if404ToNull)
{
    try
    {
        $tag = Invoke-ReCrGitLabRestApiGet "/repository/tags/$tagName" -if404ToNull:$if404ToNull
        $tag
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

function New-ReCrGitLabTag($branch, $tagName, $buildPath)
{
    <#if([string]::IsNullOrEmpty($tagName) -eq $true)
    {
        throw [System.Exception] "`$tagName requery"
    }#>


    #$tagName = "1"
    #$r = BuildGLRestGet "/repository/tags/" @{ per_page = 1000 } #?per_page=3&page=2&sort=commit.committed_date
    <#
    $r = $r | Sort-Object { $_.commit.committed_date } -Descending
    $r | Select-Object { $_.name; $_.commit.committed_date } | Format-Table
    $lastTag = $r[0]
    [Regex]::Matches($lastTag.name, "(?<stend>/)")
    $g = [Regex]::Matches($lastTag.name, "(?<stend>^\S[^/]+)/v(?<v1>\d+).(?<v2>\d+).(?<v3>\d+)")
    $g[0].Groups
    $nextVer = [int]::Parse($g[0].Groups["v2"].Value) + 1
    $newTagName = "$($g[0].Groups["stend"].Value)/v$($g[0].Groups["v1"].Value).$nextVer.$($g[0].Groups["v3"].Value)"
    #>


    <#$tag = BuildGLRestGet "/repository/tags/$tagName" -if404ToNull:true
 
    if($tag -ne $null)
    {
        throw [System.Exception] "Tag $tagName уже существует"
    }
 
 
    $issues = BuildGLRestGet "/issues" @{ labels = "$tagName"; state = "opened" }
    $issues = $issues | sort iid
    $issues.Count
    $issues | Format-Table iid, title
 
    $release_description = ""
    foreach ($issue in $issues)
    {
        $release_description += "* #$($issue.iid)
"
    }
 
    $newtag = BuildGLRestPost "/repository/tags/" @{
        tag_name = $tagName
        ref = $branch
        message = "$buildPath"
        release_description = $release_description
    }
    #>

}

function New-ReCrGitLabTagRelease($tagName, $description)
{
    try
    {
        if([string]::IsNullOrEmpty($tagName) -eq $true)
        {
             throw new-object System.Exception ("tagName require")
        }
        if([string]::IsNullOrEmpty($description) -eq $true)
        {
             throw new-object System.Exception ("description require")
        }

        $r = Invoke-ReCrGitLabRestApiPost "/repository/tags/$tagName/release/" @{ description = $description }
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

function Update-ReCrGitLabTagRelease($tagName, $description)
{
    try
    {
        if([string]::IsNullOrEmpty($tagName) -eq $true)
        {
             throw new-object System.Exception ("tagName require")
        }
        if([string]::IsNullOrEmpty($description) -eq $true)
        {
             throw new-object System.Exception ("description require")
        }

        $r = Invoke-ReCrGitLabRestApiPut "/repository/tags/$tagName/release/" @{ description = $description }
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

function Update-ReCrGitLabTagReleaseBuildPathAndIssues($tagName, $buildPath, $issueIds)
{
    # $tagName = "mrgrp-suip.v0.0-build.1-revision.1"
    #$buildPath = $buildDevServerPath
    try
    {
        if([string]::IsNullOrEmpty($tagName) -eq $true)
        {
             throw new-object System.Exception ("tagName require")
        }
        
        $tag = Get-ReCrGitLabTag -tagName:$tagName -if404ToNull:$true

        $release_description = ""
        if($tag.release -ne $null -and $tag.release.description -ne $null)
        {
            $release_description = $tag.release.description
        }

        $releaseDescription = "`r`n>>>`r`n"
        $releaseDescription += "Билд тут: "
        # \\aakozlov-vm4.test.local\C$\_MRGRP-SUIP\Builds\mrgrp-suip.v0.0-build.1-revision.1
        #Билд тут: \\aakozlov-vm4.test.local\C$_MRGRP-SUIP\Builds\mrgrp-suip.v0.0-build.1-revision.1
        #$releaseDescription += $buildPath.Replace("\\", "\\\\").Replace("$\", "$\\")
        $releaseDescription += "file://" + $buildPath.Replace("\\", "").Replace("\", "/")
        $releaseDescription += "`r`n ``(если не открывается, то нужно ссылку скопировать и вставить в Explorer, IE или файловый менеджер или новую вкладку браузера)``"
        
        $groups = $issueIds | Group-Object -Property prj
        foreach ($group in $groups)
        {
            $releaseDescription += "`r`n`r`n"

            $labelName = [System.Uri]::EscapeDataString("$($ReCr.GitLab.Prj)/$tagName")

            $releaseDescription += "[**issues $($group.Name)**](https://git.i-sys.ru/$($group.Name)/issues?label_name%5B%5D=$labelName)"
            $releaseDescription += "`r`n"

            foreach ($issueId in $group.Group)
            {
                $releaseDescription += "* $($issueId.prj)#$($issueId.id) `r`n"
            }
        }
        
        $releaseDescription += ">>>"

        if($release_description -match "Билд тут:")
        {
            $pat = "[\r\n]+>>>[\r\n]+Билд тут: (.*[\r\n])+>>>"
            [System.Text.RegularExpressions.Regex]::Match($release_description, $pat, [System.Text.RegularExpressions.RegexOptions]::Multiline)
            $release_description = [System.Text.RegularExpressions.Regex]::Replace($release_description, $pat, "", [System.Text.RegularExpressions.RegexOptions]::Multiline)
        }


        $release_description += $releaseDescription + "`r`n`r`n"

        if($tag.release -eq $null)
        {
            New-ReCrGitLabTagRelease -tagName:$tagName -description:$release_description
        }
        else
        {
            Update-ReCrGitLabTagRelease -tagName:$tagName -description:$release_description
        }
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

function Get-ReCrGitLabTagCommits($tagName, $regExpMaskTagName)
{
    #$tagName = $buildOptions.tagName
    #$regExpMaskTagName = $buildOptions.regExpMaskTagName
    try
    {
        $tag = Get-ReCrGitLabTag -tagName:$tagName -if404ToNull:$false
        $tags = Invoke-ReCrGitLabRestApiGet "/repository/tags/" @{
            order_by = "updated"
            sort = "desc"
        }

        $tags = $tags | Where-Object { 
            [System.Text.RegularExpressions.Regex]::IsMatch($_.name, $regExpMaskTagName) -eq $true `
            -and $_.commit.committed_date -lt $tag.commit.committed_date } `
            | Sort-Object -Property @{ Expression = "commit.committed_date" } -Descending `
            | select -First 1
        #$tags | Format-List

        $tagCommitsCurrent = @()
        $tagCommits = @()

        while($true)
        {
            $p = @{
                ref_name = "$tagName"
            }

            $l = $tagCommits | select -Last 1
            if($l -ne $null)
            {
                $d = [System.DateTime]::Parse($l.committed_date)
                #$l.committed_date
                $until = $d.AddMilliseconds(-1).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
                $p.until = $until
            }

            $tagCommits = Invoke-ReCrGitLabRestApiGet "/repository/commits/" $p
            #$tagCommits | select message

            if($tagCommits.Count -eq 0)
            {
                break
            }

            $flag = $false
            foreach($commit in $tagCommits)
            {
                #$commit = $tagCommits[0]
                #$commitRefs = Invoke-ReCrGitLabRestApiGet "/repository/commits/$($commit.short_id)/refs/"

                if($tags -ne $null -and $tags.commit.id -eq $commit.id)
                {
                    $flag = $true
                    break
                }

                $tagCommitsCurrent += $commit
            }

            if($flag -eq $true)
            {
                break
            }
        }

        #$tagCommitsCurrent | select -Property short_id,message | Format-Table

        $tagCommitsCurrent
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

function Get-ReCrGitLabTagCommitsIssueIds($tagName, $regExpMaskTagName)
{
    #$tagName = $buildOptions.tagName
    #$regExpMaskTagName = $buildOptions.regExpMaskTagName
    try
    {
        $commits = Get-ReCrGitLabTagCommits -tagName:$tagName -regExpMaskTagName:$regExpMaskTagName
        #$commits = $tagCommitsCurrent
        #$commits | select message

        if($commits.Count -gt 1)
        {
            [array]::Reverse($commits)
        }

        $groups = @("id", "idWitProj", "url")
        $issueIds = @()
        foreach($commit in $commits)
        {
            $msg = $commit.message
            <#
            $msg = "[*] #1177 Структура решения
[*] #2 Структура решения #3"
 
            $msg = "Merge branch 'task/1-structure-solution' into 'master'
 
#1 Структура решения 2
 
See merge request MRGRP/SUIP!2"
#>

            #$msg = "[*] systemz/ISYS-DXFM#132 Test #478 #479 https://git.i-sys.ru/systemz/ISYS-DXFM/issues/132"
            #$msg = "[*]#192 Запретил редактирование DelegateFrom и DelegateTo...."
            #$msg = "Revert `"#342 Обязательность поля Commentaries approval-таски не соответствует тз`"... "
            #$msg = "[+] #1 Добавлена базовая инфраструктура создания сайтов...."
            #cls
            #$msg
            $mathes = [System.Text.RegularExpressions.Regex]::Matches($msg, "(^|\s|\]|`"|')(?<f>(?<id>#[0-9]+)|(?<idWitProj>[^\s\]`"']+#[0-9]+)|(?<url>https://git.i-sys.ru/[^\s]+/issues/[0-9]+))")
            <#
            foreach($math in $mathes)
            {
                $math.Groups | Where-Object { $_.Success -eq $true -and $groups -contains $_.Name }
            }
            #>


            if($mathes.Count -eq 0)
            {
                $branchMathes = [System.Text.RegularExpressions.Regex]::Matches($msg, "^Merge branch '(?<branch>[^\s'`"]+)' into 'master'")
                if($branchMathes.Count -eq 1)
                {
                    <#
                    Merge branch 'task/108-table-requirement' into 'master'
 
Task/108 table requirement
 
See merge request 4FIN/APRS!412
                    #>

                    # todo Write-Host ">>>$msg"
                }
                elseif([System.Text.RegularExpressions.Regex]::IsMatch($msg, "^Merge branch 'master' ") -eq $true)
                {
                    continue
                }
                elseif([System.Text.RegularExpressions.Regex]::IsMatch($msg, "^\[~\] Merge master into ") -eq $true)
                {
                    continue
                }
                elseif([System.Text.RegularExpressions.Regex]::IsMatch($msg, "^\[~\] Merge from master to ") -eq $true)
                {
                    continue
                }
                elseif([System.Text.RegularExpressions.Regex]::IsMatch($msg, "^\[~\] Merge from origin/master to") -eq $true)
                {
                    continue
                }
                elseif([System.Text.RegularExpressions.Regex]::IsMatch($msg, "^\[~\] Merge origin/master to ") -eq $true)
                {
                    continue
                }
                elseif([System.Text.RegularExpressions.Regex]::IsMatch($msg, "^Merge remote-tracking branch 'origin/master' into ") -eq $true)
                {
                    continue
                }
                elseif([System.Text.RegularExpressions.Regex]::IsMatch($msg, "^Merge remote-tracking branch 'origin' into ") -eq $true)
                {
                    continue
                }
                else
                {
                    #Write-Host ">>>$msg"
                }
            }

            foreach($math in $mathes)
            {
                $gs = $math.Groups | Where-Object { $_.Success -eq $true -and $groups -contains $_.Name }

                foreach($g in $gs)
                {
                    #Write-Host "$($g.Value) $($g.Name)"
                    $id = $g.Value

                    if($g.Name -eq "id")
                    {   
                        $id = $id.Replace("#", "")
                    }    
                    
                    if($issueIds.Contains($id) -eq $false)
                    {
                        $issueIds += $id
                    }
                }
            }
        }

        $issueIdsCmplx = $issueIds | ForEach-Object { Get-ReCrPrjFromIssueId $_ } | Select-Object -Property @{Name="prj"; Expression = {$_.prj}},@{Name="id"; Expression = {$_.id}} -Unique
        $issueIdsCmplx
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

# tags e



# label b

function New-ReCrGitLabLabel($labelName, $color, $prj = $null)
{
    try
    {
        if([string]::IsNullOrEmpty($labelName) -eq $true)
        {
            throw [System.Exception] "`$labelName requery"
        }

        $labels = Invoke-ReCrGitLabRestApiGet "/labels" @{ per_page = 1000 } -prj:$prj
        $label = $labels | where name -eq $labelName
        if($label -ne $null)
        {
            return $label
        }

        $label = Invoke-ReCrGitLabRestApiPost "/labels/" @{
            name = $labelName
            color = $color
        } -prj:$prj

        return $label
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

# label e



# Issue b

function Get-ReCrPrjFromIssueId($id)
{
    $idInt = 0
    if([int]::TryParse($id, [ref] $idInt))
    {
        $settings = $ReCr.GitLab
        return @{ prj = $settings.Prj; id = $id; }
    }
    #$id = "https://git.i-sys.ru/systemz/ISYS-DXFM/issues/132"
    #$id = "systemz/ISYS-DXFM#132"

    $mathes = [System.Text.RegularExpressions.Regex]::Matches($id, "^(?<prj>[^\s]+)#(?<id>[0-9]+)$")
    if($mathes.Success)
    {
        return @{ prj = $mathes[0].Groups["prj"].Value; id = $mathes[0].Groups["id"].Value; }
    }

    $mathes = [System.Text.RegularExpressions.Regex]::Matches($id, "^https://git.i-sys.ru/(?<prj>[^\s]+)/issues/(?<id>[0-9]+)$")
    if($mathes.Success)
    {
        return @{ prj = $mathes[0].Groups["prj"].Value; id = $mathes[0].Groups["id"].Value; }
    }

    throw [System.Exception] "не удалось распарсить issue id: $id"
}

function Get-ReCrGitLabIssue($id, $if404ToNull, $prj = $null)
{
    try
    {
        if($id.GetType() -eq [string])
        {
            $idInt = 0
            if([int]::TryParse($id, [ref] $idInt))
            {
                $issue = Invoke-ReCrGitLabRestApiGet "/issues/$id/" -if404ToNull:$if404ToNull -prj:$prj
                return $issue
            }
            else
            {
                $idComplex = Get-ReCrPrjFromIssueId $id
                $issue = Invoke-ReCrGitLabRestApiGet "/issues/$($idComplex.id)/" -if404ToNull:$if404ToNull -prj:$idComplex.prj
                return $issue
            }
        }
        else
        {
            $issue = Invoke-ReCrGitLabRestApiGet "/issues/$($id.id)/" -if404ToNull:$if404ToNull -prj:$id.prj
            return $issue
        }
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

function Add-ReCrGitLabCommentToIssue($id, $body, $prj = $null)
{
    try
    {
        $newNote = Invoke-ReCrGitLabRestApiPost "/issues/$id/notes" @{
                body = $body
            } -prj:$prj
        $newNote
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

function Set-ReCrGitLabLabelToIssue($labelName, $labelBuildColor, $issueIds, $needToDeployLabel, $msg)
{
    try
    {
        $needToDeployLabelCmd = ""
        if($needToDeployLabel -ne $null)
        {
            $needToDeployLabelCmd = " ~`"$needToDeployLabel`""
        }

        $groups = $issueIds | Group-Object -Property prj
        foreach ($group in $groups)
        {
            $labelFullName = "$($ReCr.GitLab.Prj)/$labelName"
            $labelBuild = New-ReCrGitLabLabel -labelName:$labelFullName -color:$labelBuildColor -prj:$group.Name

            foreach ($issueId in $group.Group)
            {
                $issue = Get-ReCrGitLabIssue -id:$issueId.id -if404ToNull:true -prj:$issueId.prj
                if($issue -eq $null)
                {
                    Write-Host-Indent 1 "issue not found by id '$id'" -ForegroundColor:$ReCr.ConsoleColors.errorMessage
                    continue
                }

                $body = "$msg`r`n`r`n/label ~`"$labelFullName`"$needToDeployLabelCmd`r`n"
                $newNote = Add-ReCrGitLabCommentToIssue -id:$issueId.id -body:$body -prj:$issueId.prj
            }
        }
    }
    catch
    {
        Show-ReCrError "$($MyInvocation.MyCommand)" $_.Exception
        throw new-object System.Exception ("$($MyInvocation.MyCommand) error", $_.Exception)
    }
}

# Issue e