/// <summary> /// Push a single package to the azure devops nuget feed. /// </summary> /// <param name="feedConfig">Feed</param> /// <param name="packageToPublish">Package to push</param> /// <returns>Task</returns> /// <remarks> /// This method attempts to take the most efficient path to push the package. /// There are two cases: /// - The package does not exist, and is pushed normally /// - The package exists, and its contents may or may not be equivalent. /// The second case is is by far the most common. So, we first attempt to push the package normally using nuget.exe. /// If this fails, this could mean any number of things (like failed auth). But in normal circumstances, this might /// mean the package already exists. This either means that we are attempting to push the same package, or attemtping to push /// a different package with the same id and version. The second case is an error, as azure devops feeds are immutable, the former /// is simply a case where we should continue onward. /// </remarks> private async Task PushNugetPackageAsync(FeedConfig feedConfig, HttpClient client, PackageArtifactModel packageToPublish, string feedAccount, string feedVisibility, string feedName) { Log.LogMessage(MessageImportance.High, $"Pushing package '{packageToPublish.Id}' to feed {feedConfig.TargetFeedURL}"); PackageAssetsBasePath = PackageAssetsBasePath.TrimEnd( Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; string localPackageLocation = $"{PackageAssetsBasePath}{packageToPublish.Id}.{packageToPublish.Version}.nupkg"; if (!File.Exists(localPackageLocation)) { Log.LogError($"Could not locate '{packageToPublish.Id}.{packageToPublish.Version}' at '{localPackageLocation}'"); return; } try { // The feed key when pushing to AzDo feeds is "AzureDevOps" (works with the credential helper). int result = await StartProcessAsync(NugetPath, $"push \"{localPackageLocation}\" -Source \"{feedConfig.TargetFeedURL}\" -NonInteractive -ApiKey AzureDevOps"); if (result != 0) { Log.LogMessage(MessageImportance.Low, $"Failed to push {localPackageLocation}, attempting to determine whether the package already exists on the feed with the same content."); try { string packageContentUrl = $"https://pkgs.dev.azure.com/{feedAccount}/{feedVisibility}_apis/packaging/feeds/{feedName}/nuget/packages/{packageToPublish.Id}/versions/{packageToPublish.Version}/content"; if (await IsLocalPackageIdenticalToFeedPackage(localPackageLocation, packageContentUrl, client)) { Log.LogMessage(MessageImportance.Normal, $"Package '{packageToPublish.Id}@{packageToPublish.Version}' already exists on '{feedConfig.TargetFeedURL}' but has the same content. Skipping."); } else { Log.LogError($"Package '{packageToPublish.Id}@{packageToPublish.Version}' already exists on '{feedConfig.TargetFeedURL}' with different content."); } return; } catch (Exception e) { // This is an error. It means we were unable to push using nuget, and then could not access to the package otherwise. Log.LogWarning($"Failed to determine whether an existing package on the feed has the same content as '{localPackageLocation}': {e.Message}"); } Log.LogError($"Failed to push '{packageToPublish.Id}@{packageToPublish.Version}'. Result code '{result}'."); } } catch (Exception e) { Log.LogError($"Unexpected exception pushing package '{packageToPublish.Id}@{packageToPublish.Version}': {e.Message}"); } }
private async Task PublishPackagesToAzureStorageNugetFeedAsync( List <PackageArtifactModel> packagesToPublish, IMaestroApi client, Maestro.Client.Models.Build buildInformation, FeedConfig feedConfig) { PackageAssetsBasePath = PackageAssetsBasePath.TrimEnd( Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; var packages = packagesToPublish.Select(p => $"{PackageAssetsBasePath}{p.Id}.{p.Version}.nupkg"); var blobFeedAction = CreateBlobFeedAction(feedConfig); var pushOptions = new PushOptions { AllowOverwrite = false, PassIfExistingItemIdentical = true }; foreach (var package in packagesToPublish) { var assetRecord = buildInformation.Assets .Where(a => a.Name.Equals(package.Id) && a.Version.Equals(package.Version)) .FirstOrDefault(); if (assetRecord == null) { Log.LogError($"Asset with Id {package.Id}, Version {package.Version} isn't registered on the BAR Build with ID {BARBuildId}"); continue; } var assetWithLocations = await client.Assets.GetAssetAsync(assetRecord.Id); if (assetWithLocations?.Locations.Any(al => al.Location.Equals(feedConfig.TargetFeedURL, StringComparison.OrdinalIgnoreCase)) ?? false) { Log.LogMessage($"Asset with Id {package.Id}, Version {package.Version} already has location {feedConfig.TargetFeedURL}"); continue; } await client.Assets.AddAssetLocationToAssetAsync(assetRecord.Id, AddAssetLocationToAssetAssetLocationType.NugetFeed, feedConfig.TargetFeedURL); } await blobFeedAction.PushToFeedAsync(packages, pushOptions); }
public async Task <bool> ExecuteAsync() { try { Log.LogMessage(MessageImportance.High, "Performing push feeds."); if (string.IsNullOrWhiteSpace(ExpectedFeedUrl) || string.IsNullOrWhiteSpace(AccountKey)) { Log.LogError($"{nameof(ExpectedFeedUrl)} / {nameof(AccountKey)} is not set properly."); } else if (string.IsNullOrWhiteSpace(AssetManifestPath) || !File.Exists(AssetManifestPath)) { Log.LogError($"Problem reading asset manifest path from {AssetManifestPath}"); } 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."); } var buildModel = BuildManifestUtil.ManifestFileToModel(AssetManifestPath, Log); // Parsing the manifest may fail for several reasons if (Log.HasLoggedErrors) { return(false); } // Fetch Maestro record of the build. We're going to use it to get the BAR ID // of the assets being published so we can add a new location for them. IMaestroApi client = ApiFactory.GetAuthenticated(MaestroApiEndpoint, BuildAssetRegistryToken); Maestro.Client.Models.Build buildInformation = await client.Builds.GetBuildAsync(BARBuildId); var blobFeedAction = new BlobFeedAction(ExpectedFeedUrl, AccountKey, Log); var pushOptions = new PushOptions { AllowOverwrite = Overwrite, PassIfExistingItemIdentical = PassIfExistingItemIdentical }; if (buildModel.Artifacts.Packages.Any()) { if (!Directory.Exists(PackageAssetsBasePath)) { Log.LogError($"Invalid {nameof(PackageAssetsBasePath)} was supplied: {PackageAssetsBasePath}"); return(false); } PackageAssetsBasePath = PackageAssetsBasePath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; var packages = buildModel.Artifacts.Packages.Select(p => $"{PackageAssetsBasePath}{p.Id}.{p.Version}.nupkg"); await blobFeedAction.PushToFeedAsync(packages, pushOptions); foreach (var package in buildModel.Artifacts.Packages) { var assetRecord = buildInformation.Assets .Where(a => a.Name.Equals(package.Id) && a.Version.Equals(package.Version)) .Single(); if (assetRecord == null) { Log.LogError($"Asset with Id {package.Id}, Version {package.Version} isn't registered on the BAR Build with ID {BARBuildId}"); continue; } await client.Assets.AddAssetLocationToAssetAsync(assetRecord.Id.Value, ExpectedFeedUrl, "NugetFeed"); } } if (buildModel.Artifacts.Blobs.Any()) { if (!Directory.Exists(BlobAssetsBasePath)) { Log.LogError($"Invalid {nameof(BlobAssetsBasePath)} was supplied: {BlobAssetsBasePath}"); return(false); } BlobAssetsBasePath = BlobAssetsBasePath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + Path.DirectorySeparatorChar; var blobs = buildModel.Artifacts.Blobs .Select(blob => { var fileName = Path.GetFileName(blob.Id); return(new MSBuild.TaskItem($"{BlobAssetsBasePath}{fileName}", new Dictionary <string, string> { { "RelativeBlobPath", $"{BuildManifestUtil.AssetsVirtualDir}{blob.Id}" } })); }) .ToArray(); await blobFeedAction.PublishToFlatContainerAsync(blobs, MaxClients, UploadTimeoutInMinutes, pushOptions); foreach (var package in buildModel.Artifacts.Blobs) { var assetRecord = buildInformation.Assets .Where(a => a.Name.Equals(package.Id)) .SingleOrDefault(); if (assetRecord == null) { Log.LogError($"Asset with Id {package.Id} isn't registered on the BAR Build with ID {BARBuildId}"); continue; } await client.Assets.AddAssetLocationToAssetAsync(assetRecord.Id.Value, ExpectedFeedUrl, "NugetFeed"); } } } catch (Exception e) { Log.LogErrorFromException(e, true); } return(!Log.HasLoggedErrors); }