Temporarily copy the dotnet/buildtools tasks we use into dotnet/cli to allow us to move up to a newer MSBuild.
This commit is contained in:
parent
16692fc75c
commit
d5179f9168
9 changed files with 1179 additions and 8 deletions
|
@ -1,12 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<Import Project="$(BuildToolsDir)PublishContent.targets" />
|
<Import Project="$(MSBuildThisFileDirectory)/publish/PublishContent.targets" />
|
||||||
<Import Project="$(MSBuildThisFileDirectory)/publish/Microsoft.DotNet.Cli.Badge.targets" />
|
<Import Project="$(MSBuildThisFileDirectory)/publish/Microsoft.DotNet.Cli.Badge.targets" />
|
||||||
|
|
||||||
<UsingTask TaskName="UploadToAzure" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
|
|
||||||
<UsingTask TaskName="FinalizeBuild" AssemblyFile="$(CLIBuildDll)" />
|
|
||||||
<UsingTask TaskName="SetBlobPropertiesBasedOnFileType" AssemblyFile="$(CLIBuildDll)" />
|
|
||||||
|
|
||||||
<!-- PUBLISH_TO_AZURE_BLOB env variable set by CI -->
|
<!-- PUBLISH_TO_AZURE_BLOB env variable set by CI -->
|
||||||
<Target Name="Publish"
|
<Target Name="Publish"
|
||||||
Condition=" '$(PUBLISH_TO_AZURE_BLOB)' != '' "
|
Condition=" '$(PUBLISH_TO_AZURE_BLOB)' != '' "
|
||||||
|
|
|
@ -23,7 +23,8 @@
|
||||||
<UsingTask TaskName="SetEnvVar" AssemblyFile="$(CLIBuildDll)" />
|
<UsingTask TaskName="SetEnvVar" AssemblyFile="$(CLIBuildDll)" />
|
||||||
<UsingTask TaskName="TarGzFileCreateFromDirectory" AssemblyFile="$(CLIBuildDll)" />
|
<UsingTask TaskName="TarGzFileCreateFromDirectory" AssemblyFile="$(CLIBuildDll)" />
|
||||||
<UsingTask TaskName="TarGzFileExtractToDirectory" AssemblyFile="$(CLIBuildDll)" />
|
<UsingTask TaskName="TarGzFileExtractToDirectory" AssemblyFile="$(CLIBuildDll)" />
|
||||||
<UsingTask TaskName="UploadToAzure" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
|
<UsingTask TaskName="CreateAzureContainer" AssemblyFile="$(CLIBuildDll)"/>
|
||||||
<UsingTask TaskName="ZipFileCreateFromDirectory" AssemblyFile="$(BuildToolsDir)Microsoft.DotNet.Build.Tasks.dll"/>
|
<UsingTask TaskName="UploadToAzure" AssemblyFile="$(CLIBuildDll)"/>
|
||||||
<UsingTask TaskName="ZipFileExtractToDirectory" AssemblyFile="$(BuildToolsDir)Microsoft.DotNet.Build.Tasks.dll"/>
|
<UsingTask TaskName="ZipFileCreateFromDirectory" AssemblyFile="$(CLIBuildDll)"/>
|
||||||
|
<UsingTask TaskName="ZipFileExtractToDirectory" AssemblyFile="$(CLIBuildDll)"/>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
41
build/publish/PublishContent.targets
Normal file
41
build/publish/PublishContent.targets
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OverwriteOnPublish Condition="'$(OverwriteOnPublish)' == ''">false</OverwriteOnPublish>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- gathers the items to be published -->
|
||||||
|
<Target Name="GatherItemsForPattern">
|
||||||
|
<Error Condition="'$(PublishPattern)' == ''" Text="Please specify a value for PublishPattern using standard msbuild 'include' syntax." />
|
||||||
|
<ItemGroup>
|
||||||
|
<ForPublishing Include="$(PublishPattern)" />
|
||||||
|
</ItemGroup>
|
||||||
|
<!-- add relative blob path metadata -->
|
||||||
|
<ItemGroup>
|
||||||
|
<ForPublishing>
|
||||||
|
<RelativeBlobPath>$([System.String]::Copy('%(RecursiveDir)%(Filename)%(Extension)').Replace('\' ,'/'))</RelativeBlobPath>
|
||||||
|
</ForPublishing>
|
||||||
|
</ItemGroup>
|
||||||
|
<Error Condition="'@(ForPublishing)' == ''" Text="No items were found matching pattern '$(PublishPattern)'." />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
<!-- publishes items to blob storage in Azure -->
|
||||||
|
<Target Name="UploadToAzure" DependsOnTargets="GatherItemsForPattern">
|
||||||
|
<Error Condition="'$(ContainerName)' == ''" Text="Missing property ContainerName." />
|
||||||
|
<Error Condition="'$(CloudDropAccountName)' == ''" Text="Missing property CloudDropAccountName." />
|
||||||
|
<Error Condition="'$(CloudDropAccessToken)' == ''" Text="Missing property CloudDropAccessToken." />
|
||||||
|
<!-- create the container if it doesn't exist -->
|
||||||
|
<CreateAzureContainer
|
||||||
|
AccountKey="$(CloudDropAccessToken)"
|
||||||
|
AccountName="$(CloudDropAccountName)"
|
||||||
|
ContainerName="$(ContainerName)" />
|
||||||
|
<!-- now upload the items -->
|
||||||
|
<UploadToAzure
|
||||||
|
AccountKey="$(CloudDropAccessToken)"
|
||||||
|
AccountName="$(CloudDropAccountName)"
|
||||||
|
ContainerName="$(ContainerName)"
|
||||||
|
Items="@(ForPublishing)"
|
||||||
|
Overwrite="$(OverwriteOnPublish)" />
|
||||||
|
</Target>
|
||||||
|
|
||||||
|
</Project>
|
401
build_projects/dotnet-cli-build/AzureHelper.cs
Normal file
401
build_projects/dotnet-cli-build/AzureHelper.cs
Normal file
|
@ -0,0 +1,401 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.Build.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Build.CloudTestTasks
|
||||||
|
{
|
||||||
|
public static class AzureHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The storage api version.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly string StorageApiVersion = "2015-04-05";
|
||||||
|
|
||||||
|
public const string DateHeaderString = "x-ms-date";
|
||||||
|
|
||||||
|
public const string VersionHeaderString = "x-ms-version";
|
||||||
|
|
||||||
|
public const string AuthorizationHeaderString = "Authorization";
|
||||||
|
|
||||||
|
public enum SasAccessType
|
||||||
|
{
|
||||||
|
Read,
|
||||||
|
Write,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static string AuthorizationHeader(
|
||||||
|
string storageAccount,
|
||||||
|
string storageKey,
|
||||||
|
string method,
|
||||||
|
DateTime now,
|
||||||
|
HttpRequestMessage request,
|
||||||
|
string ifMatch = "",
|
||||||
|
string contentMD5 = "",
|
||||||
|
string size = "",
|
||||||
|
string contentType = "")
|
||||||
|
{
|
||||||
|
string stringToSign = string.Format(
|
||||||
|
"{0}\n\n\n{1}\n{5}\n{6}\n\n\n{2}\n\n\n\n{3}{4}",
|
||||||
|
method,
|
||||||
|
(size == string.Empty) ? string.Empty : size,
|
||||||
|
ifMatch,
|
||||||
|
GetCanonicalizedHeaders(request),
|
||||||
|
GetCanonicalizedResource(request.RequestUri, storageAccount),
|
||||||
|
contentMD5,
|
||||||
|
contentType);
|
||||||
|
|
||||||
|
byte[] signatureBytes = Encoding.UTF8.GetBytes(stringToSign);
|
||||||
|
string authorizationHeader;
|
||||||
|
using (HMACSHA256 hmacsha256 = new HMACSHA256(Convert.FromBase64String(storageKey)))
|
||||||
|
{
|
||||||
|
authorizationHeader = "SharedKey " + storageAccount + ":"
|
||||||
|
+ Convert.ToBase64String(hmacsha256.ComputeHash(signatureBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizationHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateContainerSasToken(
|
||||||
|
string accountName,
|
||||||
|
string containerName,
|
||||||
|
string key,
|
||||||
|
SasAccessType accessType,
|
||||||
|
int validityTimeInDays)
|
||||||
|
{
|
||||||
|
string signedPermissions = string.Empty;
|
||||||
|
switch (accessType)
|
||||||
|
{
|
||||||
|
case SasAccessType.Read:
|
||||||
|
signedPermissions = "r";
|
||||||
|
break;
|
||||||
|
case SasAccessType.Write:
|
||||||
|
signedPermissions = "wdl";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(accessType), accessType, "Unrecognized value");
|
||||||
|
}
|
||||||
|
|
||||||
|
string signedStart = DateTime.UtcNow.ToString("O");
|
||||||
|
string signedExpiry = DateTime.UtcNow.AddDays(validityTimeInDays).ToString("O");
|
||||||
|
string canonicalizedResource = "/blob/" + accountName + "/" + containerName;
|
||||||
|
string signedIdentifier = string.Empty;
|
||||||
|
string signedVersion = StorageApiVersion;
|
||||||
|
|
||||||
|
string stringToSign = ConstructServiceStringToSign(
|
||||||
|
signedPermissions,
|
||||||
|
signedVersion,
|
||||||
|
signedExpiry,
|
||||||
|
canonicalizedResource,
|
||||||
|
signedIdentifier,
|
||||||
|
signedStart);
|
||||||
|
|
||||||
|
byte[] signatureBytes = Encoding.UTF8.GetBytes(stringToSign);
|
||||||
|
string signature;
|
||||||
|
using (HMACSHA256 hmacSha256 = new HMACSHA256(Convert.FromBase64String(key)))
|
||||||
|
{
|
||||||
|
signature = Convert.ToBase64String(hmacSha256.ComputeHash(signatureBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
string sasToken = string.Format(
|
||||||
|
"?sv={0}&sr={1}&sig={2}&st={3}&se={4}&sp={5}",
|
||||||
|
WebUtility.UrlEncode(signedVersion),
|
||||||
|
WebUtility.UrlEncode("c"),
|
||||||
|
WebUtility.UrlEncode(signature),
|
||||||
|
WebUtility.UrlEncode(signedStart),
|
||||||
|
WebUtility.UrlEncode(signedExpiry),
|
||||||
|
WebUtility.UrlEncode(signedPermissions));
|
||||||
|
|
||||||
|
return sasToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetCanonicalizedHeaders(HttpRequestMessage request)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
List<string> headerNameList = (from headerName in request.Headers
|
||||||
|
where
|
||||||
|
headerName.Key.ToLowerInvariant()
|
||||||
|
.StartsWith("x-ms-", StringComparison.Ordinal)
|
||||||
|
select headerName.Key.ToLowerInvariant()).ToList();
|
||||||
|
headerNameList.Sort();
|
||||||
|
foreach (string headerName in headerNameList)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder(headerName);
|
||||||
|
string separator = ":";
|
||||||
|
foreach (string headerValue in GetHeaderValues(request.Headers, headerName))
|
||||||
|
{
|
||||||
|
string trimmedValue = headerValue.Replace("\r\n", string.Empty);
|
||||||
|
builder.Append(separator);
|
||||||
|
builder.Append(trimmedValue);
|
||||||
|
separator = ",";
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.Append(builder);
|
||||||
|
sb.Append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetCanonicalizedResource(Uri address, string accountName)
|
||||||
|
{
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
StringBuilder builder = new StringBuilder("/");
|
||||||
|
builder.Append(accountName);
|
||||||
|
builder.Append(address.AbsolutePath);
|
||||||
|
str.Append(builder);
|
||||||
|
Dictionary<string, HashSet<string>> queryKeyValues = ExtractQueryKeyValues(address);
|
||||||
|
Dictionary<string, HashSet<string>> dictionary = GetCommaSeparatedList(queryKeyValues);
|
||||||
|
|
||||||
|
foreach (KeyValuePair<string, HashSet<string>> pair in dictionary.OrderBy(p => p.Key))
|
||||||
|
{
|
||||||
|
StringBuilder stringBuilder = new StringBuilder(string.Empty);
|
||||||
|
stringBuilder.Append(pair.Key + ":");
|
||||||
|
string commaList = string.Join(",", pair.Value);
|
||||||
|
stringBuilder.Append(commaList);
|
||||||
|
str.Append("\n");
|
||||||
|
str.Append(stringBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> GetHeaderValues(HttpRequestHeaders headers, string headerName)
|
||||||
|
{
|
||||||
|
List<string> list = new List<string>();
|
||||||
|
IEnumerable<string> values;
|
||||||
|
headers.TryGetValues(headerName, out values);
|
||||||
|
if (values != null)
|
||||||
|
{
|
||||||
|
list.Add((values.FirstOrDefault() ?? string.Empty).TrimStart(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsWithinRetryRange(HttpStatusCode statusCode)
|
||||||
|
{
|
||||||
|
// Retry on http client and server error codes (4xx - 5xx) as well as redirect
|
||||||
|
|
||||||
|
var rawStatus = (int)statusCode;
|
||||||
|
if (rawStatus == 302)
|
||||||
|
return true;
|
||||||
|
else if (rawStatus >= 400 && rawStatus <= 599)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<HttpResponseMessage> RequestWithRetry(TaskLoggingHelper loggingHelper, HttpClient client,
|
||||||
|
Func<HttpRequestMessage> createRequest, Func<HttpResponseMessage, bool> validationCallback = null, int retryCount = 5,
|
||||||
|
int retryDelaySeconds = 5)
|
||||||
|
{
|
||||||
|
if (loggingHelper == null)
|
||||||
|
throw new ArgumentNullException(nameof(loggingHelper));
|
||||||
|
if (client == null)
|
||||||
|
throw new ArgumentNullException(nameof(client));
|
||||||
|
if (createRequest == null)
|
||||||
|
throw new ArgumentNullException(nameof(createRequest));
|
||||||
|
if (retryCount < 1)
|
||||||
|
throw new ArgumentException(nameof(retryCount));
|
||||||
|
if (retryDelaySeconds < 1)
|
||||||
|
throw new ArgumentException(nameof(retryDelaySeconds));
|
||||||
|
|
||||||
|
int retries = 0;
|
||||||
|
HttpResponseMessage response = null;
|
||||||
|
|
||||||
|
// add a bit of randomness to the retry delay
|
||||||
|
var rng = new Random();
|
||||||
|
|
||||||
|
while (retries < retryCount)
|
||||||
|
{
|
||||||
|
if (retries > 0)
|
||||||
|
{
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
response.Dispose();
|
||||||
|
response = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delay = retryDelaySeconds * retries * rng.Next(1, 5);
|
||||||
|
loggingHelper.LogMessage(MessageImportance.Low, "Waiting {0} seconds before retry", delay);
|
||||||
|
await System.Threading.Tasks.Task.Delay(delay * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var request = createRequest())
|
||||||
|
response = await client.SendAsync(request);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
loggingHelper.LogWarningFromException(e, true);
|
||||||
|
|
||||||
|
// if this is the final iteration let the exception bubble up
|
||||||
|
if (retries + 1 == retryCount)
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// response can be null if we fail to send the request
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
if (validationCallback == null)
|
||||||
|
{
|
||||||
|
// check if the response code is within the range of failures
|
||||||
|
if (IsWithinRetryRange(response.StatusCode))
|
||||||
|
{
|
||||||
|
loggingHelper.LogWarning("Request failed with status code {0}", response.StatusCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loggingHelper.LogMessage(MessageImportance.Low, "Response completed with status code {0}", response.StatusCode);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool isSuccess = validationCallback(response);
|
||||||
|
if (!isSuccess)
|
||||||
|
{
|
||||||
|
loggingHelper.LogMessage("Validation callback returned retry for status code {0}", response.StatusCode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
loggingHelper.LogMessage("Validation callback returned success for status code {0}", response.StatusCode);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++retries;
|
||||||
|
}
|
||||||
|
|
||||||
|
// retry count exceeded
|
||||||
|
loggingHelper.LogWarning("Retry count {0} exceeded", retryCount);
|
||||||
|
|
||||||
|
// set some default values in case response is null
|
||||||
|
var statusCode = "None";
|
||||||
|
var contentStr = "Null";
|
||||||
|
if (response != null)
|
||||||
|
{
|
||||||
|
statusCode = response.StatusCode.ToString();
|
||||||
|
contentStr = await response.Content.ReadAsStringAsync();
|
||||||
|
response.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new HttpRequestException(string.Format("Request failed with status {0} response {1}", statusCode, contentStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ConstructServiceStringToSign(
|
||||||
|
string signedPermissions,
|
||||||
|
string signedVersion,
|
||||||
|
string signedExpiry,
|
||||||
|
string canonicalizedResource,
|
||||||
|
string signedIdentifier,
|
||||||
|
string signedStart,
|
||||||
|
string signedIP = "",
|
||||||
|
string signedProtocol = "",
|
||||||
|
string rscc = "",
|
||||||
|
string rscd = "",
|
||||||
|
string rsce = "",
|
||||||
|
string rscl = "",
|
||||||
|
string rsct = "")
|
||||||
|
{
|
||||||
|
// constructing string to sign based on spec in https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
|
||||||
|
var stringToSign = string.Join(
|
||||||
|
"\n",
|
||||||
|
signedPermissions,
|
||||||
|
signedStart,
|
||||||
|
signedExpiry,
|
||||||
|
canonicalizedResource,
|
||||||
|
signedIdentifier,
|
||||||
|
signedIP,
|
||||||
|
signedProtocol,
|
||||||
|
signedVersion,
|
||||||
|
rscc,
|
||||||
|
rscd,
|
||||||
|
rsce,
|
||||||
|
rscl,
|
||||||
|
rsct);
|
||||||
|
return stringToSign;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, HashSet<string>> ExtractQueryKeyValues(Uri address)
|
||||||
|
{
|
||||||
|
Dictionary<string, HashSet<string>> values = new Dictionary<string, HashSet<string>>();
|
||||||
|
//Decode this to allow the regex to pull out the correct groups for signing
|
||||||
|
address = new Uri(WebUtility.UrlDecode(address.ToString()));
|
||||||
|
Regex newreg = new Regex(@"\?(\w+)\=([\w|\=]+)|\&(\w+)\=([\w|\=]+)");
|
||||||
|
MatchCollection matches = newreg.Matches(address.Query);
|
||||||
|
foreach (Match match in matches)
|
||||||
|
{
|
||||||
|
string key, value;
|
||||||
|
if (!string.IsNullOrEmpty(match.Groups[1].Value))
|
||||||
|
{
|
||||||
|
key = match.Groups[1].Value;
|
||||||
|
value = match.Groups[2].Value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
key = match.Groups[3].Value;
|
||||||
|
value = match.Groups[4].Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<string> setOfValues;
|
||||||
|
if (values.TryGetValue(key, out setOfValues))
|
||||||
|
{
|
||||||
|
setOfValues.Add(value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HashSet<string> newSet = new HashSet<string> { value };
|
||||||
|
values.Add(key, newSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, HashSet<string>> GetCommaSeparatedList(
|
||||||
|
Dictionary<string, HashSet<string>> queryKeyValues)
|
||||||
|
{
|
||||||
|
Dictionary<string, HashSet<string>> dictionary = new Dictionary<string, HashSet<string>>();
|
||||||
|
|
||||||
|
foreach (string queryKeys in queryKeyValues.Keys)
|
||||||
|
{
|
||||||
|
HashSet<string> setOfValues;
|
||||||
|
queryKeyValues.TryGetValue(queryKeys, out setOfValues);
|
||||||
|
List<string> list = new List<string>();
|
||||||
|
list.AddRange(setOfValues);
|
||||||
|
list.Sort();
|
||||||
|
string commaSeparatedValues = string.Join(",", list);
|
||||||
|
string key = queryKeys.ToLowerInvariant();
|
||||||
|
HashSet<string> setOfValues2;
|
||||||
|
if (dictionary.TryGetValue(key, out setOfValues2))
|
||||||
|
{
|
||||||
|
setOfValues2.Add(commaSeparatedValues);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
HashSet<string> newSet = new HashSet<string> { commaSeparatedValues };
|
||||||
|
dictionary.Add(key, newSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
167
build_projects/dotnet-cli-build/CreateAzureContainer.cs
Normal file
167
build_projects/dotnet-cli-build/CreateAzureContainer.cs
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
|
||||||
|
using Task = Microsoft.Build.Utilities.Task;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Build.CloudTestTasks
|
||||||
|
{
|
||||||
|
public sealed class CreateAzureContainer : Task
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The Azure account key used when creating the connection string.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string AccountKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Azure account name used when creating the connection string.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string AccountName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the container to create. The specified name must be in the correct format, see the
|
||||||
|
/// following page for more info. https://msdn.microsoft.com/en-us/library/azure/dd135715.aspx
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string ContainerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When false, if the specified container already exists get a reference to it.
|
||||||
|
/// When true, if the specified container already exists the task will fail.
|
||||||
|
/// </summary>
|
||||||
|
public bool FailIfExists { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The read-only SAS token created when ReadOnlyTokenDaysValid is greater than zero.
|
||||||
|
/// </summary>
|
||||||
|
[Output]
|
||||||
|
public string ReadOnlyToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of days for which the read-only token should be valid.
|
||||||
|
/// </summary>
|
||||||
|
public int ReadOnlyTokenDaysValid { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The URI of the created container.
|
||||||
|
/// </summary>
|
||||||
|
[Output]
|
||||||
|
public string StorageUri { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The write-only SAS token create when WriteOnlyTokenDaysValid is greater than zero.
|
||||||
|
/// </summary>
|
||||||
|
[Output]
|
||||||
|
public string WriteOnlyToken { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The number of days for which the write-only token should be valid.
|
||||||
|
/// </summary>
|
||||||
|
public int WriteOnlyTokenDaysValid { get; set; }
|
||||||
|
|
||||||
|
public override bool Execute()
|
||||||
|
{
|
||||||
|
return ExecuteAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExecuteAsync()
|
||||||
|
{
|
||||||
|
Log.LogMessage(
|
||||||
|
MessageImportance.High,
|
||||||
|
"Creating container named '{0}' in storage account {1}.",
|
||||||
|
ContainerName,
|
||||||
|
AccountName);
|
||||||
|
string url = string.Format(
|
||||||
|
"https://{0}.blob.core.windows.net/{1}?restype=container",
|
||||||
|
AccountName,
|
||||||
|
ContainerName);
|
||||||
|
StorageUri = string.Format(
|
||||||
|
"https://{0}.blob.core.windows.net/{1}/",
|
||||||
|
AccountName,
|
||||||
|
ContainerName);
|
||||||
|
|
||||||
|
Log.LogMessage(MessageImportance.Low, "Sending request to create Container");
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
Func<HttpRequestMessage> createRequest = () =>
|
||||||
|
{
|
||||||
|
DateTime dt = DateTime.UtcNow;
|
||||||
|
var req = new HttpRequestMessage(HttpMethod.Put, url);
|
||||||
|
req.Headers.Add(AzureHelper.DateHeaderString, dt.ToString("R", CultureInfo.InvariantCulture));
|
||||||
|
req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
|
||||||
|
req.Headers.Add(AzureHelper.AuthorizationHeaderString, AzureHelper.AuthorizationHeader(
|
||||||
|
AccountName,
|
||||||
|
AccountKey,
|
||||||
|
"PUT",
|
||||||
|
dt,
|
||||||
|
req));
|
||||||
|
byte[] bytestoWrite = new byte[0];
|
||||||
|
int bytesToWriteLength = 0;
|
||||||
|
|
||||||
|
Stream postStream = new MemoryStream();
|
||||||
|
postStream.Write(bytestoWrite, 0, bytesToWriteLength);
|
||||||
|
req.Content = new StreamContent(postStream);
|
||||||
|
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
|
||||||
|
Func<HttpResponseMessage, bool> validate = (HttpResponseMessage response) =>
|
||||||
|
{
|
||||||
|
// the Conflict status (409) indicates that the container already exists, so
|
||||||
|
// if FailIfExists is set to false and we get a 409 don't fail the task.
|
||||||
|
return response.IsSuccessStatusCode || (!FailIfExists && response.StatusCode == HttpStatusCode.Conflict);
|
||||||
|
};
|
||||||
|
|
||||||
|
using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(Log, client, createRequest, validate))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.LogMessage(
|
||||||
|
MessageImportance.Low,
|
||||||
|
"Received response to create Container {0}: Status Code: {1} {2}",
|
||||||
|
ContainerName, response.StatusCode, response.Content.ToString());
|
||||||
|
|
||||||
|
// specifying zero is valid, it means "I don't want a token"
|
||||||
|
if (ReadOnlyTokenDaysValid > 0)
|
||||||
|
{
|
||||||
|
ReadOnlyToken = AzureHelper.CreateContainerSasToken(
|
||||||
|
AccountName,
|
||||||
|
ContainerName,
|
||||||
|
AccountKey,
|
||||||
|
AzureHelper.SasAccessType.Read,
|
||||||
|
ReadOnlyTokenDaysValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// specifying zero is valid, it means "I don't want a token"
|
||||||
|
if (WriteOnlyTokenDaysValid > 0)
|
||||||
|
{
|
||||||
|
WriteOnlyToken = AzureHelper.CreateContainerSasToken(
|
||||||
|
AccountName,
|
||||||
|
ContainerName,
|
||||||
|
AccountKey,
|
||||||
|
AzureHelper.SasAccessType.Write,
|
||||||
|
WriteOnlyTokenDaysValid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.LogErrorFromException(e, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
183
build_projects/dotnet-cli-build/UploadClient.cs
Normal file
183
build_projects/dotnet-cli-build/UploadClient.cs
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.Build.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using Task = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Build.CloudTestTasks
|
||||||
|
{
|
||||||
|
public class UploadClient
|
||||||
|
{
|
||||||
|
private TaskLoggingHelper log;
|
||||||
|
|
||||||
|
public UploadClient(TaskLoggingHelper loggingHelper)
|
||||||
|
{
|
||||||
|
log = loggingHelper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EncodeBlockIds(int numberOfBlocks, int lengthOfId)
|
||||||
|
{
|
||||||
|
string numberOfBlocksString = numberOfBlocks.ToString("D" + lengthOfId);
|
||||||
|
if (Encoding.UTF8.GetByteCount(numberOfBlocksString) <= 64)
|
||||||
|
{
|
||||||
|
byte[] bytes = Encoding.UTF8.GetBytes(numberOfBlocksString);
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception("Task failed - Could not encode block id.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UploadBlockBlobAsync(
|
||||||
|
CancellationToken ct,
|
||||||
|
string AccountName,
|
||||||
|
string AccountKey,
|
||||||
|
string ContainerName,
|
||||||
|
string filePath,
|
||||||
|
string destinationBlob)
|
||||||
|
{
|
||||||
|
|
||||||
|
string resourceUrl = string.Format("https://{0}.blob.core.windows.net/{1}", AccountName, ContainerName);
|
||||||
|
|
||||||
|
string fileName = destinationBlob;
|
||||||
|
fileName = fileName.Replace("\\", "/");
|
||||||
|
string blobUploadUrl = resourceUrl + "/" + fileName;
|
||||||
|
int size = (int)new FileInfo(filePath).Length;
|
||||||
|
int blockSize = 4 * 1024 * 1024; //4MB max size of a block blob
|
||||||
|
int bytesLeft = size;
|
||||||
|
List<string> blockIds = new List<string>();
|
||||||
|
int numberOfBlocks = (size / blockSize) + 1;
|
||||||
|
int countForId = 0;
|
||||||
|
using (FileStream fileStreamTofilePath = new FileStream(filePath, FileMode.Open))
|
||||||
|
{
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (bytesLeft > 0)
|
||||||
|
{
|
||||||
|
int nextBytesToRead = (bytesLeft < blockSize) ? bytesLeft : blockSize;
|
||||||
|
byte[] fileBytes = new byte[blockSize];
|
||||||
|
int read = fileStreamTofilePath.Read(fileBytes, 0, nextBytesToRead);
|
||||||
|
|
||||||
|
if (nextBytesToRead != read)
|
||||||
|
{
|
||||||
|
throw new Exception(string.Format(
|
||||||
|
"Number of bytes read ({0}) from file {1} isn't equal to the number of bytes expected ({2}) .",
|
||||||
|
read, fileName, nextBytesToRead));
|
||||||
|
}
|
||||||
|
|
||||||
|
string blockId = EncodeBlockIds(countForId, numberOfBlocks.ToString().Length);
|
||||||
|
|
||||||
|
blockIds.Add(blockId);
|
||||||
|
string blockUploadUrl = blobUploadUrl + "?comp=block&blockid=" + WebUtility.UrlEncode(blockId);
|
||||||
|
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
client.DefaultRequestHeaders.Clear();
|
||||||
|
Func<HttpRequestMessage> createRequest = () =>
|
||||||
|
{
|
||||||
|
DateTime dt = DateTime.UtcNow;
|
||||||
|
var req = new HttpRequestMessage(HttpMethod.Put, blockUploadUrl);
|
||||||
|
req.Headers.Add(
|
||||||
|
AzureHelper.DateHeaderString,
|
||||||
|
dt.ToString("R", CultureInfo.InvariantCulture));
|
||||||
|
req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
|
||||||
|
req.Headers.Add(
|
||||||
|
AzureHelper.AuthorizationHeaderString,
|
||||||
|
AzureHelper.AuthorizationHeader(
|
||||||
|
AccountName,
|
||||||
|
AccountKey,
|
||||||
|
"PUT",
|
||||||
|
dt,
|
||||||
|
req,
|
||||||
|
string.Empty,
|
||||||
|
string.Empty,
|
||||||
|
nextBytesToRead.ToString(),
|
||||||
|
string.Empty));
|
||||||
|
|
||||||
|
Stream postStream = new MemoryStream();
|
||||||
|
postStream.Write(fileBytes, 0, nextBytesToRead);
|
||||||
|
postStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
req.Content = new StreamContent(postStream);
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
|
||||||
|
log.LogMessage(MessageImportance.Low, "Sending request to upload part {0} of file {1}", countForId, fileName);
|
||||||
|
|
||||||
|
using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(log, client, createRequest))
|
||||||
|
{
|
||||||
|
log.LogMessage(
|
||||||
|
MessageImportance.Low,
|
||||||
|
"Received response to upload part {0} of file {1}: Status Code:{2} Status Desc: {3}",
|
||||||
|
countForId,
|
||||||
|
fileName,
|
||||||
|
response.StatusCode,
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += read;
|
||||||
|
bytesLeft -= nextBytesToRead;
|
||||||
|
countForId += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string blockListUploadUrl = blobUploadUrl + "?comp=blocklist";
|
||||||
|
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
Func<HttpRequestMessage> createRequest = () =>
|
||||||
|
{
|
||||||
|
DateTime dt1 = DateTime.UtcNow;
|
||||||
|
var req = new HttpRequestMessage(HttpMethod.Put, blockListUploadUrl);
|
||||||
|
req.Headers.Add(AzureHelper.DateHeaderString, dt1.ToString("R", CultureInfo.InvariantCulture));
|
||||||
|
req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
|
||||||
|
|
||||||
|
var body = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?><BlockList>");
|
||||||
|
foreach (object item in blockIds)
|
||||||
|
body.AppendFormat("<Latest>{0}</Latest>", item);
|
||||||
|
|
||||||
|
body.Append("</BlockList>");
|
||||||
|
byte[] bodyData = Encoding.UTF8.GetBytes(body.ToString());
|
||||||
|
req.Headers.Add(
|
||||||
|
AzureHelper.AuthorizationHeaderString,
|
||||||
|
AzureHelper.AuthorizationHeader(
|
||||||
|
AccountName,
|
||||||
|
AccountKey,
|
||||||
|
"PUT",
|
||||||
|
dt1,
|
||||||
|
req,
|
||||||
|
string.Empty,
|
||||||
|
string.Empty,
|
||||||
|
bodyData.Length.ToString(),
|
||||||
|
""));
|
||||||
|
Stream postStream = new MemoryStream();
|
||||||
|
postStream.Write(bodyData, 0, bodyData.Length);
|
||||||
|
postStream.Seek(0, SeekOrigin.Begin);
|
||||||
|
req.Content = new StreamContent(postStream);
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
|
||||||
|
using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(log, client, createRequest))
|
||||||
|
{
|
||||||
|
log.LogMessage(
|
||||||
|
MessageImportance.Low,
|
||||||
|
"Received response to combine block list for file {0}: Status Code:{1} Status Desc: {2}",
|
||||||
|
fileName,
|
||||||
|
response.StatusCode,
|
||||||
|
await response.Content.ReadAsStringAsync());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
186
build_projects/dotnet-cli-build/UploadToAzure.cs
Normal file
186
build_projects/dotnet-cli-build/UploadToAzure.cs
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Xml;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
|
||||||
|
using Task = Microsoft.Build.Utilities.Task;
|
||||||
|
using ThreadingTask = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Build.CloudTestTasks
|
||||||
|
{
|
||||||
|
public class UploadToAzure : Task, ICancelableTask
|
||||||
|
{
|
||||||
|
private static readonly CancellationTokenSource TokenSource = new CancellationTokenSource();
|
||||||
|
private static readonly CancellationToken CancellationToken = TokenSource.Token;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Azure account key used when creating the connection string.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string AccountKey { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The Azure account name used when creating the connection string.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string AccountName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the container to access. The specified name must be in the correct format, see the
|
||||||
|
/// following page for more info. https://msdn.microsoft.com/en-us/library/azure/dd135715.aspx
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string ContainerName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An item group of files to upload. Each item must have metadata RelativeBlobPath
|
||||||
|
/// that specifies the path relative to ContainerName where the item will be uploaded.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public ITaskItem[] Items { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the destination blob should be overwritten if it already exists. The default if false.
|
||||||
|
/// </summary>
|
||||||
|
public bool Overwrite { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Specifies the maximum number of clients to concurrently upload blobs to azure
|
||||||
|
/// </summary>
|
||||||
|
public int MaxClients { get; set; } = 8;
|
||||||
|
|
||||||
|
public void Cancel()
|
||||||
|
{
|
||||||
|
TokenSource.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Execute()
|
||||||
|
{
|
||||||
|
return ExecuteAsync(CancellationToken).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ExecuteAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
Log.LogMessage(
|
||||||
|
MessageImportance.High,
|
||||||
|
"Begin uploading blobs to Azure account {0} in container {1}.",
|
||||||
|
AccountName,
|
||||||
|
ContainerName);
|
||||||
|
|
||||||
|
if (Items.Length == 0)
|
||||||
|
{
|
||||||
|
Log.LogError("No items were provided for upload.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first check what blobs are present
|
||||||
|
string checkListUrl = string.Format(
|
||||||
|
"https://{0}.blob.core.windows.net/{1}?restype=container&comp=list",
|
||||||
|
AccountName,
|
||||||
|
ContainerName);
|
||||||
|
|
||||||
|
HashSet<string> blobsPresent = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
Func<HttpRequestMessage> createRequest = () =>
|
||||||
|
{
|
||||||
|
DateTime dt = DateTime.UtcNow;
|
||||||
|
var req = new HttpRequestMessage(HttpMethod.Get, checkListUrl);
|
||||||
|
req.Headers.Add(AzureHelper.DateHeaderString, dt.ToString("R", CultureInfo.InvariantCulture));
|
||||||
|
req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
|
||||||
|
req.Headers.Add(AzureHelper.AuthorizationHeaderString, AzureHelper.AuthorizationHeader(
|
||||||
|
AccountName,
|
||||||
|
AccountKey,
|
||||||
|
"GET",
|
||||||
|
dt,
|
||||||
|
req));
|
||||||
|
return req;
|
||||||
|
};
|
||||||
|
|
||||||
|
Log.LogMessage(MessageImportance.Low, "Sending request to check whether Container blobs exist");
|
||||||
|
using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(Log, client, createRequest))
|
||||||
|
{
|
||||||
|
var doc = new XmlDocument();
|
||||||
|
doc.LoadXml(await response.Content.ReadAsStringAsync());
|
||||||
|
|
||||||
|
XmlNodeList nodes = doc.DocumentElement.GetElementsByTagName("Blob");
|
||||||
|
|
||||||
|
foreach (XmlNode node in nodes)
|
||||||
|
{
|
||||||
|
blobsPresent.Add(node["Name"].InnerText);
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.LogMessage(MessageImportance.Low, "Received response to check whether Container blobs exist");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var clientThrottle = new SemaphoreSlim(this.MaxClients, this.MaxClients))
|
||||||
|
{
|
||||||
|
await ThreadingTask.WhenAll(Items.Select(item => UploadAsync(ct, item, blobsPresent, clientThrottle)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.LogMessage(MessageImportance.High, "Upload to Azure is complete, a total of {0} items were uploaded.", Items.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.LogErrorFromException(e, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ThreadingTask UploadAsync(CancellationToken ct, ITaskItem item, HashSet<string> blobsPresent, SemaphoreSlim clientThrottle)
|
||||||
|
{
|
||||||
|
if (ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Log.LogError("Task UploadToAzure cancelled");
|
||||||
|
ct.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
string relativeBlobPath = item.GetMetadata("RelativeBlobPath");
|
||||||
|
if (string.IsNullOrEmpty(relativeBlobPath))
|
||||||
|
throw new Exception(string.Format("Metadata 'RelativeBlobPath' is missing for item '{0}'.", item.ItemSpec));
|
||||||
|
|
||||||
|
if (!File.Exists(item.ItemSpec))
|
||||||
|
throw new Exception(string.Format("The file '{0}' does not exist.", item.ItemSpec));
|
||||||
|
|
||||||
|
if (!Overwrite && blobsPresent.Contains(relativeBlobPath))
|
||||||
|
throw new Exception(string.Format("The blob '{0}' already exists.", relativeBlobPath));
|
||||||
|
|
||||||
|
await clientThrottle.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.LogMessage("Uploading {0} to {1}.", item.ItemSpec, ContainerName);
|
||||||
|
UploadClient uploadClient = new UploadClient(Log);
|
||||||
|
await
|
||||||
|
uploadClient.UploadBlockBlobAsync(
|
||||||
|
ct,
|
||||||
|
AccountName,
|
||||||
|
AccountKey,
|
||||||
|
ContainerName,
|
||||||
|
item.ItemSpec,
|
||||||
|
relativeBlobPath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
clientThrottle.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
build_projects/dotnet-cli-build/ZipFileCreateFromDirectory.cs
Normal file
131
build_projects/dotnet-cli-build/ZipFileCreateFromDirectory.cs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.Build.Utilities;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Build.Tasks
|
||||||
|
{
|
||||||
|
public sealed class ZipFileCreateFromDirectory : Task
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The path to the directory to be archived.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string SourceDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The path of the archive to be created.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string DestinationArchive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the destination archive should be overwritten if it already exists.
|
||||||
|
/// </summary>
|
||||||
|
public bool OverwriteDestination { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If zipping an entire folder without exclusion patterns, whether to include the folder in the archive.
|
||||||
|
/// </summary>
|
||||||
|
public bool IncludeBaseDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An item group of regular expressions for content to exclude from the archive.
|
||||||
|
/// </summary>
|
||||||
|
public ITaskItem[] ExcludePatterns { get; set; }
|
||||||
|
|
||||||
|
public override bool Execute()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(DestinationArchive))
|
||||||
|
{
|
||||||
|
if (OverwriteDestination == true)
|
||||||
|
{
|
||||||
|
Log.LogMessage(MessageImportance.Low, "{0} already existed, deleting before zipping...", DestinationArchive);
|
||||||
|
File.Delete(DestinationArchive);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.LogWarning("'{0}' already exists. Did you forget to set '{1}' to true?", DestinationArchive, nameof(OverwriteDestination));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.LogMessage(MessageImportance.High, "Compressing {0} into {1}...", SourceDirectory, DestinationArchive);
|
||||||
|
if (!Directory.Exists(Path.GetDirectoryName(DestinationArchive)))
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(DestinationArchive));
|
||||||
|
|
||||||
|
if (ExcludePatterns == null)
|
||||||
|
{
|
||||||
|
ZipFile.CreateFromDirectory(SourceDirectory, DestinationArchive, CompressionLevel.Optimal, IncludeBaseDirectory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// convert to regular expressions
|
||||||
|
Regex[] regexes = new Regex[ExcludePatterns.Length];
|
||||||
|
for (int i = 0; i < ExcludePatterns.Length; ++i)
|
||||||
|
regexes[i] = new Regex(ExcludePatterns[i].ItemSpec, RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
using (FileStream writer = new FileStream(DestinationArchive, FileMode.CreateNew))
|
||||||
|
{
|
||||||
|
using (ZipArchive zipFile = new ZipArchive(writer, ZipArchiveMode.Create))
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(SourceDirectory, "*", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
foreach (var file in files)
|
||||||
|
{
|
||||||
|
// look for a match
|
||||||
|
bool foundMatch = false;
|
||||||
|
foreach (var regex in regexes)
|
||||||
|
{
|
||||||
|
if (regex.IsMatch(file))
|
||||||
|
{
|
||||||
|
foundMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundMatch)
|
||||||
|
{
|
||||||
|
Log.LogMessage(MessageImportance.Low, "Excluding {0} from archive.", file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativePath = MakeRelativePath(SourceDirectory, file);
|
||||||
|
zipFile.CreateEntryFromFile(file, relativePath, CompressionLevel.Optimal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// We have 2 log calls because we want a nice error message but we also want to capture the callstack in the log.
|
||||||
|
Log.LogError("An exception has occured while trying to compress '{0}' into '{1}'.", SourceDirectory, DestinationArchive);
|
||||||
|
Log.LogMessage(MessageImportance.Low, e.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MakeRelativePath(string root, string subdirectory)
|
||||||
|
{
|
||||||
|
if (!subdirectory.StartsWith(root))
|
||||||
|
throw new Exception(string.Format("'{0}' is not a subdirectory of '{1}'.", subdirectory, root));
|
||||||
|
|
||||||
|
// returned string should not start with a directory separator
|
||||||
|
int chop = root.Length;
|
||||||
|
if (subdirectory[chop] == Path.DirectorySeparatorChar)
|
||||||
|
++chop;
|
||||||
|
|
||||||
|
return subdirectory.Substring(chop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
build_projects/dotnet-cli-build/ZipFileExtractToDirectory.cs
Normal file
65
build_projects/dotnet-cli-build/ZipFileExtractToDirectory.cs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.Build.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Build.Tasks
|
||||||
|
{
|
||||||
|
public sealed class ZipFileExtractToDirectory : Task
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The path to the directory to be archived.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string SourceArchive { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The path of the archive to be created.
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string DestinationDirectory { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the destination archive should be overwritten if it already exists.
|
||||||
|
/// </summary>
|
||||||
|
public bool OverwriteDestination { get; set; }
|
||||||
|
|
||||||
|
public override bool Execute()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Directory.Exists(DestinationDirectory))
|
||||||
|
{
|
||||||
|
if (OverwriteDestination == true)
|
||||||
|
{
|
||||||
|
Log.LogMessage(MessageImportance.Low, "'{0}' already exists, trying to delete before unzipping...", DestinationDirectory);
|
||||||
|
Directory.Delete(DestinationDirectory, recursive: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log.LogWarning("'{0}' already exists. Did you forget to set '{1}' to true?", DestinationDirectory, nameof(OverwriteDestination));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.LogMessage(MessageImportance.High, "Decompressing '{0}' into '{1}'...", SourceArchive, DestinationDirectory);
|
||||||
|
if (!Directory.Exists(Path.GetDirectoryName(DestinationDirectory)))
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(DestinationDirectory));
|
||||||
|
|
||||||
|
ZipFile.ExtractToDirectory(SourceArchive, DestinationDirectory);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// We have 2 log calls because we want a nice error message but we also want to capture the callstack in the log.
|
||||||
|
Log.LogError("An exception has occured while trying to decompress '{0}' into '{1}'.", SourceArchive, DestinationDirectory);
|
||||||
|
Log.LogMessage(MessageImportance.Low, e.ToString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue