// 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. #if !SOURCE_BUILD using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using System; using System.IO; using System.Linq; using System.Text.RegularExpressions; namespace Microsoft.DotNet.Cli.Build { public class CopyBlobsToLatest : Task { private const string feedRegex = @"(?https:\/\/(?[^\.-]+)(?[^\/]*)\/((?[a-zA-Z0-9+\/]*?\/\d{4}-\d{2}-\d{2})\/)?(?[^\/]+)\/(?.*\/)?)index\.json"; private AzurePublisher _azurePublisher; [Required] public string FeedUrl { get; set; } [Required] public string AccountKey { get; set; } [Required] public string Channel { get; set; } [Required] public string CommitHash { get; set; } [Required] public string NugetVersion { get; set; } private string ContainerName { get; set; } private AzurePublisher AzurePublisherTool { get { if (_azurePublisher == null) { Match m = Regex.Match(FeedUrl, feedRegex); if (m.Success) { string accountName = m.Groups["accountname"].Value; string ContainerName = m.Groups["containername"].Value; _azurePublisher = new AzurePublisher( accountName, AccountKey, ContainerName); } else { throw new Exception( "Unable to parse expected feed. Please check ExpectedFeedUrl."); } } return _azurePublisher; } } public override bool Execute() { string targetFolder = $"{AzurePublisher.Product.Sdk}/{Channel}"; string targetVersionFile = $"{targetFolder}/{CommitHash}"; string semaphoreBlob = $"{targetFolder}/publishSemaphore"; AzurePublisherTool.CreateBlobIfNotExists(semaphoreBlob); string leaseId = AzurePublisherTool.AcquireLeaseOnBlob(semaphoreBlob); // Prevent race conditions by dropping a version hint of what version this is. If we see this file // and it is the same as our version then we know that a race happened where two+ builds finished // at the same time and someone already took care of publishing and we have no work to do. if (AzurePublisherTool.IsLatestSpecifiedVersion(targetVersionFile)) { AzurePublisherTool.ReleaseLeaseOnBlob(semaphoreBlob, leaseId); return true; } else { Regex versionFileRegex = new Regex(@"(?[\w\d]{40})"); // Delete old version files AzurePublisherTool.ListBlobs(targetFolder) .Where(s => versionFileRegex.IsMatch(s)) .ToList() .ForEach(f => AzurePublisherTool.TryDeleteBlob(f)); // Drop the version file signaling such for any race-condition builds (see above comment). AzurePublisherTool.DropLatestSpecifiedVersion(targetVersionFile); } try { CopyBlobs(targetFolder); string cliVersion = GetVersionFileContent(CommitHash, NugetVersion); AzurePublisherTool.PublishStringToBlob($"{targetFolder}/latest.version", cliVersion); } finally { AzurePublisherTool.ReleaseLeaseOnBlob(semaphoreBlob, leaseId); } return true; } private void CopyBlobs(string destinationFolder) { Log.LogMessage("Copying blobs to {0}/{1}", ContainerName, destinationFolder); foreach (string blob in AzurePublisherTool.ListBlobs(AzurePublisher.Product.Sdk, NugetVersion)) { string targetName = Path.GetFileName(blob) .Replace(NugetVersion, "latest"); string target = $"{destinationFolder}/{targetName}"; AzurePublisherTool.CopyBlob(blob, target); } } private string GetVersionFileContent(string commitHash, string version) { return $@"{commitHash}{Environment.NewLine}{version}{Environment.NewLine}"; } } } #endif