From f730ca6052b482b4c9c4ba49447c93cd005d19ee Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Wed, 13 Oct 2021 15:35:01 -0700 Subject: [PATCH 1/5] Fixup automatically create feed permissions Tighten up the feed permissions used when creating new feeds on the fly, or when publishing signed assets, and do a bit of refactoring in the process. - Put internal feeds into the internal project, rather than the organization. This is where they should have been from the beginning. There was mainly confusion about project vs. org scoped feeds when this was created. - Tweak the default feed permissions so that: - Members of dnceng get read perms on the local view (this avoids an issue where we used to have to add people as Contributors). This is done by patching the local feed view after feed creation. - Admins get ownership - the internal and collection build service accounts get write access - Added some nice NYI exceptions that should fire if we ever change the organization or project that we are running in. - Update references to the task, in PublishSignedAssets.proj and SetupTargetFeedConfigV3, to use the new task parameters. Refactoring: - Remove SetupTargetFeeds.proj (v2 support), which is unused. I chose to do this because it also contain a reference to CreateAzureDevOpsFeed - Set the default publishing version to 3 - Remove IsInternal as a feed task creation parameter and replace it with the AzDO project. The AzDO project defines the visibility, and this cleans up a little of the dnceng-isms and enables moving them into some helper methods - Add helper methods for determining some aspects of the feed names - Refactor some variable names to make them clearer --- .../SdkTasks/PublishArtifactsInManifest.proj | 9 +- .../tools/SdkTasks/PublishSignedAssets.proj | 22 +- .../tools/SdkTasks/SetupTargetFeeds.proj | 333 ------------------ .../src/CreateAzureDevOpsFeed.cs | 163 +++++++-- .../src/model/SetupTargetFeedConfigV3.cs | 8 +- 5 files changed, 149 insertions(+), 386 deletions(-) delete mode 100644 src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SetupTargetFeeds.proj diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishArtifactsInManifest.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishArtifactsInManifest.proj index d6848ec4dad..05536cace3d 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishArtifactsInManifest.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishArtifactsInManifest.proj @@ -75,16 +75,11 @@ netcoreapp3.1 Publish - - 2 + + 3 - - - - - diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj index ce7e413169a..42a1b05135c 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj @@ -11,12 +11,6 @@ - AzdoTargetFeedPAT : Required token to publish assets to feeds --> - - netcoreapp3.1 - Publish - dnceng - - + + netcoreapp3.1 + Publish + dnceng + internal + public + + + AzureDevOpsOrg="$(AzureDevOpsOrg)" + AzureDevOpsProject="$(AzureDevOpsProject)"> + AzureDevOpsOrg="$(AzureDevOpsOrg)" + AzureDevOpsProject="$(AzureDevOpsProject)"> diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SetupTargetFeeds.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SetupTargetFeeds.proj deleted file mode 100644 index 25aefb367f7..00000000000 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/SetupTargetFeeds.proj +++ /dev/null @@ -1,333 +0,0 @@ - - - - - Publish - - - - - - - - - - - - - - - - - - - - https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json - https://dotnetfeed.blob.core.windows.net/arcade-validation/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-tools-internal/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json - https://dotnetfeed.blob.core.windows.net/nuget-nugetclient/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-entityframework6/index.json - https://dotnetfeed.blob.core.windows.net/aspnet-blazor/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-iot/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-experimental/index.json - https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs index 4b6cfd71bfa..7f13c891f39 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs @@ -23,8 +23,24 @@ public class CreateAzureDevOpsFeed : MSBuild.Task [Output] public string TargetFeedName { get; set; } + /// + /// Organization that the feed should be created in + /// + [Required] + public string AzureDevOpsOrg { get; set; } + + /// + /// Project that that feed should be created in. The public/internal visibility + /// of this project will determine whether the feed is public. + /// [Required] - public bool IsInternal { get; set; } + public string AzureDevOpsProject { get; set; } + + /// + /// Personal access token used to authorize to the API and create the feed + /// + [Required] + public string AzureDevOpsPersonalAccessToken { get; set; } public string RepositoryName { get; set; } @@ -40,12 +56,7 @@ public class CreateAzureDevOpsFeed : MSBuild.Task /// public string ContentIdentifier { get; set; } - [Required] - public string AzureDevOpsPersonalAccessToken { get; set; } - - public string AzureDevOpsFeedsApiVersion { get; set; } = "5.0-preview.1"; - - public string AzureDevOpsOrg { get; set; } = "dnceng"; + public string AzureDevOpsFeedsApiVersion { get; set; } = "5.1-preview.1"; /// /// Number of characters from the commit SHA prefix that should be included in the feed name. @@ -86,16 +97,19 @@ private async Task ExecuteAsync() // or contain any of these: @ ~ ; { } ' + = , < > | / \ ? : & $ * " # [ ] % string feedCompatibleRepositoryName = RepositoryName?.Replace('/', '-'); - string accessType = IsInternal ? "internal" : "public"; - string publicSegment = IsInternal ? string.Empty : "public/"; - string accessId = IsInternal ? "int" : "pub"; + // For clarity, and compatibility with existing infrastructure, we include the feed visibility tag. + // This serves two purposes: + // 1. In nuget.config files (and elsewhere), the name at a glance can identify its visibility + // 2. Existing automation has knowledge of "darc-int" and "darc-pub" for purposes of injecting authentication for internal builds + // and managing the isolated feeds within the NuGet.config files. + string accessTag = GetFeedVisibilityTag(AzureDevOpsOrg, AzureDevOpsProject); string extraContentInfo = !string.IsNullOrEmpty(ContentIdentifier) ? $"-{ContentIdentifier}" : ""; - string baseFeedName = FeedName ?? $"darc-{accessId}{extraContentInfo}-{feedCompatibleRepositoryName}-{CommitSha.Substring(0, ShaUsableLength)}"; + string baseFeedName = FeedName ?? $"darc-{accessTag}{extraContentInfo}-{feedCompatibleRepositoryName}-{CommitSha.Substring(0, ShaUsableLength)}"; string versionedFeedName = baseFeedName; bool needsUniqueName = false; int subVersion = 0; - Log.LogMessage(MessageImportance.High, $"Creating the new {accessType} Azure DevOps artifacts feed '{baseFeedName}'..."); + Log.LogMessage(MessageImportance.High, $"Creating the new Azure DevOps artifacts feed '{baseFeedName}'..."); if (baseFeedName.Length > MaxLengthForAzDoFeedNames) { @@ -118,38 +132,61 @@ private async Task ExecuteAsync() "Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", "", AzureDevOpsPersonalAccessToken)))); - AzureDevOpsArtifactFeed newFeed = new AzureDevOpsArtifactFeed(versionedFeedName, AzureDevOpsOrg); + AzureDevOpsArtifactFeed newFeed = new AzureDevOpsArtifactFeed(versionedFeedName, AzureDevOpsOrg, AzureDevOpsProject); - string body = JsonConvert.SerializeObject(newFeed, _serializerSettings); + string createBody = JsonConvert.SerializeObject(newFeed, _serializerSettings); - HttpRequestMessage postMessage = new HttpRequestMessage(HttpMethod.Post, $"{publicSegment}_apis/packaging/feeds"); - postMessage.Content = new StringContent(body, Encoding.UTF8, "application/json"); - HttpResponseMessage response = await client.SendAsync(postMessage); + using HttpRequestMessage createFeedMessage = new HttpRequestMessage(HttpMethod.Post, $"{AzureDevOpsProject}/_apis/packaging/feeds"); + createFeedMessage.Content = new StringContent(createBody, Encoding.UTF8, "application/json"); + using HttpResponseMessage createFeedResponse = await client.SendAsync(createFeedMessage); - if (response.StatusCode == HttpStatusCode.Created) + if (createFeedResponse.StatusCode == HttpStatusCode.Created) { needsUniqueName = false; baseFeedName = versionedFeedName; + + // Now update the 'Local' feed view with aad tenant visibility. + var feedViewVisibilityPatch = new FeedView() + { + Visibility = "collection" + }; + + string patchBody = JsonConvert.SerializeObject(feedViewVisibilityPatch, _serializerSettings); + + // Note that Framework doesn't natively have Patch +#if NETFRAMEWORK + HttpMethod patchMethod = new HttpMethod("PATCH"); +#else + HttpMethod patchMethod = HttpMethod.Patch; +#endif + using HttpRequestMessage patchFeedViewMessage = new HttpRequestMessage(patchMethod, $"{AzureDevOpsProject}/_apis/packaging/feeds/{baseFeedName}/views/Local"); + patchFeedViewMessage.Content = new StringContent(patchBody, Encoding.UTF8, "application/json"); + using HttpResponseMessage patchFeedViewResponse = await client.SendAsync(patchFeedViewMessage); + + if (patchFeedViewResponse.StatusCode != HttpStatusCode.OK) + { + throw new Exception($"Feed view 'Local' for '{baseFeedName}' could not be updated to have aadTenant visibility. Exception: {await patchFeedViewResponse.Content.ReadAsStringAsync()}"); + } } - else if (response.StatusCode == HttpStatusCode.Conflict) + else if (createFeedResponse.StatusCode == HttpStatusCode.Conflict) { versionedFeedName = $"{baseFeedName}-{++subVersion}"; needsUniqueName = true; if (versionedFeedName.Length > MaxLengthForAzDoFeedNames) { - Log.LogError($"The name of the new feed ({baseFeedName}) exceeds the maximum feed name size of 64 chars. Aborting feed creation."); + Console.WriteLine($"The name of the new feed ({baseFeedName}) exceeds the maximum feed name size of 64 chars. Aborting feed creation."); return false; } } else { - throw new Exception($"Feed '{baseFeedName}' was not created. Request failed with status code {response.StatusCode}. Exception: {await response.Content.ReadAsStringAsync()}"); + throw new Exception($"Feed '{baseFeedName}' was not created. Request failed with status code {createFeedResponse.StatusCode}. Exception: {await createFeedResponse.Content.ReadAsStringAsync()}"); } } } while (needsUniqueName); - TargetFeedURL = $"https://pkgs.dev.azure.com/{AzureDevOpsOrg}/{publicSegment}_packaging/{baseFeedName}/nuget/v3/index.json"; + TargetFeedURL = $"https://pkgs.dev.azure.com/{AzureDevOpsOrg}/{AzureDevOpsProject}/_packaging/{baseFeedName}/nuget/v3/index.json"; TargetFeedName = baseFeedName; Log.LogMessage(MessageImportance.High, $"Feed '{TargetFeedURL}' created successfully!"); @@ -161,11 +198,37 @@ private async Task ExecuteAsync() return !Log.HasLoggedErrors; } + + /// + /// Returns a tag for feed visibility that will be added to the feed name + /// + /// Organization containing the feed + /// Project within containing the feed + /// Feed tag + /// + private string GetFeedVisibilityTag(string organization, string project) + { + switch (organization) + { + case "dnceng": + switch (project) + { + case "internal": + return "int"; + case "public": + return "pub"; + default: + throw new NotImplementedException($"Project '{project}' within organization '{organization}' has no visibility mapping."); + } + default: + throw new NotImplementedException($"Organization '{organization}' has no visibility mapping."); + } + } } public class Permission { - public Permission(string identityDescriptor, int role) + public Permission(string identityDescriptor, string role) { IdentityDescriptor = identityDescriptor; Role = role; @@ -173,24 +236,56 @@ public Permission(string identityDescriptor, int role) public string IdentityDescriptor { get; set; } - public int Role { get; set; } + public string Role { get; set; } } + /// + /// Represents a feed view + /// + public class FeedView + { + public string Visibility { get; set; } + } + + /// + /// Represents the body of a request sent when creating a new feed. + /// + /// > + /// When creating a new feed, we want to set up permissions based on the org and project. + /// Right now, only dnceng's public and internal projects are supported. + /// New feeds automatically get the feed administrators and project collection administrators as owners, + /// but we want to automatically add some additional permissions so that the build services can push to them, + /// and aadTenant users can read from them. + /// public class AzureDevOpsArtifactFeed { - public AzureDevOpsArtifactFeed(string name, string organization) + public AzureDevOpsArtifactFeed(string name, string organization, string project) { Name = name; - if (organization == "dnceng") + switch (organization) { - Permissions = new List - { - // Mimic the permissions added to a feed when created in the browser - new Permission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", 3), // Project Collection Build Service - new Permission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:7ea9116e-9fac-403d-b258-b31fcf1bb293", 3), // internal Build Service - new Permission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", 4), // Feed administrators - new Permission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1846651262-2896117056-2992157471-3474698899-1-2052915359-1158038602-2757432096-2854636005", 4) // Feed administrators and contributors - }; + case "dnceng": + switch (project) + { + case "public": + case "internal": + Permissions = new List + { + // Project Collection Build Service + new Permission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:7ea9116e-9fac-403d-b258-b31fcf1bb293", "contributor"), + // internal Build Service + new Permission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", "contributor"), + // Project administrators + new Permission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", "administrator"), + }; + break; + default: + throw new NotImplementedException($"Project '{project}' within organization '{organization}' contains no feed permissions information."); + } + break; + default: + throw new NotImplementedException($"Organization '{organization}' contains no feed permissions information."); + } } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/SetupTargetFeedConfigV3.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/SetupTargetFeedConfigV3.cs index 999a3c70c58..727a4cbe285 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/SetupTargetFeedConfigV3.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/SetupTargetFeedConfigV3.cs @@ -30,6 +30,8 @@ public class SetupTargetFeedConfigV3 : SetupTargetFeedConfigBase public TaskLoggingHelper Log { get; } + public string AzureDevOpsOrg => "dnceng"; + public SetupTargetFeedConfigV3( TargetChannelConfig targetChannelConfig, bool isInternalBuild, @@ -93,7 +95,8 @@ private IEnumerable Feeds() var packagesFeedTask = new CreateAzureDevOpsFeed() { BuildEngine = BuildEngine, - IsInternal = IsInternalBuild, + AzureDevOpsOrg = AzureDevOpsOrg, + AzureDevOpsProject = IsInternalBuild ? "internal" : "public", AzureDevOpsPersonalAccessToken = AzureDevOpsFeedsKey, RepositoryName = RepositoryName, CommitSha = CommitSha @@ -112,7 +115,8 @@ private IEnumerable Feeds() var symbolsFeedTask = new CreateAzureDevOpsFeed() { BuildEngine = BuildEngine, - IsInternal = IsInternalBuild, + AzureDevOpsOrg = AzureDevOpsOrg, + AzureDevOpsProject = IsInternalBuild ? "internal" : "public", AzureDevOpsPersonalAccessToken = AzureDevOpsFeedsKey, RepositoryName = RepositoryName, CommitSha = CommitSha, From 6f474ea45ac7fbb3f961e10ace3914d0f47da462 Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Wed, 13 Oct 2021 16:11:32 -0700 Subject: [PATCH 2/5] Respond to feedback --- .../tools/SdkTasks/PublishSignedAssets.proj | 10 ++- .../src/AzureDevOpsArtifactFeed.cs | 55 ++++++++++++++ .../src/AzureDevOpsFeedPermission.cs | 18 +++++ .../src/AzureDevOpsFeedView.cs | 13 ++++ .../src/CreateAzureDevOpsFeed.cs | 76 ++----------------- 5 files changed, 98 insertions(+), 74 deletions(-) create mode 100644 src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs create mode 100644 src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedPermission.cs create mode 100644 src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs diff --git a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj index 42a1b05135c..2f4d18b3c10 100644 --- a/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj +++ b/src/Microsoft.DotNet.Arcade.Sdk/tools/SdkTasks/PublishSignedAssets.proj @@ -32,9 +32,13 @@ netcoreapp3.1 Publish dnceng - internal - public - + internal + public + + + + /// Represents the body of a request sent when creating a new feed. + /// + /// > + /// When creating a new feed, we want to set up permissions based on the org and project. + /// Right now, only dnceng's public and internal projects are supported. + /// New feeds automatically get the feed administrators and project collection administrators as owners, + /// but we want to automatically add some additional permissions so that the build services can push to them, + /// and aadTenant users can read from them. + /// + public class AzureDevOpsArtifactFeed + { + public AzureDevOpsArtifactFeed(string name, string organization, string project) + { + Name = name; + switch (organization) + { + case "dnceng": + switch (project) + { + case "public": + case "internal": + Permissions = new List + { + // Project Collection Build Service + new AzDoFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:7ea9116e-9fac-403d-b258-b31fcf1bb293", "contributor"), + // internal Build Service + new AzDoFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", "contributor"), + // Project administrators + new AzDoFeedPermission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", "administrator"), + }; + break; + default: + throw new NotImplementedException($"Project '{project}' within organization '{organization}' contains no feed permissions information."); + } + break; + default: + throw new NotImplementedException($"Organization '{organization}' contains no feed permissions information."); + + } + } + + public string Name { get; set; } + + public List Permissions { get; private set; } + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedPermission.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedPermission.cs new file mode 100644 index 00000000000..f081ee5eb8e --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedPermission.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.Build.Tasks.Feed +{ + public class AzureDevOpsFeedPermission + { + public AzureDevOpsFeedPermission(string identityDescriptor, string role) + { + IdentityDescriptor = identityDescriptor; + Role = role; + } + + public string IdentityDescriptor { get; set; } + + public string Role { get; set; } + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs new file mode 100644 index 00000000000..5e9b41ecd45 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.Build.Tasks.Feed +{ + /// + /// Represents a feed view + /// + public class AzureDevOpsFeedView + { + public string Visibility { get; set; } + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs index 7f13c891f39..2f68b103ef1 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs @@ -58,6 +58,8 @@ public class CreateAzureDevOpsFeed : MSBuild.Task public string AzureDevOpsFeedsApiVersion { get; set; } = "5.1-preview.1"; + public string LocalViewVisibility { get; set; } = "collection"; + /// /// Number of characters from the commit SHA prefix that should be included in the feed name. /// @@ -146,9 +148,9 @@ private async Task ExecuteAsync() baseFeedName = versionedFeedName; // Now update the 'Local' feed view with aad tenant visibility. - var feedViewVisibilityPatch = new FeedView() + var feedViewVisibilityPatch = new AzureDevOpsFeedView() { - Visibility = "collection" + Visibility = LocalViewVisibility }; string patchBody = JsonConvert.SerializeObject(feedViewVisibilityPatch, _serializerSettings); @@ -175,7 +177,7 @@ private async Task ExecuteAsync() if (versionedFeedName.Length > MaxLengthForAzDoFeedNames) { - Console.WriteLine($"The name of the new feed ({baseFeedName}) exceeds the maximum feed name size of 64 chars. Aborting feed creation."); + Log.LogError($"The name of the new feed ({baseFeedName}) exceeds the maximum feed name size of 64 chars. Aborting feed creation."); return false; } } @@ -225,72 +227,4 @@ private string GetFeedVisibilityTag(string organization, string project) } } } - - public class Permission - { - public Permission(string identityDescriptor, string role) - { - IdentityDescriptor = identityDescriptor; - Role = role; - } - - public string IdentityDescriptor { get; set; } - - public string Role { get; set; } - } - - /// - /// Represents a feed view - /// - public class FeedView - { - public string Visibility { get; set; } - } - - /// - /// Represents the body of a request sent when creating a new feed. - /// - /// > - /// When creating a new feed, we want to set up permissions based on the org and project. - /// Right now, only dnceng's public and internal projects are supported. - /// New feeds automatically get the feed administrators and project collection administrators as owners, - /// but we want to automatically add some additional permissions so that the build services can push to them, - /// and aadTenant users can read from them. - /// - public class AzureDevOpsArtifactFeed - { - public AzureDevOpsArtifactFeed(string name, string organization, string project) - { - Name = name; - switch (organization) - { - case "dnceng": - switch (project) - { - case "public": - case "internal": - Permissions = new List - { - // Project Collection Build Service - new Permission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:7ea9116e-9fac-403d-b258-b31fcf1bb293", "contributor"), - // internal Build Service - new Permission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", "contributor"), - // Project administrators - new Permission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", "administrator"), - }; - break; - default: - throw new NotImplementedException($"Project '{project}' within organization '{organization}' contains no feed permissions information."); - } - break; - default: - throw new NotImplementedException($"Organization '{organization}' contains no feed permissions information."); - - } - } - - public string Name { get; set; } - - public List Permissions { get; private set; } - } } From 5028cce9b1d42213b88bf14933a8a8d6b452a7ba Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Wed, 13 Oct 2021 16:22:43 -0700 Subject: [PATCH 3/5] Respond to feedback --- .../src/AzureDevOpsArtifactFeed.cs | 12 ++++++------ .../src/AzureDevOpsFeedView.cs | 2 +- .../src/AzureDevOpsFeedVisibility.cs | 13 +++++++++++++ .../src/CreateAzureDevOpsFeed.cs | 2 +- 4 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedVisibility.cs diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs index 27d4d893e48..03c04387456 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -28,14 +28,14 @@ public AzureDevOpsArtifactFeed(string name, string organization, string project) { case "public": case "internal": - Permissions = new List + Permissions = new List { // Project Collection Build Service - new AzDoFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:7ea9116e-9fac-403d-b258-b31fcf1bb293", "contributor"), + new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:7ea9116e-9fac-403d-b258-b31fcf1bb293", "contributor"), // internal Build Service - new AzDoFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", "contributor"), + new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", "contributor"), // Project administrators - new AzDoFeedPermission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", "administrator"), + new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", "administrator"), }; break; default: @@ -50,6 +50,6 @@ public AzureDevOpsArtifactFeed(string name, string organization, string project) public string Name { get; set; } - public List Permissions { get; private set; } + public List Permissions { get; private set; } } } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs index 5e9b41ecd45..b12d49d5963 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs @@ -8,6 +8,6 @@ namespace Microsoft.DotNet.Build.Tasks.Feed /// public class AzureDevOpsFeedView { - public string Visibility { get; set; } + public AzureDevOpsFeedVisibility Visibility { get; set; } } } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedVisibility.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedVisibility.cs new file mode 100644 index 00000000000..809973b94b6 --- /dev/null +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedVisibility.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.DotNet.Build.Tasks.Feed +{ + public enum AzureDevOpsFeedVisibility + { + collection, + aadTenant, + @private, + none + } +} diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs index 2f68b103ef1..629294a39db 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs @@ -150,7 +150,7 @@ private async Task ExecuteAsync() // Now update the 'Local' feed view with aad tenant visibility. var feedViewVisibilityPatch = new AzureDevOpsFeedView() { - Visibility = LocalViewVisibility + Visibility = AzureDevOpsFeedVisibility.collection }; string patchBody = JsonConvert.SerializeObject(feedViewVisibilityPatch, _serializerSettings); From 85411fa659c233c635022eb1283eec9f8aa2f02e Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Mon, 18 Oct 2021 09:31:17 -0700 Subject: [PATCH 4/5] Switch back to setting org feed permissions explicitly instead of through a view --- .../src/AzureDevOpsArtifactFeed.cs | 14 ++++++++++- .../src/AzureDevOpsFeedView.cs | 13 ---------- .../src/AzureDevOpsFeedVisibility.cs | 13 ---------- .../src/CreateAzureDevOpsFeed.cs | 25 +++---------------- 4 files changed, 16 insertions(+), 49 deletions(-) delete mode 100644 src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs delete mode 100644 src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedVisibility.cs diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs index 03c04387456..200b8adcbe3 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsArtifactFeed.cs @@ -14,7 +14,17 @@ namespace Microsoft.DotNet.Build.Tasks.Feed /// Right now, only dnceng's public and internal projects are supported. /// New feeds automatically get the feed administrators and project collection administrators as owners, /// but we want to automatically add some additional permissions so that the build services can push to them, - /// and aadTenant users can read from them. + /// and organization users can read from them. + /// + /// Note that there are two ways of providing read access to the feed: + /// 1. Providing explicit access in the permissions list to the "Project Collection Valid Users" + /// 2. Updating the Local feed view to allow 'collection' users access. + /// + /// The second is probably preferrable from a an AzDO pattern and usage standpoint. BUT the AzDO API has a drawback where + /// the create feed operation cannot create the local view with the appropriate access. Instead, it must be updated after the + /// feed is created. This would be fine except that updating a feed's permissions requires administrative permissions, while + /// creating a feed only requires contributor permissions. This would require passing around a PAT with management permissions, + /// instead of just r/w permissions, which is not ideal. /// public class AzureDevOpsArtifactFeed { @@ -36,6 +46,8 @@ public AzureDevOpsArtifactFeed(string name, string organization, string project) new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.ServiceIdentity;116cce53-b859-4624-9a95-934af41eccef:Build:b55de4ed-4b5a-4215-a8e4-0a0a5f71e7d8", "contributor"), // Project administrators new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-1349140002-2196814402-2899064621-3782482097-0-0-0-0-1", "administrator"), + // Project Collection value users (see class comment for info) + new AzureDevOpsFeedPermission("Microsoft.TeamFoundation.Identity;S-1-9-1551374245-3991166389-1514870082-2833517066-1601300440-0-0-0-0-3", "reader"), }; break; default: diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs deleted file mode 100644 index b12d49d5963..00000000000 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedView.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Build.Tasks.Feed -{ - /// - /// Represents a feed view - /// - public class AzureDevOpsFeedView - { - public AzureDevOpsFeedVisibility Visibility { get; set; } - } -} diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedVisibility.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedVisibility.cs deleted file mode 100644 index 809973b94b6..00000000000 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/AzureDevOpsFeedVisibility.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.DotNet.Build.Tasks.Feed -{ - public enum AzureDevOpsFeedVisibility - { - collection, - aadTenant, - @private, - none - } -} diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs index 629294a39db..49032c55350 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/CreateAzureDevOpsFeed.cs @@ -147,28 +147,9 @@ private async Task ExecuteAsync() needsUniqueName = false; baseFeedName = versionedFeedName; - // Now update the 'Local' feed view with aad tenant visibility. - var feedViewVisibilityPatch = new AzureDevOpsFeedView() - { - Visibility = AzureDevOpsFeedVisibility.collection - }; - - string patchBody = JsonConvert.SerializeObject(feedViewVisibilityPatch, _serializerSettings); - - // Note that Framework doesn't natively have Patch -#if NETFRAMEWORK - HttpMethod patchMethod = new HttpMethod("PATCH"); -#else - HttpMethod patchMethod = HttpMethod.Patch; -#endif - using HttpRequestMessage patchFeedViewMessage = new HttpRequestMessage(patchMethod, $"{AzureDevOpsProject}/_apis/packaging/feeds/{baseFeedName}/views/Local"); - patchFeedViewMessage.Content = new StringContent(patchBody, Encoding.UTF8, "application/json"); - using HttpResponseMessage patchFeedViewResponse = await client.SendAsync(patchFeedViewMessage); - - if (patchFeedViewResponse.StatusCode != HttpStatusCode.OK) - { - throw new Exception($"Feed view 'Local' for '{baseFeedName}' could not be updated to have aadTenant visibility. Exception: {await patchFeedViewResponse.Content.ReadAsStringAsync()}"); - } + /// This is where we would potentially update the Local feed view with permissions to the organization's + /// valid users. But, see for more info on why this is not + /// done this way. } else if (createFeedResponse.StatusCode == HttpStatusCode.Conflict) { From f4da5c3e8c9f9d082140c9e7e519d87bb8e3d7cd Mon Sep 17 00:00:00 2001 From: Matt Mitchell Date: Tue, 19 Oct 2021 15:33:38 -0700 Subject: [PATCH 5/5] Fix isolated feed issue When the new target feed specifications were created, they collapsed down the feed specs for shipping and non-shipping package asset selection in cases where the target would be the same feed. This subtlety was required though, since in case of a stable build, we want just the shipping packages to go to a separate, newly created feed. The current code correct substitutes this if the TargetFeedSpecifications are broken out for ShippingOnly and NonShippingOnly, but there are a number of channels that did not break them out. Instead, it tried to send shipping packages to the isolated feed, and all packages to the non-isolated feed. To fix this, separate out the TargetFeedSpecifications and put a check into the constructor to ensure that this can't be done accidentally. Also - Do a little refactoring for clarity. - Add a test to ensure that this throws --- .../SetupTargetFeedConfigV3Tests.cs | 13 +++ .../src/model/PublishingConstants.cs | 21 ++-- .../src/model/SetupTargetFeedConfigV3.cs | 106 +++++++++++------- .../src/model/TargetChannelConfig.cs | 9 ++ 4 files changed, 101 insertions(+), 48 deletions(-) diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV3Tests.cs b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV3Tests.cs index dd11607ea0f..84d2a9d15e9 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV3Tests.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed.Tests/SetupTargetFeedConfigV3Tests.cs @@ -437,5 +437,18 @@ private bool AreEquivalent(List expectedItems, List new TargetFeedSpecification(new TargetFeedContentType[] { TargetFeedContentType.Package }, "FooFeed", AssetSelection.All); + shouldFail.Should().Throw(); + + Action shouldPassShippingOnly = () => new TargetFeedSpecification(new TargetFeedContentType[] { TargetFeedContentType.Package }, "FooFeed", AssetSelection.ShippingOnly); + shouldPassShippingOnly.Should().NotThrow(); + + Action shouldPassNonShippingOnly = () => new TargetFeedSpecification(new TargetFeedContentType[] { TargetFeedContentType.Package }, "FooFeed", AssetSelection.NonShippingOnly); + shouldPassNonShippingOnly.Should().NotThrow(); + } } } diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs index b4b55fe42ef..6d44e394605 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/PublishingConstants.cs @@ -155,7 +155,8 @@ public enum BuildQuality private static TargetFeedSpecification[] DotNet31BlazorFeeds = { - (TargetFeedContentType.Package, FeedDotNet31Blazor), + (TargetFeedContentType.Package, FeedDotNet31Blazor, AssetSelection.ShippingOnly), + (TargetFeedContentType.Package, FeedDotNet31Blazor, AssetSelection.NonShippingOnly), (InstallersAndSymbols, FeedInternalForInstallers), (TargetFeedContentType.Checksum, FeedInternalForChecksums), }; @@ -204,35 +205,40 @@ public enum BuildQuality private static TargetFeedSpecification[] DotNetEngFeeds = { - (TargetFeedContentType.Package, FeedDotNetEng), + (TargetFeedContentType.Package, FeedDotNetEng, AssetSelection.ShippingOnly), + (TargetFeedContentType.Package, FeedDotNetEng, AssetSelection.NonShippingOnly), (InstallersAndSymbols, FeedForInstallers), (TargetFeedContentType.Checksum, FeedForChecksums), }; private static TargetFeedSpecification[] DotNetToolsFeeds = { - (TargetFeedContentType.Package, FeedDotNetTools), + (TargetFeedContentType.Package, FeedDotNetTools, AssetSelection.ShippingOnly), + (TargetFeedContentType.Package, FeedDotNetTools, AssetSelection.NonShippingOnly), (InstallersAndSymbols, FeedForInstallers), (TargetFeedContentType.Checksum, FeedForChecksums), }; private static TargetFeedSpecification[] DotNetToolsInternalFeeds = { - (TargetFeedContentType.Package, FeedDotNetToolsInternal), + (TargetFeedContentType.Package, FeedDotNetToolsInternal, AssetSelection.ShippingOnly), + (TargetFeedContentType.Package, FeedDotNetToolsInternal, AssetSelection.NonShippingOnly), (InstallersAndSymbols, FeedInternalForInstallers), (TargetFeedContentType.Checksum, FeedInternalForChecksums), }; private static TargetFeedSpecification[] DotNetExperimentalFeeds = { - (TargetFeedContentType.Package, FeedDotNetExperimental), + (TargetFeedContentType.Package, FeedDotNetExperimental, AssetSelection.ShippingOnly), + (TargetFeedContentType.Package, FeedDotNetExperimental, AssetSelection.NonShippingOnly), (InstallersAndSymbols, FeedForInstallers), (TargetFeedContentType.Checksum, FeedForChecksums), }; private static TargetFeedSpecification[] GeneralTestingFeeds = { - (TargetFeedContentType.Package, FeedGeneralTesting), + (TargetFeedContentType.Package, FeedGeneralTesting, AssetSelection.ShippingOnly), + (TargetFeedContentType.Package, FeedGeneralTesting, AssetSelection.NonShippingOnly), (InstallersAndSymbols, FeedForInstallers), (TargetFeedContentType.Checksum, FeedForChecksums), (InstallersAndSymbols, FeedStagingForInstallers), @@ -241,7 +247,8 @@ public enum BuildQuality private static TargetFeedSpecification[] GeneralTestingInternalFeeds = { - (TargetFeedContentType.Package, FeedGeneralTestingInternal), + (TargetFeedContentType.Package, FeedGeneralTestingInternal, AssetSelection.ShippingOnly), + (TargetFeedContentType.Package, FeedGeneralTestingInternal, AssetSelection.NonShippingOnly), (InstallersAndSymbols, FeedInternalForInstallers), (TargetFeedContentType.Checksum, FeedInternalForChecksums), (InstallersAndSymbols, FeedStagingInternalForInstallers), diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/SetupTargetFeedConfigV3.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/SetupTargetFeedConfigV3.cs index 727a4cbe285..be97cf2f605 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/SetupTargetFeedConfigV3.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/SetupTargetFeedConfigV3.cs @@ -88,48 +88,14 @@ public override List Setup() private IEnumerable Feeds() { + // If the build is stable, we need to create two new feeds (if not provided) + // that can contain stable packages. These packages cannot be pushed to the normal + // feeds specified in the feed config because it would mean pushing the same package more than once + // to the same feed on successive builds, which is not allowed. if (IsStableBuild) { - if (string.IsNullOrEmpty(StablePackagesFeed)) - { - var packagesFeedTask = new CreateAzureDevOpsFeed() - { - BuildEngine = BuildEngine, - AzureDevOpsOrg = AzureDevOpsOrg, - AzureDevOpsProject = IsInternalBuild ? "internal" : "public", - AzureDevOpsPersonalAccessToken = AzureDevOpsFeedsKey, - RepositoryName = RepositoryName, - CommitSha = CommitSha - }; - - if (!packagesFeedTask.Execute()) - { - throw new Exception($"Problems creating an AzureDevOps feed for repository '{RepositoryName}' and commit '{CommitSha}'."); - } - - StablePackagesFeed = packagesFeedTask.TargetFeedURL; - } - - if (string.IsNullOrEmpty(StableSymbolsFeed)) - { - var symbolsFeedTask = new CreateAzureDevOpsFeed() - { - BuildEngine = BuildEngine, - AzureDevOpsOrg = AzureDevOpsOrg, - AzureDevOpsProject = IsInternalBuild ? "internal" : "public", - AzureDevOpsPersonalAccessToken = AzureDevOpsFeedsKey, - RepositoryName = RepositoryName, - CommitSha = CommitSha, - ContentIdentifier = "sym" - }; - - if (!symbolsFeedTask.Execute()) - { - throw new Exception($"Problems creating an AzureDevOps (symbols) feed for repository '{RepositoryName}' and commit '{CommitSha}'."); - } - - StableSymbolsFeed = symbolsFeedTask.TargetFeedURL; - } + CreateStablePackagesFeedIfNeeded(); + CreateStableSymbolsFeedIfNeeded(); yield return new TargetFeedConfig( TargetFeedContentType.Package, @@ -156,6 +122,7 @@ private IEnumerable Feeds() filenamesToExclude: FilesToExclude, flatten: Flatten); } + foreach (var spec in _targetChannelConfig.TargetFeeds) { foreach (var type in spec.ContentTypes) @@ -167,9 +134,11 @@ private IEnumerable Feeds() continue; } } + + // If dealing with a stable build, the package feed targeted for shipping packages and symbols + // should be skipped, as it is added above. if (IsStableBuild && ((type is TargetFeedContentType.Package && spec.Assets == AssetSelection.ShippingOnly) || type is TargetFeedContentType.Symbols)) { - // stable build shipping packages and symbols were handled above continue; } @@ -217,6 +186,61 @@ private IEnumerable Feeds() } } + /// + /// Create the stable symbol packages feed if one is not already explicitly provided + /// + /// Throws if the feed cannot be created + private void CreateStableSymbolsFeedIfNeeded() + { + if (string.IsNullOrEmpty(StableSymbolsFeed)) + { + var symbolsFeedTask = new CreateAzureDevOpsFeed() + { + BuildEngine = BuildEngine, + AzureDevOpsOrg = AzureDevOpsOrg, + AzureDevOpsProject = IsInternalBuild ? "internal" : "public", + AzureDevOpsPersonalAccessToken = AzureDevOpsFeedsKey, + RepositoryName = RepositoryName, + CommitSha = CommitSha, + ContentIdentifier = "sym" + }; + + if (!symbolsFeedTask.Execute()) + { + throw new Exception($"Problems creating an AzureDevOps (symbols) feed for repository '{RepositoryName}' and commit '{CommitSha}'."); + } + + StableSymbolsFeed = symbolsFeedTask.TargetFeedURL; + } + } + + /// + /// Create the stable packages feed if one is not already explicitly provided + /// + /// Throws if the feed cannot be created + private void CreateStablePackagesFeedIfNeeded() + { + if (string.IsNullOrEmpty(StablePackagesFeed)) + { + var packagesFeedTask = new CreateAzureDevOpsFeed() + { + BuildEngine = BuildEngine, + AzureDevOpsOrg = AzureDevOpsOrg, + AzureDevOpsProject = IsInternalBuild ? "internal" : "public", + AzureDevOpsPersonalAccessToken = AzureDevOpsFeedsKey, + RepositoryName = RepositoryName, + CommitSha = CommitSha + }; + + if (!packagesFeedTask.Execute()) + { + throw new Exception($"Problems creating an AzureDevOps feed for repository '{RepositoryName}' and commit '{CommitSha}'."); + } + + StablePackagesFeed = packagesFeedTask.TargetFeedURL; + } + } + private string GetFeedOverride(string feed) { foreach (var prefix in FeedOverrides.Keys) diff --git a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/TargetChannelConfig.cs b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/TargetChannelConfig.cs index dced737bc3f..18e82775783 100644 --- a/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/TargetChannelConfig.cs +++ b/src/Microsoft.DotNet.Build.Tasks.Feed/src/model/TargetChannelConfig.cs @@ -153,6 +153,15 @@ public static implicit operator TargetFeedSpecification((TargetFeedContentType t public TargetFeedSpecification(IEnumerable contentTypes, string feedUrl, AssetSelection assets) { + // A feed targeted for content type 'Package' may not have asset selection 'All'. + // During TargetFeedConfig creation, the default feed spec for shipping packages will be ignored and replaced with + // a separate target feed config. + + if (assets == AssetSelection.All && contentTypes.Contains(TargetFeedContentType.Package)) + { + throw new ArgumentException($"Target feed specification for {feedUrl} must have a separated asset selection 'ShippingOnly' and 'NonShippingOnly packages"); + } + ContentTypes = contentTypes.ToImmutableList(); FeedUrl = feedUrl; Assets = assets;