// 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.Threading; using System.Threading.Tasks; using Microsoft.Build.Framework; using Microsoft.WindowsAzure.Storage; using Microsoft.WindowsAzure.Storage.Blob; namespace RepoTasks { /// /// Publish files to an Azure storage blob /// public class PublishToAzureBlob : Microsoft.Build.Utilities.Task, ICancelableTask { private CancellationTokenSource _cts = new CancellationTokenSource(); /// /// The files to publish. /// [Required] public ITaskItem[] Files { get; set; } /// /// The Azure blob storage account name. /// [Required] public string AccountName { get; set; } /// /// The SAS token used to write to Azure. /// [Required] public string SharedAccessToken { get; set; } /// /// The Azure blob storage container name /// [Required] public string ContainerName { get; set; } /// /// The maximum number of parallel pushes. /// public int MaxParallelism { get; set; } = 8; public void Cancel() => _cts.Cancel(); public override bool Execute() => ExecuteAsync().Result; private async Task ExecuteAsync() { var connectionString = $"BlobEndpoint=https://{AccountName}.blob.core.windows.net;SharedAccessSignature={SharedAccessToken}"; var account = CloudStorageAccount.Parse(connectionString); var client = account.CreateCloudBlobClient(); var container = client.GetContainerReference(ContainerName); var ctx = new OperationContext(); var tasks = new List(); using (var throttler = new SemaphoreSlim(MaxParallelism)) { foreach (var item in Files) { _cts.Token.ThrowIfCancellationRequested(); await throttler.WaitAsync( _cts.Token); tasks.Add( Task.Run(async () => { try { await PushFileAsync(ctx, container, item, _cts.Token); } finally { throttler.Release(); } })); } await Task.WhenAll(tasks); } return !Log.HasLoggedErrors; } private async Task PushFileAsync(OperationContext ctx, CloudBlobContainer container, ITaskItem item, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // normalize slashes var dest = item.GetMetadata("RelativeBlobPath") .Replace('\\', '/') .Replace("//", "/"); var contentType = item.GetMetadata("ContentType"); var cacheControl = item.GetMetadata("CacheControl"); if (string.IsNullOrEmpty(dest)) { Log.LogError($"Item {item.ItemSpec} is missing required metadata 'RelativeBlobPath'"); return; } var blob = container.GetBlockBlobReference(dest); if (!string.IsNullOrEmpty(cacheControl)) { blob.Properties.CacheControl = cacheControl; } if (!string.IsNullOrEmpty(contentType)) { blob.Properties.ContentType = contentType; } Log.LogMessage(MessageImportance.High, $"Beginning push of {item.ItemSpec} to https://{AccountName}.blob.core.windows.net/{ContainerName}/{dest}"); var accessCondition = bool.TryParse(item.GetMetadata("Overwrite"), out var overwrite) && overwrite ? AccessCondition.GenerateEmptyCondition() : AccessCondition.GenerateIfNotExistsCondition(); try { await blob.UploadFromFileAsync(item.ItemSpec, accessCondition, new BlobRequestOptions(), ctx, cancellationToken); } catch (Exception ex) { Log.LogError($"Error publishing {item.ItemSpec}: {ex}"); return; } finally { Log.LogMessage(MessageImportance.High, $"Done publishing {item.ItemSpec} to https://{AccountName}.blob.core.windows.net/{ContainerName}/{dest}"); } } } }