/// <summary> /// Parse out the input TargetFeedConfig into a dictionary of FeedConfig types /// </summary> public async Task ParseTargetFeedConfigAsync() { using (HttpClient httpClient = new HttpClient(new HttpClientHandler { CheckCertificateRevocationList = true })) { foreach (var fc in TargetFeedConfig) { string targetFeedUrl = fc.GetMetadata(nameof(Model.TargetFeedConfig.TargetURL)); string feedKey = fc.GetMetadata(nameof(Model.TargetFeedConfig.Token)); string type = fc.GetMetadata(nameof(Model.TargetFeedConfig.Type)); AssetSelection assetSelection = AssetSelection.All; bool isInternalFeed; bool isIsolatedFeed = false; bool isOverridableFeed = false; if (string.IsNullOrEmpty(targetFeedUrl) || string.IsNullOrEmpty(feedKey) || string.IsNullOrEmpty(type)) { Log.LogError($"Invalid FeedConfig entry. {nameof(Model.TargetFeedConfig.TargetURL)}='{targetFeedUrl}' {nameof(Model.TargetFeedConfig.Type)}='{type}' {nameof(Model.TargetFeedConfig.Token)}='{feedKey}'"); continue; } if (!targetFeedUrl.EndsWith(PublishingConstants.ExpectedFeedUrlSuffix)) { Log.LogError($"Exepcted that feed '{targetFeedUrl}' would end in {PublishingConstants.ExpectedFeedUrlSuffix}"); continue; } if (!Enum.TryParse <FeedType>(type, true, out FeedType feedType)) { Log.LogError($"Invalid feed config type '{type}'. Possible values are: {string.Join(", ", Enum.GetNames(typeof(FeedType)))}"); continue; } string assetSelectionStr = fc.GetMetadata(nameof(Model.TargetFeedConfig.AssetSelection)); if (!string.IsNullOrEmpty(assetSelectionStr)) { if (!Enum.TryParse <AssetSelection>(assetSelectionStr, true, out assetSelection)) { Log.LogError($"Invalid feed config asset selection '{type}'. Possible values are: {string.Join(", ", Enum.GetNames(typeof(AssetSelection)))}"); continue; } } // To determine whether a feed is internal, we allow the user to // specify the value explicitly. string isInternalFeedStr = fc.GetMetadata(nameof(Model.TargetFeedConfig.Internal)); if (!string.IsNullOrEmpty(isInternalFeedStr)) { if (!bool.TryParse(isInternalFeedStr, out isInternalFeed)) { Log.LogError($"Invalid feed config '{nameof(Model.TargetFeedConfig.Internal)}' setting. Must be 'true' or 'false'."); continue; } } else { bool?isPublicFeed = await GeneralUtils.IsFeedPublicAsync(targetFeedUrl, httpClient, Log); if (!isPublicFeed.HasValue) { continue; } else { isInternalFeed = !isPublicFeed.Value; } } string isIsolatedFeedStr = fc.GetMetadata(nameof(Model.TargetFeedConfig.Isolated)); if (!string.IsNullOrEmpty(isIsolatedFeedStr)) { if (!bool.TryParse(isIsolatedFeedStr, out isIsolatedFeed)) { Log.LogError($"Invalid feed config '{nameof(Model.TargetFeedConfig.Isolated)}' setting. Must be 'true' or 'false'."); continue; } } string allowOverwriteOnFeed = fc.GetMetadata(nameof(Model.TargetFeedConfig.AllowOverwrite)); if (!string.IsNullOrEmpty(allowOverwriteOnFeed)) { if (!bool.TryParse(allowOverwriteOnFeed, out isOverridableFeed)) { Log.LogError($"Invalid feed config '{nameof(Model.TargetFeedConfig.AllowOverwrite)}' setting. Must be 'true' or 'false'."); continue; } } string latestLinkShortUrlPrefix = fc.GetMetadata(nameof(Model.TargetFeedConfig.LatestLinkShortUrlPrefixes)); if (!string.IsNullOrEmpty(latestLinkShortUrlPrefix)) { // Verify other inputs are provided if (string.IsNullOrEmpty(AkaMSClientId) || string.IsNullOrEmpty(AkaMSClientSecret) || string.IsNullOrEmpty(AkaMSTenant) || string.IsNullOrEmpty(AkaMsOwners) || string.IsNullOrEmpty(AkaMSCreatedBy)) { Log.LogError($"If a short url path is provided, please provide {nameof(AkaMSClientId)}, {nameof(AkaMSClientSecret)}, " + $"{nameof(AkaMSTenant)}, {nameof(AkaMsOwners)}, {nameof(AkaMSCreatedBy)}"); continue; } // Set up the link manager if it hasn't already been done if (LinkManager == null) { LinkManager = new LatestLinksManager(AkaMSClientId, AkaMSClientSecret, AkaMSTenant, AkaMSGroupOwner, AkaMSCreatedBy, AkaMsOwners, Log); } } if (!Enum.TryParse(fc.ItemSpec, ignoreCase: true, out TargetFeedContentType categoryKey)) { Log.LogError($"Invalid target feed config category '{fc.ItemSpec}'."); } if (!FeedConfigs.TryGetValue(categoryKey, out _)) { FeedConfigs[categoryKey] = new HashSet <TargetFeedConfig>(); } TargetFeedConfig feedConfig = new TargetFeedConfig( contentType: categoryKey, targetURL: targetFeedUrl, type: feedType, token: feedKey, latestLinkShortUrlPrefixes: new List <string>() { latestLinkShortUrlPrefix }, assetSelection: assetSelection, isolated: isIsolatedFeed, @internal: isInternalFeed, allowOverwrite: isOverridableFeed); CheckForInternalBuildsOnPublicFeeds(feedConfig); FeedConfigs[categoryKey].Add(feedConfig); } } }
public override async Task <bool> ExecuteAsync() { if (AnyMissingRequiredProperty()) { Log.LogError("Missing required properties. Aborting execution."); return(false); } try { List <int> targetChannelsIds = new List <int>(); foreach (var channelIdStr in TargetChannels.Split('-')) { if (!int.TryParse(channelIdStr, out var channelId)) { Log.LogError( $"Value '{channelIdStr}' isn't recognized as a valid Maestro++ channel ID. To add a channel refer to https://github.com/dotnet/arcade/blob/master/Documentation/CorePackages/Publishing.md#how-to-add-a-new-channel-to-use-v3-publishing."); continue; } targetChannelsIds.Add(channelId); } if (Log.HasLoggedErrors) { Log.LogError( $"Could not parse the target channels list '{TargetChannels}'. It should be a comma separated list of integers."); return(false); } SplitArtifactsInCategories(BuildModel); 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); Dictionary <string, HashSet <Asset> > buildAssets = CreateBuildAssetDictionary(buildInformation); foreach (var targetChannelId in targetChannelsIds) { TargetChannelConfig targetChannelConfig = PublishingConstants.ChannelInfos .Where(ci => ci.Id == targetChannelId && (ci.PublishingInfraVersion == PublishingInfraVersion.All || ci.PublishingInfraVersion == PublishingInfraVersion.Next)) .FirstOrDefault(); // Invalid channel ID was supplied if (targetChannelConfig.Equals(default(TargetChannelConfig))) { Log.LogError($"Channel with ID '{targetChannelId}' is not configured to be published to."); return(false); } if (await client.Channels.GetChannelAsync(targetChannelId) == null) { Log.LogError($"Channel with ID '{targetChannelId}' does not exist in BAR."); return(false); } Log.LogMessage(MessageImportance.High, $"Publishing to this target channel: {targetChannelConfig}"); string shortLinkUrl = string.IsNullOrEmpty(targetChannelConfig.AkaMSChannelName) ? $"dotnet/" : $"dotnet/{targetChannelConfig.AkaMSChannelName}/{BuildQuality}"; var targetFeedsSetup = new SetupTargetFeedConfigV3( targetChannelConfig.IsInternal, BuildModel.Identity.IsStable, BuildModel.Identity.Name, BuildModel.Identity.Commit, AzureStorageTargetFeedKey, PublishInstallersAndChecksums, GetFeed(targetChannelConfig.InstallersFeed, InstallersFeedOverride), targetChannelConfig.IsInternal ? InternalInstallersFeedKey : InstallersFeedKey, GetFeed(targetChannelConfig.ChecksumsFeed, ChecksumsFeedOverride), targetChannelConfig.IsInternal ? InternalCheckSumsFeedKey : CheckSumsFeedKey, GetFeed(targetChannelConfig.ShippingFeed, ShippingFeedOverride), GetFeed(targetChannelConfig.TransportFeed, TransportFeedOverride), GetFeed(targetChannelConfig.SymbolsFeed, SymbolsFeedOverride), shortLinkUrl, AzureDevOpsFeedsKey, BuildEngine = this.BuildEngine, targetChannelConfig.SymbolTargetType, azureDevOpsPublicStaticSymbolsFeed: GetFeed(null, PublicSymbolsFeedOverride), filesToExclude: targetChannelConfig.FilenamesToExclude, flatten: targetChannelConfig.Flatten); var targetFeedConfigs = targetFeedsSetup.Setup(); // No target feeds to publish to, very likely this is an error if (targetFeedConfigs.Count() == 0) { Log.LogError($"No target feeds were found to publish the assets to."); return(false); } foreach (var feedConfig in targetFeedConfigs) { Log.LogMessage(MessageImportance.High, $"Target feed config: {feedConfig}"); TargetFeedContentType categoryKey = feedConfig.ContentType; if (!FeedConfigs.TryGetValue(categoryKey, out _)) { FeedConfigs[categoryKey] = new HashSet <TargetFeedConfig>(); } FeedConfigs[categoryKey].Add(feedConfig); } } CheckForStableAssetsInNonIsolatedFeeds(); if (Log.HasLoggedErrors) { return(false); } string temporarySymbolsLocation = ""; if (!UseStreamingPublishing) { temporarySymbolsLocation = Path.GetFullPath(Path.Combine(BlobAssetsBasePath, @"..\", "tempSymbols")); EnsureTemporaryDirectoryExists(temporarySymbolsLocation); DeleteTemporaryFiles(temporarySymbolsLocation); // Copying symbol files to temporary location is required because the symUploader API needs read/write access to the files, // since we publish blobs and symbols in parallel this will cause IO exceptions. CopySymbolFilesToTemporaryLocation(BuildModel, temporarySymbolsLocation); } using var clientThrottle = new SemaphoreSlim(MaxClients, MaxClients); await Task.WhenAll(new Task[] { HandlePackagePublishingAsync(buildAssets, clientThrottle), HandleBlobPublishingAsync(buildAssets, clientThrottle), HandleSymbolPublishingAsync( PdbArtifactsBasePath, MsdlToken, SymWebToken, SymbolPublishingExclusionsFile, PublishSpecialClrFiles, buildAssets, clientThrottle, temporarySymbolsLocation) }); DeleteTemporaryFiles(temporarySymbolsLocation); DeleteTemporaryDirectory(temporarySymbolsLocation); await PersistPendingAssetLocationAsync(client); } catch (Exception e) { Log.LogErrorFromException(e, true); } if (!Log.HasLoggedErrors) { Log.LogMessage(MessageImportance.High, "Publishing finished with success."); } return(!Log.HasLoggedErrors); }
public override async Task<bool> ExecuteAsync() { if (AnyMissingRequiredProperty()) { Log.LogError("Missing required properties. Aborting execution."); return false; } try { List<int> targetChannelsIds = new List<int>(); foreach (var channelIdStr in TargetChannels.Split(',')) { if (!int.TryParse(channelIdStr, out var channelId)) { Log.LogError($"Value '{channelIdStr}' isn't recognized as a valid Maestro++ channel ID."); continue; } targetChannelsIds.Add(channelId); } if (Log.HasLoggedErrors) { Log.LogError($"Could not parse the target channels list '{TargetChannels}'. It should be a comma separated list of integers."); return false; } SplitArtifactsInCategories(BuildModel); 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); Dictionary<string, HashSet<Asset>> buildAssets = CreateBuildAssetDictionary(buildInformation); foreach (var targetChannelId in targetChannelsIds) { TargetChannelConfig targetChannelConfig = PublishingConstants.ChannelInfos .Where(ci => ci.Id == targetChannelId && (ci.PublishingInfraVersion == PublishingInfraVersion.All || ci.PublishingInfraVersion == PublishingInfraVersion.Next)) .FirstOrDefault(); // Invalid channel ID was supplied if (targetChannelConfig.Equals(default(TargetChannelConfig))) { Log.LogError($"Channel with ID '{targetChannelId}' is not configured to be published to."); return false; } if (await client.Channels.GetChannelAsync(targetChannelId) == null) { Log.LogError($"Channel with ID '{targetChannelId}' does not exist in BAR."); return false; } Log.LogMessage(MessageImportance.High, $"Publishing to this target channel: {targetChannelConfig}"); var targetFeedsSetup = new SetupTargetFeedConfigV3( InternalBuild, BuildModel.Identity.IsStable.Equals("true", System.StringComparison.OrdinalIgnoreCase), BuildModel.Identity.Name, BuildModel.Identity.Commit, AzureStorageTargetFeedKey, PublishInstallersAndChecksums, targetChannelConfig.InstallersFeed, InstallersFeedKey, targetChannelConfig.ChecksumsFeed, CheckSumsFeedKey, targetChannelConfig.ShippingFeed, targetChannelConfig.TransportFeed, targetChannelConfig.SymbolsFeed, $"dotnet/{targetChannelConfig.AkaMSChannelName}", AzureDevOpsFeedsKey, BuildEngine = this.BuildEngine); var targetFeedConfigs = targetFeedsSetup.Setup(); // No target feeds to publish to, very likely this is an error if (targetFeedConfigs.Count() == 0) { Log.LogError($"No target feeds were found to publish the assets to."); return false; } foreach (var feedConfig in targetFeedConfigs) { Log.LogMessage(MessageImportance.High, $"Target feed config: {feedConfig}"); TargetFeedContentType categoryKey = feedConfig.ContentType; if (!FeedConfigs.TryGetValue(categoryKey, out _)) { FeedConfigs[categoryKey] = new HashSet<TargetFeedConfig>(); } FeedConfigs[categoryKey].Add(feedConfig); } } CheckForStableAssetsInNonIsolatedFeeds(); if (Log.HasLoggedErrors) { return false; } await Task.WhenAll(new Task[] { HandlePackagePublishingAsync(buildAssets), HandleBlobPublishingAsync(buildAssets) }); await PersistPendingAssetLocationAsync(client); } catch (Exception e) { Log.LogErrorFromException(e, true); } if (!Log.HasLoggedErrors) { Log.LogMessage(MessageImportance.High, "Publishing finished with success."); } return !Log.HasLoggedErrors; }