343374d7c7
Currently, using a custom container name is supported for only some publish steps. This commit makes it supported for all publish steps. This commit also ensures that the Overwrite property is specified on all calls to the UploadToAzure task.
229 lines
8.3 KiB
C#
229 lines
8.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.WindowsAzure.Storage;
|
|
using Microsoft.WindowsAzure.Storage.Auth;
|
|
using Microsoft.WindowsAzure.Storage.Blob;
|
|
|
|
namespace Microsoft.DotNet.Cli.Build
|
|
{
|
|
public class AzurePublisher
|
|
{
|
|
public enum Product
|
|
{
|
|
SharedFramework,
|
|
Host,
|
|
HostFxr,
|
|
Sdk,
|
|
}
|
|
|
|
private const string s_dotnetBlobContainerName = "dotnet";
|
|
|
|
private string _connectionString { get; set; }
|
|
private string _containerName { get; set; }
|
|
private CloudBlobContainer _blobContainer { get; set; }
|
|
|
|
public AzurePublisher(string containerName = s_dotnetBlobContainerName)
|
|
{
|
|
_connectionString = EnvVars.EnsureVariable("CONNECTION_STRING").Trim('"');
|
|
_containerName = containerName;
|
|
_blobContainer = GetDotnetBlobContainer(_connectionString, containerName);
|
|
}
|
|
|
|
public AzurePublisher(string accountName, string accountKey, string containerName = s_dotnetBlobContainerName)
|
|
{
|
|
_containerName = containerName;
|
|
_blobContainer = GetDotnetBlobContainer(accountName, accountKey, containerName);
|
|
}
|
|
|
|
private CloudBlobContainer GetDotnetBlobContainer(string connectionString, string containerName)
|
|
{
|
|
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
|
|
|
|
return GetDotnetBlobContainer(storageAccount, containerName);
|
|
}
|
|
|
|
private CloudBlobContainer GetDotnetBlobContainer(string accountName, string accountKey, string containerName)
|
|
{
|
|
var storageCredentials = new StorageCredentials(accountName, accountKey);
|
|
var storageAccount = new CloudStorageAccount(storageCredentials, true);
|
|
return GetDotnetBlobContainer(storageAccount, containerName);
|
|
}
|
|
|
|
private CloudBlobContainer GetDotnetBlobContainer(CloudStorageAccount storageAccount, string containerName)
|
|
{
|
|
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
|
|
|
|
return blobClient.GetContainerReference(containerName);
|
|
}
|
|
|
|
public string UploadFile(string file, Product product, string version)
|
|
{
|
|
string url = CalculateRelativePathForFile(file, product, version);
|
|
CloudBlockBlob blob = _blobContainer.GetBlockBlobReference(url);
|
|
blob.UploadFromFileAsync(file, FileMode.Open).Wait();
|
|
SetBlobPropertiesBasedOnFileType(blob);
|
|
return url;
|
|
}
|
|
|
|
public void PublishStringToBlob(string blob, string content)
|
|
{
|
|
CloudBlockBlob blockBlob = _blobContainer.GetBlockBlobReference(blob);
|
|
blockBlob.UploadTextAsync(content).Wait();
|
|
|
|
SetBlobPropertiesBasedOnFileType(blockBlob);
|
|
}
|
|
|
|
public void CopyBlob(string sourceBlob, string targetBlob)
|
|
{
|
|
CloudBlockBlob source = _blobContainer.GetBlockBlobReference(sourceBlob);
|
|
CloudBlockBlob target = _blobContainer.GetBlockBlobReference(targetBlob);
|
|
|
|
// Create the empty blob
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
target.UploadFromStreamAsync(ms).Wait();
|
|
}
|
|
|
|
// Copy actual blob data
|
|
target.StartCopyAsync(source).Wait();
|
|
}
|
|
|
|
public void SetBlobPropertiesBasedOnFileType(string path)
|
|
{
|
|
CloudBlockBlob blob = _blobContainer.GetBlockBlobReference(path);
|
|
SetBlobPropertiesBasedOnFileType(blob);
|
|
}
|
|
|
|
private void SetBlobPropertiesBasedOnFileType(CloudBlockBlob blockBlob)
|
|
{
|
|
if (Path.GetExtension(blockBlob.Uri.AbsolutePath.ToLower()) == ".svg")
|
|
{
|
|
blockBlob.Properties.ContentType = "image/svg+xml";
|
|
blockBlob.Properties.CacheControl = "no-cache";
|
|
blockBlob.SetPropertiesAsync().Wait();
|
|
}
|
|
else if (Path.GetExtension(blockBlob.Uri.AbsolutePath.ToLower()) == ".version")
|
|
{
|
|
blockBlob.Properties.ContentType = "text/plain";
|
|
blockBlob.Properties.CacheControl = "no-cache";
|
|
blockBlob.SetPropertiesAsync().Wait();
|
|
}
|
|
}
|
|
|
|
public IEnumerable<string> ListBlobs(Product product, string version)
|
|
{
|
|
string virtualDirectory = $"{product}/{version}";
|
|
return ListBlobs(virtualDirectory);
|
|
}
|
|
|
|
public IEnumerable<string> ListBlobs(string virtualDirectory)
|
|
{
|
|
CloudBlobDirectory blobDir = _blobContainer.GetDirectoryReference(virtualDirectory);
|
|
BlobContinuationToken continuationToken = new BlobContinuationToken();
|
|
|
|
var blobFiles = blobDir.ListBlobsSegmentedAsync(continuationToken).Result;
|
|
return blobFiles.Results.Select(bf => bf.Uri.PathAndQuery.Replace($"/{_containerName}/", string.Empty));
|
|
}
|
|
|
|
public string AcquireLeaseOnBlob(
|
|
string blob,
|
|
TimeSpan? maxWaitDefault = null,
|
|
TimeSpan? delayDefault = null)
|
|
{
|
|
TimeSpan maxWait = maxWaitDefault ?? TimeSpan.FromSeconds(120);
|
|
TimeSpan delay = delayDefault ?? TimeSpan.FromMilliseconds(500);
|
|
|
|
Stopwatch stopWatch = new Stopwatch();
|
|
stopWatch.Start();
|
|
|
|
// This will throw an exception with HTTP code 409 when we cannot acquire the lease
|
|
// But we should block until we can get this lease, with a timeout (maxWaitSeconds)
|
|
while (stopWatch.ElapsedMilliseconds < maxWait.TotalMilliseconds)
|
|
{
|
|
try
|
|
{
|
|
CloudBlockBlob cloudBlob = _blobContainer.GetBlockBlobReference(blob);
|
|
Task<string> task = cloudBlob.AcquireLeaseAsync(TimeSpan.FromMinutes(1), null);
|
|
task.Wait();
|
|
return task.Result;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine($"Retrying lease acquisition on {blob}, {e.Message}");
|
|
Thread.Sleep(delay);
|
|
}
|
|
}
|
|
|
|
throw new Exception($"Unable to acquire lease on {blob}");
|
|
}
|
|
|
|
public void ReleaseLeaseOnBlob(string blob, string leaseId)
|
|
{
|
|
CloudBlockBlob cloudBlob = _blobContainer.GetBlockBlobReference(blob);
|
|
AccessCondition ac = new AccessCondition() { LeaseId = leaseId };
|
|
cloudBlob.ReleaseLeaseAsync(ac).Wait();
|
|
}
|
|
|
|
public bool IsLatestSpecifiedVersion(string version)
|
|
{
|
|
Task<bool> task = _blobContainer.GetBlockBlobReference(version).ExistsAsync();
|
|
task.Wait();
|
|
return task.Result;
|
|
}
|
|
|
|
public void DropLatestSpecifiedVersion(string version)
|
|
{
|
|
CloudBlockBlob blob = _blobContainer.GetBlockBlobReference(version);
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
blob.UploadFromStreamAsync(ms).Wait();
|
|
}
|
|
}
|
|
|
|
public void CreateBlobIfNotExists(string path)
|
|
{
|
|
Task<bool> task = _blobContainer.GetBlockBlobReference(path).ExistsAsync();
|
|
task.Wait();
|
|
if (!task.Result)
|
|
{
|
|
CloudBlockBlob blob = _blobContainer.GetBlockBlobReference(path);
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
blob.UploadFromStreamAsync(ms).Wait();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool TryDeleteBlob(string path)
|
|
{
|
|
try
|
|
{
|
|
DeleteBlob(path);
|
|
|
|
return true;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine($"Deleting blob {path} failed with \r\n{e.Message}");
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void DeleteBlob(string path)
|
|
{
|
|
_blobContainer.GetBlockBlobReference(path).DeleteAsync().Wait();
|
|
}
|
|
|
|
private static string CalculateRelativePathForFile(string file, Product product, string version)
|
|
{
|
|
return $"{product}/{version}/{Path.GetFileName(file)}";
|
|
}
|
|
}
|
|
}
|