File: src\PushToBlobFeed.cs
Web Access
Project: src\src\Microsoft.DotNet.Build.Tasks.Feed\Microsoft.DotNet.Build.Tasks.Feed.csproj (Microsoft.DotNet.Build.Tasks.Feed)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Arcade.Common;
using Microsoft.Build.Framework;
using Microsoft.DotNet.VersionTools.Automation;
using Microsoft.DotNet.VersionTools.BuildManifest.Model;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
 
namespace Microsoft.DotNet.Build.Tasks.Feed
{
    public class PushToBlobFeed : MSBuildTaskBase
    {
        [Required]
        public string ExpectedFeedUrl { get; set; }
 
        [Required]
        public string AccountKey { get; set; }
 
        [Required]
        public ITaskItem[] ItemsToPush { get; set; }
 
        public bool Overwrite { get; set; }
 
        /// <summary>
        /// Enables idempotency when Overwrite is false.
        /// 
        /// false: (default) Attempting to upload an item that already exists fails.
        /// 
        /// true: When an item already exists, download the existing blob to check if it's
        /// byte-for-byte identical to the one being uploaded. If so, pass. If not, fail.
        /// </summary>
        public bool PassIfExistingItemIdentical { get; set; }
 
        public bool PublishFlatContainer { get; set; }
 
        public int MaxClients { get; set; } = 8;
 
        public bool SkipCreateContainer { get; set; } = false;
 
        public int UploadTimeoutInMinutes { get; set; } = 5;
 
        public string ManifestRepoUri { get; set; }
 
        public string ManifestBuildId { get; set; } = "no build id provided";
 
        public string ManifestBranch { get; set; }
 
        public string ManifestCommit { get; set; }
 
        public string[] ManifestBuildData { get; set; }
 
        public string AssetManifestPath { get; set; }
 
        public bool IsStableBuild { get; set; }
 
        public bool IsReleaseOnlyPackageVersion { get; set; }
 
        private IBuildModelFactory _buildModelFactory;
        private IPackageArtifactModelFactory _packageArtifactModelFactory;
        private IBlobArtifactModelFactory _blobArtifactModelFactory;
 
        public override void ConfigureServices(IServiceCollection collection)
        {
            collection.TryAddSingleton<IBuildModelFactory, BuildModelFactory>();
            collection.TryAddSingleton<ISigningInformationModelFactory, SigningInformationModelFactory>();
            collection.TryAddSingleton<IBlobArtifactModelFactory, BlobArtifactModelFactory>();
            collection.TryAddSingleton<IPackageArtifactModelFactory, PackageArtifactModelFactory>();
            collection.TryAddSingleton<INupkgInfoFactory, NupkgInfoFactory>();
            collection.TryAddSingleton<IPackageArchiveReaderFactory, PackageArchiveReaderFactory>();
            collection.TryAddSingleton<IFileSystem, FileSystem>();
            collection.TryAddSingleton(Log);
        }
 
        public bool ExecuteTask(IBuildModelFactory buildModelFactory,
            IBlobArtifactModelFactory blobArtifactModelFactory,
            IPackageArtifactModelFactory packageArtifactModelFactory)
        {
            _buildModelFactory = buildModelFactory;
            _packageArtifactModelFactory = packageArtifactModelFactory;
            _blobArtifactModelFactory = blobArtifactModelFactory;
 
            return ExecuteAsync().GetAwaiter().GetResult();
        }
 
        private async Task<bool> ExecuteAsync()
        {
            try
            {
                Log.LogMessage(MessageImportance.High, "Performing feed push...");
 
                if (ItemsToPush == null)
                {
                    Log.LogError($"No items to push. Please check ItemGroup ItemsToPush.");
                }
                else if (string.IsNullOrWhiteSpace(ExpectedFeedUrl) || string.IsNullOrWhiteSpace(AccountKey))
                {
                    Log.LogError($"{nameof(ExpectedFeedUrl)} / {nameof(AccountKey)} is not set properly.");
                }
                else if (string.IsNullOrWhiteSpace(AssetManifestPath))
                {
                    Log.LogError($"{nameof(AssetManifestPath)} is not set properly.");
                }
                else if (MaxClients <= 0)
                {
                    Log.LogError($"{nameof(MaxClients)} should be greater than zero.");
                }
                else if (UploadTimeoutInMinutes <= 0)
                {
                    Log.LogError($"{nameof(UploadTimeoutInMinutes)} should be greater than zero.");
                }
 
                if (Log.HasLoggedErrors)
                {
                    return false;
                }
 
                BlobFeedAction blobFeedAction = new BlobFeedAction(ExpectedFeedUrl, AccountKey, Log);
                var pushOptions = new PushOptions
                {
                    AllowOverwrite = Overwrite,
                    PassIfExistingItemIdentical = PassIfExistingItemIdentical
                };
 
                IEnumerable<BlobArtifactModel> blobArtifacts = Enumerable.Empty<BlobArtifactModel>();
                IEnumerable<PackageArtifactModel> packageArtifacts = Enumerable.Empty<PackageArtifactModel>();
 
                if (!SkipCreateContainer)
                {
                    await blobFeedAction.CreateContainerAsync(BuildEngine, PublishFlatContainer);
                }
 
                if (PublishFlatContainer)
                {
                    await blobFeedAction.PublishToFlatContainerAsync(ItemsToPush, 
                        MaxClients, 
                        pushOptions);
                    blobArtifacts = ConcatBlobArtifacts(blobArtifacts, ItemsToPush);
                }
                else
                {
                    throw new NotImplementedException("Sleet feeds no longer supported.");
                }
 
                if (!(MSBuildListSplitter.GetNamedProperties(ManifestBuildData).ContainsKey("Location") || 
                    MSBuildListSplitter.GetNamedProperties(ManifestBuildData).ContainsKey("InitialAssetsLocation")))
                {
                    string[] locationAttribute = new string[] { $"Location={ExpectedFeedUrl}" };
                    ManifestBuildData = ManifestBuildData == null ? locationAttribute : ManifestBuildData.Concat(locationAttribute).ToArray();
                }
 
                _buildModelFactory.CreateBuildManifest(
                    blobArtifacts,
                    packageArtifacts,
                    AssetManifestPath,
                    ManifestRepoUri,
                    ManifestBuildId,
                    ManifestBranch,
                    ManifestCommit,
                    ManifestBuildData,
                    IsStableBuild,
                    PublishingInfraVersion.Legacy,
                    IsReleaseOnlyPackageVersion);
            }
            catch (Exception e)
            {
                Log.LogErrorFromException(e, true);
            }
 
            return !Log.HasLoggedErrors;
        }
 
        private IEnumerable<PackageArtifactModel> ConcatPackageArtifacts(
            IEnumerable<PackageArtifactModel> artifacts,
            IEnumerable<ITaskItem> items)
        {
            return artifacts.Concat(items
                .Where(i => !string.Equals(i.GetMetadata("ExcludeFromManifest"), "true", StringComparison.OrdinalIgnoreCase))
                .Select(_packageArtifactModelFactory.CreatePackageArtifactModel));
        }
 
        private IEnumerable<BlobArtifactModel> ConcatBlobArtifacts(
            IEnumerable<BlobArtifactModel> artifacts,
            IEnumerable<ITaskItem> items)
        {
            return artifacts.Concat(items
                .Where(i => !string.Equals(i.GetMetadata("ExcludeFromManifest"), "true", StringComparison.OrdinalIgnoreCase))
                .Select(i => _blobArtifactModelFactory.CreateBlobArtifactModel(i))
                .Where(blob => blob != null));
        }
    }
}