// 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.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).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 ListBlobs(Product product, string version) { string virtualDirectory = $"{product}/{version}"; return ListBlobs(virtualDirectory); } public IEnumerable 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 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 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 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)}"; } } }