Allow building an pushing site extension for azure functional testing (#87)
This commit is contained in:
parent
f4ee5bcf04
commit
9778c29007
|
|
@ -30,3 +30,5 @@ project.lock.json
|
|||
.testPublish/
|
||||
global.json
|
||||
korebuild-lock.txt
|
||||
msbuild.binlog
|
||||
.test-dotnet
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.26412.1
|
||||
VisualStudioVersion = 15.0.26814.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.AzureAppServicesIntegration", "src\Microsoft.AspNetCore.AzureAppServicesIntegration\Microsoft.AspNetCore.AzureAppServicesIntegration.csproj", "{5916BEB5-0969-469B-976C-A392E015DFAC}"
|
||||
EndProject
|
||||
|
|
@ -35,6 +35,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.AzureA
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationInsights.HostingStartup.Tests", "test\ApplicationInsights.HostingStartup.Tests\ApplicationInsights.HostingStartup.Tests.csproj", "{0899A101-E451-40A4-81B0-7AA18202C25D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.AzureAppServices.FunctionalTests", "test\Microsoft.AspNetCore.AzureAppServices.FunctionalTests\Microsoft.AspNetCore.AzureAppServices.FunctionalTests.csproj", "{2B2C37FF-9249-4EA4-9A7F-038B55A15C2C}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.AzureAppServices.TestBundle", "src\Microsoft.AspNetCore.AzureAppServices.TestBundle\Microsoft.AspNetCore.AzureAppServices.TestBundle.csproj", "{1EC31DA1-131D-4257-B001-BE8391E6077E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -81,6 +85,14 @@ Global
|
|||
{0899A101-E451-40A4-81B0-7AA18202C25D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0899A101-E451-40A4-81B0-7AA18202C25D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0899A101-E451-40A4-81B0-7AA18202C25D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2B2C37FF-9249-4EA4-9A7F-038B55A15C2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2B2C37FF-9249-4EA4-9A7F-038B55A15C2C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2B2C37FF-9249-4EA4-9A7F-038B55A15C2C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2B2C37FF-9249-4EA4-9A7F-038B55A15C2C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1EC31DA1-131D-4257-B001-BE8391E6077E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1EC31DA1-131D-4257-B001-BE8391E6077E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1EC31DA1-131D-4257-B001-BE8391E6077E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1EC31DA1-131D-4257-B001-BE8391E6077E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -96,5 +108,10 @@ Global
|
|||
{9B22E525-FEC9-4C7C-9F9C-598C15BD0250} = {FF9B744E-6C59-40CC-9E41-9D2EBD292435}
|
||||
{1CE2D76B-39E6-46C0-8F6F-C63E370955A9} = {FF9B744E-6C59-40CC-9E41-9D2EBD292435}
|
||||
{0899A101-E451-40A4-81B0-7AA18202C25D} = {CD650B4B-81C2-4A44-AEF2-A251A877C1F0}
|
||||
{2B2C37FF-9249-4EA4-9A7F-038B55A15C2C} = {CD650B4B-81C2-4A44-AEF2-A251A877C1F0}
|
||||
{1EC31DA1-131D-4257-B001-BE8391E6077E} = {FF9B744E-6C59-40CC-9E41-9D2EBD292435}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5743DFE7-1AA5-439D-84AE-A480EA389927}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
<ApplicationInsightsVersion>2.1.1</ApplicationInsightsVersion>
|
||||
<InternalAspNetCoreSdkVersion>2.1.1-*</InternalAspNetCoreSdkVersion>
|
||||
<MoqVersion>4.7.49</MoqVersion>
|
||||
<MicrosoftAzureManagementFluentVersion>1.1.3</MicrosoftAzureManagementFluentVersion>
|
||||
<NETStandardImplicitPackageVersion>2.0.0-*</NETStandardImplicitPackageVersion>
|
||||
<NETStandardLibraryNETFrameworkVersion>2.0.0-*</NETStandardLibraryNETFrameworkVersion>
|
||||
<RuntimeFrameworkVersion Condition="'$(TargetFramework)'=='netcoreapp2.0'">2.0.0-*</RuntimeFrameworkVersion>
|
||||
<TestSdkVersion>15.3.0</TestSdkVersion>
|
||||
<XdtVersion>1.4.0</XdtVersion>
|
||||
<XunitVersion>2.3.0-beta4-build3742</XunitVersion>
|
||||
<WindowsAzureStorageVersion>8.3.0</WindowsAzureStorageVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
@ECHO OFF
|
||||
PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "& '%~dp0dotnet-install.ps1' %*; exit $LASTEXITCODE"
|
||||
|
|
@ -0,0 +1,503 @@
|
|||
#
|
||||
# Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
# Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
#
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Installs dotnet cli
|
||||
.DESCRIPTION
|
||||
Installs dotnet cli. If dotnet installation already exists in the given directory
|
||||
it will update it only if the requested version differs from the one already installed.
|
||||
.PARAMETER Channel
|
||||
Default: LTS
|
||||
Download from the Channel specified. Possible values:
|
||||
- Current - most current release
|
||||
- LTS - most current supported release
|
||||
- 2-part version in a format A.B - represents a specific release
|
||||
examples: 2.0; 1.0
|
||||
- Branch name
|
||||
examples: release/2.0.0; Master
|
||||
.PARAMETER Version
|
||||
Default: latest
|
||||
Represents a build version on specific channel. Possible values:
|
||||
- latest - most latest build on specific channel
|
||||
- coherent - most latest coherent build on specific channel
|
||||
coherent applies only to SDK downloads
|
||||
- 3-part version in a format A.B.C - represents specific version of build
|
||||
examples: 2.0.0-preview2-006120; 1.1.0
|
||||
.PARAMETER InstallDir
|
||||
Default: %LocalAppData%\Microsoft\dotnet
|
||||
Path to where to install dotnet. Note that binaries will be placed directly in a given directory.
|
||||
.PARAMETER Architecture
|
||||
Default: <auto> - this value represents currently running OS architecture
|
||||
Architecture of dotnet binaries to be installed.
|
||||
Possible values are: <auto>, x64 and x86
|
||||
.PARAMETER SharedRuntime
|
||||
Default: false
|
||||
Installs just the shared runtime bits, not the entire SDK
|
||||
.PARAMETER DryRun
|
||||
If set it will not perform installation but instead display what command line to use to consistently install
|
||||
currently requested version of dotnet cli. In example if you specify version 'latest' it will display a link
|
||||
with specific version so that this command can be used deterministicly in a build script.
|
||||
It also displays binaries location if you prefer to install or download it yourself.
|
||||
.PARAMETER NoPath
|
||||
By default this script will set environment variable PATH for the current process to the binaries folder inside installation folder.
|
||||
If set it will display binaries location but not set any environment variable.
|
||||
.PARAMETER Verbose
|
||||
Displays diagnostics information.
|
||||
.PARAMETER AzureFeed
|
||||
Default: https://dotnetcli.azureedge.net/dotnet
|
||||
This parameter typically is not changed by the user.
|
||||
It allows to change URL for the Azure feed used by this installer.
|
||||
.PARAMETER UncachedFeed
|
||||
This parameter typically is not changed by the user.
|
||||
It allows to change URL for the Uncached feed used by this installer.
|
||||
.PARAMETER ProxyAddress
|
||||
If set, the installer will use the proxy when making web requests
|
||||
.PARAMETER ProxyUseDefaultCredentials
|
||||
Default: false
|
||||
Use default credentials, when using proxy address.
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[string]$Channel="LTS",
|
||||
[string]$Version="Latest",
|
||||
[string]$InstallDir="<auto>",
|
||||
[string]$Architecture="<auto>",
|
||||
[switch]$SharedRuntime,
|
||||
[switch]$DryRun,
|
||||
[switch]$NoPath,
|
||||
[string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet",
|
||||
[string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet",
|
||||
[string]$ProxyAddress,
|
||||
[switch]$ProxyUseDefaultCredentials
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference="Stop"
|
||||
$ProgressPreference="SilentlyContinue"
|
||||
|
||||
$BinFolderRelativePath=""
|
||||
|
||||
# example path with regex: shared/1.0.0-beta-12345/somepath
|
||||
$VersionRegEx="/\d+\.\d+[^/]+/"
|
||||
$OverrideNonVersionedFiles=$true
|
||||
|
||||
function Say($str) {
|
||||
Write-Output "dotnet-install: $str"
|
||||
}
|
||||
|
||||
function Say-Verbose($str) {
|
||||
Write-Verbose "dotnet-install: $str"
|
||||
}
|
||||
|
||||
function Say-Invocation($Invocation) {
|
||||
$command = $Invocation.MyCommand;
|
||||
$args = (($Invocation.BoundParameters.Keys | foreach { "-$_ `"$($Invocation.BoundParameters[$_])`"" }) -join " ")
|
||||
Say-Verbose "$command $args"
|
||||
}
|
||||
|
||||
function Invoke-With-Retry([ScriptBlock]$ScriptBlock, [int]$MaxAttempts = 3, [int]$SecondsBetweenAttempts = 1) {
|
||||
$Attempts = 0
|
||||
|
||||
while ($true) {
|
||||
try {
|
||||
return $ScriptBlock.Invoke()
|
||||
}
|
||||
catch {
|
||||
$Attempts++
|
||||
if ($Attempts -lt $MaxAttempts) {
|
||||
Start-Sleep $SecondsBetweenAttempts
|
||||
}
|
||||
else {
|
||||
throw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Machine-Architecture() {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
# possible values: AMD64, IA64, x86
|
||||
return $ENV:PROCESSOR_ARCHITECTURE
|
||||
}
|
||||
|
||||
# TODO: Architecture and CLIArchitecture should be unified
|
||||
function Get-CLIArchitecture-From-Architecture([string]$Architecture) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
switch ($Architecture.ToLower()) {
|
||||
{ $_ -eq "<auto>" } { return Get-CLIArchitecture-From-Architecture $(Get-Machine-Architecture) }
|
||||
{ ($_ -eq "amd64") -or ($_ -eq "x64") } { return "x64" }
|
||||
{ $_ -eq "x86" } { return "x86" }
|
||||
default { throw "Architecture not supported. If you think this is a bug, please report it at https://github.com/dotnet/cli/issues" }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Version-Info-From-Version-Text([string]$VersionText) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
$Data = @($VersionText.Split([char[]]@(), [StringSplitOptions]::RemoveEmptyEntries));
|
||||
|
||||
$VersionInfo = @{}
|
||||
$VersionInfo.CommitHash = $Data[0].Trim()
|
||||
$VersionInfo.Version = $Data[1].Trim()
|
||||
return $VersionInfo
|
||||
}
|
||||
|
||||
function Load-Assembly([string] $Assembly) {
|
||||
try {
|
||||
Add-Type -Assembly $Assembly | Out-Null
|
||||
}
|
||||
catch {
|
||||
# On Nano Server, Powershell Core Edition is used. Add-Type is unable to resolve base class assemblies because they are not GAC'd.
|
||||
# Loading the base class assemblies is not unnecessary as the types will automatically get resolved.
|
||||
}
|
||||
}
|
||||
|
||||
function GetHTTPResponse([Uri] $Uri)
|
||||
{
|
||||
Invoke-With-Retry(
|
||||
{
|
||||
|
||||
$HttpClient = $null
|
||||
|
||||
try {
|
||||
# HttpClient is used vs Invoke-WebRequest in order to support Nano Server which doesn't support the Invoke-WebRequest cmdlet.
|
||||
Load-Assembly -Assembly System.Net.Http
|
||||
|
||||
if(-not $ProxyAddress)
|
||||
{
|
||||
# Despite no proxy being explicitly specified, we may still be behind a default proxy
|
||||
$DefaultProxy = [System.Net.WebRequest]::DefaultWebProxy;
|
||||
if($DefaultProxy -and (-not $DefaultProxy.IsBypassed($Uri))){
|
||||
$ProxyAddress = $DefaultProxy.GetProxy($Uri).OriginalString
|
||||
$ProxyUseDefaultCredentials = $true
|
||||
}
|
||||
}
|
||||
|
||||
if($ProxyAddress){
|
||||
$HttpClientHandler = New-Object System.Net.Http.HttpClientHandler
|
||||
$HttpClientHandler.Proxy = New-Object System.Net.WebProxy -Property @{Address=$ProxyAddress;UseDefaultCredentials=$ProxyUseDefaultCredentials}
|
||||
$HttpClient = New-Object System.Net.Http.HttpClient -ArgumentList $HttpClientHandler
|
||||
}
|
||||
else {
|
||||
$HttpClient = New-Object System.Net.Http.HttpClient
|
||||
}
|
||||
# Default timeout for HttpClient is 100s. For a 50 MB download this assumes 500 KB/s average, any less will time out
|
||||
# 10 minutes allows it to work over much slower connections.
|
||||
$HttpClient.Timeout = New-TimeSpan -Minutes 10
|
||||
$Response = $HttpClient.GetAsync($Uri).Result
|
||||
if (($Response -eq $null) -or (-not ($Response.IsSuccessStatusCode)))
|
||||
{
|
||||
$ErrorMsg = "Failed to download $Uri."
|
||||
if ($Response -ne $null)
|
||||
{
|
||||
$ErrorMsg += " $Response"
|
||||
}
|
||||
|
||||
throw $ErrorMsg
|
||||
}
|
||||
|
||||
return $Response
|
||||
}
|
||||
finally {
|
||||
if ($HttpClient -ne $null) {
|
||||
$HttpClient.Dispose()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function Get-Latest-Version-Info([string]$AzureFeed, [string]$Channel, [bool]$Coherent) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
$VersionFileUrl = $null
|
||||
if ($SharedRuntime) {
|
||||
$VersionFileUrl = "$UncachedFeed/Runtime/$Channel/latest.version"
|
||||
}
|
||||
else {
|
||||
if ($Coherent) {
|
||||
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.coherent.version"
|
||||
}
|
||||
else {
|
||||
$VersionFileUrl = "$UncachedFeed/Sdk/$Channel/latest.version"
|
||||
}
|
||||
}
|
||||
|
||||
$Response = GetHTTPResponse -Uri $VersionFileUrl
|
||||
$StringContent = $Response.Content.ReadAsStringAsync().Result
|
||||
|
||||
switch ($Response.Content.Headers.ContentType) {
|
||||
{ ($_ -eq "application/octet-stream") } { $VersionText = [Text.Encoding]::UTF8.GetString($StringContent) }
|
||||
{ ($_ -eq "text/plain") } { $VersionText = $StringContent }
|
||||
{ ($_ -eq "text/plain; charset=UTF-8") } { $VersionText = $StringContent }
|
||||
default { throw "``$Response.Content.Headers.ContentType`` is an unknown .version file content type." }
|
||||
}
|
||||
|
||||
$VersionInfo = Get-Version-Info-From-Version-Text $VersionText
|
||||
|
||||
return $VersionInfo
|
||||
}
|
||||
|
||||
|
||||
function Get-Specific-Version-From-Version([string]$AzureFeed, [string]$Channel, [string]$Version) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
switch ($Version.ToLower()) {
|
||||
{ $_ -eq "latest" } {
|
||||
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $False
|
||||
return $LatestVersionInfo.Version
|
||||
}
|
||||
{ $_ -eq "coherent" } {
|
||||
$LatestVersionInfo = Get-Latest-Version-Info -AzureFeed $AzureFeed -Channel $Channel -Coherent $True
|
||||
return $LatestVersionInfo.Version
|
||||
}
|
||||
default { return $Version }
|
||||
}
|
||||
}
|
||||
|
||||
function Get-Download-Link([string]$AzureFeed, [string]$Channel, [string]$SpecificVersion, [string]$CLIArchitecture) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
if ($SharedRuntime) {
|
||||
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-runtime-$SpecificVersion-win-$CLIArchitecture.zip"
|
||||
}
|
||||
else {
|
||||
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-sdk-$SpecificVersion-win-$CLIArchitecture.zip"
|
||||
}
|
||||
|
||||
Say-Verbose "Constructed primary payload URL: $PayloadURL"
|
||||
|
||||
return $PayloadURL
|
||||
}
|
||||
|
||||
function Get-LegacyDownload-Link([string]$AzureFeed, [string]$Channel, [string]$SpecificVersion, [string]$CLIArchitecture) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
if ($SharedRuntime) {
|
||||
$PayloadURL = "$AzureFeed/Runtime/$SpecificVersion/dotnet-win-$CLIArchitecture.$SpecificVersion.zip"
|
||||
}
|
||||
else {
|
||||
$PayloadURL = "$AzureFeed/Sdk/$SpecificVersion/dotnet-dev-win-$CLIArchitecture.$SpecificVersion.zip"
|
||||
}
|
||||
|
||||
Say-Verbose "Constructed legacy payload URL: $PayloadURL"
|
||||
|
||||
return $PayloadURL
|
||||
}
|
||||
|
||||
function Get-User-Share-Path() {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
$InstallRoot = $env:DOTNET_INSTALL_DIR
|
||||
if (!$InstallRoot) {
|
||||
$InstallRoot = "$env:LocalAppData\Microsoft\dotnet"
|
||||
}
|
||||
return $InstallRoot
|
||||
}
|
||||
|
||||
function Resolve-Installation-Path([string]$InstallDir) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
if ($InstallDir -eq "<auto>") {
|
||||
return Get-User-Share-Path
|
||||
}
|
||||
return $InstallDir
|
||||
}
|
||||
|
||||
function Get-Version-Info-From-Version-File([string]$InstallRoot, [string]$RelativePathToVersionFile) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
$VersionFile = Join-Path -Path $InstallRoot -ChildPath $RelativePathToVersionFile
|
||||
Say-Verbose "Local version file: $VersionFile"
|
||||
|
||||
if (Test-Path $VersionFile) {
|
||||
$VersionText = cat $VersionFile
|
||||
Say-Verbose "Local version file text: $VersionText"
|
||||
return Get-Version-Info-From-Version-Text $VersionText
|
||||
}
|
||||
|
||||
Say-Verbose "Local version file not found."
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Is-Dotnet-Package-Installed([string]$InstallRoot, [string]$RelativePathToPackage, [string]$SpecificVersion) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
$DotnetPackagePath = Join-Path -Path $InstallRoot -ChildPath $RelativePathToPackage | Join-Path -ChildPath $SpecificVersion
|
||||
Say-Verbose "Is-Dotnet-Package-Installed: Path to a package: $DotnetPackagePath"
|
||||
return Test-Path $DotnetPackagePath -PathType Container
|
||||
}
|
||||
|
||||
function Get-Absolute-Path([string]$RelativeOrAbsolutePath) {
|
||||
# Too much spam
|
||||
# Say-Invocation $MyInvocation
|
||||
|
||||
return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($RelativeOrAbsolutePath)
|
||||
}
|
||||
|
||||
function Get-Path-Prefix-With-Version($path) {
|
||||
$match = [regex]::match($path, $VersionRegEx)
|
||||
if ($match.Success) {
|
||||
return $entry.FullName.Substring(0, $match.Index + $match.Length)
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package([System.IO.Compression.ZipArchive]$Zip, [string]$OutPath) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
$ret = @()
|
||||
foreach ($entry in $Zip.Entries) {
|
||||
$dir = Get-Path-Prefix-With-Version $entry.FullName
|
||||
if ($dir -ne $null) {
|
||||
$path = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $dir)
|
||||
if (-Not (Test-Path $path -PathType Container)) {
|
||||
$ret += $dir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ret = $ret | Sort-Object | Get-Unique
|
||||
|
||||
$values = ($ret | foreach { "$_" }) -join ";"
|
||||
Say-Verbose "Directories to unpack: $values"
|
||||
|
||||
return $ret
|
||||
}
|
||||
|
||||
# Example zip content and extraction algorithm:
|
||||
# Rule: files if extracted are always being extracted to the same relative path locally
|
||||
# .\
|
||||
# a.exe # file does not exist locally, extract
|
||||
# b.dll # file exists locally, override only if $OverrideFiles set
|
||||
# aaa\ # same rules as for files
|
||||
# ...
|
||||
# abc\1.0.0\ # directory contains version and exists locally
|
||||
# ... # do not extract content under versioned part
|
||||
# abc\asd\ # same rules as for files
|
||||
# ...
|
||||
# def\ghi\1.0.1\ # directory contains version and does not exist locally
|
||||
# ... # extract content
|
||||
function Extract-Dotnet-Package([string]$ZipPath, [string]$OutPath) {
|
||||
Say-Invocation $MyInvocation
|
||||
|
||||
Load-Assembly -Assembly System.IO.Compression.FileSystem
|
||||
Set-Variable -Name Zip
|
||||
try {
|
||||
$Zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)
|
||||
|
||||
$DirectoriesToUnpack = Get-List-Of-Directories-And-Versions-To-Unpack-From-Dotnet-Package -Zip $Zip -OutPath $OutPath
|
||||
|
||||
foreach ($entry in $Zip.Entries) {
|
||||
$PathWithVersion = Get-Path-Prefix-With-Version $entry.FullName
|
||||
if (($PathWithVersion -eq $null) -Or ($DirectoriesToUnpack -contains $PathWithVersion)) {
|
||||
$DestinationPath = Get-Absolute-Path $(Join-Path -Path $OutPath -ChildPath $entry.FullName)
|
||||
$DestinationDir = Split-Path -Parent $DestinationPath
|
||||
$OverrideFiles=$OverrideNonVersionedFiles -Or (-Not (Test-Path $DestinationPath))
|
||||
if ((-Not $DestinationPath.EndsWith("\")) -And $OverrideFiles) {
|
||||
New-Item -ItemType Directory -Force -Path $DestinationDir | Out-Null
|
||||
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $DestinationPath, $OverrideNonVersionedFiles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if ($Zip -ne $null) {
|
||||
$Zip.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function DownloadFile([Uri]$Uri, [string]$OutPath) {
|
||||
$Stream = $null
|
||||
|
||||
try {
|
||||
$Response = GetHTTPResponse -Uri $Uri
|
||||
$Stream = $Response.Content.ReadAsStreamAsync().Result
|
||||
$File = [System.IO.File]::Create($OutPath)
|
||||
$Stream.CopyTo($File)
|
||||
$File.Close()
|
||||
}
|
||||
finally {
|
||||
if ($Stream -ne $null) {
|
||||
$Stream.Dispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Prepend-Sdk-InstallRoot-To-Path([string]$InstallRoot, [string]$BinFolderRelativePath) {
|
||||
$BinPath = Get-Absolute-Path $(Join-Path -Path $InstallRoot -ChildPath $BinFolderRelativePath)
|
||||
if (-Not $NoPath) {
|
||||
Say "Adding to current process PATH: `"$BinPath`". Note: This change will not be visible if PowerShell was run as a child process."
|
||||
$env:path = "$BinPath;" + $env:path
|
||||
}
|
||||
else {
|
||||
Say "Binaries of dotnet can be found in $BinPath"
|
||||
}
|
||||
}
|
||||
|
||||
$CLIArchitecture = Get-CLIArchitecture-From-Architecture $Architecture
|
||||
$SpecificVersion = Get-Specific-Version-From-Version -AzureFeed $AzureFeed -Channel $Channel -Version $Version
|
||||
$DownloadLink = Get-Download-Link -AzureFeed $AzureFeed -Channel $Channel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
|
||||
$LegacyDownloadLink = Get-LegacyDownload-Link -AzureFeed $AzureFeed -Channel $Channel -SpecificVersion $SpecificVersion -CLIArchitecture $CLIArchitecture
|
||||
|
||||
if ($DryRun) {
|
||||
Say "Payload URLs:"
|
||||
Say "Primary - $DownloadLink"
|
||||
Say "Legacy - $LegacyDownloadLink"
|
||||
Say "Repeatable invocation: .\$($MyInvocation.MyCommand) -Version $SpecificVersion -Channel $Channel -Architecture $CLIArchitecture -InstallDir $InstallDir"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$InstallRoot = Resolve-Installation-Path $InstallDir
|
||||
Say-Verbose "InstallRoot: $InstallRoot"
|
||||
|
||||
$IsSdkInstalled = Is-Dotnet-Package-Installed -InstallRoot $InstallRoot -RelativePathToPackage "sdk" -SpecificVersion $SpecificVersion
|
||||
Say-Verbose ".NET SDK installed? $IsSdkInstalled"
|
||||
if ($IsSdkInstalled) {
|
||||
Say ".NET SDK version $SpecificVersion is already installed."
|
||||
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
|
||||
exit 0
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
|
||||
|
||||
$installDrive = $((Get-Item $InstallRoot).PSDrive.Name);
|
||||
Write-Output "${installDrive}:";
|
||||
$free = Get-CimInstance -Class win32_logicaldisk | where Deviceid -eq "${installDrive}:"
|
||||
if ($free.Freespace / 1MB -le 100 ) {
|
||||
Say "There is not enough disk space on drive ${installDrive}:"
|
||||
exit 0
|
||||
}
|
||||
|
||||
$ZipPath = [System.IO.Path]::GetTempFileName()
|
||||
Say-Verbose "Zip path: $ZipPath"
|
||||
Say "Downloading link: $DownloadLink"
|
||||
try {
|
||||
DownloadFile -Uri $DownloadLink -OutPath $ZipPath
|
||||
}
|
||||
catch {
|
||||
Say "Cannot download: $DownloadLink"
|
||||
$DownloadLink = $LegacyDownloadLink
|
||||
$ZipPath = [System.IO.Path]::GetTempFileName()
|
||||
Say-Verbose "Legacy zip path: $ZipPath"
|
||||
Say "Downloading legacy link: $DownloadLink"
|
||||
DownloadFile -Uri $DownloadLink -OutPath $ZipPath
|
||||
}
|
||||
|
||||
Say "Extracting zip from $DownloadLink"
|
||||
Extract-Dotnet-Package -ZipPath $ZipPath -OutPath $InstallRoot
|
||||
|
||||
Remove-Item $ZipPath
|
||||
|
||||
Prepend-Sdk-InstallRoot-To-Path -InstallRoot $InstallRoot -BinFolderRelativePath $BinFolderRelativePath
|
||||
|
||||
Say "Installation finished"
|
||||
exit 0
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<FunctionalTestsProject>$(RepositoryRoot)test\Microsoft.AspNetCore.AzureAppServices.FunctionalTests\Microsoft.AspNetCore.AzureAppServices.FunctionalTests.csproj</FunctionalTestsProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ExcludeFromPack Include="$(RepositoryRoot)src\Microsoft.AspNetCore.AzureAppServices.TestBundle\Microsoft.AspNetCore.AzureAppServices.TestBundle.csproj" />
|
||||
|
||||
<ExcludeFromTest
|
||||
Condition="'$(AntaresTests)' != ''"
|
||||
Include="$(RepositoryRoot)test\*\*.csproj"
|
||||
Exclude="$(FunctionalTestsProject)" />
|
||||
|
||||
<ExcludeFromTest
|
||||
Condition="'$(AntaresTests)' == ''"
|
||||
Include="$(FunctionalTestsProject)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<SiteExtensionProjectDirectory>$(RepositoryRoot)src\Microsoft.AspNetCore.AzureAppServices.TestBundle\</SiteExtensionProjectDirectory>
|
||||
<PublishFeed Condition="$(PublishFeed) == ''">https://dotnet.myget.org/F/aspnetcore-ci-dev/</PublishFeed>
|
||||
<DotnetChannel>master</DotnetChannel>
|
||||
<DotnetVersion>coherent</DotnetVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="BuildSiteExtension">
|
||||
|
||||
<PropertyGroup>
|
||||
<SiteExtensionWorkingDirectory>$(RepositoryRoot).test-dotnet\</SiteExtensionWorkingDirectory>
|
||||
<CliVersionRelativePath>build\dotnet.version</CliVersionRelativePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<RemoveDir Directories="$(SiteExtensionWorkingDirectory)" />
|
||||
<Exec Command="$(MSBuildThisFileDirectory)\dotnet-install.cmd -Channel $(DotnetChannel) -Version $(DotnetVersion) -Architecture x86 -InstallDir $(SiteExtensionWorkingDirectory) -NoPath" />
|
||||
<MSBuild Projects="$(SiteExtensionProjectDirectory)Microsoft.AspNetCore.AzureAppServices.TestBundle.csproj"
|
||||
Targets="Restore;Pack"
|
||||
Properties="DotnetHomeDirectory=$(SiteExtensionWorkingDirectory)" />
|
||||
|
||||
<Exec Command="$(SiteExtensionWorkingDirectory)dotnet --version > $(RepositoryRoot)artifacts\$(CliVersionRelativePath)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="PushSiteExtension">
|
||||
<ItemGroup>
|
||||
<RepositoryNupkgs Include="$(SiteExtensionProjectDirectory)bin\$(Configuration)\*.nupkg" />
|
||||
</ItemGroup>
|
||||
|
||||
<PushNuGetPackages
|
||||
Packages="@(RepositoryNupkgs)"
|
||||
Feed="$(PublishFeed)"
|
||||
ApiKey="$(APIKey)"
|
||||
Timeout="6000"/>
|
||||
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<Title>ASP.NET Core Test Bundle Extensions</Title>
|
||||
<Description>This extension enables testing functionality of ASP.NET Core on Azure WebSites.</Description>
|
||||
<TargetFramework>net461</TargetFramework>
|
||||
<GenerateDocumentationFile>false</GenerateDocumentationFile>
|
||||
<PackageTags>aspnet;logging;aspnetcore;AzureSiteExtension</PackageTags>
|
||||
<PackageType>AzureSiteExtension</PackageType>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<IncludeSymbols>false</IncludeSymbols>
|
||||
<IncludeSource>false</IncludeSource>
|
||||
<ContentTargetFolders>content</ContentTargetFolders>
|
||||
<PackageId>AspNetCoreTestBundle</PackageId>
|
||||
<!-- These need to be set manually because this is not marked as a shipping (to nuget.org) package. https://github.com/aspnet/AzureIntegration/issues/38 -->
|
||||
<PackageLicenseUrl>https://github.com/aspnet/AzureIntegration/blob/rel/2.0.0-preview1/LICENSE.txt</PackageLicenseUrl>
|
||||
<PackageIconUrl>https://go.microsoft.com/fwlink/?LinkID=288859</PackageIconUrl>
|
||||
<PackageProjectUrl>https://www.asp.net/</PackageProjectUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="applicationHost.xdt" />
|
||||
<Content Include="$(OutputPath)\Microsoft.Web.Xdt.Extensions.dll" PackagePath="content" />
|
||||
<Content Include="$(DotnetHomeDirectory)\**\*.*" Exclude="$(DotnetHomeDirectory)\**\nuGetPackagesArchive.lzma" Condition="$(DotnetHomeDirectory) != ''" PackagePath="content\%(RecursiveDir)%(FileName)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Microsoft.Web.Xdt.Extensions\Microsoft.Web.Xdt.Extensions.csproj" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
||||
|
||||
<xdt:Import path="%XDT_EXTENSIONPATH%\Microsoft.Web.Xdt.Extensions.dll"
|
||||
namespace="Microsoft.Web.Xdt.Extensions" />
|
||||
|
||||
<system.webServer xdt:Transform="InsertIfMissing">
|
||||
<runtime xdt:Transform="InsertIfMissing" >
|
||||
<environmentVariables xdt:Transform="InsertIfMissing">
|
||||
<add name="PATH" value="%XDT_EXTENSIONPATH%" xdt:Locator="Match(name)" xdt:Transform="InsertOrAppendAttribute(Attribute='value')" />
|
||||
</environmentVariables>
|
||||
</runtime>
|
||||
</system.webServer>
|
||||
</configuration>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests
|
||||
{
|
||||
[CollectionDefinition("Azure")]
|
||||
public class AzureCollection : ICollectionFixture<AzureFixture>
|
||||
{
|
||||
// This class has no code, and is never created. Its purpose is simply
|
||||
// to be the place to apply [CollectionDefinition] and all the
|
||||
// ICollectionFixture<> interfaces.
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Management.AppService.Fluent;
|
||||
using Microsoft.Azure.Management.Fluent;
|
||||
using Microsoft.Azure.Management.ResourceManager.Fluent;
|
||||
using Microsoft.Azure.Management.ResourceManager.Fluent.Core;
|
||||
using Microsoft.Azure.Management.ResourceManager.Fluent.Models;
|
||||
using Microsoft.Azure.Management.Storage.Fluent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Testing;
|
||||
using Microsoft.Rest;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests
|
||||
{
|
||||
public class AzureFixture : IDisposable
|
||||
{
|
||||
public string Timestamp { get; set; }
|
||||
|
||||
public AzureFixture()
|
||||
{
|
||||
TestLog = AssemblyTestLog.ForAssembly(typeof(AzureFixture).Assembly);
|
||||
|
||||
// TODO: Temporary to see if it's useful and worth exposing
|
||||
var globalLoggerFactory =
|
||||
(ILoggerFactory) TestLog.GetType().GetField("_globalLoggerFactory", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.GetValue(TestLog);
|
||||
|
||||
var logger = globalLoggerFactory.CreateLogger<AzureFixture>();
|
||||
|
||||
ServiceClientTracing.IsEnabled = true;
|
||||
ServiceClientTracing.AddTracingInterceptor(new LoggingInterceptor(globalLoggerFactory.CreateLogger(nameof(ServiceClientTracing))));
|
||||
|
||||
var clientId = GetRequiredEnvironmentVariable("AZURE_AUTH_CLIENT_ID");
|
||||
var clientSecret = GetRequiredEnvironmentVariable("AZURE_AUTH_CLIENT_SECRET");
|
||||
var tenant = GetRequiredEnvironmentVariable("AZURE_AUTH_TENANT");
|
||||
|
||||
var credentials = SdkContext.AzureCredentialsFactory.FromServicePrincipal(clientId, clientSecret, tenant, AzureEnvironment.AzureGlobalCloud);
|
||||
Azure = Microsoft.Azure.Management.Fluent.Azure.Configure()
|
||||
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
|
||||
.Authenticate(credentials)
|
||||
.WithDefaultSubscription();
|
||||
|
||||
Timestamp = DateTime.Now.ToString("yyyyMMddhhmmss");
|
||||
var testRunName = GetTimestampedName("FunctionalTests");
|
||||
|
||||
logger.LogInformation("Creating resource group {TestRunName}", testRunName);
|
||||
ResourceGroup = Azure.ResourceGroups
|
||||
.Define(testRunName)
|
||||
.WithRegion(Region.USWest2)
|
||||
.Create();
|
||||
|
||||
var servicePlanName = GetTimestampedName("TestPlan");
|
||||
logger.LogInformation("Creating service plan {servicePlanName}", testRunName);
|
||||
|
||||
Plan = Azure.AppServices.AppServicePlans.Define(servicePlanName)
|
||||
.WithRegion(Region.USWest2)
|
||||
.WithExistingResourceGroup(ResourceGroup)
|
||||
.WithFreePricingTier()
|
||||
.Create();
|
||||
}
|
||||
|
||||
private static string GetRequiredEnvironmentVariable(string name)
|
||||
{
|
||||
var authFile = Environment.GetEnvironmentVariable(name);
|
||||
if (string.IsNullOrEmpty(authFile))
|
||||
{
|
||||
throw new InvalidOperationException($"{name} environment variable has to be set to run these tests.");
|
||||
}
|
||||
|
||||
return authFile;
|
||||
}
|
||||
|
||||
public IAppServicePlan Plan { get; set; }
|
||||
|
||||
public IStorageAccount DeploymentStorageAccount { get; set; }
|
||||
|
||||
public AssemblyTestLog TestLog { get; set; }
|
||||
|
||||
public bool DeleteResourceGroup { get; set; } = true;
|
||||
|
||||
public IResourceGroup ResourceGroup { get; set; }
|
||||
|
||||
public IAzure Azure { get; set; }
|
||||
|
||||
public string GetTimestampedName(string name)
|
||||
{
|
||||
return name + Timestamp;
|
||||
}
|
||||
|
||||
public async Task<IWebApp> Deploy(string template, IDictionary<string, string> additionalArguments = null, [CallerMemberName] string baseName = null)
|
||||
{
|
||||
var siteName = GetTimestampedName(baseName);
|
||||
var parameters = new Dictionary<string, string>
|
||||
{
|
||||
{"siteName", siteName},
|
||||
{"hostingPlanName", Plan.Name},
|
||||
{"resourceGroupName", ResourceGroup.Name},
|
||||
};
|
||||
|
||||
foreach (var pair in additionalArguments ?? Enumerable.Empty<KeyValuePair<string, string>>())
|
||||
{
|
||||
parameters[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
var readAllText = File.ReadAllText(template);
|
||||
var deployment = await Azure.Deployments.Define(GetTimestampedName("Deployment"))
|
||||
.WithExistingResourceGroup(ResourceGroup)
|
||||
.WithTemplate(readAllText)
|
||||
.WithParameters(ToParametersObject(parameters))
|
||||
.WithMode(DeploymentMode.Incremental)
|
||||
.CreateAsync();
|
||||
|
||||
deployment = await deployment.RefreshAsync();
|
||||
|
||||
var outputs = (JObject)deployment.Outputs;
|
||||
|
||||
var siteIdOutput = outputs["siteId"];
|
||||
if (siteIdOutput == null)
|
||||
{
|
||||
throw new InvalidOperationException("Deployment was expected to have 'siteId' output parameter");
|
||||
}
|
||||
var siteId = siteIdOutput["value"].Value<string>();
|
||||
return await Azure.AppServices.WebApps.GetByIdAsync(siteId);
|
||||
}
|
||||
|
||||
private JObject ToParametersObject(Dictionary<string, string> parameters)
|
||||
{
|
||||
return new JObject(
|
||||
parameters.Select(parameter =>
|
||||
new JProperty(
|
||||
parameter.Key,
|
||||
new JObject(
|
||||
new JProperty("value", parameter.Value)))));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
TestLog.Dispose();
|
||||
if (DeleteResourceGroup && ResourceGroup != null)
|
||||
{
|
||||
Azure.ResourceGroups.DeleteByName(ResourceGroup.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests
|
||||
{
|
||||
public struct CommandResult
|
||||
{
|
||||
public static readonly CommandResult Empty = new CommandResult();
|
||||
|
||||
public ProcessStartInfo StartInfo { get; }
|
||||
public int ExitCode { get; }
|
||||
public string StdOut { get; }
|
||||
public string StdErr { get; }
|
||||
|
||||
public CommandResult(ProcessStartInfo startInfo, int exitCode, string stdOut, string stdErr)
|
||||
{
|
||||
StartInfo = startInfo;
|
||||
ExitCode = exitCode;
|
||||
StdOut = stdOut;
|
||||
StdErr = stdErr;
|
||||
}
|
||||
|
||||
public void AssertSuccess()
|
||||
{
|
||||
Assert.True(0 == ExitCode, StdOut + Environment.NewLine + StdErr + Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Rest;
|
||||
|
||||
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests
|
||||
{
|
||||
public class LoggingInterceptor : IServiceClientTracingInterceptor
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public LoggingInterceptor(ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Information(string message)
|
||||
{
|
||||
_logger.LogInformation(message);
|
||||
}
|
||||
|
||||
public void TraceError(string invocationId, Exception exception)
|
||||
{
|
||||
_logger.LogInformation(exception, "Exception in {invocationId}", invocationId);
|
||||
}
|
||||
|
||||
public void ReceiveResponse(string invocationId, HttpResponseMessage response)
|
||||
{
|
||||
_logger.LogInformation(response.AsFormattedString());
|
||||
}
|
||||
|
||||
public void SendRequest(string invocationId, HttpRequestMessage request)
|
||||
{
|
||||
_logger.LogInformation(request.AsFormattedString());
|
||||
}
|
||||
|
||||
public void Configuration(string source, string name, string value) { }
|
||||
|
||||
public void EnterMethod(string invocationId, object instance, string method, IDictionary<string, object> parameters) { }
|
||||
|
||||
public void ExitMethod(string invocationId, object returnValue) { }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<Import Project="..\..\build\common.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net461</TargetFrameworks>
|
||||
<LangVersion>7.1</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Templates\*.*" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(AspNetIntegrationTestingVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(AspNetCoreVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(AspNetCoreVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.Process.Sources" Version="$(AspNetCoreVersion)" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XunitVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XunitVersion)" />
|
||||
|
||||
<PackageReference Include="Microsoft.Azure.Management.Fluent" Version="$(MicrosoftAzureManagementFluentVersion)" />
|
||||
<PackageReference Include="WindowsAzure.Storage" Version="$(WindowsAzureStorageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Azure.Management.AppService.Fluent;
|
||||
using Microsoft.Azure.Management.AppService.Fluent.Models;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests
|
||||
{
|
||||
[Collection("Azure")]
|
||||
public class TemplateFunctionalTests
|
||||
{
|
||||
readonly AzureFixture _fixture;
|
||||
|
||||
private readonly ITestOutputHelper _outputHelper;
|
||||
|
||||
public TemplateFunctionalTests(AzureFixture fixture, ITestOutputHelper outputHelper)
|
||||
{
|
||||
_fixture = fixture;
|
||||
_outputHelper = outputHelper;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DotnetNewWebRunsInWebApp()
|
||||
{
|
||||
using (var logger = GetLogger())
|
||||
{
|
||||
Assert.NotNull(_fixture.Azure);
|
||||
|
||||
var site = await _fixture.Deploy("Templates\\BasicAppServices.json", null);
|
||||
var testDirectory = GetTestDirectory();
|
||||
|
||||
var dotnet = DotNet(logger, testDirectory);
|
||||
|
||||
var result = await dotnet.ExecuteAsync("new web");
|
||||
result.AssertSuccess();
|
||||
|
||||
await site.BuildPublishProfileAsync(testDirectory.FullName);
|
||||
|
||||
result = await dotnet.ExecuteAsync("publish /p:PublishProfile=Profile");
|
||||
result.AssertSuccess();
|
||||
|
||||
using (var httpClient = site.CreateClient())
|
||||
{
|
||||
var getResult = await httpClient.GetAsync("/");
|
||||
getResult.EnsureSuccessStatusCode();
|
||||
Assert.Equal("Hello World!", await getResult.Content.ReadAsStringAsync());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TestLogger GetLogger([CallerMemberName] string callerName = null)
|
||||
{
|
||||
_fixture.TestLog.StartTestLog(_outputHelper, nameof(TemplateFunctionalTests), out var factory, callerName);
|
||||
return new TestLogger(factory, factory.CreateLogger(callerName));
|
||||
}
|
||||
|
||||
private TestCommand DotNet(TestLogger logger, DirectoryInfo workingDirectory)
|
||||
{
|
||||
return new TestCommand("dotnet")
|
||||
{
|
||||
Logger = logger,
|
||||
WorkingDirectory = workingDirectory.FullName
|
||||
};
|
||||
}
|
||||
|
||||
private DirectoryInfo GetTestDirectory([CallerMemberName] string callerName = null)
|
||||
{
|
||||
if (Directory.Exists(callerName))
|
||||
{
|
||||
Directory.Delete(callerName, recursive:true);
|
||||
}
|
||||
return Directory.CreateDirectory(callerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"siteName": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostingPlanName": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceGroupName": {
|
||||
"type": "string"
|
||||
},
|
||||
"extensionFeed": {
|
||||
"type": "string"
|
||||
},
|
||||
"extensionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"extensionVersion": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2015-08-01",
|
||||
"name": "[parameters('siteName')]",
|
||||
"type": "Microsoft.Web/sites",
|
||||
"location": "West US 2",
|
||||
"properties": {
|
||||
"serverFarmId": "[resourceId(parameters('resourceGroupName'), 'Microsoft.Web/serverFarms', parameters('hostingPlanName'))]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"type": "extensions",
|
||||
"name": "[parameters('extensionName')]",
|
||||
"apiVersion": "2015-08-01",
|
||||
"location": "West US 2",
|
||||
"properties": {
|
||||
"version": "[parameters('extensionVersion')]",
|
||||
"feed_url": "[parameters('extensionFeed')]"
|
||||
},
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Web/Sites', parameters('siteName'))]"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"siteId": {
|
||||
"type": "string",
|
||||
"value": "[resourceId(parameters('resourceGroupName'), 'Microsoft.Web/sites', parameters('siteName'))]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"siteName": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostingPlanName": {
|
||||
"type": "string"
|
||||
},
|
||||
"resourceGroupName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2015-08-01",
|
||||
"name": "[parameters('siteName')]",
|
||||
"type": "Microsoft.Web/sites",
|
||||
"location": "West US 2",
|
||||
"properties": {
|
||||
"serverFarmId": "[resourceId(parameters('resourceGroupName'), 'Microsoft.Web/serverFarms', parameters('hostingPlanName'))]"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"siteId": {
|
||||
"type": "string",
|
||||
"value": "[resourceId(parameters('resourceGroupName'), 'Microsoft.Web/sites', parameters('siteName'))]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests
|
||||
{
|
||||
public class TestCommand
|
||||
{
|
||||
private string _dotnetPath = GetDotnetPath();
|
||||
|
||||
private static string GetDotnetPath()
|
||||
{
|
||||
var current = new DirectoryInfo(Directory.GetCurrentDirectory());
|
||||
while (current != null)
|
||||
{
|
||||
var dotnetSubdir = new DirectoryInfo(Path.Combine(current.FullName, ".test-dotnet"));
|
||||
if (dotnetSubdir.Exists)
|
||||
{
|
||||
var dotnetName = Path.Combine(dotnetSubdir.FullName, "dotnet.exe");
|
||||
if (!File.Exists(dotnetName))
|
||||
{
|
||||
throw new InvalidOperationException("dotnet directory was found but dotnet.exe is not in it");
|
||||
}
|
||||
return dotnetName;
|
||||
}
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("dotnet executable was not found");
|
||||
}
|
||||
|
||||
private List<string> _cliGeneratedEnvironmentVariables = new List<string> { "MSBuildSDKsPath" };
|
||||
|
||||
protected string _command;
|
||||
|
||||
public Process CurrentProcess { get; private set; }
|
||||
|
||||
public Dictionary<string, string> Environment { get; } = new Dictionary<string, string>();
|
||||
|
||||
public event DataReceivedEventHandler ErrorDataReceived;
|
||||
|
||||
public event DataReceivedEventHandler OutputDataReceived;
|
||||
|
||||
public string WorkingDirectory { get; set; }
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public TestCommand(string command)
|
||||
{
|
||||
_command = command;
|
||||
}
|
||||
|
||||
public void KillTree()
|
||||
{
|
||||
if (CurrentProcess == null)
|
||||
{
|
||||
throw new InvalidOperationException("No process is available to be killed");
|
||||
}
|
||||
|
||||
CurrentProcess.KillTree();
|
||||
}
|
||||
|
||||
public virtual async Task<CommandResult> ExecuteAsync(string args = "")
|
||||
{
|
||||
var resolvedCommand = _command;
|
||||
|
||||
ResolveCommand(ref resolvedCommand, ref args);
|
||||
|
||||
Logger.LogInformation($"Executing - {resolvedCommand} {args} - {WorkingDirectoryInfo()}");
|
||||
|
||||
return await ExecuteAsyncInternal(resolvedCommand, args);
|
||||
}
|
||||
|
||||
private async Task<CommandResult> ExecuteAsyncInternal(string executable, string args)
|
||||
{
|
||||
var stdOut = new List<String>();
|
||||
|
||||
var stdErr = new List<String>();
|
||||
|
||||
CurrentProcess = CreateProcess(executable, args);
|
||||
|
||||
CurrentProcess.ErrorDataReceived += (s, e) =>
|
||||
{
|
||||
stdErr.Add(e.Data);
|
||||
|
||||
var handler = ErrorDataReceived;
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
handler(s, e);
|
||||
}
|
||||
};
|
||||
|
||||
CurrentProcess.OutputDataReceived += (s, e) =>
|
||||
{
|
||||
stdOut.Add(e.Data);
|
||||
|
||||
var handler = OutputDataReceived;
|
||||
|
||||
if (handler != null)
|
||||
{
|
||||
handler(s, e);
|
||||
}
|
||||
};
|
||||
|
||||
var completionTask = StartAndWaitForExitAsync(CurrentProcess);
|
||||
|
||||
CurrentProcess.BeginOutputReadLine();
|
||||
|
||||
CurrentProcess.BeginErrorReadLine();
|
||||
|
||||
await completionTask;
|
||||
|
||||
CurrentProcess.WaitForExit();
|
||||
|
||||
RemoveNullTerminator(stdOut);
|
||||
|
||||
RemoveNullTerminator(stdErr);
|
||||
|
||||
var stdOutString = String.Join(System.Environment.NewLine, stdOut);
|
||||
var stdErrString = String.Join(System.Environment.NewLine, stdErr);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(stdOutString))
|
||||
{
|
||||
Logger.LogInformation("stdout: {out}", stdOutString);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(stdErrString))
|
||||
{
|
||||
Logger.LogInformation("stderr: {err}", stdErrString);
|
||||
}
|
||||
|
||||
return new CommandResult(
|
||||
CurrentProcess.StartInfo,
|
||||
CurrentProcess.ExitCode,
|
||||
stdOutString,
|
||||
stdErrString);
|
||||
}
|
||||
|
||||
private Process CreateProcess(string executable, string args)
|
||||
{
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = executable,
|
||||
Arguments = args,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardInput = true,
|
||||
UseShellExecute = false
|
||||
};
|
||||
|
||||
RemoveCliGeneratedEnvironmentVariablesFrom(psi);
|
||||
|
||||
AddEnvironmentVariablesTo(psi);
|
||||
|
||||
AddWorkingDirectoryTo(psi);
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = psi
|
||||
};
|
||||
|
||||
process.EnableRaisingEvents = true;
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
private string WorkingDirectoryInfo()
|
||||
{
|
||||
if (WorkingDirectory == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return $" in {WorkingDirectory}";
|
||||
}
|
||||
|
||||
private void RemoveNullTerminator(List<string> strings)
|
||||
{
|
||||
var count = strings.Count;
|
||||
|
||||
if (count < 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (strings[count - 1] == null)
|
||||
{
|
||||
strings.RemoveAt(count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveCommand(ref string executable, ref string args)
|
||||
{
|
||||
if (executable == "dotnet")
|
||||
{
|
||||
executable = _dotnetPath;
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ArgumentOutOfRangeException(nameof(executable));
|
||||
}
|
||||
|
||||
private void RemoveCliGeneratedEnvironmentVariablesFrom(ProcessStartInfo psi)
|
||||
{
|
||||
foreach (var name in _cliGeneratedEnvironmentVariables)
|
||||
{
|
||||
psi.Environment.Remove(name);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddEnvironmentVariablesTo(ProcessStartInfo psi)
|
||||
{
|
||||
foreach (var item in Environment)
|
||||
{
|
||||
psi.Environment[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddWorkingDirectoryTo(ProcessStartInfo psi)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(WorkingDirectory))
|
||||
{
|
||||
psi.WorkingDirectory = WorkingDirectory;
|
||||
}
|
||||
}
|
||||
public static Task StartAndWaitForExitAsync(Process subject)
|
||||
{
|
||||
var taskCompletionSource = new TaskCompletionSource<object>();
|
||||
|
||||
subject.EnableRaisingEvents = true;
|
||||
|
||||
subject.Exited += (s, a) =>
|
||||
{
|
||||
taskCompletionSource.SetResult(null);
|
||||
};
|
||||
|
||||
subject.Start();
|
||||
|
||||
return taskCompletionSource.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests
|
||||
{
|
||||
internal class TestLogger: ILogger, IDisposable
|
||||
{
|
||||
private readonly ILoggerFactory _factory;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public TestLogger(ILoggerFactory factory, ILogger logger)
|
||||
{
|
||||
_factory = factory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||
{
|
||||
_logger.Log(logLevel, eventId, state, exception, formatter);
|
||||
}
|
||||
|
||||
public bool IsEnabled(LogLevel logLevel)
|
||||
{
|
||||
return _logger.IsEnabled(logLevel);
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state)
|
||||
{
|
||||
return _logger.BeginScope(state);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_factory.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Azure.Management.AppService.Fluent;
|
||||
using Microsoft.Azure.Management.AppService.Fluent.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNetCore.AzureAppServices.FunctionalTests
|
||||
{
|
||||
internal static class WebAppExtensions
|
||||
{
|
||||
public static HttpClient CreateClient(this IWebApp site)
|
||||
{
|
||||
var domain = site.GetHostNameBindings().First().Key;
|
||||
|
||||
return new HttpClient { BaseAddress = new Uri("http://" + domain) };
|
||||
}
|
||||
|
||||
public static async Task UploadFilesAsync(this IWebApp site, DirectoryInfo from, string to, IPublishingProfile publishingProfile, ILogger logger)
|
||||
{
|
||||
foreach (var info in from.GetFileSystemInfos("*", SearchOption.AllDirectories))
|
||||
{
|
||||
if (info is FileInfo file)
|
||||
{
|
||||
var address = new Uri(
|
||||
"ftp://" + publishingProfile.FtpUrl + to + file.FullName.Substring(from.FullName.Length).Replace('\\', '/'));
|
||||
logger.LogInformation($"Uploading {file.FullName} to {address}");
|
||||
|
||||
var request = (FtpWebRequest)WebRequest.Create(address);
|
||||
request.Method = WebRequestMethods.Ftp.UploadFile;
|
||||
request.KeepAlive = true;
|
||||
request.UseBinary = true;
|
||||
request.UsePassive = false;
|
||||
request.Credentials = new NetworkCredential(publishingProfile.FtpUsername, publishingProfile.FtpPassword);
|
||||
request.ConnectionGroupName = "group";
|
||||
using (var fileStream = File.OpenRead(file.FullName))
|
||||
{
|
||||
using (var requestStream = await request.GetRequestStreamAsync())
|
||||
{
|
||||
await fileStream.CopyToAsync(requestStream);
|
||||
}
|
||||
}
|
||||
await request.GetResponseAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task BuildPublishProfileAsync(this IWebApp site, string projectDirectory)
|
||||
{
|
||||
var result = await site.Manager.WebApps.Inner.ListPublishingProfileXmlWithSecretsAsync(
|
||||
site.ResourceGroupName,
|
||||
site.Name,
|
||||
new CsmPublishingProfileOptionsInner());
|
||||
|
||||
var targetDirectory = Path.Combine(projectDirectory, "Properties", "PublishProfiles");
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
|
||||
var publishSettings = XDocument.Load(result);
|
||||
foreach (var profile in publishSettings.Root.Elements("publishProfile"))
|
||||
{
|
||||
if ((string) profile.Attribute("publishMethod") == "MSDeploy")
|
||||
{
|
||||
new XDocument(
|
||||
new XElement("Project",
|
||||
new XElement("PropertyGroup",
|
||||
new XElement("WebPublishMethod", "MSDeploy"),
|
||||
new XElement("PublishProvider", "AzureWebSite"),
|
||||
new XElement("UserName", (string)profile.Attribute("userName")),
|
||||
new XElement("Password", (string)profile.Attribute("userPWD")),
|
||||
new XElement("MSDeployServiceURL", (string)profile.Attribute("publishUrl")),
|
||||
new XElement("DeployIisAppPath", (string)profile.Attribute("msdeploySite"))
|
||||
)))
|
||||
.Save(Path.Combine(targetDirectory, "Profile.pubxml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue