Merge upstream changes

This commit is contained in:
Daniel Plaisted 2017-09-01 13:17:22 -07:00
commit 9b331673fe
67 changed files with 877 additions and 316 deletions

View file

@ -56,7 +56,6 @@ namespace Microsoft.DotNet.Cli.Build
{ "rhel_7_x64", false },
{ "ubuntu_14_04_x64", false },
{ "ubuntu_16_04_x64", false },
{ "ubuntu_16_10_x64", false },
{ "linux_x64", false }
};

View file

@ -0,0 +1,62 @@
// 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.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using NuGet.Protocol;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
internal class AddPackageStrategy : IAzurelinuxRepositoryServiceHttpStrategy
{
private readonly IdInRepositoryService _idInRepositoryService;
private readonly string _packageName;
private readonly string _packageVersion;
private readonly string _repositoryId;
public AddPackageStrategy(
IdInRepositoryService idInRepositoryService,
string packageName,
string packageVersion,
string repositoryId)
{
_idInRepositoryService = idInRepositoryService
?? throw new ArgumentNullException(nameof(idInRepositoryService));
_packageName = packageName;
_packageVersion = packageVersion;
_repositoryId = repositoryId;
}
public async Task<string> Execute(HttpClient client, Uri baseAddress)
{
var debianUploadJsonContent = new Dictionary<string, string>
{
["name"] = _packageName,
["version"] = AppendDebianRevisionNumber(_packageVersion),
["fileId"] = _idInRepositoryService.Id,
["repositoryId"] = _repositoryId
}.ToJson();
var content = new StringContent(debianUploadJsonContent,
Encoding.UTF8,
"application/json");
using (var response = await client.PostAsync(new Uri(baseAddress, "/v1/packages"), content))
{
if (!response.IsSuccessStatusCode)
throw new FailedToAddPackageToPackageRepositoryException(
$"request:{debianUploadJsonContent} response:{response.ToJson()}");
return response.Headers.GetValues("Location").Single();
}
}
private static string AppendDebianRevisionNumber(string packageVersion)
{
return packageVersion + "-1";
}
}
}

View file

@ -0,0 +1,53 @@
// 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.Linq;
using System.Threading.Tasks;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
public static class ExponentialRetry
{
public static IEnumerable<TimeSpan> Intervals
{
get
{
var seconds = 5;
while (true)
{
yield return TimeSpan.FromSeconds(seconds);
seconds *= 2;
}
}
}
public static async Task ExecuteWithRetry(Func<Task<string>> action,
Func<string, bool> isSuccess,
int maxRetryCount,
Func<IEnumerable<Task>> timer,
string taskDescription = "")
{
var count = 0;
foreach (var t in timer())
{
await t;
var result = await action();
if (isSuccess(result))
return;
count++;
if (count == maxRetryCount)
throw new RetryFailedException(
$"Retry failed for {taskDescription} after {count} times with result: {result}");
}
throw new Exception("Timer should not be exhausted");
}
public static IEnumerable<Task> Timer(IEnumerable<TimeSpan> interval)
{
return interval.Select(Task.Delay);
}
}
}

View file

@ -0,0 +1,24 @@
// 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;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
public class FailedToAddPackageToPackageRepositoryException : Exception
{
public FailedToAddPackageToPackageRepositoryException(string message) : base(message)
{
}
public FailedToAddPackageToPackageRepositoryException()
{
}
public FailedToAddPackageToPackageRepositoryException(string message, Exception innerException) : base(message,
innerException)
{
}
}
}

View file

@ -0,0 +1,49 @@
// 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.Net.Http;
using System.Threading.Tasks;
using NuGet.Protocol;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
internal class FileUploadStrategy : IAzurelinuxRepositoryServiceHttpStrategy
{
private readonly string _pathToPackageToUpload;
public FileUploadStrategy(string pathToPackageToUpload)
{
_pathToPackageToUpload = pathToPackageToUpload
?? throw new ArgumentNullException(nameof(pathToPackageToUpload));
}
public async Task<string> Execute(HttpClient client, Uri baseAddress)
{
var fileName = Path.GetFileName(_pathToPackageToUpload);
using (var content =
new MultipartFormDataContent())
{
var url = new Uri(baseAddress, "/v1/files");
content.Add(
new StreamContent(
new MemoryStream(
File.ReadAllBytes(_pathToPackageToUpload))),
"file",
fileName);
using (var message = await client.PostAsync(url, content))
{
if (!message.IsSuccessStatusCode)
{
throw new FailedToAddPackageToPackageRepositoryException(
$"{message.ToJson()} failed to post file to {url} file name:{fileName} pathToPackageToUpload:{_pathToPackageToUpload}");
}
return await message.Content.ReadAsStringAsync();
}
}
}
}
}

View file

@ -0,0 +1,15 @@
// 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.Net.Http;
using System.Threading.Tasks;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
internal interface IAzurelinuxRepositoryServiceHttpStrategy
{
Task<string> Execute(HttpClient client, Uri baseAddress);
}
}

View file

@ -0,0 +1,18 @@
// 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;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
internal class IdInRepositoryService
{
public IdInRepositoryService(string id)
{
Id = id ?? throw new ArgumentNullException(nameof(id));
}
public string Id { get; }
}
}

View file

@ -0,0 +1,38 @@
// 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;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
internal class LinuxPackageRepositoryDestiny
{
private readonly string _password;
private readonly string _server;
private readonly string _username;
public LinuxPackageRepositoryDestiny(string username,
string password,
string server,
string repositoryId)
{
_username = username ?? throw new ArgumentNullException(nameof(username));
_password = password ?? throw new ArgumentNullException(nameof(password));
_server = server ?? throw new ArgumentNullException(nameof(server));
RepositoryId = repositoryId ?? throw new ArgumentNullException(nameof(repositoryId));
}
public string RepositoryId { get; }
public Uri GetBaseAddress()
{
return new Uri($"https://{_server}");
}
public string GetSimpleAuth()
{
return $"{_username}:{_password}";
}
}
}

View file

@ -0,0 +1,46 @@
// 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.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
internal class LinuxPackageRepositoryHttpPrepare
{
private readonly IAzurelinuxRepositoryServiceHttpStrategy _httpStrategy;
private readonly LinuxPackageRepositoryDestiny _linuxPackageRepositoryDestiny;
public LinuxPackageRepositoryHttpPrepare(
LinuxPackageRepositoryDestiny linuxPackageRepositoryDestiny,
IAzurelinuxRepositoryServiceHttpStrategy httpStrategy
)
{
_linuxPackageRepositoryDestiny = linuxPackageRepositoryDestiny
?? throw new ArgumentNullException(nameof(linuxPackageRepositoryDestiny));
_httpStrategy = httpStrategy ?? throw new ArgumentNullException(nameof(httpStrategy));
}
public async Task<string> RemoteCall()
{
using (var handler = new HttpClientHandler())
{
using (var client = new HttpClient(handler))
{
var authHeader =
Convert.ToBase64String(Encoding.UTF8.GetBytes((string) _linuxPackageRepositoryDestiny.GetSimpleAuth()));
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic", authHeader);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.Timeout = TimeSpan.FromMinutes(10);
return await _httpStrategy.Execute(client, _linuxPackageRepositoryDestiny.GetBaseAddress());
}
}
}
}
}

View file

@ -0,0 +1,34 @@
// 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.Net.Http;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
internal class PullQueuedPackageStatus : IAzurelinuxRepositoryServiceHttpStrategy
{
private readonly QueueResourceLocation _queueResourceLocation;
public PullQueuedPackageStatus(QueueResourceLocation queueResourceLocation)
{
_queueResourceLocation = queueResourceLocation
?? throw new ArgumentNullException(nameof(queueResourceLocation));
}
public async Task<string> Execute(HttpClient client, Uri baseAddress)
{
using (var response = await client.GetAsync(new Uri(baseAddress, _queueResourceLocation.Location)))
{
if (!response.IsSuccessStatusCode)
throw new FailedToAddPackageToPackageRepositoryException(
"Failed to make request to " + _queueResourceLocation.Location);
var body = await response.Content.ReadAsStringAsync();
return !body.Contains("status") ? "" : JObject.Parse(body)["status"].ToString();
}
}
}
}

