DevOpsHandling/Get-AndInstallApp.ps1

function  Get-AndInstallApp {
    param (
        [Parameter(Mandatory = $true, ParameterSetName = "Repository")]
        [string]$Repository,
        [Parameter(Mandatory = $false, ParameterSetName = "External")]
        [string]$relativePath = "",
        [Parameter(Mandatory = $false)]
        [string]$Version = "latest",
        [Parameter(Mandatory = $false)]
        [pscredential] $Credential,
        [Parameter(Mandatory = $true)]
        [string]$appFolder,
        [switch]$useDevEndpoint,
        [switch]$silentlyContinue,
        [switch]$isClientRepo,
        [switch]$isExternal
    )

    $settings = import-config

    $stAcc = ""
    $stCont = ""
    $stRel = ""

    if ($isExternal.IsPresent) {
        $stAcc = $settings.storageAccountExternalApps
        $stCont = $settings.storageContainerExternalApps
        $stRel = $settings.storageRelativePathExternalApps
    }
    else {
        $stAcc = $settings.storageAccount
        $stCont = $settings.storageContainer
        $stRel = $settings.storageRelativePath
    }

    # if any of the settings are not set, throw an error
    if ($stAcc -eq "" -or $stCont -eq "" -or $stRel -eq "") {
        if ($silentlyContinue.IsPresent) {
            $false
            return
        }
        Write-Error "Configuration is not complete"
        return
    }

    # create storage context using $settings and Get-Secret
    $storageContext = New-AzStorageContext -StorageAccountName $stAcc -StorageAccountKey (Get-Secret -SecretName $stAcc) -ErrorAction SilentlyContinue
    if ($null -eq $storageContext) {
        if ($silentlyContinue.IsPresent) {
            $false
            return
        }
        Write-Error "Storage account $($stAcc) does not exist or configuration is not complete"
        return
    }

    # test if container in $settings exist in storage account defined in $storageContext
    $container = Get-AzStorageContainer -Context $storageContext -Name $stCont -ErrorAction SilentlyContinue
    if ($null -eq $container) {
        if ($silentlyContinue.IsPresent) {
            $false
            return
        }
        Write-Error "Container $($stCont) does not exist in storage account $($stAcc)"
        return
    }

    # create relative path from $settings and replace {repository} and {version}
    if (!$isExternal.IsPresent) {
        if ($isClientRepo.IsPresent) {
            # currently only allow to use all projects in a repository
            $relativePath = $settings.storageRelativePathClient.Replace("{repository}", $Repository).Replace("{project}", "*").Replace("{version}", $Version)
        }
        else {
            $relativePath = $settings.storageRelativePath.Replace("{repository}", $Repository).Replace("{version}", $Version)
        }
    }

    # test if relative path exists in container
    $blob = Get-AzStorageBlob -Container $stCont -Context $storageContext -Blob "$($relativePath)*" -ErrorAction SilentlyContinue
    if ($null -eq $blob) {
        # if not, try to check for the same version with 0 patch level, if the version is not latest or preview and it is a semantic version
        if ($Version -ne "latest" -and $Version -ne "preview" -and $Version -match "^\d+\.\d+\.\d+$") {
            $relativePath = $settings.storageRelativePath.Replace("{repository}", $Repository).Replace("{version}", ($Version.Split(".")[0..2] -join ".") + ".0")
            $blob = Get-AzStorageBlob -Container $stCont -Context $storageContext -Blob $relativePath -ErrorAction SilentlyContinue
        }

        if ($null -eq $blob) {
            if ($silentlyContinue.IsPresent) {
                $false
                return
            }
            Write-Error "Version $Version for repository $Repository cannot be found (Relative path $relativePath does not exist in container $($stCont))"
            return
        }
    }

    if ($isExternal.IsPresent) {
        $apps = Get-AzStorageBlob -Container $stCont -Context $storageContext -Blob "$($relativePath)" -ErrorAction SilentlyContinue
        $apps = $apps | Sort-Object -Property LastModified -Descending | Select-Object -First 1
    }
    else {
        # download all zip files in container that contain "-apps" in name from $relativePath and store it in $appFolder
        $apps = Get-AzStorageBlob -Container $stCont -Context $storageContext -Blob "$($relativePath)*-apps.zip" -ErrorAction SilentlyContinue
    }
    if ($null -eq $apps) {
        if ($silentlyContinue.IsPresent) {
            $false
            return
        }
        Write-Error "No apps found in for repository $Repository in $relativePath"
        return
    }

    # loop through all apps, save the app in the $appfolder, and unzip the downloaded app into a subdirectory $repository
    foreach ($app in $apps) {
        $appPath = Join-Path $appFolder ($app.Name.Split('/')[-1])
        $app | Get-AzStorageBlobContent -Destination $appPath -Force | Out-Null
        $name = ""
        if (Split-Path $app.Name -Extension -OutVariable name -ErrorAction SilentlyContinue) {
            if ($name -eq ".zip") {
                Expand-Archive -Path $appPath -DestinationPath (Join-Path $appFolder $Repository) -Force
                Remove-Item -Path $appPath -Filter *.zip -Force | Out-Null
            }
        }
    }

    # loop through all apps and, if the publisher is not Microsoft, extract the app to a new temp folder
    $tempFolder = New-TempDirectory

    foreach ($appfile in (Get-ChildItem -Path (Join-Path $appFolder $Repository) -filter '*.app' -File)) {
        $isRuntimePackage = $false
        try {
            Extract-AppFileToFolder -appFilename $appfile.FullName -appFolder $tempFolder -generateAppJson | Out-Null
        }
        catch {
            if ($_.Exception.Message -eq "You cannot extract a runtime package") {
                $isRuntimePackage = $true
            }
        }

        if (!$isRuntimePackage) {
            $appJson = Get-Content -Path (Join-Path $tempFolder 'app.json') | ConvertFrom-Json
            if ($null -ne $appJson.dependencies) {
                foreach($dep in $appJson.dependencies) {
                    if ($dep.Publisher -ne "Microsoft") {
                        $tempVersion = [version]$dep.Version
                        $depVersion = ("{0}.{1}.{2}" -f $tempVersion.Major, $tempVersion.Minor, $tempVersion.Build)

                        # do we need to install the app or is it already installed?
                        $appInfo = (Get-BcContainerAppInfo -containerName $containerName -tenantSpecificProperties | Where-Object { $_.Name -eq $dep.Name -and $_.Publisher -eq $dep.Publisher})

                        $requireInstall = $false
                        if ($null -ne $appInfo) {
                            if ($appInfo.Name -ne $dep.Name -or $appInfo.Version -lt $dep.Version) {
                                $requireInstall = $true
                            }
                        }
                        else {
                            $requireInstall = $true
                        }

                        if ($requireInstall) {
                            # Translate the dependency name to an existing GitHub repository
                            $localRepo = $Repository
                            $isExternal = $false

                            switch ($true) {
                                $dep.Name.StartsWith("NAV-X Allocation") {
                                    $localRepo = "nav-x-allocations"
                                    break
                                }
                                $dep.Name.StartsWith("NAV-X Library") {
                                    $localRepo = "nav-x-library"
                                    break
                                }
                                $dep.Name.StartsWith("NAV-X Base Application") {
                                    $localRepo = "nav-x-base-application"
                                    break
                                }
                                $dep.Name.StartsWith("NAV-X Commission Management") {
                                    $localRepo = "nav-x-commission-management"
                                    break
                                }
                                $dep.Name.StartsWith("NAV-X Credit Card") {
                                    $localRepo = "nav-x-credit-card"
                                    break
                                }
                                $dep.Name.StartsWith("NAV-X Credit Management") {
                                    $localRepo = "na-x-credit-management"
                                    break
                                }
                                $dep.Name.StartsWith("NAV-X National Accounts") {
                                    $localRepo = "nav-x-national-accounts"
                                    break
                                }
                                $dep.Name.StartsWith("NAV-X PayAssist") {
                                    $localRepo = "nav-x-payassist"
                                    break
                                }
                                $dep.Name.StartsWith("NAV-X Search") {
                                    $localRepo = "nav-x-search"
                                    break
                                }
                                default {
                                    $isExternal = $true

                                    $relativePath = Get-RelativePathForExternalApp -settings $settings -publisher $dep.Publisher -name $dep.Name -version $dep.Version
                                    if ($relativePath -eq "") {
                                        if ($silentlyContinue.IsPresent) {
                                            $false
                                            return
                                        }
                                        Write-Error "Could not find external app $($dep.Name) $($dep.Version) $($dep.Publisher)"
                                        return
                                    }
                                    break
                                }
                            }

                            if ($isExternal) {
                                $result = Get-AndInstallApp -relativePath $relativePath -appFolder $appFolder -useDevEndpoint:$useDevEndpoint -silentlyContinue -isExternal
                            }
                            else {
                                $result = Get-AndInstallApp -Repository $localRepo -Version $depVersion -appFolder $appFolder -useDevEndpoint:$useDevEndpoint -silentlyContinue
                                if (!$result) {
                                    $result = Get-AndInstallApp -Repository $localRepo -Version "latest" -appFolder $appFolder -useDevEndpoint:$useDevEndpoint -silentlyContinue
                                    if (!$result) {
                                        $result = Get-AndInstallApp -Repository $localRepo -Version "preview" -appFolder $appFolder -useDevEndpoint:$useDevEndpoint -silentlyContinue
                                    }
                                }
                            }

                            if (!$result) {
                                Write-Error "Dependency $($dep.Name) $($dep.Version) for repository $Repository cannot be found"
                                return
                            }
                        }
                    }
                }
            }

        }

        $parameters = @{}
        if ($useDevEndpoint.IsPresent) {
            $parameters.Add('useDevEndpoint', $true)
            $parameters.Add('credential', $credential)
        }
        try {
            $result = (Publish-BcContainerApp -containerName $ContainerName -appFile $appFile.FullName -sync -tenant "default" -skipVerification -ignoreIfAppexists -install @parameters 6>&1)
        }
        catch {
            # if the exception starts with "Cannot synchronize the extension because no synchronized extension could be found",
            # extract the app name from the exception after "the dependency definition for" and before "by".
            if ($_.Exception.Message.StartsWith("Cannot synchronize the extension because no synchronized extension could be found")) {
                $appName = $_.Exception.Message.Split("the dependency definition for")[1].Split("by")[0].Trim()
                $appVersion = $_.Exception.Message.Split(" ")[-1].Trim().Trim(".")
                $appPublisher = $_.Exception.Message.Split("by")[1].Split($appVersion)[0].Trim()

                $relativePath = Get-RelativePathForExternalApp -settings $settings -publisher $appPublisher -name $appName -version $appVersion
                if (Get-AndInstallApp -relativePath $relativePath -appFolder $appFolder -useDevEndpoint:$useDevEndpoint -silentlyContinue -isExternal) {
                    $result = (Publish-BcContainerApp -containerName $ContainerName -appFile $appFile.FullName -sync -tenant "default" -skipVerification -ignoreIfAppexists -install @parameters 6>&1)
                }
            }
        }
    }

    Remove-Item -Path $tempFolder -Recurse -Force

    if ($silentlyContinue.IsPresent) {
        $true
    }
}