View file

@ -0,0 +1,18 @@
// 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;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
internal class QueueResourceLocation
{
public QueueResourceLocation(string location)
{
Location = location ?? throw new ArgumentNullException(nameof(location));
}
public string Location { get; }
}
}

View file

@ -0,0 +1,23 @@
// 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;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
public class RetryFailedException : Exception
{
public RetryFailedException(string message) : base(message)
{
}
public RetryFailedException()
{
}
public RetryFailedException(string message, Exception innerException) : base(message, innerException)
{
}
}
}

View file

@ -0,0 +1,121 @@
// 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.Net.Http;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Newtonsoft.Json.Linq;
using Task = Microsoft.Build.Utilities.Task;
namespace Microsoft.DotNet.Cli.Build.UploadToLinuxPackageRepository
{
public class UploadToLinuxPackageRepository : Task
{
/// <summary>
/// The Azure repository service user name.
/// </summary>
[Required]
public string Username { get; set; }
/// <summary>
/// The Azure repository service Password.
/// </summary>
[Required]
public string Password { get; set; }
/// <summary>
/// The Azure repository service URL ex: "tux-devrepo.corp.microsoft.com".
/// </summary>
[Required]
public string Server { get; set; }
[Required]
public string RepositoryId { get; set; }
[Required]
public string PathOfPackageToUpload { get; set; }
[Required]
public string PackageNameInLinuxPackageRepository { get; set; }
[Required]
public string PackageVersionInLinuxPackageRepository { get; set; }
public override bool Execute()
{
ExecuteAsyncWithRetry().GetAwaiter().GetResult();
return true;
}
private async System.Threading.Tasks.Task ExecuteAsyncWithRetry()
{
await ExponentialRetry.ExecuteWithRetry(
UploadAndAddpackageAndEnsureItIsReady,
s => s == "",
maxRetryCount: 3,
timer: () => ExponentialRetry.Timer(ExponentialRetry.Intervals),
taskDescription: $"running {nameof(UploadAndAddpackageAndEnsureItIsReady)}");
}
private async Task<string> UploadAndAddpackageAndEnsureItIsReady()
{
try
{
Log.LogMessage(
MessageImportance.High,
"Begin uploading Linux Package to feed service, RepositoryId {0}, Server {1}, Package to upload {2}.",
RepositoryId,
Server,
PathOfPackageToUpload);
var linuxPackageRepositoryDestiny =
new LinuxPackageRepositoryDestiny(Username, Password, Server, RepositoryId);
var uploadResponse = await new LinuxPackageRepositoryHttpPrepare(
linuxPackageRepositoryDestiny,
new FileUploadStrategy(PathOfPackageToUpload)).RemoteCall();
var idInRepositoryService = new IdInRepositoryService(JObject.Parse(uploadResponse)["id"].ToString());
var addPackageResponse = await new LinuxPackageRepositoryHttpPrepare(
linuxPackageRepositoryDestiny,
new AddPackageStrategy(
idInRepositoryService,
PackageNameInLinuxPackageRepository,
PackageVersionInLinuxPackageRepository,
linuxPackageRepositoryDestiny.RepositoryId)).RemoteCall();
var queueResourceLocation = new QueueResourceLocation(addPackageResponse);
Func<Task<string>> pullQueuedPackageStatus = new LinuxPackageRepositoryHttpPrepare(
linuxPackageRepositoryDestiny,
new PullQueuedPackageStatus(queueResourceLocation)).RemoteCall;
await ExponentialRetry.ExecuteWithRetry(
pullQueuedPackageStatus,
s => s == "fileReady",
5,
() => ExponentialRetry.Timer(ExponentialRetry.Intervals),
$"PullQueuedPackageStatus location: {queueResourceLocation.Location}");
Log.LogMessage(
MessageImportance.High,
"Upload to feed service is completed, queue resource location {0}",
queueResourceLocation.Location);
return "";
}
catch (FailedToAddPackageToPackageRepositoryException e)
{
return e.ToString();
}
catch (HttpRequestException e)
{
return e.ToString();
}
}
}
}